All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.codehaus.mojo.exec.URLClassLoaderBuilder Maven / Gradle / Ivy

package org.codehaus.mojo.exec;

/*
 * 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.
 */

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;

import org.apache.maven.plugin.logging.Log;
import org.codehaus.plexus.util.IOUtil;

import static java.util.Arrays.asList;

/**
 *
 * @author Robert Scholte
 * @since 3.0.0
 */
class URLClassLoaderBuilder {
    private Log logger;
    private Collection paths;
    private Collection exclusions;
    private ClassFileTransformer transformer;

    private URLClassLoaderBuilder() {}

    static URLClassLoaderBuilder builder() {
        return new URLClassLoaderBuilder();
    }

    public URLClassLoaderBuilder setTransformer(final ClassFileTransformer transformer) {
        this.transformer = transformer;
        return this;
    }

    URLClassLoaderBuilder setLogger(Log logger) {
        this.logger = logger;
        return this;
    }

    URLClassLoaderBuilder setExclusions(Collection exclusions) {
        this.exclusions = exclusions;
        return this;
    }

    URLClassLoaderBuilder setPaths(Collection paths) {
        this.paths = paths;
        return this;
    }

    URLClassLoader build() throws IOException {
        List urls = new ArrayList<>(paths.size());

        for (Path dependency : paths) {
            if (exclusions != null
                    && exclusions.contains(dependency.getFileName().toString())) {
                if (logger != null) {
                    logger.debug("Excluding as requested '" + dependency + "'");
                }
                continue;
            }
            try {
                urls.add(dependency.toUri().toURL());
            } catch (MalformedURLException e) {
                throw new IOException("Error during setting up classpath", e);
            }
        }

        return new ExecJavaClassLoader(urls.toArray(new URL[0]), transformer, logger);
    }

    // child first strategy
    private static class ExecJavaClassLoader extends URLClassLoader {
        static {
            try {
                registerAsParallelCapable();
            } catch (Exception e) {
                // no-op, not that important
            }
        }

        private final String jre;
        private final Log logger;
        private final ClassFileTransformer transformer;

        public ExecJavaClassLoader(final URL[] urls, final ClassFileTransformer transformer, final Log logger) {
            super(urls);
            this.jre = getJre();
            this.logger = logger;
            this.transformer = transformer;
        }

        @Override
        public Class loadClass(final String name, final boolean resolve) throws ClassNotFoundException {
            if (name == null) {
                throw new ClassNotFoundException();
            }

            if ("org.codehaus.mojo.exec.SystemExitManager".equals(name)) {
                return SystemExitManager.class;
            }

            synchronized (getClassLoadingLock(name)) {
                Class clazz;

                // if in the JVM, never override them
                if (isDirectJvmClass(name)) {
                    try {
                        clazz = getSystemClassLoader().loadClass(name);
                        if (postLoad(resolve, clazz)) {
                            return clazz;
                        }
                    } catch (NoClassDefFoundError | ClassNotFoundException ignored) {
                        // no-op
                    }
                }

                // already loaded?
                clazz = findLoadedClass(name);
                if (postLoad(resolve, clazz)) {
                    return clazz;
                }

                // look for it in this classloader
                try {
                    clazz = transformer != null ? doFindClass(name) : super.findClass(name);
                    if (clazz != null) {
                        if (postLoad(resolve, clazz)) {
                            return clazz;
                        }
                        return clazz;
                    }
                } catch (ClassNotFoundException | NoClassDefFoundError ignored) {
                    // try next
                }

                // load from parent - todo: exclude some classes?
                ClassLoader parent = getParent();
                if (parent == null) {
                    parent = getSystemClassLoader();
                }
                try {
                    clazz = Class.forName(name, false, parent);
                    if (postLoad(resolve, clazz)) {
                        return clazz;
                    }
                } catch (ClassNotFoundException ignored) {
                    // no-op
                }

                throw new ClassNotFoundException(name);
            }
        }

