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
6 changes: 6 additions & 0 deletions org.eclipse.jdt.ls.core/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -225,4 +225,10 @@
id="org.eclipse.jdt.core.javanature">
</requires-nature>
</extension>

<extension point="org.eclipse.core.contenttype.contentTypes">
<file-association
content-type="org.eclipse.jdt.core.javaDerivedSource"
file-extensions="kt,scala,groovy,clj"/>
</extension>
</plugin>
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.jdt.core.CompletionProposal;
import org.eclipse.jdt.core.Flags;

import org.eclipse.jdt.core.IAnnotatable;
import org.eclipse.jdt.core.IAnnotation;
import org.eclipse.jdt.core.IBuffer;
Expand Down Expand Up @@ -106,7 +105,6 @@
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.SuperConstructorInvocation;
import org.eclipse.jdt.core.dom.Type;

import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.manipulation.CoreASTProvider;
import org.eclipse.jdt.core.manipulation.SharedASTProviderCore;
Expand Down Expand Up @@ -147,6 +145,7 @@

/**
* General utilities for working with JDT APIs
*
* @author Gorkem Ercan
*
*/
Expand Down Expand Up @@ -298,10 +297,10 @@ static ICompilationUnit getFakeCompilationUnit(URI uri, IProgressMonitor monitor
public IBuffer createBuffer(ICompilationUnit workingCopy) {
return new DocumentAdapter(workingCopy, path);
}
};
try {
return owner.newWorkingCopy(fileName, new IClasspathEntry[] { JavaRuntime.getDefaultJREContainerEntry() }, monitor);
} catch (JavaModelException e) {
};
try {
return owner.newWorkingCopy(fileName, new IClasspathEntry[] { JavaRuntime.getDefaultJREContainerEntry() }, monitor);
} catch (JavaModelException e) {
return null;
}
}
Expand Down Expand Up @@ -432,9 +431,13 @@ public static IClassFile resolveClassFile(String uriString){
* @param uri with 'jdt' scheme
* @return class file
*/
public static IClassFile resolveClassFile(URI uri){
public static IClassFile resolveClassFile(URI uri) {
if (uri != null && JDT_SCHEME.equals(uri.getScheme()) && "contents".equals(uri.getAuthority())) {
String handleId = uri.getQuery();
int idx = handleId.indexOf("&element=");
if (idx != -1) {
handleId = handleId.substring(0, idx);
}
IJavaElement element = JavaCore.create(handleId);
IClassFile cf = (IClassFile) element.getAncestor(IJavaElement.CLASS_FILE);
return cf;
Expand Down Expand Up @@ -891,7 +894,25 @@ public static String toUri(IClassFile classFile) {
String jarName = classFile.getParent().getParent().getElementName();
String uriString = null;
try {
uriString = new URI(JDT_SCHEME, "contents", PATH_SEPARATOR + jarName + PATH_SEPARATOR + packageName + PATH_SEPARATOR + classFile.getElementName(), classFile.getHandleIdentifier(), null).toASCIIString();
String elementName = classFile.getElementName();
// Use the original source file name if available
String sourceFileName = SourceFileAttributeReader.getSourceFileName(classFile);
String fileName = sourceFileName == null ? elementName : sourceFileName;
StringBuilder pathBuilder = new StringBuilder();
pathBuilder.append(PATH_SEPARATOR).append(jarName);
if (packageName != null && !packageName.isBlank()) {
pathBuilder.append(PATH_SEPARATOR).append(packageName);
}
pathBuilder.append(PATH_SEPARATOR).append(fileName);

String handleIdentifier = classFile.getHandleIdentifier();
StringBuilder query = new StringBuilder(handleIdentifier);
if (!handleIdentifier.contains(elementName)) {
//Add the element name to the query so decompilers can detect it (looking at you module-info.class!)
query.append("&element=").append(elementName);
}
uriString = new URI(JDT_SCHEME, "contents", pathBuilder.toString(), query.toString(), null).toASCIIString();

} catch (URISyntaxException e) {
JavaLanguageServerPlugin.logException("Error generating URI for class ", e);
}
Expand Down Expand Up @@ -1168,26 +1189,26 @@ public static IResource findResource(URI uri, Function<URI, IResource[]> resourc
}
}
switch(resources.length) {
case 0:
return null;
case 1:
return resources[0];
default://several candidates if a linked resource was created before the real project was configured
case 0:
return null;
case 1:
return resources[0];
default://several candidates if a linked resource was created before the real project was configured
IResource resource = null;
for (IResource f : resources) {
//delete linked resource
if (ProjectsManager.getDefaultProject().equals(f.getProject())) {
try {
f.delete(true, null);
} catch (CoreException e) {
//delete linked resource
if (ProjectsManager.getDefaultProject().equals(f.getProject())) {
try {
f.delete(true, null);
} catch (CoreException e) {
JavaLanguageServerPlugin.logException(e.getMessage(), e);
}
}
}
//find closest project containing that file, in case of nested projects
//find closest project containing that file, in case of nested projects
if (resource == null || f.getProjectRelativePath().segmentCount() < resource.getProjectRelativePath().segmentCount()) {
resource = f;
}
}
}
return resource;
}
}
Expand Down Expand Up @@ -1970,3 +1991,4 @@ public static CompilationUnit getAst(ITypeRoot typeRoot, IProgressMonitor monito
}

}

Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*******************************************************************************
* Copyright (c) 2026 Red Hat Inc. and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.ls.core.internal;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.IClassFile;
import org.eclipse.jdt.core.util.IClassFileReader;
import org.eclipse.jdt.core.util.ISourceAttribute;
import org.eclipse.jdt.internal.core.util.ClassFileReader;

/**
* Utility class to read the SourceFile attribute from class files.
* The SourceFile attribute contains the name of the source file from which
* the class was compiled (e.g., "OkHttpClient.kt" for Kotlin classes).
*/
public class SourceFileAttributeReader {

private SourceFileAttributeReader() {
// Utility class - no instantiation
}

/**
* Gets the source file name from the SourceFile attribute of the given class file.
*
* @param classFile the class file to read
* @return the source file name (e.g., "OkHttpClient.kt", "MyClass.java"),
* or null if the attribute is not present or cannot be read
*/
public static String getSourceFileName(IClassFile classFile) {
if (classFile == null) {
return null;
}
try {
return getSourceFileName(classFile.getBytes());
} catch (CoreException e) {
JavaLanguageServerPlugin.logException("Error reading class file bytes", e);
return null;
}
}

/**
* Gets the source file name from the SourceFile attribute of the given class file bytes.
*
* @param classFileBytes the raw bytes of the class file
* @return the source file name (e.g., "OkHttpClient.kt", "MyClass.java"),
* or null if the attribute is not present or cannot be read
*/
public static String getSourceFileName(byte[] classFileBytes) {
if (classFileBytes == null || classFileBytes.length == 0) {
return null;
}

try {
// Use Eclipse JDT's class file reader to parse the class file
IClassFileReader reader = new ClassFileReader(classFileBytes, IClassFileReader.CLASSFILE_ATTRIBUTES);

// Get the SourceFile attribute from the class file
ISourceAttribute sourceFileAttribute = reader.getSourceFileAttribute();
if (sourceFileAttribute == null) {
return null;
}

// Get the source file name from the constant pool
char[] sourceFileName = sourceFileAttribute.getSourceFileName();
if (sourceFileName == null || sourceFileName.length == 0) {
return null;
}

return new String(sourceFileName);
} catch (Exception e) {
JavaLanguageServerPlugin.logException("Error parsing class file format", e);
}
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,20 @@
import org.apache.commons.lang3.StringUtils;

public class ResolveSourceMappingHandler {
private static final Pattern SOURCE_PATTERN = Pattern.compile("([\\w$\\.]+\\/)?(([\\w$]+\\.)+[<\\w$>]+)\\(([\\w-$]+\\.java:\\d+)\\)");
private static final Pattern SOURCE_PATTERN = Pattern.compile(
"([\\w$\\.]+\\/)?(([\\w$]+\\.)+[<\\w$>]+)\\(([\\w-$]+\\.(?:java|kt|groovy|clj|scala)(?::\\d+)?)+\\)"
);
private static final JdtSourceLookUpProvider sourceProvider = new JdtSourceLookUpProvider();

/**
* Given a line of stacktrace, resolve the uri of the source file or class file.
*
*
* @param lineText
* the line of the stacktrace.
* @param projectNames
* A list of the project names that needs to search in. If the given list is empty,
* All the projects in the workspace will be searched.
*
*
* @return the uri of the associated source file or class file.
*/
public static String resolveStackTraceLocation(String lineText, List<String> projectNames) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

import java.net.URI;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
Expand Down Expand Up @@ -153,7 +154,7 @@ private List<ContentProviderDescriptor> findMatchingProviders(URI uri) {
Set<ContentProviderDescriptor> descriptors = getDescriptors(preferredProviderIds);
if (descriptors.isEmpty()) {
JavaLanguageServerPlugin.logError("No content providers found");
return null;
return Collections.emptyList();
}

String uriString = uri != null ? uri.toString() : null;
Expand All @@ -166,7 +167,7 @@ private List<ContentProviderDescriptor> findMatchingProviders(URI uri) {

if (matches.isEmpty()) {
JavaLanguageServerPlugin.logError("Unable to find content provider for URI " + uri);
return null;
return Collections.emptyList();
}

return matches;
Expand Down
10 changes: 10 additions & 0 deletions org.eclipse.jdt.ls.tests/projects/maven/quickstart2/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,16 @@
<version>4.13</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp-jvm</artifactId>
<version>5.3.2</version>
</dependency>
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-actor_2.13</artifactId>
<version>2.8.8</version>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
import com.google.gson.Gson;

public class SourceAttachmentCommandTest extends AbstractProjectsManagerBasedTest {
private static final String classFileUri = "jdt://contents/foo.jar/foo/bar.class?%3Dsource-attachment%2Ffoo.jar%3Cfoo%28bar.class";
private static final String classFileUri = "jdt://contents/foo.jar/foo/bar.java?%3Dsource-attachment%2Ffoo.jar%3Cfoo%28bar.class";
private IProject project;

@BeforeEach
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,8 +189,8 @@ public void outgoing_jar() throws Exception {

String jarUri = call0Calls.get(0).getTo().getUri();
assertTrue(jarUri.startsWith("jdt://"));
assertTrue(jarUri.contains("org.apache.commons.lang3.text"));
assertTrue(jarUri.contains("WordUtils.class"));
assertTrue(jarUri.contains("org.apache.commons.lang3.text/WordUtils.java?"));
assertTrue(jarUri.contains("org.apache.commons.lang3.text(WordUtils.class"));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,31 +21,45 @@

public class ResolveSourceMappingHandlerTest extends AbstractProjectsManagerBasedTest {

@BeforeEach
@BeforeEach
public void setup() throws Exception {
importProjects("maven/quickstart2");
}

@Test
public void testResolveSourceUri() {
String uri = ResolveSourceMappingHandler.resolveStackTraceLocation("at quickstart.AppTest.shouldAnswerWithTrue(AppTest.java:10)", Arrays.asList("quickstart2"));
assertTrue(uri.startsWith("file://"));
assertTrue(uri.contains("quickstart2/src/test/java/quickstart/AppTest.java"));
}

@Test
public void testResolveDependencyUri() {
String uri = ResolveSourceMappingHandler.resolveStackTraceLocation("at org.junit.Assert.assertEquals(Assert.java:117)", Arrays.asList("quickstart2"));
assertTrue(uri.startsWith("jdt://contents/junit-4.13.jar/org.junit/Assert.class"));
}

@Test
public void testResolveSourceUri() {
String uri = ResolveSourceMappingHandler.resolveStackTraceLocation("at quickstart.AppTest.shouldAnswerWithTrue(AppTest.java:10)", Arrays.asList("quickstart2"));
assertTrue(uri.startsWith("file://"));
assertTrue(uri.contains("quickstart2/src/test/java/quickstart/AppTest.java"));
}

@Test
public void testResolveKotlinDerivedSources() {
String uri = ResolveSourceMappingHandler.resolveStackTraceLocation("at okhttp3.OkHttpClient.<init>(OkHttpClient.kt)", Arrays.asList("quickstart2"));
assertTrue(uri.startsWith("jdt://contents/okhttp-jvm-5.3.2.jar/okhttp3/OkHttpClient.kt"), "Unexpected URI: " + uri);
assertTrue(uri.contains("com%5C/squareup%5C/okhttp3%5C/okhttp-jvm%5C/5.3.2%5C/okhttp-jvm-5.3.2.jar"));
}

@Test
public void testResolveScalaDerivedSources() {
String uri = ResolveSourceMappingHandler.resolveStackTraceLocation("at akka.actor.Actor.$init$(Actor.scala:492)", Arrays.asList("quickstart2"));
assertTrue(uri.startsWith("jdt://contents/akka-actor_2.13-2.8.8.jar/akka.actor/Actor.scala"), "Unexpected URI: " + uri);
assertTrue(uri.contains("com%5C/typesafe%5C/akka%5C/akka-actor_2.13%5C/2.8.8%5C/akka-actor_2.13-2.8.8.jar"));
}

@Test
public void testResolveDependencyUri() {
String uri = ResolveSourceMappingHandler.resolveStackTraceLocation("at org.junit.Assert.assertEquals(Assert.java:117)", Arrays.asList("quickstart2"));
assertTrue(uri.startsWith("jdt://contents/junit-4.13.jar/org.junit/Assert.java"));
assertTrue(uri.contains(
"junit%5C/junit%5C/4.13%5C/junit-4.13.jar=/maven.pomderived=/true=/=/test=/true=/=/maven.groupId=/junit=/=/maven.artifactId=/junit=/=/maven.version=/4.13=/=/maven.scope=/test=/=/maven.pomderived=/true=/%3Corg.junit(Assert.class"));
}
}

@Test
public void testResolveDependencyUriWithoutGivingProjectNames() {
String uri = ResolveSourceMappingHandler.resolveStackTraceLocation("at org.junit.Assert.assertEquals(Assert.java:117)", null);
assertTrue(uri.startsWith("jdt://contents/junit-4.13.jar/org.junit/Assert.class"));
@Test
public void testResolveDependencyUriWithoutGivingProjectNames() {
String uri = ResolveSourceMappingHandler.resolveStackTraceLocation("at org.junit.Assert.assertEquals(Assert.java:117)", null);
assertTrue(uri.startsWith("jdt://contents/junit-4.13.jar/org.junit/Assert.java"));
assertTrue(uri.contains(
"junit%5C/4.13%5C/junit-4.13.jar=/maven.pomderived=/true=/=/test=/true=/=/maven.groupId=/junit=/=/maven.artifactId=/junit=/=/maven.version=/4.13=/=/maven.scope=/test=/=/maven.pomderived=/true=/%3Corg.junit(Assert.class"));
}
}
}