diff --git a/kernel/src/main/scala/org/apache/toree/magic/builtin/BuiltinLoader.scala b/kernel/src/main/scala/org/apache/toree/magic/builtin/BuiltinLoader.scala index 927ed444a..2c90969b4 100644 --- a/kernel/src/main/scala/org/apache/toree/magic/builtin/BuiltinLoader.scala +++ b/kernel/src/main/scala/org/apache/toree/magic/builtin/BuiltinLoader.scala @@ -17,10 +17,11 @@ package org.apache.toree.magic.builtin -import com.google.common.reflect.ClassPath -import com.google.common.reflect.ClassPath.ClassInfo import org.apache.toree.magic.InternalClassLoader import com.google.common.base.Strings._ +import org.apache.toree.utils.ClassPath +import org.apache.toree.utils.ClassPath.ClassInfo + import scala.collection.JavaConverters._ /** @@ -38,15 +39,13 @@ class BuiltinLoader * @return list of ClassInfo objects */ def getClasses(pkg: String = pkgName): List[ClassInfo] = { - isNullOrEmpty(pkg) match { - case true => - List() - case false => - // TODO: Decide if this.getClass.getClassLoader should just be this - val classPath = ClassPath.from(this.getClass.getClassLoader) - classPath.getTopLevelClasses(pkg).asScala.filter( - _.getSimpleName != this.getClass.getSimpleName - ).toList + if (isNullOrEmpty(pkg)) { + List.empty + } else { + // TODO: Decide if this.getClass.getClassLoader should just be this + val classPath = ClassPath.from(this.getClass.getClassLoader) + classPath.getTopLevelClasses(pkg).asScala + .filter(_.getSimpleName != this.getClass.getSimpleName).toList } } diff --git a/kernel/src/main/scala/org/apache/toree/utils/ClassPath.java b/kernel/src/main/scala/org/apache/toree/utils/ClassPath.java new file mode 100644 index 000000000..42f33f0c6 --- /dev/null +++ b/kernel/src/main/scala/org/apache/toree/utils/ClassPath.java @@ -0,0 +1,720 @@ +/* + * 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 + */ + +// Below are original header from Guava project + +/* + * Copyright (C) 2012 The Guava Authors + * + * 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 org.apache.toree.utils; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.CharMatcher; +import com.google.common.base.Splitter; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Maps; +import com.google.common.io.ByteSource; +import com.google.common.io.CharSource; +import com.google.common.io.Resources; +import com.google.common.reflect.Reflection; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.charset.Charset; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.jar.Attributes; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.Manifest; +import javax.annotation.CheckForNull; + +// This class is derived from Guava v32.1.2 +// https://github.com/google/guava/blob/v32.1.2/guava/src/com/google/common/reflect/ClassPath.java +// +// TOREE-552: Apache Toree should use Guava 14 to align with Apache Spark, unfortunately, ClassPath +// from Guava 14 does not work well with Java 11+. +// See detail at: https://github.com/google/guava/issues/3249 + +/** + * Scans the source of a {@link ClassLoader} and finds all loadable classes and resources. + * + *
We recommend using ClassGraph + * instead of {@code ClassPath}. ClassGraph improves upon {@code ClassPath} in several ways, + * including addressing many of its limitations. Limitations of {@code ClassPath} include: + * + *
In the case of directory classloaders, symlinks are supported but cycles are not traversed.
+ * This guarantees discovery of each unique loadable resource. However, not all possible
+ * aliases for resources on cyclic paths will be listed.
+ *
+ * @author Ben Yu
+ * @since 14.0
+ */
+public final class ClassPath {
+ private static final Logger logger = LoggerFactory.getLogger(ClassPath.class.getName());
+
+ /**
+ * Separator for the Class-Path manifest attribute value in jar files.
+ */
+ private static final Splitter CLASS_PATH_ATTRIBUTE_SEPARATOR =
+ Splitter.on(" ").omitEmptyStrings();
+
+ private static final String CLASS_FILE_NAME_EXTENSION = ".class";
+
+ private final ImmutableSet Warning: {@code ClassPath} can find classes and resources only from:
+ *
+ * See {@link ClassLoader#getResource}
+ *
+ * @throws NoSuchElementException if the resource cannot be loaded through the class loader,
+ * despite physically existing in the class path.
+ */
+ public final URL url() {
+ URL url = loader.getResource(resourceName);
+ if (url == null) {
+ throw new NoSuchElementException(resourceName);
+ }
+ return url;
+ }
+
+ /**
+ * Returns a {@link ByteSource} view of the resource from which its bytes can be read.
+ *
+ * @throws NoSuchElementException if the resource cannot be loaded through the class loader,
+ * despite physically existing in the class path.
+ * @since 20.0
+ */
+ public final ByteSource asByteSource() {
+ return Resources.asByteSource(url());
+ }
+
+ /**
+ * Returns a {@link CharSource} view of the resource from which its bytes can be read as
+ * characters decoded with the given {@code charset}.
+ *
+ * @throws NoSuchElementException if the resource cannot be loaded through the class loader,
+ * despite physically existing in the class path.
+ * @since 20.0
+ */
+ public final CharSource asCharSource(Charset charset) {
+ return Resources.asCharSource(url(), charset);
+ }
+
+ /**
+ * Returns the fully qualified name of the resource. Such as "com/mycomp/foo/bar.txt".
+ */
+ public final String getResourceName() {
+ return resourceName;
+ }
+
+ /**
+ * Returns the file that includes this resource.
+ */
+ final File getFile() {
+ return file;
+ }
+
+ @Override
+ public int hashCode() {
+ return resourceName.hashCode();
+ }
+
+ @Override
+ public boolean equals(@CheckForNull Object obj) {
+ if (obj instanceof ResourceInfo) {
+ ResourceInfo that = (ResourceInfo) obj;
+ return resourceName.equals(that.resourceName) && loader == that.loader;
+ }
+ return false;
+ }
+
+ // Do not change this arbitrarily. We rely on it for sorting ResourceInfo.
+ @Override
+ public String toString() {
+ return resourceName;
+ }
+ }
+
+ /**
+ * Represents a class that can be loaded through {@link #load}.
+ *
+ * @since 14.0
+ */
+ public static final class ClassInfo extends ResourceInfo {
+ private final String className;
+
+ ClassInfo(File file, String resourceName, ClassLoader loader) {
+ super(file, resourceName, loader);
+ this.className = getClassName(resourceName);
+ }
+
+ /**
+ * Returns the package name of the class, without attempting to load the class.
+ *
+ * Behaves similarly to {@code class.getPackage().}{@link Package#getName() getName()} but
+ * does not require the class (or package) to be loaded.
+ *
+ * But note that this method may behave differently for a class in the default package: For
+ * such classes, this method always returns an empty string. But under some version of Java,
+ * {@code class.getPackage().getName()} produces a {@code NullPointerException} because {@code
+ * class.getPackage()} returns {@code null}.
+ */
+ public String getPackageName() {
+ return Reflection.getPackageName(className);
+ }
+
+ /**
+ * Returns the simple name of the underlying class as given in the source code.
+ *
+ * Behaves similarly to {@link Class#getSimpleName()} but does not require the class to be
+ * loaded.
+ *
+ * But note that this class uses heuristics to identify the simple name. See a related
+ * discussion in issue 3349.
+ */
+ public String getSimpleName() {
+ int lastDollarSign = className.lastIndexOf('$');
+ if (lastDollarSign != -1) {
+ String innerClassName = className.substring(lastDollarSign + 1);
+ // local and anonymous classes are prefixed with number (1,2,3...), anonymous classes are
+ // entirely numeric whereas local classes have the user supplied name as a suffix
+ return CharMatcher.inRange('0', '9').trimLeadingFrom(innerClassName);
+ }
+ String packageName = getPackageName();
+ if (packageName.isEmpty()) {
+ return className;
+ }
+
+ // Since this is a top level class, its simple name is always the part after package name.
+ return className.substring(packageName.length() + 1);
+ }
+
+ /**
+ * Returns the fully qualified name of the class.
+ *
+ * Behaves identically to {@link Class#getName()} but does not require the class to be
+ * loaded.
+ */
+ public String getName() {
+ return className;
+ }
+
+ /**
+ * Returns true if the class name "looks to be" top level (not nested), that is, it includes no
+ * '$' in the name. This method may return false for a top-level class that's intentionally
+ * named with the '$' character. If this is a concern, you could use {@link #load} and then
+ * check on the loaded {@link Class} object instead.
+ *
+ * @since 30.1
+ */
+ public boolean isTopLevel() {
+ return className.indexOf('$') == -1;
+ }
+
+ /**
+ * Loads (but doesn't link or initialize) the class.
+ *
+ * @throws LinkageError when there were errors in loading classes that this class depends on.
+ * For example, {@link NoClassDefFoundError}.
+ */
+ public Class> load() {
+ try {
+ return loader.loadClass(className);
+ } catch (ClassNotFoundException e) {
+ // Shouldn't happen, since the class name is read from the class path.
+ throw new IllegalStateException(e);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return className;
+ }
+ }
+
+ /**
+ * Returns all locations that {@code classloader} and parent loaders load classes and resources
+ * from. Callers can {@linkplain LocationInfo#scanResources scan} individual locations selectively
+ * or even in parallel.
+ */
+ static ImmutableSet This file and jar files from "Class-Path" entry in the scanned manifest files will be
+ * added to {@code scannedFiles}.
+ *
+ * A file will be scanned at most once even if specified multiple times by one or multiple
+ * jar files' "Class-Path" manifest entries. Particularly, if a jar file from the "Class-Path"
+ * manifest entry is already in {@code scannedFiles}, either because it was scanned earlier, or
+ * it was intentionally added to the set by the caller, it will not be scanned again.
+ *
+ * Note that when you call {@code location.scanResources(scannedFiles)}, the location will
+ * always be scanned even if {@code scannedFiles} already contains it.
+ */
+ public ImmutableSet
+ *
+ *
+ * @throws IOException if the attempt to read class path resources (jar files or directories)
+ * failed.
+ */
+ public static ClassPath from(ClassLoader classloader) throws IOException {
+ ImmutableSet