Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 2 additions & 0 deletions jdtls.ext/com.microsoft.jdtls.ext.core/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
<command id="java.project.checkImportStatus" />
<command id="java.project.getImportClassContent" />
<command id="java.project.getDependencies" />
<command id="java.project.getImportClassContentWithResult" />
<command id="java.project.getProjectDependenciesWithResult" />
</delegateCommandHandler>
</extension>
<extension
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ public Object executeCommand(String commandId, List<Object> arguments, IProgress
return ProjectCommand.getImportClassContent(arguments, monitor);
case "java.project.getDependencies":
return ProjectCommand.getProjectDependencies(arguments, monitor);
case "java.project.getImportClassContentWithResult":
return ProjectCommand.getImportClassContentWithResult(arguments, monitor);
case "java.project.getProjectDependenciesWithResult":
return ProjectCommand.getProjectDependenciesWithResult(arguments, monitor);
default:
break;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,101 @@ public DependencyInfo(String key, String value) {
}
}

/**
* Empty reasons for ImportClassContent operation
*/
public enum ImportClassContentErrorReason {
NULL_ARGUMENTS("NullArgs"),
INVALID_URI("InvalidUri"),
URI_PARSE_FAILED("UriParseFail"),
FILE_NOT_FOUND("FileNotFound"),
FILE_NOT_EXISTS("FileNotExists"),
NOT_JAVA_PROJECT("NotJavaProject"),
PROJECT_NOT_EXISTS("ProjectNotExists"),
NOT_COMPILATION_UNIT("NotCompilationUnit"),
NO_IMPORTS("NoImports"),
OPERATION_CANCELLED("Cancelled"),
TIME_LIMIT_EXCEEDED("Timeout"),
NO_RESULTS("NoResults"),
PROCESSING_EXCEPTION("ProcessingError");

private final String message;

ImportClassContentErrorReason(String message) {
this.message = message;
}

public String getMessage() {
return message;
}
}

/**
* Empty reasons for ProjectDependencies operation
*/
public enum ProjectDependenciesErrorReason {
NULL_ARGUMENTS("NullArgs"),
INVALID_URI("InvalidUri"),
URI_PARSE_FAILED("UriParseFail"),
MALFORMED_URI("MalformedUri"),
OPERATION_CANCELLED("Cancelled"),
RESOLVER_NULL_RESULT("ResolverNull"),
NO_DEPENDENCIES("NoDependencies"),
PROCESSING_EXCEPTION("ProcessingError");

private final String message;

ProjectDependenciesErrorReason(String message) {
this.message = message;
}

public String getMessage() {
return message;
}
}

/**
* Result wrapper for getImportClassContent method
*/
public static class ImportClassContentResult {
public List<ImportClassInfo> classInfoList;
public String emptyReason; // Reason why the result is empty
public boolean isEmpty;

public ImportClassContentResult(List<ImportClassInfo> classInfoList) {
this.classInfoList = classInfoList;
this.emptyReason = null;
this.isEmpty = false;
}

public ImportClassContentResult(ImportClassContentErrorReason errorReason) {
this.classInfoList = Collections.emptyList();
this.emptyReason = errorReason.getMessage(); // Use enum message
this.isEmpty = true;
}
}

/**
* Result wrapper for getProjectDependencies method
*/
public static class ProjectDependenciesResult {
public List<DependencyInfo> dependencyInfoList;
public String emptyReason; // Reason why the result is empty
public boolean isEmpty;

public ProjectDependenciesResult(List<DependencyInfo> dependencyInfoList) {
this.dependencyInfoList = dependencyInfoList;
this.emptyReason = null;
this.isEmpty = false;
}

public ProjectDependenciesResult(ProjectDependenciesErrorReason errorReason) {
this.dependencyInfoList = new ArrayList<>();
this.emptyReason = errorReason.getMessage(); // Use enum message
this.isEmpty = true;
}
}

