co.cask.tigon.lang.ClassLoaders.java Source code

Java tutorial

Introduction

Here is the source code for co.cask.tigon.lang.ClassLoaders.java

Source

/*
 * Copyright  2014 Cask Data, Inc.
 *
 * Licensed 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 co.cask.tigon.lang;

import co.cask.tigon.api.flow.Flow;
import co.cask.tigon.internal.guava.ClassPath;
import co.cask.tigon.lang.jar.ProgramClassLoader;
import com.google.common.base.Objects;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.base.Splitter;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.reflect.TypeToken;
import org.apache.twill.internal.utils.Dependencies;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Collection;
import java.util.List;
import java.util.Queue;
import java.util.Set;
import javax.annotation.Nullable;

/**
 * Utility class for collection of methods for dealing with ClassLoader and loading class.
 */
public final class ClassLoaders {

    private static final List<String> HADOOP_PACKAGES = Lists.newArrayList("org.apache.hadoop");
    private static final List<String> TIGON_API_PACKAGES = Lists.newArrayList("co.cask.tigon.api",
            "co.cask.tigon.internal");
    private static final Predicate<URI> JAR_ONLY_URI = new Predicate<URI>() {
        @Override
        public boolean apply(URI input) {
            return input.getPath().endsWith(".jar");
        }
    };

    private ClassLoaders() {
    }

    public static ProgramClassLoader newProgramClassLoader(File unpackedJarDir, Iterable<String> apiResourceList)
            throws IOException {
        Predicate<String> predicate = Predicates.in(Sets.newHashSet(apiResourceList));
        ClassLoader filterParent = Objects.firstNonNull(Thread.currentThread().getContextClassLoader(),
                ClassLoaders.class.getClassLoader());
        return new ProgramClassLoader(unpackedJarDir, new FilterClassLoader(predicate, filterParent));
    }

    public static Iterable<String> getAPIResources(ClassLoader classLoader) throws IOException {
        // Get the bootstrap classpath. This is for exclusion.
        Set<String> bootstrapPaths = Sets.newHashSet();
        for (String classpath : Splitter.on(File.pathSeparatorChar)
                .split(System.getProperty("sun.boot.class.path"))) {
            File file = new File(classpath);
            bootstrapPaths.add(file.getAbsolutePath());
            try {
                bootstrapPaths.add(file.getCanonicalPath());
            } catch (IOException e) {
                // Ignore the exception and proceed.
            }
        }

        Set<String> resources = getResources(classLoader, getAPIClassPath(), TIGON_API_PACKAGES, true,
                bootstrapPaths, Sets.<String>newHashSet());

        return getResources(classLoader, ClassPath.from(classLoader, JAR_ONLY_URI), HADOOP_PACKAGES, false,
                bootstrapPaths, resources);
    }

    /**
     * Returns the ClassLoader of the given type. If the given type is a {@link java.lang.reflect.ParameterizedType},
     * it returns a {@link CombineClassLoader} of all types. The context ClassLoader or System ClassLoader would be used
     * as the parent of the CombineClassLoader.
     *
     * @return A new CombineClassLoader. If no ClassLoader is found from the type,
     *         it returns the current thread context ClassLoader if it's not null, otherwise, return system ClassLoader.
     */
    public static ClassLoader getClassLoader(TypeToken<?> type) {
        Set<ClassLoader> classLoaders = Sets.newIdentityHashSet();

        // Breath first traversal into the Type.
        Queue<TypeToken<?>> queue = Lists.newLinkedList();
        queue.add(type);
        while (!queue.isEmpty()) {
            type = queue.remove();
            ClassLoader classLoader = type.getRawType().getClassLoader();
            if (classLoader != null) {
                classLoaders.add(classLoader);
            }

            if (type.getType() instanceof ParameterizedType) {
                for (Type typeArg : ((ParameterizedType) type.getType()).getActualTypeArguments()) {
                    queue.add(TypeToken.of(typeArg));
                }
            }
        }

        // Determine the parent classloader
        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
        ClassLoader parent = (contextClassLoader == null) ? ClassLoader.getSystemClassLoader() : contextClassLoader;

        if (classLoaders.isEmpty()) {
            return parent;
        }
        return new CombineClassLoader(parent, classLoaders);
    }

