Merge branch 'master' of github.com:spring-projects/sts4

This commit is contained in:
Kris De Volder
2018-02-14 16:45:34 -08:00
21 changed files with 611 additions and 252 deletions

View File

@@ -250,9 +250,9 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent
providers.put(Annotations.SPRING_PATCH_MAPPING, new RequestMappingHoverProvider()); providers.put(Annotations.SPRING_PATCH_MAPPING, new RequestMappingHoverProvider());
providers.put(Annotations.PROFILE, new ActiveProfilesProvider()); providers.put(Annotations.PROFILE, new ActiveProfilesProvider());
providers.put(Annotations.AUTOWIRED, new AutowiredHoverProvider()); providers.put(Annotations.AUTOWIRED, new AutowiredHoverProvider(this));
providers.put(Annotations.COMPONENT, new ComponentInjectionsHoverProvider()); providers.put(Annotations.COMPONENT, new ComponentInjectionsHoverProvider(this));
providers.put(Annotations.BEAN, new BeanInjectedIntoHoverProvider()); providers.put(Annotations.BEAN, new BeanInjectedIntoHoverProvider(this));
providers.put(Annotations.CONDITIONAL, new ConditionalsLiveHoverProvider()); providers.put(Annotations.CONDITIONAL, new ConditionalsLiveHoverProvider());
providers.put(Annotations.CONDITIONAL_ON_BEAN, new ConditionalsLiveHoverProvider()); providers.put(Annotations.CONDITIONAL_ON_BEAN, new ConditionalsLiveHoverProvider());

View File

@@ -22,6 +22,7 @@ import org.eclipse.lsp4j.Hover;
import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.jsonrpc.messages.Either; import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.springframework.ide.vscode.boot.java.Annotations; 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.annotations.AnnotationHierarchies;
import org.springframework.ide.vscode.boot.java.handlers.HoverProvider; import org.springframework.ide.vscode.boot.java.handlers.HoverProvider;
import org.springframework.ide.vscode.boot.java.livehover.ComponentInjectionsHoverProvider; import org.springframework.ide.vscode.boot.java.livehover.ComponentInjectionsHoverProvider;
@@ -42,6 +43,12 @@ import com.google.common.collect.ImmutableList;
*/ */
public class AutowiredHoverProvider implements HoverProvider { public class AutowiredHoverProvider implements HoverProvider {
private BootJavaLanguageServer server;
public AutowiredHoverProvider(BootJavaLanguageServer server) {
this.server = server;
}
@Override @Override
public Collection<Range> getLiveHoverHints(Annotation annotation, TextDocument doc, SpringBootApp[] runningApps) { public Collection<Range> getLiveHoverHints(Annotation annotation, TextDocument doc, SpringBootApp[] runningApps) {
try { try {
@@ -155,7 +162,7 @@ public class AutowiredHoverProvider implements HoverProvider {
} }
List<LiveBean> dependencyBeans = beans.getBeansOfName(injectedBean); List<LiveBean> dependencyBeans = beans.getBeansOfName(injectedBean);
for (LiveBean dependencyBean : dependencyBeans) { for (LiveBean dependencyBean : dependencyBeans) {
hover.append("- " + LiveHoverUtils.showBeanWithResource(dependencyBean, " ", project)); hover.append("- " + LiveHoverUtils.showBeanWithResource(server, dependencyBean, " ", project));
} }
firstDependency = false; firstDependency = false;
} }

View File

@@ -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]);
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}

View File

@@ -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();
}
}

View File

