summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--TODOS.md2
-rw-r--r--src/main/java/org/javacs/JavaCompilerService.java246
-rw-r--r--src/main/java/org/javacs/TemplatePrinter.java136
-rw-r--r--src/test/java/org/javacs/CompletionsTest.java8
-rw-r--r--src/test/test-project/workspace/src/org/javacs/example/AutocompleteOverride.java10
5 files changed, 323 insertions, 79 deletions
diff --git a/TODOS.md b/TODOS.md
index a825d89..00378e0 100644
--- a/TODOS.md
+++ b/TODOS.md
@@ -8,7 +8,7 @@
- NameOfClass... default constructor initializing final fields
- im[plements]
- Annotation fields
-- @Override [method body from super]
+- cc should match CamelCase
## Navigation
- Go-to-subclasses
diff --git a/src/main/java/org/javacs/JavaCompilerService.java b/src/main/java/org/javacs/JavaCompilerService.java
index c1915fb..1935459 100644
--- a/src/main/java/org/javacs/JavaCompilerService.java
+++ b/src/main/java/org/javacs/JavaCompilerService.java
@@ -288,6 +288,64 @@ public class JavaCompilerService {
return new FindSmallest().find(cache.root);
}
+ private List<ExecutableElement> virtualMethods(DeclaredType type) {
+ var result = new ArrayList<ExecutableElement>();
+ for (var member : type.asElement().getEnclosedElements()) {
+ if (member instanceof ExecutableElement) {
+ var method = (ExecutableElement) member;
+ if (!method.getSimpleName().contentEquals("<init>") && !method.getModifiers().contains(Modifier.STATIC)) {
+ result.add(method);
+ }
+ }
+ }
+ return result;
+ }
+
+ private TypeMirror enclosingClass(URI file, String contents, int line, int character) {
+ recompile(file, contents, line, character);
+
+ var trees = Trees.instance(cache.task);
+ var path = path(file, line, character);
+ while (!(path.getLeaf() instanceof ClassTree)) path = path.getParentPath();
+ var enclosingClass = trees.getElement(path);
+
+ return enclosingClass.asType();
+ }
+
+ private List<ExecutableElement> thisMethods(URI file, String contents, int line, int character) {
+ var thisType = enclosingClass(file, contents, line, character);
+ var types = cache.task.getTypes();
+ var result = new ArrayList<ExecutableElement>();
+
+ if (thisType instanceof DeclaredType) {
+ var type = (DeclaredType) thisType;
+ result.addAll(virtualMethods(type));
+ }
+
+ return result;
+ }
+
+ private void collectSuperMethods(TypeMirror thisType, List<ExecutableElement> result) {
+ var types = cache.task.getTypes();
+
+ for (var superType : types.directSupertypes(thisType)) {
+ if (superType instanceof DeclaredType) {
+ var type = (DeclaredType) superType;
+ result.addAll(virtualMethods(type));
+ collectSuperMethods(type, result);
+ }
+ }
+ }
+
+ private List<ExecutableElement> superMethods(URI file, String contents, int line, int character) {
+ var thisType = enclosingClass(file, contents, line, character);
+ var result = new ArrayList<ExecutableElement>();
+
+ collectSuperMethods(thisType, result);
+
+ return result;
+ }
+
/** Find all identifiers in scope at line:character */
public List<Element> scopeMembers(URI file, String contents, int line, int character) {
recompile(file, contents, line, character);
@@ -312,8 +370,8 @@ public class JavaCompilerService {
}
boolean isThisOrSuper(VariableElement ve) {
- var name = ve.getSimpleName().toString();
- return name.equals("this") || name.equals("super");
+ var name = ve.getSimpleName();
+ return name.contentEquals("this") || name.contentEquals("super");
}
// Place each member of `this` or `super` directly into `results`
@@ -704,6 +762,34 @@ public class JavaCompilerService {
}
@Override
+ public Void visitAnnotation(AnnotationTree node, Void nothing) {
+ if (containsCursor(node.getAnnotationType())) {
+ LOG.info("...completing annotation");
+ result = new ArrayList<>();
+ var id = (IdentifierTree) node.getAnnotationType();
+ var partialName = Objects.toString(id.getName(), "");
+ // Add @Override, @Test, other simple class names
+ completeScopeIdentifiers(partialName);
+ // Add @Override ... snippet
+ if ("Override".startsWith(partialName)) {
+ // TODO filter out already-implemented methods using thisMethods
+ for (var method : superMethods(file, contents, line, character)) {
+ var mods = method.getModifiers();
+ if (mods.contains(Modifier.STATIC) || mods.contains(Modifier.PRIVATE)) continue;
+
+ var label = "Override " + ShortTypePrinter.printMethod(method);
+ var snippet = "Override\n" + new TemplatePrinter().printMethod(method) + " {\n $0\n}";
+ var override = Completion.ofSnippet(label, snippet);
+ result.add(override);
+ }
+ }
+ } else {
+ super.visitAnnotation(node, nothing);
+ }
+ return null;
+ }
+
+ @Override
public Void visitIdentifier(IdentifierTree node, Void nothing) {
super.visitIdentifier(node, nothing);
@@ -754,88 +840,92 @@ public class JavaCompilerService {
}
}
}
- var startsWithUpperCase = partialName.length() > 0 && Character.isUpperCase(partialName.charAt(0));
- var alreadyImported = new HashSet<String>();
- // Add names that have already been imported
- for (var m : scopeMembers(file, contents, line, character)) {
- if (m.getSimpleName().toString().startsWith(partialName)) {
- result.add(Completion.ofElement(m));
-
- if (m instanceof TypeElement && startsWithUpperCase) {
- var t = (TypeElement) m;
- alreadyImported.add(t.getQualifiedName().toString());
- }
+ completeScopeIdentifiers(partialName);
+ }
+ return null;
+ }
+
+ private void completeScopeIdentifiers(String partialName) {
+ var startsWithUpperCase = partialName.length() > 0 && Character.isUpperCase(partialName.charAt(0));
+ var alreadyImported = new HashSet<String>();
+ // Add names that have already been imported
+ for (var m : scopeMembers(file, contents, line, character)) {
+ if (m.getSimpleName().toString().startsWith(partialName)) {
+ result.add(Completion.ofElement(m));
+
+ if (m instanceof TypeElement && startsWithUpperCase) {
+ var t = (TypeElement) m;
+ alreadyImported.add(t.getQualifiedName().toString());
}
}
- // Add names of classes that haven't been imported
- if (startsWithUpperCase) {
- var packageName = Objects.toString(parse.getPackageName(), "");
- Predicate<String> matchesPartialName =
- qualifiedName -> {
- var className = Parser.lastName(qualifiedName);
- return className.startsWith(partialName);
- };
- Predicate<String> notAlreadyImported = className -> !alreadyImported.contains(className);
- var fromJdk =
- jdkClasses
- .classes()
- .stream()
- .filter(matchesPartialName)
- .filter(notAlreadyImported)
- .filter(c -> jdkClasses.isAccessibleFromPackage(c, packageName));
- var fromClasspath =
- classPathClasses
- .classes()
- .stream()
- .filter(matchesPartialName)
- .filter(notAlreadyImported)
- .filter(c -> classPathClasses.isAccessibleFromPackage(c, packageName));
- Function<Path, Stream<String>> classesInDir = dir -> {
- Predicate<Path> matchesFileName =
- file -> file.getFileName().toString().startsWith(partialName);
- Predicate<Path> isPublic =
+ }
+ // Add names of classes that haven't been imported
+ if (startsWithUpperCase) {
+ var packageName = Objects.toString(parse.getPackageName(), "");
+ Predicate<String> matchesPartialName =
+ qualifiedName -> {
+ var className = Parser.lastName(qualifiedName);
+ return className.startsWith(partialName);
+ };
+ Predicate<String> notAlreadyImported = className -> !alreadyImported.contains(className);
+ var fromJdk =
+ jdkClasses
+ .classes()
+ .stream()
+ .filter(matchesPartialName)
+ .filter(notAlreadyImported)
+ .filter(c -> jdkClasses.isAccessibleFromPackage(c, packageName));
+ var fromClasspath =
+ classPathClasses
+ .classes()
+ .stream()
+ .filter(matchesPartialName)
+ .filter(notAlreadyImported)
+ .filter(c -> classPathClasses.isAccessibleFromPackage(c, packageName));
+ Function<Path, Stream<String>> classesInDir = dir -> {
+ Predicate<Path> matchesFileName =
+ file -> file.getFileName().toString().startsWith(partialName);
+ Predicate<Path> isPublic =
+ file -> {
+ var fileName = file.getFileName().toString();
+ if (!fileName.endsWith(".java")) return false;
+ var simpleName = fileName.substring(0, fileName.length() - ".java".length());
+ Stream<String> lines;
+ try {
+ lines = Files.lines(file);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ return lines.anyMatch(line -> line.matches(".*public\\s+class\\s+" + simpleName + ".*"));
+ };
+ Function<Path, String> qualifiedName =
file -> {
- var fileName = file.getFileName().toString();
- if (!fileName.endsWith(".java")) return false;
- var simpleName = fileName.substring(0, fileName.length() - ".java".length());
- Stream<String> lines;
- try {
- lines = Files.lines(file);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- return lines.anyMatch(line -> line.matches(".*public\\s+class\\s+" + simpleName + ".*"));
+ var relative = dir.relativize(file).toString().replace('/', '.');
+ if (!relative.endsWith(".java")) return "??? " + relative + " does not end in .java";
+ return relative.substring(0, relative.length() - ".java".length());
};
- Function<Path, String> qualifiedName =
- file -> {
- var relative = dir.relativize(file).toString().replace('/', '.');
- if (!relative.endsWith(".java")) return "??? " + relative + " does not end in .java";
- return relative.substring(0, relative.length() - ".java".length());
- };
- return javaSourcesInDir(dir).filter(matchesFileName).filter(isPublic).map(qualifiedName);
- };
- var fromSourcePath =
- sourcePath.stream()
- .flatMap(classesInDir)
- .filter(notAlreadyImported);
- Consumer<Stream<String>> addCompletions = qualifiedNames -> {
- Iterable<String> it = qualifiedNames::iterator;
- for (var name : it) {
- if (result.size() >= limitHint) {
- isIncomplete = true;
- return;
- } else {
- var completion = Completion.ofNotImportedClass(name);
- result.add(completion);
- }
+ return javaSourcesInDir(dir).filter(matchesFileName).filter(isPublic).map(qualifiedName);
+ };
+ var fromSourcePath =
+ sourcePath.stream()
+ .flatMap(classesInDir)
+ .filter(notAlreadyImported);
+ Consumer<Stream<String>> addCompletions = qualifiedNames -> {
+ Iterable<String> it = qualifiedNames::iterator;
+ for (var name : it) {
+ if (result.size() >= limitHint) {
+ isIncomplete = true;
+ return;
+ } else {
+ var completion = Completion.ofNotImportedClass(name);
+ result.add(completion);
}
- };
- addCompletions.accept(fromJdk);
- addCompletions.accept(fromClasspath);
- addCompletions.accept(fromSourcePath);
- }
+ }
+ };
+ addCompletions.accept(fromJdk);
+ addCompletions.accept(fromClasspath);
+ addCompletions.accept(fromSourcePath);
}
- return null;
}
@Override
diff --git a/src/main/java/org/javacs/TemplatePrinter.java b/src/main/java/org/javacs/TemplatePrinter.java
new file mode 100644
index 0000000..a7892ef
--- /dev/null
+++ b/src/main/java/org/javacs/TemplatePrinter.java
@@ -0,0 +1,136 @@
+package org.javacs;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.StringJoiner;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.type.*;
+import javax.lang.model.util.AbstractTypeVisitor8;
+
+class TemplatePrinter extends AbstractTypeVisitor8<String, Void> {
+ private final Logger LOG = Logger.getLogger("main");
+
+ private Map<TypeMirror, Integer> parameters = new HashMap<>();
+
+ private int parameter(TypeMirror t) {
+ if (parameters.containsKey(t)) {
+ return parameters.get(t);
+ } else {
+ parameters.put(t, parameters.size() + 1);
+ return parameters.get(t);
+ }
+ }
+
+ String print(TypeMirror type) {
+ return type.accept(this, null);
+ }
+
+ @Override
+ public String visitIntersection(IntersectionType t, Void aVoid) {
+ var types = new StringJoiner(" & ");
+ for (var b : t.getBounds()) {
+ types.add(print(b));
+ }
+ return "? extends " + types;
+ }
+
+ @Override
+ public String visitUnion(UnionType t, Void aVoid) {
+ return "UNION";
+ }
+
+ @Override
+ public String visitPrimitive(PrimitiveType t, Void aVoid) {
+ return t.toString();
+ }
+
+ @Override
+ public String visitNull(NullType t, Void aVoid) {
+ return "NULL";
+ }
+
+ @Override
+ public String visitArray(ArrayType t, Void aVoid) {
+ return print(t.getComponentType()) + "[]";
+ }
+
+ @Override
+ public String visitDeclared(DeclaredType t, Void aVoid) {
+ var result = t.asElement().getSimpleName().toString();
+
+ if (!t.getTypeArguments().isEmpty()) {
+ String params = t.getTypeArguments().stream().map(this::print).collect(Collectors.joining(", "));
+
+ result += "<" + params + ">";
+ }
+
+ if (result.matches("java\\.lang\\.\\w+")) return result.substring("java.lang.".length());
+ else if (result.startsWith("java\\.util\\.\\w+")) return result.substring("java.util.".length());
+ else return result;
+ }
+
+ @Override
+ public String visitError(ErrorType t, Void aVoid) {
+ return "ERROR";
+ }
+
+ @Override
+ public String visitTypeVariable(TypeVariable t, Void aVoid) {
+ return "$" + parameter(t);
+ }
+
+ @Override
+ public String visitWildcard(WildcardType t, Void aVoid) {
+ return "?";
+ }
+
+ @Override
+ public String visitExecutable(ExecutableType t, Void aVoid) {
+ return "EXECUTABLE";
+ }
+
+ @Override
+ public String visitNoType(NoType t, Void aVoid) {
+ return "void";
+ }
+
+ private String printArguments(ExecutableElement e) {
+ var result = new StringJoiner(", ");
+ for (var p : e.getParameters()) {
+ var s = new StringBuilder();
+ result.add(print(p.asType()) + " " + p.getSimpleName());
+ }
+ return result.toString();
+ }
+
+ String printMethod(ExecutableElement m) {
+ if (m.getSimpleName().contentEquals("<init>")) {
+ return m.getEnclosingElement().getSimpleName() + "(" + printArguments(m) + ")";
+ } else {
+ if (m.getModifiers().contains(Modifier.STATIC)) return "ERROR " + m.getSimpleName() + " IS STATIC";
+ if (m.getModifiers().contains(Modifier.PRIVATE)) return "ERROR " + m.getSimpleName() + " IS PRIVATE";
+
+ var result = new StringBuilder();
+ // public void foo
+ if (m.getModifiers().contains(Modifier.PUBLIC)) result.append("public ");
+ if (m.getModifiers().contains(Modifier.PROTECTED)) result.append("protected ");
+ result.append(print(m.getReturnType())).append(" ");
+ result.append(m.getSimpleName());
+ // (int arg, String other)
+ result.append("(").append(printArguments(m)).append(")");
+ // throws Foo, Bar
+ if (!m.getThrownTypes().isEmpty()) {
+ result.append(" throws ");
+ var types = new StringJoiner(", ");
+ for (var t : m.getThrownTypes()) {
+ types.add(print(t));
+ }
+ result.append(types);
+ }
+ return result.toString();
+ }
+ }
+}
diff --git a/src/test/java/org/javacs/CompletionsTest.java b/src/test/java/org/javacs/CompletionsTest.java
index 52b1921..c8f01ca 100644
--- a/src/test/java/org/javacs/CompletionsTest.java
+++ b/src/test/java/org/javacs/CompletionsTest.java
@@ -776,4 +776,12 @@ public class CompletionsTest extends CompletionsBase {
assertThat(suggestions, hasItem(startsWith("Override")));
}
+
+ @Test
+ public void overrideMethod() {
+ var file = "/org/javacs/example/AutocompleteOverride.java";
+ var suggestions = insertText(file, 8, 15);
+
+ assertThat(suggestions, hasItem(containsString("void superMethod() {")));
+ }
}
diff --git a/src/test/test-project/workspace/src/org/javacs/example/AutocompleteOverride.java b/src/test/test-project/workspace/src/org/javacs/example/AutocompleteOverride.java
new file mode 100644
index 0000000..8211b18
--- /dev/null
+++ b/src/test/test-project/workspace/src/org/javacs/example/AutocompleteOverride.java
@@ -0,0 +1,10 @@
+package org.javacs.example;
+
+class AutocompleteOverride {
+ class Super {
+ void superMethod() {}
+ }
+ class Sub extends Super {
+ @Overr
+ }
+} \ No newline at end of file