Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support debugging Java 21 instance main method and unnamed class #542

Merged
merged 4 commits into from
Mar 21, 2024
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
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
Loading