Skip to content

Commit 56c0145

Browse files
Support debugging Java 21 instance main method and unnamed class (#542)
* Support debugging Java 21 instance main method and unnamed class
1 parent 736dcac commit 56c0145

File tree

6 files changed

+158
-64
lines changed

6 files changed

+158
-64
lines changed

.github/workflows/build.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ jobs:
2828
${{ runner.os }}-maven-
2929
3030
- name: Verify
31-
run: ./mvnw clean verify
31+
run: ./mvnw clean verify -U
3232

3333
- name: Checkstyle
3434
run: ./mvnw checkstyle:check
@@ -85,7 +85,7 @@ jobs:
8585
${{ runner.os }}-maven-
8686
8787
- name: Verify
88-
run: ./mvnw clean verify
88+
run: ./mvnw clean verify -U
8989

9090
- name: Checkstyle
9191
run: ./mvnw checkstyle:check

com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JdtSourceLookUpProvider.java

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import java.util.stream.Collectors;
3333
import java.util.stream.Stream;
3434

35+
import org.apache.commons.lang3.StringUtils;
3536
import org.eclipse.core.resources.IFile;
3637
import org.eclipse.core.resources.IResource;
3738
import org.eclipse.core.resources.ResourcesPlugin;
@@ -56,6 +57,7 @@
5657
import org.eclipse.jdt.core.dom.IMethodBinding;
5758
import org.eclipse.jdt.core.dom.ITypeBinding;
5859
import org.eclipse.jdt.core.dom.LambdaExpression;
60+
import org.eclipse.jdt.core.dom.UnnamedClass;
5961
import org.eclipse.jdt.core.manipulation.CoreASTProvider;
6062
import org.eclipse.jdt.internal.core.JarPackageFragmentRoot;
6163
import org.eclipse.jdt.launching.IVMInstall;
@@ -172,6 +174,11 @@ public JavaBreakpointLocation[] getBreakpointLocations(String sourceUri, SourceB
172174
.map(sourceBreakpoint -> new JavaBreakpointLocation(sourceBreakpoint.line, sourceBreakpoint.column))
173175
.toArray(JavaBreakpointLocation[]::new);
174176
if (astUnit != null) {
177+
List<?> types = astUnit.types();
178+
String unnamedClass = null;
179+
if (types.size() == 1 && types.get(0) instanceof UnnamedClass) {
180+
unnamedClass = inferPrimaryTypeName(sourceUri, astUnit);
181+
}
175182
Map<Integer, BreakpointLocation[]> resolvedLocations = new HashMap<>();
176183
for (JavaBreakpointLocation sourceLocation : sourceLocations) {
177184
int sourceLine = sourceLocation.lineNumber();
@@ -222,7 +229,7 @@ public JavaBreakpointLocation[] getBreakpointLocations(String sourceUri, SourceB
222229
// be hit in current implementation.
223230
if (sourceLine == locator.getLineLocation()
224231
&& locator.getLocationType() == BreakpointLocationLocator.LOCATION_LINE) {
225-
sourceLocation.setClassName(locator.getFullyQualifiedTypeName());
232+
sourceLocation.setClassName(StringUtils.isBlank(unnamedClass) ? locator.getFullyQualifiedTypeName() : unnamedClass);
226233
if (resolvedLocations.containsKey(sourceLine)) {
227234
sourceLocation.setAvailableBreakpointLocations(resolvedLocations.get(sourceLine));
228235
} else {
@@ -231,7 +238,7 @@ public JavaBreakpointLocation[] getBreakpointLocations(String sourceUri, SourceB
231238
resolvedLocations.put(sourceLine, inlineLocations);
232239
}
233240
} else if (locator.getLocationType() == BreakpointLocationLocator.LOCATION_METHOD) {
234-
sourceLocation.setClassName(locator.getFullyQualifiedTypeName());
241+
sourceLocation.setClassName(StringUtils.isBlank(unnamedClass) ? locator.getFullyQualifiedTypeName() : unnamedClass);
235242
sourceLocation.setMethodName(locator.getMethodName());
236243
sourceLocation.setMethodSignature(locator.getMethodSignature());
237244
}
@@ -241,6 +248,27 @@ public JavaBreakpointLocation[] getBreakpointLocations(String sourceUri, SourceB
241248
return sourceLocations;
242249
}
243250

251+
private String inferPrimaryTypeName(String uri, CompilationUnit astUnit) {
252+
String fileName = "";
253+
String filePath = AdapterUtils.toPath(uri);
254+
if (filePath != null && Files.isRegularFile(Paths.get(filePath))) {
255+
fileName = Paths.get(filePath).getFileName().toString();
256+
} else if (astUnit.getTypeRoot() != null) {
257+
fileName = astUnit.getTypeRoot().getElementName();
258+
}
259+
260+
if (StringUtils.isNotBlank(fileName)) {
261+
String[] extensions = JavaCore.getJavaLikeExtensions();
262+
for (String extension : extensions) {
263+
if (fileName.endsWith("." + extension)) {
264+
return fileName.substring(0, fileName.length() - 1 - extension.length());
265+
}
266+
}
267+
}
268+
269+
return fileName;
270+
}
271+
244272
private BreakpointLocation[] getInlineBreakpointLocations(final CompilationUnit astUnit, int sourceLine) {
245273
List<BreakpointLocation> locations = new ArrayList<>();
246274
// The starting position of each line is the default breakpoint location for

com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/ResolveMainClassHandler.java

Lines changed: 69 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import org.eclipse.jdt.core.search.SearchParticipant;
4444
import org.eclipse.jdt.core.search.SearchPattern;
4545
import org.eclipse.jdt.core.search.SearchRequestor;
46+
import org.eclipse.jdt.internal.core.SourceMethod;
4647
import org.eclipse.jdt.ls.core.internal.ProjectUtils;
4748
import org.eclipse.jdt.ls.core.internal.ResourceUtils;
4849
import org.eclipse.jdt.ls.core.internal.managers.ProjectsManager;
@@ -101,49 +102,44 @@ private List<ResolutionItem> resolveMainClassUnderPaths(List<IPath> parentPaths)
101102
// Limit to search main method from source code only.
102103
IJavaSearchScope searchScope = SearchEngine.createJavaSearchScope(ProjectUtils.getJavaProjects(),
103104
IJavaSearchScope.REFERENCED_PROJECTS | IJavaSearchScope.SOURCES);
104-
SearchPattern pattern = SearchPattern.createPattern("main(String[]) void", IJavaSearchConstants.METHOD,
105-
IJavaSearchConstants.DECLARATIONS, SearchPattern.R_CASE_SENSITIVE | SearchPattern.R_EXACT_MATCH);
105+
SearchPattern pattern = createMainMethodSearchPattern();
106106
final List<ResolutionItem> res = new ArrayList<>();
107107
SearchRequestor requestor = new SearchRequestor() {
108108
@Override
109109
public void acceptSearchMatch(SearchMatch match) {
110110
Object element = match.getElement();
111111
if (element instanceof IMethod) {
112112
IMethod method = (IMethod) element;
113-
try {
114-
if (method.isMainMethod()) {
115-
IResource resource = method.getResource();
116-
if (resource != null) {
117-
IProject project = resource.getProject();
118-
if (project != null) {
119-
String mainClass = method.getDeclaringType().getFullyQualifiedName();
120-
IJavaProject javaProject = JdtUtils.getJavaProject(project);
121-
if (javaProject != null) {
122-
String moduleName = JdtUtils.getModuleName(javaProject);
123-
if (moduleName != null) {
124-
mainClass = moduleName + "/" + mainClass;
125-
}
113+
if (isMainMethod(method)) {
114+
IResource resource = method.getResource();
115+
if (resource != null) {
116+
IProject project = resource.getProject();
117+
if (project != null) {
118+
String mainClass = method.getDeclaringType().getFullyQualifiedName();
119+
IJavaProject javaProject = JdtUtils.getJavaProject(project);
120+
if (javaProject != null) {
121+
String moduleName = JdtUtils.getModuleName(javaProject);
122+
if (moduleName != null) {
123+
mainClass = moduleName + "/" + mainClass;
126124
}
127-
String projectName = ProjectsManager.DEFAULT_PROJECT_NAME.equals(project.getName()) ? null : project.getName();
128-
if (parentPaths.isEmpty()
129-
|| ResourceUtils.isContainedIn(project.getLocation(), parentPaths)
130-
|| isContainedInInvisibleProject(project, parentPaths)) {
131-
String filePath = null;
132-
133-
if (match.getResource() instanceof IFile) {
134-
try {
135-
filePath = match.getResource().getLocation().toOSString();
136-
} catch (Exception ex) {
137-
// ignore
138-
}
125+
}
126+
String projectName = ProjectsManager.DEFAULT_PROJECT_NAME.equals(project.getName()) ? null : project.getName();
127+
if (parentPaths.isEmpty()
128+
|| ResourceUtils.isContainedIn(project.getLocation(), parentPaths)
129+
|| isContainedInInvisibleProject(project, parentPaths)) {
130+
String filePath = null;
131+
132+
if (match.getResource() instanceof IFile) {
133+
try {
134+
filePath = match.getResource().getLocation().toOSString();
135+
} catch (Exception ex) {
136+
// ignore
139137
}
140-
res.add(new ResolutionItem(mainClass, projectName, filePath));
141138
}
139+
res.add(new ResolutionItem(mainClass, projectName, filePath));
142140
}
143141
}
144142
}
145-
} catch (JavaModelException e) {
146-
// ignore
147143
}
148144
}
149145
}
@@ -166,44 +162,39 @@ private List<ResolutionItem> resolveMainClassUnderProject(final String projectNa
166162
IJavaProject javaProject = ProjectUtils.getJavaProject(projectName);
167163
IJavaSearchScope searchScope = SearchEngine.createJavaSearchScope(javaProject == null ? new IJavaProject[0] : new IJavaProject[] {javaProject},
168164
IJavaSearchScope.REFERENCED_PROJECTS | IJavaSearchScope.SOURCES);
169-
SearchPattern pattern = SearchPattern.createPattern("main(String[]) void", IJavaSearchConstants.METHOD,
170-
IJavaSearchConstants.DECLARATIONS, SearchPattern.R_CASE_SENSITIVE | SearchPattern.R_EXACT_MATCH);
165+
SearchPattern pattern = createMainMethodSearchPattern();
171166
final List<ResolutionItem> res = new ArrayList<>();
172167
SearchRequestor requestor = new SearchRequestor() {
173168
@Override
174169
public void acceptSearchMatch(SearchMatch match) {
175170
Object element = match.getElement();
176171
if (element instanceof IMethod) {
177172
IMethod method = (IMethod) element;
178-
try {
179-
if (method.isMainMethod()) {
180-
IResource resource = method.getResource();
181-
if (resource != null) {
182-
IProject project = resource.getProject();
183-
if (project != null) {
184-
String mainClass = method.getDeclaringType().getFullyQualifiedName();
185-
IJavaProject javaProject = JdtUtils.getJavaProject(project);
186-
if (javaProject != null) {
187-
String moduleName = JdtUtils.getModuleName(javaProject);
188-
if (moduleName != null) {
189-
mainClass = moduleName + "/" + mainClass;
190-
}
173+
if (isMainMethod(method)) {
174+
IResource resource = method.getResource();
175+
if (resource != null) {
176+
IProject project = resource.getProject();
177+
if (project != null) {
178+
String mainClass = method.getDeclaringType().getFullyQualifiedName();
179+
IJavaProject javaProject = JdtUtils.getJavaProject(project);
180+
if (javaProject != null) {
181+
String moduleName = JdtUtils.getModuleName(javaProject);
182+
if (moduleName != null) {
183+
mainClass = moduleName + "/" + mainClass;
191184
}
185+
}
192186

193-
String filePath = null;
194-
if (match.getResource() instanceof IFile) {
195-
try {
196-
filePath = match.getResource().getLocation().toOSString();
197-
} catch (Exception ex) {
198-
// ignore
199-
}
187+
String filePath = null;
188+
if (match.getResource() instanceof IFile) {
189+
try {
190+
filePath = match.getResource().getLocation().toOSString();
191+
} catch (Exception ex) {
192+
// ignore
200193
}
201-
res.add(new ResolutionItem(mainClass, projectName, filePath));
202194
}
195+
res.add(new ResolutionItem(mainClass, projectName, filePath));
203196
}
204197
}
205-
} catch (JavaModelException e) {
206-
// ignore
207198
}
208199
}
209200
}
@@ -221,6 +212,29 @@ public void acceptSearchMatch(SearchMatch match) {
221212
return resolutions;
222213
}
223214

215+
private SearchPattern createMainMethodSearchPattern() {
216+
SearchPattern pattern1 = SearchPattern.createPattern("main(String[]) void", IJavaSearchConstants.METHOD,
217+
IJavaSearchConstants.DECLARATIONS, SearchPattern.R_CASE_SENSITIVE | SearchPattern.R_EXACT_MATCH);
218+
SearchPattern pattern2 = SearchPattern.createPattern("main() void", IJavaSearchConstants.METHOD,
219+
IJavaSearchConstants.DECLARATIONS, SearchPattern.R_CASE_SENSITIVE | SearchPattern.R_EXACT_MATCH);
220+
return SearchPattern.createOrPattern(pattern1, pattern2);
221+
}
222+
223+
private boolean isMainMethod(IMethod method) {
224+
try {
225+
if (method instanceof SourceMethod
226+
&& ((SourceMethod) method).isMainMethodCandidate()) {
227+
return true;
228+
}
229+
230+
return method.isMainMethod();
231+
} catch (JavaModelException e) {
232+
// do nothing
233+
}
234+
235+
return false;
236+
}
237+
224238
private boolean isContainedInInvisibleProject(IProject project, Collection<IPath> rootPaths) {
225239
if (project == null) {
226240
return false;

com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/ResolveMainMethodHandler.java

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import java.util.Arrays;
1616
import java.util.Collections;
1717
import java.util.List;
18+
import java.util.Map;
1819

1920
import org.eclipse.core.resources.IProject;
2021
import org.eclipse.core.resources.IResource;
@@ -30,7 +31,11 @@
3031
import org.eclipse.jdt.core.ISourceRange;
3132
import org.eclipse.jdt.core.ISourceReference;
3233
import org.eclipse.jdt.core.IType;
34+
import org.eclipse.jdt.core.JavaCore;
3335
import org.eclipse.jdt.core.JavaModelException;
36+
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
37+
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
38+
import org.eclipse.jdt.internal.core.SourceMethod;
3439
import org.eclipse.jdt.ls.core.internal.JDTUtils;
3540
import org.eclipse.jdt.ls.core.internal.handlers.DocumentLifeCycleHandler;
3641
import org.eclipse.jdt.ls.core.internal.managers.ProjectsManager;
@@ -104,16 +109,58 @@ private static List<IMethod> searchMainMethods(ICompilationUnit compilationUnit)
104109
* Returns the main method defined in the type.
105110
*/
106111
public static IMethod getMainMethod(IType type) throws JavaModelException {
112+
boolean allowInstanceMethod = isInstanceMainMethodSupported(type);
113+
List<IMethod> methods = new ArrayList<>();
107114
for (IMethod method : type.getMethods()) {
108-
// Have at most one main method in the member methods of the type.
115+
if (method instanceof SourceMethod
116+
&& ((SourceMethod) method).isMainMethodCandidate()) {
117+
methods.add(method);
118+
}
119+
109120
if (method.isMainMethod()) {
110-
return method;
121+
methods.add(method);
122+
}
123+
124+
if (!allowInstanceMethod && !methods.isEmpty()) {
125+
return methods.get(0);
111126
}
112127
}
113128

129+
if (!methods.isEmpty()) {
130+
methods.sort((method1, method2) -> {
131+
return getMainMethodPriority(method1) - getMainMethodPriority(method2);
132+
});
133+
134+
return methods.get(0);
135+
}
136+
114137
return null;
115138
}
116139

140+
private static boolean isInstanceMainMethodSupported(IType type) {
141+
Map<String, String> options = type.getJavaProject().getOptions(true);
142+
return CompilerOptions.versionToJdkLevel(options.get(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM)) >= ClassFileConstants.JDK21;
143+
}
144+
145+
private static int getMainMethodPriority(IMethod method) {
146+
int flags = 0;
147+
try {
148+
flags = method.getFlags();
149+
} catch (JavaModelException e) {
150+
// do nothing
151+
}
152+
String[] params = method.getParameterTypes();
153+
if (Flags.isStatic(flags) && params.length == 1) {
154+
return 1;
155+
} else if (Flags.isStatic(flags)) {
156+
return 2;
157+
} else if (params.length == 1) {
158+
return 3;
159+
}
160+
161+
return 4;
162+
}
163+
117164
private static List<IType> getPotentialMainClassTypes(ICompilationUnit compilationUnit) throws JavaModelException {
118165
List<IType> result = new ArrayList<>();
119166
IType[] topLevelTypes = compilationUnit.getTypes();

com.microsoft.java.debug.target/com.microsoft.java.debug.tp.target

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,18 @@
1414
<unit id="org.eclipse.equinox.executable.feature.group" version="0.0.0"/>
1515
<unit id="org.eclipse.equinox.p2.core.feature.source.feature.group" version="0.0.0"/>
1616
<unit id="org.eclipse.equinox.sdk.feature.group" version="0.0.0"/>
17-
<unit id="org.eclipse.jdt.source.feature.group" version="0.0.0"/>
1817
<unit id="org.eclipse.jdt.apt.pluggable.core" version="0.0.0"/>
1918
<repository location="https://download.eclipse.org/eclipse/updates/4.31/"/>
2019
</location>
20+
<location includeAllPlatforms="false" includeConfigurePhase="false" includeMode="planner" includeSource="true" type="InstallableUnit">
21+
<unit id="org.eclipse.jdt.core.compiler.batch" version="0.0.0"/>
22+
<unit id="org.eclipse.jdt.core" version="0.0.0"/>
23+
<unit id="org.eclipse.jdt.apt.core" version="0.0.0"/>
24+
<repository location="https://download.eclipse.org/jdtls/jdt-core-incubator/snapshots/"/>
25+
</location>
2126
<location includeAllPlatforms="false" includeConfigurePhase="false" includeMode="planner" includeSource="true" type="InstallableUnit">
2227
<unit id="org.eclipse.xtext.sdk.feature.group" version="0.0.0"/>
23-
<repository location="https://download.eclipse.org/releases/2023-12/"/>
28+
<repository location="https://download.eclipse.org/releases/2024-03/"/>
2429
</location>
2530
<location includeAllPlatforms="false" includeConfigurePhase="false" includeMode="planner" includeSource="true" type="InstallableUnit">
2631
<unit id="org.jboss.tools.maven.apt.core" version="0.0.0"/>

0 commit comments

Comments
 (0)