private static class Classpath {
public String source;
public String destination;
Expand Down Expand Up @@ -350,18 +445,38 @@ public static boolean checkImportStatus() {
return hasError;
}

/**
* Get import class content for Copilot integration (backward compatibility
* wrapper).
* This method maintains compatibility with the original return type.
*
* @param arguments List containing the file URI as the first element
* @param monitor Progress monitor for cancellation support
* @return List of ImportClassInfo containing class information and JavaDoc
*/
public static List<ImportClassInfo> getImportClassContent(List<Object> arguments, IProgressMonitor monitor) {
ImportClassContentResult result = getImportClassContentWithResult(arguments, monitor);
if (result.isEmpty) {
// Log the error reason for debugging
JdtlsExtActivator.logError("getImportClassContent failed: " + result.emptyReason);
}
return result.classInfoList;
}

/**
* Get import class content for Copilot integration.
* This method extracts information about imported classes from a Java file.
* Uses a time-controlled strategy: prioritizes internal classes, adds external classes only if time permits.
* Uses a time-controlled strategy: prioritizes internal classes, adds external
* classes only if time permits.
*
* @param arguments List containing the file URI as the first element
* @param monitor Progress monitor for cancellation support
* @param monitor Progress monitor for cancellation support
* @return List of ImportClassInfo containing class information and JavaDoc
*/
public static List<ImportClassInfo> getImportClassContent(List<Object> arguments, IProgressMonitor monitor) {
public static ImportClassContentResult getImportClassContentWithResult(List<Object> arguments,
IProgressMonitor monitor) {
if (arguments == null || arguments.isEmpty()) {
return Collections.emptyList();
return new ImportClassContentResult(ImportClassContentErrorReason.NULL_ARGUMENTS);
}

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

try {
String fileUri = (String) arguments.get(0);

if (fileUri == null || fileUri.trim().isEmpty()) {
return new ImportClassContentResult(ImportClassContentErrorReason.INVALID_URI);
}
// Parse URI manually to avoid restricted API
java.net.URI uri = new java.net.URI(fileUri);
String filePath = uri.getPath();
if (filePath == null) {
return Collections.emptyList();
return new ImportClassContentResult(ImportClassContentErrorReason.URI_PARSE_FAILED);
}

IPath path = new Path(filePath);
Expand All @@ -385,19 +502,25 @@ public static List<ImportClassInfo> getImportClassContent(List<Object> arguments
IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
IFile file = root.getFileForLocation(path);
if (file == null || !file.exists()) {
return Collections.emptyList();
return new ImportClassContentResult(ImportClassContentErrorReason.FILE_NOT_FOUND);
}
if (!file.exists()) {
return new ImportClassContentResult(ImportClassContentErrorReason.FILE_NOT_EXISTS);
}

// Get the Java project
IJavaProject javaProject = JavaCore.create(file.getProject());
if (javaProject == null || !javaProject.exists()) {
return Collections.emptyList();
if (javaProject == null) {
return new ImportClassContentResult(ImportClassContentErrorReason.NOT_JAVA_PROJECT);
}
if (!javaProject.exists()) {
return new ImportClassContentResult(ImportClassContentErrorReason.PROJECT_NOT_EXISTS);
}

// Find the compilation unit
IJavaElement javaElement = JavaCore.create(file);
if (!(javaElement instanceof org.eclipse.jdt.core.ICompilationUnit)) {
return Collections.emptyList();
return new ImportClassContentResult(ImportClassContentErrorReason.NOT_COMPILATION_UNIT);
}

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

// Check if file has no imports
if (imports == null || imports.length == 0) {
return new ImportClassContentResult(ImportClassContentErrorReason.NO_IMPORTS);
}

// Phase 1: Priority - Resolve project source classes (internal)
for (org.eclipse.jdt.core.IImportDeclaration importDecl : imports) {
// Check time budget before each operation
long elapsed = System.currentTimeMillis() - startTime;
if (monitor.isCanceled() || elapsed >= EARLY_RETURN_MS) {
return classInfoList; // Early return if approaching time limit
if (monitor.isCanceled()) {
return new ImportClassContentResult(ImportClassContentErrorReason.OPERATION_CANCELLED);
}
if (elapsed >= EARLY_RETURN_MS) {
return new ImportClassContentResult(ImportClassContentErrorReason.TIME_LIMIT_EXCEEDED);
}

String importName = importDecl.getElementName();
boolean isStatic = (importDecl.getFlags() & org.eclipse.jdt.core.Flags.AccStatic) != 0;

if (isStatic) {
// Handle static imports - delegate to ContextResolver
ContextResolver.resolveStaticImport(javaProject, importName, classInfoList, processedTypes, monitor);
ContextResolver.resolveStaticImport(javaProject, importName, classInfoList, processedTypes,
monitor);
} else if (importName.endsWith(".*")) {
// Handle package imports - delegate to ContextResolver
String packageName = importName.substring(0, importName.length() - 2);
ContextResolver.resolvePackageTypes(javaProject, packageName, classInfoList, processedTypes, monitor);
ContextResolver.resolvePackageTypes(javaProject, packageName, classInfoList, processedTypes,
monitor);
} else {
// Handle single type imports - delegate to ContextResolver
ContextResolver.resolveSingleType(javaProject, importName, classInfoList, processedTypes, monitor);
Expand All @@ -438,11 +571,11 @@ public static List<ImportClassInfo> getImportClassContent(List<Object> arguments
if (elapsedAfterInternal < EARLY_RETURN_MS && !monitor.isCanceled()) {
// Calculate remaining time budget for external classes
long remainingTime = TIME_BUDGET_MS - elapsedAfterInternal;

// Only proceed with external if we have reasonable time left (at least 15ms)
if (remainingTime >= 15) {
List<ImportClassInfo> externalClasses = new ArrayList<>();

for (org.eclipse.jdt.core.IImportDeclaration importDecl : imports) {
// Check time before each external resolution
long currentElapsed = System.currentTimeMillis() - startTime;
Expand All @@ -452,29 +585,32 @@ public static List<ImportClassInfo> getImportClassContent(List<Object> arguments

String importName = importDecl.getElementName();
boolean isStatic = (importDecl.getFlags() & org.eclipse.jdt.core.Flags.AccStatic) != 0;

// Skip package imports (*.* ) - too broad for external dependencies
if (importName.endsWith(".*")) {
continue;
}

// Resolve external (binary) types with simplified content
if (!isStatic) {
ContextResolver.resolveBinaryType(javaProject, importName, externalClasses,
ContextResolver.resolveBinaryType(javaProject, importName, externalClasses,
processedTypes, Integer.MAX_VALUE, monitor);
}
}

// Append external classes after project sources
classInfoList.addAll(externalClasses);
}
}

return classInfoList;
// Success case - return the resolved class information
if (classInfoList.isEmpty()) {
return new ImportClassContentResult(ImportClassContentErrorReason.NO_RESULTS);
}
return new ImportClassContentResult(classInfoList);

} catch (Exception e) {
JdtlsExtActivator.logException("Error in getImportClassContent", e);
return Collections.emptyList();
return new ImportClassContentResult(ImportClassContentErrorReason.PROCESSING_EXCEPTION);
}
}

Expand Down Expand Up @@ -503,28 +639,70 @@ private static String getSeverityString(int severity) {
}
}

public static List<DependencyInfo> getProjectDependencies(List<Object> arguments, IProgressMonitor monitor) {
ProjectDependenciesResult result = getProjectDependenciesWithResult(arguments, monitor);
return result == null ? Collections.emptyList() : result.dependencyInfoList;
}

/**
* Get project dependencies information including JDK version.
*
* @param arguments List containing the project URI as the first element
* @param monitor Progress monitor for cancellation support
* @return List of DependencyInfo containing key-value pairs of project information
* @param monitor Progress monitor for cancellation support
* @return List of DependencyInfo containing key-value pairs of project
* information
*/
public static List<DependencyInfo> getProjectDependencies(List<Object> arguments, IProgressMonitor monitor) {
public static ProjectDependenciesResult getProjectDependenciesWithResult(List<Object> arguments,
IProgressMonitor monitor) {
if (arguments == null || arguments.isEmpty()) {
return new ArrayList<>();
return new ProjectDependenciesResult(ProjectDependenciesErrorReason.NULL_ARGUMENTS);
}

String projectUri = (String) arguments.get(0);
List<ProjectResolver.DependencyInfo> resolverResult = ProjectResolver.resolveProjectDependencies(projectUri, monitor);

// Convert ProjectResolver.DependencyInfo to ProjectCommand.DependencyInfo
List<DependencyInfo> result = new ArrayList<>();
for (ProjectResolver.DependencyInfo info : resolverResult) {
result.add(new DependencyInfo(info.key, info.value));
try {
String projectUri = (String) arguments.get(0);
if (projectUri == null || projectUri.trim().isEmpty()) {
return new ProjectDependenciesResult(ProjectDependenciesErrorReason.INVALID_URI);
}

// Validate URI format
try {
java.net.URI uri = new java.net.URI(projectUri);
if (uri.getPath() == null) {
return new ProjectDependenciesResult(ProjectDependenciesErrorReason.URI_PARSE_FAILED);
}
} catch (java.net.URISyntaxException e) {
return new ProjectDependenciesResult(ProjectDependenciesErrorReason.MALFORMED_URI);
}

// Check if monitor is cancelled before processing
if (monitor.isCanceled()) {
return new ProjectDependenciesResult(ProjectDependenciesErrorReason.OPERATION_CANCELLED);
}
List<ProjectResolver.DependencyInfo> resolverResult = ProjectResolver.resolveProjectDependencies(projectUri,
monitor);
// Check if resolver returned null (should not happen, but defensive
// programming)
if (resolverResult == null) {
return new ProjectDependenciesResult(ProjectDependenciesErrorReason.RESOLVER_NULL_RESULT);
}
// Convert ProjectResolver.DependencyInfo to ProjectCommand.DependencyInfo
List<DependencyInfo> result = new ArrayList<>();
for (ProjectResolver.DependencyInfo info : resolverResult) {
if (info != null) {
result.add(new DependencyInfo(info.key, info.value));
}
}

// Check if no dependencies were resolved
if (result.isEmpty()) {
return new ProjectDependenciesResult(ProjectDependenciesErrorReason.NO_DEPENDENCIES);
}

return new ProjectDependenciesResult(result);
} catch (Exception e) {
JdtlsExtActivator.logException("Error in getProjectDependenciesWithReason", e);
return new ProjectDependenciesResult(ProjectDependenciesErrorReason.PROCESSING_EXCEPTION);
}

return result;
}

private static final class LinkedFolderVisitor implements IResourceVisitor {
Expand Down
Loading