summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGeorge Fraser <george@fivetran.com>2018-12-28 17:24:27 -0800
committerGeorge Fraser <george@fivetran.com>2018-12-28 17:24:27 -0800
commitc8cca030cd5313618e6d8b3071482419a92452bd (patch)
tree0a87e1e02bfafcebe9b9ba6fdf643dcadf0d381f /src
parentc527250e77f7242156a965e4286325dc1938ca88 (diff)
downloadjava-language-server-c8cca030cd5313618e6d8b3071482419a92452bd.zip
Fork ClassPath from guava
Diffstat (limited to 'src')
-rw-r--r--src/main/java/org/javacs/Classes.java2
-rw-r--r--src/main/java/org/javacs/CompileBatch.java15
-rw-r--r--src/main/java/org/javacs/InferSourcePath.java3
-rw-r--r--src/main/java/org/javacs/JavaCompilerService.java15
-rw-r--r--src/main/java/org/javacs/guava/ClassPath.java570
-rw-r--r--src/main/java/org/javacs/guava/README.md1
-rw-r--r--src/test/java/org/javacs/ClassesTest.java2
-rw-r--r--src/test/java/org/javacs/CodeLensTest.java3
8 files changed, 592 insertions, 19 deletions
diff --git a/src/main/java/org/javacs/Classes.java b/src/main/java/org/javacs/Classes.java
index a8a85c1..6249070 100644
--- a/src/main/java/org/javacs/Classes.java
+++ b/src/main/java/org/javacs/Classes.java
@@ -1,6 +1,5 @@
package org.javacs;
-import com.google.common.reflect.ClassPath;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
@@ -18,6 +17,7 @@ import java.util.function.Function;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
+import org.javacs.guava.ClassPath;
class Classes {
diff --git a/src/main/java/org/javacs/CompileBatch.java b/src/main/java/org/javacs/CompileBatch.java
index 9acd669..5a6fa95 100644
--- a/src/main/java/org/javacs/CompileBatch.java
+++ b/src/main/java/org/javacs/CompileBatch.java
@@ -191,7 +191,11 @@ public class CompileBatch {
var refs = new ArrayList<Ptr>();
class IndexFile extends TreePathScanner<Void, Void> {
void check(TreePath from) {
- ref(from).map(Ptr::new).ifPresent(refs::add);
+ var r = ref(from);
+ if (r.isPresent()) {
+ var ptr = new Ptr(r.get());
+ refs.add(ptr);
+ }
}
@Override
@@ -219,10 +223,11 @@ public class CompileBatch {
}
}
new IndexFile().scan(root, null);
- if (refs.size() > 0)
- LOG.info(
- String.format(
- "Found %d refs in %s", refs.size(), Paths.get(root.getSourceFile().toUri()).getFileName()));
+ if (refs.size() > 0) {
+ var n = refs.size();
+ var file = Parser.fileName(root.getSourceFile().toUri());
+ LOG.info(String.format("Found %d refs in %s", n, file));
+ }
return refs;
}
diff --git a/src/main/java/org/javacs/InferSourcePath.java b/src/main/java/org/javacs/InferSourcePath.java
index b6f30f2..f1fd1d2 100644
--- a/src/main/java/org/javacs/InferSourcePath.java
+++ b/src/main/java/org/javacs/InferSourcePath.java
@@ -46,7 +46,8 @@ class InferSourcePath {
var packagePath = packageName.replace('.', File.separatorChar);
var dir = java.getParent();
if (packagePath.isEmpty()) {
- return Optional.of(dir);
+ LOG.warning("Ignoring file with missing package declaration " + java);
+ return Optional.empty();
} else if (!dir.endsWith(packagePath)) {
LOG.warning("Java source file " + java + " is not in " + packagePath);
return Optional.empty();
diff --git a/src/main/java/org/javacs/JavaCompilerService.java b/src/main/java/org/javacs/JavaCompilerService.java
index cc73cb2..f59957d 100644
--- a/src/main/java/org/javacs/JavaCompilerService.java
+++ b/src/main/java/org/javacs/JavaCompilerService.java
@@ -265,16 +265,15 @@ public class JavaCompilerService {
return Optional.empty();
}
- private List<URI> potentialReferencesToClasses(String toPackage, List<String> toClasses) {
- // Enumerate all files on source path
- var allFiles = allJavaSources();
+ // TODO should probably cache this
+ private Collection<URI> potentialReferencesToClasses(String toPackage, List<String> toClasses) {
// Filter for files that import toPackage.toClass
- // TODO should probably cache this
- var result = new ArrayList<URI>();
- int nScanned = 0;
+ var result = new LinkedHashSet<URI>();
for (var dir : sourcePath) {
for (var file : javaSourcesInDir(dir)) {
- if (importsAnyClass(toPackage, toClasses, file)) result.add(file.toUri());
+ if (importsAnyClass(toPackage, toClasses, file)) {
+ result.add(file.toUri());
+ }
}
}
return result;
@@ -296,7 +295,7 @@ public class JavaCompilerService {
private Map<URI, Index> index = new HashMap<>();
- private void updateIndex(List<URI> possible) {
+ private void updateIndex(Collection<URI> possible) {
LOG.info(String.format("Check %d files for modifications compared to index...", possible.size()));
var outOfDate = new ArrayList<URI>();
for (var p : possible) {
diff --git a/src/main/java/org/javacs/guava/ClassPath.java b/src/main/java/org/javacs/guava/ClassPath.java
new file mode 100644
index 0000000..7a6ddac
--- /dev/null
+++ b/src/main/java/org/javacs/guava/ClassPath.java
@@ -0,0 +1,570 @@
+/*
+ * 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.javacs.guava;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.StandardSystemProperty.JAVA_CLASS_PATH;
+import static com.google.common.base.StandardSystemProperty.PATH_SEPARATOR;
+import static java.util.logging.Level.WARNING;
+
+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.ImmutableList;
+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.Entry;
+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;
+
+/**
+ * Scans the source of a {@link ClassLoader} and finds all loadable classes and resources.
+ *
+ * <p><b>Warning:</b> Current limitations:
+ *
+ * <ul>
+ * <li>Looks only for files and JARs in URLs available from {@link URLClassLoader} instances or the {@linkplain
+ * ClassLoader#getSystemClassLoader() system class loader}.
+ * <li>Only understands {@code file:} URLs.
+ * </ul>
+ *
+ * <p>In the case of directory classloaders, symlinks are supported but cycles are not traversed. This guarantees
+ * discovery of each <em>unique</em> 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 = Logger.getLogger(ClassPath.class.getName());
+
+ private static final Predicate<ClassInfo> IS_TOP_LEVEL =
+ new Predicate<ClassInfo>() {
+ @Override
+ public boolean apply(ClassInfo info) {
+ return info.className.indexOf('$') == -1;
+ }
+ };
+
+ /** 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<ResourceInfo> resources;
+
+ private ClassPath(ImmutableSet<ResourceInfo> resources) {
+ this.resources = resources;
+ }
+
+ /**
+ * Returns a {@code ClassPath} representing all classes and resources loadable from {@code classloader} and its
+ * ancestor class loaders.
+ *
+ * <p><b>Warning:</b> {@code ClassPath} can find classes and resources only from:
+ *
+ * <ul>
+ * <li>{@link URLClassLoader} instances' {@code file:} URLs
+ * <li>the {@linkplain ClassLoader#getSystemClassLoader() system class loader}. To search the system class loader
+ * even when it is not a {@link URLClassLoader} (as in Java 9), {@code ClassPath} searches the files from the
+ * {@code java.class.path} system property.
+ * </ul>
+ *
+ * @throws IOException if the attempt to read class path resources (jar files or directories) failed.
+ */
+ public static ClassPath from(ClassLoader classloader) throws IOException {
+ DefaultScanner scanner = new DefaultScanner();
+ scanner.scan(classloader);
+ return new ClassPath(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<ResourceInfo> getResources() {
+ return resources;
+ }
+
+ /**
+ * Returns all classes loadable from the current class path.
+ *
+ * @since 16.0
+ */
+ public ImmutableSet<ClassInfo> getAllClasses() {
+ return FluentIterable.from(resources).filter(ClassInfo.class).toSet();
+ }
+
+ /** Returns all top level classes loadable from the current class path. */
+ public ImmutableSet<ClassInfo> getTopLevelClasses() {
+ return FluentIterable.from(resources).filter(ClassInfo.class).filter(IS_TOP_LEVEL).toSet();
+ }
+
+ /** Returns all top level classes whose package name is {@code packageName}. */
+ public ImmutableSet<ClassInfo> getTopLevelClasses(String packageName) {
+ checkNotNull(packageName);
+ ImmutableSet.Builder<ClassInfo> builder = ImmutableSet.builder();
+ for (ClassInfo 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<ClassInfo> getTopLevelClassesRecursive(String packageName) {
+ checkNotNull(packageName);
+ String packagePrefix = packageName + '.';
+ ImmutableSet.Builder<ClassInfo> builder = ImmutableSet.builder();
+ for (ClassInfo 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
+ */
+ 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 ClassInfo(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.
+ *
+ * <p>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
+ */
+ public static final class ClassInfo extends ResourceInfo {
+ private final String className;
+
+ ClassInfo(String resourceName, ClassLoader loader) {
+ super(resourceName, loader);
+ this.className = getClassName(resourceName);
+ }
+
+ /**
+ * Returns the package name of the class, without attempting to load the class.
+ *
+ * <p>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.
+ *
+ * <p>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.
+ *
+ * <p>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<File> scannedUris = Sets.newHashSet();
+
+ public final void scan(ClassLoader classloader) throws IOException {
+ for (Entry<File, ClassLoader> entry : getClassPathEntries(classloader).entrySet()) {
+ scan(entry.getKey(), entry.getValue());
+ }
+ }
+
+ final void scan(File file, ClassLoader classloader) throws IOException {
+ if (scannedUris.add(file.getCanonicalFile())) {
+ scanFrom(file, classloader);
+ }
+ }
+
+ /** 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;
+
+ 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 <a
+ * href="http://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#Main_Attributes">JAR File
+ * Specification</a>. If {@code manifest} is null, it means the jar file has no manifest, and an empty set will
+ * be returned.
+ */
+ static ImmutableSet<File> getClassPathFromManifest(File jarFile, Manifest manifest) {
+ if (manifest == null) {
+ return ImmutableSet.of();
+ }
+ ImmutableSet.Builder<File> 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();
+ }
+
+ static ImmutableMap<File, ClassLoader> getClassPathEntries(ClassLoader classloader) {
+ LinkedHashMap<File, ClassLoader> 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));
+ }
+ for (URL url : getClassLoaderUrls(classloader)) {
+ if (url.getProtocol().equals("file")) {
+ File file = toFile(url);
+ if (!entries.containsKey(file)) {
+ entries.put(file, classloader);
+ }
+ }
+ }
+ return ImmutableMap.copyOf(entries);
+ }
+
+ private static ImmutableList<URL> getClassLoaderUrls(ClassLoader classloader) {
+ if (classloader instanceof URLClassLoader) {
+ return ImmutableList.copyOf(((URLClassLoader) classloader).getURLs());
+ }
+ if (classloader.equals(ClassLoader.getSystemClassLoader())) {
+ return parseJavaClassPath();
+ }
+ return ImmutableList.of();
+ }
+
+ /**
+ * Returns the URLs in the class path specified by the {@code java.class.path} {@linkplain System#getProperty
+ * system property}.
+ */
+ // TODO(b/65488446): Make this a public API.
+ static ImmutableList<URL> parseJavaClassPath() {
+ ImmutableList.Builder<URL> urls = ImmutableList.builder();
+ for (String entry : Splitter.on(PATH_SEPARATOR.value()).split(JAVA_CLASS_PATH.value())) {
+ try {
+ try {
+ urls.add(new File(entry).toURI().toURL());
+ } catch (SecurityException e) { // File.toURI checks to see if the file is a directory
+ urls.add(new URL("file", null, new File(entry).getAbsolutePath()));
+ }
+ } catch (MalformedURLException e) {
+ logger.log(WARNING, "malformed classpath entry: " + entry, e);
+ }
+ }
+ return urls.build();
+ }
+
+ /**
+ * Returns the absolute uri of the Class-Path entry value as specified in <a
+ * href="http://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#Main_Attributes">JAR File
+ * Specification</a>. Even though the specification only talks about relative urls, absolute urls are actually
+ * supported too (for example, in Maven surefire plugin).
+ */
+ static URL getClassPathEntry(File jarFile, String path) throws MalformedURLException {
+ return new URL(jarFile.toURI().toURL(), path);
+ }
+ }
+
+ static final class DefaultScanner extends Scanner {
+ private final SetMultimap<ClassLoader, String> resources =
+ MultimapBuilder.hashKeys().linkedHashSetValues().build();
+
+ ImmutableSet<ResourceInfo> getResources() {
+ ImmutableSet.Builder<ResourceInfo> builder = ImmutableSet.builder();
+ for (Entry<ClassLoader, String> entry : resources.entries()) {
+ builder.add(ResourceInfo.of(entry.getValue(), entry.getKey()));
+ }
+ return builder.build();
+ }
+
+ @Override
+ protected void scanJarFile(ClassLoader classloader, JarFile file) {
+ Enumeration<JarEntry> 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<File> 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<File> 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);
+ }
+ }
+ }
+ }
+ }
+
+ 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.
+
+ 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.
+ }
+ }
+}
diff --git a/src/main/java/org/javacs/guava/README.md b/src/main/java/org/javacs/guava/README.md
new file mode 100644
index 0000000..1145953
--- /dev/null
+++ b/src/main/java/org/javacs/guava/README.md
@@ -0,0 +1 @@
+Forked from Guava so we can eliminate the dependency and make jlink work. \ No newline at end of file
diff --git a/src/test/java/org/javacs/ClassesTest.java b/src/test/java/org/javacs/ClassesTest.java
index 035c22a..95212fe 100644
--- a/src/test/java/org/javacs/ClassesTest.java
+++ b/src/test/java/org/javacs/ClassesTest.java
@@ -3,11 +3,11 @@ package org.javacs;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
-import com.google.common.reflect.ClassPath;
import java.net.URI;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.util.Collections;
+import org.javacs.guava.ClassPath;
import org.junit.Ignore;
import org.junit.Test;
diff --git a/src/test/java/org/javacs/CodeLensTest.java b/src/test/java/org/javacs/CodeLensTest.java
index 29935c5..b77afc6 100644
--- a/src/test/java/org/javacs/CodeLensTest.java
+++ b/src/test/java/org/javacs/CodeLensTest.java
@@ -3,7 +3,6 @@ package org.javacs;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
-import com.google.gson.Gson;
import java.util.ArrayList;
import java.util.List;
import org.javacs.lsp.*;
@@ -20,8 +19,6 @@ public class CodeLensTest {
var resolved = new ArrayList<CodeLens>();
for (var lens : lenses) {
if (lens.command == null) {
- var gson = new Gson();
- var data = lens.data;
lens = server.resolveCodeLens(lens);
}
resolved.add(lens);