@@ -1,5 +1,5 @@
/******************************************************************************* /*******************************************************************************
* Copyright (c) 2017 Pivotal, Inc. * Copyright (c) 2017, 2018 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * 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.Hover;
import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.jsonrpc.messages.Either; 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.handlers.HoverProvider;
import org.springframework.ide.vscode.boot.java.utils.ASTUtils; 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.SpringBootApp;
@@ -35,6 +36,12 @@ import com.google.common.collect.ImmutableList;
public abstract class AbstractInjectedIntoHoverProvider implements HoverProvider { public abstract class AbstractInjectedIntoHoverProvider implements HoverProvider {
protected BootJavaLanguageServer server;
public AbstractInjectedIntoHoverProvider(BootJavaLanguageServer server) {
this.server = server;
}
@Override @Override
public Collection<Range> getLiveHoverHints(Annotation annotation, TextDocument doc, SpringBootApp[] runningApps) { public Collection<Range> getLiveHoverHints(Annotation annotation, TextDocument doc, SpringBootApp[] runningApps) {
// Highlight if any running app contains an instance of this component // Highlight if any running app contains an instance of this component
@@ -113,7 +120,7 @@ public abstract class AbstractInjectedIntoHoverProvider implements HoverProvider
if (!firstDependency) { if (!firstDependency) {
hover.append("\n"); hover.append("\n");
} }
hover.append("- " + LiveHoverUtils.showBeanWithResource(dependingBean, " ", project)); hover.append("- " + LiveHoverUtils.showBeanWithResource(server, dependingBean, " ", project));
firstDependency = false; firstDependency = false;
} }
} }

View File

@@ -20,6 +20,7 @@ import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.lsp4j.Hover; import org.eclipse.lsp4j.Hover;
import org.eclipse.lsp4j.Range; 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.boot.java.utils.ASTUtils;
import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp; import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp;
import org.springframework.ide.vscode.commons.boot.app.cli.livebean.LiveBean; 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 class BeanInjectedIntoHoverProvider extends AbstractInjectedIntoHoverProvider {
public BeanInjectedIntoHoverProvider(BootJavaLanguageServer server) {
super(server);
}
@Override @Override
protected LiveBean getDefinedBean(Annotation annotation) { protected LiveBean getDefinedBean(Annotation annotation) {
MethodDeclaration beanMethod = ASTUtils.getAnnotatedMethod(annotation); MethodDeclaration beanMethod = ASTUtils.getAnnotatedMethod(annotation);

View File

@@ -26,6 +26,7 @@ import org.eclipse.lsp4j.Hover;
import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.jsonrpc.messages.Either; import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.springframework.ide.vscode.boot.java.Annotations; 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.ASTUtils;
import org.springframework.ide.vscode.boot.java.utils.FunctionUtils; import org.springframework.ide.vscode.boot.java.utils.FunctionUtils;
import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp; 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 class ComponentInjectionsHoverProvider extends AbstractInjectedIntoHoverProvider {
public ComponentInjectionsHoverProvider(BootJavaLanguageServer server) {
super(server);
}
@Override @Override
protected void addAutomaticallyWiredContructor(StringBuilder hover, Annotation annotation, LiveBeansModel beans, LiveBean bean, IJavaProject project) { protected void addAutomaticallyWiredContructor(StringBuilder hover, Annotation annotation, LiveBeansModel beans, LiveBean bean, IJavaProject project) {
TypeDeclaration typeDecl = ASTUtils.findDeclaringType(annotation); TypeDeclaration typeDecl = ASTUtils.findDeclaringType(annotation);
@@ -63,7 +68,7 @@ public class ComponentInjectionsHoverProvider extends AbstractInjectedIntoHoverP
} }
List<LiveBean> dependencyBeans = beans.getBeansOfName(injectedBean); List<LiveBean> dependencyBeans = beans.getBeansOfName(injectedBean);
for (LiveBean dependencyBean : dependencyBeans) { for (LiveBean dependencyBean : dependencyBeans) {
hover.append("- " + LiveHoverUtils.showBeanWithResource(dependencyBean, " ", project)); hover.append("- " + LiveHoverUtils.showBeanWithResource(server, dependencyBean, " ", project));
} }
firstDependency = false; firstDependency = false;
} }

View File

@@ -13,7 +13,9 @@ package org.springframework.ide.vscode.boot.java.livehover;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Stream; 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.boot.java.utils.SpringResource;
import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp; import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp;
import org.springframework.ide.vscode.commons.boot.app.cli.livebean.LiveBean; import org.springframework.ide.vscode.commons.boot.app.cli.livebean.LiveBean;
@@ -34,18 +36,19 @@ public class LiveHoverUtils {
return buf.toString(); 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 newline = " \n"+indentStr; //Note: the double space before newline makes markdown see it as a real line break
String type = bean.getType(true); String type = bean.getType(true);
StringBuilder buf = new StringBuilder("Bean: "); StringBuilder buf = new StringBuilder("Bean: ");
buf.append(bean.getId()); buf.append(bean.getId());
SourceLinks sourceLinks = SourceLinkFactory.createSourceLinks(server);
if (type != null) { if (type != null) {
// Try creating a URL link to open source for the type // Try creating a URL link to open source for the type
buf.append(newline); buf.append(newline);
buf.append("Type: "); buf.append("Type: ");
Optional<String> url = SourceLinks.sourceLinkUrlForFQName(project, type); Optional<String> url = sourceLinks.sourceLinkUrlForFQName(project, type);
if (url.isPresent()) { if (url.isPresent()) {
buf.append(Renderables.link(type, url.get()).toMarkdown()); buf.append(Renderables.link(type, url.get()).toMarkdown());
} else { } else {
@@ -56,13 +59,13 @@ public class LiveHoverUtils {
if (StringUtil.hasText(resource)) { if (StringUtil.hasText(resource)) {
buf.append(newline); buf.append(newline);
buf.append("Resource: "); buf.append("Resource: ");
buf.append(showResource(resource, project)); buf.append(showResource(sourceLinks, resource, project));
} }
return buf.toString(); return buf.toString();
} }
public static String showResource(String resource, IJavaProject project) { public static String showResource(SourceLinks sourceLinks, String resource, IJavaProject project) {
return new SpringResource(resource, project).toMarkdown(); return new SpringResource(sourceLinks, resource, project).toMarkdown();
} }
public static String niceAppName(SpringBootApp app) { public static String niceAppName(SpringBootApp app) {

View File

@@ -133,6 +133,14 @@ public final class CompilationUnitCache {
} }
public static CompilationUnit parse(TextDocument document, IJavaProject project) throws Exception { 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); ASTParser parser = ASTParser.newParser(AST.JLS9);
Map<String, String> options = JavaCore.getOptions(); Map<String, String> options = JavaCore.getOptions();
JavaCore.setComplianceOptions(JavaCore.VERSION_1_8, options); JavaCore.setComplianceOptions(JavaCore.VERSION_1_8, options);
@@ -142,14 +150,11 @@ public final class CompilationUnitCache {
parser.setBindingsRecovery(true); parser.setBindingsRecovery(true);
parser.setResolveBindings(true); parser.setResolveBindings(true);
String[] classpathEntries = getClasspathEntries(document, project);
String[] sourceEntries = new String[] {}; String[] sourceEntries = new String[] {};
parser.setEnvironment(classpathEntries, sourceEntries, null, true); parser.setEnvironment(classpathEntries, sourceEntries, null, true);
String docURI = document.getUri();
String unitName = docURI.substring(docURI.lastIndexOf("/"));
parser.setUnitName(unitName); parser.setUnitName(unitName);
parser.setSource(document.get(0, document.getLength()).toCharArray()); parser.setSource(source);
CompilationUnit cu = (CompilationUnit) parser.createAST(null); CompilationUnit cu = (CompilationUnit) parser.createAST(null);

View File

@@ -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]));
// }
}

View File

@@ -10,16 +10,15 @@
*******************************************************************************/ *******************************************************************************/
package org.springframework.ide.vscode.boot.java.utils; package org.springframework.ide.vscode.boot.java.utils;
import java.io.File;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.Optional; import java.util.Optional;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; 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.IClasspath;
import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.util.Renderable;
import org.springframework.ide.vscode.commons.util.Renderables; 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 FILE = "file";
private static final String CLASS_PATH_RESOURCE = "class path resource"; private static final String CLASS_PATH_RESOURCE = "class path resource";
private SourceLinks sourceLinks;
private String type; private String type;
private String path; private String path;
private IJavaProject project; private IJavaProject project;
private static final Pattern BRACKETS = Pattern.compile("\\[[^\\]]*\\]"); 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; this.project = project;
Matcher matcher = BRACKETS.matcher(toParse); Matcher matcher = BRACKETS.matcher(toParse);
if (matcher.find()) { if (matcher.find()) {
@@ -57,15 +58,15 @@ public class SpringResource {
case FILE: case FILE:
String relativePath = projectRelativePath(path); String relativePath = projectRelativePath(path);
if (relativePath != path && path.endsWith(SourceLinks.CLASS)) { if (relativePath != path && path.endsWith(SourceLinks.CLASS)) {
linkUrl = SourceLinks.sourceLinkUrlForClasspathResource(project, relativePath); linkUrl = sourceLinks.sourceLinkUrlForClasspathResource(project, relativePath);
} else { } else {
linkUrl = SourceLinks.sourceLinkForResourcePath(Paths.get(path)); linkUrl = sourceLinks.sourceLinkForResourcePath(Paths.get(path));
} }
// not a project relative path // not a project relative path
return linkUrl.isPresent() ? Renderables.link(relativePath, linkUrl.get()).toMarkdown() return linkUrl.isPresent() ? Renderables.link(relativePath, linkUrl.get()).toMarkdown()
: "`" + projectRelativePath(path) + "`"; : "`" + projectRelativePath(path) + "`";
case CLASS_PATH_RESOURCE: case CLASS_PATH_RESOURCE:
linkUrl = SourceLinks.sourceLinkUrlForClasspathResource(project, path); linkUrl = sourceLinks.sourceLinkUrlForClasspathResource(project, path);
return linkUrl.isPresent() ? Renderables.link(path, linkUrl.get()).toMarkdown() : "`"+path+"`"; return linkUrl.isPresent() ? Renderables.link(path, linkUrl.get()).toMarkdown() : "`"+path+"`";
default: default:
return path; return path;

View File

@@ -18,11 +18,8 @@ import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.Optional; import java.util.Optional;
import org.junit.After;
import org.junit.Before;
import org.junit.Test; 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.MavenBuilder;
import org.springframework.ide.vscode.commons.maven.MavenCore; import org.springframework.ide.vscode.commons.maven.MavenCore;
import org.springframework.ide.vscode.commons.maven.java.MavenJavaProject; import org.springframework.ide.vscode.commons.maven.java.MavenJavaProject;
@@ -50,42 +47,38 @@ public class VSCodeSourceLinksTest {
}); });
@Before
public void setupAll() throws Exception {
System.setProperty("sts.lsp.client", LspClient.Client.VSCODE.toString());
}
@After
public void tearDownAll() throws Exception {
System.setProperty("sts.lsp.client", "");
}
@Test @Test
public void testJavaSourceUrl() throws Exception { public void testJavaSourceUrl() throws Exception {
MavenJavaProject project = mavenProjectsCache.get("empty-boot-15-web-app"); 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()); assertTrue(url.isPresent());
Path projectPath = Paths.get(project.pom().getParent()); 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); assertEquals(Paths.get("src/main/java/com/example/EmptyBoot15WebAppApplication.java"), relativePath);
String positionPart = url.get().substring(url.get().lastIndexOf('#'));
assertEquals("#7,14", positionPart);
} }
@Test @Test
public void testJarUrl() throws Exception { public void testJarUrl() throws Exception {
MavenJavaProject project = mavenProjectsCache.get("empty-boot-15-web-app"); 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()); assertTrue(url.isPresent());
String headerPart = url.get().substring(0, url.get().indexOf('?')); 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); 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 @Test
public void testJarUrlInnerType() throws Exception { public void testJarUrlInnerType() throws Exception {
MavenJavaProject project = mavenProjectsCache.get("empty-boot-15-web-app"); 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()); assertTrue(url.isPresent());
String headerPart = url.get().substring(0, url.get().indexOf('?')); 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); 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);
} }
} }

