ClassPath.java
- /*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- package org.apache.bcel.util;
- import java.io.Closeable;
- import java.io.DataInputStream;
- import java.io.File;
- import java.io.FileInputStream;
- import java.io.FilenameFilter;
- import java.io.IOException;
- import java.io.InputStream;
- import java.net.MalformedURLException;
- import java.net.URL;
- import java.nio.file.Files;
- import java.nio.file.Path;
- import java.nio.file.Paths;
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.Enumeration;
- import java.util.List;
- import java.util.Locale;
- import java.util.Objects;
- import java.util.StringTokenizer;
- import java.util.Vector;
- import java.util.stream.Collectors;
- import java.util.zip.ZipEntry;
- import java.util.zip.ZipFile;
- import org.apache.bcel.classfile.JavaClass;
- import org.apache.bcel.classfile.Utility;
- import org.apache.commons.lang3.SystemProperties;
- /**
- * Loads class files from the CLASSPATH. Inspired by sun.tools.ClassPath.
- */
- public class ClassPath implements Closeable {
- private abstract static class AbstractPathEntry implements Closeable {
- abstract ClassFile getClassFile(String name, String suffix);
- abstract URL getResource(String name);
- abstract InputStream getResourceAsStream(String name);
- }
- private abstract static class AbstractZip extends AbstractPathEntry {
- private final ZipFile zipFile;
- AbstractZip(final ZipFile zipFile) {
- this.zipFile = Objects.requireNonNull(zipFile, "zipFile");
- }
- @Override
- public void close() throws IOException {
- if (zipFile != null) {
- zipFile.close();
- }
- }
- @Override
- ClassFile getClassFile(final String name, final String suffix) {
- final ZipEntry entry = zipFile.getEntry(toEntryName(name, suffix));
- if (entry == null) {
- return null;
- }
- return new ClassFile() {
- @Override
- public String getBase() {
- return zipFile.getName();
- }
- @Override
- public InputStream getInputStream() throws IOException {
- return zipFile.getInputStream(entry);
- }
- @Override
- public String getPath() {
- return entry.toString();
- }
- @Override
- public long getSize() {
- return entry.getSize();
- }
- @Override
- public long getTime() {
- return entry.getTime();
- }
- };
- }
- @Override
- URL getResource(final String name) {
- final ZipEntry entry = zipFile.getEntry(name);
- try {
- return entry != null ? new URL("jar:file:" + zipFile.getName() + "!/" + name) : null;
- } catch (final MalformedURLException e) {
- return null;
- }
- }
- @Override
- InputStream getResourceAsStream(final String name) {
- final ZipEntry entry = zipFile.getEntry(name);
- try {
- return entry != null ? zipFile.getInputStream(entry) : null;
- } catch (final IOException e) {
- return null;
- }
- }
- protected abstract String toEntryName(final String name, final String suffix);
- @Override
- public String toString() {
- return zipFile.getName();
- }
- }
- /**
- * Contains information about file/ZIP entry of the Java class.
- */
- public interface ClassFile {
- /**
- * @return base path of found class, i.e. class is contained relative to that path, which may either denote a directory,
- * or ZIP file
- */
- String getBase();
- /**
- * @return input stream for class file.
- * @throws IOException if an I/O error occurs.
- */
- InputStream getInputStream() throws IOException;
- /**
- * @return canonical path to class file.
- */
- String getPath();
- /**
- * @return size of class file.
- */
- long getSize();
- /**
- * @return modification time of class file.
- */
- long getTime();
- }
- private static final class Dir extends AbstractPathEntry {
- private final String dir;
- Dir(final String d) {
- dir = d;
- }
- @Override
- public void close() throws IOException {
- // Nothing to do
- }
- @Override
- ClassFile getClassFile(final String name, final String suffix) {
- final File file = new File(dir + File.separatorChar + name.replace('.', File.separatorChar) + suffix);
- return file.exists() ? new ClassFile() {
- @Override
- public String getBase() {
- return dir;
- }
- @Override
- public InputStream getInputStream() throws IOException {
- return new FileInputStream(file);
- }
- @Override
- public String getPath() {
- try {
- return file.getCanonicalPath();
- } catch (final IOException e) {
- return null;
- }
- }
- @Override
- public long getSize() {
- return file.length();
- }
- @Override
- public long getTime() {
- return file.lastModified();
- }
- } : null;
- }
- @Override
- URL getResource(final String name) {
- // Resource specification uses '/' whatever the platform
- final File file = toFile(name);
- try {
- return file.exists() ? file.toURI().toURL() : null;
- } catch (final MalformedURLException e) {
- return null;
- }
- }
- @Override
- InputStream getResourceAsStream(final String name) {
- // Resource specification uses '/' whatever the platform
- final File file = toFile(name);
- try {
- return file.exists() ? new FileInputStream(file) : null;
- } catch (final IOException e) {
- return null;
- }
- }
- private File toFile(final String name) {
- return new File(dir + File.separatorChar + name.replace('/', File.separatorChar));
- }
- @Override
- public String toString() {
- return dir;
- }
- }
- private static final class Jar extends AbstractZip {
- Jar(final ZipFile zip) {
- super(zip);
- }
- @Override
- protected String toEntryName(final String name, final String suffix) {
- return Utility.packageToPath(name) + suffix;
- }
- }
- private static final class JrtModule extends AbstractPathEntry {
- private final Path modulePath;
- public JrtModule(final Path modulePath) {
- this.modulePath = Objects.requireNonNull(modulePath, "modulePath");
- }
- @Override
- public void close() throws IOException {
- // Nothing to do.
- }
- @Override
- ClassFile getClassFile(final String name, final String suffix) {
- final Path resolved = modulePath.resolve(Utility.packageToPath(name) + suffix);
- if (Files.exists(resolved)) {
- return new ClassFile() {
- @Override
- public String getBase() {
- return Objects.toString(resolved.getFileName(), null);
- }
- @Override
- public InputStream getInputStream() throws IOException {
- return Files.newInputStream(resolved);
- }
- @Override
- public String getPath() {
- return resolved.toString();
- }
- @Override
- public long getSize() {
- try {
- return Files.size(resolved);
- } catch (final IOException e) {
- return 0;
- }
- }
- @Override
- public long getTime() {
- try {
- return Files.getLastModifiedTime(resolved).toMillis();
- } catch (final IOException e) {
- return 0;
- }
- }
- };
- }
- return null;
- }
- @Override
- URL getResource(final String name) {
- final Path resovled = modulePath.resolve(name);
- try {
- return Files.exists(resovled) ? new URL("jrt:" + modulePath + "/" + name) : null;
- } catch (final MalformedURLException e) {
- return null;
- }
- }
- @Override
- InputStream getResourceAsStream(final String name) {
- try {
- return Files.newInputStream(modulePath.resolve(name));
- } catch (final IOException e) {
- return null;
- }
- }
- @Override
- public String toString() {
- return modulePath.toString();
- }
- }
- private static final class JrtModules extends AbstractPathEntry {
- private final ModularRuntimeImage modularRuntimeImage;
- private final JrtModule[] modules;
- public JrtModules(final String path) throws IOException {
- this.modularRuntimeImage = new ModularRuntimeImage();
- this.modules = modularRuntimeImage.list(path).stream().map(JrtModule::new).toArray(JrtModule[]::new);
- }
- @Override
- public void close() throws IOException {
- if (modules != null) {
- // don't use a for each loop to avoid creating an iterator for the GC to collect.
- for (final JrtModule module : modules) {
- module.close();
- }
- }
- if (modularRuntimeImage != null) {
- modularRuntimeImage.close();
- }
- }
- @Override
- ClassFile getClassFile(final String name, final String suffix) {
- // don't use a for each loop to avoid creating an iterator for the GC to collect.
- for (final JrtModule module : modules) {
- final ClassFile classFile = module.getClassFile(name, suffix);
- if (classFile != null) {
- return classFile;
- }
- }
- return null;
- }
- @Override
- URL getResource(final String name) {
- // don't use a for each loop to avoid creating an iterator for the GC to collect.
- for (final JrtModule module : modules) {
- final URL url = module.getResource(name);
- if (url != null) {
- return url;
- }
- }
- return null;
- }
- @Override
- InputStream getResourceAsStream(final String name) {
- // don't use a for each loop to avoid creating an iterator for the GC to collect.
- for (final JrtModule module : modules) {
- final InputStream inputStream = module.getResourceAsStream(name);
- if (inputStream != null) {
- return inputStream;
- }
- }
- return null;
- }
- @Override
- public String toString() {
- return Arrays.toString(modules);
- }
- }
- private static final class Module extends AbstractZip {
- Module(final ZipFile zip) {
- super(zip);
- }
- @Override
- protected String toEntryName(final String name, final String suffix) {
- return "classes/" + Utility.packageToPath(name) + suffix;
- }
- }
- private static final FilenameFilter ARCHIVE_FILTER = (dir, name) -> {
- name = name.toLowerCase(Locale.ENGLISH);
- return name.endsWith(".zip") || name.endsWith(".jar");
- };
- private static final FilenameFilter MODULES_FILTER = (dir, name) -> {
- name = name.toLowerCase(Locale.ENGLISH);
- return name.endsWith(org.apache.bcel.classfile.Module.EXTENSION);
- };
- public static final ClassPath SYSTEM_CLASS_PATH = new ClassPath(getClassPath());
- private static void addJdkModules(final String javaHome, final List<String> list) {
- String modulesPath = System.getProperty("java.modules.path");
- if (modulesPath == null || modulesPath.trim().isEmpty()) {
- // Default to looking in JAVA_HOME/jmods
- modulesPath = javaHome + File.separator + "jmods";
- }
- final File modulesDir = new File(modulesPath);
- if (modulesDir.exists()) {
- final String[] modules = modulesDir.list(MODULES_FILTER);
- if (modules != null) {
- for (final String module : modules) {
- list.add(modulesDir.getPath() + File.separatorChar + module);
- }
- }
- }
- }
- /**
- * Checks for class path components in the following properties: "java.class.path", "sun.boot.class.path",
- * "java.ext.dirs"
- *
- * @return class path as used by default by BCEL
- */
- // @since 6.0 no longer final
- public static String getClassPath() {
- final String classPathProp = SystemProperties.getJavaClassPath();
- final String bootClassPathProp = System.getProperty("sun.boot.class.path");
- final String extDirs = SystemProperties.getJavaExtDirs();
- // System.out.println("java.version = " + System.getProperty("java.version"));
- // System.out.println("java.class.path = " + classPathProp);
- // System.out.println("sun.boot.class.path=" + bootClassPathProp);
- // System.out.println("java.ext.dirs=" + extDirs);
- final String javaHome = SystemProperties.getJavaHome();
- final List<String> list = new ArrayList<>();
- // Starting in JRE 9, .class files are in the modules directory. Add them to the path.
- final Path modulesPath = Paths.get(javaHome).resolve("lib/modules");
- if (Files.exists(modulesPath) && Files.isRegularFile(modulesPath)) {
- list.add(modulesPath.toAbsolutePath().toString());
- }
- // Starting in JDK 9, .class files are in the jmods directory. Add them to the path.
- addJdkModules(javaHome, list);
- getPathComponents(classPathProp, list);
- getPathComponents(bootClassPathProp, list);
- final List<String> dirs = new ArrayList<>();
- getPathComponents(extDirs, dirs);
- for (final String d : dirs) {
- final File extDir = new File(d);
- final String[] extensions = extDir.list(ARCHIVE_FILTER);
- if (extensions != null) {
- for (final String extension : extensions) {
- list.add(extDir.getPath() + File.separatorChar + extension);
- }
- }
- }
- return list.stream().collect(Collectors.joining(File.pathSeparator));
- }
- private static void getPathComponents(final String path, final List<String> list) {
- if (path != null) {
- final StringTokenizer tokenizer = new StringTokenizer(path, File.pathSeparator);
- while (tokenizer.hasMoreTokens()) {
- final String name = tokenizer.nextToken();
- final File file = new File(name);
- if (file.exists()) {
- list.add(name);
- }
- }
- }
- }
- private final String classPathString;
- private final ClassPath parent;
- private final List<AbstractPathEntry> paths;
- /**
- * Search for classes in CLASSPATH.
- *
- * @deprecated Use SYSTEM_CLASS_PATH constant
- */
- @Deprecated
- public ClassPath() {
- this(getClassPath());
- }
- @SuppressWarnings("resource")
- public ClassPath(final ClassPath parent, final String classPathString) {
- this.parent = parent;
- this.classPathString = Objects.requireNonNull(classPathString, "classPathString");
- this.paths = new ArrayList<>();
- for (final StringTokenizer tokenizer = new StringTokenizer(classPathString, File.pathSeparator); tokenizer.hasMoreTokens();) {
- final String path = tokenizer.nextToken();
- if (!path.isEmpty()) {
- final File file = new File(path);
- try {
- if (file.exists()) {
- if (file.isDirectory()) {
- paths.add(new Dir(path));
- } else if (path.endsWith(org.apache.bcel.classfile.Module.EXTENSION)) {
- paths.add(new Module(new ZipFile(file)));
- } else if (path.endsWith(ModularRuntimeImage.MODULES_PATH)) {
- paths.add(new JrtModules(ModularRuntimeImage.MODULES_PATH));
- } else {
- paths.add(new Jar(new ZipFile(file)));
- }
- }
- } catch (final IOException e) {
- if (path.endsWith(".zip") || path.endsWith(".jar")) {
- System.err.println("CLASSPATH component " + file + ": " + e);
- }
- }
- }
- }
- }
- /**
- * Search for classes in given path.
- *
- * @param classPath
- */
- public ClassPath(final String classPath) {
- this(null, classPath);
- }
- @Override
- public void close() throws IOException {
- for (final AbstractPathEntry path : paths) {
- path.close();
- }
- }
- @Override
- public boolean equals(final Object obj) {
- if (this == obj) {
- return true;
- }
- if (obj == null) {
- return false;
- }
- if (getClass() != obj.getClass()) {
- return false;
- }
- final ClassPath other = (ClassPath) obj;
- return Objects.equals(classPathString, other.classPathString);
- }
- /**
- * @param name fully qualified file name, e.g. java/lang/String
- * @return byte array for class
- * @throws IOException if an I/O error occurs.
- */
- public byte[] getBytes(final String name) throws IOException {
- return getBytes(name, JavaClass.EXTENSION);
- }
- /**
- * @param name fully qualified file name, e.g. java/lang/String
- * @param suffix file name ends with suffix, e.g. .java
- * @return byte array for file on class path
- * @throws IOException if an I/O error occurs.
- */
- public byte[] getBytes(final String name, final String suffix) throws IOException {
- DataInputStream dis = null;
- try (InputStream inputStream = getInputStream(name, suffix)) {
- if (inputStream == null) {
- throw new IOException("Couldn't find: " + name + suffix);
- }
- dis = new DataInputStream(inputStream);
- final byte[] bytes = new byte[inputStream.available()];
- dis.readFully(bytes);
- return bytes;
- } finally {
- if (dis != null) {
- dis.close();
- }
- }
- }
- /**
- * @param name fully qualified class name, e.g. java.lang.String
- * @return input stream for class
- * @throws IOException if an I/O error occurs.
- */
- public ClassFile getClassFile(final String name) throws IOException {
- return getClassFile(name, JavaClass.EXTENSION);
- }
- /**
- * @param name fully qualified file name, e.g. java/lang/String
- * @param suffix file name ends with suff, e.g. .java
- * @return class file for the Java class
- * @throws IOException if an I/O error occurs.
- */
- public ClassFile getClassFile(final String name, final String suffix) throws IOException {
- ClassFile cf = null;
- if (parent != null) {
- cf = parent.getClassFileInternal(name, suffix);
- }
- if (cf == null) {
- cf = getClassFileInternal(name, suffix);
- }
- if (cf != null) {
- return cf;
- }
- throw new IOException("Couldn't find: " + name + suffix);
- }
- private ClassFile getClassFileInternal(final String name, final String suffix) {
- for (final AbstractPathEntry path : paths) {
- final ClassFile cf = path.getClassFile(name, suffix);
- if (cf != null) {
- return cf;
- }
- }
- return null;
- }
- /**
- * Gets an InputStream.
- * <p>
- * The caller is responsible for closing the InputStream.
- * </p>
- * @param name fully qualified class name, e.g. java.lang.String
- * @return input stream for class
- * @throws IOException if an I/O error occurs.
- */
- public InputStream getInputStream(final String name) throws IOException {
- return getInputStream(Utility.packageToPath(name), JavaClass.EXTENSION);
- }
- /**
- * Gets an InputStream for a class or resource on the classpath.
- * <p>
- * The caller is responsible for closing the InputStream.
- * </p>
- *
- * @param name fully qualified file name, e.g. java/lang/String
- * @param suffix file name ends with suff, e.g. .java
- * @return input stream for file on class path
- * @throws IOException if an I/O error occurs.
- */
- public InputStream getInputStream(final String name, final String suffix) throws IOException {
- try {
- final java.lang.ClassLoader classLoader = getClass().getClassLoader();
- @SuppressWarnings("resource") // closed by caller
- final
- InputStream inputStream = classLoader == null ? null : classLoader.getResourceAsStream(name + suffix);
- if (inputStream != null) {
- return inputStream;
- }
- } catch (final Exception ignored) {
- // ignored
- }
- return getClassFile(name, suffix).getInputStream();
- }
- /**
- * @param name name of file to search for, e.g. java/lang/String.java
- * @return full (canonical) path for file
- * @throws IOException if an I/O error occurs.
- */
- public String getPath(String name) throws IOException {
- final int index = name.lastIndexOf('.');
- String suffix = "";
- if (index > 0) {
- suffix = name.substring(index);
- name = name.substring(0, index);
- }
- return getPath(name, suffix);
- }
- /**
- * @param name name of file to search for, e.g. java/lang/String
- * @param suffix file name suffix, e.g. .java
- * @return full (canonical) path for file, if it exists
- * @throws IOException if an I/O error occurs.
- */
- public String getPath(final String name, final String suffix) throws IOException {
- return getClassFile(name, suffix).getPath();
- }
- /**
- * @param name fully qualified resource name, e.g. java/lang/String.class
- * @return URL supplying the resource, or null if no resource with that name.
- * @since 6.0
- */
- public URL getResource(final String name) {
- for (final AbstractPathEntry path : paths) {
- URL url;
- if ((url = path.getResource(name)) != null) {
- return url;
- }
- }
- return null;
- }
- /**
- * @param name fully qualified resource name, e.g. java/lang/String.class
- * @return InputStream supplying the resource, or null if no resource with that name.
- * @since 6.0
- */
- public InputStream getResourceAsStream(final String name) {
- for (final AbstractPathEntry path : paths) {
- InputStream is;
- if ((is = path.getResourceAsStream(name)) != null) {
- return is;
- }
- }
- return null;
- }
- /**
- * @param name fully qualified resource name, e.g. java/lang/String.class
- * @return An Enumeration of URLs supplying the resource, or an empty Enumeration if no resource with that name.
- * @since 6.0
- */
- public Enumeration<URL> getResources(final String name) {
- final Vector<URL> results = new Vector<>();
- for (final AbstractPathEntry path : paths) {
- URL url;
- if ((url = path.getResource(name)) != null) {
- results.add(url);
- }
- }
- return results.elements();
- }
- @Override
- public int hashCode() {
- return classPathString.hashCode();
- }
- /**
- * @return used class path string
- */
- @Override
- public String toString() {
- if (parent != null) {
- return parent + File.pathSeparator + classPathString;
- }
- return classPathString;
- }
- }