diff options
author | George Fraser <george@fivetran.com> | 2017-11-08 23:56:30 -0800 |
---|---|---|
committer | George Fraser <george@fivetran.com> | 2017-11-09 16:39:50 -0800 |
commit | 2bd269b5338689bcc0492e28b5646ecb7c22fb5b (patch) | |
tree | 76ca79c158fcc404abf0fea21aec72678b86f684 | |
parent | 123793d79e24479701ea273ed82f950ccfb79e17 (diff) | |
download | java-language-server-2bd269b5338689bcc0492e28b5646ecb7c22fb5b.zip |
Run with custom classloader
-rw-r--r-- | TODOS.md | 25 | ||||
-rw-r--r-- | pom.xml | 2 | ||||
-rw-r--r-- | src/main/java/org/javacs/ChildFirstClassLoader.java | 64 | ||||
-rw-r--r-- | src/main/java/org/javacs/LangTools.java | 26 | ||||
-rw-r--r-- | src/main/java/org/javacs/Main.java | 14 | ||||
-rw-r--r-- | src/test/java/org/javacs/ChildFirstClassLoaderTest.java | 31 |
6 files changed, 160 insertions, 2 deletions
@@ -11,6 +11,31 @@ * implements | doesn't autocomplete * super.[protected member] doesn't autocomplete * When there is an annotation processor in the *client* dependencies, linting fails +* When file is open but no longer on disk, language server fails with + ``` + Caused by: java.lang.RuntimeException: java.nio.file.NoSuchFileException: /Users/george/Documents/vscode-javac/src/test/java/org/javacs/MainTest.java + at org.javacs.SymbolIndex.needsUpdate(SymbolIndex.java:86) + at org.javacs.SymbolIndex.updateFile(SymbolIndex.java:70) + at java.util.Iterator.forEachRemaining(Iterator.java:116) + at java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1801) + at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580) + at org.javacs.SymbolIndex.updateIndex(SymbolIndex.java:66) + at org.javacs.SymbolIndex.updateOpenFiles(SymbolIndex.java:152) + at org.javacs.SymbolIndex.sourcePath(SymbolIndex.java:100) + at org.javacs.JavaLanguageServer.configured(JavaLanguageServer.java:59) + at org.javacs.JavaTextDocumentService.hover(JavaTextDocumentService.java:156) + ... 15 more + Caused by: java.nio.file.NoSuchFileException: /Users/george/Documents/vscode-javac/src/test/java/org/javacs/MainTest.java + at sun.nio.fs.UnixException.translateToIOException(UnixException.java:86) + at sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:102) + at sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:107) + at sun.nio.fs.UnixFileAttributeViews$Basic.readAttributes(UnixFileAttributeViews.java:55) + at sun.nio.fs.UnixFileSystemProvider.readAttributes(UnixFileSystemProvider.java:144) + at java.nio.file.Files.readAttributes(Files.java:1737) + at java.nio.file.Files.getLastModifiedTime(Files.java:2266) + at org.javacs.SymbolIndex.needsUpdate(SymbolIndex.java:82) + ... 24 more + ``` ## Default configuration * Alert if we can't find a dependency @@ -91,7 +91,7 @@ <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> - <version>4.11</version> + <version>4.12</version> <scope>test</scope> </dependency> <dependency> diff --git a/src/main/java/org/javacs/ChildFirstClassLoader.java b/src/main/java/org/javacs/ChildFirstClassLoader.java new file mode 100644 index 0000000..e619f04 --- /dev/null +++ b/src/main/java/org/javacs/ChildFirstClassLoader.java @@ -0,0 +1,64 @@ +package org.javacs; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.logging.Logger; + +class ChildFirstClassLoader extends ClassLoader { + private final ClassLoader child, parent; + private final String[] packages; + private static final Logger LOG = Logger.getLogger("main"); + + private boolean loadFromChild(String className) { + for (String p : packages) { + if (className.startsWith(p)) return true; + } + + return false; + } + + static URL[] parseClassPath(String classPath) { + return Arrays.stream(classPath.split(File.pathSeparator)) + .map(ChildFirstClassLoader::parse) + .toArray(URL[]::new); + } + + private static URL parse(String urlString) { + try { + if (urlString.startsWith("/")) return Paths.get(urlString).toUri().toURL(); + else return new URL(urlString); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } + + static ChildFirstClassLoader fromClassPath( + String classPath, String[] packages, ClassLoader parent) { + ClassLoader child = new URLClassLoader(parseClassPath(classPath), null); + return new ChildFirstClassLoader(child, packages, parent); + } + + private ChildFirstClassLoader(ClassLoader child, String[] packages, ClassLoader parent) { + this.child = child; + this.parent = parent; + this.packages = packages; + } + + @Override + protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { + boolean fromChild = loadFromChild(name); + Class<?> c = findLoadedClass(name); + + if (c == null && fromChild) c = child.loadClass(name); + + if (c == null) c = parent.loadClass(name); + + if (resolve) resolveClass(c); + + return c; + } +} diff --git a/src/main/java/org/javacs/LangTools.java b/src/main/java/org/javacs/LangTools.java new file mode 100644 index 0000000..52d8e33 --- /dev/null +++ b/src/main/java/org/javacs/LangTools.java @@ -0,0 +1,26 @@ +package org.javacs; + +import java.util.Objects; + +/** + * Loads langtools off the classpath, which contains our custom build of javac, instead of the + * platform. In Java 8, this was not necessary because tools.jar isn't loaded by default, so java + * only sees our custom build of javac. But in Java 9, tools.jar is in the main platform, so we need + * to avoid loading it through a custom classloader. + */ +class LangTools { + public static final String[] LANGTOOLS_PACKAGES = { + "com.sun.source", + "com.sun.tools", + "javax.annotation.processing", + "javax.lang.model", + "javax.tools" + }; + + public static ClassLoader createLangToolsClassLoader() { + String classPath = System.getProperty("java.class.path"); + Objects.requireNonNull(classPath, "java.class.path was null"); + ClassLoader parent = LangTools.class.getClassLoader(); + return ChildFirstClassLoader.fromClassPath(classPath, LANGTOOLS_PACKAGES, parent); + } +} diff --git a/src/main/java/org/javacs/Main.java b/src/main/java/org/javacs/Main.java index 902a3d9..cdbcfc8 100644 --- a/src/main/java/org/javacs/Main.java +++ b/src/main/java/org/javacs/Main.java @@ -8,6 +8,7 @@ import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; import com.fasterxml.jackson.datatype.jsr310.JSR310Module; import java.io.IOException; +import java.lang.reflect.Method; import java.net.Socket; import java.nio.file.Path; import java.nio.file.Paths; @@ -65,7 +66,18 @@ public class Main { return m; } - public static void main(String[] args) throws IOException { + public static void main(String[] args) { + try { + ClassLoader langTools = LangTools.createLangToolsClassLoader(); + Class<?> main = Class.forName("org.javacs.Main", true, langTools); + Method run = main.getMethod("run"); + run.invoke(null); + } catch (Exception e) { + LOG.log(Level.SEVERE, "Failed", e); + } + } + + private static void run() { setRootFormat(); try { diff --git a/src/test/java/org/javacs/ChildFirstClassLoaderTest.java b/src/test/java/org/javacs/ChildFirstClassLoaderTest.java new file mode 100644 index 0000000..a32574c --- /dev/null +++ b/src/test/java/org/javacs/ChildFirstClassLoaderTest.java @@ -0,0 +1,31 @@ +package org.javacs; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import org.junit.Test; + +public class ChildFirstClassLoaderTest { + class ExceptionClassLoader extends ClassLoader { + @Override + protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { + throw new RuntimeException("This should never be called"); + } + } + + @Test + public void dontCallParent() throws ClassNotFoundException { + String[] packages = {"org.javacs"}; + ClassLoader childFirst = + ChildFirstClassLoader.fromClassPath( + System.getProperty("java.class.path"), + packages, + new ExceptionClassLoader()); + Class<?> found = childFirst.loadClass("org.javacs.ChildFirstClassLoaderTest"); + assertThat("found ChildFirstClassLoaderTest", found, not(nullValue())); + assertThat( + "reloaded ChildFirstClassLoaderTest", + found, + not(equalTo(ChildFirstClassLoaderTest.class))); + } +} |