Package co.cask.tigon.lang

Source Code of co.cask.tigon.lang.ClassLoaders

/*
* 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");
  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);
  }
}
TOP

Related Classes of co.cask.tigon.lang.ClassLoaders

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.