diff --git a/src/com/massivecraft/massivecore/util/ReflectionUtil.java b/src/com/massivecraft/massivecore/util/ReflectionUtil.java index f22a8af7..636501dc 100644 --- a/src/com/massivecraft/massivecore/util/ReflectionUtil.java +++ b/src/com/massivecraft/massivecore/util/ReflectionUtil.java @@ -4,7 +4,8 @@ import com.massivecraft.massivecore.collections.MassiveList; import com.massivecraft.massivecore.comparator.ComparatorNaturalOrder; import com.massivecraft.massivecore.predicate.Predicate; import com.massivecraft.massivecore.predicate.PredicateAnd; -import com.massivecraft.massivecore.util.XClassPath.XClassInfo; +import com.massivecraft.massivecore.xlib.guava.reflect.ClassPath; +import com.massivecraft.massivecore.xlib.guava.reflect.ClassPath.ClassInfo; import org.bukkit.Bukkit; import java.io.IOException; @@ -457,12 +458,12 @@ public class ReflectionUtil try { // Get info - XClassPath classPath = XClassPath.from(classLoader); + ClassPath classPath = ClassPath.from(classLoader); Predicate> predicateCombined = PredicateAnd.get(predicates); - Collection classInfos = recursive ? classPath.getTopLevelClassesRecursive(packageName) : classPath.getTopLevelClasses(packageName); + Collection classInfos = recursive ? classPath.getTopLevelClassesRecursive(packageName) : classPath.getTopLevelClasses(packageName); - for (XClassInfo classInfo : classInfos) + for (ClassInfo classInfo : classInfos) { // Get name of class String className = classInfo.getName(); diff --git a/src/com/massivecraft/massivecore/util/XClassPath.java b/src/com/massivecraft/massivecore/util/XClassPath.java deleted file mode 100644 index 59f8b4c5..00000000 --- a/src/com/massivecraft/massivecore/util/XClassPath.java +++ /dev/null @@ -1,558 +0,0 @@ -/* - * 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 com.massivecraft.massivecore.util; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; - -import com.google.common.annotations.Beta; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.CharMatcher; -import com.google.common.base.Predicate; -import com.google.common.base.Splitter; -import com.google.common.collect.FluentIterable; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Maps; -import com.google.common.collect.MultimapBuilder; -import com.google.common.collect.SetMultimap; -import com.google.common.collect.Sets; -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 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 java.util.logging.Logger; - -/** - * NOTE: - * This class was copied from: https://github.com/google/guava/blob/7c11b8d9220b710d5598b774e22cc774f68f40e7/guava/src/com/google/common/reflect/ClassPath.java - * So that we could get the benefits of an important bug fix in Guava 22 releated to spaces. - * https://github.com/MassiveCraft/MassiveCore/issues/307 - * Once Spigot updates to Guava 22 we are good to go again. - * - * Scans the source of a {@link ClassLoader} and finds all loadable classes and resources. - * - *

Warning: Currently only {@link URLClassLoader} and only {@code file://} urls are - * supported. - * - *

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 - */ -@Beta -public final class XClassPath { - private static final Logger logger = Logger.getLogger(XClassPath.class.getName()); - - private static final Predicate IS_TOP_LEVEL = - new Predicate() { - @Override - public boolean apply(XClassInfo info) { - return info.className.indexOf('$') == -1; - } - - @Override - public boolean test(XClassInfo info) - { - return this.apply(info); - } - }; - - /** 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 resources; - - private XClassPath(ImmutableSet resources) { - this.resources = resources; - } - - /** - * Returns a {@code XClassPath} representing all classes and resources loadable from {@code - * classloader} and its parent class loaders. - * - *

Warning: Currently only {@link URLClassLoader} and only {@code file://} urls are - * supported. - * - * @throws IOException if the attempt to read class path resources (jar files or directories) - * failed. - */ - public static XClassPath from(ClassLoader classloader) throws IOException { - DefaultScanner scanner = new DefaultScanner(); - scanner.scan(classloader); - return new XClassPath(scanner.getResources()); - } - - /** - * Returns all resources loadable from the current class path, including the class files of all - * loadable classes but excluding the "META-INF/MANIFEST.MF" file. - */ - public ImmutableSet getResources() { - return resources; - } - - /** - * Returns all classes loadable from the current class path. - * - * @since 16.0 - */ - public ImmutableSet getAllClasses() { - return FluentIterable.from(resources).filter(XClassInfo.class).toSet(); - } - - /** Returns all top level classes loadable from the current class path. */ - public ImmutableSet getTopLevelClasses() { - return FluentIterable.from(resources).filter(XClassInfo.class).filter(IS_TOP_LEVEL).toSet(); - } - - /** Returns all top level classes whose package name is {@code packageName}. */ - public ImmutableSet getTopLevelClasses(String packageName) { - checkNotNull(packageName); - ImmutableSet.Builder builder = ImmutableSet.builder(); - for (XClassInfo classInfo : getTopLevelClasses()) { - if (classInfo.getPackageName().equals(packageName)) { - builder.add(classInfo); - } - } - return builder.build(); - } - - /** - * Returns all top level classes whose package name is {@code packageName} or starts with - * {@code packageName} followed by a '.'. - */ - public ImmutableSet getTopLevelClassesRecursive(String packageName) { - checkNotNull(packageName); - String packagePrefix = packageName + '.'; - ImmutableSet.Builder builder = ImmutableSet.builder(); - for (XClassInfo classInfo : getTopLevelClasses()) { - if (classInfo.getName().startsWith(packagePrefix)) { - builder.add(classInfo); - } - } - return builder.build(); - } - - /** - * Represents a class path resource that can be either a class file or any other resource file - * loadable from the class path. - * - * @since 14.0 - */ - @Beta - public static class ResourceInfo { - private final String resourceName; - - final ClassLoader loader; - - static ResourceInfo of(String resourceName, ClassLoader loader) { - if (resourceName.endsWith(CLASS_FILE_NAME_EXTENSION)) { - return new XClassInfo(resourceName, loader); - } else { - return new ResourceInfo(resourceName, loader); - } - } - - ResourceInfo(String resourceName, ClassLoader loader) { - this.resourceName = checkNotNull(resourceName); - this.loader = checkNotNull(loader); - } - - /** - * Returns the url identifying the resource. - * - *

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; - } - - @Override - public int hashCode() { - return resourceName.hashCode(); - } - - @Override - public boolean equals(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 - */ - @Beta - public static final class XClassInfo extends ResourceInfo { - private final String className; - - XClassInfo(String resourceName, ClassLoader loader) { - super(resourceName, loader); - this.className = getClassName(resourceName); - } - - /** - * Returns the package name of the class, without attempting to load the class. - * - *

Behaves identically to {@link Package#getName()} but does not require the class (or - * package) to be loaded. - */ - public String getPackageName() { - return Reflection.getPackageName(className); - } - - /** - * Returns the simple name of the underlying class as given in the source code. - * - *

Behaves identically to {@link Class#getSimpleName()} but does not require the class to be - * loaded. - */ - 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.digit().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; - } - - /** - * 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; - } - } - - /** - * Abstract class that scans through the class path represented by a {@link ClassLoader} and calls - * {@link #scanDirectory} and {@link #scanJarFile} for directories and jar files on the class path - * respectively. - */ - abstract static class Scanner { - - // We only scan each file once independent of the classloader that resource might be associated - // with. - private final Set scannedUris = Sets.newHashSet(); - - public final void scan(ClassLoader classloader) throws IOException { - for (Map.Entry entry : getClassPathEntries(classloader).entrySet()) { - scan(entry.getKey(), entry.getValue()); - } - } - - /** Called when a directory is scanned for resource files. */ - protected abstract void scanDirectory(ClassLoader loader, File directory) throws IOException; - - /** Called when a jar file is scanned for resource entries. */ - protected abstract void scanJarFile(ClassLoader loader, JarFile file) throws IOException; - - @VisibleForTesting - final void scan(File file, ClassLoader classloader) throws IOException { - if (scannedUris.add(file.getCanonicalFile())) { - scanFrom(file, classloader); - } - } - - private void scanFrom(File file, ClassLoader classloader) throws IOException { - try { - if (!file.exists()) { - return; - } - } catch (SecurityException e) { - logger.warning("Cannot access " + file + ": " + e); - // TODO(emcmanus): consider whether to log other failure cases too. - return; - } - if (file.isDirectory()) { - scanDirectory(classloader, file); - } else { - scanJar(file, classloader); - } - } - - private void scanJar(File file, ClassLoader classloader) throws IOException { - JarFile jarFile; - try { - jarFile = new JarFile(file); - } catch (IOException e) { - // Not a jar file - return; - } - try { - for (File path : getClassPathFromManifest(file, jarFile.getManifest())) { - scan(path, classloader); - } - scanJarFile(classloader, jarFile); - } finally { - try { - jarFile.close(); - } catch (IOException ignored) { - } - } - } - - /** - * Returns the class path URIs specified by the {@code Class-Path} manifest attribute, according - * to - * - * JAR File Specification. If {@code manifest} is null, it means the jar file has no - * manifest, and an empty set will be returned. - */ - @VisibleForTesting - static ImmutableSet getClassPathFromManifest(File jarFile, Manifest manifest) { - if (manifest == null) { - return ImmutableSet.of(); - } - ImmutableSet.Builder builder = ImmutableSet.builder(); - String classpathAttribute = manifest.getMainAttributes().getValue(Attributes.Name.CLASS_PATH.toString()); - if (classpathAttribute != null) { - for (String path : CLASS_PATH_ATTRIBUTE_SEPARATOR.split(classpathAttribute)) { - URL url; - try { - url = getClassPathEntry(jarFile, path); - } catch (MalformedURLException e) { - // Ignore bad entry - logger.warning("Invalid Class-Path entry: " + path); - continue; - } - if (url.getProtocol().equals("file")) { - builder.add(toFile(url)); - } - } - } - return builder.build(); - } - - @VisibleForTesting - static ImmutableMap getClassPathEntries(ClassLoader classloader) { - LinkedHashMap entries = Maps.newLinkedHashMap(); - // Search parent first, since it's the order ClassLoader#loadClass() uses. - ClassLoader parent = classloader.getParent(); - if (parent != null) { - entries.putAll(getClassPathEntries(parent)); - } - if (classloader instanceof URLClassLoader) { - URLClassLoader urlClassLoader = (URLClassLoader) classloader; - for (URL entry : urlClassLoader.getURLs()) { - if (entry.getProtocol().equals("file")) { - File file = toFile(entry); - if (!entries.containsKey(file)) { - entries.put(file, classloader); - } - } - } - } - return ImmutableMap.copyOf(entries); - } - - /** - * Returns the absolute uri of the Class-Path entry value as specified in - * - * JAR File Specification. Even though the specification only talks about relative urls, - * absolute urls are actually supported too (for example, in Maven surefire plugin). - */ - @VisibleForTesting - static URL getClassPathEntry(File jarFile, String path) throws MalformedURLException { - return new URL(jarFile.toURI().toURL(), path); - } - } - - @VisibleForTesting - static final class DefaultScanner extends Scanner { - private final SetMultimap resources = - MultimapBuilder.hashKeys().linkedHashSetValues().build(); - - ImmutableSet getResources() { - ImmutableSet.Builder builder = ImmutableSet.builder(); - for (Map.Entry entry : resources.entries()) { - builder.add(ResourceInfo.of(entry.getValue(), entry.getKey())); - } - return builder.build(); - } - - @Override - protected void scanJarFile(ClassLoader classloader, JarFile file) { - Enumeration entries = file.entries(); - while (entries.hasMoreElements()) { - JarEntry entry = entries.nextElement(); - if (entry.isDirectory() || entry.getName().equals(JarFile.MANIFEST_NAME)) { - continue; - } - resources.get(classloader).add(entry.getName()); - } - } - - @Override - protected void scanDirectory(ClassLoader classloader, File directory) throws IOException { - Set currentPath = new HashSet(); - currentPath.add(directory.getCanonicalFile()); - scanDirectory(directory, classloader, "", currentPath); - } - - /** - * Recursively scan the given directory, adding resources for each file encountered. Symlinks - * which have already been traversed in the current tree path will be skipped to eliminate - * cycles; otherwise symlinks are traversed. - * - * @param directory the root of the directory to scan - * @param classloader the classloader that includes resources found in {@code directory} - * @param packagePrefix resource path prefix inside {@code classloader} for any files found - * under {@code directory} - * @param currentPath canonical files already visited in the current directory tree path, for - * cycle elimination - */ - private void scanDirectory(File directory, ClassLoader classloader, String packagePrefix, Set currentPath) throws IOException { - File[] files = directory.listFiles(); - if (files == null) { - logger.warning("Cannot read directory " + directory); - // IO error, just skip the directory - return; - } - for (File f : files) { - String name = f.getName(); - if (f.isDirectory()) { - File deref = f.getCanonicalFile(); - if (currentPath.add(deref)) { - scanDirectory(deref, classloader, packagePrefix + name + "/", currentPath); - currentPath.remove(deref); - } - } else { - String resourceName = packagePrefix + name; - if (!resourceName.equals(JarFile.MANIFEST_NAME)) { - resources.get(classloader).add(resourceName); - } - } - } - } - } - - @VisibleForTesting - static String getClassName(String filename) { - int classNameEnd = filename.length() - CLASS_FILE_NAME_EXTENSION.length(); - return filename.substring(0, classNameEnd).replace('/', '.'); - } - - // TODO(benyu): Try java.nio.file.Paths#get() when Guava drops JDK 6 support. - @VisibleForTesting - static File toFile(URL url) { - checkArgument(url.getProtocol().equals("file")); - try { - return new File(url.toURI()); // Accepts escaped characters like %20. - } catch (URISyntaxException e) { // URL.toURI() doesn't escape chars. - return new File(url.getPath()); // Accepts non-escaped chars like space. - } - } -}