Skip to content

Commit

Permalink
Support debugging Java 21 instance main method and unnamed class (#542)
Browse files Browse the repository at this point in the history
* Support debugging Java 21 instance main method and unnamed class
  • Loading branch information
testforstephen authored Mar 21, 2024
1 parent 736dcac commit 56c0145
Show file tree
Hide file tree
Showing 6 changed files with 158 additions and 64 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
${{ runner.os }}-maven-
- name: Verify
run: ./mvnw clean verify
run: ./mvnw clean verify -U

- name: Checkstyle
run: ./mvnw checkstyle:check
Expand Down Expand Up @@ -85,7 +85,7 @@ jobs:
${{ runner.os }}-maven-
- name: Verify
run: ./mvnw clean verify
run: ./mvnw clean verify -U

- name: Checkstyle
run: ./mvnw checkstyle:check
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.commons.lang3.StringUtils;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
Expand All @@ -56,6 +57,7 @@
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.LambdaExpression;
import org.eclipse.jdt.core.dom.UnnamedClass;
import org.eclipse.jdt.core.manipulation.CoreASTProvider;
import org.eclipse.jdt.internal.core.JarPackageFragmentRoot;
import org.eclipse.jdt.launching.IVMInstall;
Expand Down Expand Up @@ -172,6 +174,11 @@ public JavaBreakpointLocation[] getBreakpointLocations(String sourceUri, SourceB
.map(sourceBreakpoint -> new JavaBreakpointLocation(sourceBreakpoint.line, sourceBreakpoint.column))
.toArray(JavaBreakpointLocation[]::new);
if (astUnit != null) {
List<?> types = astUnit.types();
String unnamedClass = null;
if (types.size() == 1 && types.get(0) instanceof UnnamedClass) {
unnamedClass = inferPrimaryTypeName(sourceUri, astUnit);
}
Map<Integer, BreakpointLocation[]> resolvedLocations = new HashMap<>();
for (JavaBreakpointLocation sourceLocation : sourceLocations) {
int sourceLine = sourceLocation.lineNumber();
Expand Down Expand Up @@ -222,7 +229,7 @@ public JavaBreakpointLocation[] getBreakpointLocations(String sourceUri, SourceB
// be hit in current implementation.
if (sourceLine == locator.getLineLocation()
&& locator.getLocationType() == BreakpointLocationLocator.LOCATION_LINE) {
sourceLocation.setClassName(locator.getFullyQualifiedTypeName());
sourceLocation.setClassName(StringUtils.isBlank(unnamedClass) ? locator.getFullyQualifiedTypeName() : unnamedClass);
if (resolvedLocations.containsKey(sourceLine)) {
sourceLocation.setAvailableBreakpointLocations(resolvedLocations.get(sourceLine));
} else {
Expand All @@ -231,7 +238,7 @@ public JavaBreakpointLocation[] getBreakpointLocations(String sourceUri, SourceB
resolvedLocations.put(sourceLine, inlineLocations);
}
} else if (locator.getLocationType() == BreakpointLocationLocator.LOCATION_METHOD) {
sourceLocation.setClassName(locator.getFullyQualifiedTypeName());
sourceLocation.setClassName(StringUtils.isBlank(unnamedClass) ? locator.getFullyQualifiedTypeName() : unnamedClass);
sourceLocation.setMethodName(locator.getMethodName());
sourceLocation.setMethodSignature(locator.getMethodSignature());
}
Expand All @@ -241,6 +248,27 @@ public JavaBreakpointLocation[] getBreakpointLocations(String sourceUri, SourceB
return sourceLocations;
}

private String inferPrimaryTypeName(String uri, CompilationUnit astUnit) {
String fileName = "";
String filePath = AdapterUtils.toPath(uri);
if (filePath != null && Files.isRegularFile(Paths.get(filePath))) {
fileName = Paths.get(filePath).getFileName().toString();
} else if (astUnit.getTypeRoot() != null) {
fileName = astUnit.getTypeRoot().getElementName();
}

if (StringUtils.isNotBlank(fileName)) {
String[] extensions = JavaCore.getJavaLikeExtensions();
for (String extension : extensions) {
if (fileName.endsWith("." + extension)) {
return fileName.substring(0, fileName.length() - 1 - extension.length());
}
}
}

return fileName;
}

private BreakpointLocation[] getInlineBreakpointLocations(final CompilationUnit astUnit, int sourceLine) {
List<BreakpointLocation> locations = new ArrayList<>();
// The starting position of each line is the default breakpoint location for
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import org.eclipse.jdt.core.search.SearchParticipant;
import org.eclipse.jdt.core.search.SearchPattern;
import org.eclipse.jdt.core.search.SearchRequestor;
import org.eclipse.jdt.internal.core.SourceMethod;
import org.eclipse.jdt.ls.core.internal.ProjectUtils;
import org.eclipse.jdt.ls.core.internal.ResourceUtils;
import org.eclipse.jdt.ls.core.internal.managers.ProjectsManager;
Expand Down Expand Up @@ -101,49 +102,44 @@ private List<ResolutionItem> resolveMainClassUnderPaths(List<IPath> parentPaths)
// Limit to search main method from source code only.
IJavaSearchScope searchScope = SearchEngine.createJavaSearchScope(ProjectUtils.getJavaProjects(),
IJavaSearchScope.REFERENCED_PROJECTS | IJavaSearchScope.SOURCES);
SearchPattern pattern = SearchPattern.createPattern("main(String[]) void", IJavaSearchConstants.METHOD,
IJavaSearchConstants.DECLARATIONS, SearchPattern.R_CASE_SENSITIVE | SearchPattern.R_EXACT_MATCH);
SearchPattern pattern = createMainMethodSearchPattern();
final List<ResolutionItem> res = new ArrayList<>();
SearchRequestor requestor = new SearchRequestor() {
@Override
public void acceptSearchMatch(SearchMatch match) {
Object element = match.getElement();
if (element instanceof IMethod) {
IMethod method = (IMethod) element;
try {
if (method.isMainMethod()) {
IResource resource = method.getResource();
if (resource != null) {
IProject project = resource.getProject();
if (project != null) {
String mainClass = method.getDeclaringType().getFullyQualifiedName();
IJavaProject javaProject = JdtUtils.getJavaProject(project);
if (javaProject != null) {
String moduleName = JdtUtils.getModuleName(javaProject);
if (moduleName != null) {
mainClass = moduleName + "/" + mainClass;
}
if (isMainMethod(method)) {
IResource resource = method.getResource();
if (resource != null) {
IProject project = resource.getProject();
if (project != null) {
String mainClass = method.getDeclaringType().getFullyQualifiedName();
IJavaProject javaProject = JdtUtils.getJavaProject(project);
if (javaProject != null) {
String moduleName = JdtUtils.getModuleName(javaProject);
if (moduleName != null) {
mainClass = moduleName + "/" + mainClass;
}
String projectName = ProjectsManager.DEFAULT_PROJECT_NAME.equals(project.getName()) ? null : project.getName();
if (parentPaths.isEmpty()
|| ResourceUtils.isContainedIn(project.getLocation(), parentPaths)
|| isContainedInInvisibleProject(project, parentPaths)) {
String filePath = null;

if (match.getResource() instanceof IFile) {
try {
filePath = match.getResource().getLocation().toOSString();
} catch (Exception ex) {
// ignore
}
}
String projectName = ProjectsManager.DEFAULT_PROJECT_NAME.equals(project.getName()) ? null : project.getName();
if (parentPaths.isEmpty()
|| ResourceUtils.isContainedIn(project.getLocation(), parentPaths)
|| isContainedInInvisibleProject(project, parentPaths)) {
String filePath = null;

if (match.getResource() instanceof IFile) {
try {
filePath = match.getResource().getLocation().toOSString();
} catch (Exception ex) {
// ignore
}
res.add(new ResolutionItem(mainClass, projectName, filePath));
}
res.add(new ResolutionItem(mainClass, projectName, filePath));
}
}
}
} catch (JavaModelException e) {
// ignore
}
}
}
Expand All @@ -166,44 +162,39 @@ private List<ResolutionItem> resolveMainClassUnderProject(final String projectNa
IJavaProject javaProject = ProjectUtils.getJavaProject(projectName);
IJavaSearchScope searchScope = SearchEngine.createJavaSearchScope(javaProject == null ? new IJavaProject[0] : new IJavaProject[] {javaProject},
IJavaSearchScope.REFERENCED_PROJECTS | IJavaSearchScope.SOURCES);
SearchPattern pattern = SearchPattern.createPattern("main(String[]) void", IJavaSearchConstants.METHOD,
IJavaSearchConstants.DECLARATIONS, SearchPattern.R_CASE_SENSITIVE | SearchPattern.R_EXACT_MATCH);
SearchPattern pattern = createMainMethodSearchPattern();
final List<ResolutionItem> res = new ArrayList<>();
SearchRequestor requestor = new SearchRequestor() {
@Override
public void acceptSearchMatch(SearchMatch match) {
Object element = match.getElement();
if (element instanceof IMethod) {
IMethod method = (IMethod) element;
try {
if (method.isMainMethod()) {
IResource resource = method.getResource();
if (resource != null) {
IProject project = resource.getProject();
if (project != null) {
String mainClass = method.getDeclaringType().getFullyQualifiedName();
IJavaProject javaProject = JdtUtils.getJavaProject(project);
if (javaProject != null) {
String moduleName = JdtUtils.getModuleName(javaProject);
if (moduleName != null) {
mainClass = moduleName + "/" + mainClass;
}
if (isMainMethod(method)) {
IResource resource = method.getResource();
if (resource != null) {
IProject project = resource.getProject();
if (project != null) {
String mainClass = method.getDeclaringType().getFullyQualifiedName();
IJavaProject javaProject = JdtUtils.getJavaProject(project);
if (javaProject != null) {
String moduleName = JdtUtils.getModuleName(javaProject);
if (moduleName != null) {
mainClass = moduleName + "/" + mainClass;
}
}

String filePath = null;
if (match.getResource() instanceof IFile) {
try {
filePath = match.getResource().getLocation().toOSString();
} catch (Exception ex) {
// ignore
}
String filePath = null;
if (match.getResource() instanceof IFile) {
try {
filePath = match.getResource().getLocation().toOSString();
} catch (Exception ex) {
// ignore
}
res.add(new ResolutionItem(mainClass, projectName, filePath));
}
res.add(new ResolutionItem(mainClass, projectName, filePath));
}
}
} catch (JavaModelException e) {
// ignore
}
}
}
Expand All @@ -221,6 +212,29 @@ public void acceptSearchMatch(SearchMatch match) {
return resolutions;
}

private SearchPattern createMainMethodSearchPattern() {
SearchPattern pattern1 = SearchPattern.createPattern("main(String[]) void", IJavaSearchConstants.METHOD,
IJavaSearchConstants.DECLARATIONS, SearchPattern.R_CASE_SENSITIVE | SearchPattern.R_EXACT_MATCH);
SearchPattern pattern2 = SearchPattern.createPattern("main() void", IJavaSearchConstants.METHOD,
IJavaSearchConstants.DECLARATIONS, SearchPattern.R_CASE_SENSITIVE | SearchPattern.R_EXACT_MATCH);
return SearchPattern.createOrPattern(pattern1, pattern2);
}

private boolean isMainMethod(IMethod method) {
try {
if (method instanceof SourceMethod
&& ((SourceMethod) method).isMainMethodCandidate()) {
return true;
}

return method.isMainMethod();
} catch (JavaModelException e) {
// do nothing
}

return false;
}

private boolean isContainedInInvisibleProject(IProject project, Collection<IPath> rootPaths) {
if (project == null) {
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
Expand All @@ -30,7 +31,11 @@
import org.eclipse.jdt.core.ISourceRange;
import org.eclipse.jdt.core.ISourceReference;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
import org.eclipse.jdt.internal.core.SourceMethod;
import org.eclipse.jdt.ls.core.internal.JDTUtils;
import org.eclipse.jdt.ls.core.internal.handlers.DocumentLifeCycleHandler;
import org.eclipse.jdt.ls.core.internal.managers.ProjectsManager;
Expand Down Expand Up @@ -104,16 +109,58 @@ private static List<IMethod> searchMainMethods(ICompilationUnit compilationUnit)
* Returns the main method defined in the type.
*/
public static IMethod getMainMethod(IType type) throws JavaModelException {
boolean allowInstanceMethod = isInstanceMainMethodSupported(type);
List<IMethod> methods = new ArrayList<>();
for (IMethod method : type.getMethods()) {
// Have at most one main method in the member methods of the type.
if (method instanceof SourceMethod
&& ((SourceMethod) method).isMainMethodCandidate()) {
methods.add(method);
}

if (method.isMainMethod()) {
return method;
methods.add(method);
}

if (!allowInstanceMethod && !methods.isEmpty()) {
return methods.get(0);
}
}

if (!methods.isEmpty()) {
methods.sort((method1, method2) -> {
return getMainMethodPriority(method1) - getMainMethodPriority(method2);
});

return methods.get(0);
}

return null;
}

private static boolean isInstanceMainMethodSupported(IType type) {
Map<String, String> options = type.getJavaProject().getOptions(true);
return CompilerOptions.versionToJdkLevel(options.get(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM)) >= ClassFileConstants.JDK21;
}

private static int getMainMethodPriority(IMethod method) {
int flags = 0;
try {
flags = method.getFlags();
} catch (JavaModelException e) {
// do nothing
}
String[] params = method.getParameterTypes();
if (Flags.isStatic(flags) && params.length == 1) {
return 1;
} else if (Flags.isStatic(flags)) {
return 2;
} else if (params.length == 1) {
return 3;
}

return 4;
}

private static List<IType> getPotentialMainClassTypes(ICompilationUnit compilationUnit) throws JavaModelException {
List<IType> result = new ArrayList<>();
IType[] topLevelTypes = compilationUnit.getTypes();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,18 @@
<unit id="org.eclipse.equinox.executable.feature.group" version="0.0.0"/>
<unit id="org.eclipse.equinox.p2.core.feature.source.feature.group" version="0.0.0"/>
<unit id="org.eclipse.equinox.sdk.feature.group" version="0.0.0"/>
<unit id="org.eclipse.jdt.source.feature.group" version="0.0.0"/>
<unit id="org.eclipse.jdt.apt.pluggable.core" version="0.0.0"/>
<repository location="https://download.eclipse.org/eclipse/updates/4.31/"/>
</location>
<location includeAllPlatforms="false" includeConfigurePhase="false" includeMode="planner" includeSource="true" type="InstallableUnit">
<unit id="org.eclipse.jdt.core.compiler.batch" version="0.0.0"/>
<unit id="org.eclipse.jdt.core" version="0.0.0"/>
<unit id="org.eclipse.jdt.apt.core" version="0.0.0"/>
<repository location="https://download.eclipse.org/jdtls/jdt-core-incubator/snapshots/"/>
</location>
<location includeAllPlatforms="false" includeConfigurePhase="false" includeMode="planner" includeSource="true" type="InstallableUnit">
<unit id="org.eclipse.xtext.sdk.feature.group" version="0.0.0"/>
<repository location="https://download.eclipse.org/releases/2023-12/"/>
<repository location="https://download.eclipse.org/releases/2024-03/"/>
</location>
<location includeAllPlatforms="false" includeConfigurePhase="false" includeMode="planner" includeSource="true" type="InstallableUnit">
<unit id="org.jboss.tools.maven.apt.core" version="0.0.0"/>
Expand Down
Loading

0 comments on commit 56c0145

Please sign in to comment.