        private Class doFindClass(final String name) throws ClassNotFoundException {
            final String resource = name.replace('.', '/') + ".class";
            final URL url = super.findResource(resource);
            if (url == null) {
                throw new ClassNotFoundException(name);
            }

            try (final InputStream inputStream = url.openStream()) {
                final byte[] raw = IOUtil.toByteArray(inputStream);
                final byte[] res = transformer.transform(this, name, null, null, raw);
                final byte[] bin = res == null ? raw : res;
                return super.defineClass(name, bin, 0, bin.length);
            } catch (final ClassFormatError | IOException | IllegalClassFormatException var4) {
                throw new ClassNotFoundException(name, var4);
            }
        }

        @Override
        public Enumeration getResources(String name) throws IOException {
            final Enumeration selfResources = findResources(name);
            final Enumeration parentResources = getParent().getResources(name);
            if (!parentResources.hasMoreElements()) {
                return selfResources;
            }
            if (!selfResources.hasMoreElements()) {
                return new FilteringUrlEnum(parentResources);
            }
            return new ChainedEnumerations(
                    asList(selfResources, new FilteringUrlEnum(parentResources)).iterator());
        }

        private boolean isInJvm(URL resource) {
            final Path path = toPath(resource);
            if (path == null) {
                return false;
            }
            return path.normalize().toAbsolutePath().toString().startsWith(jre);
        }

        private String getJre() {
            final Path home = new File(System.getProperty("java.home", "")).toPath();
            if ("jre".equals(home.getFileName().toString())
                    && home.getParent() != null
                    && Files.exists(home.getParent().resolve("lib/tools.jar"))) {
                return home.getParent().toAbsolutePath().toString();
            }
            return home.toAbsolutePath().toString();
        }

        private Path toPath(final URL url) {
            if ("jar".equals(url.getProtocol())) {
                try {
                    String spec = url.getFile();
                    int separator = spec.indexOf('!');
                    if (separator == -1) {
                        return null;
                    }
                    return toPath(new URL(spec.substring(0, separator + 1)));
                } catch (MalformedURLException e) {
                    // no-op
                }
            } else if ("file".equals(url.getProtocol())) {
                String path = decode(url.getFile());
                if (path.endsWith("!")) {
                    path = path.substring(0, path.length() - 1);
                }
                return new File(path).getAbsoluteFile().toPath();
            }
            return null;
        }

        private String decode(String fileName) {
            if (fileName.indexOf('%') == -1) {
                return fileName;
            }

            final StringBuilder result = new StringBuilder(fileName.length());
            final ByteArrayOutputStream out = new ByteArrayOutputStream();

            for (int i = 0; i < fileName.length(); ) {
                final char c = fileName.charAt(i);

                if (c == '%') {
                    out.reset();
                    do {
                        if (i + 2 >= fileName.length()) {
                            throw new IllegalArgumentException("Incomplete % sequence at: " + i);
                        }

                        final int d1 = Character.digit(fileName.charAt(i + 1), 16);
                        final int d2 = Character.digit(fileName.charAt(i + 2), 16);

                        if (d1 == -1 || d2 == -1) {
                            throw new IllegalArgumentException(
                                    "Invalid % sequence (" + fileName.substring(i, i + 3) + ") at: " + i);
                        }

                        out.write((byte) ((d1 << 4) + d2));

                        i += 3;

                    } while (i < fileName.length() && fileName.charAt(i) == '%');

                    result.append(out.toString());

                    continue;
                } else {
                    result.append(c);
                }

                i++;
            }
            return result.toString();
        }

        private boolean postLoad(boolean resolve, Class clazz) {
            if (clazz != null) {
                if (resolve) {
                    resolveClass(clazz);
                }
                return true;
            }
            return false;
        }

