Use synchronized methods for java compilation

This commit is contained in:
Akash Yadav
2022-01-17 11:15:28 +05:30
parent 7eca1cdc70
commit 314c50d4f2
17 changed files with 371 additions and 268 deletions
@@ -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<CompilationUnitTree> roots;
public final List<Diagnostic<? extends JavaFileObject>> diagnostics;
private final Runnable close;
private final CompileBatch compileBatch;
public CompileTask(@NonNull CompileBatch compileBatch, List<Diagnostic<? extends JavaFileObject>> 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<CompilationUnitTree> roots,
List<Diagnostic<? extends JavaFileObject>> diagnostics,
Runnable close) {
this.task = task;
this.roots = roots;
this.diagnostics = diagnostics;
this.close = close;
}
@Override
public void close() {
close.run();
compileBatch.close ();
}
}
@@ -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<? extends JavaFileObject> sources);
SynchronizedTask compile(Collection<? extends JavaFileObject> sources);
Path NOT_FOUND = Paths.get("");
}
@@ -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<Path> classPath, docPath;
final Set<String> jdkClasses = new HashSet<> (), classPathClasses;
final ReusableCompiler compiler = new ReusableCompiler ();
final SynchronizedTask synchronizedTask = new SynchronizedTask ();
final ReentrantLock lock = new ReentrantLock ();
final List<Diagnostic<? extends JavaFileObject>> diags = new ArrayList<> ();
final Map<JavaFileObject, Long> 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<JavaFileObject, Long> cachedModified = new HashMap<> ();
private boolean needsCompile (Collection<? extends JavaFileObject> sources) {
if (cachedModified.size () != sources.size ()) {
@@ -325,18 +327,34 @@ class JavaCompilerService implements CompilerProvider {
}
@Override
public CompileTask compile (Path... files) {
List<JavaFileObject> 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<JavaFileObject> sources = new ArrayList<> ();
for (Path f : files) {
sources.add (new SourceFileObject (f));
}
return compile (sources);
});
}
@Override
public CompileTask compile (Collection<? extends JavaFileObject> sources) {
CompileBatch compile = compileBatch (sources);
return new CompileTask (compile.task, compile.roots, diags, compile::close);
public synchronized SynchronizedTask compile (Collection<? extends JavaFileObject> 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");
@@ -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<String> currentOptions = new ArrayList<> ();
@@ -119,8 +119,7 @@ class ReusableCompiler {
}
checkedOut = true;
List<String> opts =
StreamSupport.stream (options.spliterator (), false).collect (Collectors.toCollection (ArrayList::new));
List<String> 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 {
}
}
@@ -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<String, Rewrite> 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<CodeActionItem> 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<CodeActionItem> actions = new ArrayList<>();
for (DiagnosticItem d : params.getDiagnostics()) {
List<CodeActionItem> 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<CodeActionItem> actions = new ArrayList<>();
for (DiagnosticItem d : params.getDiagnostics()) {
List<CodeActionItem> 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;
});
}
}
@@ -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;
}
});
}
}
@@ -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<ExecutableElement> overloads = methodOverloads(task, invoke);
List<SignatureInformation> 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<ExecutableElement> overloads = methodOverloads(task, invoke);
List<SignatureInformation> 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<ExecutableElement> overloads = constructorOverloads(task, invoke);
List<SignatureInformation> 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<ExecutableElement> overloads = constructorOverloads(task, invoke);
List<SignatureInformation> 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;
});
}
}
@@ -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);
});
}
}
}
@@ -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);
});
}
}
}
@@ -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<Path, TextEdit[]> 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;
}
});
}
}
@@ -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<VariableTree> 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<VariableTree> 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);
});
}
}
@@ -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<Path, TextEdit[]> 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);
});
}
}
@@ -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<JavaFileObject> 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<JavaFileObject> 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;
});
}
}
@@ -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<Path, TextEdit[]> 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);
});
}
}
@@ -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);
});
}
}
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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<CompileTask> taskConsumer) {
taskConsumer.accept (task);
}
public synchronized <T> T getWithTask (Function<CompileTask, T> function) {
return function.apply (task);
}
public synchronized void setTask (CompileTask task) {
this.task = task;
}
@Override
public void close () {
if (task != null) {
task.close ();
}
}
}
@@ -32,6 +32,7 @@ import com.sun.source.util.TreePathScanner;
import com.sun.source.util.Trees;
public class FindCompletionsAt extends TreePathScanner<TreePath, Long> {
private final JavacTask task;
private CompilationUnitTree root;