View File

@@ -68,7 +68,7 @@ public class GradleProjectClasspath extends JandexClasspath {
URL javadocUrl = new URL("https://docs.oracle.com/javase/" + javaVersion + "/docs/api/"); URL javadocUrl = new URL("https://docs.oracle.com/javase/" + javaVersion + "/docs/api/");
return new HtmlJavadocProvider( return new HtmlJavadocProvider(
(type) -> SourceUrlProviderFromSourceContainer.JAVADOC_FOLDER_URL_SUPPLIER (type) -> SourceUrlProviderFromSourceContainer.JAVADOC_FOLDER_URL_SUPPLIER
.sourceUrl(javadocUrl, type)); .sourceUrl(javadocUrl, type.getFullyQualifiedName()));
} catch (MalformedURLException e) { } catch (MalformedURLException e) {
Log.log(e); Log.log(e);
return null; return null;
@@ -150,7 +150,7 @@ public class GradleProjectClasspath extends JandexClasspath {
if (classpathFolder.isPresent()) { if (classpathFolder.isPresent()) {
return new ParserJavadocProvider(type -> { return new ParserJavadocProvider(type -> {
return SourceUrlProviderFromSourceContainer.SOURCE_FOLDER_URL_SUPPLIER 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()); return ClasspathData.from(getName(), getClasspathEntries(), getClasspathResources(), getOutputFolder());
} }
@Override
public Optional<URL> sourceContainer(File classpathResource) {
return Optional.empty();
}
} }

View File

@@ -11,6 +11,7 @@
package org.springframework.ide.vscode.commons.java; package org.springframework.ide.vscode.commons.java;
import java.io.File; import java.io.File;
import java.net.URL;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
@@ -179,4 +180,10 @@ public class DelegatingCachedClasspath<T extends IClasspath> implements IClasspa
} }
} }
@Override
public Optional<URL> sourceContainer(File classpathResource) {
T t = cachedClasspath.get();
return t == null ? Optional.empty() : t.sourceContainer(classpathResource);
}
} }

View File

@@ -11,6 +11,7 @@
package org.springframework.ide.vscode.commons.java; package org.springframework.ide.vscode.commons.java;
import java.io.File; import java.io.File;
import java.net.URL;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Optional; import java.util.Optional;
import java.util.function.Predicate; import java.util.function.Predicate;
@@ -64,4 +65,6 @@ public interface IClasspath {
ClasspathData createClasspathData() throws Exception; ClasspathData createClasspathData() throws Exception;
void reindex(); void reindex();
Optional<URL> sourceContainer(File classpathResource);
} }

View File

@@ -14,51 +14,54 @@ package org.springframework.ide.vscode.commons.javadoc;
import java.net.URL; import java.net.URL;
import java.nio.file.Paths; import java.nio.file.Paths;
import org.springframework.ide.vscode.commons.java.IType;
@FunctionalInterface @FunctionalInterface
public interface SourceUrlProviderFromSourceContainer { 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(); StringBuilder sourceUrlStr = new StringBuilder();
sourceUrlStr.append("jar:"); sourceUrlStr.append("jar:");
sourceUrlStr.append(sourceContainerUrl); sourceUrlStr.append(sourceContainerUrl);
sourceUrlStr.append("!"); sourceUrlStr.append("!");
sourceUrlStr.append('/'); sourceUrlStr.append('/');
sourceUrlStr.append(type.getFullyQualifiedName().replaceAll("\\.", "/")); sourceUrlStr.append(extractTopLevelType(fqName).replaceAll("\\.", "/"));
sourceUrlStr.append(".java"); sourceUrlStr.append(".java");
return new URL(sourceUrlStr.toString()); return new URL(sourceUrlStr.toString());
}; };
public static final SourceUrlProviderFromSourceContainer SOURCE_FOLDER_URL_SUPPLIER = (sourceContainerUrl, type) -> { public static final SourceUrlProviderFromSourceContainer SOURCE_FOLDER_URL_SUPPLIER = (sourceContainerUrl, fqName) -> {
return Paths.get(sourceContainerUrl.toURI()).resolve(type.getFullyQualifiedName().replaceAll("\\.", "/") + ".java").toUri().toURL(); 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(); StringBuilder sourceUrlStr = new StringBuilder();
sourceUrlStr.append("jar:"); sourceUrlStr.append("jar:");
sourceUrlStr.append(javadocContainerUrl); sourceUrlStr.append(javadocContainerUrl);
sourceUrlStr.append("!"); sourceUrlStr.append("!");
sourceUrlStr.append('/'); sourceUrlStr.append('/');
// Inner classes are in separate Top.Nesting1.Nesting2.Nesting3.MyType.html files // 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"); sourceUrlStr.append(".html");
return new URL(sourceUrlStr.toString()); 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(); String urlStr = sourceContainerUrl.toString();
StringBuilder sb = new StringBuilder(urlStr); StringBuilder sb = new StringBuilder(urlStr);
if (!urlStr.endsWith("/")) { if (!urlStr.endsWith("/")) {
sb.append('/'); sb.append('/');
} }
// Inner classes are in separate Top.Nesting1.Nesting2.Nesting3.MyType.html files // 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()); return new URL(sb.toString());
}; };
URL sourceUrl(URL sourceContainerUrl, IType type) throws Exception; URL sourceUrl(URL sourceContainerUrl, String fqName) throws Exception;
} }