        // not all jvm classes, for ex "javax" can be overriden so don't list it here
        private boolean isDirectJvmClass(final String name) {
            if (name.startsWith("java.")) {
                return true;
            }
            if (name.startsWith("sun.")) {
                return true;
            }
            if (name.startsWith("jdk.")) {
                return true;
            }
            if (name.startsWith("oracle.")) {
                return true;
            }
            if (name.startsWith("javafx.")) {
                return true;
            }
            if (name.startsWith("netscape.")) {
                return true;
            }
            if (name.startsWith("org.")) {
                final String sub = name.substring("org.".length());
                if (sub.startsWith("w3c.dom.")) {
                    return true;
                }
                if (sub.startsWith("omg.")) {
                    return true;
                }
                if (sub.startsWith("xml.sax.")) {
                    return true;
                }
                if (sub.startsWith("ietf.jgss.")) {
                    return true;
                }
                if (sub.startsWith("jcp.xml.dsig.internal.")) {
                    return true;
                }
            }
            if (name.startsWith("com.")) {
                final String sub = name.substring("com.".length());
                if (sub.startsWith("oracle.")) {
                    return true;
                }
                if (sub.startsWith("sun.")) {
                    final String subSun = sub.substring("sun.".length());
                    if (subSun.startsWith("accessibility.")) {
                        return true;
                    }
                    if (subSun.startsWith("activation.")) {
                        return true;
                    }
                    if (subSun.startsWith("awt.")) {
                        return true;
                    }
                    if (subSun.startsWith("beans.")) {
                        return true;
                    }
                    if (subSun.startsWith("corba.se.")) {
                        return true;
                    }
                    if (subSun.startsWith("demo.jvmti.")) {
                        return true;
                    }
                    if (subSun.startsWith("image.codec.jpeg.")) {
                        return true;
                    }
                    if (subSun.startsWith("imageio.")) {
                        return true;
                    }
                    if (subSun.startsWith("istack.internal.")) {
                        return true;
                    }
                    if (subSun.startsWith("java.")) {
                        return true;
                    }
                    if (subSun.startsWith("java_cup.")) {
                        return true;
                    }
                    if (subSun.startsWith("jmx.")) {
                        return true;
                    }
                    if (subSun.startsWith("jndi.")) {
                        return true;
                    }
                    if (subSun.startsWith("management.")) {
                        return true;
                    }
                    if (subSun.startsWith("media.sound.")) {
                        return true;
                    }
                    if (subSun.startsWith("naming.internal.")) {
                        return true;
                    }
                    if (subSun.startsWith("net.")) {
                        return true;
                    }
                    if (subSun.startsWith("nio.")) {
                        return true;
                    }
                    if (subSun.startsWith("org.")) {
                        return true;
                    }
                    if (subSun.startsWith("rmi.rmid.")) {
                        return true;
                    }
                    if (subSun.startsWith("rowset.")) {
                        return true;
                    }
                    if (subSun.startsWith("security.")) {
                        return true;
                    }
                    if (subSun.startsWith("swing.")) {
                        return true;
                    }
                    if (subSun.startsWith("tracing.")) {
                        return true;
                    }
                    if (subSun.startsWith("xml.internal.")) {
                        return true;
                    }
                    return false;
                }
            }
            return false;
        }

        private class FilteringUrlEnum implements Enumeration {
            private final Enumeration delegate;
            private URL next;

            private FilteringUrlEnum(Enumeration delegate) {
                this.delegate = delegate;
            }

            @Override
            public boolean hasMoreElements() {
                while (delegate.hasMoreElements()) {
                    next = delegate.nextElement();
                    if (isInJvm(next)) {
                        return true;
                    }
                }
                return false;
            }

            @Override
            public URL nextElement() {
                return next;
            }
        }

        private static class ChainedEnumerations implements Enumeration {
            private final Iterator> enumerations;
            private Enumeration current;

            private ChainedEnumerations(Iterator> enumerations) {
                this.enumerations = enumerations;
            }

            @Override
            public boolean hasMoreElements() {
                if (current == null || !current.hasMoreElements()) {
                    if (!enumerations.hasNext()) {
                        return false;
                    }
                    current = enumerations.next();
                }
                return current.hasMoreElements();
            }

            @Override
            public URL nextElement() {
                return current.nextElement();
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy