summaryrefslogtreecommitdiff
path: root/src/main/java/com/fivetran
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/fivetran')
-rw-r--r--src/main/java/com/fivetran/javac/BridgeExpressionScanner.java736
-rw-r--r--src/main/java/com/fivetran/javac/BridgeTypeVisitor.java145
-rw-r--r--src/main/java/com/fivetran/javac/GetResourceFileObject.java44
-rw-r--r--src/main/java/com/fivetran/javac/JavacTaskBuilder.java225
-rw-r--r--src/main/java/com/fivetran/javac/LineMap.java69
-rw-r--r--src/main/java/com/fivetran/javac/Main.java172
-rw-r--r--src/main/java/com/fivetran/javac/Services.java82
-rw-r--r--src/main/java/com/fivetran/javac/StringFileObject.java22
-rw-r--r--src/main/java/com/fivetran/javac/TestContext.java38
-rw-r--r--src/main/java/com/fivetran/javac/autocomplete/AutocompleteSuggestion.java104
-rw-r--r--src/main/java/com/fivetran/javac/autocomplete/AutocompleteVisitor.java279
-rw-r--r--src/main/java/com/fivetran/javac/logging/LoggingFormat.java67
-rw-r--r--src/main/java/com/fivetran/javac/message/JavacArgs.java22
-rw-r--r--src/main/java/com/fivetran/javac/message/LintMessage.java19
-rw-r--r--src/main/java/com/fivetran/javac/message/Point.java10
-rw-r--r--src/main/java/com/fivetran/javac/message/Range.java10
-rw-r--r--src/main/java/com/fivetran/javac/message/Request.java51
-rw-r--r--src/main/java/com/fivetran/javac/message/RequestAutocomplete.java9
-rw-r--r--src/main/java/com/fivetran/javac/message/RequestLint.classbin0 -> 411 bytes
-rw-r--r--src/main/java/com/fivetran/javac/message/RequestLint.java4
-rw-r--r--src/main/java/com/fivetran/javac/message/Response.java50
-rw-r--r--src/main/java/com/fivetran/javac/message/ResponseAutocomplete.java13
-rw-r--r--src/main/java/com/fivetran/javac/message/ResponseChannel.java7
-rw-r--r--src/main/java/com/fivetran/javac/message/ResponseError.java9
-rw-r--r--src/main/java/com/fivetran/javac/message/ResponseLint.java8
-rw-r--r--src/main/java/com/fivetran/javac/message/package-info.java4
26 files changed, 2199 insertions, 0 deletions
diff --git a/src/main/java/com/fivetran/javac/BridgeExpressionScanner.java b/src/main/java/com/fivetran/javac/BridgeExpressionScanner.java
new file mode 100644
index 0000000..5db5ba7
--- /dev/null
+++ b/src/main/java/com/fivetran/javac/BridgeExpressionScanner.java
@@ -0,0 +1,736 @@
+package com.fivetran.javac;
+
+import com.sun.source.tree.*;
+import com.sun.source.util.JavacTask;
+import com.sun.source.util.TreePath;
+import com.sun.tools.javac.api.JavacTrees;
+import com.sun.tools.javac.model.JavacTypes;
+
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
+
+/**
+ * Override TreeVisitor with an imperative API that avoids map-reduce style
+ */
+public class BridgeExpressionScanner implements TreeVisitor<Void, Void> {
+
+ /**
+ * Compilation task we are currently doing
+ */
+ public JavacTask task;
+
+ private TreePath path;
+
+ private JavacTrees trees;
+
+ private CompilationUnitTree compilationUnit;
+ private Types types;
+ private Elements elements;
+
+ /**
+ * Path to the current node
+ */
+ public TreePath path() {
+ return path;
+ }
+
+ public JavacTrees trees() {
+ return trees;
+ }
+
+ public Types types() {
+ return types;
+ }
+
+ public Elements elements() {
+ return elements;
+ }
+
+ public CompilationUnitTree compilationUnit() {
+ return compilationUnit;
+ }
+
+ public void scan(Tree node) {
+ if (node != null) {
+ TreePath prev = path;
+
+ path = new TreePath(path, node);
+
+ try {
+ node.accept(this, null);
+ } finally {
+ path = prev;
+ }
+ }
+ }
+
+ public void scan(Iterable<? extends Tree> nodes) {
+ if (nodes != null) {
+ boolean first = true;
+
+ for (Tree node : nodes) {
+ if (first)
+ scan(node);
+ else
+ scan(node);
+
+ first = false;
+ }
+ }
+ }
+
+ public final Void visitCompilationUnit(CompilationUnitTree node, Void p) {
+ visitCompilationUnit(node);
+
+ return null;
+ }
+
+ protected void visitCompilationUnit(CompilationUnitTree node) {
+ path = new TreePath(node);
+ compilationUnit = node;
+ trees = JavacTrees.instance(task);
+ types = (JavacTypes) task.getTypes();
+ elements = task.getElements();
+
+ scan(node.getPackageAnnotations());
+
+ scan(node.getPackageName());
+
+ scan(node.getImports());
+
+ scan(node.getTypeDecls());
+ }
+
+ public final Void visitImport(ImportTree node, Void p) {
+ visitImport(node);
+
+ return null;
+ }
+
+ protected void visitImport(ImportTree node) {
+ scan(node.getQualifiedIdentifier());
+ }
+
+ public final Void visitClass(ClassTree node, Void p) {
+ visitClass(node);
+
+ return null;
+ }
+
+ protected void visitClass(ClassTree node) {
+ scan(node.getModifiers());
+
+ scan(node.getTypeParameters());
+
+ scan(node.getExtendsClause());
+
+ scan(node.getImplementsClause());
+
+ scan(node.getMembers());
+ }
+
+ public final Void visitMethod(MethodTree node, Void p) {
+ visitMethod(node);
+
+ return null;
+ }
+
+ protected void visitMethod(MethodTree node) {
+ scan(node.getModifiers());
+
+ scan(node.getReturnType());
+
+ scan(node.getTypeParameters());
+
+ scan(node.getParameters());
+
+ scan(node.getReceiverParameter());
+
+ scan(node.getThrows());
+
+ scan(node.getBody());
+
+ scan(node.getDefaultValue());
+ }
+
+ public final Void visitVariable(VariableTree node, Void p) {
+ visitVariable(node);
+
+ return null;
+ }
+
+ protected void visitVariable(VariableTree node) {
+ scan(node.getModifiers());
+
+ scan(node.getType());
+
+ scan(node.getNameExpression());
+
+ scan(node.getInitializer());
+ }
+
+ public final Void visitEmptyStatement(EmptyStatementTree node, Void p) {
+ visitEmptyStatement(node);
+
+ return null;
+ }
+
+ protected void visitEmptyStatement(EmptyStatementTree node) {
+
+ }
+
+ public final Void visitBlock(BlockTree node, Void p) {
+ visitBlock(node);
+
+ return null;
+ }
+
+ protected void visitBlock(BlockTree node) {
+ scan(node.getStatements());
+ }
+
+ public final Void visitDoWhileLoop(DoWhileLoopTree node, Void p) {
+ visitDoWhileLoop(node);
+
+ return null;
+ }
+
+ protected void visitDoWhileLoop(DoWhileLoopTree node) {
+ scan(node.getStatement());
+
+ scan(node.getCondition());
+ }
+
+ public final Void visitWhileLoop(WhileLoopTree node, Void p) {
+ visitWhileLoop(node);
+
+ return null;
+ }
+
+ protected void visitWhileLoop(WhileLoopTree node) {
+ scan(node.getCondition());
+
+ scan(node.getStatement());
+ }
+
+ public final Void visitForLoop(ForLoopTree node, Void p) {
+ visitForLoop(node);
+
+ return null;
+ }
+
+ protected void visitForLoop(ForLoopTree node) {
+ scan(node.getInitializer());
+
+ scan(node.getCondition());
+
+ scan(node.getUpdate());
+
+ scan(node.getStatement());
+ }
+
+ public final Void visitEnhancedForLoop(EnhancedForLoopTree node, Void p) {
+ visitEnhancedForLoop(node);
+
+ return null;
+ }
+
+ protected void visitEnhancedForLoop(EnhancedForLoopTree node) {
+ scan(node.getVariable());
+
+ scan(node.getExpression());
+
+ scan(node.getStatement());
+ }
+
+ public final Void visitLabeledStatement(LabeledStatementTree node, Void p) {
+ visitLabeledStatement(node);
+
+ return null;
+ }
+
+ protected void visitLabeledStatement(LabeledStatementTree node) {
+ scan(node.getStatement());
+ }
+
+ public final Void visitSwitch(SwitchTree node, Void p) {
+ visitSwitch(node);
+
+ return null;
+ }
+
+ protected void visitSwitch(SwitchTree node) {
+ scan(node.getExpression());
+
+ scan(node.getCases());
+ }
+
+ public final Void visitCase(CaseTree node, Void p) {
+ visitCase(node);
+
+ return null;
+ }
+
+ protected void visitCase(CaseTree node) {
+ scan(node.getExpression());
+
+ scan(node.getStatements());
+ }
+
+ public final Void visitSynchronized(SynchronizedTree node, Void p) {
+ visitSynchronized(node);
+
+ return null;
+ }
+
+ protected void visitSynchronized(SynchronizedTree node) {
+ scan(node.getExpression());
+
+ scan(node.getBlock());
+ }
+
+ public final Void visitTry(TryTree node, Void p) {
+ visitTry(node);
+
+ return null;
+ }
+
+ protected void visitTry(TryTree node) {
+ scan(node.getResources());
+
+ scan(node.getBlock());
+
+ scan(node.getCatches());
+
+ scan(node.getFinallyBlock());
+ }
+
+ public final Void visitCatch(CatchTree node, Void p) {
+ visitCatch(node);
+
+ return null;
+ }
+
+ protected void visitCatch(CatchTree node) {
+ scan(node.getParameter());
+
+ scan(node.getBlock());
+ }
+
+ public final Void visitConditionalExpression(ConditionalExpressionTree node, Void p) {
+ visitConditionalExpression(node);
+
+ return null;
+ }
+
+ protected void visitConditionalExpression(ConditionalExpressionTree node) {
+ scan(node.getCondition());
+
+ scan(node.getTrueExpression());
+
+ scan(node.getFalseExpression());
+ }
+
+ public final Void visitIf(IfTree node, Void p) {
+ visitIf(node);
+
+ return null;
+ }
+
+ protected void visitIf(IfTree node) {
+ scan(node.getCondition());
+
+ scan(node.getThenStatement());
+
+ scan(node.getElseStatement());
+ }
+
+ public final Void visitExpressionStatement(ExpressionStatementTree node, Void p) {
+ visitExpressionStatement(node);
+
+ return null;
+ }
+
+ protected void visitExpressionStatement(ExpressionStatementTree node) {
+ scan(node.getExpression());
+ }
+
+ public final Void visitBreak(BreakTree node, Void p) {
+ visitBreak(node);
+
+ return null;
+ }
+
+ protected void visitBreak(BreakTree node) {
+
+ }
+
+ public final Void visitContinue(ContinueTree node, Void p) {
+ visitContinue(node);
+
+ return null;
+ }
+
+ protected void visitContinue(ContinueTree node) {
+
+ }
+
+ public final Void visitReturn(ReturnTree node, Void p) {
+ visitReturn(node);
+
+ return null;
+ }
+
+ protected void visitReturn(ReturnTree node) {
+ scan(node.getExpression());
+ }
+
+ public final Void visitThrow(ThrowTree node, Void p) {
+ visitThrow(node);
+
+ return null;
+ }
+
+ protected void visitThrow(ThrowTree node) {
+ scan(node.getExpression());
+ }
+
+ public final Void visitAssert(AssertTree node, Void p) {
+ visitAssert(node);
+
+ return null;
+ }
+
+ protected void visitAssert(AssertTree node) {
+ scan(node.getCondition());
+
+ scan(node.getDetail());
+ }
+
+ public final Void visitMethodInvocation(MethodInvocationTree node, Void p) {
+ visitMethodInvocation(node);
+
+ return null;
+ }
+
+ protected void visitMethodInvocation(MethodInvocationTree node) {
+ scan(node.getTypeArguments());
+
+ scan(node.getMethodSelect());
+
+ scan(node.getArguments());
+ }
+
+ public final Void visitNewClass(NewClassTree node, Void p) {
+ visitNewClass(node);
+
+ return null;
+ }
+
+ protected void visitNewClass(NewClassTree node) {
+ scan(node.getEnclosingExpression());
+
+ scan(node.getIdentifier());
+
+ scan(node.getTypeArguments());
+
+ scan(node.getArguments());
+
+ scan(node.getClassBody());
+ }
+
+ public final Void visitNewArray(NewArrayTree node, Void p) {
+ visitNewArray(node);
+
+ return null;
+ }
+
+ protected void visitNewArray(NewArrayTree node) {
+ scan(node.getType());
+
+ scan(node.getDimensions());
+
+ scan(node.getInitializers());
+
+ scan(node.getAnnotations());
+
+ for (Iterable< ? extends Tree> dimAnno : node.getDimAnnotations()) {
+ scan(dimAnno);
+ }
+ }
+
+ public final Void visitLambdaExpression(LambdaExpressionTree node, Void p) {
+ visitLambdaExpression(node);
+
+ return null;
+ }
+
+ protected void visitLambdaExpression(LambdaExpressionTree node) {
+ scan(node.getParameters());
+
+ scan(node.getBody());
+ }
+
+ public final Void visitParenthesized(ParenthesizedTree node, Void p) {
+ visitParenthesized(node);
+
+ return null;
+ }
+
+ protected void visitParenthesized(ParenthesizedTree node) {
+ scan(node.getExpression());
+ }
+
+ public final Void visitAssignment(AssignmentTree node, Void p) {
+ visitAssignment(node);
+
+ return null;
+ }
+
+ protected void visitAssignment(AssignmentTree node) {
+ scan(node.getVariable());
+
+ scan(node.getExpression());
+ }
+
+ public final Void visitCompoundAssignment(CompoundAssignmentTree node, Void p) {
+ visitCompoundAssignment(node);
+
+ return null;
+ }
+
+ protected void visitCompoundAssignment(CompoundAssignmentTree node) {
+ scan(node.getVariable());
+
+ scan(node.getExpression());
+ }
+
+ public final Void visitUnary(UnaryTree node, Void p) {
+ visitUnary(node);
+
+ return null;
+ }
+
+ protected void visitUnary(UnaryTree node) {
+ scan(node.getExpression());
+ }
+
+ public final Void visitBinary(BinaryTree node, Void p) {
+ visitBinary(node);
+
+ return null;
+ }
+
+ protected void visitBinary(BinaryTree node) {
+ scan(node.getLeftOperand());
+
+ scan(node.getRightOperand());
+ }
+
+ public final Void visitTypeCast(TypeCastTree node, Void p) {
+ visitTypeCast(node);
+
+ return null;
+ }
+
+ protected void visitTypeCast(TypeCastTree node) {
+ scan(node.getType());
+
+ scan(node.getExpression());
+ }
+
+ public final Void visitInstanceOf(InstanceOfTree node, Void p) {
+ visitInstanceOf(node);
+
+ return null;
+ }
+
+ protected void visitInstanceOf(InstanceOfTree node) {
+ scan(node.getExpression());
+
+ scan(node.getType());
+ }
+
+ public final Void visitArrayAccess(ArrayAccessTree node, Void p) {
+ visitArrayAccess(node);
+
+ return null;
+ }
+
+ protected void visitArrayAccess(ArrayAccessTree node) {
+ scan(node.getExpression());
+
+ scan(node.getIndex());
+ }
+
+ public final Void visitMemberSelect(MemberSelectTree node, Void p) {
+ visitMemberSelect(node);
+
+ return null;
+ }
+
+ protected void visitMemberSelect(MemberSelectTree node) {
+ scan(node.getExpression());
+ }
+
+ public final Void visitMemberReference(MemberReferenceTree node, Void p) {
+ visitMemberReference(node);
+
+ return null;
+ }
+
+ protected void visitMemberReference(MemberReferenceTree node) {
+ scan(node.getQualifierExpression());
+
+ scan(node.getTypeArguments());
+ }
+
+ public final Void visitIdentifier(IdentifierTree node, Void p) {
+ visitIdentifier(node);
+
+ return null;
+ }
+
+ protected void visitIdentifier(IdentifierTree node) {
+
+ }
+
+ public final Void visitLiteral(LiteralTree node, Void p) {
+ visitLiteral(node);
+
+ return null;
+ }
+
+ protected void visitLiteral(LiteralTree node) {
+
+ }
+
+ public final Void visitPrimitiveType(PrimitiveTypeTree node, Void p) {
+ visitPrimitiveType(node);
+
+ return null;
+ }
+
+ protected void visitPrimitiveType(PrimitiveTypeTree node) {
+
+ }
+
+ public final Void visitArrayType(ArrayTypeTree node, Void p) {
+ visitArrayType(node);
+
+ return null;
+ }
+
+ protected void visitArrayType(ArrayTypeTree node) {
+ scan(node.getType());
+ }
+
+ public final Void visitParameterizedType(ParameterizedTypeTree node, Void p) {
+ visitParameterizedType(node);
+
+ return null;
+ }
+
+ protected void visitParameterizedType(ParameterizedTypeTree node) {
+ scan(node.getType());
+
+ scan(node.getTypeArguments());
+ }
+
+ public final Void visitUnionType(UnionTypeTree node, Void p) {
+ visitUnionType(node);
+
+ return null;
+ }
+
+ protected void visitUnionType(UnionTypeTree node) {
+ scan(node.getTypeAlternatives());
+ }
+
+ public final Void visitIntersectionType(IntersectionTypeTree node, Void p) {
+ visitIntersectionType(node);
+
+ return null;
+ }
+
+ protected void visitIntersectionType(IntersectionTypeTree node) {
+ scan(node.getBounds());
+ }
+
+ public final Void visitTypeParameter(TypeParameterTree node, Void p) {
+ visitTypeParameter(node);
+
+ return null;
+ }
+
+ protected void visitTypeParameter(TypeParameterTree node) {
+ scan(node.getAnnotations());
+
+ scan(node.getBounds());
+ }
+
+ public final Void visitWildcard(WildcardTree node, Void p) {
+ visitWildcard(node);
+
+ return null;
+ }
+
+ protected void visitWildcard(WildcardTree node) {
+ scan(node.getBound());
+ }
+
+ public final Void visitModifiers(ModifiersTree node, Void p) {
+ visitModifiers(node);
+
+ return null;
+ }
+
+ protected void visitModifiers(ModifiersTree node) {
+ scan(node.getAnnotations());
+ }
+
+ public final Void visitAnnotation(AnnotationTree node, Void p) {
+ visitAnnotation(node);
+
+ return null;
+ }
+
+ protected void visitAnnotation(AnnotationTree node) {
+ scan(node.getAnnotationType());
+
+ scan(node.getArguments());
+ }
+
+ public final Void visitAnnotatedType(AnnotatedTypeTree node, Void p) {
+ visitAnnotatedType(node);
+
+ return null;
+ }
+
+ protected void visitAnnotatedType(AnnotatedTypeTree node) {
+ scan(node.getAnnotations());
+
+ scan(node.getUnderlyingType());
+ }
+
+ public final Void visitOther(Tree node, Void p) {
+ visitOther(node);
+
+ return null;
+ }
+
+ private void visitOther(Tree node) {
+
+ }
+
+ public final Void visitErroneous(ErroneousTree node, Void p) {
+ visitErroneous(node);
+
+ return null;
+ }
+
+ private void visitErroneous(ErroneousTree node) {
+ scan(node.getErrorTrees());
+ }
+}
+
diff --git a/src/main/java/com/fivetran/javac/BridgeTypeVisitor.java b/src/main/java/com/fivetran/javac/BridgeTypeVisitor.java
new file mode 100644
index 0000000..d61657b
--- /dev/null
+++ b/src/main/java/com/fivetran/javac/BridgeTypeVisitor.java
@@ -0,0 +1,145 @@
+package com.fivetran.javac;
+
+import javax.lang.model.type.*;
+import javax.lang.model.util.AbstractTypeVisitor8;
+import java.util.List;
+
+public class BridgeTypeVisitor extends AbstractTypeVisitor8<Void, Void> {
+
+ public void scan(List<? extends TypeMirror> types) {
+ for (TypeMirror t : types)
+ visit(t);
+ }
+
+ @Override
+ public final Void visitPrimitive(PrimitiveType t, Void aVoid) {
+ visitPrimitive(t);
+
+ return null;
+ }
+
+ public void visitPrimitive(PrimitiveType t) {
+
+ }
+
+ @Override
+ public final Void visitNull(NullType t, Void aVoid) {
+ visitNull(t);
+
+ return null;
+ }
+
+ public void visitNull(NullType t) {
+
+ }
+
+ @Override
+ public final Void visitArray(ArrayType t, Void aVoid) {
+ visitArray(t);
+
+ return null;
+ }
+
+ public void visitArray(ArrayType t) {
+
+ }
+
+ @Override
+ public final Void visitDeclared(DeclaredType t, Void aVoid) {
+ visitDeclared(t);
+
+ return null;
+ }
+
+ public void visitDeclared(DeclaredType t) {
+
+ }
+
+ @Override
+ public final Void visitError(ErrorType t, Void aVoid) {
+ visitError(t);
+
+ return null;
+ }
+
+ public void visitError(ErrorType t) {
+
+ }
+
+ @Override
+ public final Void visitTypeVariable(TypeVariable t, Void aVoid) {
+ visitTypeVariable(t);
+
+ return null;
+ }
+
+ public void visitTypeVariable(TypeVariable t) {
+
+ }
+
+ @Override
+ public final Void visitWildcard(WildcardType t, Void aVoid) {
+ visitWildcard(t);
+
+ return null;
+ }
+
+ public void visitWildcard(WildcardType t) {
+
+ }
+
+ @Override
+ public final Void visitExecutable(ExecutableType t, Void aVoid) {
+ visitExecutable(t);
+
+ return null;
+ }
+
+ public void visitExecutable(ExecutableType t) {
+
+ }
+
+ @Override
+ public final Void visitNoType(NoType t, Void aVoid) {
+ visitNoType(t);
+
+ return null;
+ }
+
+ public void visitNoType(NoType t) {
+
+ }
+
+ @Override
+ public final Void visitUnknown(TypeMirror t, Void aVoid) {
+ visitUnknown(t);
+
+ return null;
+ }
+
+ public void visitUnknown(TypeMirror t) {
+
+ }
+
+ @Override
+ public final Void visitUnion(UnionType t, Void aVoid) {
+ visitUnion(t);
+
+ return null;
+ }
+
+ public void visitUnion(UnionType t) {
+
+ }
+
+ @Override
+ public final Void visitIntersection(IntersectionType t, Void aVoid) {
+ visitIntersection(t);
+
+ return null;
+ }
+
+ public void visitIntersection(IntersectionType t) {
+
+ }
+}
diff --git a/src/main/java/com/fivetran/javac/GetResourceFileObject.java b/src/main/java/com/fivetran/javac/GetResourceFileObject.java
new file mode 100644
index 0000000..4f06068
--- /dev/null
+++ b/src/main/java/com/fivetran/javac/GetResourceFileObject.java
@@ -0,0 +1,44 @@
+package com.fivetran.javac;
+
+import com.google.common.io.CharStreams;
+
+import javax.tools.SimpleJavaFileObject;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+/**
+ * Represents a java source on the system resource path.
+ */
+public class GetResourceFileObject extends SimpleJavaFileObject {
+ public final String path;
+
+ public GetResourceFileObject(String path) {
+ super(getResourceUri(path), Kind.SOURCE);
+
+ this.path = path;
+ }
+
+ private static URI getResourceUri(String path) {
+ try {
+ return GetResourceFileObject.class.getResource(path).toURI();
+ } catch (URISyntaxException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
+ try (Reader r = new InputStreamReader(openInputStream())) {
+ return CharStreams.toString(r);
+ }
+ }
+
+ @Override
+ public InputStream openInputStream() throws IOException {
+ return GetResourceFileObject.class.getResourceAsStream(path);
+ }
+}
diff --git a/src/main/java/com/fivetran/javac/JavacTaskBuilder.java b/src/main/java/com/fivetran/javac/JavacTaskBuilder.java
new file mode 100644
index 0000000..3637210
--- /dev/null
+++ b/src/main/java/com/fivetran/javac/JavacTaskBuilder.java
@@ -0,0 +1,225 @@
+package com.fivetran.javac;
+
+import com.google.common.base.Joiner;
+import com.sun.source.util.JavacTask;
+import com.sun.source.util.TaskEvent;
+import com.sun.source.util.TaskListener;
+import com.sun.tools.javac.api.JavacTool;
+import com.sun.tools.javac.comp.CompileStates;
+import com.sun.tools.javac.file.JavacFileManager;
+import com.sun.tools.javac.main.JavaCompiler;
+import com.sun.tools.javac.parser.FuzzyParserFactory;
+import com.sun.tools.javac.util.Context;
+
+import javax.tools.DiagnosticCollector;
+import javax.tools.JavaFileObject;
+import javax.tools.ToolProvider;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Logger;
+
+public class JavacTaskBuilder {
+ private static final Logger LOG = Logger.getLogger("");
+
+ public static final JavacTool SYSTEM_JAVA_COMPILER = (JavacTool) ToolProvider.getSystemJavaCompiler();
+ public static final JavacFileManager STANDARD_FILE_MANAGER = SYSTEM_JAVA_COMPILER.getStandardFileManager(null, null, null);
+
+ private final Context context = new Context();
+ private boolean fuzzyParser = false;
+ /** Files we're going to compile */
+ private final List<JavaFileObject> files = new ArrayList<>();
+ /** Error collector. Can only be set once. */
+ private DiagnosticCollector<JavaFileObject> errors;
+ /** Tasks that get run after the parsing phase of compilation */
+ private List<BridgeExpressionScanner> afterParse = new ArrayList<>();
+ /** Tasks that get run after the enter phase of compilation */
+ private List<BridgeExpressionScanner> afterEnter = new ArrayList<>();
+ /** Tasks that get run after the analysis phase of compilation */
+ private List<BridgeExpressionScanner> afterAnalyze = new ArrayList<>();
+ /** Command line options */
+ private List<String> options = new ArrayList<>();
+ /** When to stop if error */
+ private CompileStates.CompileState shouldStopPolicyIfError = CompileStates.CompileState.ENTER;
+ /** When to stop if no error */
+ private CompileStates.CompileState shouldStopPolicyIfNoError = CompileStates.CompileState.GENERATE;
+
+ private JavacTaskBuilder() {
+ }
+
+ /**
+ * Build a JavacTask using the system java compiler and the standard file manager
+ */
+ public static JavacTaskBuilder create() {
+ return new JavacTaskBuilder();
+ }
+
+ public JavacTaskBuilder fuzzyParser() {
+ fuzzyParser = true;
+
+ return this;
+ }
+
+ /**
+ * Add a file to the compilation todo list
+ */
+ public JavacTaskBuilder addFile(JavaFileObject file) {
+ files.add(file);
+
+ return this;
+ }
+
+ /**
+ * Report errors from all phases of compilation to collector
+ */
+ public JavacTaskBuilder reportErrors(DiagnosticCollector<JavaFileObject> collector) {
+ if (errors != null)
+ throw new IllegalStateException();
+ else {
+ errors = collector;
+
+ return this;
+ }
+ }
+
+ /**
+ * After parsing, scan the abstract syntax tree with visitor.
+ * The javac parser has error recovery, so this will still work even if there are syntax errors.
+ */
+ public JavacTaskBuilder afterParse(BridgeExpressionScanner visitor) {
+ afterParse.add(visitor);
+
+ return this;
+ }
+
+ /**
+ * After entering the tree, scan the abstract syntax tree with visitor.
+ * If syntax errors are present, visitor will never get run.
+ */
+ public JavacTaskBuilder afterEnter(BridgeExpressionScanner visitor) {
+ afterEnter.add(visitor);
+
+ return this;
+ }
+
+ /**
+ * After analysis, scan the abstract syntax tree with visitor.
+ * If syntax errors are present, visitor will never get run.
+ */
+ public JavacTaskBuilder afterAnalyze(BridgeExpressionScanner visitor) {
+ afterAnalyze.add(visitor);
+
+ return this;
+ }
+
+ public JavacTaskBuilder classPath(List<String> classPath) {
+ LOG.info("classpath: " + classPath);
+
+ options.add("-classpath");
+ options.add(Joiner.on(':').join(classPath));
+
+ return this;
+ }
+
+ public JavacTaskBuilder sourcePath(List<String> sourcePath) {
+ LOG.info("sourcepath: " + sourcePath);
+
+ options.add("-sourcepath");
+ options.add(Joiner.on(':').join(sourcePath));
+
+ return this;
+ }
+
+ public JavacTaskBuilder outputDirectory(String outputDirectory) {
+ LOG.info("outputDirectory: " + outputDirectory);
+
+ try {
+ Files.createDirectories(Paths.get(outputDirectory));
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+
+ options.add("-d");
+ options.add(outputDirectory);
+
+ return this;
+ }
+
+ public JavacTaskBuilder stopIfError(CompileStates.CompileState state) {
+ shouldStopPolicyIfError = state;
+
+ return this;
+ }
+
+ public JavacTaskBuilder stopIfNoError(CompileStates.CompileState state) {
+ shouldStopPolicyIfNoError = state;
+
+ return this;
+ }
+
+ public JavacTask build() {
+ JavacTask task = SYSTEM_JAVA_COMPILER.getTask(null,
+ STANDARD_FILE_MANAGER,
+ errors,
+ options,
+ null,
+ files,
+ context);
+
+ if (fuzzyParser)
+ FuzzyParserFactory.instance(context);
+
+ JavaCompiler.instance(context).shouldStopPolicyIfError = shouldStopPolicyIfError;
+ JavaCompiler.instance(context).shouldStopPolicyIfNoError = shouldStopPolicyIfNoError;
+
+ task.addTaskListener(new TaskListener() {
+ @Override
+ public void started(TaskEvent e) {
+ LOG.info("started " + e);
+ }
+
+ @Override
+ public void finished(TaskEvent e) {
+ LOG.info("finished " + e);
+
+ switch (e.getKind()) {
+ case PARSE:
+ for (BridgeExpressionScanner visitor : afterParse) {
+ visitor.task = task;
+
+ e.getCompilationUnit().accept(visitor, null);
+ }
+
+ break;
+ case ENTER:
+ for (BridgeExpressionScanner visitor : afterEnter) {
+ visitor.task = task;
+
+ e.getCompilationUnit().accept(visitor, null);
+ }
+
+ break;
+ case ANALYZE:
+ for (BridgeExpressionScanner visitor : afterAnalyze) {
+ visitor.task = task;
+
+ e.getCompilationUnit().accept(visitor, null);
+ }
+
+ break;
+ case GENERATE:
+ break;
+ case ANNOTATION_PROCESSING:
+ break;
+ case ANNOTATION_PROCESSING_ROUND:
+ break;
+ }
+ }
+ });
+
+ return task;
+ }
+}
diff --git a/src/main/java/com/fivetran/javac/LineMap.java b/src/main/java/com/fivetran/javac/LineMap.java
new file mode 100644
index 0000000..b8da9bf
--- /dev/null
+++ b/src/main/java/com/fivetran/javac/LineMap.java
@@ -0,0 +1,69 @@
+package com.fivetran.javac;
+
+import com.fivetran.javac.message.Point;
+
+import java.io.*;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+
+public class LineMap {
+ private final List<Long> startOfLineOffset;
+
+ private LineMap(List<Long> offsets) {
+ startOfLineOffset = offsets;
+ }
+
+ private static List<Long> findStarts(Reader in) throws IOException {
+ List<Long> offsets = new ArrayList<>();
+ long offset = 0;
+
+ offsets.add(0L);
+
+ while (true) {
+ int next = in.read();
+
+ if (next < 0) {
+ // Important for 1-line files, which have no \n chars
+ offsets.add(offset);
+
+ return offsets;
+ }
+ else {
+ offset++;
+
+ char nextChar = (char) next;
+
+ if (nextChar == '\n')
+ offsets.add(offset);
+ }
+ }
+ }
+
+ public long offset(int row, int column) {
+ return startOfLineOffset.get(row) + column;
+ }
+
+ public Point point(long offset) {
+ for (int row = 0; row < startOfLineOffset.size() - 1; row++) {
+ Long startOffset = startOfLineOffset.get(row);
+ Long endOffset = startOfLineOffset.get(row + 1);
+
+ if (endOffset > offset)
+ return new Point(row, offset - startOffset);
+ }
+
+ throw new IllegalArgumentException("Offset " + offset + " is after the end of the file " + startOfLineOffset.get(startOfLineOffset.size()));
+ }
+
+ public static LineMap fromPath(Path path) throws IOException {
+ try (InputStream in = Files.newInputStream(path)) {
+ return new LineMap(findStarts(new InputStreamReader(in)));
+ }
+ }
+
+ public static LineMap fromString(String text) throws IOException {
+ return new LineMap(findStarts(new StringReader(text)));
+ }
+}
diff --git a/src/main/java/com/fivetran/javac/Main.java b/src/main/java/com/fivetran/javac/Main.java
new file mode 100644
index 0000000..7a52718
--- /dev/null
+++ b/src/main/java/com/fivetran/javac/Main.java
@@ -0,0 +1,172 @@
+package com.fivetran.javac;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.MappingIterator;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
+import com.fasterxml.jackson.datatype.jsr310.JSR310Module;
+import com.fivetran.javac.logging.LoggingFormat;
+import com.fivetran.javac.message.Request;
+import com.fivetran.javac.message.Response;
+import com.fivetran.javac.message.ResponseChannel;
+import com.fivetran.javac.message.ResponseError;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.io.UncheckedIOException;
+import java.net.Socket;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class Main {
+ public static final ObjectMapper JSON = new ObjectMapper().registerModule(new Jdk8Module())
+ .registerModule(new JSR310Module())
+ .configure(JsonGenerator.Feature.AUTO_CLOSE_TARGET, false);
+ private static final ObjectMapper PRETTY_JSON = new ObjectMapper().registerModule(new Jdk8Module())
+ .registerModule(new JSR310Module())
+ .configure(JsonGenerator.Feature.AUTO_CLOSE_TARGET, false)
+ .configure(SerializationFeature.WRITE_NULL_MAP_VALUES,
+ false);
+
+ private static final Logger LOG = Logger.getLogger("");
+
+ public static void main(String[] args) throws IOException {
+ LoggingFormat.startLogging();
+
+ Connection connection = connectToNode();
+ JsonParser parser = JSON.getFactory().createParser(connection.in);
+ MappingIterator<Request> requests = JSON.readValues(parser, Request.class);
+
+ ResponseChannel responses = response -> {
+ JSON.writeValue(connection.out, response);
+
+ connection.out.print('\n');
+ connection.out.flush();
+ };
+
+ new Main(requests, responses).run();
+ }
+
+ private static Connection connectToNode() throws IOException {
+ String port = System.getProperty("servicePort");
+
+ if (port != null) {
+ Socket socket = new Socket("localhost", Integer.parseInt(port));
+
+ InputStream in = socket.getInputStream();
+ PrintStream out = new PrintStream(socket.getOutputStream());
+
+ LOG.info("Connected to parent using socket on port " + port);
+
+ return new Connection(in, out);
+ }
+ else {
+ InputStream in = System.in;
+ PrintStream out = System.out;
+
+ LOG.info("Connected to parent using stdio");
+
+ return new Connection(in, out);
+ }
+ }
+
+ private static class Connection {
+ final InputStream in;
+ final PrintStream out;
+
+ private Connection(InputStream in, PrintStream out) {
+ this.in = in;
+ this.out = out;
+ }
+ }
+
+ public Main(MappingIterator<Request> in, ResponseChannel out) {
+ this.in = in;
+ this.out = out;
+ this.pool = new ScheduledThreadPoolExecutor(8);
+ }
+
+ /**
+ * Requests from the parent node process
+ */
+ public final MappingIterator<Request> in;
+
+ /**
+ * Where to send the responses
+ */
+ public final ResponseChannel out;
+
+ /**
+ * Thread pool that gets used to execute requests
+ */
+ private final ScheduledThreadPoolExecutor pool;
+
+ private final Services services = new Services();
+
+ /**
+ * Listen for requests from the parent node process.
+ * Send replies asynchronously.
+ * When the request stream is closed, wait for 5s for all outstanding responses to compute, then return.
+ */
+ public void run() throws IOException {
+ while (in.hasNextValue()) {
+ final Request request = in.nextValue();
+
+ pool.submit(() -> {
+ Response response = new Response(request.requestId);
+
+ try {
+ // Put request id in logging context
+ LoggingFormat.request.set(request.requestId);
+
+ LOG.info("request " + prettyPrint(request));
+
+ if (request.echo.isPresent())
+ response.echo = Optional.of(services.echo(request.echo.get()));
+ else if (request.lint.isPresent())
+ response.lint = Optional.of(services.lint(request.lint.get()));
+ else if (request.autocomplete.isPresent())
+ response.autocomplete = Optional.of(services.autocomplete(request.autocomplete.get()));
+ // Continue the pattern for additional request / response types
+ else
+ LOG.severe("Unrecognized message " + request);
+ } catch (Exception e) {
+ response.error = Optional.of(new ResponseError(e.getClass().getSimpleName() + ": " + e.getMessage()));
+
+ LOG.log(Level.SEVERE, e.getMessage(), e);
+ } finally {
+ try {
+ LOG.info("response " + prettyPrint(response));
+
+ out.next(response);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+ });
+ }
+
+ pool.shutdown();
+
+ try {
+ pool.awaitTermination(5, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ LOG.log(Level.SEVERE, e.getMessage(), e);
+ }
+ }
+
+ private String prettyPrint(Object value) throws JsonProcessingException {
+ Map asMap = PRETTY_JSON.convertValue(value, Map.class);
+
+ return PRETTY_JSON.writeValueAsString(asMap);
+ }
+
+}
diff --git a/src/main/java/com/fivetran/javac/Services.java b/src/main/java/com/fivetran/javac/Services.java
new file mode 100644
index 0000000..44c4b67
--- /dev/null
+++ b/src/main/java/com/fivetran/javac/Services.java
@@ -0,0 +1,82 @@
+package com.fivetran.javac;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fivetran.javac.autocomplete.AutocompleteVisitor;
+import com.fivetran.javac.message.*;
+import com.sun.source.util.JavacTask;
+import com.sun.tools.javac.comp.CompileStates;
+
+import javax.tools.Diagnostic;
+import javax.tools.DiagnosticCollector;
+import javax.tools.JavaFileObject;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.logging.Logger;
+
+public class Services {
+ private static final Logger LOG = Logger.getLogger("");
+
+ public ResponseAutocomplete autocomplete(RequestAutocomplete request) throws IOException {
+ Path path = Paths.get(request.path);
+ DiagnosticCollector<JavaFileObject> errors = new DiagnosticCollector<>();
+ StringFileObject file = new StringFileObject(request.text, path);
+ LineMap lines = LineMap.fromString(request.text);
+ long cursor = lines.offset(request.row, request.column);
+ AutocompleteVisitor autocompleter = new AutocompleteVisitor(cursor);
+ JavacTask task = JavacTaskBuilder.create()
+ .fuzzyParser()
+ .addFile(file)
+ .reportErrors(errors)
+ .afterAnalyze(autocompleter)
+ .classPath(request.classPath)
+ .sourcePath(request.sourcePath)
+ .outputDirectory(request.outputDirectory.orElse("target"))
+ .stopIfError(CompileStates.CompileState.GENERATE)
+ .build();
+
+ task.call();
+
+ for (Diagnostic<? extends JavaFileObject> error : errors.getDiagnostics()) {
+ LOG.warning(error.toString());
+ }
+
+ return new ResponseAutocomplete(autocompleter.suggestions);
+ }
+
+ public JsonNode echo(JsonNode echo) {
+ return echo;
+ }
+
+ public ResponseLint lint(RequestLint request) throws IOException {
+ DiagnosticCollector<JavaFileObject> errors = new DiagnosticCollector<>();
+ Path path = Paths.get(request.path);
+ JavaFileObject file = JavacTaskBuilder.STANDARD_FILE_MANAGER.getRegularFile(path.toFile());
+ LineMap lines = LineMap.fromPath(path);
+ JavacTask task = JavacTaskBuilder.create()
+ .addFile(file)
+ .reportErrors(errors)
+ .classPath(request.classPath)
+ .sourcePath(request.sourcePath)
+ .outputDirectory(request.outputDirectory.orElse("target"))
+ .build();
+
+ task.call();
+
+ ResponseLint response = new ResponseLint();
+
+ for (Diagnostic<? extends JavaFileObject> error : errors.getDiagnostics()) {
+ Point start = lines.point(error.getStartPosition());
+ Point end = lines.point(error.getEndPosition());
+ Range range = new Range(start, end);
+ LintMessage message = new LintMessage(LintMessage.Type.Error,
+ error.getMessage(null),
+ error.getSource().toUri().getPath(),
+ range);
+
+ response.messages.add(message);
+ }
+
+ return response;
+ }
+}
diff --git a/src/main/java/com/fivetran/javac/StringFileObject.java b/src/main/java/com/fivetran/javac/StringFileObject.java
new file mode 100644
index 0000000..acd8df9
--- /dev/null
+++ b/src/main/java/com/fivetran/javac/StringFileObject.java
@@ -0,0 +1,22 @@
+package com.fivetran.javac;
+
+import javax.tools.SimpleJavaFileObject;
+import java.io.IOException;
+import java.nio.file.Path;
+
+public class StringFileObject extends SimpleJavaFileObject {
+ public final String content;
+ public final Path path;
+
+ public StringFileObject(String content, Path path) {
+ super(path.toUri(), Kind.SOURCE);
+
+ this.content = content;
+ this.path = path;
+ }
+
+ @Override
+ public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
+ return content;
+ }
+}
diff --git a/src/main/java/com/fivetran/javac/TestContext.java b/src/main/java/com/fivetran/javac/TestContext.java
new file mode 100644
index 0000000..f3f0ca1
--- /dev/null
+++ b/src/main/java/com/fivetran/javac/TestContext.java
@@ -0,0 +1,38 @@
+package com.fivetran.javac;
+
+import com.sun.tools.javac.util.Context;
+
+/**
+ * Useful for debugging what regular javac is doing with context.
+ */
+public class TestContext extends Context {
+ @Override
+ public <T> T get(Class<T> clazz) {
+ return super.get(clazz);
+ }
+
+ @Override
+ public <T> void put(Class<T> clazz, T data) {
+ super.put(clazz, data);
+ }
+
+ @Override
+ public <T> void put(Class<T> clazz, Factory<T> fac) {
+ super.put(clazz, fac);
+ }
+
+ @Override
+ public <T> T get(Key<T> key) {
+ return super.get(key);
+ }
+
+ @Override
+ public <T> void put(Key<T> key, T data) {
+ super.put(key, data);
+ }
+
+ @Override
+ public <T> void put(Key<T> key, Factory<T> fac) {
+ super.put(key, fac);
+ }
+}
diff --git a/src/main/java/com/fivetran/javac/autocomplete/AutocompleteSuggestion.java b/src/main/java/com/fivetran/javac/autocomplete/AutocompleteSuggestion.java
new file mode 100644
index 0000000..55f340e
--- /dev/null
+++ b/src/main/java/com/fivetran/javac/autocomplete/AutocompleteSuggestion.java
@@ -0,0 +1,104 @@
+package com.fivetran.javac.autocomplete;
+
+import com.fasterxml.jackson.annotation.JsonValue;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fivetran.javac.Main;
+
+import java.util.Optional;
+
+/**
+ * Jackson JSON definition for Atom autocomplete suggestion
+ * <p>
+ * {@see https://github.com/atom/autocomplete-plus/wiki/Provider-API#suggestions}
+ */
+public class AutocompleteSuggestion {
+ /**
+ * The text which will be inserted into the editor, in place of the prefix
+ */
+ public String text;
+
+ /**
+ * A snippet string. This will allow users to tab through function arguments or other options. e.g.
+ * 'myFunction(${1:arg1}, ${2:arg2})'. See the snippets package for more information.
+ */
+ public String snippet;
+
+ /**
+ * The suggestion type. It will be converted into an icon shown against the suggestion. screenshot. Predefined
+ * styles exist for variable, constant, property, value, method, function, class, type, keyword, tag, snippet,
+ * import, require. This list represents nearly everything being colorized.
+ */
+ public Type type;
+
+ /**
+ * A string that will show in the UI for this suggestion. When not set, snippet || text is displayed. This is useful
+ * when snippet or text displays too much, and you want to simplify. e.g. {type: 'attribute', snippet:
+ * 'class="$0"$1', displayText: 'class'}
+ */
+ public Optional<String> displayText = Optional.empty();
+ /**
+ * The text immediately preceding the cursor, which will be replaced by the text. If not provided, the prefix passed
+ * into getSuggestions will be used.
+ */
+ public Optional<String> replacementPrefix = Optional.empty();
+ /**
+ * This is shown before the suggestion. Useful for return values. screenshot
+ */
+ public Optional<String> leftLabel = Optional.empty();
+ /**
+ * Use this instead of leftLabel if you want to use html for the left label.
+ */
+ public Optional<String> leftLabelHTML = Optional.empty();
+ /**
+ * An indicator (e.g. function, variable) denoting the "kind" of suggestion this represents
+ */
+ public Optional<String> rightLabel = Optional.empty();
+ /**
+ * Use this instead of rightLabel if you want to use html for the right label.
+ */
+ public Optional<String> rightLabelHTML = Optional.empty();
+ /**
+ * Class name for the suggestion in the suggestion list. Allows you to style your suggestion via CSS, if desired
+ */
+ public Optional<String> className = Optional.empty();
+ /**
+ * If you want complete control over the icon shown against the suggestion. e.g. iconHTML: '<i
+ * class="icon-move-right"></i>' screenshot. The background color of the icon will still be determined (by default)
+ * from the type.
+ */
+ public Optional<String> iconHTML = Optional.empty();
+ /**
+ * A doc-string summary or short description of the suggestion. When specified, it will be displayed at the bottom
+ * of the suggestions list.
+ */
+ public Optional<String> description = Optional.empty();
+ /**
+ * A url to the documentation or more information about this suggestion. When specified, a More.. link will be
+ * displayed in the description area.
+ */
+ public Optional<String> descriptionMoreURL = Optional.empty();
+
+ public AutocompleteSuggestion(String text, String snippet, Type type) {
+ this.text = text;
+ this.snippet = snippet;
+ this.type = type;
+ }
+
+ public enum Type {
+ Variable, Constant, Property, Value, Method, Function, Class, Type, Keyword, Tag, Snippet, Import, Require;
+
+ @JsonValue
+ public String toJson() {
+ return this.name().toLowerCase();
+ }
+ }
+
+ @Override
+ public String toString() {
+ try {
+ return Main.JSON.writeValueAsString(this);
+ } catch (JsonProcessingException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/src/main/java/com/fivetran/javac/autocomplete/AutocompleteVisitor.java b/src/main/java/com/fivetran/javac/autocomplete/AutocompleteVisitor.java
new file mode 100644
index 0000000..f7b7af0
--- /dev/null
+++ b/src/main/java/com/fivetran/javac/autocomplete/AutocompleteVisitor.java
@@ -0,0 +1,279 @@
+package com.fivetran.javac.autocomplete;
+
+import com.fivetran.javac.BridgeExpressionScanner;
+import com.fivetran.javac.BridgeTypeVisitor;
+import com.google.common.base.Joiner;
+import com.sun.source.tree.*;
+import com.sun.source.util.TreePath;
+import com.sun.tools.javac.code.Symbol;
+import com.sun.tools.javac.tree.JCTree;
+
+import javax.lang.model.element.Element;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.*;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.logging.Logger;
+
+public class AutocompleteVisitor extends BridgeExpressionScanner {
+ private static final Logger LOG = Logger.getLogger("");
+ private final long cursor;
+ public final Set<AutocompleteSuggestion> suggestions = new LinkedHashSet<>();
+
+ public AutocompleteVisitor(long cursor) {
+ this.cursor = cursor;
+ }
+
+ @Override
+ public void scan(Tree node) {
+ if (containsCursor(node))
+ super.scan(node);
+ }
+
+ private boolean containsCursor(Tree node) {
+ long start = trees().getSourcePositions().getStartPosition(compilationUnit(), node);
+ long end = trees().getSourcePositions().getEndPosition(compilationUnit(), node);
+ return start <= cursor && cursor <= end;
+ }
+
+ /**
+ * [expression].[identifier]
+ */
+ @Override
+ protected void visitMemberSelect(MemberSelectTree node) {
+ // If expression contains cursor, no autocomplete
+ ExpressionTree expression = node.getExpression();
+
+ if (containsCursor(expression))
+ super.visitMemberSelect(node);
+ else {
+ TreePath pathToExpression = new TreePath(path(), expression);
+ TypeMirror type = trees().getTypeMirror(pathToExpression);
+
+ if (type == null)
+ LOG.warning("No type for " + Joiner.on("/").join(pathToExpression));
+ else if (isClassReference(expression)) {
+ suggestions.add(new AutocompleteSuggestion("class", "class", AutocompleteSuggestion.Type.Constant));
+
+ type.accept(new CollectStatics(), null);
+ }
+ else
+ type.accept(new CollectVirtuals(), null);
+ }
+ }
+
+ private boolean isClassReference(ExpressionTree expression) {
+ return expression instanceof JCTree.JCIdent &&
+ ((JCTree.JCIdent) expression).sym instanceof Symbol.ClassSymbol;
+ }
+
+ @Override
+ protected void visitMemberReference(MemberReferenceTree node) {
+ // If expression contains cursor, no autocomplete
+ ExpressionTree expression = node.getQualifierExpression();
+
+ if (containsCursor(expression))
+ super.visitMemberReference(node);
+ else {
+ TreePath pathToExpression = new TreePath(path(), expression);
+ TypeMirror type = trees().getTypeMirror(pathToExpression);
+
+ if (type == null)
+ LOG.warning("No type for " + Joiner.on("/").join(pathToExpression));
+ else if (isClassReference(expression)) {
+ suggestions.add(new AutocompleteSuggestion("new", "new", AutocompleteSuggestion.Type.Constant));
+
+ type.accept(new CollectStatics(), null);
+ }
+ else
+ type.accept(new CollectVirtuals(), null);
+ }
+ }
+
+ @Override
+ protected void visitIdentifier(IdentifierTree node) {
+ super.visitIdentifier(node);
+
+ if (containsCursor(node)) {
+ TreePath path = path();
+ Scope scope = trees().getScope(path);
+
+ while (scope != null) {
+ LOG.info(Joiner.on(", ").join(scope.getLocalElements()));
+
+ for (Element e : scope.getLocalElements())
+ addElement(e);
+ // TODO add to suggestions
+
+ scope = scope.getEnclosingScope();
+ }
+ }
+ }
+
+ private void addElement(Element e) {
+ String name = e.getSimpleName().toString();
+
+ switch (e.getKind()) {
+ case PACKAGE:
+ break;
+ case ENUM:
+ case CLASS:
+ case ANNOTATION_TYPE:
+ case INTERFACE:
+ case TYPE_PARAMETER:
+ suggestions.add(new AutocompleteSuggestion(name, name, AutocompleteSuggestion.Type.Type));
+
+ break;
+ case ENUM_CONSTANT:
+ addEnumConstant(e);
+
+ break;
+ case FIELD:
+ addField(e);
+
+ break;
+ case PARAMETER:
+ case LOCAL_VARIABLE:
+ case EXCEPTION_PARAMETER:
+ suggestions.add(new AutocompleteSuggestion(name, name, AutocompleteSuggestion.Type.Variable));
+
+ break;
+ case METHOD:
+ addMethod((Symbol.MethodSymbol) e);
+
+ break;
+ case CONSTRUCTOR:
+ // TODO
+ break;
+ case STATIC_INIT:
+ // Nothing user-enterable
+ break;
+ case INSTANCE_INIT:
+ // Nothing user-enterable
+ break;
+ case OTHER:
+ break;
+ case RESOURCE_VARIABLE:
+ break;
+ }
+ }
+
+ private void addEnumConstant(Element e) {
+ String name = e.getSimpleName().toString();
+ AutocompleteSuggestion suggestion = new AutocompleteSuggestion(name, name, AutocompleteSuggestion.Type.Constant);
+
+ suggestion.rightLabel = Optional.of(e.getEnclosingElement().getSimpleName().toString());
+
+ suggestions.add(suggestion);
+ }
+
+ private void addMethod(Symbol.MethodSymbol e) {
+ String name = e.getSimpleName().toString();
+ String snippet = name + "(";
+ List<Symbol.VarSymbol> parameters = e.getParameters();
+
+ for (int i = 0; i < parameters.size(); i++) {
+ Symbol.VarSymbol p = parameters.get(i);
+
+ snippet += "{" + (i+1) + ":" + p.getSimpleName() + "}";
+
+ if (i < parameters.size() - 1)
+ snippet += ", ";
+ }
+
+ snippet += ")";
+
+ AutocompleteSuggestion suggestion = new AutocompleteSuggestion(name, snippet, AutocompleteSuggestion.Type.Method);
+
+ suggestion.rightLabel = Optional.of(e.getEnclosingElement().getSimpleName().toString());
+
+ suggestions.add(suggestion);
+ }
+
+ private void addField(Element e) {
+ String name = e.getSimpleName().toString();
+ String snippet = name;
+ AutocompleteSuggestion suggestion = new AutocompleteSuggestion(name, snippet, AutocompleteSuggestion.Type.Property);
+
+ suggestion.rightLabel = Optional.of(e.getEnclosingElement().getSimpleName().toString());
+
+ suggestions.add(suggestion);
+ }
+
+ private class CollectStatics extends BridgeTypeVisitor {
+
+ @Override
+ public void visitDeclared(DeclaredType t) {
+ TypeElement typeElement = (TypeElement) t.asElement();
+ List<? extends Element> members = elements().getAllMembers(typeElement);
+
+ for (Element e : members) {
+ switch (e.getKind()) {
+ case FIELD:
+ Symbol.VarSymbol field = (Symbol.VarSymbol) e;
+
+ if (field.isStatic())
+ addField(field);
+
+ break;
+ case METHOD:
+ Symbol.MethodSymbol method = (Symbol.MethodSymbol) e;
+
+ if (method.isStatic())
+ addMethod(method);
+
+ break;
+ }
+ }
+ }
+ }
+
+ private class CollectVirtuals extends BridgeTypeVisitor {
+ @Override
+ public void visitArray(ArrayType t) {
+ // Array types just have 'length'
+ AutocompleteSuggestion length = new AutocompleteSuggestion("length",
+ "length",
+ AutocompleteSuggestion.Type.Property);
+
+ suggestions.add(length);
+ }
+
+ @Override
+ public void visitTypeVariable(TypeVariable t) {
+ visit(t.getUpperBound());
+ }
+
+ @Override
+ public void visitDeclared(DeclaredType t) {
+ TypeElement typeElement = (TypeElement) t.asElement();
+ List<? extends Element> members = elements().getAllMembers(typeElement);
+
+ for (Element e : members) {
+ switch (e.getKind()) {
+ case FIELD:
+ Symbol.VarSymbol field = (Symbol.VarSymbol) e;
+
+ if (!field.isStatic())
+ addField(field);
+
+ break;
+ case METHOD:
+ Symbol.MethodSymbol method = (Symbol.MethodSymbol) e;
+
+ if (!method.isStatic())
+ addMethod(method);
+
+ break;
+ }
+ }
+ }
+
+ @Override
+ public void visitWildcard(WildcardType t) {
+ visit(t.getExtendsBound());
+ }
+ }
+}
diff --git a/src/main/java/com/fivetran/javac/logging/LoggingFormat.java b/src/main/java/com/fivetran/javac/logging/LoggingFormat.java
new file mode 100644
index 0000000..838e3b3
--- /dev/null
+++ b/src/main/java/com/fivetran/javac/logging/LoggingFormat.java
@@ -0,0 +1,67 @@
+package com.fivetran.javac.logging;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.time.Instant;
+import java.util.StringJoiner;
+import java.util.logging.*;
+
+public class LoggingFormat extends Formatter {
+ public static ThreadLocal<Integer> request = new ThreadLocal<>();
+
+ @Override
+ public String format(LogRecord record) {
+ StringJoiner joiner = new StringJoiner("\t");
+
+ joiner.add(record.getLevel().getName());
+ joiner.add(requestAsString());
+ joiner.add(Thread.currentThread().getName());
+ joiner.add(Instant.ofEpochMilli(record.getMillis()).toString());
+// joiner.add(record.getLoggerName());
+ joiner.add(record.getSourceClassName() + "#" + record.getSourceMethodName());
+ joiner.add(record.getMessage());
+
+ String result = joiner.toString() + "\n";
+
+ Throwable thrown = record.getThrown();
+
+ if (thrown != null) {
+ StringWriter stackTrace = new StringWriter();
+ PrintWriter print = new PrintWriter(stackTrace);
+
+ thrown.printStackTrace(print);
+ print.flush();
+
+ result = result + stackTrace + "\n";
+ }
+
+ return result;
+ }
+
+ private CharSequence requestAsString() {
+ Integer i = request.get();
+
+ if (i == null)
+ return "?";
+ else
+ return Integer.toString(i);
+ }
+
+ private static boolean started = false;
+
+ public static void startLogging() throws IOException {
+ if (!started) {
+ started = true;
+
+ Logger root = Logger.getLogger("");
+
+ FileHandler file = new FileHandler("javac-services.%g.log", 100_000, 1, false);
+
+ root.addHandler(file);
+
+ for (Handler h : root.getHandlers())
+ h.setFormatter(new LoggingFormat());
+ }
+ }
+}
diff --git a/src/main/java/com/fivetran/javac/message/JavacArgs.java b/src/main/java/com/fivetran/javac/message/JavacArgs.java
new file mode 100644
index 0000000..5768278
--- /dev/null
+++ b/src/main/java/com/fivetran/javac/message/JavacArgs.java
@@ -0,0 +1,22 @@
+package com.fivetran.javac.message;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
+public class JavacArgs {
+
+ /**
+ * Source of the file we want to compile
+ */
+ public String text;
+
+ /**
+ * Path to a file we want to compile. But we'll use content for the actual source code.
+ */
+ public String path;
+
+ public List<String> sourcePath = Collections.emptyList(), classPath = Collections.emptyList();
+
+ public Optional<String> outputDirectory = Optional.empty();
+}
diff --git a/src/main/java/com/fivetran/javac/message/LintMessage.java b/src/main/java/com/fivetran/javac/message/LintMessage.java
new file mode 100644
index 0000000..d6e21bb
--- /dev/null
+++ b/src/main/java/com/fivetran/javac/message/LintMessage.java
@@ -0,0 +1,19 @@
+package com.fivetran.javac.message;
+
+public class LintMessage {
+ public final Type type;
+ public final String text, filePath;
+ public final Range range;
+
+ public LintMessage(Type type, String text, String filePath, Range range) {
+ this.type = type;
+ this.text = text;
+ this.filePath = filePath;
+ this.range = range;
+ }
+
+ public static enum Type {
+ Error,
+ Warning
+ }
+}
diff --git a/src/main/java/com/fivetran/javac/message/Point.java b/src/main/java/com/fivetran/javac/message/Point.java
new file mode 100644
index 0000000..8ab7da8
--- /dev/null
+++ b/src/main/java/com/fivetran/javac/message/Point.java
@@ -0,0 +1,10 @@
+package com.fivetran.javac.message;
+
+public class Point {
+ public final long row, column;
+
+ public Point(long row, long column) {
+ this.row = row;
+ this.column = column;
+ }
+}
diff --git a/src/main/java/com/fivetran/javac/message/Range.java b/src/main/java/com/fivetran/javac/message/Range.java
new file mode 100644
index 0000000..550bd6a
--- /dev/null
+++ b/src/main/java/com/fivetran/javac/message/Range.java
@@ -0,0 +1,10 @@
+package com.fivetran.javac.message;
+
+public class Range {
+ public final Point start, end;
+
+ public Range(Point start, Point end) {
+ this.start = start;
+ this.end = end;
+ }
+}
diff --git a/src/main/java/com/fivetran/javac/message/Request.java b/src/main/java/com/fivetran/javac/message/Request.java
new file mode 100644
index 0000000..78eb550
--- /dev/null
+++ b/src/main/java/com/fivetran/javac/message/Request.java
@@ -0,0 +1,51 @@
+package com.fivetran.javac.message;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fivetran.javac.Main;
+
+import java.util.Optional;
+
+public class Request {
+ public int requestId;
+
+ /**
+ * Handy to test if the channel is working
+ */
+ public Optional<JsonNode> echo = Optional.empty();
+
+ public Optional<RequestLint> lint = Optional.empty();
+
+ public Optional<RequestAutocomplete> autocomplete = Optional.empty();
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Request response = (Request) o;
+
+ try {
+ return Main.JSON.writeValueAsString(this).equals(Main.JSON.writeValueAsString(response));
+ } catch (JsonProcessingException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ try {
+ return Main.JSON.writeValueAsString(this).hashCode();
+ } catch (JsonProcessingException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public String toString() {
+ try {
+ return Main.JSON.writeValueAsString(this);
+ } catch (JsonProcessingException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/src/main/java/com/fivetran/javac/message/RequestAutocomplete.java b/src/main/java/com/fivetran/javac/message/RequestAutocomplete.java
new file mode 100644
index 0000000..a048c50
--- /dev/null
+++ b/src/main/java/com/fivetran/javac/message/RequestAutocomplete.java
@@ -0,0 +1,9 @@
+package com.fivetran.javac.message;
+
+public class RequestAutocomplete extends JavacArgs {
+
+ /**
+ * Autocomplete symbols here
+ */
+ public int row, column;
+}
diff --git a/src/main/java/com/fivetran/javac/message/RequestLint.class b/src/main/java/com/fivetran/javac/message/RequestLint.class
new file mode 100644
index 0000000..dce766d
--- /dev/null
+++ b/src/main/java/com/fivetran/javac/message/RequestLint.class
Binary files differ
diff --git a/src/main/java/com/fivetran/javac/message/RequestLint.java b/src/main/java/com/fivetran/javac/message/RequestLint.java
new file mode 100644
index 0000000..703f1f7
--- /dev/null
+++ b/src/main/java/com/fivetran/javac/message/RequestLint.java
@@ -0,0 +1,4 @@
+package com.fivetran.javac.message;
+
+public class RequestLint extends JavacArgs {
+}
diff --git a/src/main/java/com/fivetran/javac/message/Response.java b/src/main/java/com/fivetran/javac/message/Response.java
new file mode 100644
index 0000000..48bee39
--- /dev/null
+++ b/src/main/java/com/fivetran/javac/message/Response.java
@@ -0,0 +1,50 @@
+package com.fivetran.javac.message;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fivetran.javac.Main;
+
+import java.util.Optional;
+
+public class Response {
+ public final int requestId;
+ public Optional<ResponseLint> lint = Optional.empty();
+ public Optional<JsonNode> echo = Optional.empty();
+ public Optional<ResponseError> error = Optional.empty();
+ public Optional<ResponseAutocomplete> autocomplete = Optional.empty();
+
+ public Response(int requestId) {
+ this.requestId = requestId;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Response response = (Response) o;
+
+ try {
+ return Main.JSON.writeValueAsString(this).equals(Main.JSON.writeValueAsString(response));
+ } catch (JsonProcessingException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ try {
+ return Main.JSON.writeValueAsString(this).hashCode();
+ } catch (JsonProcessingException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public String toString() {
+ try {
+ return Main.JSON.writeValueAsString(this);
+ } catch (JsonProcessingException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/src/main/java/com/fivetran/javac/message/ResponseAutocomplete.java b/src/main/java/com/fivetran/javac/message/ResponseAutocomplete.java
new file mode 100644
index 0000000..2ab9be1
--- /dev/null
+++ b/src/main/java/com/fivetran/javac/message/ResponseAutocomplete.java
@@ -0,0 +1,13 @@
+package com.fivetran.javac.message;
+
+import com.fivetran.javac.autocomplete.AutocompleteSuggestion;
+
+import java.util.Set;
+
+public class ResponseAutocomplete {
+ public final Set<AutocompleteSuggestion> suggestions;
+
+ public ResponseAutocomplete(Set<AutocompleteSuggestion> suggestions) {
+ this.suggestions = suggestions;
+ }
+}
diff --git a/src/main/java/com/fivetran/javac/message/ResponseChannel.java b/src/main/java/com/fivetran/javac/message/ResponseChannel.java
new file mode 100644
index 0000000..07037bb
--- /dev/null
+++ b/src/main/java/com/fivetran/javac/message/ResponseChannel.java
@@ -0,0 +1,7 @@
+package com.fivetran.javac.message;
+
+import java.io.IOException;
+
+public interface ResponseChannel {
+ void next(Response response) throws IOException;
+}
diff --git a/src/main/java/com/fivetran/javac/message/ResponseError.java b/src/main/java/com/fivetran/javac/message/ResponseError.java
new file mode 100644
index 0000000..a435d39
--- /dev/null
+++ b/src/main/java/com/fivetran/javac/message/ResponseError.java
@@ -0,0 +1,9 @@
+package com.fivetran.javac.message;
+
+public class ResponseError {
+ public final String message;
+
+ public ResponseError(String message) {
+ this.message = message;
+ }
+}
diff --git a/src/main/java/com/fivetran/javac/message/ResponseLint.java b/src/main/java/com/fivetran/javac/message/ResponseLint.java
new file mode 100644
index 0000000..f1e4e94
--- /dev/null
+++ b/src/main/java/com/fivetran/javac/message/ResponseLint.java
@@ -0,0 +1,8 @@
+package com.fivetran.javac.message;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ResponseLint {
+ public List<LintMessage> messages = new ArrayList<>();
+}
diff --git a/src/main/java/com/fivetran/javac/message/package-info.java b/src/main/java/com/fivetran/javac/message/package-info.java
new file mode 100644
index 0000000..26d5aa2
--- /dev/null
+++ b/src/main/java/com/fivetran/javac/message/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Jackson JSON definitions for messages that go back and forth between atom and child process
+ */
+package com.fivetran.javac.message; \ No newline at end of file