Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions java/gradle.java/apichanges.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,47 +16,119 @@
* specific language governing permissions and limitations
* under the License.
*/

package org.netbeans.modules.gradle.java.api.output;

import java.util.regex.Matcher;
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
*/
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;
}
Expand All @@ -77,13 +149,38 @@ public boolean isMethod() {
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()) {
Expand All @@ -100,7 +197,7 @@ public static final Location locationFromCallStackItem(String item) {
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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,10 @@ public final void open() {
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);
}
}
Expand All @@ -81,24 +81,28 @@ private static String stripMethodParams(String methodNameWithParams) {
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;
}
Expand All @@ -114,7 +118,24 @@ private int getMethodLine(final FileObject fo, final String methodNameWithParams
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) {
Expand All @@ -125,9 +146,13 @@ private int getTargetLine(final FileObject fo) {
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;
}
Expand Down
Loading