From 382aeba372a85d44ad97b80942bd71899e9cd050 Mon Sep 17 00:00:00 2001 From: Dusan Balek Date: Tue, 18 Jun 2024 11:24:23 +0200 Subject: [PATCH] Micronaut: Provide Endpoint and Bean symbols originating from libraries. --- .../symbol/MicronautStructureProvider.java | 3 +- .../symbol/MicronautSymbolErrorProvider.java | 4 +- .../symbol/MicronautSymbolFinder.java | 389 +++++++++++++++++- .../symbol/MicronautSymbolSearcher.java | 151 ++++--- .../server/protocol/WorkspaceServiceImpl.java | 29 +- java/java.lsp.server/vscode/src/extension.ts | 6 +- 6 files changed, 489 insertions(+), 93 deletions(-) diff --git a/enterprise/micronaut/src/org/netbeans/modules/micronaut/symbol/MicronautStructureProvider.java b/enterprise/micronaut/src/org/netbeans/modules/micronaut/symbol/MicronautStructureProvider.java index 909f1cf29303..ecb65076b611 100644 --- a/enterprise/micronaut/src/org/netbeans/modules/micronaut/symbol/MicronautStructureProvider.java +++ b/enterprise/micronaut/src/org/netbeans/modules/micronaut/symbol/MicronautStructureProvider.java @@ -43,7 +43,8 @@ public List getStructure(Document doc) { JavaSource js = JavaSource.forDocument(doc); if (js != null) { ClassPath cp = js.getClasspathInfo().getClassPath(ClasspathInfo.PathKind.COMPILE); - if (cp.findResource("io/micronaut/http/annotation/HttpMethodMapping.class") != null) { + if (cp.findResource("io/micronaut/http/annotation/HttpMethodMapping.class") != null + || cp.findResource("io/micronaut/management/endpoint/annotation/Endpoint.class") != null) { try { List elements = new ArrayList<>(); js.runUserActionTask(cc -> { diff --git a/enterprise/micronaut/src/org/netbeans/modules/micronaut/symbol/MicronautSymbolErrorProvider.java b/enterprise/micronaut/src/org/netbeans/modules/micronaut/symbol/MicronautSymbolErrorProvider.java index 9a6576c1e2c8..8eb129212ca3 100644 --- a/enterprise/micronaut/src/org/netbeans/modules/micronaut/symbol/MicronautSymbolErrorProvider.java +++ b/enterprise/micronaut/src/org/netbeans/modules/micronaut/symbol/MicronautSymbolErrorProvider.java @@ -65,8 +65,8 @@ public List computeErrors(Context context) { "ERR_Duplicated_URI_path=Duplicated endpoint URI path: {0}" }) private Diagnostic desc2diag(MicronautSymbolSearcher.SymbolDescriptor descriptor) { - OffsetRange offsetRange = descriptor.getOffsetRange(null); - return Diagnostic.Builder.create(() -> offsetRange.getStart(), () -> offsetRange.getEnd(), Bundle.ERR_Duplicated_URI_path(descriptor.getName())) + OffsetRange offsetRange = descriptor.getElement().getOffsetRange(null); + return Diagnostic.Builder.create(() -> offsetRange.getStart(), () -> offsetRange.getEnd(), Bundle.ERR_Duplicated_URI_path(descriptor.getElement().getName())) .setCode("WARN_Duplicated_MN_Data_Endpoint_Path " + offsetRange.getStart() + " - " + offsetRange.getEnd()) .setSeverity(Diagnostic.Severity.Warning) .build(); diff --git a/enterprise/micronaut/src/org/netbeans/modules/micronaut/symbol/MicronautSymbolFinder.java b/enterprise/micronaut/src/org/netbeans/modules/micronaut/symbol/MicronautSymbolFinder.java index 3369c488531c..ba9a5a2581db 100644 --- a/enterprise/micronaut/src/org/netbeans/modules/micronaut/symbol/MicronautSymbolFinder.java +++ b/enterprise/micronaut/src/org/netbeans/modules/micronaut/symbol/MicronautSymbolFinder.java @@ -34,9 +34,12 @@ import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.WeakHashMap; import java.util.stream.Collectors; import javax.lang.model.element.AnnotationMirror; @@ -46,15 +49,23 @@ import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Name; import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.ArrayType; import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.IntersectionType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVariable; import javax.lang.model.util.ElementFilter; import javax.lang.model.util.Elements; import javax.lang.model.util.Types; +import javax.tools.JavaFileObject; import org.netbeans.api.editor.mimelookup.MimeRegistration; import org.netbeans.api.java.classpath.ClassPath; +import org.netbeans.api.java.source.ClassIndex; +import org.netbeans.api.java.source.ClasspathInfo; import org.netbeans.api.java.source.CompilationController; +import org.netbeans.api.java.source.ElementHandle; import org.netbeans.api.java.source.JavaSource; import org.netbeans.api.project.FileOwnerQuery; import org.netbeans.api.project.Project; @@ -66,6 +77,7 @@ import org.netbeans.modules.parsing.spi.indexing.Indexable; import org.openide.filesystems.FileObject; import org.openide.filesystems.FileUtil; +import org.openide.filesystems.URLMapper; import org.openide.util.Exceptions; import org.openide.util.Pair; import org.openide.util.WeakListeners; @@ -78,15 +90,24 @@ public final class MicronautSymbolFinder extends EmbeddingIndexer implements Pro public static final String NAME = "mn"; // NOI18N public static final int VERSION = 1; - public static final MicronautSymbolFinder INSTANCE = new MicronautSymbolFinder(); - public static final String[] META_ANNOTATIONS = new String[] { + + private static final MicronautSymbolFinder INSTANCE = new MicronautSymbolFinder(); + private static final String[] META_ANNOTATIONS = new String[] { "io.micronaut.http.annotation.HttpMethodMapping", "io.micronaut.context.annotation.Bean", "jakarta.inject.Qualifier", "jakarta.inject.Scope" }; + private static final String CONTROLLER_ANNOTATION = "io.micronaut.http.annotation.Controller"; + private static final String HTTP_METHOD_MAPPING_ANNOTATION = "io.micronaut.http.annotation.HttpMethodMapping"; + private static final String MANAGEMENT_ENDPOINT_ANNOTATION = "io.micronaut.management.endpoint.annotation.Endpoint"; + private static final String MANAGEMENT_READ_ANNOTATION = "io.micronaut.management.endpoint.annotation.Read"; + private static final String MANAGEMENT_WRITE_ANNOTATION = "io.micronaut.management.endpoint.annotation.Write"; + private static final String MANAGEMENT_DELETE_ANNOTATION = "io.micronaut.management.endpoint.annotation.Delete"; + private static final String MANAGEMENT_SELECTOR_ANNOTATION = "io.micronaut.management.endpoint.annotation.Selector"; private final Map map = new WeakHashMap<>(); + private final Map> cache = new WeakHashMap<>(); @Override protected void index(Indexable indexable, Parser.Result parserResult, Context context) { @@ -118,7 +139,8 @@ private synchronized boolean initialize(CompilationController cc) { if (ret == null) { ClassPath cp = ClassPath.getClassPath(p.getProjectDirectory(), ClassPath.COMPILE); cp.addPropertyChangeListener(WeakListeners.propertyChange(this, cp)); - ret = cp.findResource("io/micronaut/http/annotation/HttpMethodMapping.class") != null; + ret = cp.findResource("io/micronaut/http/annotation/HttpMethodMapping.class") != null + || cp.findResource("io/micronaut/management/endpoint/annotation/Endpoint.class") != null; map.put(p, ret); } return ret; @@ -135,20 +157,15 @@ public Void visitClass(ClassTree node, String path) { if (cls != null) { Pair metaAnnotated = isMetaAnnotated(cls); if (metaAnnotated != null) { + path = getPath(metaAnnotated.first()); Element annEl = metaAnnotated.first().getAnnotationType().asElement(); - if ("io.micronaut.http.annotation.Controller".contentEquals(((TypeElement) annEl).getQualifiedName())) { - path = ""; - for (Map.Entry entry : metaAnnotated.first().getElementValues().entrySet()) { - if ("value".contentEquals(entry.getKey().getSimpleName())) { - path = (String) entry.getValue().getValue(); - } - } - } String name = "@+ '" + getBeanName(node.getSimpleName().toString()) + "' (@" + annEl.getSimpleName() + (metaAnnotated.second() != null ? " <: @" + metaAnnotated.second().getAnnotationType().asElement().getSimpleName() : "") + ") " + node.getSimpleName(); int[] span = cc.getTreeUtilities().findNameSpan(node); ret.add(new SymbolLocation(name, (int) sp.getStartPosition(treePath.getCompilationUnit(), node), (int) sp.getEndPosition(treePath.getCompilationUnit(), node), span[0], span[1])); + } else { + path = getPath((TypeElement) cls); } } return super.visitClass(node, path); @@ -175,7 +192,7 @@ public Void visitMethod(MethodTree node, String path) { } } if (ids.isEmpty()) { - ids.add("/"); + ids.add(getId(ee)); } for (Object id : ids) { String name = '@' + path + id + " -- " + method; @@ -255,16 +272,25 @@ private static String check(Name name) { } public static String getEndpointMethod(TypeElement te) { - for (AnnotationMirror ann : te.getAnnotationMirrors()) { - Element el = ann.getAnnotationType().asElement(); - if ("io.micronaut.http.annotation.HttpMethodMapping".contentEquals(((TypeElement) el).getQualifiedName())) { - return te.getSimpleName().toString().toUpperCase(); - } + switch (te.getQualifiedName().toString()) { + case MANAGEMENT_READ_ANNOTATION: + return "GET"; + case MANAGEMENT_WRITE_ANNOTATION: + return "POST"; + case MANAGEMENT_DELETE_ANNOTATION: + return "DELETE"; + default: + for (AnnotationMirror ann : te.getAnnotationMirrors()) { + Element el = ann.getAnnotationType().asElement(); + if (HTTP_METHOD_MAPPING_ANNOTATION.contentEquals(((TypeElement) el).getQualifiedName())) { + return te.getSimpleName().toString().toUpperCase(); + } + } } return null; } - public static String getBeanName(String typeName) { + private static String getBeanName(String typeName) { if (typeName.length() > 0 && Character.isUpperCase(typeName.charAt(0))) { if (typeName.length() == 1 || !Character.isUpperCase(typeName.charAt(1))) { typeName = Character.toLowerCase(typeName.charAt(0)) + typeName.substring(1); @@ -273,6 +299,303 @@ public static String getBeanName(String typeName) { return typeName; } + private static String getPath(AnnotationMirror ann) { + String path = null; + Element annEl = ann.getAnnotationType().asElement(); + switch (((TypeElement) annEl).getQualifiedName().toString()) { + case CONTROLLER_ANNOTATION: + path = "/"; + for (Map.Entry entry : ann.getElementValues().entrySet()) { + if ("value".contentEquals(entry.getKey().getSimpleName())) { + path = (String) entry.getValue().getValue(); + } + } + break; + case MANAGEMENT_ENDPOINT_ANNOTATION: + path = "/"; + for (Map.Entry entry : ann.getElementValues().entrySet()) { + if ("value".contentEquals(entry.getKey().getSimpleName()) || "id".contentEquals(entry.getKey().getSimpleName())) { + path = (String) entry.getValue().getValue(); + } + } + break; + } + if (path != null && !path.startsWith("/")) { + path = "/" + path; + } + return path; + } + + private static String getPath(TypeElement te) { + for (AnnotationMirror ann : te.getAnnotationMirrors()) { + String path = getPath(ann); + if (path != null) { + return path; + } + } + return null; + } + + private static String getId(ExecutableElement ee) { + StringBuilder sb = new StringBuilder(); + for (VariableElement ve : ee.getParameters()) { + for (AnnotationMirror ann : ve.getAnnotationMirrors()) { + Element el = ann.getAnnotationType().asElement(); + if (MANAGEMENT_SELECTOR_ANNOTATION.contentEquals(((TypeElement) el).getQualifiedName())) { + sb.append("/{").append(ve.getSimpleName()).append('}'); + } + } + } + return sb.toString(); + } + + private static String[] getSignatures(Element e) { + switch (e.getKind()) { + case ANNOTATION_TYPE: + case CLASS: + case ENUM: + case INTERFACE: + case RECORD: + return new String[] {encodeClassNameOrArray((TypeElement) e)}; + case METHOD: + return createMethodDescriptor((ExecutableElement) e); + } + return new String[0]; + } + + private static String[] createMethodDescriptor(ExecutableElement ee) { + String[] result = new String[3]; + Element enclosingType = ee.getEnclosingElement(); + if (enclosingType != null && enclosingType.asType().getKind() == TypeKind.NONE) { + result[0] = ""; + } else { + assert enclosingType instanceof TypeElement : enclosingType == null ? "null" : enclosingType.toString() + "(" + enclosingType.getKind()+")"; //NOI18N + result[0] = encodeClassNameOrArray ((TypeElement)enclosingType); + } + StringBuilder retType = new StringBuilder (); + result[1] = ee.getSimpleName().toString(); + if (ee.asType().getKind() == TypeKind.EXECUTABLE) { + encodeType(ee.getReturnType(), retType); + } + StringBuilder sb = new StringBuilder (); + sb.append('('); + for (VariableElement pd : ee.getParameters()) { + encodeType(pd.asType(),sb); + } + sb.append(')'); + sb.append(retType); + result[2] = sb.toString(); + return result; + } + + private static String encodeClassNameOrArray(TypeElement td) { + CharSequence qname = td.getQualifiedName(); + TypeMirror enclosingType = td.getEnclosingElement().asType(); + if (qname != null && enclosingType != null && enclosingType.getKind() == TypeKind.NONE && "Array".equals(qname.toString())) { + return "["; + } else { + return encodeClassName(td); + } + } + + private static String encodeClassName(TypeElement td) { + StringBuilder sb = new StringBuilder (); + encodeClassName(td, sb, '.'); + return sb.toString(); + } + + private static void encodeType(TypeMirror type, StringBuilder sb) { + switch (type.getKind()) { + case VOID: + sb.append('V'); + break; + case BOOLEAN: + sb.append('Z'); + break; + case BYTE: + sb.append('B'); + break; + case SHORT: + sb.append('S'); + break; + case INT: + sb.append('I'); + break; + case LONG: + sb.append('J'); + break; + case CHAR: + sb.append('C'); + break; + case FLOAT: + sb.append('F'); + break; + case DOUBLE: + sb.append('D'); + break; + case ARRAY: + sb.append('['); + encodeType(((ArrayType)type).getComponentType(), sb); + break; + case DECLARED: + { + sb.append('L'); + TypeElement te = (TypeElement) ((DeclaredType)type).asElement(); + encodeClassName(te, sb, '/'); + sb.append(';'); + break; + } + case TYPEVAR: + { + TypeVariable tr = (TypeVariable) type; + TypeMirror upperBound = tr.getUpperBound(); + if (upperBound.getKind() == TypeKind.NULL) { + sb.append("Ljava/lang/Object;"); + } + else { + encodeType(upperBound, sb); + } + break; + } + case INTERSECTION: + { + encodeType(((IntersectionType) type).getBounds().get(0), sb); + break; + } + case ERROR: + { + TypeElement te = (TypeElement) ((DeclaredType)type).asElement(); + if (te != null) { + sb.append('L'); + encodeClassName(te, sb,'/'); + sb.append(';'); + break; + } + } + default: + throw new IllegalArgumentException (String.format("Unsupported type: %s, kind: %s", type, type.getKind())); + } + } + + private static void encodeClassName (TypeElement te, final StringBuilder sb, final char separator) { + final char[] nameChars = flatName(te).toCharArray(); + int charLength = nameChars.length; + if (separator != '.') { + for (int i = 0; i < charLength; i++) { + if (nameChars[i] == '.') { + nameChars[i] = separator; + } + } + } + sb.append(nameChars, 0, charLength); + } + + private static String flatName(TypeElement te) { + Element owner = te.getEnclosingElement(); + if (owner.getKind().isClass() || owner.getKind().isInterface()) { + return flatName((TypeElement) owner) + '$' + te.getSimpleName(); + } + return te.getQualifiedName().toString(); + } + + public static List getSymbolsFromDependencies(ClasspathInfo info, String textForQuery) { + List cached = INSTANCE.cache.get(info); + if (cached == null) { + List ret = new ArrayList<>(); + ClassIndex ci = info.getClassIndex(); + Set> beanHandles = new HashSet<>(); + Set> checked = new HashSet<>(); + LinkedList> queue = new LinkedList<>(); + for (String ann : META_ANNOTATIONS) { + queue.add(ElementHandle.createTypeElementHandle(ElementKind.ANNOTATION_TYPE, ann)); + } + while (!queue.isEmpty()) { + ElementHandle eh = queue.removeFirst(); + if (checked.add(eh)) { + Set> elems = ci.getElements(eh, Set.of(ClassIndex.SearchKind.TYPE_REFERENCES), Set.of(ClassIndex.SearchScope.DEPENDENCIES)); + for (ElementHandle elem : elems) { + if (elem.getKind() == ElementKind.ANNOTATION_TYPE) { + queue.add(elem); + } else { + beanHandles.add(elem); + } + } + } + } + ElementHandle eh = ElementHandle.createTypeElementHandle(ElementKind.ANNOTATION_TYPE, MANAGEMENT_ENDPOINT_ANNOTATION); + Set> endpointHandles = ci.getElements(eh, Set.of(ClassIndex.SearchKind.TYPE_REFERENCES), Set.of(ClassIndex.SearchScope.DEPENDENCIES)); + if (!beanHandles.isEmpty() || !endpointHandles.isEmpty()) { + JavaSource js = JavaSource.create(info); + if (js != null) { + try { + js.runUserActionTask(cc -> { + cc.toPhase(JavaSource.Phase.ELEMENTS_RESOLVED); + Elements elements = cc.getElements(); + for (ElementHandle beanHandle : beanHandles) { + TypeElement symbol = beanHandle.resolve(cc); + if (symbol != null) { + Pair metaAnnotated = isMetaAnnotated(symbol); + if (metaAnnotated != null) { + JavaFileObject jfo = elements.getFileObjectOf(symbol); + FileObject fo = jfo != null && jfo.getName().endsWith(".class") ? URLMapper.findFileObject(jfo.toUri().toURL()) : null; + if (fo != null) { + Element annEl = metaAnnotated.first().getAnnotationType().asElement(); + String name = "@+ '" + getBeanName(symbol.getSimpleName().toString()) + "' (@" + annEl.getSimpleName() + + (metaAnnotated.second() != null ? " <: @" + metaAnnotated.second().getAnnotationType().asElement().getSimpleName() : "") + + ") " + symbol.getSimpleName(); + ret.add(new ClassSymbolLocation(name, fo, symbol.getKind().name(), getSignatures(symbol))); + if (CONTROLLER_ANNOTATION.contentEquals(((TypeElement) annEl).getQualifiedName())) { + String path = getPath(metaAnnotated.first()); + if (path != null) { + addEndpointSymbols(cc, fo, symbol, path, ret); + } + } + } + } + } + } + for (ElementHandle endpointHandle : endpointHandles) { + TypeElement symbol = endpointHandle.resolve(cc); + String path = symbol != null ? getPath(symbol) : null; + if (path != null) { + JavaFileObject jfo = elements.getFileObjectOf(symbol); + FileObject fo = jfo != null && jfo.getName().endsWith(".class") ? URLMapper.findFileObject(jfo.toUri().toURL()) : null; + if (fo != null) { + addEndpointSymbols(cc, fo, symbol, path, ret); + } + } + } + }, true); + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } + } + } + cached = ret; + synchronized (INSTANCE.cache) { + INSTANCE.cache.clear(); + INSTANCE.cache.put(info, ret); + } + } + return cached.stream().filter(s -> s.getName().startsWith(textForQuery)).collect(Collectors.toList()); + } + + private static void addEndpointSymbols(CompilationController cc, FileObject fo, TypeElement symbol, String path, List ret) { + for (ExecutableElement mth : ElementFilter.methodsIn(symbol.getEnclosedElements())) { + MthIterator it = new MthIterator(mth, cc.getElements(), cc.getTypes()); + while (it.hasNext()) { + ExecutableElement ee = it.next(); + for (AnnotationMirror ann : ee.getAnnotationMirrors()) { + String method = getEndpointMethod((TypeElement) ann.getAnnotationType().asElement()); + if (method != null) { + String name = '@' + path + getId(ee) + " -- " + method; + ret.add(new ClassSymbolLocation(name, fo, mth.getKind().name(), getSignatures(mth))); + } + } + } + } + } + @MimeRegistration(mimeType="text/x-java", service=EmbeddingIndexerFactory.class) //NOI18N public static class Factory extends EmbeddingIndexerFactory { @@ -343,6 +666,36 @@ public int getSelectionEnd() { } } + public static class ClassSymbolLocation { + private final String name; + private final FileObject file; + private final String kind; + private final String[] signatures; + + private ClassSymbolLocation(String name, FileObject file, String kind, String[] signatures) { + this.name = name; + this.file = file; + this.kind = kind; + this.signatures = signatures; + } + + public FileObject getFile() { + return file; + } + + public String getName() { + return name; + } + + public String getKind() { + return kind; + } + + public String[] getSignatures() { + return signatures; + } + } + public static class MthIterator implements Iterator { private final ExecutableElement ee; diff --git a/enterprise/micronaut/src/org/netbeans/modules/micronaut/symbol/MicronautSymbolSearcher.java b/enterprise/micronaut/src/org/netbeans/modules/micronaut/symbol/MicronautSymbolSearcher.java index 9436aecb9376..c3cb17f50b07 100644 --- a/enterprise/micronaut/src/org/netbeans/modules/micronaut/symbol/MicronautSymbolSearcher.java +++ b/enterprise/micronaut/src/org/netbeans/modules/micronaut/symbol/MicronautSymbolSearcher.java @@ -35,6 +35,7 @@ import javax.swing.Icon; import javax.swing.text.StyledDocument; import org.netbeans.api.java.project.JavaProjectConstants; +import org.netbeans.api.java.source.ClasspathInfo; import org.netbeans.api.java.source.JavaSource; import org.netbeans.api.lsp.Diagnostic; import org.netbeans.api.project.FileOwnerQuery; @@ -141,6 +142,9 @@ private static Set getSymbols(Project project, String textForQ Exceptions.printStackTrace(ex); } } + for (MicronautSymbolFinder.ClassSymbolLocation loc : MicronautSymbolFinder.getSymbolsFromDependencies(ClasspathInfo.create(project.getProjectDirectory()), textForQuery)) { + symbols.add(new SymbolDescriptor(loc.getName(), loc.getFile(), loc.getKind(), loc.getSignatures())); + } return symbols; } @@ -180,28 +184,30 @@ private static FileObject getCacheRoot(URL root) throws IOException { return dataFolder != null ? FileUtil.createFolder(dataFolder, MicronautSymbolFinder.NAME + "/" + MicronautSymbolFinder.VERSION) : null; //NOI18N } - static class SymbolDescriptor extends IndexSearcher.Descriptor implements org.netbeans.modules.csl.api.ElementHandle { + static class SymbolDescriptor extends IndexSearcher.Descriptor { - private final String name; - private final FileObject fo; + private final Handle handle; private final Project project; - private final OffsetRange range; + + private SymbolDescriptor(String name, FileObject fo, String kind, String[] singatures) { + this.project = FileOwnerQuery.getOwner(fo); + String uri = FileUtil.isArchiveArtifact(fo) ? FileUtil.getArchiveRoot(FileUtil.getArchiveFile(fo)).toURI().toString() + '?' + kind + '#' + String.join(":", singatures) : null; + this.handle = new Handle(uri, fo, name, OffsetRange.NONE); + } private SymbolDescriptor(String name, FileObject fo, int start, int end) { - this.name = name; - this.fo = fo; this.project = FileOwnerQuery.getOwner(fo); - this.range = new OffsetRange(start, end); + this.handle = new Handle(null, fo, name, new OffsetRange(start, end)); } @Override public org.netbeans.modules.csl.api.ElementHandle getElement() { - return this; + return handle; } @Override public String getSimpleName() { - return name; + return handle.getName(); } @Override @@ -236,12 +242,12 @@ public Icon getProjectIcon() { @Override public FileObject getFileObject() { - return fo; + return handle.getFileObject(); } @Override public int getOffset() { - return range.getStart(); + return handle.range.getStart(); } @Override @@ -249,74 +255,97 @@ public void open() { GsfUtilities.open(getFileObject(), getOffset(), null); } - @Override - public String getMimeType() { - return getFileObject().getMIMEType(); + public int hashCode() { + return handle.hashCode(); } @Override - public String getName() { - return getSimpleName(); + public boolean equals(Object obj) { + return handle.equals(obj); } - @Override - public String getIn() { - return getOuterName(); - } + private static class Handle extends org.netbeans.modules.csl.api.ElementHandle.UrlHandle { - @Override - public org.netbeans.modules.csl.api.ElementKind getKind() { - return org.netbeans.modules.csl.api.ElementKind.INTERFACE; - } + private final FileObject fo; + private final String name; + private final OffsetRange range; - @Override - public Set getModifiers() { - return Collections.singleton(Modifier.PUBLIC); - } + public Handle(String url, FileObject fo, String name, OffsetRange range) { + super(url); + this.fo = fo; + this.name = name; + this.range = range; + } - @Override - public boolean signatureEquals(org.netbeans.modules.csl.api.ElementHandle handle) { - if (handle instanceof SymbolDescriptor) { - return this.getName().equals(handle.getName()); - } else { - return false; + @Override + public FileObject getFileObject() { + return fo; } - } - @Override - public OffsetRange getOffsetRange(ParserResult result) { - return range; - } + @Override + public String getMimeType() { + return getFileObject().getMIMEType(); + } - @Override - public int hashCode() { - int hash = 3; - hash = 67 * hash + Objects.hashCode(this.fo); - hash = 67 * hash + Objects.hashCode(this.range); - hash = 67 * hash + Objects.hashCode(this.name); - return hash; - } + @Override + public String getName() { + return name; + } - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; + @Override + public org.netbeans.modules.csl.api.ElementKind getKind() { + return org.netbeans.modules.csl.api.ElementKind.INTERFACE; + } + + @Override + public Set getModifiers() { + return Collections.singleton(Modifier.PUBLIC); } - if (obj == null) { - return false; + + @Override + public boolean signatureEquals(org.netbeans.modules.csl.api.ElementHandle handle) { + if (handle instanceof Handle) { + return this.getName().equals(handle.getName()); + } else { + return false; + } } - if (getClass() != obj.getClass()) { - return false; + + @Override + public OffsetRange getOffsetRange(ParserResult result) { + return range; } - final SymbolDescriptor other = (SymbolDescriptor) obj; - if (!Objects.equals(this.name, other.name)) { - return false; + + @Override + public int hashCode() { + int hash = 3; + hash = 67 * hash + Objects.hashCode(this.fo); + hash = 67 * hash + Objects.hashCode(this.range); + hash = 67 * hash + Objects.hashCode(this.name); + return hash; } - if (!Objects.equals(this.fo, other.fo)) { - return false; + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final Handle other = (Handle) obj; + if (!Objects.equals(this.name, other.name)) { + return false; + } + if (!Objects.equals(this.fo, other.fo)) { + return false; + } + return Objects.equals(this.range, other.range); } - return Objects.equals(this.range, other.range); } } } diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/WorkspaceServiceImpl.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/WorkspaceServiceImpl.java index 9cb5bde33036..7a0c6be6b084 100644 --- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/WorkspaceServiceImpl.java +++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/WorkspaceServiceImpl.java @@ -32,6 +32,7 @@ import java.net.URI; import java.net.URISyntaxException; import java.net.URL; +import java.net.URLDecoder; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; @@ -1069,15 +1070,21 @@ public boolean cancel(boolean mayInterruptIfRunning) { descriptors = provider.getSymbols(project, queryFin, Utils.searchType2QueryKind(searchType), null); for (IndexSearcher.Descriptor desc : descriptors) { FileObject fo = desc.getFileObject(); - org.netbeans.modules.csl.api.ElementHandle element = desc.getElement(); if (fo != null) { - Position pos = Utils.createPosition(fo, desc.getOffset()); - WorkspaceSymbol symbol = new WorkspaceSymbol( + org.netbeans.modules.csl.api.ElementHandle element = desc.getElement(); + Position pos = desc.getOffset() > 0 ? Utils.createPosition(fo, desc.getOffset()) : new Position(0, 0); + String uri = null; + if (element instanceof org.netbeans.modules.csl.api.ElementHandle.UrlHandle) { + uri = ((org.netbeans.modules.csl.api.ElementHandle.UrlHandle) element).getUrl(); + } + if (uri == null) { + uri = Utils.toUri(fo); + } + symbols.add(new WorkspaceSymbol( desc.getSimpleName(), Utils.cslElementKind2SymbolKind(element.getKind()), - Either.forLeft(new Location(Utils.toUri(fo), new Range(pos, pos))), - desc.getContextName()); - symbols.add(symbol); + Either.forLeft(new Location(uri, new Range(pos, pos))), + desc.getContextName())); } } } @@ -1244,8 +1251,9 @@ public CompletableFuture resolveWorkspaceSymbol(WorkspaceSymbol if (root == null) { throw new IllegalStateException("Unable to find root: " + rootUri); } - ElementHandle typeHandle = ElementHandleAccessor.getInstance().create(ElementKind.valueOf(sourceUri.substring(qIdx + 1, hIdx)), sourceUri.substring(hIdx + 1)); - CompletableFuture location = ElementOpen.getLocation(ClasspathInfo.create(root), typeHandle, typeHandle.getQualifiedName().replace('.', '/') + ".class"); + String[] signatures = URLDecoder.decode(sourceUri.substring(hIdx + 1)).split(":"); + ElementHandle handle = ElementHandleAccessor.getInstance().create(ElementKind.valueOf(sourceUri.substring(qIdx + 1, hIdx)), signatures); + CompletableFuture location = ElementOpen.getLocation(ClasspathInfo.create(root), handle, signatures[0].replace('.', '/') + ".class"); location.exceptionally(ex -> { result.completeExceptionally(ex); return null; @@ -1254,15 +1262,16 @@ public CompletableFuture resolveWorkspaceSymbol(WorkspaceSymbol ShowDocumentParams sdp = new ShowDocumentParams(Utils.toUri(loc.getFileObject())); Position position = Utils.createPosition(loc.getFileObject(), loc.getStartOffset()); sdp.setSelection(new Range(position, position)); + sdp.setTakeFocus(true); client.showDocument(sdp).thenAccept(res -> { if (res.isSuccess()) { result.complete(null); } else { - result.completeExceptionally(new IllegalStateException("Cannot open source for: " + typeHandle.getQualifiedName())); + result.completeExceptionally(new IllegalStateException("Cannot open source for: " + workspaceSymbol.getName())); } }); } else if (!result.isCompletedExceptionally()) { - result.completeExceptionally(new IllegalStateException("Cannot find source for: " + typeHandle.getQualifiedName())); + result.completeExceptionally(new IllegalStateException("Cannot find source for: " + workspaceSymbol.getName())); } }); } diff --git a/java/java.lsp.server/vscode/src/extension.ts b/java/java.lsp.server/vscode/src/extension.ts index 176dda62c869..0434ecc07400 100644 --- a/java/java.lsp.server/vscode/src/extension.ts +++ b/java/java.lsp.server/vscode/src/extension.ts @@ -767,7 +767,11 @@ export function activate(context: ExtensionContext): VSNetBeansAPI { })); context.subscriptions.push(commands.registerCommand('nbls.workspace.symbols', async (query) => { const c = await client; - return (await c.sendRequest("workspace/symbol", { "query": query })) ?? []; + return (await c.sendRequest('workspace/symbol', { 'query': query })) ?? []; + })); + context.subscriptions.push(commands.registerCommand('nbls.workspace.symbol.resolve', async (symbol) => { + const c = await client; + return (await c.sendRequest('workspaceSymbol/resolve', symbol)) ?? null; })); context.subscriptions.push(commands.registerCommand(COMMAND_PREFIX + '.java.complete.abstract.methods', async () => { const active = vscode.window.activeTextEditor;