| /* |
| * 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 |
| * |
| * https://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.tools.ant; |
| |
| import java.io.ByteArrayOutputStream; |
| import java.io.Closeable; |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.lang.reflect.Constructor; |
| import java.net.MalformedURLException; |
| import java.net.URL; |
| import java.nio.file.Files; |
| import java.security.CodeSource; |
| import java.security.ProtectionDomain; |
| import java.security.cert.Certificate; |
| import java.util.Collections; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.Hashtable; |
| import java.util.Map; |
| import java.util.NoSuchElementException; |
| import java.util.Objects; |
| import java.util.StringTokenizer; |
| import java.util.Vector; |
| import java.util.jar.Attributes; |
| import java.util.jar.Attributes.Name; |
| import java.util.jar.JarEntry; |
| import java.util.jar.JarFile; |
| import java.util.jar.Manifest; |
| import java.util.stream.Collectors; |
| import java.util.stream.Stream; |
| import java.util.zip.ZipFile; |
| |
| import org.apache.tools.ant.launch.Locator; |
| import org.apache.tools.ant.types.Path; |
| import org.apache.tools.ant.util.FileUtils; |
| import org.apache.tools.ant.util.JavaEnvUtils; |
| import org.apache.tools.ant.util.LoaderUtils; |
| import org.apache.tools.ant.util.ReflectUtil; |
| import org.apache.tools.ant.util.StringUtils; |
| import org.apache.tools.ant.util.VectorSet; |
| import org.apache.tools.zip.ZipLong; |
| |
| /** |
| * Used to load classes within ant with a different classpath from |
| * that used to start ant. Note that it is possible to force a class |
| * into this loader even when that class is on the system classpath by |
| * using the forceLoadClass method. Any subsequent classes loaded by that |
| * class will then use this loader rather than the system class loader. |
| * |
| * <p> |
| * Note that this classloader has a feature to allow loading |
| * in reverse order and for "isolation". |
| * Due to the fact that a number of |
| * methods in java.lang.ClassLoader are final (at least |
| * in java 1.4 getResources) this means that the |
| * class has to fake the given parent. |
| * </p> |
| * |
| */ |
| public class AntClassLoader extends ClassLoader implements SubBuildListener, Closeable { |
| |
| private static final FileUtils FILE_UTILS = FileUtils.getFileUtils(); |
| |
| private static final boolean IS_ATLEAST_JAVA9 = JavaEnvUtils.isAtLeastJavaVersion(JavaEnvUtils.JAVA_9); |
| // constructs needed to create (via reflection) a java.util.jar.JarFile instance when Java runtime version is >= 9 |
| private static final Class[] MR_JARFILE_CTOR_ARGS; |
| private static final Object MR_JARFILE_CTOR_RUNTIME_VERSION_VAL; |
| |
| static { |
| registerAsParallelCapable(); |
| if (IS_ATLEAST_JAVA9) { |
| Class[] ctorArgs = null; |
| Object runtimeVersionVal = null; |
| try { |
| final Class<?> runtimeVersionClass = Class.forName("java.lang.Runtime$Version"); |
| ctorArgs = new Class[] {File.class, boolean.class, int.class, runtimeVersionClass}; |
| runtimeVersionVal = Runtime.class.getDeclaredMethod("version").invoke(null); |
| } catch (Exception e) { |
| // ignore - we consider this as multi-release jar unsupported |
| } |
| MR_JARFILE_CTOR_ARGS = ctorArgs; |
| MR_JARFILE_CTOR_RUNTIME_VERSION_VAL = runtimeVersionVal; |
| } else { |
| MR_JARFILE_CTOR_ARGS = null; |
| MR_JARFILE_CTOR_RUNTIME_VERSION_VAL = null; |
| } |
| } |
| |
| /** |
| * An enumeration of all resources of a given name found within the |
| * classpath of this class loader. This enumeration is used by the |
| * ClassLoader.findResources method, which is in |
| * turn used by the ClassLoader.getResources method. |
| * |
| * @see AntClassLoader#findResources(String) |
| * @see java.lang.ClassLoader#getResources(String) |
| */ |
| private class ResourceEnumeration implements Enumeration<URL> { |
| /** |
| * The name of the resource being searched for. |
| */ |
| private final String resourceName; |
| |
| /** |
| * The index of the next classpath element to search. |
| */ |
| private int pathElementsIndex; |
| |
| /** |
| * The URL of the next resource to return in the enumeration. If this |
| * field is <code>null</code> then the enumeration has been completed, |
| * i.e., there are no more elements to return. |
| */ |
| private URL nextResource; |
| |
| /** |
| * Constructs a new enumeration of resources of the given name found |
| * within this class loader's classpath. |
| * |
| * @param name the name of the resource to search for. |
| */ |
| ResourceEnumeration(final String name) { |
| this.resourceName = name; |
| this.pathElementsIndex = 0; |
| findNextResource(); |
| } |
| |
| /** |
| * Indicates whether there are more elements in the enumeration to |
| * return. |
| * |
| * @return <code>true</code> if there are more elements in the |
| * enumeration; <code>false</code> otherwise. |
| */ |
| public boolean hasMoreElements() { |
| return (this.nextResource != null); |
| } |
| |
| /** |
| * Returns the next resource in the enumeration. |
| * |
| * @return the next resource in the enumeration |
| */ |
| public URL nextElement() { |
| final URL ret = this.nextResource; |
| if (ret == null) { |
| throw new NoSuchElementException(); |
| } |
| findNextResource(); |
| return ret; |
| } |
| |
| /** |
| * Locates the next resource of the correct name in the classpath and |
| * sets <code>nextResource</code> to the URL of that resource. If no |
| * more resources can be found, <code>nextResource</code> is set to |
| * <code>null</code>. |
| */ |
| private void findNextResource() { |
| URL url = null; |
| while ((pathElementsIndex < pathComponents.size()) && (url == null)) { |
| try { |
| final File pathComponent = pathComponents.elementAt(pathElementsIndex); |
| url = getResourceURL(pathComponent, this.resourceName); |
| pathElementsIndex++; |
| } catch (final BuildException e) { |
| // ignore path elements which are not valid relative to the |
| // project |
| } |
| } |
| this.nextResource = url; |
| } |
| } |
| |
| /** |
| * The size of buffers to be used in this classloader. |
| */ |
| private static final int BUFFER_SIZE = 8192; |
| |
| /** |
| * Number of array elements in a test array of strings |
| */ |
| private static final int NUMBER_OF_STRINGS = 256; |
| |
| /** |
| * The components of the classpath that the classloader searches |
| * for classes. |
| */ |
| private final Vector<File> pathComponents = new VectorSet<>(); |
| |
| /** |
| * The project to which this class loader belongs. |
| */ |
| private Project project; |
| |
| /** |
| * Indicates whether the parent class loader should be |
| * consulted before trying to load with this class loader. |
| */ |
| private boolean parentFirst = true; |
| |
| /** |
| * These are the package roots that are to be loaded by the parent class |
| * loader regardless of whether the parent class loader is being searched |
| * first or not. |
| */ |
| private final Vector<String> systemPackages = new Vector<>(); |
| |
| /** |
| * These are the package roots that are to be loaded by this class loader |
| * regardless of whether the parent class loader is being searched first |
| * or not. |
| */ |
| private final Vector<String> loaderPackages = new Vector<>(); |
| |
| /** |
| * Whether or not this classloader will ignore the base |
| * classloader if it can't find a class. |
| * |
| * @see #setIsolated(boolean) |
| */ |
| private boolean ignoreBase = false; |
| |
| /** |
| * The parent class loader, if one is given or can be determined. |
| */ |
| private ClassLoader parent = null; |
| |
| /** |
| * A hashtable of zip files opened by the classloader (File to JarFile). |
| */ |
| private Hashtable<File, JarFile> jarFiles = new Hashtable<>(); |
| |
| /** Static map of jar file/time to manifest class-path entries */ |
| private static Map<String, String> pathMap = |
| Collections.synchronizedMap(new HashMap<>()); |
| |
| /** |
| * The context loader saved when setting the thread's current |
| * context loader. |
| */ |
| private ClassLoader savedContextLoader = null; |
| |
| /** |
| * Whether or not the context loader is currently saved. |
| */ |
| private boolean isContextLoaderSaved = false; |
| |
| /** |
| * Create an Ant ClassLoader for a given project, with |
| * a parent classloader and an initial classpath. |
| * @since Ant 1.7. |
| * @param parent the parent for this classloader. |
| * @param project The project to which this classloader is to |
| * belong. |
| * @param classpath The classpath to use to load classes. |
| */ |
| public AntClassLoader(final ClassLoader parent, final Project project, final Path classpath) { |
| setParent(parent); |
| setClassPath(classpath); |
| setProject(project); |
| } |
| |
| /** |
| * Create an Ant Class Loader |
| */ |
| public AntClassLoader() { |
| setParent(null); |
| } |
| |
| /** |
| * Creates a classloader for the given project using the classpath given. |
| * |
| * @param project The project to which this classloader is to belong. |
| * Must not be <code>null</code>. |
| * @param classpath The classpath to use to load the classes. This |
| * is combined with the system classpath in a manner |
| * determined by the value of ${build.sysclasspath}. |
| * May be <code>null</code>, in which case no path |
| * elements are set up to start with. |
| */ |
| public AntClassLoader(final Project project, final Path classpath) { |
| setParent(null); |
| setProject(project); |
| setClassPath(classpath); |
| } |
| |
| /** |
| * Creates a classloader for the given project using the classpath given. |
| * |
| * @param parent The parent classloader to which unsatisfied loading |
| * attempts are delegated. May be <code>null</code>, |
| * in which case the classloader which loaded this |
| * class is used as the parent. |
| * @param project The project to which this classloader is to belong. |
| * Must not be <code>null</code>. |
| * @param classpath the classpath to use to load the classes. |
| * May be <code>null</code>, in which case no path |
| * elements are set up to start with. |
| * @param parentFirst If <code>true</code>, indicates that the parent |
| * classloader should be consulted before trying to |
| * load the a class through this loader. |
| */ |
| public AntClassLoader(final ClassLoader parent, final Project project, |
| final Path classpath, final boolean parentFirst) { |
| this(project, classpath); |
| if (parent != null) { |
| setParent(parent); |
| } |
| setParentFirst(parentFirst); |
| addJavaLibraries(); |
| } |
| |
| /** |
| * Creates a classloader for the given project using the classpath given. |
| * |
| * @param project The project to which this classloader is to belong. |
| * Must not be <code>null</code>. |
| * @param classpath The classpath to use to load the classes. May be |
| * <code>null</code>, in which case no path |
| * elements are set up to start with. |
| * @param parentFirst If <code>true</code>, indicates that the parent |
| * classloader should be consulted before trying to |
| * load the a class through this loader. |
| */ |
| public AntClassLoader(final Project project, final Path classpath, |
| final boolean parentFirst) { |
| this(null, project, classpath, parentFirst); |
| } |
| |
| /** |
| * Creates an empty class loader. The classloader should be configured |
| * with path elements to specify where the loader is to look for |
| * classes. |
| * |
| * @param parent The parent classloader to which unsatisfied loading |
| * attempts are delegated. May be <code>null</code>, |
| * in which case the classloader which loaded this |
| * class is used as the parent. |
| * @param parentFirst If <code>true</code>, indicates that the parent |
| * classloader should be consulted before trying to |
| * load the a class through this loader. |
| */ |
| public AntClassLoader(final ClassLoader parent, final boolean parentFirst) { |
| setParent(parent); |
| project = null; |
| this.parentFirst = parentFirst; |
| } |
| |
| /** |
| * Set the project associated with this class loader |
| * |
| * @param project the project instance |
| */ |
| public void setProject(final Project project) { |
| this.project = project; |
| if (project != null) { |
| project.addBuildListener(this); |
| } |
| } |
| |
| /** |
| * Set the classpath to search for classes to load. This should not be |
| * changed once the classloader starts to server classes |
| * |
| * @param classpath the search classpath consisting of directories and |
| * jar/zip files. |
| */ |
| public void setClassPath(final Path classpath) { |
| pathComponents.removeAllElements(); |
| if (classpath != null) { |
| for (String pathElement : classpath.concatSystemClasspath("ignore").list()) { |
| try { |
| addPathElement(pathElement); |
| } catch (final BuildException e) { |
| // ignore path elements which are invalid |
| // relative to the project |
| log("Ignoring path element " + pathElement + " from " + |
| "classpath due to exception " + e, Project.MSG_DEBUG); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Set the parent for this class loader. This is the class loader to which |
| * this class loader will delegate to load classes |
| * |
| * @param parent the parent class loader. |
| */ |
| public void setParent(final ClassLoader parent) { |
| this.parent = parent == null ? AntClassLoader.class.getClassLoader() : parent; |
| } |
| |
| /** |
| * Control whether class lookup is delegated to the parent loader first |
| * or after this loader. Use with extreme caution. Setting this to |
| * false violates the class loader hierarchy and can lead to Linkage errors |
| * |
| * @param parentFirst if true, delegate initial class search to the parent |
| * classloader. |
| */ |
| public void setParentFirst(final boolean parentFirst) { |
| this.parentFirst = parentFirst; |
| } |
| |
| /** |
| * Logs a message through the project object if one has been provided. |
| * |
| * @param message The message to log. |
| * Should not be <code>null</code>. |
| * |
| * @param priority The logging priority of the message. |
| */ |
| protected void log(final String message, final int priority) { |
| if (project != null) { |
| project.log(message, priority); |
| } else if (priority < Project.MSG_INFO) { |
| System.err.println(message); |
| } |
| } |
| |
| /** |
| * Sets the current thread's context loader to this classloader, storing |
| * the current loader value for later resetting. |
| */ |
| public void setThreadContextLoader() { |
| if (isContextLoaderSaved) { |
| throw new BuildException("Context loader has not been reset"); |
| } |
| if (LoaderUtils.isContextLoaderAvailable()) { |
| savedContextLoader = LoaderUtils.getContextClassLoader(); |
| ClassLoader loader = this; |
| if (project != null && "only".equals(project.getProperty(MagicNames.BUILD_SYSCLASSPATH))) { |
| loader = this.getClass().getClassLoader(); |
| } |
| LoaderUtils.setContextClassLoader(loader); |
| isContextLoaderSaved = true; |
| } |
| } |
| |
| /** |
| * Resets the current thread's context loader to its original value. |
| */ |
| public void resetThreadContextLoader() { |
| if (LoaderUtils.isContextLoaderAvailable() && isContextLoaderSaved) { |
| LoaderUtils.setContextClassLoader(savedContextLoader); |
| savedContextLoader = null; |
| isContextLoaderSaved = false; |
| } |
| } |
| |
| |
| /** |
| * Adds an element to the classpath to be searched. |
| * |
| * @param pathElement The path element to add. Must not be |
| * <code>null</code>. |
| * |
| * @exception BuildException if the given path element cannot be resolved |
| * against the project. |
| */ |
| public void addPathElement(final String pathElement) throws BuildException { |
| final File pathComponent = project != null ? project.resolveFile(pathElement) : new File( |
| pathElement); |
| try { |
| addPathFile(pathComponent); |
| } catch (final IOException e) { |
| throw new BuildException(e); |
| } |
| } |
| |
| /** |
| * Add a path component. |
| * This simply adds the file, unlike addPathElement |
| * it does not open jar files and load files from |
| * their CLASSPATH entry in the manifest file. |
| * @param file the jar file or directory to add. |
| */ |
| public void addPathComponent(final File file) { |
| if (pathComponents.contains(file)) { |
| return; |
| } |
| pathComponents.addElement(file); |
| } |
| |
| /** |
| * Add a file to the path. |
| * Reads the manifest, if available, and adds any additional class path jars |
| * specified in the manifest. |
| * |
| * @param pathComponent the file which is to be added to the path for |
| * this class loader |
| * |
| * @throws IOException if data needed from the file cannot be read. |
| */ |
| protected void addPathFile(final File pathComponent) throws IOException { |
| if (!pathComponents.contains(pathComponent)) { |
| pathComponents.addElement(pathComponent); |
| } |
| if (pathComponent.isDirectory()) { |
| return; |
| } |
| |
| final String absPathPlusTimeAndLength = pathComponent.getAbsolutePath() |
| + pathComponent.lastModified() + "-" + pathComponent.length(); |
| String classpath = pathMap.get(absPathPlusTimeAndLength); |
| if (classpath == null) { |
| try (JarFile jarFile = newJarFile(pathComponent)) { |
| final Manifest manifest = jarFile.getManifest(); |
| if (manifest == null) { |
| return; |
| } |
| classpath = manifest.getMainAttributes() |
| .getValue(Attributes.Name.CLASS_PATH); |
| } |
| if (classpath == null) { |
| classpath = ""; |
| } |
| pathMap.put(absPathPlusTimeAndLength, classpath); |
| } |
| |
| if (!classpath.isEmpty()) { |
| final URL baseURL = FILE_UTILS.getFileURL(pathComponent); |
| final StringTokenizer st = new StringTokenizer(classpath); |
| while (st.hasMoreTokens()) { |
| final String classpathElement = st.nextToken(); |
| final URL libraryURL = new URL(baseURL, classpathElement); |
| if (!libraryURL.getProtocol().equals("file")) { |
| log("Skipping jar library " + classpathElement |
| + " since only relative URLs are supported by this" + " loader", |
| Project.MSG_VERBOSE); |
| continue; |
| } |
| final String decodedPath = Locator.decodeUri(libraryURL.getFile()); |
| final File libraryFile = new File(decodedPath); |
| if (libraryFile.exists() && !isInPath(libraryFile)) { |
| addPathFile(libraryFile); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Returns the classpath this classloader will consult. |
| * |
| * @return the classpath used for this classloader, with elements |
| * separated by the path separator for the system. |
| */ |
| public String getClasspath() { |
| final StringBuilder sb = new StringBuilder(); |
| for (final File component : pathComponents) { |
| if (sb.length() > 0) { |
| sb.append(File.pathSeparator); |
| } |
| sb.append(component.getAbsolutePath()); |
| } |
| return sb.toString(); |
| } |
| |
| /** |
| * Sets whether this classloader should run in isolated mode. In |
| * isolated mode, classes not found on the given classpath will |
| * not be referred to the parent class loader but will cause a |
| * ClassNotFoundException. |
| * |
| * @param isolated Whether or not this classloader should run in |
| * isolated mode. |
| */ |
| public synchronized void setIsolated(final boolean isolated) { |
| ignoreBase = isolated; |
| } |
| |
| /** |
| * Forces initialization of a class in a JDK 1.1 compatible, albeit hacky |
| * way. |
| * |
| * @param theClass The class to initialize. |
| * Must not be <code>null</code>. |
| * |
| * @deprecated since 1.6.x. |
| * Use Class.forName with initialize=true instead. |
| */ |
| @Deprecated |
| public static void initializeClass(final Class<?> theClass) { |
| // ***HACK*** We ask the VM to create an instance |
| // by voluntarily providing illegal arguments to force |
| // the VM to run the class' static initializer, while |
| // at the same time not running a valid constructor. |
| |
| final Constructor<?>[] cons = theClass.getDeclaredConstructors(); |
| //At least one constructor is guaranteed to be there, but check anyway. |
| if (cons != null) { |
| if (cons.length > 0 && cons[0] != null) { |
| final String[] strs = new String[NUMBER_OF_STRINGS]; |
| try { |
| cons[0].newInstance((Object[]) strs); |
| // Expecting an exception to be thrown by this call: |
| // IllegalArgumentException: wrong number of Arguments |
| } catch (final Exception e) { |
| // Ignore - we are interested only in the side |
| // effect - that of getting the static initializers |
| // invoked. As we do not want to call a valid |
| // constructor to get this side effect, an |
| // attempt is made to call a hopefully |
| // invalid constructor - come on, nobody |
| // would have a constructor that takes in |
| // 256 String arguments ;-) |
| // (In fact, they can't - according to JVM spec |
| // section 4.10, the number of method parameters is limited |
| // to 255 by the definition of a method descriptor. |
| // Constructors count as methods here.) |
| } |
| } |
| } |
| } |
| |
| /** |
| * Adds a package root to the list of packages which must be loaded on the |
| * parent loader. |
| * |
| * All subpackages are also included. |
| * |
| * @param packageRoot The root of all packages to be included. |
| * Should not be <code>null</code>. |
| */ |
| public void addSystemPackageRoot(final String packageRoot) { |
| systemPackages.addElement(packageRoot + (packageRoot.endsWith(".") ? "" : ".")); |
| } |
| |
| /** |
| * Adds a package root to the list of packages which must be loaded using |
| * this loader. |
| * |
| * All subpackages are also included. |
| * |
| * @param packageRoot The root of all packages to be included. |
| * Should not be <code>null</code>. |
| */ |
| public void addLoaderPackageRoot(final String packageRoot) { |
| loaderPackages.addElement(packageRoot + (packageRoot.endsWith(".") ? "" : ".")); |
| } |
| |
| /** |
| * Loads a class through this class loader even if that class is available |
| * on the parent classpath. |
| * |
| * This ensures that any classes which are loaded by the returned class |
| * will use this classloader. |
| * |
| * @param classname The name of the class to be loaded. |
| * Must not be <code>null</code>. |
| * |
| * @return the required Class object |
| * |
| * @exception ClassNotFoundException if the requested class does not exist |
| * on this loader's classpath. |
| */ |
| public Class<?> forceLoadClass(final String classname) throws ClassNotFoundException { |
| log("force loading " + classname, Project.MSG_DEBUG); |
| |
| Class<?> theClass = findLoadedClass(classname); |
| |
| if (theClass == null) { |
| theClass = findClass(classname); |
| } |
| return theClass; |
| } |
| |
| /** |
| * Loads a class through this class loader but defer to the parent class |
| * loader. |
| * |
| * This ensures that instances of the returned class will be compatible |
| * with instances which have already been loaded on the parent |
| * loader. |
| * |
| * @param classname The name of the class to be loaded. |
| * Must not be <code>null</code>. |
| * |
| * @return the required Class object |
| * |
| * @exception ClassNotFoundException if the requested class does not exist |
| * on this loader's classpath. |
| */ |
| public Class<?> forceLoadSystemClass(final String classname) throws ClassNotFoundException { |
| log("force system loading " + classname, Project.MSG_DEBUG); |
| |
| Class<?> theClass = findLoadedClass(classname); |
| |
| if (theClass == null) { |
| theClass = findBaseClass(classname); |
| } |
| return theClass; |
| } |
| |
| /** |
| * Returns a stream to read the requested resource name. |
| * |
| * @param name The name of the resource for which a stream is required. |
| * Must not be <code>null</code>. |
| * |
| * @return a stream to the required resource or <code>null</code> if the |
| * resource cannot be found on the loader's classpath. |
| */ |
| @Override |
| public InputStream getResourceAsStream(final String name) { |
| InputStream resourceStream = null; |
| if (isParentFirst(name)) { |
| resourceStream = loadBaseResource(name); |
| } |
| if (resourceStream != null) { |
| log("ResourceStream for " + name |
| + " loaded from parent loader", Project.MSG_DEBUG); |
| } else { |
| resourceStream = loadResource(name); |
| if (resourceStream != null) { |
| log("ResourceStream for " + name |
| + " loaded from ant loader", Project.MSG_DEBUG); |
| } |
| } |
| if (resourceStream == null && !isParentFirst(name)) { |
| if (ignoreBase) { |
| resourceStream = getRootLoader() == null |
| ? null |
| : getRootLoader().getResourceAsStream(name); |
| } else { |
| resourceStream = loadBaseResource(name); |
| } |
| if (resourceStream != null) { |
| log("ResourceStream for " + name + " loaded from parent loader", |
| Project.MSG_DEBUG); |
| } |
| } |
| if (resourceStream == null) { |
| log("Couldn't load ResourceStream for " + name, Project.MSG_DEBUG); |
| } |
| return resourceStream; |
| } |
| |
| /** |
| * Returns a stream to read the requested resource name from this loader. |
| * |
| * @param name The name of the resource for which a stream is required. |
| * Must not be <code>null</code>. |
| * |
| * @return a stream to the required resource or <code>null</code> if |
| * the resource cannot be found on the loader's classpath. |
| */ |
| private InputStream loadResource(final String name) { |
| // we need to search the components of the path to see if we can |
| // find the class we want. |
| return pathComponents.stream().map(path -> getResourceStream(path, name)) |
| .filter(Objects::nonNull).findFirst().orElse(null); |
| } |
| |
| /** |
| * Finds a system resource (which should be loaded from the parent |
| * classloader). |
| * |
| * @param name The name of the system resource to load. |
| * Must not be <code>null</code>. |
| * |
| * @return a stream to the named resource, or <code>null</code> if |
| * the resource cannot be found. |
| */ |
| private InputStream loadBaseResource(final String name) { |
| return parent == null ? super.getResourceAsStream(name) : parent.getResourceAsStream(name); |
| } |
| |
| /** |
| * Returns an inputstream to a given resource in the given file which may |
| * either be a directory or a zip file. |
| * |
| * @param file the file (directory or jar) in which to search for the |
| * resource. Must not be <code>null</code>. |
| * @param resourceName The name of the resource for which a stream is |
| * required. Must not be <code>null</code>. |
| * |
| * @return a stream to the required resource or <code>null</code> if |
| * the resource cannot be found in the given file. |
| */ |
| private InputStream getResourceStream(final File file, final String resourceName) { |
| try { |
| JarFile jarFile = jarFiles.get(file); |
| if (jarFile == null && file.isDirectory()) { |
| final File resource = new File(file, resourceName); |
| if (resource.exists()) { |
| return Files.newInputStream(resource.toPath()); |
| } |
| } else { |
| if (jarFile == null) { |
| if (file.exists()) { |
| jarFile = newJarFile(file); |
| jarFiles.put(file, jarFile); |
| } else { |
| return null; |
| } |
| //to eliminate a race condition, retrieve the entry |
| //that is in the hash table under that filename |
| jarFile = jarFiles.get(file); |
| } |
| final JarEntry entry = jarFile.getJarEntry(resourceName); |
| if (entry != null) { |
| return jarFile.getInputStream(entry); |
| } |
| } |
| } catch (final Exception e) { |
| log("Ignoring Exception " + e.getClass().getName() + ": " + e.getMessage() |
| + " reading resource " + resourceName + " from " + file, Project.MSG_VERBOSE); |
| } |
| return null; |
| } |
| |
| /** |
| * Tests whether or not the parent classloader should be checked for a |
| * resource before this one. If the resource matches both the "use parent |
| * classloader first" and the "use this classloader first" lists, the latter |
| * takes priority. |
| * |
| * @param resourceName |
| * The name of the resource to check. Must not be |
| * <code>null</code>. |
| * |
| * @return whether or not the parent classloader should be checked for a |
| * resource before this one is. |
| */ |
| private boolean isParentFirst(final String resourceName) { |
| // default to the global setting and then see |
| // if this class belongs to a package which has been |
| // designated to use a specific loader first |
| // (this one or the parent one) |
| |
| // TODO - shouldn't this always return false in isolated mode? |
| |
| return loaderPackages.stream().noneMatch(resourceName::startsWith) |
| && (systemPackages.stream().anyMatch(resourceName::startsWith) || parentFirst); |
| } |
| |
| /** |
| * Used for isolated resource searching. |
| * @return the root classloader of AntClassLoader. |
| */ |
| private ClassLoader getRootLoader() { |
| ClassLoader ret = getClass().getClassLoader(); |
| while (ret != null && ret.getParent() != null) { |
| ret = ret.getParent(); |
| } |
| return ret; |
| } |
| |
| /** |
| * Finds the resource with the given name. A resource is |
| * some data (images, audio, text, etc) that can be accessed by class |
| * code in a way that is independent of the location of the code. |
| * |
| * @param name The name of the resource for which a stream is required. |
| * Must not be <code>null</code>. |
| * |
| * @return a URL for reading the resource, or <code>null</code> if the |
| * resource could not be found or the caller doesn't have |
| * adequate privileges to get the resource. |
| */ |
| @Override |
| public URL getResource(final String name) { |
| // we need to search the components of the path to see if |
| // we can find the class we want. |
| URL url = null; |
| if (isParentFirst(name)) { |
| url = parent == null ? super.getResource(name) : parent.getResource(name); |
| } |
| if (url != null) { |
| log("Resource " + name + " loaded from parent loader", Project.MSG_DEBUG); |
| } else { |
| // try and load from this loader if the parent either didn't find |
| // it or wasn't consulted. |
| for (final File pathComponent : pathComponents) { |
| url = getResourceURL(pathComponent, name); |
| if (url != null) { |
| log("Resource " + name + " loaded from ant loader", Project.MSG_DEBUG); |
| break; |
| } |
| } |
| } |
| if (url == null && !isParentFirst(name)) { |
| // this loader was first but it didn't find it - try the parent |
| if (ignoreBase) { |
| url = getRootLoader() == null ? null : getRootLoader().getResource(name); |
| } else { |
| url = parent == null ? super.getResource(name) : parent.getResource(name); |
| } |
| if (url != null) { |
| log("Resource " + name + " loaded from parent loader", Project.MSG_DEBUG); |
| } |
| } |
| if (url == null) { |
| log("Couldn't load Resource " + name, Project.MSG_DEBUG); |
| } |
| return url; |
| } |
| |
| /** |
| * Finds all the resources with the given name. A resource is some |
| * data (images, audio, text, etc) that can be accessed by class |
| * code in a way that is independent of the location of the code. |
| * |
| * <p>Would override getResources if that wasn't final in Java |
| * 1.4.</p> |
| * |
| * @param name name of the resource |
| * @return possible URLs as enumeration |
| * @throws IOException if something goes wrong |
| * @see #findResources(String, boolean) |
| * @since Ant 1.8.0 |
| */ |
| public Enumeration<URL> getNamedResources(final String name) |
| throws IOException { |
| return findResources(name, false); |
| } |
| |
| /** |
| * Returns an enumeration of URLs representing all the resources with the |
| * given name by searching the class loader's classpath. |
| * |
| * @param name The resource name to search for. |
| * Must not be <code>null</code>. |
| * @return an enumeration of URLs for the resources |
| * @exception IOException if I/O errors occurs (can't happen) |
| */ |
| @Override |
| protected Enumeration<URL> findResources(final String name) throws IOException { |
| return findResources(name, true); |
| } |
| |
| /** |
| * Returns an enumeration of URLs representing all the resources with the |
| * given name by searching the class loader's classpath. |
| * |
| * @param name The resource name to search for. |
| * Must not be <code>null</code>. |
| * @param parentHasBeenSearched whether ClassLoader.this.parent |
| * has been searched - will be true if the method is (indirectly) |
| * called from ClassLoader.getResources |
| * @return an enumeration of URLs for the resources |
| * @exception IOException if I/O errors occurs (can't happen) |
| */ |
| protected Enumeration<URL> findResources(final String name, |
| final boolean parentHasBeenSearched) |
| throws IOException { |
| final Enumeration<URL> mine = new ResourceEnumeration(name); |
| Enumeration<URL> base; |
| if (parent != null && (!parentHasBeenSearched || parent != getParent())) { |
| // Delegate to the parent: |
| base = parent.getResources(name); |
| // Note: could cause overlaps in case |
| // ClassLoader.this.parent has matches and |
| // parentHasBeenSearched is true |
| } else { |
| // ClassLoader.this.parent is already delegated to for example from |
| // ClassLoader.getResources, no need: |
| base = Collections.emptyEnumeration(); |
| } |
| if (isParentFirst(name)) { |
| // Normal case. |
| return append(base, mine); |
| } |
| if (ignoreBase) { |
| return getRootLoader() == null ? mine |
| : append(mine, getRootLoader().getResources(name)); |
| } |
| // parent last: |
| return append(mine, base); |
| } |
| |
| private static Enumeration<URL> append(Enumeration<URL> one, Enumeration<URL> two) { |
| return Stream.concat(Collections.list(one).stream(), Collections.list(two).stream()) |
| .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::enumeration)); |
| } |
| |
| /** |
| * Returns the URL of a given resource in the given file which may |
| * either be a directory or a zip file. |
| * |
| * @param file The file (directory or jar) in which to search for |
| * the resource. Must not be <code>null</code>. |
| * @param resourceName The name of the resource for which a stream |
| * is required. Must not be <code>null</code>. |
| * |
| * @return a stream to the required resource or <code>null</code> if the |
| * resource cannot be found in the given file object. |
| */ |
| protected URL getResourceURL(final File file, final String resourceName) { |
| try { |
| JarFile jarFile = jarFiles.get(file); |
| if (jarFile == null && file.isDirectory()) { |
| final File resource = new File(file, resourceName); |
| |
| if (resource.exists()) { |
| try { |
| return FILE_UTILS.getFileURL(resource); |
| } catch (final MalformedURLException ex) { |
| return null; |
| } |
| } |
| } else { |
| if (jarFile == null) { |
| if (file.exists()) { |
| if (!isZip(file)) { |
| final String msg = "CLASSPATH element " + file |
| + " is not a JAR."; |
| log(msg, Project.MSG_WARN); |
| return null; |
| } |
| jarFile = newJarFile(file); |
| jarFiles.put(file, jarFile); |
| } else { |
| return null; |
| } |
| // potential race-condition |
| jarFile = jarFiles.get(file); |
| } |
| final JarEntry entry = jarFile.getJarEntry(resourceName); |
| if (entry != null) { |
| try { |
| return new URL("jar:" + FILE_UTILS.getFileURL(file) + "!/" + entry); |
| } catch (final MalformedURLException ex) { |
| return null; |
| } |
| } |
| } |
| } catch (final Exception e) { |
| final String msg = "Unable to obtain resource from " + file + ": "; |
| log(msg + e, Project.MSG_WARN); |
| log(StringUtils.getStackTrace(e), Project.MSG_WARN); |
| } |
| return null; |
| } |
| |
| /** |
| * Loads a class with this class loader. |
| * |
| * This class attempts to load the class in an order determined by whether |
| * or not the class matches the system/loader package lists, with the |
| * loader package list taking priority. If the classloader is in isolated |
| * mode, failure to load the class in this loader will result in a |
| * ClassNotFoundException. |
| * |
| * @param classname The name of the class to be loaded. |
| * Must not be <code>null</code>. |
| * @param resolve <code>true</code> if all classes upon which this class |
| * depends are to be loaded. |
| * |
| * @return the required Class object |
| * |
| * @exception ClassNotFoundException if the requested class does not exist |
| * on the system classpath (when not in isolated mode) or this loader's |
| * classpath. |
| */ |
| @Override |
| protected synchronized Class<?> loadClass(final String classname, final boolean resolve) |
| throws ClassNotFoundException { |
| // 'sync' is needed - otherwise 2 threads can load the same class |
| // twice, resulting in LinkageError: duplicated class definition. |
| // findLoadedClass avoids that, but without sync it won't work. |
| |
| Class<?> theClass = findLoadedClass(classname); |
| if (theClass != null) { |
| return theClass; |
| } |
| if (isParentFirst(classname)) { |
| try { |
| theClass = findBaseClass(classname); |
| log("Class " + classname + " loaded from parent loader " + "(parentFirst)", |
| Project.MSG_DEBUG); |
| } catch (final ClassNotFoundException cnfe) { |
| theClass = findClass(classname); |
| log("Class " + classname + " loaded from ant loader " + "(parentFirst)", |
| Project.MSG_DEBUG); |
| } |
| } else { |
| try { |
| theClass = findClass(classname); |
| log("Class " + classname + " loaded from ant loader", Project.MSG_DEBUG); |
| } catch (final ClassNotFoundException cnfe) { |
| if (ignoreBase) { |
| throw cnfe; |
| } |
| theClass = findBaseClass(classname); |
| log("Class " + classname + " loaded from parent loader", Project.MSG_DEBUG); |
| } |
| } |
| if (resolve) { |
| resolveClass(theClass); |
| } |
| return theClass; |
| } |
| |
| /** |
| * Converts the class dot notation to a filesystem equivalent for |
| * searching purposes. |
| * |
| * @param classname The class name in dot format (eg java.lang.Integer). |
| * Must not be <code>null</code>. |
| * |
| * @return the classname in filesystem format (eg java/lang/Integer.class) |
| */ |
| private String getClassFilename(final String classname) { |
| return classname.replace('.', '/') + ".class"; |
| } |
| |
| /** |
| * Define a class given its bytes |
| * |
| * @param container the container from which the class data has been read |
| * may be a directory or a jar/zip file. |
| * |
| * @param classData the bytecode data for the class |
| * @param classname the name of the class |
| * |
| * @return the Class instance created from the given data |
| * |
| * @throws IOException if the class data cannot be read. |
| */ |
| protected Class<?> defineClassFromData(final File container, final byte[] classData, final String classname) |
| throws IOException { |
| definePackage(container, classname); |
| final ProtectionDomain currentPd = Project.class.getProtectionDomain(); |
| final String classResource = getClassFilename(classname); |
| final CodeSource src = new CodeSource(FILE_UTILS.getFileURL(container), |
| getCertificates(container, |
| classResource)); |
| final ProtectionDomain classesPd = |
| new ProtectionDomain(src, currentPd.getPermissions(), |
| this, |
| currentPd.getPrincipals()); |
| return defineClass(classname, classData, 0, classData.length, |
| classesPd); |
| } |
| |
| /** |
| * Define the package information associated with a class. |
| * |
| * @param container the file containing the class definition. |
| * @param className the class name of for which the package information |
| * is to be determined. |
| * |
| * @exception IOException if the package information cannot be read from the |
| * container. |
| */ |
| protected void definePackage(final File container, final String className) throws IOException { |
| final int classIndex = className.lastIndexOf('.'); |
| if (classIndex == -1) { |
| return; |
| } |
| final String packageName = className.substring(0, classIndex); |
| if (getPackage(packageName) != null) { |
| // already defined |
| return; |
| } |
| // define the package now |
| final Manifest manifest = getJarManifest(container); |
| |
| if (manifest == null) { |
| definePackage(packageName, null, null, null, null, null, null, null); |
| } else { |
| definePackage(container, packageName, manifest); |
| } |
| } |
| |
| /** |
| * Get the manifest from the given jar, if it is indeed a jar and it has a |
| * manifest |
| * |
| * @param container the File from which a manifest is required. |
| * |
| * @return the jar's manifest or null is the container is not a jar or it |
| * has no manifest. |
| * |
| * @exception IOException if the manifest cannot be read. |
| */ |
| private Manifest getJarManifest(final File container) throws IOException { |
| if (container.isDirectory()) { |
| return null; |
| } |
| final JarFile jarFile = jarFiles.get(container); |
| if (jarFile == null) { |
| return null; |
| } |
| return jarFile.getManifest(); |
| } |
| |
| /** |
| * Get the certificates for a given jar entry, if it is indeed a jar. |
| * |
| * @param container the File from which to read the entry |
| * @param entry the entry of which the certificates are requested |
| * |
| * @return the entry's certificates or null is the container is |
| * not a jar or it has no certificates. |
| */ |
| private Certificate[] getCertificates(final File container, final String entry) { |
| if (container.isDirectory()) { |
| return null; |
| } |
| final JarFile jarFile = jarFiles.get(container); |
| if (jarFile == null) { |
| return null; |
| } |
| final JarEntry ent = jarFile.getJarEntry(entry); |
| return ent == null ? null : ent.getCertificates(); |
| } |
| |
| /** |
| * Define the package information when the class comes from a |
| * jar with a manifest |
| * |
| * @param container the jar file containing the manifest |
| * @param packageName the name of the package being defined. |
| * @param manifest the jar's manifest |
| */ |
| protected void definePackage(final File container, final String packageName, final Manifest manifest) { |
| final String sectionName = packageName.replace('.', '/') + "/"; |
| |
| String specificationTitle = null; |
| String specificationVendor = null; |
| String specificationVersion = null; |
| String implementationTitle = null; |
| String implementationVendor = null; |
| String implementationVersion = null; |
| String sealedString = null; |
| URL sealBase = null; |
| |
| final Attributes sectionAttributes = manifest.getAttributes(sectionName); |
| if (sectionAttributes != null) { |
| specificationTitle = sectionAttributes.getValue(Name.SPECIFICATION_TITLE); |
| specificationVendor = sectionAttributes.getValue(Name.SPECIFICATION_VENDOR); |
| specificationVersion = sectionAttributes.getValue(Name.SPECIFICATION_VERSION); |
| implementationTitle = sectionAttributes.getValue(Name.IMPLEMENTATION_TITLE); |
| implementationVendor = sectionAttributes.getValue(Name.IMPLEMENTATION_VENDOR); |
| implementationVersion = sectionAttributes.getValue(Name.IMPLEMENTATION_VERSION); |
| sealedString = sectionAttributes.getValue(Name.SEALED); |
| } |
| final Attributes mainAttributes = manifest.getMainAttributes(); |
| if (mainAttributes != null) { |
| if (specificationTitle == null) { |
| specificationTitle = mainAttributes.getValue(Name.SPECIFICATION_TITLE); |
| } |
| if (specificationVendor == null) { |
| specificationVendor = mainAttributes.getValue(Name.SPECIFICATION_VENDOR); |
| } |
| if (specificationVersion == null) { |
| specificationVersion = mainAttributes.getValue(Name.SPECIFICATION_VERSION); |
| } |
| if (implementationTitle == null) { |
| implementationTitle = mainAttributes.getValue(Name.IMPLEMENTATION_TITLE); |
| } |
| if (implementationVendor == null) { |
| implementationVendor = mainAttributes.getValue(Name.IMPLEMENTATION_VENDOR); |
| } |
| if (implementationVersion == null) { |
| implementationVersion = mainAttributes.getValue(Name.IMPLEMENTATION_VERSION); |
| } |
| if (sealedString == null) { |
| sealedString = mainAttributes.getValue(Name.SEALED); |
| } |
| } |
| if (sealedString != null && sealedString.equalsIgnoreCase("true")) { |
| try { |
| sealBase = new URL(FileUtils.getFileUtils().toURI(container.getAbsolutePath())); |
| } catch (final MalformedURLException e) { |
| // ignore |
| } |
| } |
| definePackage(packageName, specificationTitle, specificationVersion, specificationVendor, |
| implementationTitle, implementationVersion, implementationVendor, sealBase); |
| } |
| |
| /** |
| * Reads a class definition from a stream. |
| * |
| * @param stream The stream from which the class is to be read. |
| * Must not be <code>null</code>. |
| * @param classname The name of the class in the stream. |
| * Must not be <code>null</code>. |
| * @param container the file or directory containing the class. |
| * |
| * @return the Class object read from the stream. |
| * |
| * @exception IOException if there is a problem reading the class from the |
| * stream. |
| * @exception SecurityException if there is a security problem while |
| * reading the class from the stream. |
| */ |
| private Class<?> getClassFromStream(final InputStream stream, final String classname, final File container) |
| throws IOException, SecurityException { |
| final ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
| int bytesRead = -1; |
| final byte[] buffer = new byte[BUFFER_SIZE]; |
| |
| while ((bytesRead = stream.read(buffer, 0, BUFFER_SIZE)) != -1) { |
| baos.write(buffer, 0, bytesRead); |
| } |
| final byte[] classData = baos.toByteArray(); |
| return defineClassFromData(container, classData, classname); |
| } |
| |
| /** |
| * Searches for and load a class on the classpath of this class loader. |
| * |
| * @param name The name of the class to be loaded. Must not be |
| * <code>null</code>. |
| * |
| * @return the required Class object |
| * |
| * @exception ClassNotFoundException if the requested class does not exist |
| * on this loader's classpath. |
| */ |
| @Override |
| public Class<?> findClass(final String name) throws ClassNotFoundException { |
| log("Finding class " + name, Project.MSG_DEBUG); |
| return findClassInComponents(name); |
| } |
| |
| /** |
| * Indicate if the given file is in this loader's path |
| * |
| * @param component the file which is to be checked |
| * |
| * @return true if the file is in the class path |
| */ |
| protected boolean isInPath(final File component) { |
| return pathComponents.contains(component); |
| } |
| |
| /** |
| * Finds a class on the given classpath. |
| * |
| * @param name The name of the class to be loaded. Must not be |
| * <code>null</code>. |
| * |
| * @return the required Class object |
| * |
| * @exception ClassNotFoundException if the requested class does not exist |
| * on this loader's classpath. |
| */ |
| private Class<?> findClassInComponents(final String name) |
| throws ClassNotFoundException { |
| // we need to search the components of the path to see if |
| // we can find the class we want. |
| final String classFilename = getClassFilename(name); |
| for (final File pathComponent : pathComponents) { |
| try (InputStream stream = getResourceStream(pathComponent, classFilename)) { |
| if (stream != null) { |
| log("Loaded from " + pathComponent + " " |
| + classFilename, Project.MSG_DEBUG); |
| return getClassFromStream(stream, name, pathComponent); |
| } |
| } catch (final SecurityException se) { |
| throw se; |
| } catch (final IOException ioe) { |
| // ioe.printStackTrace(); |
| log("Exception reading component " + pathComponent + " (reason: " |
| + ioe.getMessage() + ")", Project.MSG_VERBOSE); |
| } |
| } |
| throw new ClassNotFoundException(name); |
| } |
| |
| /** |
| * Finds a system class (which should be loaded from the same classloader |
| * as the Ant core). |
| * |
| * For JDK 1.1 compatibility, this uses the findSystemClass method if |
| * no parent classloader has been specified. |
| * |
| * @param name The name of the class to be loaded. |
| * Must not be <code>null</code>. |
| * |
| * @return the required Class object |
| * |
| * @exception ClassNotFoundException if the requested class does not exist |
| * on this loader's classpath. |
| */ |
| private Class<?> findBaseClass(final String name) throws ClassNotFoundException { |
| return parent == null ? findSystemClass(name) : parent.loadClass(name); |
| } |
| |
| /** |
| * Cleans up any resources held by this classloader. Any open archive |
| * files are closed. |
| */ |
| public synchronized void cleanup() { |
| for (final JarFile jarFile : jarFiles.values()) { |
| FileUtils.close(jarFile); |
| } |
| jarFiles = new Hashtable<>(); |
| if (project != null) { |
| project.removeBuildListener(this); |
| } |
| project = null; |
| } |
| |
| /** |
| * Gets the parent as has been specified in the constructor or via |
| * setParent. |
| * |
| * @return classloader |
| * @since Ant 1.8.0 |
| */ |
| public ClassLoader getConfiguredParent() { |
| return parent; |
| } |
| |
| /** |
| * Empty implementation to satisfy the BuildListener interface. |
| * |
| * @param event the buildStarted event |
| */ |
| public void buildStarted(final BuildEvent event) { |
| // Not significant for the class loader. |
| } |
| |
| /** |
| * Cleans up any resources held by this classloader at the end |
| * of a build. |
| * |
| * @param event the buildFinished event |
| */ |
| public void buildFinished(final BuildEvent event) { |
| cleanup(); |
| } |
| |
| /** |
| * Cleans up any resources held by this classloader at the end of |
| * a subbuild if it has been created for the subbuild's project |
| * instance. |
| * |
| * @param event the buildFinished event |
| * |
| * @since Ant 1.6.2 |
| */ |
| public void subBuildFinished(final BuildEvent event) { |
| if (event.getProject() == project) { |
| cleanup(); |
| } |
| } |
| |
| /** |
| * Empty implementation to satisfy the BuildListener interface. |
| * |
| * @param event the buildStarted event |
| * |
| * @since Ant 1.6.2 |
| */ |
| public void subBuildStarted(final BuildEvent event) { |
| // Not significant for the class loader. |
| } |
| |
| /** |
| * Empty implementation to satisfy the BuildListener interface. |
| * |
| * @param event the targetStarted event |
| */ |
| public void targetStarted(final BuildEvent event) { |
| // Not significant for the class loader. |
| } |
| |
| /** |
| * Empty implementation to satisfy the BuildListener interface. |
| * |
| * @param event the targetFinished event |
| */ |
| public void targetFinished(final BuildEvent event) { |
| // Not significant for the class loader. |
| } |
| |
| /** |
| * Empty implementation to satisfy the BuildListener interface. |
| * |
| * @param event the taskStarted event |
| */ |
| public void taskStarted(final BuildEvent event) { |
| // Not significant for the class loader. |
| } |
| |
| /** |
| * Empty implementation to satisfy the BuildListener interface. |
| * |
| * @param event the taskFinished event |
| */ |
| public void taskFinished(final BuildEvent event) { |
| // Not significant for the class loader. |
| } |
| |
| /** |
| * Empty implementation to satisfy the BuildListener interface. |
| * |
| * @param event the messageLogged event |
| */ |
| public void messageLogged(final BuildEvent event) { |
| // Not significant for the class loader. |
| } |
| |
| /** |
| * add any libraries that come with different java versions |
| * here |
| */ |
| public void addJavaLibraries() { |
| JavaEnvUtils.getJrePackages().forEach(this::addSystemPackageRoot); |
| } |
| |
| /** |
| * Returns a <code>String</code> representing this loader. |
| * @return the path that this classloader has. |
| */ |
| @Override |
| public String toString() { |
| return "AntClassLoader[" + getClasspath() + "]"; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public Enumeration<URL> getResources(String name) throws IOException { |
| return getNamedResources(name); |
| } |
| |
| /** {@inheritDoc} */ |
| public void close() { |
| cleanup(); |
| } |
| |
| /** |
| * Factory method |
| * |
| * @param parent ClassLoader |
| * @param project Project |
| * @param path Path |
| * @param parentFirst boolean |
| * @return AntClassLoader |
| */ |
| public static AntClassLoader newAntClassLoader(final ClassLoader parent, |
| final Project project, |
| final Path path, |
| final boolean parentFirst) { |
| return new AntClassLoader(parent, project, path, parentFirst); |
| } |
| |
| private static final ZipLong EOCD_SIG = new ZipLong(0X06054B50L); |
| private static final ZipLong SINGLE_SEGMENT_SPLIT_MARKER = |
| new ZipLong(0X30304B50L); |
| |
| private static boolean isZip(final File file) throws IOException { |
| final byte[] sig = new byte[4]; |
| if (readFully(file, sig)) { |
| final ZipLong start = new ZipLong(sig); |
| return ZipLong.LFH_SIG.equals(start) // normal file |
| || EOCD_SIG.equals(start) // empty zip |
| || ZipLong.DD_SIG.equals(start) // split zip |
| || SINGLE_SEGMENT_SPLIT_MARKER.equals(start); |
| } |
| return false; |
| } |
| |
| private static boolean readFully(final File f, final byte[] b) throws IOException { |
| try (InputStream fis = Files.newInputStream(f.toPath())) { |
| final int len = b.length; |
| int count = 0, x = 0; |
| while (count != len) { |
| x = fis.read(b, count, len - count); |
| if (x == -1) { |
| break; |
| } |
| count += x; |
| } |
| return count == len; |
| } |
| } |
| |
| /** |
| * |
| * @param file The file representing the jar |
| * @return Returns a {@link JarFile} instance, which is constructed based upon the Java runtime version. |
| * Depending on the Java runtime version, the returned instance may or may not be {@code multi-release} |
| * aware (a feature introduced in Java 9) |
| * @throws IOException |
| */ |
| private static JarFile newJarFile(final File file) throws IOException { |
| if (!IS_ATLEAST_JAVA9 || MR_JARFILE_CTOR_ARGS == null || MR_JARFILE_CTOR_RUNTIME_VERSION_VAL == null) { |
| return new JarFile(file); |
| } |
| return ReflectUtil.newInstance(JarFile.class, MR_JARFILE_CTOR_ARGS, |
| new Object[] {file, true, ZipFile.OPEN_READ, MR_JARFILE_CTOR_RUNTIME_VERSION_VAL}); |
| } |
| } |