    /**
     * Gathers all resources for api classes.
     */
    private static ClassPath getAPIClassPath() throws IOException {
        ClassLoader classLoader = Flow.class.getClassLoader();
        String resourceName = Flow.class.getName().replace('.', '/') + ".class";
        URL url = classLoader.getResource(resourceName);
        if (url == null) {
            throw new IOException("Resource not found for " + resourceName);
        }

        try {
            URI classPathURI = getClassPathURL(resourceName, url).toURI();
            return ClassPath.from(classPathURI, classLoader);
        } catch (URISyntaxException e) {
            throw new IOException(e);
        }
    }

    /**
     * Find the classpath that contains the given resource.
     */
    private static URL getClassPathURL(String resourceName, URL resourceURL) {
        try {
            if ("file".equals(resourceURL.getProtocol())) {
                String path = resourceURL.getFile();
                // Compute the directory container the class.
                int endIdx = path.length() - resourceName.length();
                if (endIdx > 1) {
                    // If it is not the root directory, return the end index to remove the trailing '/'.
                    endIdx--;
                }
                return new URL("file", "", -1, path.substring(0, endIdx));
            }
            if ("jar".equals(resourceURL.getProtocol())) {
                String path = resourceURL.getFile();
                return URI.create(path.substring(0, path.indexOf("!/"))).toURL();
            }
        } catch (MalformedURLException e) {
            throw Throwables.propagate(e);
        }
        throw new IllegalStateException("Unsupported class URL: " + resourceURL);
    }

    private static <T extends Collection<String>> T getResources(ClassLoader classLoader, ClassPath classPath,
            Iterable<String> packages, boolean includeDependencies, final Set<String> bootstrapPaths,
            final T result) throws IOException {
        Set<String> classes = Sets.newHashSet();
        for (String pkg : packages) {
            ImmutableSet<ClassPath.ClassInfo> packageClasses = classPath.getAllClassesRecursive(pkg);
            for (ClassPath.ClassInfo cls : packageClasses) {
                result.add(cls.getResourceName());
                classes.add(cls.getName());
            }
        }

        if (includeDependencies) {
            final Set<URL> classPathSeen = Sets.newHashSet();

            Dependencies.findClassDependencies(classLoader, new Dependencies.ClassAcceptor() {
                @Override
                public boolean accept(String className, URL classUrl, URL classPathUrl) {
                    if (bootstrapPaths.contains(classPathUrl.getFile())) {
                        return false;
                    }

                    // Add all resources in the given class path
                    if (!classPathSeen.add(classPathUrl)) {
                        return true;
                    }

                    try {
                        ClassPath classPath = ClassPath.from(classPathUrl.toURI(),
                                ClassLoader.getSystemClassLoader());
                        for (ClassPath.ResourceInfo resourceInfo : classPath.getResources()) {
                            result.add(resourceInfo.getResourceName());
                        }
                    } catch (Exception e) {
                        // If fail to get classes/resources from the classpath, ignore this classpath.
                    }
                    return true;
                }
            }, classes);
        }

        return result;
    }

    /**
     * Loads the class with the given class name with the given classloader. If it is {@code null},
     * load the class with the context ClassLoader of current thread if it presents, otherwise load the class
     * with the ClassLoader of the caller object.
     *
     * @param className Name of the class to load.
     * @param classLoader Classloader for loading the class. It could be {@code null}.
     * @param caller The object who call this method.
     * @return The loaded class.
     * @throws ClassNotFoundException If failed to load the given class.
     */
    public static Class<?> loadClass(String className, @Nullable ClassLoader classLoader, Object caller)
            throws ClassNotFoundException {
        ClassLoader cl = Objects.firstNonNull(classLoader, Objects
                .firstNonNull(Thread.currentThread().getContextClassLoader(), caller.getClass().getClassLoader()));
        return cl.loadClass(className);
    }
}