From 3ba032a49211ef0c5ca1d37230e036362a09421c Mon Sep 17 00:00:00 2001 From: wenyutang Date: Thu, 18 Sep 2025 15:41:22 +0800 Subject: [PATCH 1/7] feat: add getImportClassContent method to collect import class info --- .../com.microsoft.jdtls.ext.core/plugin.xml | 1 + .../jdtls/ext/core/CommandHandler.java | 2 + .../jdtls/ext/core/ProjectCommand.java | 344 +++++++++++++++++- 3 files changed, 333 insertions(+), 14 deletions(-) diff --git a/jdtls.ext/com.microsoft.jdtls.ext.core/plugin.xml b/jdtls.ext/com.microsoft.jdtls.ext.core/plugin.xml index 0352e983..6643c1a7 100644 --- a/jdtls.ext/com.microsoft.jdtls.ext.core/plugin.xml +++ b/jdtls.ext/com.microsoft.jdtls.ext.core/plugin.xml @@ -10,6 +10,7 @@ + arguments, IProgress return ProjectCommand.exportJar(arguments, monitor); case "java.project.checkImportStatus": return ProjectCommand.checkImportStatus(); + case "java.project.getImportClassContent": + return ProjectCommand.getImportClassContent(arguments, monitor); default: break; } diff --git a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java index 3f10d3e6..f7e15541 100644 --- a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java +++ b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java @@ -42,7 +42,6 @@ import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.MultiStatus; -import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.core.runtime.Path; import org.eclipse.jdt.core.IJavaElement; @@ -66,7 +65,6 @@ import org.eclipse.jdt.ls.core.internal.managers.ProjectsManager; import org.eclipse.jdt.ls.core.internal.managers.UpdateClasspathJob; import org.eclipse.jdt.ls.core.internal.preferences.Preferences.ReferencedLibraries; -import org.eclipse.jdt.ls.core.internal.preferences.Preferences.SearchScope; import org.eclipse.lsp4j.jsonrpc.json.adapters.CollectionTypeAdapter; import org.eclipse.lsp4j.jsonrpc.json.adapters.EnumTypeAdapter; @@ -88,6 +86,16 @@ public MainClassInfo(String name, String path) { } } + private static class ImportClassInfo { + public String uri; + public String className; + + public ImportClassInfo(String uri, String className) { + this.uri = uri; + this.className = className; + } + } + private static class Classpath { public String source; public String destination; @@ -110,7 +118,7 @@ public static List listProjects(List arguments, IProgressMo projects = ProjectUtils.getAllProjects(); } else { projects = Arrays.stream(ProjectUtils.getJavaProjects()) - .map(IJavaProject::getProject).toArray(IProject[]::new); + .map(IJavaProject::getProject).toArray(IProject[]::new); } ArrayList children = new ArrayList<>(); @@ -202,11 +210,14 @@ private static boolean exportJarExecution(String mainClass, Classpath[] classpat } if (classpath.isArtifact) { MultiStatus resultStatus = writeArchive(new ZipFile(classpath.source), - /* areDirectoryEntriesIncluded = */true, /* isCompressed = */true, target, directories, monitor); + /* areDirectoryEntriesIncluded = */true, /* isCompressed = */true, target, directories, + monitor); int severity = resultStatus.getSeverity(); if (severity == IStatus.OK) { java.nio.file.Path path = java.nio.file.Paths.get(classpath.source); - reportExportJarMessage(terminalId, IStatus.OK, "Successfully extracted the file to the exported jar: " + path.getFileName().toString()); + reportExportJarMessage(terminalId, IStatus.OK, + "Successfully extracted the file to the exported jar: " + + path.getFileName().toString()); continue; } if (resultStatus.isMultiStatus()) { @@ -218,9 +229,13 @@ private static boolean exportJarExecution(String mainClass, Classpath[] classpat } } else { try { - writeFile(new File(classpath.source), new Path(classpath.destination), /* areDirectoryEntriesIncluded = */true, - /* isCompressed = */true, target, directories); - reportExportJarMessage(terminalId, IStatus.OK, "Successfully added the file to the exported jar: " + classpath.destination); + writeFile(new File(classpath.source), new Path(classpath.destination), /* + * areDirectoryEntriesIncluded + * = + */true, + /* isCompressed = */true, target, directories); + reportExportJarMessage(terminalId, IStatus.OK, + "Successfully added the file to the exported jar: " + classpath.destination); } catch (CoreException e) { reportExportJarMessage(terminalId, IStatus.ERROR, e.getMessage()); } @@ -233,7 +248,8 @@ private static boolean exportJarExecution(String mainClass, Classpath[] classpat return true; } - public static List getMainClasses(List arguments, IProgressMonitor monitor) throws Exception { + public static List getMainClasses(List arguments, IProgressMonitor monitor) + throws Exception { List args = new ArrayList<>(arguments); if (args.size() <= 1) { args.add(Boolean.TRUE); @@ -246,7 +262,7 @@ public static List getMainClasses(List arguments, IProgre } final List res = new ArrayList<>(); List javaProjects = new ArrayList<>(); - for (PackageNode project: projectList) { + for (PackageNode project : projectList) { IJavaProject javaProject = PackageCommand.getJavaProject(project.getUri()); if (javaProject != null && javaProject.exists()) { javaProjects.add(javaProject); @@ -254,7 +270,7 @@ public static List getMainClasses(List arguments, IProgre } int includeMask = IJavaSearchScope.SOURCES; IJavaSearchScope scope = SearchEngine.createJavaSearchScope(javaProjects.toArray(new IJavaProject[0]), - includeMask); + includeMask); 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, @@ -285,7 +301,7 @@ public void acceptSearchMatch(SearchMatch match) { }; SearchEngine searchEngine = new SearchEngine(); try { - searchEngine.search(pattern, new SearchParticipant[] {SearchEngine.getDefaultSearchParticipant()}, scope, + searchEngine.search(pattern, new SearchParticipant[] { SearchEngine.getDefaultSearchParticipant() }, scope, requestor, monitor); } catch (CoreException e) { // ignore @@ -332,11 +348,311 @@ public static boolean checkImportStatus() { return hasError; } + public static ImportClassInfo[] getImportClassContent(List arguments, IProgressMonitor monitor) { + if (arguments == null || arguments.isEmpty()) { + return new ImportClassInfo[0]; + } + + try { + String fileUri = (String) arguments.get(0); + + // Parse URI manually to avoid restricted API + java.net.URI uri = new java.net.URI(fileUri); + String filePath = uri.getPath(); + if (filePath == null) { + return new ImportClassInfo[0]; + } + + IPath path = new Path(filePath); + + // Get the file resource + IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); + IFile file = root.getFileForLocation(path); + if (file == null || !file.exists()) { + return new ImportClassInfo[0]; + } + + // Get the Java project + IJavaProject javaProject = JavaCore.create(file.getProject()); + if (javaProject == null || !javaProject.exists()) { + return new ImportClassInfo[0]; + } + + // Find the compilation unit + IJavaElement javaElement = JavaCore.create(file); + if (!(javaElement instanceof org.eclipse.jdt.core.ICompilationUnit)) { + return new ImportClassInfo[0]; + } + + org.eclipse.jdt.core.ICompilationUnit compilationUnit = (org.eclipse.jdt.core.ICompilationUnit) javaElement; + + // Parse imports and resolve local project files + List result = new ArrayList<>(); + + // Get all imports from the compilation unit + org.eclipse.jdt.core.IImportDeclaration[] imports = compilationUnit.getImports(); + Set processedTypes = new HashSet<>(); + + for (org.eclipse.jdt.core.IImportDeclaration importDecl : imports) { + if (monitor.isCanceled()) { + break; + } + + String importName = importDecl.getElementName(); + if (importName.endsWith(".*")) { + // Handle package imports + String packageName = importName.substring(0, importName.length() - 2); + resolvePackageTypes(javaProject, packageName, result, processedTypes); + } else { + // Handle single type imports + resolveSingleType(javaProject, importName, result, processedTypes); + } + } + + return result.toArray(new ImportClassInfo[0]); + + } catch (Exception e) { + JdtlsExtActivator.logException("Error in resolveCopilotRequest", e); + return new ImportClassInfo[0]; + } + } + + private static void resolveSingleType(IJavaProject javaProject, String typeName, List result, + Set processedTypes) { + try { + if (processedTypes.contains(typeName)) { + return; + } + processedTypes.add(typeName); + + // Find the type in the project + org.eclipse.jdt.core.IType type = javaProject.findType(typeName); + if (type != null && type.exists()) { + // Check if it's a local project type (not from external dependencies) + IPackageFragmentRoot packageRoot = (IPackageFragmentRoot) type + .getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT); + if (packageRoot != null && packageRoot.getKind() == IPackageFragmentRoot.K_SOURCE) { + // This is a source type from the local project + extractTypeInfo(type, result); + } + } + } catch (JavaModelException e) { + // Log but continue processing other types + JdtlsExtActivator.logException("Error resolving type: " + typeName, e); + } + } + + private static void resolvePackageTypes(IJavaProject javaProject, String packageName, List result, + Set processedTypes) { + try { + // Find all package fragments with this name + IPackageFragmentRoot[] packageRoots = javaProject.getPackageFragmentRoots(); + for (IPackageFragmentRoot packageRoot : packageRoots) { + if (packageRoot.getKind() == IPackageFragmentRoot.K_SOURCE) { + org.eclipse.jdt.core.IPackageFragment packageFragment = packageRoot.getPackageFragment(packageName); + if (packageFragment != null && packageFragment.exists()) { + // Get all compilation units in this package + org.eclipse.jdt.core.ICompilationUnit[] compilationUnits = packageFragment + .getCompilationUnits(); + for (org.eclipse.jdt.core.ICompilationUnit cu : compilationUnits) { + // Get all types in the compilation unit + org.eclipse.jdt.core.IType[] types = cu.getAllTypes(); + for (org.eclipse.jdt.core.IType type : types) { + String fullTypeName = type.getFullyQualifiedName(); + if (!processedTypes.contains(fullTypeName)) { + processedTypes.add(fullTypeName); + extractTypeInfo(type, result); + } + } + } + } + } + } + } catch (JavaModelException e) { + // Log but continue processing + JdtlsExtActivator.logException("Error resolving package: " + packageName, e); + } + } + + private static void extractTypeInfo(org.eclipse.jdt.core.IType type, List result) { + try { + String typeName = type.getFullyQualifiedName(); + String typeInfo = ""; + + // Determine type kind + if (type.isInterface()) { + typeInfo = "interface:" + typeName; + } else if (type.isClass()) { + extractDetailedClassInfo(type, result); + return; // extractDetailedClassInfo handles adding to result + } else if (type.isEnum()) { + typeInfo = "enum:" + typeName; + } else if (type.isAnnotation()) { + typeInfo = "annotation:" + typeName; + } else { + typeInfo = "type:" + typeName; + } + + // Get URI for this type + String uri = getTypeUri(type); + if (uri != null) { + result.add(new ImportClassInfo(uri, typeInfo)); + } + + // Also add nested types + org.eclipse.jdt.core.IType[] nestedTypes = type.getTypes(); + for (org.eclipse.jdt.core.IType nestedType : nestedTypes) { + extractTypeInfo(nestedType, result); + } + + } catch (JavaModelException e) { + // Log but continue processing other types + JdtlsExtActivator.logException("Error extracting type info for: " + type.getElementName(), e); + } + } + + private static void extractDetailedClassInfo(org.eclipse.jdt.core.IType type, List result) { + try { + if (!type.isClass()) { + return; // Only process classes + } + + String className = type.getFullyQualifiedName(); + List classDetails = new ArrayList<>(); + + // 1. Class declaration information + classDetails.add("class:" + className); + + // 2. Modifiers + int flags = type.getFlags(); + List modifiers = new ArrayList<>(); + if (org.eclipse.jdt.core.Flags.isPublic(flags)) + modifiers.add("public"); + if (org.eclipse.jdt.core.Flags.isAbstract(flags)) + modifiers.add("abstract"); + if (org.eclipse.jdt.core.Flags.isFinal(flags)) + modifiers.add("final"); + if (org.eclipse.jdt.core.Flags.isStatic(flags)) + modifiers.add("static"); + if (!modifiers.isEmpty()) { + classDetails.add("modifiers:" + String.join(",", modifiers)); + } + + // 3. Inheritance + String superclass = type.getSuperclassName(); + if (superclass != null && !"Object".equals(superclass)) { + classDetails.add("extends:" + superclass); + } + + // 4. Implemented interfaces + String[] interfaces = type.getSuperInterfaceNames(); + if (interfaces.length > 0) { + classDetails.add("implements:" + String.join(",", interfaces)); + } + + // 5. Constructors + IMethod[] methods = type.getMethods(); + List constructors = new ArrayList<>(); + List publicMethods = new ArrayList<>(); + + for (IMethod method : methods) { + if (method.isConstructor()) { + constructors.add(method.getElementName() + getParameterTypes(method)); + } else if (org.eclipse.jdt.core.Flags.isPublic(method.getFlags())) { + publicMethods.add(method.getElementName() + getParameterTypes(method)); + } + } + + if (!constructors.isEmpty()) { + classDetails.add("constructors:" + String.join(",", constructors)); + } + + if (!publicMethods.isEmpty()) { + classDetails.add("publicMethods:" + + String.join(",", publicMethods.subList(0, Math.min(publicMethods.size(), 10)))); + } + + // 6. Public fields + org.eclipse.jdt.core.IField[] fields = type.getFields(); + List publicFields = new ArrayList<>(); + for (org.eclipse.jdt.core.IField field : fields) { + if (org.eclipse.jdt.core.Flags.isPublic(field.getFlags())) { + publicFields.add(field.getElementName()); + } + } + + if (!publicFields.isEmpty()) { + classDetails.add("publicFields:" + String.join(",", publicFields)); + } + + // Get URI for this type + String uri = getTypeUri(type); + if (uri != null) { + // Combine all information into one string + String classInfo = String.join("|", classDetails); + result.add(new ImportClassInfo(uri, classInfo)); + } + + } catch (JavaModelException e) { + JdtlsExtActivator.logException("Error extracting detailed class info", e); + } + } + + private static String getTypeUri(org.eclipse.jdt.core.IType type) { + try { + // Get the resource where the type is located + IResource resource = type.getResource(); + if (resource != null && resource.exists()) { + // Get the complete path of the file + IPath location = resource.getLocation(); + if (location != null) { + // 转换为 URI 格式 + return location.toFile().toURI().toString(); + } + + // If unable to get physical path, use workspace relative path + String workspacePath = resource.getFullPath().toString(); + IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); + IPath rootLocation = root.getLocation(); + if (rootLocation != null) { + IPath fullPath = rootLocation.append(workspacePath); + return fullPath.toFile().toURI().toString(); + } + } + + // As a fallback, try to get from compilation unit + org.eclipse.jdt.core.ICompilationUnit compilationUnit = type.getCompilationUnit(); + if (compilationUnit != null) { + IResource cuResource = compilationUnit.getResource(); + if (cuResource != null && cuResource.exists()) { + IPath cuLocation = cuResource.getLocation(); + if (cuLocation != null) { + return cuLocation.toFile().toURI().toString(); + } + } + } + + return null; + } catch (Exception e) { + JdtlsExtActivator.logException("Error getting type URI for: " + type.getElementName(), e); + return null; + } + } + + // Helper method: Get method parameter types + private static String getParameterTypes(IMethod method) { + String[] paramTypes = method.getParameterTypes(); + if (paramTypes.length == 0) { + return "()"; + } + return "(" + String.join(",", paramTypes) + ")"; + } + private static void reportExportJarMessage(String terminalId, int severity, String message) { if (StringUtils.isNotBlank(message) && StringUtils.isNotBlank(terminalId)) { String readableSeverity = getSeverityString(severity); JavaLanguageServerPlugin.getInstance().getClientConnection().executeClientCommand(COMMAND_EXPORT_JAR_REPORT, - terminalId, "[" + readableSeverity + "] " + message); + terminalId, "[" + readableSeverity + "] " + message); } } @@ -392,4 +708,4 @@ public boolean isBelongsToWorkspace() { return belongsToWorkspace; } } -} +} \ No newline at end of file From 4e4d2b579a1cb9207cf2bffd175436e934fea5d9 Mon Sep 17 00:00:00 2001 From: wenyutang Date: Thu, 18 Sep 2025 16:07:56 +0800 Subject: [PATCH 2/7] feat: update the java command --- .../jdtls/ext/core/ProjectCommand.java | 39 +++---------------- 1 file changed, 5 insertions(+), 34 deletions(-) diff --git a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java index f7e15541..f167bba7 100644 --- a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java +++ b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java @@ -598,43 +598,14 @@ private static void extractDetailedClassInfo(org.eclipse.jdt.core.IType type, Li } } + // Helper method: Get fully qualified class name (used as identifier instead of file URI) private static String getTypeUri(org.eclipse.jdt.core.IType type) { try { - // Get the resource where the type is located - IResource resource = type.getResource(); - if (resource != null && resource.exists()) { - // Get the complete path of the file - IPath location = resource.getLocation(); - if (location != null) { - // 转换为 URI 格式 - return location.toFile().toURI().toString(); - } - - // If unable to get physical path, use workspace relative path - String workspacePath = resource.getFullPath().toString(); - IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); - IPath rootLocation = root.getLocation(); - if (rootLocation != null) { - IPath fullPath = rootLocation.append(workspacePath); - return fullPath.toFile().toURI().toString(); - } - } - - // As a fallback, try to get from compilation unit - org.eclipse.jdt.core.ICompilationUnit compilationUnit = type.getCompilationUnit(); - if (compilationUnit != null) { - IResource cuResource = compilationUnit.getResource(); - if (cuResource != null && cuResource.exists()) { - IPath cuLocation = cuResource.getLocation(); - if (cuLocation != null) { - return cuLocation.toFile().toURI().toString(); - } - } - } - - return null; + // Return the fully qualified class name instead of file URI + // This matches the import statement format like "com.acme.user.UserService" + return type.getFullyQualifiedName(); } catch (Exception e) { - JdtlsExtActivator.logException("Error getting type URI for: " + type.getElementName(), e); + JdtlsExtActivator.logException("Error getting type name for: " + type.getElementName(), e); return null; } } From 2f0abf31064aead97523f58cfc87b503777f75bd Mon Sep 17 00:00:00 2001 From: wenytang-ms Date: Mon, 22 Sep 2025 09:59:46 +0800 Subject: [PATCH 3/7] fix: update findtype to more useable --- .../jdtls/ext/core/ProjectCommand.java | 47 +++++++++++++++---- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java index f167bba7..b1f749cf 100644 --- a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java +++ b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java @@ -425,15 +425,44 @@ private static void resolveSingleType(IJavaProject javaProject, String typeName, } processedTypes.add(typeName); - // Find the type in the project - org.eclipse.jdt.core.IType type = javaProject.findType(typeName); - if (type != null && type.exists()) { - // Check if it's a local project type (not from external dependencies) - IPackageFragmentRoot packageRoot = (IPackageFragmentRoot) type - .getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT); - if (packageRoot != null && packageRoot.getKind() == IPackageFragmentRoot.K_SOURCE) { - // This is a source type from the local project - extractTypeInfo(type, result); + // Extract package and simple name from the fully qualified type name + int lastDotIndex = typeName.lastIndexOf('.'); + if (lastDotIndex == -1) { + // Default package or invalid type name + return; + } + + String packageName = typeName.substring(0, lastDotIndex); + String simpleName = typeName.substring(lastDotIndex + 1); + + // Search for the type in source package fragments only + IPackageFragmentRoot[] packageRoots = javaProject.getPackageFragmentRoots(); + for (IPackageFragmentRoot packageRoot : packageRoots) { + if (packageRoot.getKind() == IPackageFragmentRoot.K_SOURCE) { + org.eclipse.jdt.core.IPackageFragment packageFragment = packageRoot.getPackageFragment(packageName); + if (packageFragment != null && packageFragment.exists()) { + // Look for compilation unit with matching name + org.eclipse.jdt.core.ICompilationUnit cu = packageFragment.getCompilationUnit(simpleName + ".java"); + if (cu != null && cu.exists()) { + // Get primary type from compilation unit + org.eclipse.jdt.core.IType primaryType = cu.findPrimaryType(); + if (primaryType != null && primaryType.exists() && + typeName.equals(primaryType.getFullyQualifiedName())) { + // This is a local project type + extractTypeInfo(primaryType, result); + return; + } + + // Also check for inner types in the compilation unit + org.eclipse.jdt.core.IType[] allTypes = cu.getAllTypes(); + for (org.eclipse.jdt.core.IType type : allTypes) { + if (typeName.equals(type.getFullyQualifiedName())) { + extractTypeInfo(type, result); + return; + } + } + } + } } } } catch (JavaModelException e) { From f625d9fbf08c6fb7a5980004cb50f3210f7c7cd6 Mon Sep 17 00:00:00 2001 From: wenytang-ms Date: Mon, 22 Sep 2025 10:50:51 +0800 Subject: [PATCH 4/7] fix: update --- .../jdtls/ext/core/ProjectCommand.java | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java index b1f749cf..13e19852 100644 --- a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java +++ b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java @@ -435,7 +435,21 @@ private static void resolveSingleType(IJavaProject javaProject, String typeName, String packageName = typeName.substring(0, lastDotIndex); String simpleName = typeName.substring(lastDotIndex + 1); - // Search for the type in source package fragments only + // First try to find the type using JDT's built-in type resolution + try { + org.eclipse.jdt.core.IType type = javaProject.findType(typeName); + if (type != null && type.exists() && type.isBinary() == false) { + // This is a local project type (not from a JAR) + extractTypeInfo(type, result); + return; + } + } catch (JavaModelException e) { + JdtlsExtActivator.logException("Error finding type: " + typeName, e); + // Fall back to manual search if findType fails + return; + } + + // Fallback: Search for the type in source package fragments manually IPackageFragmentRoot[] packageRoots = javaProject.getPackageFragmentRoots(); for (IPackageFragmentRoot packageRoot : packageRoots) { if (packageRoot.getKind() == IPackageFragmentRoot.K_SOURCE) { @@ -443,7 +457,7 @@ private static void resolveSingleType(IJavaProject javaProject, String typeName, if (packageFragment != null && packageFragment.exists()) { // Look for compilation unit with matching name org.eclipse.jdt.core.ICompilationUnit cu = packageFragment.getCompilationUnit(simpleName + ".java"); - if (cu != null && cu.exists()) { + if (cu != null && cu.exists() && cu.getResource() != null && cu.getResource().exists()) { // Get primary type from compilation unit org.eclipse.jdt.core.IType primaryType = cu.findPrimaryType(); if (primaryType != null && primaryType.exists() && From e724bd3f81af4cdd3c3b6b29231b98742819f9f7 Mon Sep 17 00:00:00 2001 From: wenyutang-ms Date: Wed, 24 Sep 2025 10:10:16 +0800 Subject: [PATCH 5/7] fix: update logic to resolve type --- .../jdtls/ext/core/ProjectCommand.java | 36 +++++++++++-------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java index 13e19852..454e8475 100644 --- a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java +++ b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java @@ -435,21 +435,10 @@ private static void resolveSingleType(IJavaProject javaProject, String typeName, String packageName = typeName.substring(0, lastDotIndex); String simpleName = typeName.substring(lastDotIndex + 1); - // First try to find the type using JDT's built-in type resolution - try { - org.eclipse.jdt.core.IType type = javaProject.findType(typeName); - if (type != null && type.exists() && type.isBinary() == false) { - // This is a local project type (not from a JAR) - extractTypeInfo(type, result); - return; - } - } catch (JavaModelException e) { - JdtlsExtActivator.logException("Error finding type: " + typeName, e); - // Fall back to manual search if findType fails - return; - } + // Strategy: First search in local source packages (fast), then fallback to global search (slow) + // This optimizes for the common case where imports reference local project types - // Fallback: Search for the type in source package fragments manually + // Fast path: Search for the type in source package fragments directly IPackageFragmentRoot[] packageRoots = javaProject.getPackageFragmentRoots(); for (IPackageFragmentRoot packageRoot : packageRoots) { if (packageRoot.getKind() == IPackageFragmentRoot.K_SOURCE) { @@ -462,7 +451,7 @@ private static void resolveSingleType(IJavaProject javaProject, String typeName, org.eclipse.jdt.core.IType primaryType = cu.findPrimaryType(); if (primaryType != null && primaryType.exists() && typeName.equals(primaryType.getFullyQualifiedName())) { - // This is a local project type + // Found local project source type - fast path success extractTypeInfo(primaryType, result); return; } @@ -479,6 +468,23 @@ private static void resolveSingleType(IJavaProject javaProject, String typeName, } } } + + // Slow path: Use JDT's global type resolution as fallback for external dependencies + // This is only needed if the type is not found in local source packages + try { + org.eclipse.jdt.core.IType type = javaProject.findType(typeName); + if (type != null && type.exists()) { + // Found type in dependencies/JRE, but we only process local source types + // for this specific use case (Copilot context) + if (!type.isBinary()) { + extractTypeInfo(type, result); + } + // Note: Binary types (from JARs) are intentionally ignored + // as they don't provide useful context for code completion + } + } catch (JavaModelException e) { + JdtlsExtActivator.logException("Error finding type in global search: " + typeName, e); + } } catch (JavaModelException e) { // Log but continue processing other types JdtlsExtActivator.logException("Error resolving type: " + typeName, e); From 125bd9b24e2e4ecefd9bd2db38fcabcddc43be9b Mon Sep 17 00:00:00 2001 From: wenyutang-ms Date: Wed, 24 Sep 2025 21:55:49 +0800 Subject: [PATCH 6/7] feat: support static import --- .../jdtls/ext/core/ProjectCommand.java | 152 +++++++++++++++++- 1 file changed, 146 insertions(+), 6 deletions(-) diff --git a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java index 454e8475..a9f8e699 100644 --- a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java +++ b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java @@ -399,7 +399,12 @@ public static ImportClassInfo[] getImportClassContent(List arguments, IP } String importName = importDecl.getElementName(); - if (importName.endsWith(".*")) { + boolean isStatic = (importDecl.getFlags() & org.eclipse.jdt.core.Flags.AccStatic) != 0; + + if (isStatic) { + // Handle static imports + resolveStaticImport(javaProject, importName, result, processedTypes); + } else if (importName.endsWith(".*")) { // Handle package imports String packageName = importName.substring(0, importName.length() - 2); resolvePackageTypes(javaProject, packageName, result, processedTypes); @@ -491,6 +496,118 @@ private static void resolveSingleType(IJavaProject javaProject, String typeName, } } + private static void resolveStaticImport(IJavaProject javaProject, String staticImportName, List result, + Set processedTypes) { + try { + if (staticImportName.endsWith(".*")) { + // Static import of all static members from a class: import static MyClass.*; + String className = staticImportName.substring(0, staticImportName.length() - 2); + resolveStaticMembersFromClass(javaProject, className, result, processedTypes); + } else { + // Static import of specific member: import static MyClass.myMethod; + int lastDotIndex = staticImportName.lastIndexOf('.'); + if (lastDotIndex > 0) { + String className = staticImportName.substring(0, lastDotIndex); + String memberName = staticImportName.substring(lastDotIndex + 1); + resolveStaticMemberFromClass(javaProject, className, memberName, result, processedTypes); + } + } + } catch (Exception e) { + JdtlsExtActivator.logException("Error resolving static import: " + staticImportName, e); + } + } + + private static void resolveStaticMembersFromClass(IJavaProject javaProject, String className, + List result, Set processedTypes) { + try { + // First resolve the class itself to get context information + resolveSingleType(javaProject, className, result, processedTypes); + + // Find the type and extract its static members + org.eclipse.jdt.core.IType type = javaProject.findType(className); + if (type != null && type.exists() && !type.isBinary()) { + String staticMemberInfo = "staticImport:" + className + ".*"; + + // Add information about available static members + List staticMembers = new ArrayList<>(); + + // Get static methods + IMethod[] methods = type.getMethods(); + for (IMethod method : methods) { + int flags = method.getFlags(); + if (org.eclipse.jdt.core.Flags.isStatic(flags) && org.eclipse.jdt.core.Flags.isPublic(flags)) { + staticMembers.add("method:" + method.getElementName() + getParameterTypes(method)); + } + } + + // Get static fields + org.eclipse.jdt.core.IField[] fields = type.getFields(); + for (org.eclipse.jdt.core.IField field : fields) { + int flags = field.getFlags(); + if (org.eclipse.jdt.core.Flags.isStatic(flags) && org.eclipse.jdt.core.Flags.isPublic(flags)) { + staticMembers.add("field:" + field.getElementName()); + } + } + + if (!staticMembers.isEmpty()) { + staticMemberInfo += "|members:" + String.join(",", staticMembers.subList(0, Math.min(staticMembers.size(), 10))); + } + + String uri = getTypeUri(type); + if (uri != null) { + result.add(new ImportClassInfo(uri, staticMemberInfo)); + } + } + } catch (JavaModelException e) { + JdtlsExtActivator.logException("Error resolving static members from: " + className, e); + } + } + + private static void resolveStaticMemberFromClass(IJavaProject javaProject, String className, String memberName, + List result, Set processedTypes) { + try { + // First resolve the class itself + resolveSingleType(javaProject, className, result, processedTypes); + + // Find the specific static member + org.eclipse.jdt.core.IType type = javaProject.findType(className); + if (type != null && type.exists() && !type.isBinary()) { + String memberInfo = "staticImport:" + className + "." + memberName; + + // Check if it's a method + IMethod[] methods = type.getMethods(); + for (IMethod method : methods) { + if (method.getElementName().equals(memberName)) { + int flags = method.getFlags(); + if (org.eclipse.jdt.core.Flags.isStatic(flags)) { + memberInfo += "|type:method" + getParameterTypes(method); + break; + } + } + } + + // Check if it's a field + org.eclipse.jdt.core.IField[] fields = type.getFields(); + for (org.eclipse.jdt.core.IField field : fields) { + if (field.getElementName().equals(memberName)) { + int flags = field.getFlags(); + if (org.eclipse.jdt.core.Flags.isStatic(flags)) { + memberInfo += "|type:field"; + break; + } + } + } + + String uri = getTypeUri(type); + if (uri != null) { + result.add(new ImportClassInfo(uri, memberInfo)); + } + } + } catch (JavaModelException e) { + JdtlsExtActivator.logException("Error resolving static member: " + className + "." + memberName, e); + } + } + private static void resolvePackageTypes(IJavaProject javaProject, String packageName, List result, Set processedTypes) { try { @@ -647,15 +764,38 @@ private static void extractDetailedClassInfo(org.eclipse.jdt.core.IType type, Li } } - // Helper method: Get fully qualified class name (used as identifier instead of file URI) + // Helper method: Get file URI/path for the type (instead of fully qualified class name) private static String getTypeUri(org.eclipse.jdt.core.IType type) { try { - // Return the fully qualified class name instead of file URI - // This matches the import statement format like "com.acme.user.UserService" + // Get the compilation unit that contains this type + org.eclipse.jdt.core.ICompilationUnit compilationUnit = type.getCompilationUnit(); + if (compilationUnit != null) { + // Get the underlying resource (file) + org.eclipse.core.resources.IResource resource = compilationUnit.getUnderlyingResource(); + if (resource != null && resource instanceof org.eclipse.core.resources.IFile) { + org.eclipse.core.resources.IFile file = (org.eclipse.core.resources.IFile) resource; + // Get the file location as a file URI + java.net.URI fileUri = file.getLocationURI(); + if (fileUri != null) { + return fileUri.toString(); + } + + // Fallback: use workspace-relative path as URI + return file.getFullPath().toString(); + } + } + + // Fallback: if we can't get file URI, return the fully qualified class name + // This should rarely happen for source types return type.getFullyQualifiedName(); } catch (Exception e) { - JdtlsExtActivator.logException("Error getting type name for: " + type.getElementName(), e); - return null; + JdtlsExtActivator.logException("Error getting file URI for type: " + type.getElementName(), e); + // Fallback to class name in case of error + try { + return type.getFullyQualifiedName(); + } catch (Exception e2) { + return null; + } } } From 276ac7134b7ebd5e9dec6e23b0bac33d64384f1d Mon Sep 17 00:00:00 2001 From: wenyutang Date: Fri, 26 Sep 2025 09:32:09 +0800 Subject: [PATCH 7/7] feat: adjust logic and bump up version --- .../jdtls/ext/core/ProjectCommand.java | 44 ++++++++++--------- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 25 insertions(+), 23 deletions(-) diff --git a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java index a9f8e699..aad11b68 100644 --- a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java +++ b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java @@ -440,10 +440,29 @@ private static void resolveSingleType(IJavaProject javaProject, String typeName, String packageName = typeName.substring(0, lastDotIndex); String simpleName = typeName.substring(lastDotIndex + 1); - // Strategy: First search in local source packages (fast), then fallback to global search (slow) - // This optimizes for the common case where imports reference local project types + // Strategy: Use JDT's global type resolution first (comprehensive), + // then fallback to manual package fragment traversal if needed - // Fast path: Search for the type in source package fragments directly + // Primary path: Use JDT's findType which searches all sources and dependencies + try { + org.eclipse.jdt.core.IType type = javaProject.findType(typeName); + if (type != null && type.exists()) { + // Found type - check if it's a source type we want to process + if (!type.isBinary()) { + // Source type found - extract information and return + extractTypeInfo(type, result); + return; + } + // Note: Binary types (from JARs/JRE) are intentionally ignored + // as they don't provide useful context for code completion + } + } catch (JavaModelException e) { + JdtlsExtActivator.logException("Error in primary type search: " + typeName, e); + // Continue to fallback method + } + + // Fallback path: Manual search in local source package fragments + // This is used when findType() doesn't return results or fails IPackageFragmentRoot[] packageRoots = javaProject.getPackageFragmentRoots(); for (IPackageFragmentRoot packageRoot : packageRoots) { if (packageRoot.getKind() == IPackageFragmentRoot.K_SOURCE) { @@ -456,7 +475,7 @@ private static void resolveSingleType(IJavaProject javaProject, String typeName, org.eclipse.jdt.core.IType primaryType = cu.findPrimaryType(); if (primaryType != null && primaryType.exists() && typeName.equals(primaryType.getFullyQualifiedName())) { - // Found local project source type - fast path success + // Found local project source type via fallback method extractTypeInfo(primaryType, result); return; } @@ -473,23 +492,6 @@ private static void resolveSingleType(IJavaProject javaProject, String typeName, } } } - - // Slow path: Use JDT's global type resolution as fallback for external dependencies - // This is only needed if the type is not found in local source packages - try { - org.eclipse.jdt.core.IType type = javaProject.findType(typeName); - if (type != null && type.exists()) { - // Found type in dependencies/JRE, but we only process local source types - // for this specific use case (Copilot context) - if (!type.isBinary()) { - extractTypeInfo(type, result); - } - // Note: Binary types (from JARs) are intentionally ignored - // as they don't provide useful context for code completion - } - } catch (JavaModelException e) { - JdtlsExtActivator.logException("Error finding type in global search: " + typeName, e); - } } catch (JavaModelException e) { // Log but continue processing other types JdtlsExtActivator.logException("Error resolving type: " + typeName, e); diff --git a/package-lock.json b/package-lock.json index 86f0b6a8..5ce64393 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "vscode-java-dependency", - "version": "0.25.0", + "version": "0.26.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 31fd592f..df3f3f59 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "vscode-java-dependency", "displayName": "Project Manager for Java", "description": "%description%", - "version": "0.25.0", + "version": "0.26.0", "publisher": "vscjava", "preview": false, "aiKey": "5c642b22-e845-4400-badb-3f8509a70777",