Skip to content
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ You can tell that the glob pattern is supported. And here's more - you can incl

## Requirements

- VS Code (version 1.83.1+)
- VS Code (version 1.95.0+)
- [Language Support for Java by Red Hat](https://marketplace.visualstudio.com/items?itemName=redhat.java)


Expand Down
1 change: 1 addition & 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,7 @@
<command id="java.project.checkImportStatus" />
<command id="java.project.getImportClassContent" />
<command id="java.project.getDependencies" />
<command id="java.project.getFileImports" />
</delegateCommandHandler>
</extension>
<extension
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
/*******************************************************************************
* Copyright (c) 2018 Microsoft Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/

package com.microsoft.jdtls.ext.core;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IImportDeclaration;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.ls.core.internal.JDTUtils;

import com.microsoft.jdtls.ext.core.model.FileImportsResult;
import com.microsoft.jdtls.ext.core.model.FileImportsResult.ImportEntry;
import com.microsoft.jdtls.ext.core.model.FileImportsResult.StaticImportEntry;

/**
* Lightweight command handler for AI context tools.
* All methods in this class are designed to be non-blocking and fast (< 10ms).
* They only read AST-level information and do NOT trigger classpath resolution,
* type resolution, or any expensive JDT operations.
*/
public class AiContextCommand {

// Well-known JDK package prefixes
private static final Set<String> JDK_PREFIXES = new HashSet<>();
static {
JDK_PREFIXES.add("java.");
JDK_PREFIXES.add("javax.");
JDK_PREFIXES.add("jdk.");
JDK_PREFIXES.add("sun.");
JDK_PREFIXES.add("com.sun.");
JDK_PREFIXES.add("org.xml.");
JDK_PREFIXES.add("org.w3c.");
JDK_PREFIXES.add("jakarta."); // Jakarta EE (post Java EE)
}

/**
* Get the classified import list of a Java file.
* This is a lightweight AST-only operation — it reads import declarations
* without doing any type resolution (findType) or classpath resolution.
*
* Typical response time: < 5ms
*
* @param arguments List containing the file URI as the first element
* @param monitor Progress monitor for cancellation support
* @return FileImportsResult with classified imports
*/
public static FileImportsResult getFileImports(List<Object> arguments, IProgressMonitor monitor) {
FileImportsResult result = new FileImportsResult();
result.imports = new ArrayList<>();
result.staticImports = new ArrayList<>();

if (arguments == null || arguments.isEmpty()) {
result.error = "No arguments provided";
return result;
}

try {
String fileUri = (String) arguments.get(0);
if (fileUri == null || fileUri.trim().isEmpty()) {
result.error = "Invalid file URI";
return result;
}

// Resolve compilation unit from URI — this is fast, just a model lookup
java.net.URI uri = JDTUtils.toURI(fileUri);
ICompilationUnit compilationUnit = JDTUtils.resolveCompilationUnit(uri);

if (compilationUnit == null || !compilationUnit.exists()) {
result.error = "File not found or not a Java file: " + fileUri;
return result;
}

// Get project-relative file path (strip the leading project segment
// from the Eclipse workspace-relative path, e.g. "/my-project/src/Foo.java" → "src/Foo.java")
IJavaProject javaProject = compilationUnit.getJavaProject();
result.file = compilationUnit.getPath().removeFirstSegments(1).toString();

// Collect project source package names for classification
Set<String> projectPackages = collectProjectPackages(javaProject);

// Read import declarations — pure AST operation, no type resolution
IImportDeclaration[] imports = compilationUnit.getImports();
if (imports == null || imports.length == 0) {
return result; // No imports, return empty (not an error)
}

for (IImportDeclaration imp : imports) {
if (monitor.isCanceled()) {
break;
}

String name = imp.getElementName();
boolean isStatic = Flags.isStatic(imp.getFlags());
boolean isOnDemand = name.endsWith(".*");

if (isStatic) {
StaticImportEntry entry = new StaticImportEntry();
entry.name = name;
entry.memberKind = "unknown"; // Would need findType to know — skip
entry.source = classifyByPackageName(name, projectPackages);
result.staticImports.add(entry);
} else {
ImportEntry entry = new ImportEntry();
entry.name = name;
entry.kind = "unknown"; // Would need findType to know — skip
entry.source = classifyByPackageName(name, projectPackages);
entry.artifact = null; // Would need classpath attributes — skip for now
entry.isOnDemand = isOnDemand;
result.imports.add(entry);
}
}

return result;

} catch (Exception e) {
JdtlsExtActivator.logException("Error in getFileImports", e);
result.error = "Exception: " + e.getMessage();
return result;
}
}

/**
* Classify an import by its package name prefix.
* This is a heuristic — no type resolution involved.
*
* @param qualifiedName the fully qualified name of the import
* @param projectPackages set of package names found in the project's source roots
* @return "jdk", "project", or "external"
*/
private static String classifyByPackageName(String qualifiedName, Set<String> projectPackages) {
// Check JDK
for (String prefix : JDK_PREFIXES) {
if (qualifiedName.startsWith(prefix)) {
return "jdk";
}
}

// Check project packages
String packageName = getPackageName(qualifiedName);
if (packageName != null && projectPackages.contains(packageName)) {
return "project";
}

// Check if any project package is a prefix of this import
for (String projPkg : projectPackages) {
if (qualifiedName.startsWith(projPkg + ".")) {
return "project";
}
}

return "external";
}

/**
* Get the package name from a fully qualified name.
* e.g., "com.example.model.Order" → "com.example.model"
* "com.example.model.*" → "com.example.model"
*/
private static String getPackageName(String qualifiedName) {
if (qualifiedName == null) {
return null;
}
// Handle wildcard imports
if (qualifiedName.endsWith(".*")) {
return qualifiedName.substring(0, qualifiedName.length() - 2);
}
int lastDot = qualifiedName.lastIndexOf('.');
if (lastDot > 0) {
return qualifiedName.substring(0, lastDot);
}
return null;
}

/**
* Collect all package names that exist in the project's source roots.
* This uses getPackageFragmentRoots(K_SOURCE) which is fast — it reads
* the project model, not the filesystem.
*/
private static Set<String> collectProjectPackages(IJavaProject javaProject) {
Set<String> packages = new HashSet<>();
if (javaProject == null) {
return packages;
}

try {
IPackageFragmentRoot[] roots = javaProject.getPackageFragmentRoots();
for (IPackageFragmentRoot root : roots) {
if (root.getKind() == IPackageFragmentRoot.K_SOURCE) {
org.eclipse.jdt.core.IJavaElement[] children = root.getChildren();
for (org.eclipse.jdt.core.IJavaElement child : children) {
if (child instanceof org.eclipse.jdt.core.IPackageFragment) {
String pkgName = child.getElementName();
if (pkgName != null && !pkgName.isEmpty()) {
packages.add(pkgName);
}
}
}
}
}
} catch (Exception e) {
// Non-critical — fall back to treating everything as external
JdtlsExtActivator.logException("Error collecting project packages", e);
}

return packages;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ 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.getFileImports":
return AiContextCommand.getFileImports(arguments, monitor);
default:
break;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.microsoft.jdtls.ext.core.model;

import java.util.List;

/**
* L1: Detailed class information.
* AI calls this for specific classes it needs to understand, not for all imports.
*/
public class ClassDetailResult {

public String qualifiedName; // "com.example.model.Order"
public String kind; // "class" | "interface" | "enum" | "annotation"
public String uri; // file URI (for project source) or jar URI
public String source; // "project" | "external" | "jdk"
public String artifact; // GAV for external: "com.google.code.gson:gson:2.10.1"

public String signature; // "public class Order implements Serializable"
public String superClass; // "java.lang.Object" (null if Object)
public List<String> interfaces; // ["java.io.Serializable"]
public List<String> annotations; // ["@Entity", "@Table(name = \"orders\")"]

public String javadocSummary; // First sentence only, null if none

public List<String> constructors; // ["Order()", "Order(String orderId, Customer customer)"]
public List<String> methods; // ["String getOrderId()", "void setStatus(OrderStatus)"]
public List<String> fields; // ["private String orderId", "private List<OrderItem> items"]

public int totalMethodCount; // actual total (methods list may be truncated)
public int totalFieldCount; // actual total

public String error; // null if success

/**
* Builder-style static factories for common cases
*/
public static ClassDetailResult notFound(String qualifiedName) {
ClassDetailResult r = new ClassDetailResult();
r.qualifiedName = qualifiedName;
r.error = "Type not found: " + qualifiedName;
return r;
}

public static ClassDetailResult error(String qualifiedName, String message) {
ClassDetailResult r = new ClassDetailResult();
r.qualifiedName = qualifiedName;
r.error = message;
return r;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.microsoft.jdtls.ext.core.model;

import java.util.List;

/**
* L1: Detailed dependency information with query filtering.
* AI calls this when it needs to investigate specific dependencies.
*/
public class DependencyDetailsResult {

public List<DependencyEntry> dependencies;
public String error; // null if success

public static class DependencyEntry {
public String groupId; // "com.google.code.gson"
public String artifactId; // "gson"
public String version; // "2.10.1"
public String scope; // "compile" | "test" | "runtime" | "provided" | "system"
public boolean isDirect; // true = declared in pom.xml/build.gradle
public String broughtBy; // for transitive: "com.google.guava:guava:32.1.3-jre"
public String jarFileName; // "gson-2.10.1.jar"

public DependencyEntry() {}

public DependencyEntry(String groupId, String artifactId, String version,
String scope, boolean isDirect, String broughtBy,
String jarFileName) {
this.groupId = groupId;
this.artifactId = artifactId;
this.version = version;
this.scope = scope;
this.isDirect = isDirect;
this.broughtBy = broughtBy;
this.jarFileName = jarFileName;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.microsoft.jdtls.ext.core.model;

import java.util.List;

/**
* L0: Import list for a Java file.
* Returns classified imports without expanding class details.
*/
public class FileImportsResult {

public String file; // project-relative file path (e.g. "src/main/java/com/example/Foo.java")
public List<ImportEntry> imports;
public List<StaticImportEntry> staticImports;
public String error; // null if success

public static class ImportEntry {
public String name; // fully qualified name: "com.example.model.Order"
public String kind; // "class" | "interface" | "enum" | "annotation" | "unknown"
public String source; // "project" | "external" | "jdk"
public String artifact; // only for "external": "spring-context", null for others
public boolean isOnDemand; // true for wildcard imports (e.g. "import com.example.model.*")

public ImportEntry() {}

public ImportEntry(String name, String kind, String source, String artifact) {
this.name = name;
this.kind = kind;
this.source = source;
this.artifact = artifact;
}
}

public static class StaticImportEntry {
public String name; // "org.junit.Assert.assertEquals"
public String memberKind; // "method" | "field" | "unknown"
public String source; // "project" | "external" | "jdk"

public StaticImportEntry() {}

public StaticImportEntry(String name, String memberKind, String source) {
this.name = name;
this.memberKind = memberKind;
this.source = source;
}
}
}
Loading
Loading