Skip to content

Commit cef0200

Browse files
committed
perf: add info to track empty reason
1 parent 609dfd2 commit cef0200

File tree

3 files changed

+221
-37
lines changed

3 files changed

+221
-37
lines changed

jdtls.ext/com.microsoft.jdtls.ext.core/plugin.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
<command id="java.project.checkImportStatus" />
1313
<command id="java.project.getImportClassContent" />
1414
<command id="java.project.getDependencies" />
15+
<command id="java.project.getImportClassContentWithResult" />
16+
<command id="java.project.getProjectDependenciesWithResult" />
1517
</delegateCommandHandler>
1618
</extension>
1719
<extension

jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/CommandHandler.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ public Object executeCommand(String commandId, List<Object> arguments, IProgress
4141
return ProjectCommand.getImportClassContent(arguments, monitor);
4242
case "java.project.getDependencies":
4343
return ProjectCommand.getProjectDependencies(arguments, monitor);
44+
case "java.project.getImportClassContentWithResult":
45+
return ProjectCommand.getImportClassContentWithResult(arguments, monitor);
46+
case "java.project.getProjectDependenciesWithResult":
47+
return ProjectCommand.getProjectDependenciesWithResult(arguments, monitor);
4448
default:
4549
break;
4650
}

jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java

Lines changed: 215 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,101 @@ public DependencyInfo(String key, String value) {
9898
}
9999
}
100100

