Navigate to specific position in file via source link

This commit is contained in:
BoykoAlex
2018-02-14 10:27:14 -05:00
parent 9d89229cea
commit f3d6bba7ec
21 changed files with 611 additions and 239 deletions

View File

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

View File

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

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

View File

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

View File

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

View File

@@ -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) {

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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) {

View File

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