summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGeorge Fraser <george@fivetran.com>2017-11-08 23:56:30 -0800
committerGeorge Fraser <george@fivetran.com>2017-11-09 16:39:50 -0800
commit2bd269b5338689bcc0492e28b5646ecb7c22fb5b (patch)
tree76ca79c158fcc404abf0fea21aec72678b86f684
parent123793d79e24479701ea273ed82f950ccfb79e17 (diff)
downloadjava-language-server-2bd269b5338689bcc0492e28b5646ecb7c22fb5b.zip
Run with custom classloader
-rw-r--r--TODOS.md25
-rw-r--r--pom.xml2
-rw-r--r--src/main/java/org/javacs/ChildFirstClassLoader.java64
-rw-r--r--src/main/java/org/javacs/LangTools.java26
-rw-r--r--src/main/java/org/javacs/Main.java14
-rw-r--r--src/test/java/org/javacs/ChildFirstClassLoaderTest.java31
6 files changed, 160 insertions, 2 deletions
diff --git a/TODOS.md b/TODOS.md
index 2fbe90b..7e2ab96 100644
--- a/TODOS.md
+++ b/TODOS.md
@@ -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
diff --git a/pom.xml b/pom.xml
index fd0e0c7..f7f2468 100644
--- a/pom.xml
+++ b/pom.xml
@@ -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)));
+ }
+}