101+
/**
102+
* Empty reasons for ImportClassContent operation
103+
*/
104+
public enum ImportClassContentErrorReason {
105+
NULL_ARGUMENTS("NullArgs"),
106+
INVALID_URI("InvalidUri"),
107+
URI_PARSE_FAILED("UriParseFail"),
108+
FILE_NOT_FOUND("FileNotFound"),
109+
FILE_NOT_EXISTS("FileNotExists"),
110+
NOT_JAVA_PROJECT("NotJavaProject"),
111+
PROJECT_NOT_EXISTS("ProjectNotExists"),
112+
NOT_COMPILATION_UNIT("NotCompilationUnit"),
113+
NO_IMPORTS("NoImports"),
114+
OPERATION_CANCELLED("Cancelled"),
115+
TIME_LIMIT_EXCEEDED("Timeout"),
116+
NO_RESULTS("NoResults"),
117+
PROCESSING_EXCEPTION("ProcessingError");
118+
119+
private final String message;
120+
121+
ImportClassContentErrorReason(String message) {
122+
this.message = message;
123+
}
124+
125+
public String getMessage() {
126+
return message;
127+
}
128+
}
129+
130+
/**
131+
* Empty reasons for ProjectDependencies operation
132+
*/
133+
public enum ProjectDependenciesErrorReason {
134+
NULL_ARGUMENTS("NullArgs"),
135+
INVALID_URI("InvalidUri"),
136+
URI_PARSE_FAILED("UriParseFail"),
137+
MALFORMED_URI("MalformedUri"),
138+
OPERATION_CANCELLED("Cancelled"),
139+
RESOLVER_NULL_RESULT("ResolverNull"),
140+
NO_DEPENDENCIES("NoDependencies"),
141+
PROCESSING_EXCEPTION("ProcessingError");
142+
143+
private final String message;
144+
145+
ProjectDependenciesErrorReason(String message) {
146+
this.message = message;
147+
}
148+
149+
public String getMessage() {
150+
return message;
151+
}
152+
}
153+
154+
/**
155+
* Result wrapper for getImportClassContent method
156+
*/
157+
public static class ImportClassContentResult {
158+
public List<ImportClassInfo> classInfoList;
159+
public String emptyReason; // Reason why the result is empty
160+
public boolean isEmpty;
161+
162+
public ImportClassContentResult(List<ImportClassInfo> classInfoList) {
163+
this.classInfoList = classInfoList;
164+
this.emptyReason = null;
165+
this.isEmpty = false;
166+
}
167+
168+
public ImportClassContentResult(ImportClassContentErrorReason errorReason) {
169+
this.classInfoList = Collections.emptyList();
170+
this.emptyReason = errorReason.getMessage(); // Use enum message
171+
this.isEmpty = true;
172+
}
173+
}
174+
175+
/**
176+
* Result wrapper for getProjectDependencies method
177+
*/
178+
public static class ProjectDependenciesResult {
179+
public List<DependencyInfo> dependencyInfoList;
180+
public String emptyReason; // Reason why the result is empty
181+
public boolean isEmpty;
182+
183+
public ProjectDependenciesResult(List<DependencyInfo> dependencyInfoList) {
184+
this.dependencyInfoList = dependencyInfoList;
185+
this.emptyReason = null;
186+
this.isEmpty = false;
187+
}
188+
189+
public ProjectDependenciesResult(ProjectDependenciesErrorReason errorReason) {
190+
this.dependencyInfoList = new ArrayList<>();
191+
this.emptyReason = errorReason.getMessage(); // Use enum message
192+
this.isEmpty = true;
193+
}
194+
}
195+
101196
private static class Classpath {
102197
public String source;
103198
public String destination;
@@ -350,18 +445,38 @@ public static boolean checkImportStatus() {
350445
return hasError;
351446
}
352447

448+
/**
449+
* Get import class content for Copilot integration (backward compatibility
450+
* wrapper).
451+
* This method maintains compatibility with the original return type.
452+
*
453+
* @param arguments List containing the file URI as the first element
454+
* @param monitor Progress monitor for cancellation support
455+
* @return List of ImportClassInfo containing class information and JavaDoc
456+
*/
457+
public static List<ImportClassInfo> getImportClassContent(List<Object> arguments, IProgressMonitor monitor) {
458+
ImportClassContentResult result = getImportClassContentWithResult(arguments, monitor);
459+
if (result.isEmpty) {
460+
// Log the error reason for debugging
461+
JdtlsExtActivator.logError("getImportClassContent failed: " + result.emptyReason);
462+
}
463+
return result.classInfoList;
464+
}
465+
353466
/**
354467
* Get import class content for Copilot integration.
355468
* This method extracts information about imported classes from a Java file.
356-
* Uses a time-controlled strategy: prioritizes internal classes, adds external classes only if time permits.
469+
* Uses a time-controlled strategy: prioritizes internal classes, adds external
470+
* classes only if time permits.
357471
*
358472
* @param arguments List containing the file URI as the first element
359-
* @param monitor Progress monitor for cancellation support
473+
* @param monitor Progress monitor for cancellation support
360474
* @return List of ImportClassInfo containing class information and JavaDoc
361475
*/
362-
public static List<ImportClassInfo> getImportClassContent(List<Object> arguments, IProgressMonitor monitor) {
476+
public static ImportClassContentResult getImportClassContentWithResult(List<Object> arguments,
477+
IProgressMonitor monitor) {
363478
if (arguments == null || arguments.isEmpty()) {
364-
return Collections.emptyList();
479+
return new ImportClassContentResult(ImportClassContentErrorReason.NULL_ARGUMENTS);
365480
}
366481

367482
// Time control: total budget 80ms, early return at 75ms
@@ -371,12 +486,14 @@ public static List<ImportClassInfo> getImportClassContent(List<Object> arguments
371486

372487
try {
373488
String fileUri = (String) arguments.get(0);
374-
489+
if (fileUri == null || fileUri.trim().isEmpty()) {
490+
return new ImportClassContentResult(ImportClassContentErrorReason.INVALID_URI);
491+
}
375492
// Parse URI manually to avoid restricted API
376493
java.net.URI uri = new java.net.URI(fileUri);
377494
String filePath = uri.getPath();
378495
if (filePath == null) {
379-
return Collections.emptyList();
496+
return new ImportClassContentResult(ImportClassContentErrorReason.URI_PARSE_FAILED);
380497
}
381498

382499
IPath path = new Path(filePath);
@@ -385,19 +502,25 @@ public static List<ImportClassInfo> getImportClassContent(List<Object> arguments
385502
IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
386503
IFile file = root.getFileForLocation(path);
387504
if (file == null || !file.exists()) {
388-
return Collections.emptyList();
505+
return new ImportClassContentResult(ImportClassContentErrorReason.FILE_NOT_FOUND);
506+
}
507+
if (!file.exists()) {
508+
return new ImportClassContentResult(ImportClassContentErrorReason.FILE_NOT_EXISTS);
389509
}
390510

391511
// Get the Java project
392512
IJavaProject javaProject = JavaCore.create(file.getProject());
393-
if (javaProject == null || !javaProject.exists()) {
394-
return Collections.emptyList();
513+
if (javaProject == null) {
514+
return new ImportClassContentResult(ImportClassContentErrorReason.NOT_JAVA_PROJECT);
515+
}
516+
if (!javaProject.exists()) {
517+
return new ImportClassContentResult(ImportClassContentErrorReason.PROJECT_NOT_EXISTS);
395518
}
396519

397520
// Find the compilation unit
398521
IJavaElement javaElement = JavaCore.create(file);
399522
if (!(javaElement instanceof org.eclipse.jdt.core.ICompilationUnit)) {
400-
return Collections.emptyList();
523+
return new ImportClassContentResult(ImportClassContentErrorReason.NOT_COMPILATION_UNIT);
401524
}
402525

403526
org.eclipse.jdt.core.ICompilationUnit compilationUnit = (org.eclipse.jdt.core.ICompilationUnit) javaElement;
@@ -409,24 +532,34 @@ public static List<ImportClassInfo> getImportClassContent(List<Object> arguments
409532
org.eclipse.jdt.core.IImportDeclaration[] imports = compilationUnit.getImports();
410533
Set<String> processedTypes = new HashSet<>();
411534

535+
// Check if file has no imports
536+
if (imports == null || imports.length == 0) {
537+
return new ImportClassContentResult(ImportClassContentErrorReason.NO_IMPORTS);
538+
}
539+
412540
// Phase 1: Priority - Resolve project source classes (internal)
413541
for (org.eclipse.jdt.core.IImportDeclaration importDecl : imports) {
414542
// Check time budget before each operation
415543
long elapsed = System.currentTimeMillis() - startTime;
416-
if (monitor.isCanceled() || elapsed >= EARLY_RETURN_MS) {
417-
return classInfoList; // Early return if approaching time limit
544+
if (monitor.isCanceled()) {
545+
return new ImportClassContentResult(ImportClassContentErrorReason.OPERATION_CANCELLED);
546+
}
547+
if (elapsed >= EARLY_RETURN_MS) {
548+
return new ImportClassContentResult(ImportClassContentErrorReason.TIME_LIMIT_EXCEEDED);
418549
}
419550

420551
String importName = importDecl.getElementName();
421552
boolean isStatic = (importDecl.getFlags() & org.eclipse.jdt.core.Flags.AccStatic) != 0;
422-
553+
423554
if (isStatic) {
424555
// Handle static imports - delegate to ContextResolver
425-
ContextResolver.resolveStaticImport(javaProject, importName, classInfoList, processedTypes, monitor);
556+
ContextResolver.resolveStaticImport(javaProject, importName, classInfoList, processedTypes,
557+
monitor);
426558
} else if (importName.endsWith(".*")) {
427559
// Handle package imports - delegate to ContextResolver
428560
String packageName = importName.substring(0, importName.length() - 2);
429-
ContextResolver.resolvePackageTypes(javaProject, packageName, classInfoList, processedTypes, monitor);
561+
ContextResolver.resolvePackageTypes(javaProject, packageName, classInfoList, processedTypes,
562+
monitor);
430563
} else {
431564
// Handle single type imports - delegate to ContextResolver
432565
ContextResolver.resolveSingleType(javaProject, importName, classInfoList, processedTypes, monitor);
@@ -438,11 +571,11 @@ public static List<ImportClassInfo> getImportClassContent(List<Object> arguments
438571
if (elapsedAfterInternal < EARLY_RETURN_MS && !monitor.isCanceled()) {
439572
// Calculate remaining time budget for external classes
440573
long remainingTime = TIME_BUDGET_MS - elapsedAfterInternal;
441-
574+
442575
// Only proceed with external if we have reasonable time left (at least 15ms)
443576
if (remainingTime >= 15) {
444577
List<ImportClassInfo> externalClasses = new ArrayList<>();
445-
578+
446579
for (org.eclipse.jdt.core.IImportDeclaration importDecl : imports) {
447580
// Check time before each external resolution
448581
long currentElapsed = System.currentTimeMillis() - startTime;
@@ -452,29 +585,32 @@ public static List<ImportClassInfo> getImportClassContent(List<Object> arguments
452585

453586
String importName = importDecl.getElementName();
454587
boolean isStatic = (importDecl.getFlags() & org.eclipse.jdt.core.Flags.AccStatic) != 0;
455-
588+
456589
// Skip package imports (*.* ) - too broad for external dependencies
457590
if (importName.endsWith(".*")) {
458591
continue;
459592
}
460-
593+
461594
// Resolve external (binary) types with simplified content
462595
if (!isStatic) {
463-
ContextResolver.resolveBinaryType(javaProject, importName, externalClasses,
596+
ContextResolver.resolveBinaryType(javaProject, importName, externalClasses,
464597
processedTypes, Integer.MAX_VALUE, monitor);
465598
}
466599
}
467-
600+
468601
// Append external classes after project sources
469602
classInfoList.addAll(externalClasses);
470603
}
471604
}
472-
473-
return classInfoList;
605+
// Success case - return the resolved class information
606+
if (classInfoList.isEmpty()) {
607+
return new ImportClassContentResult(ImportClassContentErrorReason.NO_RESULTS);
608+
}
609+
return new ImportClassContentResult(classInfoList);
474610

475611
} catch (Exception e) {
476612
JdtlsExtActivator.logException("Error in getImportClassContent", e);
477-
return Collections.emptyList();
613+
return new ImportClassContentResult(ImportClassContentErrorReason.PROCESSING_EXCEPTION);
478614
}
479615
}
480616

@@ -503,28 +639,70 @@ private static String getSeverityString(int severity) {
503639
}
504640
}
505641

642+
public static List<DependencyInfo> getProjectDependencies(List<Object> arguments, IProgressMonitor monitor) {
643+
ProjectDependenciesResult result = getProjectDependenciesWithResult(arguments, monitor);
644+
return result == null ? Collections.emptyList() : result.dependencyInfoList;
645+
}
646+
506647
/**
507648
* Get project dependencies information including JDK version.
508649
*
509650
* @param arguments List containing the project URI as the first element
510-
* @param monitor Progress monitor for cancellation support
511-
* @return List of DependencyInfo containing key-value pairs of project information
651+
* @param monitor Progress monitor for cancellation support
652+
* @return List of DependencyInfo containing key-value pairs of project
653+
* information
512654
*/
513-
public static List<DependencyInfo> getProjectDependencies(List<Object> arguments, IProgressMonitor monitor) {
655+
public static ProjectDependenciesResult getProjectDependenciesWithResult(List<Object> arguments,
656+
IProgressMonitor monitor) {
514657
if (arguments == null || arguments.isEmpty()) {
515-
return new ArrayList<>();
658+
return new ProjectDependenciesResult(ProjectDependenciesErrorReason.NULL_ARGUMENTS);
516659
}
517660

518-
String projectUri = (String) arguments.get(0);
519-
List<ProjectResolver.DependencyInfo> resolverResult = ProjectResolver.resolveProjectDependencies(projectUri, monitor);
520-
521-
// Convert ProjectResolver.DependencyInfo to ProjectCommand.DependencyInfo
522-
List<DependencyInfo> result = new ArrayList<>();
523-
for (ProjectResolver.DependencyInfo info : resolverResult) {
524-
result.add(new DependencyInfo(info.key, info.value));
661+
try {
662+
String projectUri = (String) arguments.get(0);
663+
if (projectUri == null || projectUri.trim().isEmpty()) {
664+
return new ProjectDependenciesResult(ProjectDependenciesErrorReason.INVALID_URI);
665+
}
666+
667+
// Validate URI format
668+
try {
669+
java.net.URI uri = new java.net.URI(projectUri);
670+
if (uri.getPath() == null) {
671+
return new ProjectDependenciesResult(ProjectDependenciesErrorReason.URI_PARSE_FAILED);
672+
}
673+
} catch (java.net.URISyntaxException e) {
674+
return new ProjectDependenciesResult(ProjectDependenciesErrorReason.MALFORMED_URI);
675+
}
676+
677+
// Check if monitor is cancelled before processing
678+
if (monitor.isCanceled()) {
679+
return new ProjectDependenciesResult(ProjectDependenciesErrorReason.OPERATION_CANCELLED);
680+
}
681+
List<ProjectResolver.DependencyInfo> resolverResult = ProjectResolver.resolveProjectDependencies(projectUri,
682+
monitor);
683+
// Check if resolver returned null (should not happen, but defensive
684+
// programming)
685+
if (resolverResult == null) {
686+
return new ProjectDependenciesResult(ProjectDependenciesErrorReason.RESOLVER_NULL_RESULT);
687+
}
688+
// Convert ProjectResolver.DependencyInfo to ProjectCommand.DependencyInfo
689+
List<DependencyInfo> result = new ArrayList<>();
690+
for (ProjectResolver.DependencyInfo info : resolverResult) {
691+
if (info != null) {
692+
result.add(new DependencyInfo(info.key, info.value));
693+
}
694+
}
695+
696+
// Check if no dependencies were resolved
697+
if (result.isEmpty()) {
698+
return new ProjectDependenciesResult(ProjectDependenciesErrorReason.NO_DEPENDENCIES);
699+
}
700+
701+
return new ProjectDependenciesResult(result);
702+
} catch (Exception e) {
703+
JdtlsExtActivator.logException("Error in getProjectDependenciesWithReason", e);
704+
return new ProjectDependenciesResult(ProjectDependenciesErrorReason.PROCESSING_EXCEPTION);
525705
}
526-
527-
return result;
528706
}
529707

530708
private static final class LinkedFolderVisitor implements IResourceVisitor {

0 commit comments

Comments
 (0)