View File

@@ -95,7 +95,7 @@ public class MavenCore {
javaVersion = "8"; javaVersion = "8";
} }
URL javadocUrl = new URL("https://docs.oracle.com/javase/" + javaVersion + "/docs/api/"); 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) { } catch (MalformedURLException e) {
Log.log(e); Log.log(e);
return null; return null;

View File

@@ -18,6 +18,7 @@ import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
@@ -184,6 +185,7 @@ public class MavenProjectClasspath extends JandexClasspath {
// } // }
// } // }
@Override @Override
protected IJavadocProvider createParserJavadocProvider(File classpathResource) { protected IJavadocProvider createParserJavadocProvider(File classpathResource) {
if (cachedData == null) { if (cachedData == null) {
@@ -193,32 +195,46 @@ public class MavenProjectClasspath extends JandexClasspath {
if (classpathResource.toString().startsWith(cachedData.outputDirectory)) { if (classpathResource.toString().startsWith(cachedData.outputDirectory)) {
return new ParserJavadocProvider(type -> { return new ParserJavadocProvider(type -> {
return SourceUrlProviderFromSourceContainer.SOURCE_FOLDER_URL_SUPPLIER 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)) { } else if (classpathResource.toString().startsWith(cachedData.testOutputDirectory)) {
return new ParserJavadocProvider(type -> { return new ParserJavadocProvider(type -> {
return SourceUrlProviderFromSourceContainer.SOURCE_FOLDER_URL_SUPPLIER return SourceUrlProviderFromSourceContainer.SOURCE_FOLDER_URL_SUPPLIER
.sourceUrl(new File(cachedData.testSourceDirectory).toURI().toURL(), type); .sourceUrl(new File(cachedData.testSourceDirectory).toURI().toURL(), type.getFullyQualifiedName());
}); });
} else { } else {
throw new IllegalArgumentException("Cannot find source folder for " + classpathResource); throw new IllegalArgumentException("Cannot find source folder for " + classpathResource);
} }
} else { } else {
// Assume it's a JAR file // Assume it's a JAR file
return new ParserJavadocProvider(type -> { return new ParserJavadocProvider(type -> sourceContainer(classpathResource).map(url -> {
try { try {
Artifact artifact = cachedData.artifacts.stream().filter(a -> classpathResource.equals(a.getFile())).findFirst().get(); return SourceUrlProviderFromSourceContainer.JAR_SOURCE_URL_PROVIDER.sourceUrl(url,
URL sourceContainer = maven.getSources(artifact, cachedData.remoteArtifactRepositories).getFile().toURI().toURL(); type.getFullyQualifiedName());
return SourceUrlProviderFromSourceContainer.JAR_SOURCE_URL_PROVIDER.sourceUrl(sourceContainer, } catch (Exception e) {
type); Log.log(e);
} catch (MavenException e) { return null;
Log.log("Failed to find sources JAR for " + classpathResource, e); }
}).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) { } catch (MalformedURLException e) {
Log.log("Invalid URL for sources JAR for " + classpathResource, 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;
} }
return null; });
});
}
} }
@Override @Override
@@ -230,12 +246,12 @@ public class MavenProjectClasspath extends JandexClasspath {
if (classpathResource.toString().startsWith(cachedData.outputDirectory)) { if (classpathResource.toString().startsWith(cachedData.outputDirectory)) {
return new HtmlJavadocProvider(type -> { return new HtmlJavadocProvider(type -> {
return SourceUrlProviderFromSourceContainer.JAVADOC_FOLDER_URL_SUPPLIER 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)) { } else if (classpathResource.toString().startsWith(cachedData.testOutputDirectory)) {
return new ParserJavadocProvider(type -> { return new ParserJavadocProvider(type -> {
return SourceUrlProviderFromSourceContainer.JAVADOC_FOLDER_URL_SUPPLIER 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 { } else {
throw new IllegalArgumentException("Cannot find source folder for " + classpathResource); 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(); Artifact artifact = cachedData.artifacts.stream().filter(a -> classpathResource.equals(a.getFile())).findFirst().get();
URL sourceContainer = maven.getJavadoc(artifact, cachedData.remoteArtifactRepositories).getFile().toURI().toURL(); URL sourceContainer = maven.getJavadoc(artifact, cachedData.remoteArtifactRepositories).getFile().toURI().toURL();
return SourceUrlProviderFromSourceContainer.JAR_JAVADOC_URL_PROVIDER.sourceUrl(sourceContainer, return SourceUrlProviderFromSourceContainer.JAR_JAVADOC_URL_PROVIDER.sourceUrl(sourceContainer,
type); type.getFullyQualifiedName());
} catch (MavenException e) { } catch (MavenException e) {
Log.log("Failed to find sources JAR for " + classpathResource, e); Log.log("Failed to find sources JAR for " + classpathResource, e);
} catch (MalformedURLException e) { } catch (MalformedURLException e) {

View File

@@ -11,6 +11,7 @@
package org.springframework.ide.vscode.commons.maven.java.classpathfile; package org.springframework.ide.vscode.commons.maven.java.classpathfile;
import java.io.File; import java.io.File;
import java.net.URL;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Optional; import java.util.Optional;
@@ -108,4 +109,9 @@ public class FileClasspath implements IClasspath {
@Override @Override
public void reindex() { public void reindex() {
} }
@Override
public Optional<URL> sourceContainer(File classpathResource) {
return Optional.empty();
}
} }