Source links via CU DEclaration AST
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
package org.springframework.ide.vscode.boot.java.links;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
import java.util.Stack;
|
||||
|
||||
import org.eclipse.jdt.internal.compiler.ASTVisitor;
|
||||
import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
|
||||
import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
|
||||
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
|
||||
import org.eclipse.jdt.internal.compiler.lookup.BlockScope;
|
||||
import org.eclipse.jdt.internal.compiler.lookup.ClassScope;
|
||||
import org.eclipse.jdt.internal.compiler.lookup.CompilationUnitScope;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache;
|
||||
import org.springframework.ide.vscode.commons.java.IClasspath;
|
||||
import org.springframework.ide.vscode.commons.java.IClasspathUtil;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaModuleData;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.javadoc.TypeUrlProviderFromContainerUrl;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
import org.springframework.ide.vscode.commons.util.text.Region;
|
||||
|
||||
public abstract class AbstractSourceLinks2 implements SourceLinks {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(AbstractSourceLinks2.class);
|
||||
|
||||
private CompilationUnitCache cuCache;
|
||||
|
||||
private JavaProjectFinder projectFinder;
|
||||
|
||||
protected AbstractSourceLinks2(CompilationUnitCache cuCache, JavaProjectFinder projectFinder) {
|
||||
this.cuCache = cuCache;
|
||||
this.projectFinder = projectFinder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> sourceLinkUrlForFQName(IJavaProject project, String fqName) {
|
||||
Optional<String> url = project == null ? Optional.empty() : getSourceLinkUrlForFQName(project, fqName);
|
||||
if (!url.isPresent()) {
|
||||
for (IJavaProject jp : projectFinder.all()) {
|
||||
if (jp != project) {
|
||||
url = getSourceLinkUrlForFQName(jp, fqName);
|
||||
if (url.isPresent()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
private Optional<String> getSourceLinkUrlForFQName(IJavaProject project, String fqName) {
|
||||
IJavaModuleData classpathResource = project.getIndex().findClasspathResourceContainer(fqName);
|
||||
if (classpathResource != null) {
|
||||
File file = classpathResource.getContainer();
|
||||
if (file.isDirectory()) {
|
||||
return javaSourceLinkUrl(project, fqName, classpathResource);
|
||||
} else {
|
||||
return jarSourceLinkUrl(project, fqName, classpathResource);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> sourceLinkUrlForClasspathResource(String path) {
|
||||
return SourceLinks.sourceLinkUrlForClasspathResource(this, projectFinder, path);
|
||||
}
|
||||
|
||||
private Optional<String> javaSourceLinkUrl(IJavaProject project, String fqName, IJavaModuleData folderModuleData) {
|
||||
IClasspath classpath = project.getClasspath();
|
||||
return SourceLinks.sourceFromSourceFolder(fqName, classpath)
|
||||
.map(sourcePath -> javaSourceLinkUrl(project, sourcePath, fqName));
|
||||
}
|
||||
|
||||
private String javaSourceLinkUrl(IJavaProject project, Path sourcePath, String fqName) {
|
||||
Optional<String> linkOptional = sourceLinkForResourcePath(sourcePath);
|
||||
if (linkOptional.isPresent()) {
|
||||
Optional<String> positionLink = findCU(project, sourcePath.toUri()).map(cu -> positionLink(cu, fqName));
|
||||
return positionLink.isPresent() ? linkOptional.get() + positionLink.get() : linkOptional.get();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
abstract protected String positionLink(CompilationUnitDeclaration cu, String fqName);
|
||||
|
||||
private Optional<CompilationUnitDeclaration> findCU(IJavaProject project, URI uri) {
|
||||
return cuCache == null ? Optional.empty() : cuCache.withCompilationUnitDeclaration(project, uri, compilationUnit -> Optional.ofNullable(compilationUnit));
|
||||
}
|
||||
|
||||
abstract protected Optional<String> jarLinkUrl(IJavaProject project, String fqName, IJavaModuleData jarModuleData);
|
||||
|
||||
private Optional<String> jarSourceLinkUrl(IJavaProject project, String fqName, IJavaModuleData jarModuleData) {
|
||||
return jarLinkUrl(project, fqName, jarModuleData).map(sourceUrl -> {
|
||||
Optional<String> positionLink = findCUForFQNameFromJar(project, jarModuleData, fqName).map(cu -> positionLink(cu, fqName));
|
||||
return positionLink.isPresent() ? sourceUrl + positionLink.get() : sourceUrl;
|
||||
});
|
||||
}
|
||||
|
||||
private Optional<CompilationUnitDeclaration> findCUForFQNameFromJar(IJavaProject project, IJavaModuleData jarModuleData, String fqName) {
|
||||
return IClasspathUtil.sourceContainer(project.getClasspath(), jarModuleData.getContainer()).map(url -> {
|
||||
try {
|
||||
return TypeUrlProviderFromContainerUrl.JAR_SOURCE_URL_PROVIDER.url(url, fqName, jarModuleData.getModule());
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to determine source URL from url={} fqName={}", url, fqName, e);
|
||||
return null;
|
||||
}
|
||||
}).map(sourceUrl -> {
|
||||
try {
|
||||
return sourceUrl.toURI();
|
||||
} catch (URISyntaxException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}).map(sourcePath -> findCU(project, sourcePath).orElse(null));
|
||||
}
|
||||
|
||||
protected Region findTypeRegion(CompilationUnitDeclaration cu, String fqName) {
|
||||
if (cu == null) {
|
||||
return null;
|
||||
}
|
||||
int[] values = new int[] {0, -1};
|
||||
int lastDotIndex = fqName.lastIndexOf('.');
|
||||
String packageName = fqName.substring(0, lastDotIndex);
|
||||
String typeName = fqName.substring(lastDotIndex + 1);
|
||||
String[] tokens = new String[cu.currentPackage.tokens.length];
|
||||
for (int i = 0; i < cu.currentPackage.tokens.length; i++) {
|
||||
tokens[i] = new String(cu.currentPackage.tokens[i]);
|
||||
}
|
||||
String cuPackageName = String.join(".", tokens);
|
||||
if (packageName.equals(cuPackageName)) {
|
||||
Stack<String> visitedType = new Stack<>();
|
||||
|
||||
cu.traverse(new ASTVisitor() {
|
||||
|
||||
private boolean visitDeclaration(TypeDeclaration node) {
|
||||
visitedType.push(new String(node.name));
|
||||
if (values[1] < 0) {
|
||||
if (String.join("$", visitedType.toArray(new String[visitedType.size()])).equals(typeName)) {
|
||||
values[0] = node.sourceStart;
|
||||
values[1] = node.sourceEnd;
|
||||
}
|
||||
}
|
||||
return values[1] < 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(TypeDeclaration localTypeDeclaration, BlockScope scope) {
|
||||
return visitDeclaration(localTypeDeclaration);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(TypeDeclaration memberTypeDeclaration, ClassScope scope) {
|
||||
return visitDeclaration(memberTypeDeclaration);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(TypeDeclaration typeDeclaration, CompilationUnitScope scope) {
|
||||
return visitDeclaration(typeDeclaration);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endVisit(TypeDeclaration localTypeDeclaration, BlockScope scope) {
|
||||
visitedType.pop();
|
||||
super.endVisit(localTypeDeclaration, scope);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endVisit(TypeDeclaration memberTypeDeclaration, ClassScope scope) {
|
||||
visitedType.pop();
|
||||
super.endVisit(memberTypeDeclaration, scope);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endVisit(TypeDeclaration typeDeclaration, CompilationUnitScope scope) {
|
||||
visitedType.pop();
|
||||
super.endVisit(typeDeclaration, scope);
|
||||
}
|
||||
|
||||
}, new CompilationUnitScope(cu, new CompilerOptions(CompilationUnitCache.createCompilerOptions())), false);
|
||||
}
|
||||
return values[1] < 0 ? null : new Region(values[0], values[1]);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -55,7 +55,7 @@ public final class SourceLinkFactory {
|
||||
switch (LspClient.currentClient()) {
|
||||
case VSCODE:
|
||||
case THEIA:
|
||||
return /*new VSCodeSourceLinks(cuCache);*/server == null ? new VSCodeSourceLinks(cuCache, projectFinder) :new JavaServerSourceLinks(server, projectFinder);
|
||||
return /*new VSCodeSourceLinks(cuCache);*/server == null ? new VSCodeSourceLinks2(cuCache, projectFinder) :new JavaServerSourceLinks(server, projectFinder);
|
||||
case ECLIPSE:
|
||||
return /*new EclipseSourceLinks();*/server == null ? new EclipseSourceLinks(projectFinder) : new JavaServerSourceLinks(server, projectFinder);
|
||||
case ATOM:
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package org.springframework.ide.vscode.boot.java.links;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
|
||||
import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache;
|
||||
import org.springframework.ide.vscode.boot.java.utils.CuDeclarationUtils;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaModuleData;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
import org.springframework.ide.vscode.commons.util.text.Region;
|
||||
|
||||
public class VSCodeSourceLinks2 extends AbstractSourceLinks2 {
|
||||
|
||||
public VSCodeSourceLinks2(CompilationUnitCache cuCache, JavaProjectFinder projectFinder) {
|
||||
super(cuCache, projectFinder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> sourceLinkForResourcePath(Path path) {
|
||||
return Optional.of(path.toUri().toASCIIString());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String positionLink(CompilationUnitDeclaration cu, String fqName) {
|
||||
if (cu != null) {
|
||||
Region region = findTypeRegion(cu, fqName);
|
||||
if (region != null) {
|
||||
int line = CuDeclarationUtils.getLineNumber(cu, region.getOffset());
|
||||
int column = CuDeclarationUtils.getColumn(cu, region.getOffset());
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append('#');
|
||||
sb.append(line);
|
||||
sb.append(',');
|
||||
sb.append(column + 1); // 1-based columns?
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<String> jarLinkUrl(IJavaProject project, String fqName, IJavaModuleData jarModuleData) {
|
||||
return Optional.ofNullable(JdtJavaDocumentUriProvider.uri(project, fqName)).map(uri -> uri.toASCIIString());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package org.springframework.ide.vscode.boot.java.utils;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
|
||||
|
||||
public class CuDeclarationUtils {
|
||||
|
||||
public static int getLineNumber(CompilationUnitDeclaration cu, int offset) {
|
||||
int insertionIndex = Arrays.binarySearch(cu.compilationResult.lineSeparatorPositions, offset);
|
||||
if (insertionIndex < 0) {
|
||||
return -insertionIndex;
|
||||
}
|
||||
// start lines from 1.
|
||||
return insertionIndex + 1;
|
||||
}
|
||||
|
||||
public static int getColumn(CompilationUnitDeclaration cu, int offset) {
|
||||
int line = getLineNumber(cu, offset);
|
||||
// line start from 1 hence -1 and -1 for getting end of previous line
|
||||
return line == 0 ? offset : offset - cu.compilationResult.lineSeparatorPositions[line - 2] - 1; // -1 at end because offset is for the line separator on the previous line
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,7 +16,7 @@ import org.springframework.context.annotation.Import;
|
||||
import org.springframework.ide.vscode.boot.app.BootLanguageServerParams;
|
||||
import org.springframework.ide.vscode.boot.editor.harness.PropertyIndexHarness;
|
||||
import org.springframework.ide.vscode.boot.java.links.SourceLinks;
|
||||
import org.springframework.ide.vscode.boot.java.links.VSCodeSourceLinks;
|
||||
import org.springframework.ide.vscode.boot.java.links.VSCodeSourceLinks2;
|
||||
import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache;
|
||||
import org.springframework.ide.vscode.boot.java.utils.SymbolCache;
|
||||
import org.springframework.ide.vscode.boot.java.utils.SymbolCacheVoid;
|
||||
@@ -66,7 +66,7 @@ public class SourceLinksTestConf {
|
||||
}
|
||||
|
||||
@Bean SourceLinks sourceLinks(CompilationUnitCache cuCache, JavaProjectFinder projectFinder) {
|
||||
return new VSCodeSourceLinks(cuCache, projectFinder);
|
||||
return new VSCodeSourceLinks2(cuCache, projectFinder);
|
||||
}
|
||||
|
||||
@Bean MockProjectObserver mockProjectObserver(BootLanguageServerParams params) {
|
||||
|
||||
@@ -24,6 +24,7 @@ import org.eclipse.lsp4j.TextDocumentIdentifier;
|
||||
import org.gradle.internal.impldep.com.google.common.collect.ImmutableList;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.ide.vscode.boot.java.links.VSCodeSourceLinks;
|
||||
import org.springframework.ide.vscode.boot.java.links.VSCodeSourceLinks2;
|
||||
import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.javadoc.JavaDocProviders;
|
||||
@@ -61,7 +62,7 @@ public class VSCodeSourceLinksTest {
|
||||
@Test
|
||||
void testJavaSourceUrl() throws Exception {
|
||||
MavenJavaProject project = mavenProjectsCache.get("empty-boot-15-web-app");
|
||||
Optional<String> url = new VSCodeSourceLinks(new CompilationUnitCache(null, null, null), null).sourceLinkUrlForFQName(project, "com.example.EmptyBoot15WebAppApplication");
|
||||
Optional<String> url = new VSCodeSourceLinks2(new CompilationUnitCache(null, null, null), null).sourceLinkUrlForFQName(project, "com.example.EmptyBoot15WebAppApplication");
|
||||
assertTrue(url.isPresent());
|
||||
Path projectPath = Paths.get(project.pom().getParent());
|
||||
URI uri = URI.create(url.get());
|
||||
@@ -78,7 +79,7 @@ public class VSCodeSourceLinksTest {
|
||||
@Test
|
||||
void testClasspathResourceOnTomcatUrl() throws Exception {
|
||||
MavenJavaProject project = mavenProjectsCache.get("empty-boot-15-web-app");
|
||||
Optional<String> url = new VSCodeSourceLinks(new CompilationUnitCache(null, null, null), new JavaProjectFinder() {
|
||||
Optional<String> url = new VSCodeSourceLinks2(new CompilationUnitCache(null, null, null), new JavaProjectFinder() {
|
||||
|
||||
@Override
|
||||
public Optional<IJavaProject> find(TextDocumentIdentifier doc) {
|
||||
@@ -107,7 +108,7 @@ public class VSCodeSourceLinksTest {
|
||||
@Test
|
||||
void testJarUrl() throws Exception {
|
||||
MavenJavaProject project = mavenProjectsCache.get("empty-boot-15-web-app");
|
||||
Optional<String> url = new VSCodeSourceLinks(new CompilationUnitCache(null, null, null), null).sourceLinkUrlForFQName(project, "org.springframework.boot.autoconfigure.SpringBootApplication");
|
||||
Optional<String> url = new VSCodeSourceLinks2(new CompilationUnitCache(null, null, null), null).sourceLinkUrlForFQName(project, "org.springframework.boot.autoconfigure.SpringBootApplication");
|
||||
assertTrue(url.isPresent());
|
||||
String headerPart = url.get().substring(0, url.get().indexOf('?'));
|
||||
assertEquals("jdt://contents/spring-boot-autoconfigure-1.5.8.RELEASE.jar/org.springframework.boot.autoconfigure/SpringBootApplication.class", headerPart);
|
||||
@@ -118,7 +119,7 @@ public class VSCodeSourceLinksTest {
|
||||
@Test
|
||||
void testJarUrlInnerType() throws Exception {
|
||||
MavenJavaProject project = mavenProjectsCache.get("empty-boot-15-web-app");
|
||||
Optional<String> url = new VSCodeSourceLinks(new CompilationUnitCache(null, null, null), null).sourceLinkUrlForFQName(project, "org.springframework.web.client.RestTemplate$AcceptHeaderRequestCallback");
|
||||
Optional<String> url = new VSCodeSourceLinks2(new CompilationUnitCache(null, null, null), null).sourceLinkUrlForFQName(project, "org.springframework.web.client.RestTemplate$AcceptHeaderRequestCallback");
|
||||
assertTrue(url.isPresent());
|
||||
String headerPart = url.get().substring(0, url.get().indexOf('?'));
|
||||
assertEquals("jdt://contents/spring-web-4.3.12.RELEASE.jar/org.springframework.web.client/RestTemplate$AcceptHeaderRequestCallback.class", headerPart);
|
||||
|
||||
Reference in New Issue
Block a user