diff options
Diffstat (limited to 'src/main/java/com/fivetran')
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 Binary files differnew file mode 100644 index 0000000..dce766d --- /dev/null +++ b/src/main/java/com/fivetran/javac/message/RequestLint.class 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 |