Navigate to specific position in file via source link
This commit is contained in:
@@ -269,9 +269,9 @@ public class BootJavaLanguageServer extends SimpleLanguageServer {
|
||||
providers.put(Annotations.SPRING_PATCH_MAPPING, new RequestMappingHoverProvider());
|
||||
providers.put(Annotations.PROFILE, new ActiveProfilesProvider());
|
||||
|
||||
providers.put(Annotations.AUTOWIRED, new AutowiredHoverProvider());
|
||||
providers.put(Annotations.COMPONENT, new ComponentInjectionsHoverProvider());
|
||||
providers.put(Annotations.BEAN, new BeanInjectedIntoHoverProvider());
|
||||
providers.put(Annotations.AUTOWIRED, new AutowiredHoverProvider(this));
|
||||
providers.put(Annotations.COMPONENT, new ComponentInjectionsHoverProvider(this));
|
||||
providers.put(Annotations.BEAN, new BeanInjectedIntoHoverProvider(this));
|
||||
|
||||
providers.put(Annotations.CONDITIONAL, new ConditionalsLiveHoverProvider());
|
||||
providers.put(Annotations.CONDITIONAL_ON_BEAN, new ConditionalsLiveHoverProvider());
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.eclipse.lsp4j.Hover;
|
||||
import org.eclipse.lsp4j.Range;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
||||
import org.springframework.ide.vscode.boot.java.Annotations;
|
||||
import org.springframework.ide.vscode.boot.java.BootJavaLanguageServer;
|
||||
import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchies;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.HoverProvider;
|
||||
import org.springframework.ide.vscode.boot.java.livehover.ComponentInjectionsHoverProvider;
|
||||
@@ -41,6 +42,12 @@ import com.google.common.collect.ImmutableList;
|
||||
* @author Martin Lippert
|
||||
*/
|
||||
public class AutowiredHoverProvider implements HoverProvider {
|
||||
|
||||
private BootJavaLanguageServer server;
|
||||
|
||||
public AutowiredHoverProvider(BootJavaLanguageServer server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Range> getLiveHoverHints(Annotation annotation, TextDocument doc, SpringBootApp[] runningApps) {
|
||||
@@ -155,7 +162,7 @@ public class AutowiredHoverProvider implements HoverProvider {
|
||||
}
|
||||
List<LiveBean> dependencyBeans = beans.getBeansOfName(injectedBean);
|
||||
for (LiveBean dependencyBean : dependencyBeans) {
|
||||
hover.append("- " + LiveHoverUtils.showBeanWithResource(dependencyBean, " ", project));
|
||||
hover.append("- " + LiveHoverUtils.showBeanWithResource(server, dependencyBean, " ", project));
|
||||
}
|
||||
firstDependency = false;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,253 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2018 Pivotal, Inc.
|
||||
* 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:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.links;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Optional;
|
||||
import java.util.Stack;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.eclipse.jdt.core.dom.ASTVisitor;
|
||||
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
|
||||
import org.eclipse.jdt.core.dom.AnnotationTypeDeclaration;
|
||||
import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||
import org.eclipse.jdt.core.dom.TypeDeclaration;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ide.vscode.boot.java.BootJavaLanguageServer;
|
||||
import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache;
|
||||
import org.springframework.ide.vscode.commons.java.IClasspath;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.javadoc.SourceUrlProviderFromSourceContainer;
|
||||
import org.springframework.ide.vscode.commons.util.text.Region;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
import com.google.common.base.Supplier;
|
||||
import com.google.common.base.Suppliers;
|
||||
|
||||
/**
|
||||
* Base logic for {@link SourceLinks} independent of any client
|
||||
*
|
||||
* @author Alex Boyko
|
||||
*
|
||||
*/
|
||||
public abstract class AbstractSourceLinks implements SourceLinks {
|
||||
|
||||
private static Supplier<Logger> LOG = Suppliers.memoize(() -> LoggerFactory.getLogger(AbstractSourceLinks.class));
|
||||
|
||||
private BootJavaLanguageServer server;
|
||||
|
||||
protected AbstractSourceLinks(BootJavaLanguageServer server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> sourceLinkUrlForFQName(IJavaProject project, String fqName) {
|
||||
Optional<File> classpathResource = project.getClasspath().findClasspathResourceContainer(fqName);
|
||||
if (classpathResource.isPresent()) {
|
||||
File file = classpathResource.get();
|
||||
if (file.isDirectory()) {
|
||||
return javaSourceLinkUrl(project, fqName, file);
|
||||
} else if (file.getName().endsWith(JAR)) {
|
||||
return jarSourceLinkUrl(project, fqName, file);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> sourceLinkUrlForClasspathResource(IJavaProject project, String path) {
|
||||
int idx = path.lastIndexOf(CLASS);
|
||||
if (idx >= 0) {
|
||||
Path p = Paths.get(path.substring(0, idx));
|
||||
return sourceLinkUrlForFQName(project, p.toString().replace(File.separator, "."));
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private Optional<String> javaSourceLinkUrl(IJavaProject project, String fqName, File containerFolder) {
|
||||
IClasspath classpath = project.getClasspath();
|
||||
if (containerFolder.toPath().startsWith(classpath.getOutputFolder())) {
|
||||
return project.getClasspath().getSourceFolders().stream()
|
||||
.map(sourceFolder -> {
|
||||
try {
|
||||
return Paths.get(sourceFolder).toUri().toURL();
|
||||
} catch (MalformedURLException e) {
|
||||
LOG.get().warn("Failed to convert source folder " + sourceFolder + "to URI." + fqName, e);
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.map(url -> {
|
||||
try {
|
||||
return SourceUrlProviderFromSourceContainer.SOURCE_FOLDER_URL_SUPPLIER.sourceUrl(url, fqName);
|
||||
} catch (Exception e) {
|
||||
LOG.get().warn("Failed to determine source URL from url=" + url + " fqName=" + fqName, e);
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.map(url -> {
|
||||
try {
|
||||
return Paths.get(url.toURI());
|
||||
} catch (URISyntaxException e) {
|
||||
LOG.get().warn("Failed to convert URL " + url + " to path." + fqName, e);
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter(sourcePath -> sourcePath != null && Files.exists(sourcePath))
|
||||
.findFirst()
|
||||
.map(sourcePath -> javaSourceLinkUrl(project, sourcePath, fqName));
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private String javaSourceLinkUrl(IJavaProject project, Path sourcePath, String fqName) {
|
||||
Optional<String> linkOptional = sourceLinkForResourcePath(sourcePath);
|
||||
if (linkOptional.isPresent()) {
|
||||
Optional<String> positionLink = findCUForJavaSourceFile(sourcePath).map(cu -> positionLink(cu, fqName));
|
||||
return positionLink.isPresent() ? linkOptional.get() + positionLink.get() : linkOptional.get();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
abstract protected String positionLink(CompilationUnit cu, String fqName);
|
||||
|
||||
private Optional<CompilationUnit> findCUForJavaSourceFile(Path resourcePath) {
|
||||
Optional<CompilationUnit> cu = findCUfromCache(resourcePath.toUri().toString());
|
||||
if (cu == null) {
|
||||
try {
|
||||
char[] bytes = new String(Files.readAllBytes(resourcePath), Charset.defaultCharset()).toCharArray();
|
||||
String uri = resourcePath.toUri().toString();
|
||||
String unitName = resourcePath.getFileName().toString();
|
||||
cu = Optional.ofNullable(CompilationUnitCache.parse(bytes, uri, unitName, new String[0]));
|
||||
} catch (Exception e) {
|
||||
LOG.get().warn("Failed to create CompilationUnit from " + resourcePath, e);
|
||||
cu = Optional.empty();
|
||||
}
|
||||
}
|
||||
return cu;
|
||||
}
|
||||
|
||||
private Optional<CompilationUnit> findCUfromCache(String uri) {
|
||||
Optional<CompilationUnit> cu = null;
|
||||
if (server != null && server.getCompilationUnitCache() != null) {
|
||||
TextDocument doc = server.getTextDocumentService().get(uri);
|
||||
if (doc != null) {
|
||||
cu = server.getCompilationUnitCache().withCompilationUnit(doc, compilationUnit -> compilationUnit == null ? null : Optional.of(compilationUnit));
|
||||
}
|
||||
}
|
||||
return cu;
|
||||
}
|
||||
|
||||
abstract protected Optional<String> jarUrl(IJavaProject project, String fqName, File jarFile);
|
||||
|
||||
private Optional<String> jarSourceLinkUrl(IJavaProject project, String fqName, File jarFile) {
|
||||
return jarUrl(project, fqName, jarFile).map(sourceUrl -> {
|
||||
Optional<String> positionLink = findCUForFQNameFromJar(project, jarFile, sourceUrl, fqName).map(cu -> positionLink(cu, fqName));
|
||||
return positionLink.isPresent() ? sourceUrl + positionLink.get() : sourceUrl;
|
||||
});
|
||||
}
|
||||
|
||||
private Optional<CompilationUnit> findCUForFQNameFromJar(IJavaProject project, File jarFile, String clientSourceUri, String fqName) {
|
||||
Optional<CompilationUnit> cu = findCUfromCache(clientSourceUri);
|
||||
if (cu == null) {
|
||||
cu = project.getClasspath().sourceContainer(jarFile)
|
||||
.map(url -> {
|
||||
try {
|
||||
return SourceUrlProviderFromSourceContainer.JAR_SOURCE_URL_PROVIDER.sourceUrl(url, fqName);
|
||||
} catch (Exception e) {
|
||||
LOG.get().warn("Failed to determine source URL from url=" + url + " fqName=" + fqName, e);
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.map(sourceUrl -> {
|
||||
InputStream openStream = null;
|
||||
try {
|
||||
openStream = sourceUrl.openStream();
|
||||
char[] bytes = IOUtils.toCharArray(openStream);
|
||||
String uri = sourceUrl.toURI().toString();
|
||||
String unitName = fqName;
|
||||
return CompilationUnitCache.parse(bytes, uri, unitName, new String[0]);
|
||||
} catch (Exception e) {
|
||||
LOG.get().warn("Failed to create CompilationUnit from " + sourceUrl, e);
|
||||
return null;
|
||||
} finally {
|
||||
if (openStream != null) {
|
||||
try {
|
||||
openStream.close();
|
||||
} catch (IOException e) {
|
||||
LOG.get().error("Failed to close stream from " + sourceUrl, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return cu;
|
||||
}
|
||||
|
||||
protected Region findTypeRegion(CompilationUnit 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);
|
||||
if (packageName.equals(cu.getPackage().getName().getFullyQualifiedName())) {
|
||||
Stack<String> visitedType = new Stack<>();
|
||||
cu.accept(new ASTVisitor() {
|
||||
|
||||
private boolean visitDeclaration(AbstractTypeDeclaration node) {
|
||||
visitedType.push(node.getName().getIdentifier());
|
||||
if (values[1] < 0) {
|
||||
if (String.join("$", visitedType.toArray(new String[visitedType.size()])).equals(typeName)) {
|
||||
values[0] = node.getName().getStartPosition();
|
||||
values[1] = node.getName().getLength();
|
||||
}
|
||||
}
|
||||
return values[1] < 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(TypeDeclaration node) {
|
||||
return visitDeclaration(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(AnnotationTypeDeclaration node) {
|
||||
return visitDeclaration(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endVisit(AnnotationTypeDeclaration node) {
|
||||
visitedType.pop();
|
||||
super.endVisit(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endVisit(TypeDeclaration node) {
|
||||
visitedType.pop();
|
||||
super.endVisit(node);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
return values[1] < 0 ? null : new Region(values[0], values[1]);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2018 Pivotal, Inc.
|
||||
* 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:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.links;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.ide.vscode.boot.java.BootJavaLanguageServer;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.LspClient;
|
||||
|
||||
/**
|
||||
* Factory for creating {@link SourceLinks}
|
||||
*
|
||||
* @author Alex Boyko
|
||||
*
|
||||
*/
|
||||
public final class SourceLinkFactory {
|
||||
|
||||
private static final SourceLinks NO_SOURCE_LINKS = new SourceLinks() {
|
||||
|
||||
@Override
|
||||
public Optional<String> sourceLinkUrlForFQName(IJavaProject project, String fqName) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> sourceLinkUrlForClasspathResource(IJavaProject project, String path) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> sourceLinkForResourcePath(Path path) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates {@link SourceLinks} for specific server based on client type
|
||||
* @param server the boot LS
|
||||
* @return appropriate source links object
|
||||
*/
|
||||
public static SourceLinks createSourceLinks(BootJavaLanguageServer server) {
|
||||
switch (LspClient.currentClient()) {
|
||||
case VSCODE:
|
||||
return new VSCodeSourceLinks(server);
|
||||
default:
|
||||
return NO_SOURCE_LINKS;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2018 Pivotal, Inc.
|
||||
* 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:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.links;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
|
||||
/**
|
||||
* Instance is able to provide client specific URL links to navigate to a
|
||||
* specific file on the client given various types of data such as fully
|
||||
* qualified java type name or classpath resource or just some file resource
|
||||
* given by its path.
|
||||
*
|
||||
* @author Alex Boyko
|
||||
*
|
||||
*/
|
||||
public interface SourceLinks {
|
||||
|
||||
static final String JAR = ".jar";
|
||||
static final String CLASS = ".class";
|
||||
|
||||
/**
|
||||
* Creates link to source file defining the type passed with it's fully qualified name
|
||||
* @param project Java project in the context of which source file link is calculated
|
||||
* @param fqName type's fully qualified name
|
||||
* @return the link URL optional
|
||||
*/
|
||||
Optional<String> sourceLinkUrlForFQName(IJavaProject project, String fqName);
|
||||
|
||||
/**
|
||||
* Creates link to source file corresponding to a classpath resource
|
||||
* @param project project Java project in the context of which source file link is calculated
|
||||
* @param path the path to the classpath resource
|
||||
* @return the link URL optional
|
||||
*/
|
||||
Optional<String> sourceLinkUrlForClasspathResource(IJavaProject project, String path);
|
||||
|
||||
/**
|
||||
* Creates link to a file specified by it's path
|
||||
* @param path the resource path
|
||||
* @return the link URL optional
|
||||
*/
|
||||
Optional<String> sourceLinkForResourcePath(Path path);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2018 Pivotal, Inc.
|
||||
* 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:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.links;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ide.vscode.boot.java.BootJavaLanguageServer;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.util.text.Region;
|
||||
|
||||
import com.google.common.base.Supplier;
|
||||
import com.google.common.base.Suppliers;
|
||||
|
||||
/**
|
||||
* VSCode specific source links implementation
|
||||
*
|
||||
* @author Alex Boyko
|
||||
*
|
||||
*/
|
||||
public class VSCodeSourceLinks extends AbstractSourceLinks {
|
||||
|
||||
private static Supplier<Logger> LOG = Suppliers.memoize(() -> LoggerFactory.getLogger(AbstractSourceLinks.class));
|
||||
|
||||
public VSCodeSourceLinks(BootJavaLanguageServer server) {
|
||||
super(server);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> sourceLinkForResourcePath(Path path) {
|
||||
return Optional.of(path.toUri().toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String positionLink(CompilationUnit cu, String fqName) {
|
||||
if (cu != null) {
|
||||
Region region = findTypeRegion(cu, fqName);
|
||||
if (region != null) {
|
||||
int column = cu.getColumnNumber(region.getOffset());
|
||||
int line = cu.getLineNumber(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> jarUrl(IJavaProject project, String fqName, File jarFile) {
|
||||
try {
|
||||
int lastDotIndex = fqName.lastIndexOf('.');
|
||||
String packageName = fqName.substring(0, lastDotIndex);
|
||||
String typeName = fqName.substring(lastDotIndex + 1);
|
||||
String jarFileName = jarFile.getName();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("jdt://contents/");
|
||||
sb.append(jarFileName);
|
||||
sb.append("/");
|
||||
sb.append(packageName);
|
||||
sb.append("/");
|
||||
sb.append(typeName);
|
||||
sb.append(CLASS);
|
||||
sb.append("?");
|
||||
|
||||
StringBuilder query = new StringBuilder();
|
||||
query.append("=");
|
||||
query.append(project.getElementName());
|
||||
query.append("/");
|
||||
String convertedPath = jarFile.toString().replace(File.separator, "\\/");
|
||||
query.append(convertedPath);
|
||||
query.append("<");
|
||||
query.append(packageName);
|
||||
query.append("(");
|
||||
query.append(typeName);
|
||||
query.append(CLASS);
|
||||
|
||||
sb.append(URLEncoder.encode(query.toString(), "UTF8"));
|
||||
|
||||
return Optional.of(sb.toString());
|
||||
} catch (Throwable t) {
|
||||
LOG.get().warn("Failed creating source URI for jar " + jarFile + " type " + fqName + " in the context of project " + project.getElementName(), t);
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Pivotal, Inc.
|
||||
* Copyright (c) 2017, 2018 Pivotal, Inc.
|
||||
* 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
|
||||
@@ -22,6 +22,7 @@ import org.eclipse.jdt.core.dom.ITypeBinding;
|
||||
import org.eclipse.lsp4j.Hover;
|
||||
import org.eclipse.lsp4j.Range;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
||||
import org.springframework.ide.vscode.boot.java.BootJavaLanguageServer;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.HoverProvider;
|
||||
import org.springframework.ide.vscode.boot.java.utils.ASTUtils;
|
||||
import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp;
|
||||
@@ -34,6 +35,12 @@ import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
public abstract class AbstractInjectedIntoHoverProvider implements HoverProvider {
|
||||
|
||||
protected BootJavaLanguageServer server;
|
||||
|
||||
public AbstractInjectedIntoHoverProvider(BootJavaLanguageServer server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Range> getLiveHoverHints(Annotation annotation, TextDocument doc, SpringBootApp[] runningApps) {
|
||||
@@ -113,7 +120,7 @@ public abstract class AbstractInjectedIntoHoverProvider implements HoverProvider
|
||||
if (!firstDependency) {
|
||||
hover.append("\n");
|
||||
}
|
||||
hover.append("- " + LiveHoverUtils.showBeanWithResource(dependingBean, " ", project));
|
||||
hover.append("- " + LiveHoverUtils.showBeanWithResource(server, dependingBean, " ", project));
|
||||
firstDependency = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import org.eclipse.jdt.core.dom.MethodDeclaration;
|
||||
import org.eclipse.jdt.core.dom.TypeDeclaration;
|
||||
import org.eclipse.lsp4j.Hover;
|
||||
import org.eclipse.lsp4j.Range;
|
||||
import org.springframework.ide.vscode.boot.java.BootJavaLanguageServer;
|
||||
import org.springframework.ide.vscode.boot.java.utils.ASTUtils;
|
||||
import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp;
|
||||
import org.springframework.ide.vscode.commons.boot.app.cli.livebean.LiveBean;
|
||||
@@ -29,6 +30,10 @@ import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
public class BeanInjectedIntoHoverProvider extends AbstractInjectedIntoHoverProvider {
|
||||
|
||||
public BeanInjectedIntoHoverProvider(BootJavaLanguageServer server) {
|
||||
super(server);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LiveBean getDefinedBean(Annotation annotation) {
|
||||
MethodDeclaration beanMethod = ASTUtils.getAnnotatedMethod(annotation);
|
||||
|
||||
@@ -26,6 +26,7 @@ import org.eclipse.lsp4j.Hover;
|
||||
import org.eclipse.lsp4j.Range;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
||||
import org.springframework.ide.vscode.boot.java.Annotations;
|
||||
import org.springframework.ide.vscode.boot.java.BootJavaLanguageServer;
|
||||
import org.springframework.ide.vscode.boot.java.utils.ASTUtils;
|
||||
import org.springframework.ide.vscode.boot.java.utils.FunctionUtils;
|
||||
import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp;
|
||||
@@ -43,6 +44,10 @@ import reactor.util.function.Tuple3;
|
||||
|
||||
public class ComponentInjectionsHoverProvider extends AbstractInjectedIntoHoverProvider {
|
||||
|
||||
public ComponentInjectionsHoverProvider(BootJavaLanguageServer server) {
|
||||
super(server);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addAutomaticallyWiredContructor(StringBuilder hover, Annotation annotation, LiveBeansModel beans, LiveBean bean, IJavaProject project) {
|
||||
TypeDeclaration typeDecl = ASTUtils.findDeclaringType(annotation);
|
||||
@@ -63,7 +68,7 @@ public class ComponentInjectionsHoverProvider extends AbstractInjectedIntoHoverP
|
||||
}
|
||||
List<LiveBean> dependencyBeans = beans.getBeansOfName(injectedBean);
|
||||
for (LiveBean dependencyBean : dependencyBeans) {
|
||||
hover.append("- " + LiveHoverUtils.showBeanWithResource(dependencyBean, " ", project));
|
||||
hover.append("- " + LiveHoverUtils.showBeanWithResource(server, dependencyBean, " ", project));
|
||||
}
|
||||
firstDependency = false;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,9 @@ package org.springframework.ide.vscode.boot.java.livehover;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.ide.vscode.boot.java.utils.SourceLinks;
|
||||
import org.springframework.ide.vscode.boot.java.BootJavaLanguageServer;
|
||||
import org.springframework.ide.vscode.boot.java.links.SourceLinkFactory;
|
||||
import org.springframework.ide.vscode.boot.java.links.SourceLinks;
|
||||
import org.springframework.ide.vscode.boot.java.utils.SpringResource;
|
||||
import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp;
|
||||
import org.springframework.ide.vscode.commons.boot.app.cli.livebean.LiveBean;
|
||||
@@ -23,7 +25,7 @@ import org.springframework.ide.vscode.commons.util.Renderables;
|
||||
import org.springframework.ide.vscode.commons.util.StringUtil;
|
||||
|
||||
public class LiveHoverUtils {
|
||||
|
||||
|
||||
public static String showBean(LiveBean bean) {
|
||||
StringBuilder buf = new StringBuilder("Bean [id: " + bean.getId());
|
||||
String type = bean.getType(true);
|
||||
@@ -34,18 +36,19 @@ public class LiveHoverUtils {
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
public static String showBeanWithResource(LiveBean bean, String indentStr, IJavaProject project) {
|
||||
public static String showBeanWithResource(BootJavaLanguageServer server, LiveBean bean, String indentStr, IJavaProject project) {
|
||||
String newline = " \n"+indentStr; //Note: the double space before newline makes markdown see it as a real line break
|
||||
|
||||
String type = bean.getType(true);
|
||||
|
||||
StringBuilder buf = new StringBuilder("Bean: ");
|
||||
buf.append(bean.getId());
|
||||
SourceLinks sourceLinks = SourceLinkFactory.createSourceLinks(server);
|
||||
if (type != null) {
|
||||
// Try creating a URL link to open source for the type
|
||||
buf.append(newline);
|
||||
buf.append("Type: ");
|
||||
Optional<String> url = SourceLinks.sourceLinkUrlForFQName(project, type);
|
||||
Optional<String> url = sourceLinks.sourceLinkUrlForFQName(project, type);
|
||||
if (url.isPresent()) {
|
||||
buf.append(Renderables.link(type, url.get()).toMarkdown());
|
||||
} else {
|
||||
@@ -56,13 +59,13 @@ public class LiveHoverUtils {
|
||||
if (StringUtil.hasText(resource)) {
|
||||
buf.append(newline);
|
||||
buf.append("Resource: ");
|
||||
buf.append(showResource(resource, project));
|
||||
buf.append(showResource(sourceLinks, resource, project));
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
public static String showResource(String resource, IJavaProject project) {
|
||||
return new SpringResource(resource, project).toMarkdown();
|
||||
public static String showResource(SourceLinks sourceLinks, String resource, IJavaProject project) {
|
||||
return new SpringResource(sourceLinks, resource, project).toMarkdown();
|
||||
}
|
||||
|
||||
public static String niceAppName(SpringBootApp app) {
|
||||
|
||||
@@ -133,6 +133,14 @@ public final class CompilationUnitCache {
|
||||
}
|
||||
|
||||
public static CompilationUnit parse(TextDocument document, IJavaProject project) throws Exception {
|
||||
String[] classpathEntries = getClasspathEntries(document, project);
|
||||
String docURI = document.getUri();
|
||||
String unitName = docURI.substring(docURI.lastIndexOf("/"));
|
||||
char[] source = document.get(0, document.getLength()).toCharArray();
|
||||
return parse(source, docURI, unitName, classpathEntries);
|
||||
}
|
||||
|
||||
public static CompilationUnit parse(char[] source, String docURI, String unitName, String[] classpathEntries) throws Exception {
|
||||
ASTParser parser = ASTParser.newParser(AST.JLS9);
|
||||
Map<String, String> options = JavaCore.getOptions();
|
||||
JavaCore.setComplianceOptions(JavaCore.VERSION_1_8, options);
|
||||
@@ -142,14 +150,11 @@ public final class CompilationUnitCache {
|
||||
parser.setBindingsRecovery(true);
|
||||
parser.setResolveBindings(true);
|
||||
|
||||
String[] classpathEntries = getClasspathEntries(document, project);
|
||||
String[] sourceEntries = new String[] {};
|
||||
parser.setEnvironment(classpathEntries, sourceEntries, null, true);
|
||||
|
||||
String docURI = document.getUri();
|
||||
String unitName = docURI.substring(docURI.lastIndexOf("/"));
|
||||
parser.setUnitName(unitName);
|
||||
parser.setSource(document.get(0, document.getLength()).toCharArray());
|
||||
parser.setSource(source);
|
||||
|
||||
CompilationUnit cu = (CompilationUnit) parser.createAST(null);
|
||||
|
||||
|
||||
@@ -1,180 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2018 Pivotal, Inc.
|
||||
* 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:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.utils;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.ide.vscode.commons.java.IClasspath;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.LspClient;
|
||||
import org.springframework.ide.vscode.commons.util.Log;
|
||||
|
||||
/**
|
||||
* Utility for creating code navigate links for Markdown format documentation.
|
||||
* The utility looks at the environment variable "sts.lsp.client" to create a URL supported on a specific client
|
||||
*
|
||||
* @author Alex Boyko
|
||||
*
|
||||
*/
|
||||
public class SourceLinks {
|
||||
|
||||
static final String JAR = ".jar";
|
||||
static final String JAVA = ".java";
|
||||
static final String CLASS = ".class";
|
||||
|
||||
public static Optional<String> sourceLinkUrlForFQName(IJavaProject project, String fqName) {
|
||||
switch (LspClient.currentClient()) {
|
||||
case VSCODE:
|
||||
return createVSCodeSourceLink(project, fqName);
|
||||
default:
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
public static Optional<String> sourceLinkUrlForClasspathResource(IJavaProject project, String path) {
|
||||
switch (LspClient.currentClient()) {
|
||||
case VSCODE:
|
||||
return createVSCodeSourceLinkForClasspathResource(project, path);
|
||||
default:
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
public static Optional<String> sourceLinkForResourcePath(Path path) {
|
||||
switch (LspClient.currentClient()) {
|
||||
case VSCODE:
|
||||
return Optional.of(createVSCodeLink(path));
|
||||
default:
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
private static Optional<String> createVSCodeSourceLinkForClasspathResource(IJavaProject project, String path) {
|
||||
int idx = path.lastIndexOf(SourceLinks.CLASS);
|
||||
if (idx >= 0) {
|
||||
Path p = Paths.get(path.substring(0, idx));
|
||||
return SourceLinks.sourceLinkUrlForFQName(project, p.toString().replace(File.separator, "."));
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private static String createVSCodeLink(Path path) {
|
||||
return path.toUri().toString();
|
||||
}
|
||||
|
||||
private static Optional<String> createVSCodeSourceLink(IJavaProject project, String fqName) {
|
||||
Optional<File> classpathResource = project.getClasspath().findClasspathResourceContainer(fqName);
|
||||
if (classpathResource.isPresent()) {
|
||||
File file = classpathResource.get();
|
||||
if (file.isDirectory()) {
|
||||
return javaSourceLinkUrl(project, fqName, file);
|
||||
} else if (file.getName().endsWith(JAR)) {
|
||||
return jarSourceLinkUrl(project, fqName, file);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private static Optional<String> javaSourceLinkUrl(IJavaProject project, String javaRelativePath, String fqName) {
|
||||
Path path = Paths.get(javaRelativePath);
|
||||
Optional<Path> sourceResource = project.getClasspath().getSourceFolders().stream()
|
||||
.map(r -> Paths.get(r).resolve(path)).filter(r -> Files.exists(r)).findFirst();
|
||||
if (sourceResource.isPresent()) {
|
||||
return Optional.of(sourceResource.get().toUri().toString());
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private static Optional<String> javaSourceLinkUrl(IJavaProject project, String fqName, File containerFolder) {
|
||||
IClasspath classpath = project.getClasspath();
|
||||
if (containerFolder.toPath().startsWith(classpath.getOutputFolder())) {
|
||||
int innerTypeIdx = fqName.indexOf('$');
|
||||
String topLevelType = innerTypeIdx > 0 ? fqName.substring(0, innerTypeIdx) : fqName;
|
||||
return javaSourceLinkUrl(project, topLevelType.replaceAll("\\.", "/") + JAVA, fqName);
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private static Optional<String> jarSourceLinkUrl(IJavaProject project, String fqName, File jarFile) {
|
||||
try {
|
||||
int lastDotIndex = fqName.lastIndexOf('.');
|
||||
String packageName = fqName.substring(0, lastDotIndex);
|
||||
String typeName = fqName.substring(lastDotIndex + 1);
|
||||
String jarFileName = jarFile.getName();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("jdt://contents/");
|
||||
sb.append(jarFileName);
|
||||
sb.append("/");
|
||||
sb.append(packageName);
|
||||
sb.append("/");
|
||||
sb.append(typeName);
|
||||
sb.append(CLASS);
|
||||
sb.append("?");
|
||||
|
||||
StringBuilder query = new StringBuilder();
|
||||
query.append("=");
|
||||
query.append(project.getElementName());
|
||||
query.append("/");
|
||||
String convertedPath = jarFile.toString().replace(File.separator, "\\/");
|
||||
query.append(convertedPath);
|
||||
query.append("<");
|
||||
query.append(packageName);
|
||||
query.append("(");
|
||||
query.append(typeName);
|
||||
query.append(CLASS);
|
||||
|
||||
sb.append(URLEncoder.encode(query.toString(), "UTF8"));
|
||||
|
||||
return Optional.of(sb.toString());
|
||||
} catch (Throwable t) {
|
||||
Log.log(t);
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
// private static Optional<Region> findTypeRange(CompilationUnit cu, String fqName) {
|
||||
// int[] values = new int[] {0, -1};
|
||||
// int lastDotIndex = fqName.lastIndexOf('.');
|
||||
// String packageName = fqName.substring(0, lastDotIndex);
|
||||
// String typeName = fqName.substring(lastDotIndex + 1);
|
||||
// if (packageName.equals(cu.getPackage().getName().getFullyQualifiedName())) {
|
||||
// Stack<String> visitedType = new Stack<>();
|
||||
// cu.accept(new ASTVisitor() {
|
||||
//
|
||||
// @Override
|
||||
// public boolean visit(TypeDeclaration node) {
|
||||
// if (values[1] < 0) {
|
||||
// visitedType.push(node.getName().getIdentifier());
|
||||
// if (String.join("$", visitedType.toArray(new String[visitedType.size()])).equals(typeName)) {
|
||||
// values[0] = node.getName().getStartPosition();
|
||||
// values[1] = node.getName().getLength();
|
||||
// }
|
||||
// }
|
||||
// return values[1] < 0;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void endVisit(TypeDeclaration node) {
|
||||
// visitedType.pop();
|
||||
// super.endVisit(node);
|
||||
// }
|
||||
//
|
||||
// });
|
||||
// }
|
||||
// return Optional.ofNullable(values[1] < 0 ? null : new Region(values[0], values[1]));
|
||||
// }
|
||||
|
||||
}
|
||||
@@ -10,16 +10,15 @@
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.utils;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Optional;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.ide.vscode.boot.java.links.SourceLinks;
|
||||
import org.springframework.ide.vscode.commons.java.IClasspath;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.util.Renderable;
|
||||
import org.springframework.ide.vscode.commons.util.Renderables;
|
||||
|
||||
/**
|
||||
@@ -31,13 +30,15 @@ public class SpringResource {
|
||||
private static final String FILE = "file";
|
||||
private static final String CLASS_PATH_RESOURCE = "class path resource";
|
||||
|
||||
private SourceLinks sourceLinks;
|
||||
private String type;
|
||||
private String path;
|
||||
private IJavaProject project;
|
||||
|
||||
private static final Pattern BRACKETS = Pattern.compile("\\[[^\\]]*\\]");
|
||||
|
||||
public SpringResource(String toParse, IJavaProject project) {
|
||||
public SpringResource(SourceLinks sourceLinks, String toParse, IJavaProject project) {
|
||||
this.sourceLinks = sourceLinks;
|
||||
this.project = project;
|
||||
Matcher matcher = BRACKETS.matcher(toParse);
|
||||
if (matcher.find()) {
|
||||
@@ -57,15 +58,15 @@ public class SpringResource {
|
||||
case FILE:
|
||||
String relativePath = projectRelativePath(path);
|
||||
if (relativePath != path && path.endsWith(SourceLinks.CLASS)) {
|
||||
linkUrl = SourceLinks.sourceLinkUrlForClasspathResource(project, relativePath);
|
||||
linkUrl = sourceLinks.sourceLinkUrlForClasspathResource(project, relativePath);
|
||||
} else {
|
||||
linkUrl = SourceLinks.sourceLinkForResourcePath(Paths.get(path));
|
||||
linkUrl = sourceLinks.sourceLinkForResourcePath(Paths.get(path));
|
||||
}
|
||||
// not a project relative path
|
||||
return linkUrl.isPresent() ? Renderables.link(relativePath, linkUrl.get()).toMarkdown()
|
||||
: "`" + projectRelativePath(path) + "`";
|
||||
case CLASS_PATH_RESOURCE:
|
||||
linkUrl = SourceLinks.sourceLinkUrlForClasspathResource(project, path);
|
||||
linkUrl = sourceLinks.sourceLinkUrlForClasspathResource(project, path);
|
||||
return linkUrl.isPresent() ? Renderables.link(path, linkUrl.get()).toMarkdown() : "`"+path+"`";
|
||||
default:
|
||||
return path;
|
||||
|
||||
@@ -21,7 +21,7 @@ import java.util.Optional;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.ide.vscode.boot.java.utils.SourceLinks;
|
||||
import org.springframework.ide.vscode.boot.java.links.VSCodeSourceLinks;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.LspClient;
|
||||
import org.springframework.ide.vscode.commons.maven.MavenBuilder;
|
||||
import org.springframework.ide.vscode.commons.maven.MavenCore;
|
||||
@@ -63,29 +63,35 @@ public class VSCodeSourceLinksTest {
|
||||
@Test
|
||||
public void testJavaSourceUrl() throws Exception {
|
||||
MavenJavaProject project = mavenProjectsCache.get("empty-boot-15-web-app");
|
||||
Optional<String> url = SourceLinks.sourceLinkUrlForFQName(project, "com.example.EmptyBoot15WebAppApplication");
|
||||
Optional<String> url = new VSCodeSourceLinks(null).sourceLinkUrlForFQName(project, "com.example.EmptyBoot15WebAppApplication");
|
||||
assertTrue(url.isPresent());
|
||||
Path projectPath = Paths.get(project.pom().getParent());
|
||||
Path relativePath = projectPath.relativize(Paths.get(new URL(url.get()).toURI()));
|
||||
Path relativePath = projectPath.relativize(Paths.get(new URL(url.get()).getPath()));
|
||||
assertEquals(Paths.get("src/main/java/com/example/EmptyBoot15WebAppApplication.java"), relativePath);
|
||||
String positionPart = url.get().substring(url.get().lastIndexOf('#'));
|
||||
assertEquals("#7,14", positionPart);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJarUrl() throws Exception {
|
||||
MavenJavaProject project = mavenProjectsCache.get("empty-boot-15-web-app");
|
||||
Optional<String> url = SourceLinks.sourceLinkUrlForFQName(project, "org.springframework.boot.autoconfigure.SpringBootApplication");
|
||||
Optional<String> url = new VSCodeSourceLinks(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);
|
||||
String positionPart = url.get().substring(url.get().lastIndexOf('#'));
|
||||
assertEquals("#55,19", positionPart);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJarUrlInnerType() throws Exception {
|
||||
MavenJavaProject project = mavenProjectsCache.get("empty-boot-15-web-app");
|
||||
Optional<String> url = SourceLinks.sourceLinkUrlForFQName(project, "org.springframework.web.client.RestTemplate$AcceptHeaderRequestCallback");
|
||||
Optional<String> url = new VSCodeSourceLinks(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);
|
||||
String positionPart = url.get().substring(url.get().lastIndexOf('#'));
|
||||
assertEquals("#747,16", positionPart);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ public class GradleProjectClasspath extends JandexClasspath {
|
||||
URL javadocUrl = new URL("https://docs.oracle.com/javase/" + javaVersion + "/docs/api/");
|
||||
return new HtmlJavadocProvider(
|
||||
(type) -> SourceUrlProviderFromSourceContainer.JAVADOC_FOLDER_URL_SUPPLIER
|
||||
.sourceUrl(javadocUrl, type));
|
||||
.sourceUrl(javadocUrl, type.getFullyQualifiedName()));
|
||||
} catch (MalformedURLException e) {
|
||||
Log.log(e);
|
||||
return null;
|
||||
@@ -150,7 +150,7 @@ public class GradleProjectClasspath extends JandexClasspath {
|
||||
if (classpathFolder.isPresent()) {
|
||||
return new ParserJavadocProvider(type -> {
|
||||
return SourceUrlProviderFromSourceContainer.SOURCE_FOLDER_URL_SUPPLIER
|
||||
.sourceUrl(classpathFolder.get().toURI().toURL(), type);
|
||||
.sourceUrl(classpathFolder.get().toURI().toURL(), type.getFullyQualifiedName());
|
||||
});
|
||||
|
||||
}
|
||||
@@ -248,4 +248,9 @@ public class GradleProjectClasspath extends JandexClasspath {
|
||||
return ClasspathData.from(getName(), getClasspathEntries(), getClasspathResources(), getOutputFolder());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<URL> sourceContainer(File classpathResource) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
package org.springframework.ide.vscode.commons.java;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.Callable;
|
||||
@@ -178,5 +179,11 @@ public class DelegatingCachedClasspath<T extends IClasspath> implements IClasspa
|
||||
t.reindex();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<URL> sourceContainer(File classpathResource) {
|
||||
T t = cachedClasspath.get();
|
||||
return t == null ? Optional.empty() : t.sourceContainer(classpathResource);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
package org.springframework.ide.vscode.commons.java;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
@@ -64,4 +65,6 @@ public interface IClasspath {
|
||||
ClasspathData createClasspathData() throws Exception;
|
||||
|
||||
void reindex();
|
||||
|
||||
Optional<URL> sourceContainer(File classpathResource);
|
||||
}
|
||||
|
||||
@@ -14,51 +14,54 @@ package org.springframework.ide.vscode.commons.javadoc;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import org.springframework.ide.vscode.commons.java.IType;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface SourceUrlProviderFromSourceContainer {
|
||||
|
||||
public static final SourceUrlProviderFromSourceContainer JAR_SOURCE_URL_PROVIDER = (sourceContainerUrl, type) -> {
|
||||
static String extractTopLevelType(String fqName) {
|
||||
int innerTypeIdx = fqName.indexOf('$');
|
||||
return innerTypeIdx > 0 ? fqName.substring(0, innerTypeIdx) : fqName;
|
||||
}
|
||||
|
||||
public static final SourceUrlProviderFromSourceContainer JAR_SOURCE_URL_PROVIDER = (sourceContainerUrl, fqName) -> {
|
||||
StringBuilder sourceUrlStr = new StringBuilder();
|
||||
sourceUrlStr.append("jar:");
|
||||
sourceUrlStr.append(sourceContainerUrl);
|
||||
sourceUrlStr.append("!");
|
||||
sourceUrlStr.append('/');
|
||||
sourceUrlStr.append(type.getFullyQualifiedName().replaceAll("\\.", "/"));
|
||||
sourceUrlStr.append(extractTopLevelType(fqName).replaceAll("\\.", "/"));
|
||||
sourceUrlStr.append(".java");
|
||||
return new URL(sourceUrlStr.toString());
|
||||
|
||||
};
|
||||
|
||||
public static final SourceUrlProviderFromSourceContainer SOURCE_FOLDER_URL_SUPPLIER = (sourceContainerUrl, type) -> {
|
||||
return Paths.get(sourceContainerUrl.toURI()).resolve(type.getFullyQualifiedName().replaceAll("\\.", "/") + ".java").toUri().toURL();
|
||||
public static final SourceUrlProviderFromSourceContainer SOURCE_FOLDER_URL_SUPPLIER = (sourceContainerUrl, fqName) -> {
|
||||
return Paths.get(sourceContainerUrl.toURI()).resolve(extractTopLevelType(fqName).replaceAll("\\.", "/") + ".java").toUri().toURL();
|
||||
};
|
||||
|
||||
public static final SourceUrlProviderFromSourceContainer JAR_JAVADOC_URL_PROVIDER = (javadocContainerUrl, type) -> {
|
||||
public static final SourceUrlProviderFromSourceContainer JAR_JAVADOC_URL_PROVIDER = (javadocContainerUrl, fqName) -> {
|
||||
StringBuilder sourceUrlStr = new StringBuilder();
|
||||
sourceUrlStr.append("jar:");
|
||||
sourceUrlStr.append(javadocContainerUrl);
|
||||
sourceUrlStr.append("!");
|
||||
sourceUrlStr.append('/');
|
||||
// Inner classes are in separate Top.Nesting1.Nesting2.Nesting3.MyType.html files
|
||||
sourceUrlStr.append(type.getFullyQualifiedName().replaceAll("\\.", "/").replaceAll("\\$", "."));
|
||||
sourceUrlStr.append(fqName.replaceAll("\\.", "/").replaceAll("\\$", "."));
|
||||
sourceUrlStr.append(".html");
|
||||
return new URL(sourceUrlStr.toString());
|
||||
|
||||
};
|
||||
|
||||
public static final SourceUrlProviderFromSourceContainer JAVADOC_FOLDER_URL_SUPPLIER = (sourceContainerUrl, type) -> {
|
||||
public static final SourceUrlProviderFromSourceContainer JAVADOC_FOLDER_URL_SUPPLIER = (sourceContainerUrl, fqName) -> {
|
||||
String urlStr = sourceContainerUrl.toString();
|
||||
StringBuilder sb = new StringBuilder(urlStr);
|
||||
if (!urlStr.endsWith("/")) {
|
||||
sb.append('/');
|
||||
}
|
||||
// Inner classes are in separate Top.Nesting1.Nesting2.Nesting3.MyType.html files
|
||||
sb.append(type.getFullyQualifiedName().replaceAll("\\.", "/").replaceAll("\\$", ".") + ".html");
|
||||
sb.append(fqName.replaceAll("\\.", "/").replaceAll("\\$", ".") + ".html");
|
||||
return new URL(sb.toString());
|
||||
};
|
||||
|
||||
URL sourceUrl(URL sourceContainerUrl, IType type) throws Exception;
|
||||
URL sourceUrl(URL sourceContainerUrl, String fqName) throws Exception;
|
||||
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ public class MavenCore {
|
||||
javaVersion = "8";
|
||||
}
|
||||
URL javadocUrl = new URL("https://docs.oracle.com/javase/" + javaVersion + "/docs/api/");
|
||||
return new HtmlJavadocProvider((type) -> SourceUrlProviderFromSourceContainer.JAVADOC_FOLDER_URL_SUPPLIER.sourceUrl(javadocUrl, type));
|
||||
return new HtmlJavadocProvider((type) -> SourceUrlProviderFromSourceContainer.JAVADOC_FOLDER_URL_SUPPLIER.sourceUrl(javadocUrl, type.getFullyQualifiedName()));
|
||||
} catch (MalformedURLException e) {
|
||||
Log.log(e);
|
||||
return null;
|
||||
|
||||
@@ -18,6 +18,7 @@ import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
@@ -183,6 +184,7 @@ public class MavenProjectClasspath extends JandexClasspath {
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
@Override
|
||||
protected IJavadocProvider createParserJavadocProvider(File classpathResource) {
|
||||
@@ -193,34 +195,48 @@ public class MavenProjectClasspath extends JandexClasspath {
|
||||
if (classpathResource.toString().startsWith(cachedData.outputDirectory)) {
|
||||
return new ParserJavadocProvider(type -> {
|
||||
return SourceUrlProviderFromSourceContainer.SOURCE_FOLDER_URL_SUPPLIER
|
||||
.sourceUrl(new File(cachedData.sourceDirectory).toURI().toURL(), type);
|
||||
.sourceUrl(new File(cachedData.sourceDirectory).toURI().toURL(), type.getFullyQualifiedName());
|
||||
});
|
||||
} else if (classpathResource.toString().startsWith(cachedData.testOutputDirectory)) {
|
||||
return new ParserJavadocProvider(type -> {
|
||||
return SourceUrlProviderFromSourceContainer.SOURCE_FOLDER_URL_SUPPLIER
|
||||
.sourceUrl(new File(cachedData.testSourceDirectory).toURI().toURL(), type);
|
||||
.sourceUrl(new File(cachedData.testSourceDirectory).toURI().toURL(), type.getFullyQualifiedName());
|
||||
});
|
||||
} else {
|
||||
throw new IllegalArgumentException("Cannot find source folder for " + classpathResource);
|
||||
}
|
||||
} else {
|
||||
// Assume it's a JAR file
|
||||
return new ParserJavadocProvider(type -> {
|
||||
return new ParserJavadocProvider(type -> sourceContainer(classpathResource).map(url -> {
|
||||
try {
|
||||
Artifact artifact = cachedData.artifacts.stream().filter(a -> classpathResource.equals(a.getFile())).findFirst().get();
|
||||
URL sourceContainer = maven.getSources(artifact, cachedData.remoteArtifactRepositories).getFile().toURI().toURL();
|
||||
return SourceUrlProviderFromSourceContainer.JAR_SOURCE_URL_PROVIDER.sourceUrl(sourceContainer,
|
||||
type);
|
||||
} catch (MavenException e) {
|
||||
Log.log("Failed to find sources JAR for " + classpathResource, e);
|
||||
} catch (MalformedURLException e) {
|
||||
Log.log("Invalid URL for sources JAR for " + classpathResource, e);
|
||||
return SourceUrlProviderFromSourceContainer.JAR_SOURCE_URL_PROVIDER.sourceUrl(url,
|
||||
type.getFullyQualifiedName());
|
||||
} catch (Exception e) {
|
||||
Log.log(e);
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}).get());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<URL> sourceContainer(File classpathResource) {
|
||||
if (cachedData == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return cachedData.artifacts.stream().filter(a -> classpathResource.equals(a.getFile())).findFirst().map(artifact -> {
|
||||
try {
|
||||
return maven.getSources(artifact, cachedData.remoteArtifactRepositories).getFile().toURI().toURL();
|
||||
} catch (MalformedURLException e) {
|
||||
Log.log("Invalid URL for sources JAR for " + classpathResource, e);
|
||||
return null;
|
||||
} catch (MavenException e) {
|
||||
Log.log("Failed to find sources JAR for " + classpathResource, e);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IJavadocProvider createHtmlJavdocProvider(File classpathResource) {
|
||||
if (cachedData == null) {
|
||||
@@ -230,12 +246,12 @@ public class MavenProjectClasspath extends JandexClasspath {
|
||||
if (classpathResource.toString().startsWith(cachedData.outputDirectory)) {
|
||||
return new HtmlJavadocProvider(type -> {
|
||||
return SourceUrlProviderFromSourceContainer.JAVADOC_FOLDER_URL_SUPPLIER
|
||||
.sourceUrl(new File(cachedData.reportingOutputDirectory, "apidocs").toURI().toURL(), type);
|
||||
.sourceUrl(new File(cachedData.reportingOutputDirectory, "apidocs").toURI().toURL(), type.getFullyQualifiedName());
|
||||
});
|
||||
} else if (classpathResource.toString().startsWith(cachedData.testOutputDirectory)) {
|
||||
return new ParserJavadocProvider(type -> {
|
||||
return SourceUrlProviderFromSourceContainer.JAVADOC_FOLDER_URL_SUPPLIER
|
||||
.sourceUrl(new File(cachedData.reportingOutputDirectory, "apidocs").toURI().toURL(), type);
|
||||
.sourceUrl(new File(cachedData.reportingOutputDirectory, "apidocs").toURI().toURL(), type.getFullyQualifiedName());
|
||||
});
|
||||
} else {
|
||||
throw new IllegalArgumentException("Cannot find source folder for " + classpathResource);
|
||||
@@ -247,7 +263,7 @@ public class MavenProjectClasspath extends JandexClasspath {
|
||||
Artifact artifact = cachedData.artifacts.stream().filter(a -> classpathResource.equals(a.getFile())).findFirst().get();
|
||||
URL sourceContainer = maven.getJavadoc(artifact, cachedData.remoteArtifactRepositories).getFile().toURI().toURL();
|
||||
return SourceUrlProviderFromSourceContainer.JAR_JAVADOC_URL_PROVIDER.sourceUrl(sourceContainer,
|
||||
type);
|
||||
type.getFullyQualifiedName());
|
||||
} catch (MavenException e) {
|
||||
Log.log("Failed to find sources JAR for " + classpathResource, e);
|
||||
} catch (MalformedURLException e) {
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
package org.springframework.ide.vscode.commons.maven.java.classpathfile;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
@@ -108,4 +109,9 @@ public class FileClasspath implements IClasspath {
|
||||
@Override
|
||||
public void reindex() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<URL> sourceContainer(File classpathResource) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user