From 314c50d4f2f9e25c74750e5a1696e678929c2a94 Mon Sep 17 00:00:00 2001 From: Akash Yadav Date: Mon, 17 Jan 2022 11:15:28 +0530 Subject: [PATCH] Use synchronized methods for java compilation --- .../java/com/itsaky/lsp/java/CompileTask.java | 25 +++--- .../com/itsaky/lsp/java/CompilerProvider.java | 6 +- .../itsaky/lsp/java/JavaCompilerService.java | 46 +++++++--- .../com/itsaky/lsp/java/ReusableCompiler.java | 14 ++- .../java/providers/CodeActionProvider.java | 35 ++++---- .../providers/JavaCompletionProvider.java | 41 +++++---- .../lsp/java/providers/SignatureProvider.java | 65 +++++++------- .../itsaky/lsp/java/rewrite/AddException.java | 55 ++++++------ .../rewrite/AddSuppressWarningAnnotation.java | 35 ++++---- .../lsp/java/rewrite/CreateMissingMethod.java | 89 ++++++++++--------- .../rewrite/GenerateRecordConstructor.java | 53 +++++------ .../rewrite/ImplementAbstractMethods.java | 47 +++++----- .../java/rewrite/OverrideInheritedMethod.java | 37 ++++---- .../lsp/java/rewrite/RemoveException.java | 27 +++--- .../itsaky/lsp/java/rewrite/RemoveMethod.java | 13 +-- .../lsp/java/utils/SynchronizedTask.java | 50 +++++++++++ .../lsp/java/visitors/FindCompletionsAt.java | 1 + 17 files changed, 371 insertions(+), 268 deletions(-) create mode 100644 lsp/java/src/main/java/com/itsaky/lsp/java/utils/SynchronizedTask.java diff --git a/lsp/java/src/main/java/com/itsaky/lsp/java/CompileTask.java b/lsp/java/src/main/java/com/itsaky/lsp/java/CompileTask.java index 6a11f642c..6761f4e4a 100644 --- a/lsp/java/src/main/java/com/itsaky/lsp/java/CompileTask.java +++ b/lsp/java/src/main/java/com/itsaky/lsp/java/CompileTask.java @@ -1,5 +1,7 @@ package com.itsaky.lsp.java; +import androidx.annotation.NonNull; + import com.sun.source.tree.CompilationUnitTree; import com.sun.source.util.JavacTask; import java.nio.file.Path; @@ -12,7 +14,15 @@ public class CompileTask implements AutoCloseable { public final JavacTask task; public final List roots; public final List> diagnostics; - private final Runnable close; + + private final CompileBatch compileBatch; + + public CompileTask(@NonNull CompileBatch compileBatch, List> diagnostics) { + this.compileBatch = compileBatch; + this.task = compileBatch.task; + this.roots = compileBatch.roots; + this.diagnostics = diagnostics; + } public CompilationUnitTree root() { if (roots.size() != 1) { @@ -39,19 +49,8 @@ public class CompileTask implements AutoCloseable { throw new RuntimeException("Compilation unit not found"); } - public CompileTask( - JavacTask task, - List roots, - List> diagnostics, - Runnable close) { - this.task = task; - this.roots = roots; - this.diagnostics = diagnostics; - this.close = close; - } - @Override public void close() { - close.run(); + compileBatch.close (); } } diff --git a/lsp/java/src/main/java/com/itsaky/lsp/java/CompilerProvider.java b/lsp/java/src/main/java/com/itsaky/lsp/java/CompilerProvider.java index 0234a97ed..3d83773bb 100644 --- a/lsp/java/src/main/java/com/itsaky/lsp/java/CompilerProvider.java +++ b/lsp/java/src/main/java/com/itsaky/lsp/java/CompilerProvider.java @@ -1,5 +1,7 @@ package com.itsaky.lsp.java; +import com.itsaky.lsp.java.utils.SynchronizedTask; + import java.nio.file.Path; import java.nio.file.Paths; import java.util.Collection; @@ -29,9 +31,9 @@ public interface CompilerProvider { ParseTask parse(JavaFileObject file); - CompileTask compile(Path... files); + SynchronizedTask compile(Path... files); - CompileTask compile(Collection sources); + SynchronizedTask compile(Collection sources); Path NOT_FOUND = Paths.get(""); } diff --git a/lsp/java/src/main/java/com/itsaky/lsp/java/JavaCompilerService.java b/lsp/java/src/main/java/com/itsaky/lsp/java/JavaCompilerService.java index 383c853ff..206fb1e7e 100644 --- a/lsp/java/src/main/java/com/itsaky/lsp/java/JavaCompilerService.java +++ b/lsp/java/src/main/java/com/itsaky/lsp/java/JavaCompilerService.java @@ -2,6 +2,7 @@ package com.itsaky.lsp.java; import com.itsaky.androidide.utils.Logger; import com.itsaky.lsp.java.utils.Extractors; +import com.itsaky.lsp.java.utils.SynchronizedTask; import com.itsaky.lsp.java.visitors.FindTypeDeclarations; import com.sun.source.tree.CompilationUnitTree; @@ -18,6 +19,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.concurrent.locks.ReentrantLock; import java.util.function.Predicate; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -28,12 +30,15 @@ import javax.tools.StandardLocation; class JavaCompilerService implements CompilerProvider { + private CompileBatch cachedCompile; + final Set classPath, docPath; final Set jdkClasses = new HashSet<> (), classPathClasses; - final ReusableCompiler compiler = new ReusableCompiler (); - + final SynchronizedTask synchronizedTask = new SynchronizedTask (); + final ReentrantLock lock = new ReentrantLock (); final List> diags = new ArrayList<> (); + final Map cachedModified = new HashMap<> (); // Use the same file manager for multiple tasks, so we don't repeatedly re-compile the same files // TODO intercept files that aren't in the batch and erase method bodies so compilation is faster @@ -57,9 +62,6 @@ class JavaCompilerService implements CompilerProvider { this.fileManager = new SourceFileManager (); } - private CompileBatch cachedCompile; - private final Map cachedModified = new HashMap<> (); - private boolean needsCompile (Collection sources) { if (cachedModified.size () != sources.size ()) { @@ -325,18 +327,34 @@ class JavaCompilerService implements CompilerProvider { } @Override - public CompileTask compile (Path... files) { - List sources = new ArrayList<> (); - for (Path f : files) { - sources.add (new SourceFileObject (f)); - } - return compile (sources); + public synchronized SynchronizedTask compile (Path... files) { + return synchronizedTask.getWithTask (compileTask -> { + List sources = new ArrayList<> (); + for (Path f : files) { + sources.add (new SourceFileObject (f)); + } + return compile (sources); + }); } @Override - public CompileTask compile (Collection sources) { - CompileBatch compile = compileBatch (sources); - return new CompileTask (compile.task, compile.roots, diags, compile::close); + public synchronized SynchronizedTask compile (Collection sources) { + synchronized (synchronizedTask) { + final CompileBatch compile = compileBatch (sources); + final CompileTask compileTask = new CompileTask (compile, diags); + synchronizedTask.setTask (compileTask); + return synchronizedTask; + } + } + + public synchronized void close () { + if (cachedCompile != null && !cachedCompile.closed) { + cachedCompile.close(); + } + + if (lock.isHeldByCurrentThread() && lock.isLocked()) { + lock.unlock(); + } } private static final Logger LOG = Logger.instance ("JavaCompilerService"); diff --git a/lsp/java/src/main/java/com/itsaky/lsp/java/ReusableCompiler.java b/lsp/java/src/main/java/com/itsaky/lsp/java/ReusableCompiler.java index fd035562b..f14923a8d 100644 --- a/lsp/java/src/main/java/com/itsaky/lsp/java/ReusableCompiler.java +++ b/lsp/java/src/main/java/com/itsaky/lsp/java/ReusableCompiler.java @@ -80,7 +80,7 @@ import javax.tools.JavaFileObject; */ class ReusableCompiler { - private static final Logger LOG = Logger.instance ("main"); + private static final Logger LOG = Logger.instance ("ReusableCompiler"); private static final JavacTool systemProvider = JavacTool.create (); private List currentOptions = new ArrayList<> (); @@ -119,8 +119,7 @@ class ReusableCompiler { } checkedOut = true; - List opts = - StreamSupport.stream (options.spliterator (), false).collect (Collectors.toCollection (ArrayList::new)); + List opts = StreamSupport.stream (options.spliterator (), false).collect (Collectors.toCollection (ArrayList::new)); if (!opts.equals (currentOptions)) { LOG.warn (String.format ("Options changed from %s to %s, creating new compiler", options, opts)); currentOptions = opts; @@ -133,7 +132,7 @@ class ReusableCompiler { task.addTaskListener (currentContext); - return new Borrow (task, currentContext); + return lastBorrow = new Borrow (task); } class Borrow implements AutoCloseable { @@ -141,7 +140,7 @@ class ReusableCompiler { final JavacTask task; boolean closed; - Borrow (JavacTask task, ReusableContext ctx) { + Borrow (JavacTask task) { this.task = task; } @@ -160,7 +159,7 @@ class ReusableCompiler { } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { throw new RuntimeException ("Unable to call cleanup() on JavacTaskImpl", e); } - + checkedOut = false; closed = true; } @@ -288,7 +287,4 @@ class ReusableCompiler { } } } - - static class CompilerInUseException extends RuntimeException { - } } diff --git a/lsp/java/src/main/java/com/itsaky/lsp/java/providers/CodeActionProvider.java b/lsp/java/src/main/java/com/itsaky/lsp/java/providers/CodeActionProvider.java index c1852f173..c695cf1bd 100644 --- a/lsp/java/src/main/java/com/itsaky/lsp/java/providers/CodeActionProvider.java +++ b/lsp/java/src/main/java/com/itsaky/lsp/java/providers/CodeActionProvider.java @@ -36,6 +36,7 @@ import com.itsaky.lsp.java.rewrite.RemoveClass; import com.itsaky.lsp.java.rewrite.RemoveException; import com.itsaky.lsp.java.rewrite.RemoveMethod; import com.itsaky.lsp.java.rewrite.Rewrite; +import com.itsaky.lsp.java.utils.SynchronizedTask; import com.itsaky.lsp.java.visitors.FindMethodDeclarationAt; import com.itsaky.lsp.java.visitors.FindTypeDeclarationAt; import com.itsaky.lsp.models.CodeActionItem; @@ -106,12 +107,14 @@ public class CodeActionProvider implements ICodeActionProvider { // task to generate the code actions // If we switch to resolving code actions asynchronously using Command, that will fix this problem. final TreeMap rewrites = new TreeMap<> (); - try (final CompileTask task = compiler.compile(file)) { - long elapsed = Duration.between(started, Instant.now()).toMillis(); - LOG.info(String.format(Locale.getDefault (), "...compiled in %d ms", elapsed)); - final LineMap lines = task.root().getLineMap(); - final long cursor = lines.getPosition(params.getRange().getStart().getLine() + 1, params.getRange().getStart().getColumn () + 1); - rewrites.putAll(overrideInheritedMethods(task, file, cursor)); + try (final SynchronizedTask synchronizedTask = compiler.compile(file)) { + synchronizedTask.runWithTask (task -> { + long elapsed = Duration.between(started, Instant.now()).toMillis(); + LOG.info(String.format(Locale.getDefault (), "...compiled in %d ms", elapsed)); + final LineMap lines = task.root().getLineMap(); + final long cursor = lines.getPosition(params.getRange().getStart().getLine() + 1, params.getRange().getStart().getColumn () + 1); + rewrites.putAll(overrideInheritedMethods(task, file, cursor)); + }); } List actions = new ArrayList<> (); @@ -184,15 +187,17 @@ public class CodeActionProvider implements ICodeActionProvider { LOG.info(String.format(Locale.getDefault (), "Check %d diagnostics for quick fixes...", params.getDiagnostics().size())); Instant started = Instant.now(); Path file = params.getFile (); - try (final CompileTask task = compiler.compile(file)) { - List actions = new ArrayList<>(); - for (DiagnosticItem d : params.getDiagnostics()) { - List newActions = codeActionForDiagnostic(task, file, d); - actions.addAll(newActions); - } - long elapsed = Duration.between(started, Instant.now()).toMillis(); - LOG.info(String.format(Locale.getDefault (), "...created %d quick fixes in %d ms", actions.size(), elapsed)); - return actions; + try (final SynchronizedTask synchronizedTask = compiler.compile(file)) { + return synchronizedTask.getWithTask (task -> { + List actions = new ArrayList<>(); + for (DiagnosticItem d : params.getDiagnostics()) { + List newActions = codeActionForDiagnostic(task, file, d); + actions.addAll(newActions); + } + long elapsed = Duration.between(started, Instant.now()).toMillis(); + LOG.info(String.format(Locale.getDefault (), "...created %d quick fixes in %d ms", actions.size(), elapsed)); + return actions; + }); } } diff --git a/lsp/java/src/main/java/com/itsaky/lsp/java/providers/JavaCompletionProvider.java b/lsp/java/src/main/java/com/itsaky/lsp/java/providers/JavaCompletionProvider.java index 4dd8cd3d3..e2b13286f 100644 --- a/lsp/java/src/main/java/com/itsaky/lsp/java/providers/JavaCompletionProvider.java +++ b/lsp/java/src/main/java/com/itsaky/lsp/java/providers/JavaCompletionProvider.java @@ -34,6 +34,7 @@ import com.itsaky.lsp.java.rewrite.AddImport; import com.itsaky.lsp.java.utils.EditHelper; import com.itsaky.lsp.java.utils.Extractors; import com.itsaky.lsp.java.utils.ScopeHelper; +import com.itsaky.lsp.java.utils.SynchronizedTask; import com.itsaky.lsp.java.visitors.FindCompletionsAt; import com.itsaky.lsp.java.visitors.PruneMethodBodies; import com.itsaky.lsp.models.Command; @@ -145,25 +146,27 @@ public class JavaCompletionProvider implements ICompletionProvider { SourceFileObject source = new SourceFileObject (file, contents, Instant.now()); String partial = partialIdentifier(contents, (int) cursor); boolean endsWithParen = endsWithParen(contents, (int) cursor); - try (CompileTask task = compiler.compile(Collections.singletonList (source))) { - LOG.info("...compiled in " + Duration.between(started, Instant.now()).toMillis() + "ms"); - TreePath path = new FindCompletionsAt (task.task).scan(task.root(), cursor); - switch (path.getLeaf().getKind()) { - case IDENTIFIER: - return completeIdentifier(task, path, partial, endsWithParen); - case MEMBER_SELECT: - return completeMemberSelect(task, path, partial, endsWithParen); - case MEMBER_REFERENCE: - return completeMemberReference(task, path, partial); - case SWITCH: - return completeSwitchConstant(task, path, partial); - case IMPORT: - return completeImport(qualifiedPartialIdentifier(contents, (int) cursor)); - default: - CompletionResult list = new CompletionResult(); - addKeywords(path, partial, list); - return list; - } + try (SynchronizedTask synchronizedTask = compiler.compile(Collections.singletonList (source))) { + return synchronizedTask.getWithTask (task -> { + LOG.info("...compiled in " + Duration.between(started, Instant.now()).toMillis() + "ms"); + TreePath path = new FindCompletionsAt (task.task).scan(task.root(), cursor); + switch (path.getLeaf().getKind()) { + case IDENTIFIER: + return completeIdentifier(task, path, partial, endsWithParen); + case MEMBER_SELECT: + return completeMemberSelect(task, path, partial, endsWithParen); + case MEMBER_REFERENCE: + return completeMemberReference(task, path, partial); + case SWITCH: + return completeSwitchConstant(task, path, partial); + case IMPORT: + return completeImport(qualifiedPartialIdentifier(contents, (int) cursor)); + default: + CompletionResult list = new CompletionResult(); + addKeywords(path, partial, list); + return list; + } + }); } } diff --git a/lsp/java/src/main/java/com/itsaky/lsp/java/providers/SignatureProvider.java b/lsp/java/src/main/java/com/itsaky/lsp/java/providers/SignatureProvider.java index 5cee7eb60..6c48a19e9 100644 --- a/lsp/java/src/main/java/com/itsaky/lsp/java/providers/SignatureProvider.java +++ b/lsp/java/src/main/java/com/itsaky/lsp/java/providers/SignatureProvider.java @@ -27,6 +27,7 @@ import com.itsaky.lsp.java.MarkdownHelper; import com.itsaky.lsp.java.ParseTask; import com.itsaky.lsp.java.utils.ScopeHelper; import com.itsaky.lsp.java.utils.ShortTypePrinter; +import com.itsaky.lsp.java.utils.SynchronizedTask; import com.itsaky.lsp.java.visitors.FindInvocationAt; import com.itsaky.lsp.models.ParameterInformation; import com.itsaky.lsp.models.SignatureHelp; @@ -89,39 +90,41 @@ public class SignatureProvider implements ISignatureHelpProvider { public SignatureHelp signatureHelp(Path file, int line, int column) { // TODO prune - try (CompileTask task = compiler.compile(file)) { - long cursor = task.root().getLineMap().getPosition(line, column); - TreePath path = new FindInvocationAt (task.task).scan(task.root(), cursor); - if (path == null) return NOT_SUPPORTED; - if (path.getLeaf() instanceof MethodInvocationTree) { - MethodInvocationTree invoke = (MethodInvocationTree) path.getLeaf(); - List overloads = methodOverloads(task, invoke); - List signatures = new ArrayList<> (); - for (ExecutableElement method : overloads) { - SignatureInformation info = info(method); - addSourceInfo(task, method, info); - addFancyLabel(info); - signatures.add(info); + try (SynchronizedTask synchronizedTask = compiler.compile(file)) { + return synchronizedTask.getWithTask (task -> { + long cursor = task.root().getLineMap().getPosition(line, column); + TreePath path = new FindInvocationAt (task.task).scan(task.root(), cursor); + if (path == null) return NOT_SUPPORTED; + if (path.getLeaf() instanceof MethodInvocationTree) { + MethodInvocationTree invoke = (MethodInvocationTree) path.getLeaf(); + List overloads = methodOverloads(task, invoke); + List signatures = new ArrayList<> (); + for (ExecutableElement method : overloads) { + SignatureInformation info = info(method); + addSourceInfo(task, method, info); + addFancyLabel(info); + signatures.add(info); + } + int activeSignature = activeSignature(task, path, invoke.getArguments(), overloads); + int activeParameter = activeParameter(task, invoke.getArguments(), cursor); + return new SignatureHelp(signatures, activeSignature, activeParameter); } - int activeSignature = activeSignature(task, path, invoke.getArguments(), overloads); - int activeParameter = activeParameter(task, invoke.getArguments(), cursor); - return new SignatureHelp(signatures, activeSignature, activeParameter); - } - if (path.getLeaf() instanceof NewClassTree) { - NewClassTree invoke = (NewClassTree) path.getLeaf(); - List overloads = constructorOverloads(task, invoke); - List signatures = new ArrayList<> (); - for (ExecutableElement method : overloads) { - SignatureInformation info = info(method); - addSourceInfo(task, method, info); - addFancyLabel(info); - signatures.add(info); + if (path.getLeaf() instanceof NewClassTree) { + NewClassTree invoke = (NewClassTree) path.getLeaf(); + List overloads = constructorOverloads(task, invoke); + List signatures = new ArrayList<> (); + for (ExecutableElement method : overloads) { + SignatureInformation info = info(method); + addSourceInfo(task, method, info); + addFancyLabel(info); + signatures.add(info); + } + int activeSignature = activeSignature(task, path, invoke.getArguments(), overloads); + int activeParameter = activeParameter(task, invoke.getArguments(), cursor); + return new SignatureHelp (signatures, activeSignature, activeParameter); } - int activeSignature = activeSignature(task, path, invoke.getArguments(), overloads); - int activeParameter = activeParameter(task, invoke.getArguments(), cursor); - return new SignatureHelp (signatures, activeSignature, activeParameter); - } - return NOT_SUPPORTED; + return NOT_SUPPORTED; + }); } } diff --git a/lsp/java/src/main/java/com/itsaky/lsp/java/rewrite/AddException.java b/lsp/java/src/main/java/com/itsaky/lsp/java/rewrite/AddException.java index 3387b39d7..5ed02fb05 100644 --- a/lsp/java/src/main/java/com/itsaky/lsp/java/rewrite/AddException.java +++ b/lsp/java/src/main/java/com/itsaky/lsp/java/rewrite/AddException.java @@ -20,6 +20,7 @@ package com.itsaky.lsp.java.rewrite; import com.itsaky.lsp.java.CompileTask; import com.itsaky.lsp.java.CompilerProvider; import com.itsaky.lsp.java.utils.FindHelper; +import com.itsaky.lsp.java.utils.SynchronizedTask; import com.itsaky.lsp.models.Position; import com.itsaky.lsp.models.Range; import com.itsaky.lsp.models.TextEdit; @@ -53,32 +54,34 @@ public class AddException implements Rewrite { return CANCELLED; } - try (CompileTask task = compiler.compile(file)) { - Trees trees = Trees.instance(task.task); - ExecutableElement methodElement = FindHelper.findMethod(task, className, methodName, erasedParameterTypes); - MethodTree methodTree = trees.getTree(methodElement); - SourcePositions pos = trees.getSourcePositions(); - LineMap lines = task.root().getLineMap(); - long startBody = pos.getStartPosition(task.root(), methodTree.getBody()); - int line = (int) lines.getLineNumber(startBody); - int column = (int) lines.getColumnNumber(startBody); - Position insertPos = new Position(line - 1, column - 1); - String simpleName = exceptionType; - int lastDot = simpleName.lastIndexOf('.'); - if (lastDot != -1) { - simpleName = exceptionType.substring(lastDot + 1); - } - - String insertText; - if (methodTree.getThrows().isEmpty()) { - insertText = "throws " + simpleName + " "; - } else { - insertText = ", " + simpleName + " "; - } - TextEdit insertThrows = new TextEdit(new Range (insertPos, insertPos), insertText); - // TODO add import if needed - TextEdit[] edits = {insertThrows}; - return Collections.singletonMap (file, edits); + try (SynchronizedTask synchronizedTask = compiler.compile(file)) { + return synchronizedTask.getWithTask (task -> { + Trees trees = Trees.instance(task.task); + ExecutableElement methodElement = FindHelper.findMethod(task, className, methodName, erasedParameterTypes); + MethodTree methodTree = trees.getTree(methodElement); + SourcePositions pos = trees.getSourcePositions(); + LineMap lines = task.root().getLineMap(); + long startBody = pos.getStartPosition(task.root(), methodTree.getBody()); + int line = (int) lines.getLineNumber(startBody); + int column = (int) lines.getColumnNumber(startBody); + Position insertPos = new Position(line - 1, column - 1); + String simpleName = exceptionType; + int lastDot = simpleName.lastIndexOf('.'); + if (lastDot != -1) { + simpleName = exceptionType.substring(lastDot + 1); + } + + String insertText; + if (methodTree.getThrows().isEmpty()) { + insertText = "throws " + simpleName + " "; + } else { + insertText = ", " + simpleName + " "; + } + TextEdit insertThrows = new TextEdit(new Range (insertPos, insertPos), insertText); + // TODO add import if needed + TextEdit[] edits = {insertThrows}; + return Collections.singletonMap (file, edits); + }); } } } \ No newline at end of file diff --git a/lsp/java/src/main/java/com/itsaky/lsp/java/rewrite/AddSuppressWarningAnnotation.java b/lsp/java/src/main/java/com/itsaky/lsp/java/rewrite/AddSuppressWarningAnnotation.java index ec0c695b5..4d5c03fe3 100644 --- a/lsp/java/src/main/java/com/itsaky/lsp/java/rewrite/AddSuppressWarningAnnotation.java +++ b/lsp/java/src/main/java/com/itsaky/lsp/java/rewrite/AddSuppressWarningAnnotation.java @@ -21,6 +21,7 @@ import com.itsaky.lsp.java.CompileTask; import com.itsaky.lsp.java.CompilerProvider; import com.itsaky.lsp.java.utils.EditHelper; import com.itsaky.lsp.java.utils.FindHelper; +import com.itsaky.lsp.java.utils.SynchronizedTask; import com.itsaky.lsp.models.Position; import com.itsaky.lsp.models.Range; import com.itsaky.lsp.models.TextEdit; @@ -51,22 +52,24 @@ public class AddSuppressWarningAnnotation implements Rewrite { if (file == CompilerProvider.NOT_FOUND) { return CANCELLED; } - try (CompileTask task = compiler.compile(file)) { - Trees trees = Trees.instance(task.task); - ExecutableElement methodElement = FindHelper.findMethod(task, className, methodName, erasedParameterTypes); - MethodTree methodTree = trees.getTree(methodElement); - SourcePositions pos = trees.getSourcePositions(); - int startMethod = (int) pos.getStartPosition(task.root(), methodTree); - LineMap lines = task.root().getLineMap(); - int line = (int) lines.getLineNumber(startMethod); - int column = (int) lines.getColumnNumber(startMethod); - int startLine = (int) lines.getStartPosition(line); - String indent = EditHelper.repeatSpaces (startMethod - startLine); - String insertText = "@SuppressWarnings(\"unchecked\")\n" + indent; - Position insertPoint = new Position(line - 1, column - 1); - TextEdit insert = new TextEdit(new Range(insertPoint, insertPoint), insertText); - TextEdit[] edits = {insert}; - return Collections.singletonMap (file, edits); + try (SynchronizedTask synchronizedTask = compiler.compile(file)) { + return synchronizedTask.getWithTask (task -> { + Trees trees = Trees.instance(task.task); + ExecutableElement methodElement = FindHelper.findMethod(task, className, methodName, erasedParameterTypes); + MethodTree methodTree = trees.getTree(methodElement); + SourcePositions pos = trees.getSourcePositions(); + int startMethod = (int) pos.getStartPosition(task.root(), methodTree); + LineMap lines = task.root().getLineMap(); + int line = (int) lines.getLineNumber(startMethod); + int column = (int) lines.getColumnNumber(startMethod); + int startLine = (int) lines.getStartPosition(line); + String indent = EditHelper.repeatSpaces (startMethod - startLine); + String insertText = "@SuppressWarnings(\"unchecked\")\n" + indent; + Position insertPoint = new Position(line - 1, column - 1); + TextEdit insert = new TextEdit(new Range(insertPoint, insertPoint), insertText); + TextEdit[] edits = {insert}; + return Collections.singletonMap (file, edits); + }); } } } \ No newline at end of file diff --git a/lsp/java/src/main/java/com/itsaky/lsp/java/rewrite/CreateMissingMethod.java b/lsp/java/src/main/java/com/itsaky/lsp/java/rewrite/CreateMissingMethod.java index d012b42da..18f8c69f9 100644 --- a/lsp/java/src/main/java/com/itsaky/lsp/java/rewrite/CreateMissingMethod.java +++ b/lsp/java/src/main/java/com/itsaky/lsp/java/rewrite/CreateMissingMethod.java @@ -21,6 +21,7 @@ import com.itsaky.androidide.utils.Logger; import com.itsaky.lsp.java.CompileTask; import com.itsaky.lsp.java.CompilerProvider; import com.itsaky.lsp.java.utils.EditHelper; +import com.itsaky.lsp.java.utils.SynchronizedTask; import com.itsaky.lsp.java.visitors.FindMethodCallAt; import com.itsaky.lsp.models.Position; import com.itsaky.lsp.models.Range; @@ -61,48 +62,52 @@ public class CreateMissingMethod implements Rewrite { @Override public Map rewrite(CompilerProvider compiler) { - try (CompileTask task = compiler.compile(file)) { - final Trees trees = Trees.instance(task.task); - final FindMethodCallAt methodFinder = new FindMethodCallAt (task.task); - final MethodInvocationTree call = methodFinder.scan(task.root(), position); - if (call == null) { - return CANCELLED; - } - - final TreePath path = trees.getPath(task.root(), call); - final String returnType = methodFinder.getReturnType(); - Path sourceFile = file; - MethodTree currentMethod = surroundingMethod(path); - String insertText = "\n"; - - insertText += printMethodHeader(task, call, returnType, methodFinder.isMemberSelect(), (currentMethod.getModifiers().getFlags().contains(Modifier.STATIC) || methodFinder.isStaticAccess())) + " {\n" + - " // TODO: Implement this method\n" + - " " + createReturnStatement(returnType) + "\n" + - "}"; - - TextEdit[] edits; - if(methodFinder.isMemberSelect()) { - // Accessing method from another class - final CompilationUnitTree compilationUnit = methodFinder.getEnclosingTreePath().getCompilationUnit(); - final ClassTree enclosingClass = methodFinder.getEnclosingClass(); - final int indent = EditHelper.indent(task.task, compilationUnit, enclosingClass) + 4; - insertText = insertText.replaceAll("\n", "\n" + EditHelper.repeatSpaces (indent)); - insertText = insertText + "\n"; - final Position insertPoint = EditHelper.insertAtEndOfClass(task.task, compilationUnit, enclosingClass); - edits = new TextEdit[]{new TextEdit(new Range (insertPoint, insertPoint), insertText)}; - sourceFile = Paths.get (compilationUnit.getSourceFile ().toUri ()); - } else { - ClassTree surroundingClass = surroundingClass(path); - int indent = EditHelper.indent(task.task, task.root(), surroundingClass) + 4; - insertText = insertText.replaceAll("\n", "\n" + EditHelper.repeatSpaces (indent)); - insertText = insertText + "\n"; - Position insertPoint = EditHelper.insertAfter(task.task, task.root(), surroundingMethod(path)); - edits = new TextEdit[]{new TextEdit(new Range(insertPoint, insertPoint), insertText)}; - } - - if(file != null) - return Collections.singletonMap (sourceFile, edits); - else return null; + try (SynchronizedTask synchronizedTask = compiler.compile(file)) { + return synchronizedTask.getWithTask (task -> { + final Trees trees = Trees.instance(task.task); + final FindMethodCallAt methodFinder = new FindMethodCallAt (task.task); + final MethodInvocationTree call = methodFinder.scan(task.root(), position); + if (call == null) { + return CANCELLED; + } + + final TreePath path = trees.getPath(task.root(), call); + final String returnType = methodFinder.getReturnType(); + Path sourceFile = file; + MethodTree currentMethod = surroundingMethod(path); + String insertText = "\n"; + + insertText += printMethodHeader(task, call, returnType, methodFinder.isMemberSelect(), (currentMethod.getModifiers().getFlags().contains(Modifier.STATIC) || methodFinder.isStaticAccess())) + " {\n" + + " // TODO: Implement this method\n" + + " " + createReturnStatement(returnType) + "\n" + + "}"; + + TextEdit[] edits; + if(methodFinder.isMemberSelect()) { + // Accessing method from another class + final CompilationUnitTree compilationUnit = methodFinder.getEnclosingTreePath().getCompilationUnit(); + final ClassTree enclosingClass = methodFinder.getEnclosingClass(); + final int indent = EditHelper.indent(task.task, compilationUnit, enclosingClass) + 4; + insertText = insertText.replaceAll("\n", "\n" + EditHelper.repeatSpaces (indent)); + insertText = insertText + "\n"; + final Position insertPoint = EditHelper.insertAtEndOfClass(task.task, compilationUnit, enclosingClass); + edits = new TextEdit[]{new TextEdit(new Range (insertPoint, insertPoint), insertText)}; + sourceFile = Paths.get (compilationUnit.getSourceFile ().toUri ()); + } else { + ClassTree surroundingClass = surroundingClass(path); + int indent = EditHelper.indent(task.task, task.root(), surroundingClass) + 4; + insertText = insertText.replaceAll("\n", "\n" + EditHelper.repeatSpaces (indent)); + insertText = insertText + "\n"; + Position insertPoint = EditHelper.insertAfter(task.task, task.root(), surroundingMethod(path)); + edits = new TextEdit[]{new TextEdit(new Range(insertPoint, insertPoint), insertText)}; + } + + if(file != null) { + return Collections.singletonMap (sourceFile, edits); + } else { + return null; + } + }); } } diff --git a/lsp/java/src/main/java/com/itsaky/lsp/java/rewrite/GenerateRecordConstructor.java b/lsp/java/src/main/java/com/itsaky/lsp/java/rewrite/GenerateRecordConstructor.java index abdcccd54..46b754201 100644 --- a/lsp/java/src/main/java/com/itsaky/lsp/java/rewrite/GenerateRecordConstructor.java +++ b/lsp/java/src/main/java/com/itsaky/lsp/java/rewrite/GenerateRecordConstructor.java @@ -21,6 +21,7 @@ import com.itsaky.androidide.utils.Logger; import com.itsaky.lsp.java.CompileTask; import com.itsaky.lsp.java.CompilerProvider; import com.itsaky.lsp.java.utils.EditHelper; +import com.itsaky.lsp.java.utils.SynchronizedTask; import com.itsaky.lsp.models.Position; import com.itsaky.lsp.models.Range; import com.itsaky.lsp.models.TextEdit; @@ -55,31 +56,33 @@ public class GenerateRecordConstructor implements Rewrite { LOG.info("Generate default constructor for " + className + "..."); // TODO this needs to fall back on looking for inner classes and package-private classes Path file = compiler.findTypeDeclaration(className); - try (CompileTask task = compiler.compile(file)) { - TypeElement typeElement = task.task.getElements().getTypeElement(className); - ClassTree typeTree = Trees.instance(task.task).getTree(typeElement); - List fields = fieldsNeedingInitialization(typeTree); - String parameters = generateParameters(task, fields); - String initializers = generateInitializers(fields); - StringBuilder buf = new StringBuilder (); - buf.append("\n"); - if (typeTree.getModifiers().getFlags().contains(Modifier.PUBLIC)) { - buf.append("public "); - } - - buf.append(simpleName(className)) - .append("(") - .append(parameters) - .append(") {\n ") - .append(initializers) - .append("\n}"); - String string = buf.toString(); - int indent = EditHelper.indent(task.task, task.root(), typeTree) + 4; - string = string.replaceAll("\n", "\n" + EditHelper.repeatSpaces (indent)); - string = string + "\n\n"; - Position insert = insertPoint(task, typeTree); - TextEdit[] edits = {new TextEdit(new Range (insert, insert), string)}; - return Collections.singletonMap (file, edits); + try (SynchronizedTask synchronizedTask = compiler.compile(file)) { + return synchronizedTask.getWithTask (task -> { + TypeElement typeElement = task.task.getElements().getTypeElement(className); + ClassTree typeTree = Trees.instance(task.task).getTree(typeElement); + List fields = fieldsNeedingInitialization(typeTree); + String parameters = generateParameters(task, fields); + String initializers = generateInitializers(fields); + StringBuilder buf = new StringBuilder (); + buf.append("\n"); + if (typeTree.getModifiers().getFlags().contains(Modifier.PUBLIC)) { + buf.append("public "); + } + + buf.append(simpleName(className)) + .append("(") + .append(parameters) + .append(") {\n ") + .append(initializers) + .append("\n}"); + String string = buf.toString(); + int indent = EditHelper.indent(task.task, task.root(), typeTree) + 4; + string = string.replaceAll("\n", "\n" + EditHelper.repeatSpaces (indent)); + string = string + "\n\n"; + Position insert = insertPoint(task, typeTree); + TextEdit[] edits = {new TextEdit(new Range (insert, insert), string)}; + return Collections.singletonMap (file, edits); + }); } } diff --git a/lsp/java/src/main/java/com/itsaky/lsp/java/rewrite/ImplementAbstractMethods.java b/lsp/java/src/main/java/com/itsaky/lsp/java/rewrite/ImplementAbstractMethods.java index 1c0d40ad7..4b04f3076 100644 --- a/lsp/java/src/main/java/com/itsaky/lsp/java/rewrite/ImplementAbstractMethods.java +++ b/lsp/java/src/main/java/com/itsaky/lsp/java/rewrite/ImplementAbstractMethods.java @@ -23,6 +23,7 @@ import com.itsaky.lsp.java.CompilerProvider; import com.itsaky.lsp.java.ParseTask; import com.itsaky.lsp.java.utils.EditHelper; import com.itsaky.lsp.java.utils.FindHelper; +import com.itsaky.lsp.java.utils.SynchronizedTask; import com.itsaky.lsp.models.Position; import com.itsaky.lsp.models.Range; import com.itsaky.lsp.models.TextEdit; @@ -67,30 +68,32 @@ public class ImplementAbstractMethods implements Rewrite { @Override public Map rewrite(CompilerProvider compiler) { StringJoiner insertText = new StringJoiner ("\n"); - try (CompileTask task = compiler.compile(file)) { - Elements elements = task.task.getElements(); - Types types = task.task.getTypes(); - Trees trees = Trees.instance(task.task); - TypeElement thisClass = (TypeElement) trees.getElement(this.path); - DeclaredType thisType = (DeclaredType) thisClass.asType(); - ClassTree thisTree = trees.getTree(thisClass); - int indent = EditHelper.indent(task.task, task.root(), thisTree) + 4; - for (Element member : elements.getAllMembers(thisClass)) { - if (member.getKind() == ElementKind.METHOD && member.getModifiers().contains(Modifier.ABSTRACT)) { - ExecutableElement method = (ExecutableElement) member; - MethodTree source = findSource(compiler, task, method); - if (source == null) { - LOG.warn("...couldn't find source for " + method); + try (SynchronizedTask synchronizedTask = compiler.compile(file)) { + return synchronizedTask.getWithTask (task -> { + Elements elements = task.task.getElements(); + Types types = task.task.getTypes(); + Trees trees = Trees.instance(task.task); + TypeElement thisClass = (TypeElement) trees.getElement(this.path); + DeclaredType thisType = (DeclaredType) thisClass.asType(); + ClassTree thisTree = trees.getTree(thisClass); + int indent = EditHelper.indent(task.task, task.root(), thisTree) + 4; + for (Element member : elements.getAllMembers(thisClass)) { + if (member.getKind() == ElementKind.METHOD && member.getModifiers().contains(Modifier.ABSTRACT)) { + ExecutableElement method = (ExecutableElement) member; + MethodTree source = findSource(compiler, task, method); + if (source == null) { + LOG.warn("...couldn't find source for " + method); + } + ExecutableType parameterizedType = (ExecutableType) types.asMemberOf(thisType, method); + String text = EditHelper.printMethod(method, parameterizedType, source); + text = text.replaceAll("\n", "\n" + EditHelper.repeatSpaces (indent)); + insertText.add(text); } - ExecutableType parameterizedType = (ExecutableType) types.asMemberOf(thisType, method); - String text = EditHelper.printMethod(method, parameterizedType, source); - text = text.replaceAll("\n", "\n" + EditHelper.repeatSpaces (indent)); - insertText.add(text); } - } - Position insert = EditHelper.insertAtEndOfClass(task.task, task.root(), thisTree); - TextEdit[] edits = {new TextEdit(new Range (insert, insert), insertText + "\n")}; - return Collections.singletonMap (file, edits); + Position insert = EditHelper.insertAtEndOfClass(task.task, task.root(), thisTree); + TextEdit[] edits = {new TextEdit(new Range (insert, insert), insertText + "\n")}; + return Collections.singletonMap (file, edits); + }); } } diff --git a/lsp/java/src/main/java/com/itsaky/lsp/java/rewrite/OverrideInheritedMethod.java b/lsp/java/src/main/java/com/itsaky/lsp/java/rewrite/OverrideInheritedMethod.java index 68c63333d..eb20a69fd 100644 --- a/lsp/java/src/main/java/com/itsaky/lsp/java/rewrite/OverrideInheritedMethod.java +++ b/lsp/java/src/main/java/com/itsaky/lsp/java/rewrite/OverrideInheritedMethod.java @@ -22,6 +22,7 @@ import com.itsaky.lsp.java.CompilerProvider; import com.itsaky.lsp.java.ParseTask; import com.itsaky.lsp.java.utils.EditHelper; import com.itsaky.lsp.java.utils.FindHelper; +import com.itsaky.lsp.java.utils.SynchronizedTask; import com.itsaky.lsp.java.visitors.FindTypeDeclarationAt; import com.itsaky.lsp.models.Position; import com.itsaky.lsp.models.Range; @@ -69,23 +70,25 @@ public class OverrideInheritedMethod implements Rewrite { } private String insertText(CompilerProvider compiler) { - try (final CompileTask task = compiler.compile(file)) { - final Types types = task.task.getTypes(); - final Trees trees = Trees.instance(task.task); - final ExecutableElement superMethod = FindHelper.findMethod(task, superClassName, methodName, erasedParameterTypes); - final ClassTree thisTree = new FindTypeDeclarationAt (task.task).scan(task.root(), (long) insertPosition); - final TreePath thisPath = trees.getPath(task.root(), thisTree); - final TypeElement thisClass = (TypeElement) trees.getElement(thisPath); - final ExecutableType parameterizedType = (ExecutableType) types.asMemberOf((DeclaredType) thisClass.asType(), superMethod); - final int indent = EditHelper.indent(task.task, task.root(), thisTree) + 4; - final Optional sourceFile = compiler.findAnywhere(superClassName); - if (!sourceFile.isPresent ()) return ""; - final ParseTask parse = compiler.parse(sourceFile.get()); - final MethodTree source = FindHelper.findMethod(parse, superClassName, methodName, erasedParameterTypes); - String text = EditHelper.printMethod(superMethod, parameterizedType, source); - text = text.replaceAll("\n", "\n" + EditHelper.repeatSpaces (indent)); - text = text + "\n\n"; - return text; + try (final SynchronizedTask synchronizedTask = compiler.compile(file)) { + return synchronizedTask.getWithTask (task -> { + final Types types = task.task.getTypes(); + final Trees trees = Trees.instance(task.task); + final ExecutableElement superMethod = FindHelper.findMethod(task, superClassName, methodName, erasedParameterTypes); + final ClassTree thisTree = new FindTypeDeclarationAt (task.task).scan(task.root(), (long) insertPosition); + final TreePath thisPath = trees.getPath(task.root(), thisTree); + final TypeElement thisClass = (TypeElement) trees.getElement(thisPath); + final ExecutableType parameterizedType = (ExecutableType) types.asMemberOf((DeclaredType) thisClass.asType(), superMethod); + final int indent = EditHelper.indent(task.task, task.root(), thisTree) + 4; + final Optional sourceFile = compiler.findAnywhere(superClassName); + if (!sourceFile.isPresent ()) return ""; + final ParseTask parse = compiler.parse(sourceFile.get()); + final MethodTree source = FindHelper.findMethod(parse, superClassName, methodName, erasedParameterTypes); + String text = EditHelper.printMethod(superMethod, parameterizedType, source); + text = text.replaceAll("\n", "\n" + EditHelper.repeatSpaces (indent)); + text = text + "\n\n"; + return text; + }); } } diff --git a/lsp/java/src/main/java/com/itsaky/lsp/java/rewrite/RemoveException.java b/lsp/java/src/main/java/com/itsaky/lsp/java/rewrite/RemoveException.java index a53ec8a98..7711c076f 100644 --- a/lsp/java/src/main/java/com/itsaky/lsp/java/rewrite/RemoveException.java +++ b/lsp/java/src/main/java/com/itsaky/lsp/java/rewrite/RemoveException.java @@ -20,6 +20,7 @@ package com.itsaky.lsp.java.rewrite; import com.itsaky.lsp.java.CompileTask; import com.itsaky.lsp.java.CompilerProvider; import com.itsaky.lsp.java.utils.FindHelper; +import com.itsaky.lsp.java.utils.SynchronizedTask; import com.itsaky.lsp.models.Position; import com.itsaky.lsp.models.Range; import com.itsaky.lsp.models.TextEdit; @@ -58,20 +59,22 @@ public class RemoveException implements Rewrite { @Override public Map rewrite(CompilerProvider compiler) { Path file = compiler.findTypeDeclaration(className); - try (CompileTask task = compiler.compile(file)) { - ExecutableElement methodElement = FindHelper.findMethod(task, className, methodName, erasedParameterTypes); - MethodTree methodTree = Trees.instance(task.task).getTree(methodElement); - if (methodTree.getThrows().size() == 1) { - TextEdit delete = removeEntireThrows(task.task, task.root(), methodTree); - if (delete == TextEdit.NONE) { - return CANCELLED; + try (SynchronizedTask synchronizedTask = compiler.compile(file)) { + return synchronizedTask.getWithTask (task -> { + ExecutableElement methodElement = FindHelper.findMethod(task, className, methodName, erasedParameterTypes); + MethodTree methodTree = Trees.instance(task.task).getTree(methodElement); + if (methodTree.getThrows().size() == 1) { + TextEdit delete = removeEntireThrows(task.task, task.root(), methodTree); + if (delete == TextEdit.NONE) { + return CANCELLED; + } + + TextEdit[] edits = {delete}; + return Collections.singletonMap (file, edits); } - - TextEdit[] edits = {delete}; + TextEdit[] edits = {removeSingleException(task.task, task.root(), methodTree)}; return Collections.singletonMap (file, edits); - } - TextEdit[] edits = {removeSingleException(task.task, task.root(), methodTree)}; - return Collections.singletonMap (file, edits); + }); } } diff --git a/lsp/java/src/main/java/com/itsaky/lsp/java/rewrite/RemoveMethod.java b/lsp/java/src/main/java/com/itsaky/lsp/java/rewrite/RemoveMethod.java index d03e4b4c5..ab643356b 100644 --- a/lsp/java/src/main/java/com/itsaky/lsp/java/rewrite/RemoveMethod.java +++ b/lsp/java/src/main/java/com/itsaky/lsp/java/rewrite/RemoveMethod.java @@ -20,6 +20,7 @@ import com.itsaky.lsp.java.CompileTask; import com.itsaky.lsp.java.CompilerProvider; import com.itsaky.lsp.java.utils.EditHelper; import com.itsaky.lsp.java.utils.FindHelper; +import com.itsaky.lsp.java.utils.SynchronizedTask; import com.itsaky.lsp.models.TextEdit; import com.sun.source.tree.MethodTree; import com.sun.source.util.Trees; @@ -46,11 +47,13 @@ public class RemoveMethod implements Rewrite { if (file == CompilerProvider.NOT_FOUND) { return CANCELLED; } - try (CompileTask task = compiler.compile(file)) { - ExecutableElement methodElement = FindHelper.findMethod(task, className, methodName, erasedParameterTypes); - MethodTree methodTree = Trees.instance(task.task).getTree(methodElement); - TextEdit[] edits = {EditHelper.removeTree(task.task, task.root(), methodTree)}; - return Collections.singletonMap (file, edits); + try (SynchronizedTask synchronizedTask = compiler.compile(file)) { + return synchronizedTask.getWithTask (task -> { + ExecutableElement methodElement = FindHelper.findMethod(task, className, methodName, erasedParameterTypes); + MethodTree methodTree = Trees.instance(task.task).getTree(methodElement); + TextEdit[] edits = {EditHelper.removeTree(task.task, task.root(), methodTree)}; + return Collections.singletonMap (file, edits); + }); } } } \ No newline at end of file diff --git a/lsp/java/src/main/java/com/itsaky/lsp/java/utils/SynchronizedTask.java b/lsp/java/src/main/java/com/itsaky/lsp/java/utils/SynchronizedTask.java new file mode 100644 index 000000000..1111052d1 --- /dev/null +++ b/lsp/java/src/main/java/com/itsaky/lsp/java/utils/SynchronizedTask.java @@ -0,0 +1,50 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.lsp.java.utils; + +import com.itsaky.lsp.java.CompileTask; + +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * @author Akash Yadav + */ +public class SynchronizedTask implements AutoCloseable { + + private volatile CompileTask task; + + public synchronized void runWithTask (Consumer taskConsumer) { + taskConsumer.accept (task); + } + + public synchronized T getWithTask (Function function) { + return function.apply (task); + } + + public synchronized void setTask (CompileTask task) { + this.task = task; + } + + @Override + public void close () { + if (task != null) { + task.close (); + } + } +} diff --git a/lsp/java/src/main/java/com/itsaky/lsp/java/visitors/FindCompletionsAt.java b/lsp/java/src/main/java/com/itsaky/lsp/java/visitors/FindCompletionsAt.java index 01bde5217..c36fca149 100644 --- a/lsp/java/src/main/java/com/itsaky/lsp/java/visitors/FindCompletionsAt.java +++ b/lsp/java/src/main/java/com/itsaky/lsp/java/visitors/FindCompletionsAt.java @@ -32,6 +32,7 @@ import com.sun.source.util.TreePathScanner; import com.sun.source.util.Trees; public class FindCompletionsAt extends TreePathScanner { + private final JavacTask task; private CompilationUnitTree root;