co.cask.cdap.common.lang.ClassPathResources.java Source code

Java tutorial

Introduction

Here is the source code for co.cask.cdap.common.lang.ClassPathResources.java

Source

/*
 * Copyright  2016 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.cdap.common.lang;

import co.cask.cdap.common.internal.guava.ClassPath;
import co.cask.cdap.common.internal.guava.ClassPath.ResourceInfo;
import com.google.common.base.Function;
import com.google.common.base.Splitter;
import com.google.common.base.Throwables;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import org.apache.twill.api.ClassAcceptor;
import org.apache.twill.internal.utils.Dependencies;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Collection;
import java.util.Set;

/**
 * Utility methods for {@link ClassPath} {@link ResourceInfo resources}.
 */
public final class ClassPathResources {

    public static final Function<ClassPath.ClassInfo, String> CLASS_INFO_TO_CLASS_NAME = new Function<ClassPath.ClassInfo, String>() {
        @Override
        public String apply(ClassPath.ClassInfo input) {
            return input.getName();
        }
    };
    public static final Function<ClassPath.ResourceInfo, String> RESOURCE_INFO_TO_RESOURCE_NAME = new Function<ClassPath.ResourceInfo, String>() {
        @Override
        public String apply(ClassPath.ResourceInfo input) {
            return input.getResourceName();
        }
    };

    /**
     * Returns the base set of resources needed to load the specified {@link Class} using the
     * specified {@link ClassLoader}. Also traces and includes the dependencies for the specified class.
     *
     * @param classLoader the {@link ClassLoader} to use to generate the set of resources
     * @param classz the {@link Class} to generate the set of resources for
     * @return the set of resources needed to load the specified {@link Class} using the specified {@link ClassLoader}
     * @throws IOException
     */
    public static Set<String> getResourcesWithDependencies(ClassLoader classLoader, Class<?> classz)
            throws IOException {
        ClassPath classPath = getClassPath(classLoader, classz);

        // Add everything in the classpath as visible resources
        Set<String> result = Sets
                .newHashSet(Iterables.transform(classPath.getResources(), RESOURCE_INFO_TO_RESOURCE_NAME));
        // Trace dependencies for all classes in the classpath
        findClassDependencies(classLoader, Iterables.transform(classPath.getAllClasses(), CLASS_INFO_TO_CLASS_NAME),
                result);

        return result;
    }

    /**
     * Returns a set of {@link ResourceInfo} required for the specified {@link Class} using the specified
     * {@link ClassLoader}. Does not trace dependencies of the specified class.
     *
     * @param classLoader the {@link ClassLoader} to use to return the set of {@link ResourceInfo} for the specified
     *                    {@link Class}
     * @param cls the {@link Class} for which to return the set of {@link ResourceInfo}
     * @return the set of {@link ResourceInfo} required for the specified {@link Class} using the specified
     * {@link ClassLoader}
     */
    public static Set<ClassPath.ResourceInfo> getClassPathResources(ClassLoader classLoader, Class<?> cls)
            throws IOException {
        return getClassPath(classLoader, cls).getResources();
    }

    /**
     * Returns a {@link ClassPath} instance that represents the classpath that the given class is loaded from the given
     * ClassLoader.
     */
    private static ClassPath getClassPath(ClassLoader classLoader, Class<?> cls) throws IOException {
        String resourceName = cls.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);
        }
    }

    /**
     * Finds all resource names that the given set of classes depends on.
     *
     * @param classLoader class loader for looking up .class resources
     * @param classes set of class names that need to trace dependencies from
     * @param result collection to store the resulting resource names
     * @param <T> type of the result collection
     * @throws IOException if fails to load class bytecode during tracing
     */
    private static <T extends Collection<String>> T findClassDependencies(final ClassLoader classLoader,
            Iterable<String> classes, final T result) throws IOException {
        final Set<String> bootstrapClassPaths = getBootstrapClassPaths();
        final Set<URL> classPathSeen = Sets.newHashSet();

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

                // Should ignore classes from SLF4J implementation, otherwise it will includes logback lib, which shouldn't be
                // visible through the program classloader.
                if (className.startsWith("org.slf4j.impl.")) {
                    return false;
                }

                if (!classPathSeen.add(classPathUrl)) {
                    return true;
                }

                // Add all resources in the given class path
                try {
                    ClassPath classPath = ClassPath.from(classPathUrl.toURI(), classLoader);
                    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;
    }

    /**
     * Returns a Set containing all bootstrap classpaths as defined in the {@code sun.boot.class.path} property.
     */
    private static Set<String> getBootstrapClassPaths() {
        // Get the bootstrap classpath. This is for exclusion while tracing class dependencies.
        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.
            }
        }
        return bootstrapPaths;
    }

    /**
     * Find the URL of 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 ClassPathResources() {
    }
}