diff --git a/java/java.sourceui/nbproject/project.properties b/java/java.sourceui/nbproject/project.properties index 4fca4dbd252a..70ddbed7ceae 100644 --- a/java/java.sourceui/nbproject/project.properties +++ b/java/java.sourceui/nbproject/project.properties @@ -16,7 +16,7 @@ # under the License. javadoc.apichanges=${basedir}/apichanges.xml javac.compilerargs=-Xlint -Xlint:-serial -javac.source=1.8 +javac.release=17 javadoc.arch=${basedir}/arch.xml spec.version.base=1.74.0 diff --git a/java/java.sourceui/src/org/netbeans/api/java/source/ui/ElementOpen.java b/java/java.sourceui/src/org/netbeans/api/java/source/ui/ElementOpen.java index 0465ac2d66a1..f561cb51c8cf 100644 --- a/java/java.sourceui/src/org/netbeans/api/java/source/ui/ElementOpen.java +++ b/java/java.sourceui/src/org/netbeans/api/java/source/ui/ElementOpen.java @@ -557,27 +557,7 @@ private static void getOffset(final FileObject fo, final ElementHandle { @@ -644,6 +650,11 @@ public Object[] getOpenInfo(ClasspathInfo cpInfo, ElementHandle getOpenInfoFuture(final ClasspathInfo cpInfo, final ElementHandle el, String nameOpt, AtomicBoolean cancel, boolean acquire) { return ElementOpen.getFutureOpenInfo(cpInfo, el, nameOpt, cancel, acquire); diff --git a/java/java.sourceui/src/org/netbeans/modules/java/source/ui/ElementOpenAccessor.java b/java/java.sourceui/src/org/netbeans/modules/java/source/ui/ElementOpenAccessor.java index e9f1ca7ae745..ae78dc92f107 100644 --- a/java/java.sourceui/src/org/netbeans/modules/java/source/ui/ElementOpenAccessor.java +++ b/java/java.sourceui/src/org/netbeans/modules/java/source/ui/ElementOpenAccessor.java @@ -18,10 +18,12 @@ */ package org.netbeans.modules.java.source.ui; +import com.sun.source.tree.Tree; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicBoolean; import javax.lang.model.element.Element; import org.netbeans.api.java.source.ClasspathInfo; +import org.netbeans.api.java.source.CompilationInfo; import org.netbeans.api.java.source.ElementHandle; import org.netbeans.api.java.source.ui.ElementOpen; @@ -42,6 +44,7 @@ public static synchronized void setInstance(ElementOpenAccessor instance) { } public abstract Object[] getOpenInfo(final ClasspathInfo cpInfo, final ElementHandle el, AtomicBoolean cancel); + public abstract void fillInTreePositions(CompilationInfo info, Tree forTree, Object[] target); public abstract CompletableFuture getOpenInfoFuture(final ClasspathInfo cpInfo, final ElementHandle el, String nameOpt, AtomicBoolean cancel, boolean acquire); diff --git a/java/java.sourceui/src/org/netbeans/modules/java/source/ui/LspElementUtils.java b/java/java.sourceui/src/org/netbeans/modules/java/source/ui/LspElementUtils.java index e2ec40460334..424fcb77b657 100644 --- a/java/java.sourceui/src/org/netbeans/modules/java/source/ui/LspElementUtils.java +++ b/java/java.sourceui/src/org/netbeans/modules/java/source/ui/LspElementUtils.java @@ -19,8 +19,10 @@ package org.netbeans.modules.java.source.ui; import com.sun.source.tree.ClassTree; +import com.sun.source.tree.MethodTree; import com.sun.source.tree.NewClassTree; import com.sun.source.tree.Tree; +import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; import com.sun.source.util.TreePathScanner; import java.io.IOException; @@ -35,6 +37,7 @@ import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.NestingKind; import javax.lang.model.element.PackageElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeParameterElement; @@ -55,6 +58,7 @@ import org.netbeans.api.lsp.StructureElement; import org.netbeans.spi.lsp.StructureProvider; import org.openide.filesystems.FileObject; +import org.openide.util.NbBundle.Messages; /** * @@ -62,9 +66,9 @@ */ public class LspElementUtils { - public static StructureElement element2StructureElement(CompilationInfo info, Element el, ElementAcceptor childAcceptor, + public static StructureElement element2StructureElement(CompilationInfo info, Element el, TreePath elPath, ElementAcceptor childAcceptor, boolean allowResources, boolean bypassOpen, FileObject parentFile) { - TreePath path = info.getTrees().getPath(el); + TreePath path = elPath != null ? elPath : info.getTrees().getPath(el); if (!allowResources) { if (path == null) { return null; @@ -80,10 +84,8 @@ public static StructureElement element2StructureElement(CompilationInfo info, El FileObject f = null; FileObject owner = null; if (!bypassOpen) { - Object[] oi = setOffsets(info, el, builder); - if (oi != null) { - owner = f = (FileObject)oi[0]; - } + FileObject file = setOffsets(info, el, path, builder); + owner = f = file; } else { f = null; owner = parentFile; @@ -100,7 +102,7 @@ public static StructureElement element2StructureElement(CompilationInfo info, El if (childAcceptor != null) { for (Element child : el.getEnclosedElements()) { - TreePath p = info.getTrees().getPath(child); + TreePath p = getChildPath(info, child, path); if (!allowResources) { if (p == null) { continue; @@ -108,7 +110,7 @@ public static StructureElement element2StructureElement(CompilationInfo info, El } TypeMirror m = child.asType(); if (childAcceptor.accept(child, m)) { - StructureElement jse = element2StructureElement(info, child, childAcceptor, allowResources, f == null, owner); + StructureElement jse = element2StructureElement(info, child, p, childAcceptor, allowResources, f == null, owner); if (jse != null) { builder.children(jse); } @@ -125,8 +127,25 @@ public static StructureElement element2StructureElement(CompilationInfo info, El return builder.build(); } + private static TreePath getChildPath(CompilationInfo ci, Element child, TreePath parentPath) { + if (parentPath != null && child != null && + TreeUtilities.CLASS_TREE_KINDS.contains(parentPath.getLeaf().getKind())) { + ClassTree ct = (ClassTree) parentPath.getLeaf(); + + for (Tree member : ct.getMembers()) { + TreePath memberPath = new TreePath(parentPath, member); + + if (child.equals(ci.getTrees().getElement(memberPath))) { + return memberPath; + } + } + } + + return ci.getTrees().getPath(child); + } + public static StructureElement element2StructureElement(CompilationInfo info, Element el, ElementAcceptor childAcceptor) { - return element2StructureElement(info, el, childAcceptor, false, false, null); + return element2StructureElement(info, el, null, childAcceptor, false, false, null); } static FileObject findOwnerResource(CompilationInfo info, Element el) { @@ -166,7 +185,7 @@ static FileObject findOwnerResource(CompilationInfo info, Element el) { } public static StructureElement describeElement(CompilationInfo info, Element el, ElementAcceptor childAcceptor, boolean allowBinary) { - return element2StructureElement(info, el, childAcceptor, allowBinary, false, null); + return element2StructureElement(info, el, null, childAcceptor, allowBinary, false, null); } public static CompletableFuture createStructureElement(CompilationInfo info, Element el, boolean resolveSources) { @@ -199,6 +218,7 @@ private static CompletableFuture createStructureEleme return setFutureOffsets(info, el, builder, cancel, acquire); } + @Messages("LBL_AnonymousClass=") private static String createName(CompilationInfo ci, Element original) { switch (original.getKind()) { case PACKAGE: @@ -211,7 +231,13 @@ private static String createName(CompilationInfo ci, Element original) { case RECORD: TypeElement te = (TypeElement) original; StringBuilder sb = new StringBuilder(); - sb.append(te.getSimpleName()); + if (te.getNestingKind() == NestingKind.ANONYMOUS) { + String name = te.getInterfaces().isEmpty() ? te.getSuperclass().toString() + : te.getInterfaces().get(0).toString(); + sb.append(Bundle.LBL_AnonymousClass(name)); + } else { + sb.append(te.getSimpleName()); + } List typeParams = te.getTypeParameters(); if (typeParams != null && !typeParams.isEmpty()) { sb.append("<"); // NOI18N @@ -299,14 +325,6 @@ private static StructureProvider.Builder processOffsetInfo(Object[] info, Struct if (info == null) { return builder; } - int selStart = (int)info[3]; - if (selStart < 0) { - selStart = (int)info[1]; - } - int selEnd = (int)info[4]; - if (selEnd < 0) { - selEnd = (int)info[2]; - } TreePathHandle pathHandle = (TreePathHandle)info[6]; FileObject f = (FileObject)info[0]; boolean[] synthetic = new boolean[] { false }; @@ -330,9 +348,21 @@ private static StructureProvider.Builder processOffsetInfo(Object[] info, Struct if (synthetic[0]) { return null; } + fillInPositions(info, builder); + return builder; + } + + private static void fillInPositions(Object[] info, StructureProvider.Builder builder) { + int selStart = (int)info[3]; + if (selStart < 0) { + selStart = (int)info[1]; + } + int selEnd = (int)info[4]; + if (selEnd < 0) { + selEnd = (int)info[2]; + } builder.expandedStartOffset((int)info[1]).expandedEndOffset((int)info[2]); builder.selectionStartOffset(selStart).selectionEndOffset(selEnd); - return builder; } private static CompletableFuture setFutureOffsets(CompilationInfo ci, Element original, @@ -354,11 +384,21 @@ private static CompletableFuture setFutureOffsets(Com info -> processOffsetInfo(info, builder)); } - private static Object[] setOffsets(CompilationInfo ci, Element original, StructureProvider.Builder builder) { + private static FileObject setOffsets(CompilationInfo ci, Element original, TreePath originalPath, StructureProvider.Builder builder) { + if (originalPath != null && originalPath.getCompilationUnit() == ci.getCompilationUnit()) { + Object[] positions = new Object[] {null, -1, -1, -1, -1}; + builder.file(ci.getFileObject()); + if (ci.getTreeUtilities().isSynthetic(originalPath)) { + return null; + } + ElementOpenAccessor.getInstance().fillInTreePositions(ci, originalPath.getLeaf(), positions); + fillInPositions(positions, builder); + return ci.getFileObject(); + } ElementHandle h = ElementHandle.create(original); Object[] openInfo = ElementOpenAccessor.getInstance().getOpenInfo(ci.getClasspathInfo(), h, new AtomicBoolean()); processOffsetInfo(openInfo, builder); - return openInfo; + return (FileObject) openInfo[0]; } private static void getAnonymousInnerClasses(CompilationInfo info, TreePath path, StructureProvider.Builder builder, ElementAcceptor childAcceptor) { @@ -366,18 +406,14 @@ private static void getAnonymousInnerClasses(CompilationInfo info, TreePath path @Override public Void visitNewClass(NewClassTree node, Void p) { if (node.getClassBody() != null) { - Element e = info.getTrees().getElement(new TreePath(getCurrentPath(), node.getClassBody())); - if (e != null) { - TreePath path = new TreePath(getCurrentPath(), node.getIdentifier()); - TypeMirror m = info.getTrees().getTypeMirror(path); - Element te = info.getTrees().getElement(path); - if (te != null & childAcceptor.accept(te, m)) { - StructureElement jse = element2StructureElement(info, te, childAcceptor); - if (jse != null) { - builder.children(jse); - } + TreePath bodyPath = new TreePath(getCurrentPath(), node.getClassBody()); + Element e = info.getTrees().getElement(bodyPath); + if (e != null && childAcceptor.accept(e, e.asType())) { + StructureElement jse = element2StructureElement(info, e, bodyPath, childAcceptor, false, false, null); + if (jse != null) { + builder.children(jse); } - } + } } return null; } @@ -387,7 +423,7 @@ public Void visitClass(ClassTree node, Void p) { Element e = info.getTrees().getElement(getCurrentPath()); TypeMirror m = info.getTrees().getTypeMirror(getCurrentPath()); if (e != null & childAcceptor.accept(e, m)) { - StructureElement jse = element2StructureElement(info, e, childAcceptor); + StructureElement jse = element2StructureElement(info, e, getCurrentPath(), childAcceptor, false, false, null); if (jse != null) { builder.children(jse); } diff --git a/java/java.sourceui/test/unit/src/org/netbeans/api/java/source/ui/ElementHeadersTest.java b/java/java.sourceui/test/unit/src/org/netbeans/api/java/source/ui/ElementHeadersTest.java index e280a98964d6..ec5165651770 100644 --- a/java/java.sourceui/test/unit/src/org/netbeans/api/java/source/ui/ElementHeadersTest.java +++ b/java/java.sourceui/test/unit/src/org/netbeans/api/java/source/ui/ElementHeadersTest.java @@ -39,6 +39,7 @@ import java.util.regex.Pattern; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; +import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; import javax.swing.text.Document; @@ -66,6 +67,7 @@ import org.openide.filesystems.FileUtil; import org.openide.filesystems.URLMapper; import org.openide.loaders.DataObject; +import org.openide.util.NbBundle; import org.openide.util.RequestProcessor; /** @@ -379,6 +381,71 @@ public void testResolveNonLocalReference() throws Exception { assertNotNull(se); } + public void testAnonymousClass() throws Exception { + doStructureTest(""" + package test; + public class Test { + private Test test() { + return new Test() { + private int anonymousClassField; + }; + } + } + """, + "(Class:Test:null:Test.java:14-151[27-31]:null (Method:test():: Test:Test.java:38-149[51-55]:null (Class:LBL_AnonymousClass: test.Test:null:Test.java:86-142[86-142]:null (Field:anonymousClassField:: int:Test.java:100-132[112-131]:null))))"); + } + + public void testAnonymousClassInFieldInit() throws Exception { + doStructureTest(""" + package test; + public class Test { + Test t = new Test() { + private int anonymousClassField; + }; + } + """, + "(Class:Test:null:Test.java:22-129[35-39]:null (Field:t:: Test:Test.java:50-127[55-56]:null (Class:LBL_AnonymousClass: test.Test:null:Test.java:70-126[70-126]:null (Field:anonymousClassField:: int:Test.java:84-116[96-115]:null))))"); + } + + private void doStructureTest(String code, String expected) throws Exception { + prepareTest("test/Test.java", code); + TypeElement topLevel = info.getTopLevelElements().get(0); + StructureElement structure = ElementHeaders.toStructureElement(info, topLevel, (el, type) -> true); + assertEquals(expected, + structure2String(structure)); + } + + private String structure2String(StructureElement structure) { + StringBuilder result = new StringBuilder(); + result.append("(") + .append(structure.getKind()) + .append(":") + .append(structure.getName()) + .append(":") + .append(structure.getDetail()) + .append(":") + .append(structure.getFile() != null ? structure.getFile().getNameExt() : "null") + .append(":") + .append(structure.getExpandedStartOffset()) + .append("-") + .append(structure.getExpandedEndOffset()) + .append("[") + .append(structure.getSelectionStartOffset()) + .append("-") + .append(structure.getSelectionEndOffset()) + .append("]:") + .append(structure.getTags()); + + for (StructureElement child : structure.getChildren()) { + result.append(" ") + .append(structure2String(child)); + } + + result.append(")"); + + return result.toString(); + } + FileObject openideBin; /** @@ -457,4 +524,8 @@ public boolean attachJavadoc(URL root, SourceJavadocAttacher.AttachmentListener } } + + static { + NbBundle.setBranding("test"); + } } diff --git a/java/java.sourceui/test/unit/src/org/netbeans/modules/java/source/ui/Bundle_test.properties b/java/java.sourceui/test/unit/src/org/netbeans/modules/java/source/ui/Bundle_test.properties new file mode 100644 index 000000000000..bcbdb88a9c3d --- /dev/null +++ b/java/java.sourceui/test/unit/src/org/netbeans/modules/java/source/ui/Bundle_test.properties @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +LBL_AnonymousClass=LBL_AnonymousClass: {0}