This is an automated email from the ASF dual-hosted git repository. lkishalmi pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/netbeans.git
The following commit(s) were added to refs/heads/master by this push: new 7ae4452 Bugfix: Go To source broken for nested classes. 7ae4452 is described below commit 7ae4452ecdad828497c3317f7a5d113534978f39 Author: ratcash <develo...@ratcash.net> AuthorDate: Fri Sep 24 09:02:25 2021 +0200 Bugfix: Go To source broken for nested classes. --- java/gradle.java/apichanges.xml | 14 ++ .../modules/gradle/java/api/output/Location.java | 131 +++++++++++-- .../gradle/java/api/output/LocationOpener.java | 45 ++++- .../gradle/java/api/output/LocationTest.java | 208 +++++++++++++++++++++ java/gradle.test/nbproject/project.xml | 2 +- .../test/ui/nodes/GradleJUnitNodeOpener.java | 2 +- .../gradle/test/ui/nodes/GradleTestMethodNode.java | 2 +- 7 files changed, 374 insertions(+), 30 deletions(-) diff --git a/java/gradle.java/apichanges.xml b/java/gradle.java/apichanges.xml index 1796f74..f77b1ba 100644 --- a/java/gradle.java/apichanges.xml +++ b/java/gradle.java/apichanges.xml @@ -83,6 +83,20 @@ is the proper place. <!-- ACTUAL CHANGES BEGIN HERE: --> <changes> + <change id="nested-class-locations"> + <api name="gradle.java.api"/> + <summary>Location can represent nested classes</summary> + <version major="1" minor="17"/> + <date day="18" month="2" year="2022"/> + <author login="ratcashdev"/> + <compatibility semantic="compatible" deprecation="yes"/> + <description> + <code><a href="@TOP@/org/netbeans/modules/gradle/java/api/output/Location.html">Location</a></code> + is now capabe to represent java code location inside nested classes as well. + </description> + <class package="org.netbeans.modules.gradle.java.api.output" name="Location"/> + <issue number="NETBEANS-6041"/> + </change> <change id="gradle-7.0-deprecation"> <api name="gradle.java.api"/> <summary>Deprecating Gradle 7.0 removed API-s</summary> diff --git a/java/gradle.java/src/org/netbeans/modules/gradle/java/api/output/Location.java b/java/gradle.java/src/org/netbeans/modules/gradle/java/api/output/Location.java index 175d186..d8a1844 100644 --- a/java/gradle.java/src/org/netbeans/modules/gradle/java/api/output/Location.java +++ b/java/gradle.java/src/org/netbeans/modules/gradle/java/api/output/Location.java @@ -16,7 +16,6 @@ * specific language governing permissions and limitations * under the License. */ - package org.netbeans.modules.gradle.java.api.output; import java.util.regex.Matcher; @@ -24,6 +23,9 @@ import java.util.regex.Pattern; import org.openide.filesystems.FileObject; /** + * This class represents a location in a Java file or Class which usually + * presented in Sting form as {@code a/b/c/ClassName$SubClas1$SubClass2.java:123} or + * {@code a/b/c/ClassName$SubClas1$SubClass2.java:methodName()} * * @author Laszlo Kishalmi */ @@ -31,32 +33,102 @@ public final class Location { final String fileName; final String target; + final String[] classNames; private Integer lineNum = null; - public Location(String fileName, String target) { - this.fileName = fileName; - this.target = target; - try { - lineNum = Integer.parseInt(target); - } catch (NumberFormatException ex) { + /** + * Parses the given string in the format {@code a/b/c/ClassName$SubClas1$SubClass2.java:123} or + * {@code a/b/c/ClassName$SubClas1$SubClass2.java:methodName()} to a Location + * + * @param loc the location String + * @return the Location object represented by the location String + * @since 1.17 + */ + public static Location parseLocation(String loc) { + assert loc != null; + + // example MyFile$NestedClass.java:123 + // or // example MyFile$NestedClass.java:getMethod() + int targetDelimiterIndex = loc.lastIndexOf(':'); + if (targetDelimiterIndex == -1) { + targetDelimiterIndex = loc.length(); } - } + // the last dot will be before the .java extension + // unless there's no extension and this may be before the classname + // but those cases not supported, really + int extensionIndx = loc.lastIndexOf('.', targetDelimiterIndex); + if (extensionIndx == -1) { + extensionIndx = targetDelimiterIndex; + } + // is the dot right before the File's name (after the package's name) + int packageSlashIndx = loc.lastIndexOf('/', extensionIndx - 1); - public Location(String loc) { - int i = loc != null ? loc.indexOf(':') : 0; - if ((i > 0) && (loc != null)) { - fileName = loc.substring(0, i); - target = loc.substring(i + 1); + String[] classNames = loc.substring(packageSlashIndx + 1, extensionIndx).split("\\$"); + String ext = loc.substring(extensionIndx, targetDelimiterIndex); + String fileName = loc.substring(0, packageSlashIndx + 1) + classNames[0] + ext; + + String target; + if (targetDelimiterIndex < loc.length() - 1) { + target = loc.substring(targetDelimiterIndex + 1); } else { - fileName = loc; target = null; } + + return new Location(fileName, classNames, target); + } + + /** + * Parses and creates a location item out of a string. + * @param loc the string representation of the location + * @deprecated in favor of {@linkplain #parseLocation(java.lang.String)} + */ + @Deprecated + public Location(String loc) { + Location l = parseLocation(loc); + this.fileName = l.fileName; + this.classNames = l.classNames; + this.target = l.target; + this.lineNum = l.lineNum; + } + + /** + * Parses and creates a location item out of a string as a file name and a + * target which can be either a method name or a line number. + * @param fileName the file name part of the location + * @param target the line number or method name. + * + * @deprecated in favor of {@linkplain #parseLocation(java.lang.String)} + */ + @Deprecated + public Location(String fileName, String target) { + Location loc = parseLocation(fileName + " : " + target); + this.fileName = loc.fileName; + this.classNames = loc.classNames; + this.target = loc.target; + this.lineNum = loc.lineNum; + } + + private Location(String fileName, String[] classNames, String target) { + this.fileName = fileName; + this.target = target; + this.classNames = classNames; try { lineNum = Integer.parseInt(target); } catch (NumberFormatException ex) { } } + /** + * Returns a new location instance without the target(line number or method name) + * of this location. + * + * @return a Location without target information. + * @since 1.17 + */ + public Location withNoTarget() { + return new Location(fileName, classNames, null); + } + public String getFileName() { return fileName; } @@ -77,13 +149,38 @@ public final class Location { return (target != null) && (lineNum == null); } + /** + * Get the classes represented in this Location, the outmost and then the + * inner nested classes as well. + * + * @return the class name nesting hierarchy + * @since 1.17 + */ + String[] getClassNames() { + return classNames; + } + @Override public String toString() { - return target != null ? fileName + ":" + target : fileName; + StringBuilder sb = new StringBuilder(); + sb.append(fileName.substring(0, fileName.indexOf(classNames[0]))); + for (int i = 0; i < classNames.length; i++) { + String className = classNames[i]; + sb.append(className); + sb.append("$"); + } + sb.setLength(sb.length() - 1); + sb.append(fileName.substring( + fileName.indexOf(classNames[0]) + classNames[0].length())); + if (target != null) { + sb.append(":"); + sb.append(target); + } + return sb.toString(); } private static final Pattern CALLSTACK_ITEM_PARSER = Pattern.compile("(.*)at (\\w[\\w\\.\\$<>]*)\\.(\\w+)\\((\\w+)\\.java\\:([0-9]+)\\)"); - + public static final Location locationFromCallStackItem(String item) { Matcher m = CALLSTACK_ITEM_PARSER.matcher(item); if (m.matches()) { @@ -100,7 +197,7 @@ public final class Location { ret.append(className.replace('.', '/')); } ret.append(".java"); - return new Location(ret.toString(), line != null ? line : methodName); + return Location.parseLocation(ret.toString() + ":" + line != null ? line : methodName); } else { return null; } diff --git a/java/gradle.java/src/org/netbeans/modules/gradle/java/api/output/LocationOpener.java b/java/gradle.java/src/org/netbeans/modules/gradle/java/api/output/LocationOpener.java index b15d9bc..0301601 100644 --- a/java/gradle.java/src/org/netbeans/modules/gradle/java/api/output/LocationOpener.java +++ b/java/gradle.java/src/org/netbeans/modules/gradle/java/api/output/LocationOpener.java @@ -63,10 +63,10 @@ public final class LocationOpener { if (location.isLine()) { openAtLine(fo, location.getLineNum()); } else if (location.isMethod()) { - int l = getMethodLine(fo, location.getTarget()); + int l = getMethodLine(fo, location.getClassNames(), location.getTarget()); openAtLine(fo, l); } else { - int l = getTargetLine(fo); + int l = getTargetLine(fo, location.getClassNames()); openAtLine(fo, l); } } @@ -81,24 +81,28 @@ public final class LocationOpener { return cleanName; } - private int getMethodLine(final FileObject fo, final String methodNameWithParams) { + private int getMethodLine(final FileObject fo, final String[] classNames, final String methodNameWithParams) { String methodName = stripMethodParams(methodNameWithParams); final int[] line = new int[1]; JavaSource javaSource = JavaSource.forFileObject(fo); if (javaSource != null) { try { javaSource.runUserActionTask((CompilationController compilationController) -> { - compilationController.toPhase(JavaSource.Phase.ELEMENTS_RESOLVED); + compilationController.toPhase(JavaSource.Phase.ELEMENTS_RESOLVED); Trees trees = compilationController.getTrees(); CompilationUnitTree compilationUnitTree = compilationController.getCompilationUnit(); List<? extends Tree> typeDecls = compilationUnitTree.getTypeDecls(); for (Tree tree : typeDecls) { - Element element = trees.getElement(trees.getPath(compilationUnitTree, tree)); - if (element != null && element.getKind() == ElementKind.CLASS && element.getSimpleName().contentEquals(fo.getName())) { + Element element = getClassElement( + trees.getElement(trees.getPath(compilationUnitTree, tree)), + classNames,0); + if (element != null) { List<? extends ExecutableElement> methodElements = ElementFilter.methodsIn(element.getEnclosedElements()); + System.out.println("Looking for methodName " + methodName); for (Element child : methodElements) { if (child.getSimpleName().contentEquals(methodName)) { long pos = trees.getSourcePositions().getStartPosition(compilationUnitTree, trees.getTree(child)); + System.out.println("Found method. LINE: " + pos); line[0] = (int) compilationUnitTree.getLineMap().getLineNumber(pos); break; } @@ -114,7 +118,24 @@ public final class LocationOpener { return 1; } - private int getTargetLine(final FileObject fo) { + + private Element getClassElement(Element element, String[] classNames, int startIndex) { + if (element != null && element.getKind() == ElementKind.CLASS && + element.getSimpleName().contentEquals(classNames[startIndex])) { + if (startIndex == classNames.length - 1) { + return element; + } + for (Element enclosedElement : element.getEnclosedElements()) { + Element matchingElement = getClassElement(enclosedElement, classNames, startIndex + 1); + if (matchingElement != null) { + return matchingElement; + } + } + } + return null; + } + + private int getTargetLine(final FileObject fo, String[] classNames) { final int[] line = new int[]{0}; JavaSource javaSource = JavaSource.forFileObject(fo); if (javaSource != null) { @@ -125,9 +146,13 @@ public final class LocationOpener { CompilationUnitTree compilationUnitTree = compilationController.getCompilationUnit(); List<? extends Tree> typeDecls = compilationUnitTree.getTypeDecls(); for (Tree tree : typeDecls) { - Element element = trees.getElement(trees.getPath(compilationUnitTree, tree)); - if (element != null && element.getKind() == ElementKind.CLASS && element.getSimpleName().contentEquals(fo.getName())) { - long pos = trees.getSourcePositions().getStartPosition(compilationUnitTree, tree); + Element element = getClassElement( + trees.getElement(trees.getPath(compilationUnitTree, tree)), + classNames,0); + if (element != null) { + long pos = trees.getSourcePositions().getStartPosition( + compilationUnitTree, + trees.getTree(element)); line[0] = (int) compilationUnitTree.getLineMap().getLineNumber(pos); break; } diff --git a/java/gradle.java/test/unit/src/org/netbeans/modules/gradle/java/api/output/LocationTest.java b/java/gradle.java/test/unit/src/org/netbeans/modules/gradle/java/api/output/LocationTest.java new file mode 100644 index 0000000..6d80c08 --- /dev/null +++ b/java/gradle.java/test/unit/src/org/netbeans/modules/gradle/java/api/output/LocationTest.java @@ -0,0 +1,208 @@ +/* + * 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. + */ +package org.netbeans.modules.gradle.java.api.output; + +import org.junit.Assert; +import org.junit.Test; + +/** + * + * @author OmniBene, s.r.o. + */ +public class LocationTest { + + @Test + public void testFilenameFromClassOnly() { + String src = "A"; + Location cut = Location.parseLocation(src); + Assert.assertFalse(cut.isLine()); + Assert.assertFalse(cut.isMethod()); + Assert.assertEquals(src, cut.toString()); + } + + @Test + public void testFilenameFromClassAndExtension() { + String src = "A.java"; + Location cut = Location.parseLocation(src); + Assert.assertFalse(cut.isLine()); + Assert.assertFalse(cut.isMethod()); + Assert.assertEquals(src, cut.toString()); + } + + @Test + public void testFilenameFromPackageAndClassOnly() { + String src = "my/package.A"; + Location cut = Location.parseLocation(src); + Assert.assertFalse(cut.isLine()); + Assert.assertFalse(cut.isMethod()); + Assert.assertEquals(src, cut.toString()); + } + + @Test + public void testFilenameFromPackageAndClassAndExtension() { + String src = "my/package/A.java"; + Location cut = Location.parseLocation(src); + Assert.assertFalse(cut.isLine()); + Assert.assertFalse(cut.isMethod()); + Assert.assertEquals(src, cut.toString()); + } + + @Test + public void testLine() { + String src = ":123"; + Location cut = Location.parseLocation(src); + Assert.assertTrue(cut.isLine()); + Assert.assertFalse(cut.isMethod()); + Assert.assertEquals(src, cut.toString()); + } + + @Test + public void testLineWithClass() { + String src = "A:123"; + Location cut = Location.parseLocation(src); + Assert.assertTrue(cut.isLine()); + Assert.assertFalse(cut.isMethod()); + Assert.assertEquals(src, cut.toString()); + } + + @Test + public void testLineWithClassAndExt() { + String src = "Class.java:123"; + Location cut = Location.parseLocation(src); + Assert.assertTrue(cut.isLine()); + Assert.assertFalse(cut.isMethod()); + Assert.assertEquals(src, cut.toString()); + } + + @Test + public void testLineWithPackageAndClassAndExt() { + String src = "a/Class.java:123"; + Location cut = Location.parseLocation(src); + Assert.assertTrue(cut.isLine()); + Assert.assertFalse(cut.isMethod()); + Assert.assertEquals(src, cut.toString()); + } + + @Test + public void testLineWithPackageAndClass() { + String src = "a/Class:123"; + Location cut = Location.parseLocation(src); + Assert.assertTrue(cut.isLine()); + Assert.assertFalse(cut.isMethod()); + Assert.assertEquals(src, cut.toString()); + } + + @Test + public void testMethod() { + String src = ":myMethod()"; + Location cut = Location.parseLocation(src); + Assert.assertFalse(cut.isLine()); + Assert.assertTrue(cut.isMethod()); + Assert.assertEquals(src, cut.toString()); + } + + @Test + public void testMethodWithClass() { + String src = "A:myMethod()"; + Location cut = Location.parseLocation(src); + Assert.assertFalse(cut.isLine()); + Assert.assertTrue(cut.isMethod()); + Assert.assertEquals(src, cut.toString()); + } + + @Test + public void testMethodWithClassAndExt() { + String src = "Class.java:myMethod()"; + Location cut = Location.parseLocation(src); + Assert.assertFalse(cut.isLine()); + Assert.assertTrue(cut.isMethod()); + Assert.assertEquals(src, cut.toString()); + } + + @Test + public void testMethodWithPackageAndClassAndExt() { + String src = "a/Class.java:myMethod()"; + Location cut = Location.parseLocation(src); + Assert.assertFalse(cut.isLine()); + Assert.assertTrue(cut.isMethod()); + Assert.assertEquals(src, cut.toString()); + } + + @Test + public void testMethodWithPackageAndClass() { + String src = "a/Class:myMethod()"; + Location cut = Location.parseLocation(src); + Assert.assertFalse(cut.isLine()); + Assert.assertTrue(cut.isMethod()); + Assert.assertEquals(src, cut.toString()); + } + + @Test + public void testNestedClassNoPackage() { + String src = "A$B"; + Location cut = Location.parseLocation(src); + Assert.assertEquals("A", cut.classNames[0]); + Assert.assertEquals("B", cut.classNames[1]); + Assert.assertEquals(src, cut.toString()); + } + + @Test + public void testNestedClassWithPackageAndExtension() { + String src = "x/y/A$B.java"; + Location cut = Location.parseLocation(src); + Assert.assertEquals("A", cut.classNames[0]); + Assert.assertEquals("B", cut.classNames[1]); + Assert.assertEquals(src, cut.toString()); + } + + @Test + public void testNestedClassWithPackageAndGroovyExtension() { + String src = "x/y/A$B.groovy"; + Location cut = Location.parseLocation(src); + Assert.assertEquals("A", cut.classNames[0]); + Assert.assertEquals("B", cut.classNames[1]); + Assert.assertEquals(src, cut.toString()); + } + + @Test + public void testNestedClassWithPackageAndKotlinExtension() { + String src = "x/y/A$B.kt"; + Location cut = Location.parseLocation(src); + Assert.assertEquals("A", cut.classNames[0]); + Assert.assertEquals("B", cut.classNames[1]); + Assert.assertEquals(src, cut.toString()); + } + + @Test + public void testNestedClassWithPackageAndKotlinExtension2() { + String src = "x/y/A$B.kt"; + Location cut = Location.parseLocation(src); + Assert.assertEquals("A", cut.classNames[0]); + Assert.assertEquals("B", cut.classNames[1]); + Assert.assertEquals(src, cut.toString()); + } + + @Test + public void testPackageWithNoExtension() { + String src = "x/y/A"; + Location cut = Location.parseLocation(src); + Assert.assertEquals("A", cut.classNames[0]); + } + +} diff --git a/java/gradle.test/nbproject/project.xml b/java/gradle.test/nbproject/project.xml index 2c90eb7..38e3b00 100644 --- a/java/gradle.test/nbproject/project.xml +++ b/java/gradle.test/nbproject/project.xml @@ -47,7 +47,7 @@ <build-prerequisite/> <compile-dependency/> <run-dependency> - <specification-version>1.5</specification-version> + <specification-version>1.17</specification-version> </run-dependency> </dependency> <dependency> diff --git a/java/gradle.test/src/org/netbeans/modules/gradle/test/ui/nodes/GradleJUnitNodeOpener.java b/java/gradle.test/src/org/netbeans/modules/gradle/test/ui/nodes/GradleJUnitNodeOpener.java index 4246c42..7656f87 100644 --- a/java/gradle.test/src/org/netbeans/modules/gradle/test/ui/nodes/GradleJUnitNodeOpener.java +++ b/java/gradle.test/src/org/netbeans/modules/gradle/test/ui/nodes/GradleJUnitNodeOpener.java @@ -45,7 +45,7 @@ public final class GradleJUnitNodeOpener extends NodeOpener { Node first = children.getNodeAt(0); if ((first != null) && (first instanceof GradleTestMethodNode)) { GradleTestMethodNode node = (GradleTestMethodNode) first; - Location loc = new Location(node.getTestLocation().getFileName()); + Location loc = node.getTestLocation().withNoTarget(); new LocationOpener(loc, node).open(); } } diff --git a/java/gradle.test/src/org/netbeans/modules/gradle/test/ui/nodes/GradleTestMethodNode.java b/java/gradle.test/src/org/netbeans/modules/gradle/test/ui/nodes/GradleTestMethodNode.java index 5981b60..a9d7f50 100644 --- a/java/gradle.test/src/org/netbeans/modules/gradle/test/ui/nodes/GradleTestMethodNode.java +++ b/java/gradle.test/src/org/netbeans/modules/gradle/test/ui/nodes/GradleTestMethodNode.java @@ -93,6 +93,6 @@ public final class GradleTestMethodNode extends JUnitTestMethodNode implements L } Location getTestLocation() { - return new Location(getTestcase().getLocation()); + return Location.parseLocation(getTestcase().getLocation()); } } --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@netbeans.apache.org For additional commands, e-mail: commits-h...@netbeans.apache.org For further information about the NetBeans mailing lists, visit: https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists