PT #155474415: CTRL-CLICK navigation from .properties to Java

This commit is contained in:
BoykoAlex
2018-11-06 16:45:21 -05:00
parent a95d74d0b9
commit f2be2c717b
52 changed files with 1998 additions and 396 deletions

View File

@@ -45,6 +45,8 @@ class BindingKeyUtils {
sb.append(getBindingKey(field.declaringClass()));
sb.append('.');
sb.append(field.name());
sb.append(')');
sb.append(getGeneralTypeBindingKey(field.type()));
return sb.toString();
}
@@ -104,6 +106,9 @@ class BindingKeyUtils {
sb.append(getGeneralTypeBindingKey(argument));
}
sb.append('>');
if (type.owner() == null) {
sb.append(';');
}
return sb.toString();
}

View File

@@ -53,7 +53,7 @@ class TypeImpl implements IType {
@Override
public IType getDeclaringType() {
DotName enclosingClass = info.enclosingClass();
return enclosingClass == null ? null : index.findType(enclosingClass.toString());
return enclosingClass == null ? this : index.findType(enclosingClass.toString());
}
@Override

View File

@@ -11,6 +11,8 @@
package org.springframework.ide.vscode.commons.java;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -37,4 +39,28 @@ public class BootProjectUtil {
return name.endsWith(".jar") && name.startsWith("spring-boot");
}
public static Path javaHomeFromLibJar(Path libJar) {
for (Path home = libJar; home.getParent() != null; home = home.getParent()) {
if (Files.exists(home.resolve("release")) || Files.exists(home.resolve("release.txt"))) {
return home;
}
}
return null;
}
public static Path jreSources(Path libJar) {
Path home = javaHomeFromLibJar(libJar);
if (home != null) {
Path sources = home.resolve("src.zip");
if (Files.exists(sources)) {
return sources;
}
sources = home.resolve("lib/src.zip");
if (Files.exists(sources)) {
return sources;
}
}
return null;
}
}

View File

@@ -21,6 +21,7 @@ import java.util.Set;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.model.Resource;
import org.apache.maven.project.MavenProject;
import org.springframework.ide.vscode.commons.java.BootProjectUtil;
import org.springframework.ide.vscode.commons.java.ClasspathData;
import org.springframework.ide.vscode.commons.java.IClasspath;
import org.springframework.ide.vscode.commons.languageserver.jdt.ls.Classpath.CPE;
@@ -86,6 +87,7 @@ public class MavenProjectClasspath implements IClasspath {
if (javaVersion == null) {
javaVersion = "8";
}
cpe.setSourceContainerUrl(BootProjectUtil.jreSources(path).toUri().toURL());
cpe.setJavadocContainerUrl(new URL("https://docs.oracle.com/javase/" + javaVersion + "/docs/api/"));
cpe.setSystem(true);
entries.add(cpe);

View File

@@ -21,7 +21,9 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.ide.vscode.commons.languageserver.config.LanguageServerInitializer;
import org.springframework.ide.vscode.commons.languageserver.config.LanguageServerProperties;
import org.springframework.ide.vscode.commons.languageserver.reconcile.DiagnosticSeverityProvider;
import org.springframework.ide.vscode.commons.languageserver.util.DefinitionHandler;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
@Configuration
@EnableConfigurationProperties(LanguageServerProperties.class)
@@ -45,5 +47,14 @@ public class LanguageServerAutoConf {
};
}
@Bean SimpleTextDocumentService documents(SimpleLanguageServer ls) {
return ls.getTextDocumentService();
}
@ConditionalOnBean(DefinitionHandler.class)
@Bean
InitializingBean registerDefintionHandler(SimpleTextDocumentService documents,
DefinitionHandler definitionHandler) {
return () -> documents.onDefinition(definitionHandler);
}
}

View File

@@ -55,6 +55,7 @@ import org.springframework.ide.vscode.commons.util.Unicodes;
import org.springframework.ide.vscode.commons.util.text.LanguageId;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import reactor.core.publisher.Flux;
@@ -720,10 +721,28 @@ public class Editor {
return "Editor(\n"+getText()+"\n)";
}
public void assertLinkTargets(String hoverOver, Set<Location> expectedLocations) throws Exception {
int pos = getRawText().indexOf(hoverOver);
if (pos>=0) {
pos += hoverOver.length() / 2;
}
assertTrue("Not found in editor: '"+hoverOver+"'", pos>=0);
TextDocumentPositionParams params = new TextDocumentPositionParams(new TextDocumentIdentifier(getUri()), doc.toPosition(pos));
List<? extends Location> definitions = harness.getDefinitions(params);
assertEquals(ImmutableSet.copyOf(expectedLocations), ImmutableSet.copyOf(definitions));
}
@Deprecated
public void assertLinkTargets(String hoverOver, String... expecteds) {
throw new UnsupportedOperationException("Not implemented yet!");
// Editor editor = this;
// int pos = editor.middleOf(hoverOver);
// int pos = getRawText().indexOf(hoverOver);
// if (pos>=0) {
// pos += hoverOver.length();
// }
// return harness.getHover(doc, doc.toPosition(pos));
// assertTrue("Not found in editor: '"+hoverOver+"'", pos>=0);
//
// List<IJavaElement> targets = getLinkTargets(editor, pos);

View File

@@ -14,7 +14,15 @@ import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.context.annotation.Bean;
import org.springframework.ide.vscode.boot.java.links.DefaultJavaElementLocationProvider;
import org.springframework.ide.vscode.boot.java.links.JavaElementLocationProvider;
import org.springframework.ide.vscode.boot.java.links.JavaDocumentUriProvider;
import org.springframework.ide.vscode.boot.java.links.JdtJavaDocumentUriProvider;
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.CompilationUnitCache;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
import org.springframework.ide.vscode.commons.util.LogRedirect;
@SpringBootApplication
@@ -34,4 +42,22 @@ public class BootLanguagServerBootApp {
@Bean BootLanguageServerParams serverParams(SimpleLanguageServer server) {
return BootLanguageServerParams.createDefault(server);
}
@ConditionalOnMissingClass("org.springframework.ide.vscode.languageserver.testharness.LanguageServerHarness")
@Bean SourceLinks sourceLinks(CompilationUnitCache cuCache) {
return SourceLinkFactory.createSourceLinks(cuCache);
}
@Bean CompilationUnitCache cuCache(BootLanguageServerParams params, SimpleTextDocumentService documents) {
return new CompilationUnitCache(params.projectFinder, documents, params.projectObserver);
}
@Bean JavaDocumentUriProvider javaDocumentUriProvider() {
return new JdtJavaDocumentUriProvider();
}
@Bean JavaElementLocationProvider javaElementLocationProvider(CompilationUnitCache cuCache, JavaDocumentUriProvider javaDocUriProvider) {
return new DefaultJavaElementLocationProvider(cuCache, javaDocUriProvider);
}
}

View File

@@ -15,6 +15,8 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ide.vscode.boot.java.BootJavaLanguageServerComponents;
import org.springframework.ide.vscode.boot.java.links.SourceLinks;
import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache;
import org.springframework.ide.vscode.boot.properties.BootPropertiesLanguageServerComponents;
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionEngine;
import org.springframework.ide.vscode.commons.languageserver.completion.VscodeCompletionEngineAdapter;
@@ -32,6 +34,8 @@ public class BootLanguageServerInitializer implements InitializingBean {
@Autowired SimpleLanguageServer server;
@Autowired BootLanguageServerParams params;
@Autowired SourceLinks sourceLinks;
@Autowired CompilationUnitCache cuCache;
private CompositeLanguageServerComponents components;
private VscodeCompletionEngineAdapter completionEngineAdapter;
@@ -55,7 +59,7 @@ public class BootLanguageServerInitializer implements InitializingBean {
// some server intialization code. Migrate that code and get rid of the ComposableLanguageServer class
CompositeLanguageServerComponents.Builder builder = new CompositeLanguageServerComponents.Builder();
builder.add(new BootPropertiesLanguageServerComponents(server, (ignore) -> params));
builder.add(new BootJavaLanguageServerComponents(server, (ignore) -> params));
builder.add(new BootJavaLanguageServerComponents(server, params, sourceLinks, cuCache));
components = builder.build(server);
params.projectObserver.addListener(reconcileOpenDocuments(server, components));

View File

@@ -0,0 +1,56 @@
/*******************************************************************************
* 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.app;
import java.util.List;
import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.TextDocumentPositionParams;
import org.gradle.internal.impldep.com.google.common.collect.ImmutableList;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ide.vscode.boot.java.links.JavaElementLocationProvider;
import org.springframework.ide.vscode.boot.metadata.PropertyInfo;
import org.springframework.ide.vscode.boot.metadata.types.TypeUtil;
import org.springframework.ide.vscode.boot.properties.hover.PropertiesDefinitionCalculator;
import org.springframework.ide.vscode.commons.languageserver.util.DefinitionHandler;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
import org.springframework.ide.vscode.commons.util.BadLocationException;
import org.springframework.ide.vscode.commons.util.FuzzyMap;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
import org.springframework.stereotype.Component;
@Component
public class PropertiesJavaDefinitionHandler implements DefinitionHandler {
@Autowired
private SimpleTextDocumentService documents;
@Autowired
private JavaElementLocationProvider javaDocumentLocationProvider;
@Autowired
private BootLanguageServerParams params;
@Override
public List<Location> handle(TextDocumentPositionParams position) {
try {
TextDocument doc = documents.get(position);
TypeUtil typeUtil = params.typeUtilProvider.getTypeUtil(doc);
FuzzyMap<PropertyInfo> index = params.indexProvider.getIndex(doc);
int offset;
offset = doc.toOffset(position.getPosition());
return new PropertiesDefinitionCalculator(javaDocumentLocationProvider, index, typeUtil, doc, offset).calculate();
} catch (BadLocationException e) {
return ImmutableList.of();
}
}
}

View File

@@ -41,6 +41,7 @@ import org.springframework.ide.vscode.boot.java.handlers.HoverProvider;
import org.springframework.ide.vscode.boot.java.handlers.ReferenceProvider;
import org.springframework.ide.vscode.boot.java.handlers.RunningAppProvider;
import org.springframework.ide.vscode.boot.java.handlers.SymbolProvider;
import org.springframework.ide.vscode.boot.java.links.SourceLinks;
import org.springframework.ide.vscode.boot.java.livehover.ActiveProfilesProvider;
import org.springframework.ide.vscode.boot.java.livehover.BeanInjectedIntoHoverProvider;
import org.springframework.ide.vscode.boot.java.livehover.ComponentInjectionsHoverProvider;
@@ -70,7 +71,6 @@ import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserve
import org.springframework.ide.vscode.commons.languageserver.util.CodeLensHandler;
import org.springframework.ide.vscode.commons.languageserver.util.DocumentHighlightHandler;
import org.springframework.ide.vscode.commons.languageserver.util.HoverHandler;
import org.springframework.ide.vscode.commons.languageserver.util.LSFactory;
import org.springframework.ide.vscode.commons.languageserver.util.ReferencesHandler;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
@@ -108,15 +108,15 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent
private CodeLensHandler codeLensHandler;
private DocumentHighlightHandler highlightsEngine;
public BootJavaLanguageServerComponents(SimpleLanguageServer server, LSFactory<BootLanguageServerParams> _params) {
public BootJavaLanguageServerComponents(SimpleLanguageServer server, BootLanguageServerParams serverParams, SourceLinks sourceLinks, CompilationUnitCache cuCache) {
this.server = server;
this.serverParams = _params.create(server);
this.serverParams = serverParams;
this.config = new BootJavaConfig();
projectFinder = serverParams.projectFinder;
projectObserver = serverParams.projectObserver;
cuCache = new CompilationUnitCache(projectFinder, server.getTextDocumentService(), projectObserver);
this.cuCache = cuCache;
propertyIndexProvider = serverParams.indexProvider;
adHocPropertyIndexProvider = serverParams.adHocIndexProvider;
@@ -146,7 +146,7 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent
// documents.onCodeLens(codeLensHandler::createCodeLenses);
// documents.onCodeLensResolve(codeLensHandler::resolveCodeLens);
hoverProvider = createHoverHandler(projectFinder, serverParams.runningAppProvider);
hoverProvider = createHoverHandler(projectFinder, serverParams.runningAppProvider, sourceLinks);
liveHoverWatchdog = new SpringLiveHoverWatchdog(server, hoverProvider, serverParams.runningAppProvider,
projectFinder, projectObserver, serverParams.watchDogInterval);
documents.onDidChangeContent(params -> {
@@ -174,7 +174,14 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent
// }
});
liveChangeDetectionWatchdog = new SpringLiveChangeDetectionWatchdog(this, server, serverParams.projectObserver, serverParams.runningAppProvider, projectFinder, serverParams.watchDogInterval);
liveChangeDetectionWatchdog = new SpringLiveChangeDetectionWatchdog(
this,
server,
serverParams.projectObserver,
serverParams.runningAppProvider,
projectFinder,
serverParams.watchDogInterval,
sourceLinks);
codeLensHandler = createCodeLensEngine();
documents.onCodeLens(codeLensHandler);
@@ -293,14 +300,14 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent
}
protected BootJavaHoverProvider createHoverHandler(JavaProjectFinder javaProjectFinder,
RunningAppProvider runningAppProvider) {
RunningAppProvider runningAppProvider, SourceLinks sourceLinks) {
AnnotationHierarchyAwareLookup<HoverProvider> providers = new AnnotationHierarchyAwareLookup<>();
ValueHoverProvider valueHoverProvider = new ValueHoverProvider();
RequestMappingHoverProvider requestMappingHoverProvider = new RequestMappingHoverProvider();
AutowiredHoverProvider autowiredHoverProvider = new AutowiredHoverProvider(this);
ComponentInjectionsHoverProvider componentInjectionsHoverProvider = new ComponentInjectionsHoverProvider(this);
BeanInjectedIntoHoverProvider beanInjectedIntoHoverProvider = new BeanInjectedIntoHoverProvider(this);
AutowiredHoverProvider autowiredHoverProvider = new AutowiredHoverProvider(sourceLinks);
ComponentInjectionsHoverProvider componentInjectionsHoverProvider = new ComponentInjectionsHoverProvider(sourceLinks);
BeanInjectedIntoHoverProvider beanInjectedIntoHoverProvider = new BeanInjectedIntoHoverProvider(sourceLinks);
ConditionalsLiveHoverProvider conditionalsLiveHoverProvider = new ConditionalsLiveHoverProvider();
providers.put(org.springframework.ide.vscode.boot.java.value.Constants.SPRING_VALUE, valueHoverProvider);

View File

@@ -32,10 +32,8 @@ import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.boot.java.Annotations;
import org.springframework.ide.vscode.boot.java.BootJavaLanguageServerComponents;
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.links.SourceLinkFactory;
import org.springframework.ide.vscode.boot.java.links.SourceLinks;
import org.springframework.ide.vscode.boot.java.livehover.ComponentInjectionsHoverProvider;
import org.springframework.ide.vscode.boot.java.livehover.LiveHoverUtils;
@@ -65,11 +63,11 @@ public class AutowiredHoverProvider implements HoverProvider {
private static final int MAX_INLINE_BEANS_STRING_LENGTH = 60;
private static final String INLINE_BEANS_STRING_SEPARATOR = " ";
private SourceLinks sourceLinks;
private BootJavaLanguageServerComponents server;
public AutowiredHoverProvider(SourceLinks sourceLinks) {
this.sourceLinks = sourceLinks;
public AutowiredHoverProvider(BootJavaLanguageServerComponents server) {
this.server = server;
}
@Override
@@ -139,7 +137,7 @@ public class AutowiredHoverProvider implements HoverProvider {
} else {
hover.append(" \n \n");
}
createHoverContentForBeans(server, project, hover, autowiredBeans);
createHoverContentForBeans(sourceLinks, project, hover, autowiredBeans);
hover.append("Bean id: `");
hover.append(definedBean.getId());
hover.append("` \n");
@@ -154,14 +152,13 @@ public class AutowiredHoverProvider implements HoverProvider {
return null;
}
public static void createHoverContentForBeans(BootJavaLanguageServerComponents server, IJavaProject project, StringBuilder hover,
public static void createHoverContentForBeans(SourceLinks sourceLinks, IJavaProject project, StringBuilder hover,
List<LiveBean> autowiredBeans) {
SourceLinks sourceLinks = SourceLinkFactory.createSourceLinks(server);
hover.append("**");
hover.append(LiveHoverUtils.createBeansTitleMarkdown(sourceLinks, project, autowiredBeans, BEANS_PREFIX_MARDOWN, MAX_INLINE_BEANS_STRING_LENGTH, INLINE_BEANS_STRING_SEPARATOR));
hover.append("**\n");
hover.append(autowiredBeans.stream()
.map(b -> "- " + LiveHoverUtils.showBeanWithResource(server, b, " ", project))
.map(b -> "- " + LiveHoverUtils.showBeanWithResource(sourceLinks, b, " ", project))
.collect(Collectors.joining("\n")));
hover.append("\n \n");
}

View File

@@ -11,51 +11,41 @@
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.URI;
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.EnumDeclaration;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.boot.java.BootJavaLanguageServerComponents;
import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache;
import org.springframework.ide.vscode.commons.java.IClasspath;
import org.springframework.ide.vscode.commons.java.IClasspathUtil;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.javadoc.TypeUrlProviderFromContainerUrl;
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
*
* 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 BootJavaLanguageServerComponents server;
protected AbstractSourceLinks(BootJavaLanguageServerComponents server) {
this.server = server;
private static final Logger log = LoggerFactory.getLogger(AbstractSourceLinks.class);
private CompilationUnitCache cuCache;
protected AbstractSourceLinks(CompilationUnitCache cuCache) {
this.cuCache = cuCache;
}
@Override
@@ -65,7 +55,7 @@ public abstract class AbstractSourceLinks implements SourceLinks {
File file = classpathResource.get();
if (file.isDirectory()) {
return javaSourceLinkUrl(project, fqName, file);
} else if (file.getName().endsWith(JAR)) {
} else {
return jarSourceLinkUrl(project, fqName, file);
}
}
@@ -81,123 +71,56 @@ public abstract class AbstractSourceLinks implements SourceLinks {
}
return Optional.empty();
}
private Optional<String> javaSourceLinkUrl(IJavaProject project, String fqName, File containerFolder) {
IClasspath classpath = project.getClasspath();
return IClasspathUtil.getSourceFolders(classpath)
.map(sourceFolder -> {
try {
return 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 TypeUrlProviderFromContainerUrl.SOURCE_FOLDER_URL_SUPPLIER.url(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()
return SourceLinks.sourceFromSourceFolder(fqName, classpath)
.map(sourcePath -> javaSourceLinkUrl(project, sourcePath, fqName));
}
private String javaSourceLinkUrl(IJavaProject project, Path sourcePath, String fqName) {
Optional<String> linkOptional = sourceLinkForResourcePath(sourcePath);
if (linkOptional.isPresent()) {
Optional<String> positionLink = findCUForJavaSourceFile(sourcePath).map(cu -> positionLink(cu, fqName));
Optional<String> positionLink = findCU(project, sourcePath.toUri()).map(cu -> positionLink(cu, fqName));
return positionLink.isPresent() ? linkOptional.get() + positionLink.get() : linkOptional.get();
}
return null;
}
abstract protected String positionLink(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> findCU(IJavaProject project, URI uri) {
return cuCache == null ? Optional.empty() : cuCache.withCompilationUnit(project, uri, compilationUnit -> Optional.ofNullable(compilationUnit));
}
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);
abstract protected Optional<String> jarLinkUrl(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 jarLinkUrl(project, fqName, jarFile).map(sourceUrl -> {
Optional<String> positionLink = findCUForFQNameFromJar(project, jarFile, 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.sourceContainer(jarFile)
.map(url -> {
try {
return TypeUrlProviderFromContainerUrl.JAR_SOURCE_URL_PROVIDER.url(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;
private Optional<CompilationUnit> findCUForFQNameFromJar(IJavaProject project, File jarFile, String fqName) {
return project.sourceContainer(jarFile).map(url -> {
try {
return TypeUrlProviderFromContainerUrl.JAR_SOURCE_URL_PROVIDER.url(url, fqName);
} catch (Exception e) {
log.warn("Failed to determine source URL from url={} fqName={}", url, fqName, e);
return null;
}
}).map(sourceUrl -> {
try {
return sourceUrl.toURI();
} catch (URISyntaxException e) {
throw new IllegalStateException(e);
}
}).map(sourcePath -> findCU(project, sourcePath).orElse(null));
}
protected Region findTypeRegion(CompilationUnit cu, String fqName) {
if (cu == null) {
return null;
@@ -209,7 +132,7 @@ public abstract class AbstractSourceLinks implements SourceLinks {
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) {
@@ -231,6 +154,17 @@ public abstract class AbstractSourceLinks implements SourceLinks {
return visitDeclaration(node);
}
@Override
public boolean visit(EnumDeclaration node) {
return visitDeclaration(node);
}
@Override
public void endVisit(EnumDeclaration node) {
visitedType.pop();
super.endVisit(node);
}
@Override
public void endVisit(AnnotationTypeDeclaration node) {
visitedType.pop();
@@ -247,5 +181,5 @@ public abstract class AbstractSourceLinks implements SourceLinks {
}
return values[1] < 0 ? null : new Region(values[0], values[1]);
}
}

View File

@@ -19,7 +19,7 @@ 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.BootJavaLanguageServerComponents;
import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.util.text.Region;
@@ -28,7 +28,7 @@ import com.google.common.base.Suppliers;
/**
* Source links for Atom client
*
*
* @author Alex Boyko
*
*/
@@ -36,8 +36,8 @@ public class AtomSourceLinks extends AbstractSourceLinks {
private static Supplier<Logger> LOG = Suppliers.memoize(() -> LoggerFactory.getLogger(AbstractSourceLinks.class));
public AtomSourceLinks(BootJavaLanguageServerComponents server) {
super(server);
public AtomSourceLinks(CompilationUnitCache cuCache) {
super(cuCache);
}
@Override
@@ -69,7 +69,7 @@ public class AtomSourceLinks extends AbstractSourceLinks {
}
@Override
protected Optional<String> jarUrl(IJavaProject project, String fqName, File jarFile) {
protected Optional<String> jarLinkUrl(IJavaProject project, String fqName, File jarFile) {
// JAR URLs are not supported yet
return Optional.empty();
}

View File

@@ -0,0 +1,148 @@
/*******************************************************************************
* 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.net.URI;
import java.net.URL;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.EnumConstantDeclaration;
import org.eclipse.jdt.core.dom.EnumDeclaration;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache;
import org.springframework.ide.vscode.commons.java.IField;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.java.IMember;
import org.springframework.ide.vscode.commons.java.IMethod;
import org.springframework.ide.vscode.commons.java.IType;
public class DefaultJavaElementLocationProvider implements JavaElementLocationProvider {
private static final Logger log = LoggerFactory.getLogger(DefaultJavaElementLocationProvider.class);
private CompilationUnitCache cuCache;
private JavaDocumentUriProvider javaDocUriProvider;
public DefaultJavaElementLocationProvider(CompilationUnitCache cuCache, JavaDocumentUriProvider javaDocUriProvider) {
this.cuCache = cuCache;
this.javaDocUriProvider = javaDocUriProvider;
}
@Override
public Location findLocation(IJavaProject project, IMember member) {
Location loc = new Location();
String fqName = member.getDeclaringType().getFullyQualifiedName();
URI docUri = javaDocUriProvider.docUri(project, fqName);
if (docUri != null) {
loc.setUri(docUri.toString());
Optional<URL> url = SourceLinks.source(project, fqName);
if (url.isPresent()) {
String memberBindingKey = member.getBindingKey();
try {
URI uri = url.get().toURI();
Range r = cuCache.withCompilationUnit(project, uri, (cu) -> {
AtomicReference<Range> range = new AtomicReference<>(null);
cu.accept(new ASTVisitor() {
private Range nameRange(SimpleName nameNode) {
int startOffset = nameNode.getStartPosition();
int endOffset = nameNode.getLength() + startOffset;
// Line -1 because for CU lines are starting from 1
return new Range(
new Position(cu.getLineNumber(startOffset) - 1, cu.getColumnNumber(startOffset)),
new Position(cu.getLineNumber(endOffset) - 1, cu.getColumnNumber(endOffset)));
}
@Override
public boolean visit(MethodDeclaration node) {
if (member instanceof IMethod) {
String bindingKey = node.resolveBinding().getKey();
if (matchMethodBindingKeys(memberBindingKey, bindingKey)) {
range.set(nameRange(node.getName()));
return false;
}
}
return true;
}
@Override
public boolean visit(EnumConstantDeclaration node) {
if (member instanceof IField) {
String bindingKey = node.resolveVariable().getKey();
if (memberBindingKey.equals(bindingKey)) {
range.set(nameRange(node.getName()));
return false;
}
}
return true;
}
@Override
public boolean visit(EnumDeclaration node) {
if (member instanceof IType) {
String bindingKey = node.resolveBinding().getKey();
if (memberBindingKey.equals(bindingKey)) {
range.set(nameRange(node.getName()));
return false;
}
}
return true;
}
@Override
public boolean visit(TypeDeclaration node) {
if (member instanceof IType) {
String bindingKey = node.resolveBinding().getKey();
if (memberBindingKey.equals(bindingKey)) {
range.set(nameRange(node.getName()));
return false;
}
}
return true;
}
});
return range.get();
});
if (r == null) {
throw new IllegalStateException("Couldn't find " + member);
}
loc.setRange(r);
} catch (Throwable t) {
log.error("", t);
}
}
}
return loc;
}
private static boolean matchMethodBindingKeys(String key1, String key2) {
return removeReturnTypeFromMethodKeyBinding(key1).equals(removeReturnTypeFromMethodKeyBinding(key2));
}
private static String removeReturnTypeFromMethodKeyBinding(String key) {
int idx = key.lastIndexOf(')');
return idx >= 0 ? key.substring(0, idx + 1) : key;
}
}

View File

@@ -0,0 +1,22 @@
/*******************************************************************************
* 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.net.URI;
import org.springframework.ide.vscode.commons.java.IJavaProject;
@FunctionalInterface
public interface JavaDocumentUriProvider {
URI docUri(IJavaProject project, String fqName);
}

View File

@@ -0,0 +1,22 @@
/*******************************************************************************
* 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 org.eclipse.lsp4j.Location;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.java.IMember;
@FunctionalInterface
public interface JavaElementLocationProvider {
Location findLocation(IJavaProject project, IMember member);
}

View File

@@ -0,0 +1,78 @@
/*******************************************************************************
* 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.URI;
import java.net.URLEncoder;
import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.commons.java.IClasspath;
import org.springframework.ide.vscode.commons.java.IJavaProject;
public class JdtJavaDocumentUriProvider implements JavaDocumentUriProvider {
private static final Logger log = LoggerFactory.getLogger(JdtJavaDocumentUriProvider.class);
@Override
public URI docUri(IJavaProject project, String fqName) {
return uri(project, fqName);
}
public static URI uri(IJavaProject project, String fqName) {
Optional<File> classpathResource = project.getIndex().findClasspathResourceContainer(fqName);
if (classpathResource.isPresent()) {
File file = classpathResource.get();
if (file.isDirectory()) {
IClasspath classpath = project.getClasspath();
return SourceLinks.sourceFromSourceFolder(fqName, classpath).map(path -> path.toUri()).orElse(null);
} else {
try {
int lastDotIndex = fqName.lastIndexOf('.');
String packageName = fqName.substring(0, lastDotIndex);
String typeName = fqName.substring(lastDotIndex + 1);
String jarFileName = file.getName();
StringBuilder sb = new StringBuilder();
sb.append("jdt://contents/");
sb.append(jarFileName);
sb.append("/");
sb.append(packageName);
sb.append("/");
sb.append(typeName);
sb.append(SourceLinks.CLASS);
sb.append("?");
StringBuilder query = new StringBuilder();
query.append("=");
query.append(project.getElementName());
query.append("/");
String convertedPath = file.toString().replace(File.separator, "\\/");
query.append(convertedPath);
query.append("<");
query.append(packageName);
query.append("(");
query.append(typeName);
query.append(SourceLinks.CLASS);
sb.append(URLEncoder.encode(query.toString(), "UTF8"));
return URI.create(sb.toString());
} catch (Throwable t) {
log.warn("Failed creating Java document URI for " + file + " type " + fqName + " in the context of project " + project.getElementName(), t);
}
}
}
return null;
}
}

View File

@@ -14,54 +14,61 @@ import java.nio.file.Path;
import java.util.Optional;
import org.springframework.ide.vscode.boot.java.BootJavaLanguageServerComponents;
import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache;
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() {
public 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(BootJavaLanguageServerComponents server) {
public static SourceLinks createSourceLinks(CompilationUnitCache cuCache) {
switch (LspClient.currentClient()) {
case VSCODE:
case THEIA:
return new VSCodeSourceLinks(server);
case THEIA:
return new VSCodeSourceLinks(cuCache);
case ECLIPSE:
return new EclipseSourceLinks();
case ATOM:
return new AtomSourceLinks(server);
return new AtomSourceLinks(cuCache);
default:
return NO_SOURCE_LINKS;
}
}
@Deprecated
public static SourceLinks createSourceLinks(BootJavaLanguageServerComponents server) {
return server == null
? createSourceLinks((CompilationUnitCache)null)
: createSourceLinks(server.getCompilationUnitCache());
}
}

View File

@@ -10,25 +10,97 @@
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.links;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.commons.java.IClasspath;
import org.springframework.ide.vscode.commons.java.IClasspathUtil;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.javadoc.TypeUrlProviderFromContainerUrl;
/**
* 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 Logger log = LoggerFactory.getLogger(SourceLinks.class);
static final String JAR = ".jar";
static final String CLASS = ".class";
public static Optional<Path> sourceFromSourceFolder(String fqName, IClasspath classpath) {
return IClasspathUtil.getSourceFolders(classpath)
.map(sourceFolder -> {
try {
return sourceFolder.toURI().toURL();
} catch (MalformedURLException e) {
log.warn("Failed to convert source folder {} to URI {}", sourceFolder, fqName, e);
return null;
}
})
.map(url -> {
try {
return TypeUrlProviderFromContainerUrl.SOURCE_FOLDER_URL_SUPPLIER.url(url, fqName);
} catch (Exception e) {
log.warn("Failed to determine source URL from url={} fqName=", url, fqName, e);
return null;
}
})
.map(url -> {
try {
return Paths.get(url.toURI());
} catch (URISyntaxException e) {
log.warn("Failed to convert URL {} to path. {}", url, fqName, e);
return null;
}
})
.filter(sourcePath -> sourcePath != null && Files.exists(sourcePath))
.findFirst();
}
public static Optional<URL> source(IJavaProject project, String fqName) {
Optional<File> classpathResourceContainer = project.findClasspathResourceContainer(fqName);
// Try to find in a source JAR
Optional<URL> url = classpathResourceContainer
.flatMap(file -> project.sourceContainer(file))
.map(file -> {
try {
return TypeUrlProviderFromContainerUrl.JAR_SOURCE_URL_PROVIDER.url(file, fqName);
} catch (Exception e) {
throw new IllegalStateException(e);
}
});
if (!url.isPresent()) {
// Try Source folder
url = classpathResourceContainer
.flatMap(file -> sourceFromSourceFolder(fqName, project.getClasspath()).map(p -> {
try {
return p.toUri().toURL();
} catch (MalformedURLException e) {
throw new IllegalStateException(e);
}
}));
}
return url;
}
/**
* 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
@@ -44,12 +116,12 @@ public interface SourceLinks {
* @return the link URL optional
*/
Optional<String> sourceLinkUrlForClasspathResource(IJavaProject project, String path);
/**
* Creates link to a file specified by it's 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

@@ -11,32 +11,24 @@
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.BootJavaLanguageServerComponents;
import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache;
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(BootJavaLanguageServerComponents server) {
super(server);
public VSCodeSourceLinks(CompilationUnitCache cuCache) {
super(cuCache);
}
@Override
@@ -63,41 +55,8 @@ public class VSCodeSourceLinks extends AbstractSourceLinks {
}
@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();
protected Optional<String> jarLinkUrl(IJavaProject project, String fqName, File jarFile) {
return Optional.ofNullable(JdtJavaDocumentUriProvider.uri(project, fqName)).map(uri -> uri.toString());
}
}

View File

@@ -27,10 +27,8 @@ import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.boot.java.BootJavaLanguageServerComponents;
import org.springframework.ide.vscode.boot.java.autowired.AutowiredHoverProvider;
import org.springframework.ide.vscode.boot.java.handlers.HoverProvider;
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.ASTUtils;
import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp;
@@ -51,10 +49,10 @@ public abstract class AbstractInjectedIntoHoverProvider implements HoverProvider
private static final int MAX_INLINE_BEANS_STRING_LENGTH = 60;
private static final String INLINE_BEANS_STRING_SEPARATOR = " ";
protected BootJavaLanguageServerComponents server;
private SourceLinks sourceLinks;
public AbstractInjectedIntoHoverProvider(BootJavaLanguageServerComponents server) {
this.server = server;
public AbstractInjectedIntoHoverProvider(SourceLinks sourceLinks) {
this.sourceLinks = sourceLinks;
}
@Override
@@ -149,18 +147,17 @@ public abstract class AbstractInjectedIntoHoverProvider implements HoverProvider
}
if (!injectedBeans.isEmpty()) {
SourceLinks sourceLinks = SourceLinkFactory.createSourceLinks(server);
hover.append("**");
hover.append(LiveHoverUtils.createBeansTitleMarkdown(sourceLinks, project, injectedBeans, BEANS_PREFIX_MARKDOWN, MAX_INLINE_BEANS_STRING_LENGTH, INLINE_BEANS_STRING_SEPARATOR));
hover.append("**\n");
hover.append(injectedBeans.stream()
.map(b -> "- " + LiveHoverUtils.showBeanWithResource(server, b, " ", project))
.map(b -> "- " + LiveHoverUtils.showBeanWithResource(sourceLinks, b, " ", project))
.collect(Collectors.joining("\n")));
hover.append("\n \n");
}
List<LiveBean> wiredBeans = findWiredBeans(project, app, relevantBeans, astNode);
if (!wiredBeans.isEmpty()) {
AutowiredHoverProvider.createHoverContentForBeans(server, project, hover, wiredBeans);
AutowiredHoverProvider.createHoverContentForBeans(sourceLinks, project, hover, wiredBeans);
}
hover.append("Bean id: `");

View File

@@ -17,8 +17,8 @@ import java.util.Optional;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.springframework.ide.vscode.boot.java.BootJavaLanguageServerComponents;
import org.springframework.ide.vscode.boot.java.autowired.AutowiredHoverProvider;
import org.springframework.ide.vscode.boot.java.links.SourceLinks;
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;
@@ -27,8 +27,8 @@ import org.springframework.ide.vscode.commons.util.Optionals;
public class BeanInjectedIntoHoverProvider extends AbstractInjectedIntoHoverProvider {
public BeanInjectedIntoHoverProvider(BootJavaLanguageServerComponents server) {
super(server);
public BeanInjectedIntoHoverProvider(SourceLinks sourceLinks) {
super(sourceLinks);
}
@Override

View File

@@ -28,8 +28,8 @@ import org.eclipse.lsp4j.Range;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.boot.java.Annotations;
import org.springframework.ide.vscode.boot.java.BootJavaLanguageServerComponents;
import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchies;
import org.springframework.ide.vscode.boot.java.links.SourceLinks;
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;
@@ -44,8 +44,8 @@ public class ComponentInjectionsHoverProvider extends AbstractInjectedIntoHoverP
private static Logger LOG = LoggerFactory.getLogger(ComponentInjectionsHoverProvider.class);
public ComponentInjectionsHoverProvider(BootJavaLanguageServerComponents server) {
super(server);
public ComponentInjectionsHoverProvider(SourceLinks sourceLinks) {
super(sourceLinks);
}
@Override

View File

@@ -20,8 +20,6 @@ import java.util.stream.Collectors;
import org.eclipse.lsp4j.CodeLens;
import org.eclipse.lsp4j.Command;
import org.eclipse.lsp4j.Range;
import org.springframework.ide.vscode.boot.java.BootJavaLanguageServerComponents;
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;
@@ -47,7 +45,7 @@ public class LiveHoverUtils {
return buf.toString();
}
public static String showBeanWithResource(BootJavaLanguageServerComponents server, LiveBean bean, String indentStr, IJavaProject project) {
public static String showBeanWithResource(SourceLinks sourceLinks, 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
if (bean == CANT_MATCH_PROPER_BEAN) {
@@ -59,7 +57,6 @@ public class LiveHoverUtils {
buf.append('`');
buf.append(bean.getId());
buf.append('`');
SourceLinks sourceLinks = SourceLinkFactory.createSourceLinks(server);
if (type != null) {
// Try creating a URL link to open source for the type
buf.append(newline);
@@ -126,33 +123,6 @@ public class LiveHoverUtils {
return true;
}
public static String showBeanIdAndTypeInline(BootJavaLanguageServerComponents server, IJavaProject project, LiveBean bean) {
String id = bean.getId();
String type = bean.getType(true);
SourceLinks sourceLinks = SourceLinkFactory.createSourceLinks(server);
String displayType = type;
if (type != null) {
int lastDotIdx = type.lastIndexOf('.');
if (lastDotIdx >= 0 && lastDotIdx < type.length() - 1) {
displayType = "`" + type.substring(lastDotIdx + 1) + "`";
}
Optional<String> url = sourceLinks.sourceLinkUrlForFQName(project, type);
if (url.isPresent()) {
displayType = Renderables.link(displayType, url.get()).toMarkdown();
}
}
StringBuilder sb = new StringBuilder();
sb.append('`');
sb.append(id);
sb.append('`');
if (displayType != null) {
sb.append(' ');
sb.append(displayType);
}
return sb.toString();
}
public static String showResource(SourceLinks sourceLinks, String resource, IJavaProject project) {
return new SpringResource(sourceLinks, resource, project).toMarkdown();
}

View File

@@ -22,23 +22,27 @@ import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
import java.util.function.Function;
import java.util.stream.Stream;
import org.apache.commons.io.IOUtils;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.commons.java.IClasspath;
import org.springframework.ide.vscode.commons.java.IClasspathUtil;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
import org.springframework.ide.vscode.commons.util.Log;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
public final class CompilationUnitCache {
public final class CompilationUnitCache implements DocumentContentProvider {
private static final Logger logger = LoggerFactory.getLogger(CompilationUnitCache.class);
private static final long CU_ACCESS_EXPIRATION = 1;
private JavaProjectFinder projectFinder;
@@ -46,13 +50,15 @@ public final class CompilationUnitCache {
private Cache<URI, CompilationUnit> uriToCu;
private Cache<IJavaProject, Set<URI>> projectToDocs;
private ProjectObserver.Listener projectListener;
private SimpleTextDocumentService documents;
private ReadLock readLock;
private WriteLock writeLock;
public CompilationUnitCache(JavaProjectFinder projectFinder, SimpleTextDocumentService documentService, ProjectObserver projectObserver) {
public CompilationUnitCache(JavaProjectFinder projectFinder, SimpleTextDocumentService documents, ProjectObserver projectObserver) {
this.projectFinder = projectFinder;
this.projectObserver = projectObserver;
this.documents = documents;
projectListener = ProjectObserver.onAny(this::invalidateProject);
// PT 154618835 - Avoid retaining the CU in the cache as it consumes memory if it hasn't been
@@ -66,9 +72,9 @@ public final class CompilationUnitCache {
readLock = lock.readLock();
writeLock = lock.writeLock();
if (documentService != null) {
documentService.onDidChangeContent(doc -> invalidateCuForJavaFile(doc.getDocument().getId().getUri()));
documentService.onDidClose(doc -> invalidateCuForJavaFile(doc.getId().getUri()));
if (documents != null) {
documents.onDidChangeContent(doc -> invalidateCuForJavaFile(doc.getDocument().getId().getUri()));
documents.onDidClose(doc -> invalidateCuForJavaFile(doc.getId().getUri()));
}
if (this.projectObserver != null) {
@@ -90,10 +96,14 @@ public final class CompilationUnitCache {
* not pass of AST nodes to helper functions that work aynchronously or store AST nodes or ITypeBindings
* for later use. The JDT ASTs are not thread safe!
*/
@Deprecated
public <T> T withCompilationUnit(TextDocument document, Function<CompilationUnit, T> requestor) {
URI uri = URI.create(document.getUri());
IJavaProject project = projectFinder.find(document.getId()).orElse(null);
URI uri = URI.create(document.getUri());
return withCompilationUnit(project, uri, requestor);
}
public <T> T withCompilationUnit(IJavaProject project, URI uri, Function<CompilationUnit, T> requestor) {
if (project != null) {
readLock.lock();
@@ -101,15 +111,15 @@ public final class CompilationUnitCache {
try {
cu = uriToCu.get(uri, () -> {
CompilationUnit cUnit = parse(document, project);
projectToDocs.get(project, () -> new HashSet<>()).add(URI.create(document.getUri()));
CompilationUnit cUnit = parse(uri.toString(), fetchContent(uri).toCharArray(), project);
projectToDocs.get(project, () -> new HashSet<>()).add(uri);
return cUnit;
});
if (cu != null) {
projectToDocs.get(project, () -> new HashSet<>()).add(URI.create(document.getUri()));
projectToDocs.get(project, () -> new HashSet<>()).add(uri);
}
} catch (Exception e) {
Log.log(e);
logger.error("", e);
} finally {
readLock.unlock();
}
@@ -121,7 +131,7 @@ public final class CompilationUnitCache {
}
}
catch (Exception e) {
Log.log(e);
logger.error("", e);
}
}
}
@@ -129,6 +139,7 @@ public final class CompilationUnitCache {
return requestor.apply(null);
}
private void invalidateCuForJavaFile(String uriStr) {
URI uri = URI.create(uriStr);
writeLock.lock();
@@ -140,13 +151,19 @@ public final class CompilationUnitCache {
}
public static CompilationUnit parse(TextDocument document, IJavaProject project) throws Exception {
String[] classpathEntries = getClasspathEntries(document, project);
String[] classpathEntries = getClasspathEntries(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(String uri, char[] source, IJavaProject project) throws Exception {
String[] classpathEntries = getClasspathEntries(project);
String unitName = uri.substring(uri.lastIndexOf("/"));
return parse(source, uri, unitName, classpathEntries);
}
public static CompilationUnit parse(char[] source, String docURI, String unitName, String[] classpathEntries) throws Exception {
ASTParser parser = ASTParser.newParser(AST.JLS10);
Map<String, String> options = JavaCore.getOptions();
@@ -168,7 +185,7 @@ public final class CompilationUnitCache {
return cu;
}
private static String[] getClasspathEntries(TextDocument document, IJavaProject project) throws Exception {
private static String[] getClasspathEntries(IJavaProject project) throws Exception {
if (project == null) {
return new String[0];
} else {
@@ -192,4 +209,16 @@ public final class CompilationUnitCache {
}
}
}
@Override
public String fetchContent(URI uri) throws Exception {
if (documents != null) {
TextDocument document = documents.get(uri.toString());
if (document != null) {
return document.get(0, document.getLength());
}
}
return IOUtils.toString(uri);
}
}

View File

@@ -0,0 +1,20 @@
/*******************************************************************************
* 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.net.URI;
@FunctionalInterface
public interface DocumentContentProvider {
String fetchContent(URI uri) throws Exception;
}

View File

@@ -35,7 +35,6 @@ import org.springframework.ide.vscode.boot.java.BootJavaLanguageServerComponents
import org.springframework.ide.vscode.boot.java.handlers.RunningAppMatcher;
import org.springframework.ide.vscode.boot.java.handlers.RunningAppProvider;
import org.springframework.ide.vscode.boot.java.links.SourceLinks;
import org.springframework.ide.vscode.boot.java.links.VSCodeSourceLinks;
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.java.IJavaProject;
@@ -72,7 +71,8 @@ public class SpringLiveChangeDetectionWatchdog {
ProjectObserver projectObserver,
RunningAppProvider runningAppProvider,
JavaProjectFinder projectFinder,
Duration pollingInterval
Duration pollingInterval,
SourceLinks sourceLinks
) {
this.observedProjects = new HashSet<>();
@@ -82,7 +82,7 @@ public class SpringLiveChangeDetectionWatchdog {
this.POLLING_INTERVAL_MILLISECONDS = pollingInterval == null ? DEFAULT_INTERVAL.toMillis() : pollingInterval.toMillis();
this.changeHistory = new ChangeDetectionHistory();
this.sourceLinks = new VSCodeSourceLinks(bootJavaLanguageServerComponents);
this.sourceLinks = sourceLinks;
if (projectObserver != null) {
projectObserver.addListener(new Listener() {

View File

@@ -15,6 +15,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.boot.configurationmetadata.Deprecation;
import org.springframework.ide.vscode.boot.configurationmetadata.ValueHint;
import org.springframework.ide.vscode.boot.java.BootJavaLanguageServerComponents;
import org.springframework.ide.vscode.boot.java.links.SourceLinkFactory;
import org.springframework.ide.vscode.boot.java.links.SourceLinks;
import org.springframework.ide.vscode.boot.metadata.types.TypeUtil;
@@ -120,10 +121,10 @@ public class StsValueHint {
public Renderable getDescription() {
return description;
}
private static Renderable javaDocSnippet(IJavaProject project, IJavaElement je) {
return Renderables.lazy(() -> {
SourceLinks sourceLinks = SourceLinkFactory.createSourceLinks(null);
SourceLinks sourceLinks = SourceLinkFactory.createSourceLinks((BootJavaLanguageServerComponents)null);
return PropertyDocUtils.documentJavaElement(sourceLinks, project, je);
});
}

View File

@@ -37,6 +37,7 @@ import javax.inject.Provider;
import org.springframework.boot.convert.DurationStyle;
import org.springframework.ide.vscode.boot.configurationmetadata.Deprecation;
import org.springframework.ide.vscode.boot.java.BootJavaLanguageServerComponents;
import org.springframework.ide.vscode.boot.java.links.SourceLinkFactory;
import org.springframework.ide.vscode.boot.java.links.SourceLinks;
import org.springframework.ide.vscode.boot.metadata.ResourceHintProvider;
@@ -657,7 +658,7 @@ public class TypeUtil {
//TODO: handle type parameters.
if (typeFromIndex != null) {
SourceLinks sourceLinks = SourceLinkFactory.createSourceLinks(null);
SourceLinks sourceLinks = SourceLinkFactory.createSourceLinks((BootJavaLanguageServerComponents)null);
IJavaProject project = getJavaProject();
ArrayList<TypedProperty> properties = new ArrayList<>();
getGetterMethods(typeFromIndex).forEach(m -> {

View File

@@ -12,7 +12,6 @@ package org.springframework.ide.vscode.boot.metadata.util;
import java.util.Optional;
import org.springframework.ide.vscode.boot.java.links.SourceLinkFactory;
import org.springframework.ide.vscode.boot.java.links.SourceLinks;
import org.springframework.ide.vscode.commons.java.IJavaElement;
import org.springframework.ide.vscode.commons.java.IJavaProject;
@@ -27,15 +26,15 @@ import com.google.common.collect.ImmutableList.Builder;
/**
* Boot properties documentation info utils
*
*
* @author Alex Boyko
*
*/
public class PropertyDocUtils {
/**
* Generates documentation for boot property coming from java element
*
*
* @param sourceLinks
* @param project
* @param je
@@ -48,10 +47,10 @@ public class PropertyDocUtils {
renderableBuilder.add(Renderables.paragraph(javadoc == null ? Renderables.NO_DESCRIPTION: javadoc.getRenderable()));
return Renderables.concat(renderableBuilder.build());
}
/**
* Generates documentation for the value of some Java type. Includes signature, javadoc, link to container type.
*
*
* @param sourceLinks
* @param project
* @param je
@@ -66,7 +65,7 @@ public class PropertyDocUtils {
IType containingType = je instanceof IType ? (IType) je : ((IMember)je).getDeclaringType();
if (je != null) {
String type = containingType.getFullyQualifiedName();
Optional<String> url = SourceLinkFactory.createSourceLinks(null).sourceLinkUrlForFQName(project, type);
Optional<String> url = sourceLinks.sourceLinkUrlForFQName(project, type);
renderableBuilder.add(Renderables.lineBreak());
if (url.isPresent()) {
renderableBuilder.add(Renderables.link(type, url.get()));

View File

@@ -0,0 +1,237 @@
/*******************************************************************************
* 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.properties.hover;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.lsp4j.Location;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.boot.java.links.JavaElementLocationProvider;
import org.springframework.ide.vscode.boot.metadata.PropertyInfo;
import org.springframework.ide.vscode.boot.metadata.PropertyInfo.PropertySource;
import org.springframework.ide.vscode.boot.metadata.types.TypeUtil;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.java.IMember;
import org.springframework.ide.vscode.commons.java.IMethod;
import org.springframework.ide.vscode.commons.java.IType;
import org.springframework.ide.vscode.commons.util.FuzzyMap;
import org.springframework.ide.vscode.commons.util.StringUtil;
import org.springframework.ide.vscode.commons.util.text.IDocument;
import org.springframework.ide.vscode.java.properties.parser.PropertiesAst.Key;
import org.springframework.ide.vscode.java.properties.parser.PropertiesAst.Node;
import org.springframework.ide.vscode.java.properties.parser.PropertiesAst.Value;
import com.google.common.collect.ImmutableList;
public class PropertiesDefinitionCalculator {
private static final Logger log = LoggerFactory.getLogger(PropertiesDefinitionCalculator.class);
final private PropertyFinder propertyFinder;
final private IJavaProject project;
final private JavaElementLocationProvider javaElementLocationProvider;
public PropertiesDefinitionCalculator(JavaElementLocationProvider javaElementLocationProvider, FuzzyMap<PropertyInfo> index, TypeUtil typeUtil, IDocument doc, int offset) {
this.javaElementLocationProvider = javaElementLocationProvider;
this.propertyFinder = new PropertyFinder(index, typeUtil, doc, offset);
this.project = typeUtil.getJavaProject();
}
public List<Location> calculate() {
Node node = propertyFinder.findNode();
if (node instanceof Key) {
Collection<IMember> propertyJavaElements = getPropertyJavaElements(((Key) node).decode());
return getPropertyDefintion(propertyJavaElements);
} else if (node instanceof Value) {
Value value = (Value) node;
Key key = value.getParent().getKey();
String javaTypeFqName = getPropertyJavaTypeFqName(key.decode());
if (javaTypeFqName != null) {
// Class reference value link
if ("java.lang.Class".equals(javaTypeFqName)) {
IType javaType = project.findType(value.decode());
if (javaType != null) {
Location location = findLocation(javaType);
if (location != null) {
return ImmutableList.of(location);
}
}
}
IType javaType = project.findType(javaTypeFqName);
if (javaType != null) {
// Enum value link
if (javaType.isEnum()) {
String enumValue = StringUtil.hyphensToUpperCase(StringUtil.camelCaseToHyphens(value.decode()));
ImmutableList.Builder<Location> list = ImmutableList.builder();
javaType.getFields().forEach(field -> {
if (field.getElementName().equals(enumValue)) {
Location location = findLocation(field);
if (location != null) {
list.add(location);
}
}
});
return list.build();
}
}
}
}
return ImmutableList.of();
}
private List<Location> getPropertyDefintion(Collection<IMember> propertyJavaElements) {
return propertyJavaElements.stream().map(this::findLocation).filter(Objects::nonNull).collect(Collectors.toList());
}
private Location findLocation(IMember element) {
return javaElementLocationProvider.findLocation(project, element);
}
private String getPropertyJavaTypeFqName(String propertyKey) {
PropertyInfo best = propertyFinder.findBestHoverMatch(propertyKey);
if (best != null) {
String type = best.getType();
// Trim down generic type if present
int idx = type == null ? -1 : type.indexOf('<');
return idx < 0 ? type : type.substring(0, idx);
}
return null;
}
private Collection<IMember> getPropertyJavaElements(String propertyKey) {
PropertyInfo best = propertyFinder.findBestHoverMatch(propertyKey);
if (best != null) {
List<PropertySource> sources = best.getSources();
if (sources != null) {
ImmutableList.Builder<IMember> elements = ImmutableList.builder();
for (PropertySource source : sources) {
String typeName = source.getSourceType();
if (typeName!=null) {
IType type = project.findType(typeName);
IMethod method = null;
if (type!=null) {
String methodSig = source.getSourceMethod();
if (methodSig!=null) {
method = getMethod(type, methodSig);
} else {
method = getSetter(type, best);
}
}
if (method!=null) {
elements.add(method);
} else if (type!=null) {
elements.add(type);
}
}
}
return elements.build();
}
}
return ImmutableList.of();
}
private IMethod getMethod(IType type, String methodSig) {
String name = getMethodName(methodSig);
//TODO: This code assumes 0 arguments, which is the case currently for all
// 'real' data in spring jars.
IMethod m = type.getMethod(name, Stream.empty());
if (m!=null) {
return m;
}
//try find a method with the same name.
return type.getMethods()
.filter(meth -> name.equals(meth.getElementName()))
.findFirst()
.orElse(null);
}
private static String getMethodName(String methodSig) {
String name;
int nameEnd = methodSig.indexOf('(');
if (nameEnd>=0) {
name = methodSig.substring(0, nameEnd);
int space = name.lastIndexOf(' ');
if (space >= 0) {
name = name.substring(space + 1);
}
} else {
name = methodSig;
}
return name;
}
/**
* Attempt to find corresponding setter method for a given property.
* @return setter method, or null if not found.
*/
private IMethod getSetter(IType type, PropertyInfo propertyInfo) {
try {
String propName = propertyInfo.getName();
String setterName = "set"
+Character.toUpperCase(propName.charAt(0))
+toCamelCase(propName.substring(1));
String sloppySetterName = setterName.toLowerCase();
IMethod sloppyMatch = null;
for (IMethod m : type.getMethods().collect(Collectors.toList())) {
String mname = m.getElementName();
if (setterName.equals(mname)) {
//found 'exact' name match... done
return m;
} else if (mname.toLowerCase().equals(sloppySetterName)) {
sloppyMatch = m;
}
}
return sloppyMatch;
} catch (Exception e) {
log.error("", e);
return null;
}
}
/**
* Convert hyphened name to camel case name. It is
* safe to call this on an already camel-cased name.
*/
private String toCamelCase(String name) {
if (name.isEmpty()) {
return name;
} else {
StringBuilder camel = new StringBuilder();
char[] chars = name.toCharArray();
for (int i = 0; i < chars.length; i++) {
char c = chars[i];
if (c=='-') {
i++;
if (i<chars.length) {
camel.append(Character.toUpperCase(chars[i]));
}
} else {
camel.append(chars[i]);
}
}
return camel.toString();
}
}
}

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2016-2017 Pivotal, Inc.
* Copyright (c) 2016, 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
@@ -24,20 +24,15 @@ import java.util.Optional;
import org.springframework.ide.vscode.boot.common.InformationTemplates;
import org.springframework.ide.vscode.boot.metadata.PropertyInfo;
import org.springframework.ide.vscode.boot.metadata.SpringPropertyIndex;
import org.springframework.ide.vscode.boot.metadata.hints.StsValueHint;
import org.springframework.ide.vscode.boot.metadata.types.Type;
import org.springframework.ide.vscode.boot.metadata.types.TypeUtil;
import org.springframework.ide.vscode.boot.metadata.types.TypeUtil.EnumCaseMode;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.util.BadLocationException;
import org.springframework.ide.vscode.commons.util.FuzzyMap;
import org.springframework.ide.vscode.commons.util.Renderable;
import org.springframework.ide.vscode.commons.util.text.DocumentRegion;
import org.springframework.ide.vscode.commons.util.text.IDocument;
import org.springframework.ide.vscode.commons.util.text.IRegion;
import org.springframework.ide.vscode.java.properties.antlr.parser.AntlrParser;
import org.springframework.ide.vscode.java.properties.parser.ParseResults;
import org.springframework.ide.vscode.java.properties.parser.PropertiesAst.Key;
import org.springframework.ide.vscode.java.properties.parser.PropertiesAst.Node;
import org.springframework.ide.vscode.java.properties.parser.PropertiesAst.Value;
@@ -49,24 +44,15 @@ import reactor.util.function.Tuple2;
import reactor.util.function.Tuples;
class PropertiesHoverCalculator {
private FuzzyMap<PropertyInfo> index;
private TypeUtil typeUtil;
private IDocument doc;
private int offset;
private AntlrParser parser;
private PropertyFinder propertyFinder;
PropertiesHoverCalculator(FuzzyMap<PropertyInfo> index, TypeUtil typeUtil, IDocument doc, int offset) {
this.index = index;
this.typeUtil = typeUtil;
this.doc = doc;
this.offset = offset;
this.parser = new AntlrParser();
this.propertyFinder = new PropertyFinder(index, typeUtil, doc, offset);
}
Tuple2<Renderable, IRegion> calculate() {
ParseResults parseResults = parser.parse(doc.get());
Node node = parseResults.ast.findNode(offset);
Node node = propertyFinder.findNode();
if (node instanceof Value) {
return getValueHover((Value)node);
} else if (node instanceof Key) {
@@ -74,35 +60,24 @@ class PropertiesHoverCalculator {
}
return null;
}
private DocumentRegion createRegion(IDocument doc, Node value) {
// Trim trailing spaces (there is no leading white space already)
int length = value.getLength();
try {
length = doc.get(value.getOffset(), value.getLength()).length();
} catch (BadLocationException e) {
// ignore
}
return new DocumentRegion(doc, value.getOffset(), value.getOffset() + length);
}
private Tuple2<Renderable, IRegion> getPropertyHover(Key property) {
PropertyInfo best = findBestHoverMatch(property.decode());
PropertyInfo best = propertyFinder.findBestHoverMatch(property.decode());
if (best == null) {
return null;
} else {
Renderable renderable = InformationTemplates.createHover(best);
DocumentRegion region = createRegion(doc, property);
DocumentRegion region = propertyFinder.createRegion(property);
return Tuples.of(renderable, region.asRegion());
}
}
private Tuple2<Renderable, IRegion> getValueHover(Value value) {
DocumentRegion valueRegion = createRegion(doc, value).trimStart(SPACES).trimEnd(SPACES);
if (valueRegion.getStart() <= offset && offset < valueRegion.getEnd()) {
DocumentRegion valueRegion = propertyFinder.createRegion(value).trimStart(SPACES).trimEnd(SPACES);
if (valueRegion.getStart() <= propertyFinder.offset && propertyFinder.offset < valueRegion.getEnd()) {
String valueString = valueRegion.toString();
String propertyName = value.getParent().getKey().decode();
Type type = getValueType(index, typeUtil, propertyName);
Type type = getValueType(propertyFinder.index, propertyFinder.typeUtil, propertyName);
if (TypeUtil.isSequencable(type)) {
//It is useful to provide content assist for the values in the list when entering a list
type = TypeUtil.getDomainType(type);
@@ -110,14 +85,14 @@ class PropertiesHoverCalculator {
if (TypeUtil.isClass(type)) {
//Special case. We want to provide hoverinfos more liberally than what's suggested for completions (i.e. even class names
//that are not suggested by the hints because they do not meet subtyping constraints should be hoverable and linkable!
StsValueHint hint = StsValueHint.className(valueString, typeUtil);
StsValueHint hint = StsValueHint.className(valueString, propertyFinder.typeUtil);
if (hint!=null) {
return Tuples.of(createRenderable(hint), valueRegion.asRegion());
}
}
//Hack: pretend to invoke content-assist at the end of the value text. This should provide hints applicable to that value
// then show hoverinfo based on that. That way we can avoid duplication a lot of similar logic to compute hoverinfos and hyperlinks.
Collection<StsValueHint> hints = getValueHints(index, typeUtil, valueString, propertyName, EnumCaseMode.ALIASED);
Collection<StsValueHint> hints = getValueHints(propertyFinder.index, propertyFinder.typeUtil, valueString, propertyName, EnumCaseMode.ALIASED);
if (hints!=null) {
Optional<StsValueHint> hint = hints.stream().filter(h -> valueString.equals(h.getValue())).findFirst();
if (hint.isPresent()) {
@@ -127,7 +102,7 @@ class PropertiesHoverCalculator {
}
return null;
}
private Renderable createRenderable(StsValueHint hint) {
Renderable description = hint.getDescription();
try {
@@ -151,36 +126,4 @@ class PropertiesHoverCalculator {
return description;
}
/**
* Search known properties for the best 'match' to show as hover data.
*/
private PropertyInfo findBestHoverMatch(String propName) {
PropertyInfo propertyInfo = index.get(propName);
if (propertyInfo == null) {
propertyInfo = SpringPropertyIndex.findLongestValidProperty(index, propName);
}
return propertyInfo;
// //TODO: optimize, should be able to use index's treemap to find this without iterating all entries.
// PropertyInfo best = null;
// int bestCommonPrefixLen = 0; //We try to pick property with longest common prefix
// int bestExtraLen = Integer.MAX_VALUE;
// for (PropertyInfo candidate : index) {
// int commonPrefixLen = StringUtil.commonPrefixLength(propName, candidate.getId());
// int extraLen = candidate.getId().length()-commonPrefixLen;
// if (commonPrefixLen==propName.length() && extraLen==0) {
// //exact match found, can stop searching for better matches
// return candidate;
// }
// //candidate is better if...
// if (commonPrefixLen>bestCommonPrefixLen // it has a longer common prefix
// || commonPrefixLen==bestCommonPrefixLen && extraLen<bestExtraLen //or same common prefix but fewer extra chars
// ) {
// bestCommonPrefixLen = commonPrefixLen;
// bestExtraLen = extraLen;
// best = candidate;
// }
// }
// return best;
}
}

View File

@@ -0,0 +1,88 @@
/*******************************************************************************
* 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.properties.hover;
import org.springframework.ide.vscode.boot.metadata.PropertyInfo;
import org.springframework.ide.vscode.boot.metadata.SpringPropertyIndex;
import org.springframework.ide.vscode.boot.metadata.types.TypeUtil;
import org.springframework.ide.vscode.commons.util.BadLocationException;
import org.springframework.ide.vscode.commons.util.FuzzyMap;
import org.springframework.ide.vscode.commons.util.text.DocumentRegion;
import org.springframework.ide.vscode.commons.util.text.IDocument;
import org.springframework.ide.vscode.java.properties.antlr.parser.AntlrParser;
import org.springframework.ide.vscode.java.properties.parser.ParseResults;
import org.springframework.ide.vscode.java.properties.parser.PropertiesAst.Node;
class PropertyFinder {
final FuzzyMap<PropertyInfo> index;
final TypeUtil typeUtil;
final IDocument doc;
final int offset;
final AntlrParser parser;
PropertyFinder(FuzzyMap<PropertyInfo> index, TypeUtil typeUtil, IDocument doc, int offset) {
this.index = index;
this.typeUtil = typeUtil;
this.doc = doc;
this.offset = offset;
this.parser = new AntlrParser();
}
Node findNode() {
ParseResults parseResults = parser.parse(doc.get());
return parseResults.ast.findNode(offset);
}
DocumentRegion createRegion(Node value) {
// Trim trailing spaces (there is no leading white space already)
int length = value.getLength();
try {
length = doc.get(value.getOffset(), value.getLength()).length();
} catch (BadLocationException e) {
// ignore
}
return new DocumentRegion(doc, value.getOffset(), value.getOffset() + length);
}
/**
* Search known properties for the best 'match' to show as hover data.
*/
PropertyInfo findBestHoverMatch(String propName) {
PropertyInfo propertyInfo = index.get(propName);
if (propertyInfo == null) {
propertyInfo = SpringPropertyIndex.findLongestValidProperty(index, propName);
}
return propertyInfo;
// //TODO: optimize, should be able to use index's treemap to find this without iterating all entries.
// PropertyInfo best = null;
// int bestCommonPrefixLen = 0; //We try to pick property with longest common prefix
// int bestExtraLen = Integer.MAX_VALUE;
// for (PropertyInfo candidate : index) {
// int commonPrefixLen = StringUtil.commonPrefixLength(propName, candidate.getId());
// int extraLen = candidate.getId().length()-commonPrefixLen;
// if (commonPrefixLen==propName.length() && extraLen==0) {
// //exact match found, can stop searching for better matches
// return candidate;
// }
// //candidate is better if...
// if (commonPrefixLen>bestCommonPrefixLen // it has a longer common prefix
// || commonPrefixLen==bestCommonPrefixLen && extraLen<bestExtraLen //or same common prefix but fewer extra chars
// ) {
// bestCommonPrefixLen = commonPrefixLen;
// bestExtraLen = extraLen;
// best = candidate;
// }
// }
// return best;
}
}

View File

@@ -26,6 +26,7 @@ import org.springframework.ide.vscode.boot.common.InformationTemplates;
import org.springframework.ide.vscode.boot.common.PropertyCompletionFactory;
import org.springframework.ide.vscode.boot.common.RelaxedNameConfig;
import org.springframework.ide.vscode.boot.configurationmetadata.Deprecation;
import org.springframework.ide.vscode.boot.java.BootJavaLanguageServerComponents;
import org.springframework.ide.vscode.boot.java.links.SourceLinkFactory;
import org.springframework.ide.vscode.boot.java.links.SourceLinks;
import org.springframework.ide.vscode.boot.metadata.IndexNavigator;
@@ -551,7 +552,7 @@ public abstract class ApplicationYamlAssistContext extends AbstractYamlAssistCon
if (jes != null) {
for (IJavaElement je : jes) {
if (je instanceof IMember) {
SourceLinks sourceLinks = SourceLinkFactory.createSourceLinks(null);
SourceLinks sourceLinks = SourceLinkFactory.createSourceLinks((BootJavaLanguageServerComponents)null);
IJavaProject project = typeUtil.getJavaProject();
return PropertyDocUtils.documentJavaElement(sourceLinks, project, je);
}

View File

@@ -16,7 +16,6 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import org.junit.runner.RunWith;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.test.autoconfigure.OverrideAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
@@ -24,7 +23,6 @@ import org.springframework.ide.vscode.boot.app.BootLanguagServerBootApp;
import org.springframework.ide.vscode.languageserver.starter.LanguageServerAutoConf;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.annotation.DirtiesContext.ClassMode;
import org.springframework.test.context.junit4.SpringRunner;
@Retention(RUNTIME)
@Target(TYPE)

View File

@@ -6,6 +6,8 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.ide.vscode.boot.app.BootLanguageServerParams;
import org.springframework.ide.vscode.boot.editor.harness.PropertyIndexHarness;
import org.springframework.ide.vscode.boot.java.links.SourceLinkFactory;
import org.springframework.ide.vscode.boot.java.links.SourceLinks;
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
import org.springframework.ide.vscode.commons.util.text.LanguageId;
@@ -47,4 +49,9 @@ public class HoverTestConf {
@Bean JavaProjectFinder projectFinder(BootLanguageServerParams serverParams) {
return serverParams.projectFinder;
}
@Bean SourceLinks sourceLinks() {
return SourceLinkFactory.NO_SOURCE_LINKS;
}
}

View File

@@ -6,6 +6,9 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.ide.vscode.boot.app.BootLanguageServerParams;
import org.springframework.ide.vscode.boot.editor.harness.PropertyIndexHarness;
import org.springframework.ide.vscode.boot.java.links.SourceLinkFactory;
import org.springframework.ide.vscode.boot.java.links.SourceLinks;
import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache;
import org.springframework.ide.vscode.boot.java.utils.SpringLiveHoverWatchdog;
import org.springframework.ide.vscode.boot.metadata.types.TypeUtil;
import org.springframework.ide.vscode.boot.metadata.types.TypeUtilProvider;
@@ -56,4 +59,9 @@ import org.springframework.ide.vscode.project.harness.MockRunningAppProvider;
@Bean JavaProjectFinder projectFinder(BootLanguageServerParams serverParams) {
return serverParams.projectFinder;
}
@Bean SourceLinks sourceLinks(CompilationUnitCache cuCache) {
return SourceLinkFactory.NO_SOURCE_LINKS;
}
}

View File

@@ -6,6 +6,8 @@ import org.springframework.ide.vscode.boot.app.BootLanguageServerInitializer;
import org.springframework.ide.vscode.boot.app.BootLanguageServerParams;
import org.springframework.ide.vscode.boot.editor.harness.PropertyIndexHarness;
import org.springframework.ide.vscode.boot.java.BootJavaLanguageServerComponents;
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.SpringIndexer;
import org.springframework.ide.vscode.boot.metadata.DefaultSpringPropertyIndexProvider;
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
@@ -38,4 +40,8 @@ import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness;
@Bean DefaultSpringPropertyIndexProvider indexProvider(BootLanguageServerParams serverParams) {
return (DefaultSpringPropertyIndexProvider) serverParams.indexProvider;
}
@Bean SourceLinks sourceLinks() {
return SourceLinkFactory.NO_SOURCE_LINKS;
}
}

View File

@@ -13,7 +13,6 @@ package org.springframework.ide.vscode.boot.java.autowired.test;
import static org.junit.Assert.assertTrue;
import java.nio.file.Paths;
import java.time.Duration;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -26,11 +25,9 @@ import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest;
import org.springframework.ide.vscode.boot.bootiful.HoverTestConf;
import org.springframework.ide.vscode.commons.boot.app.cli.livebean.LiveBean;
import org.springframework.ide.vscode.commons.boot.app.cli.livebean.LiveBeansModel;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServerWrapper;
import org.springframework.ide.vscode.commons.maven.java.MavenJavaProject;
import org.springframework.ide.vscode.commons.util.text.LanguageId;
import org.springframework.ide.vscode.languageserver.testharness.Editor;
import org.springframework.ide.vscode.languageserver.testharness.LanguageServerHarness;
import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness;
import org.springframework.ide.vscode.project.harness.MockRunningAppProvider;
import org.springframework.ide.vscode.project.harness.ProjectsHarness;

View File

@@ -33,8 +33,12 @@ import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest;
import org.springframework.ide.vscode.boot.editor.harness.PropertyIndexHarness;
import org.springframework.ide.vscode.boot.java.BootJavaLanguageServerComponents;
import org.springframework.ide.vscode.boot.java.handlers.RunningAppProvider;
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.CompilationUnitCache;
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
import org.springframework.ide.vscode.commons.maven.java.MavenJavaProject;
import org.springframework.ide.vscode.commons.util.text.LanguageId;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
@@ -94,6 +98,10 @@ public class CompilationUnitCacheTest {
);
}
@Bean SourceLinks sourceLinks(SimpleTextDocumentService documents, CompilationUnitCache cuCache) {
return SourceLinkFactory.NO_SOURCE_LINKS;
}
}
@Test

View File

@@ -21,6 +21,7 @@ import java.util.Optional;
import org.junit.Test;
import org.springframework.ide.vscode.boot.java.links.VSCodeSourceLinks;
import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache;
import org.springframework.ide.vscode.commons.javadoc.JavaDocProviders;
import org.springframework.ide.vscode.commons.maven.MavenBuilder;
import org.springframework.ide.vscode.commons.maven.MavenCore;
@@ -55,11 +56,11 @@ public class VSCodeSourceLinksTest {
@Test
public void testJavaSourceUrl() throws Exception {
MavenJavaProject project = mavenProjectsCache.get("empty-boot-15-web-app");
Optional<String> url = new VSCodeSourceLinks(null).sourceLinkUrlForFQName(project, "com.example.EmptyBoot15WebAppApplication");
Optional<String> url = new VSCodeSourceLinks(new CompilationUnitCache(null, null, null)).sourceLinkUrlForFQName(project, "com.example.EmptyBoot15WebAppApplication");
assertTrue(url.isPresent());
Path projectPath = Paths.get(project.pom().getParent());
URI uri = URI.create(url.get());
// Use File to get rid of the fragment parts of the URL. The URL may have fragments that indicate line and column numbers
uri = new File(uri.getPath()).toURI();
@@ -72,7 +73,7 @@ public class VSCodeSourceLinksTest {
@Test
public void testJarUrl() throws Exception {
MavenJavaProject project = mavenProjectsCache.get("empty-boot-15-web-app");
Optional<String> url = new VSCodeSourceLinks(null).sourceLinkUrlForFQName(project, "org.springframework.boot.autoconfigure.SpringBootApplication");
Optional<String> url = new VSCodeSourceLinks(new CompilationUnitCache(null, null, null)).sourceLinkUrlForFQName(project, "org.springframework.boot.autoconfigure.SpringBootApplication");
assertTrue(url.isPresent());
String headerPart = url.get().substring(0, url.get().indexOf('?'));
assertEquals("jdt://contents/spring-boot-autoconfigure-1.5.8.RELEASE.jar/org.springframework.boot.autoconfigure/SpringBootApplication.class", headerPart);
@@ -83,7 +84,7 @@ public class VSCodeSourceLinksTest {
@Test
public void testJarUrlInnerType() throws Exception {
MavenJavaProject project = mavenProjectsCache.get("empty-boot-15-web-app");
Optional<String> url = new VSCodeSourceLinks(null).sourceLinkUrlForFQName(project, "org.springframework.web.client.RestTemplate$AcceptHeaderRequestCallback");
Optional<String> url = new VSCodeSourceLinks(new CompilationUnitCache(null, null, null)).sourceLinkUrlForFQName(project, "org.springframework.web.client.RestTemplate$AcceptHeaderRequestCallback");
assertTrue(url.isPresent());
String headerPart = url.get().substring(0, url.get().indexOf('?'));
assertEquals("jdt://contents/spring-web-4.3.12.RELEASE.jar/org.springframework.web.client/RestTemplate$AcceptHeaderRequestCallback.class", headerPart);

View File

@@ -29,11 +29,15 @@ import org.springframework.ide.vscode.boot.app.BootLanguageServerParams;
import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest;
import org.springframework.ide.vscode.boot.editor.harness.PropertyIndexHarness;
import org.springframework.ide.vscode.boot.java.handlers.RunningAppProvider;
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.CompilationUnitCache;
import org.springframework.ide.vscode.boot.java.value.ValueCompletionProcessor;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
import org.springframework.ide.vscode.commons.maven.java.MavenJavaProject;
import org.springframework.ide.vscode.commons.util.text.LanguageId;
import org.springframework.ide.vscode.languageserver.testharness.Editor;
@@ -90,6 +94,11 @@ public class ValueCompletionTest {
null
);
}
@Bean SourceLinks sourceLinks(SimpleTextDocumentService documents, CompilationUnitCache cuCache) {
return SourceLinkFactory.NO_SOURCE_LINKS;
}
}
@Before

View File

@@ -16,14 +16,30 @@ import static org.springframework.ide.vscode.boot.properties.reconcile.Applicati
import static org.springframework.ide.vscode.languageserver.testharness.ClasspathTestUtil.getOutputFolder;
import static org.springframework.ide.vscode.languageserver.testharness.TestAsserts.assertContains;
import java.net.URI;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.EnumConstantDeclaration;
import org.eclipse.jdt.core.dom.EnumDeclaration;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.lsp4j.CompletionItem;
import org.eclipse.lsp4j.Diagnostic;
import org.junit.Before;
import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.Range;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -31,22 +47,28 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.ide.vscode.boot.app.BootLanguageServerInitializer;
import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest;
import org.springframework.ide.vscode.boot.bootiful.PropertyEditorTestConf;
import org.springframework.ide.vscode.boot.editor.harness.AbstractPropsEditorTest;
import org.springframework.ide.vscode.boot.editor.harness.StyledStringMatcher;
import org.springframework.ide.vscode.boot.java.links.JavaDocumentUriProvider;
import org.springframework.ide.vscode.boot.java.links.SourceLinks;
import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache;
import org.springframework.ide.vscode.boot.metadata.CachingValueProvider;
import org.springframework.ide.vscode.boot.metadata.PropertiesLoader;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.java.IType;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
import org.springframework.ide.vscode.commons.maven.java.MavenJavaProject;
import org.springframework.ide.vscode.commons.util.BadLocationException;
import org.springframework.ide.vscode.commons.util.text.LanguageId;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
import org.springframework.ide.vscode.languageserver.testharness.Editor;
import org.springframework.ide.vscode.project.harness.ProjectsHarness.ProjectCustomizer;
import org.springframework.test.context.junit4.SpringRunner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.io.Files;
/**
@@ -59,6 +81,15 @@ import com.google.common.io.Files;
@Import(PropertyEditorTestConf.class)
public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest {
@Autowired
CompilationUnitCache cuCache;
@Autowired
SimpleTextDocumentService docService;
@Autowired
JavaDocumentUriProvider javaDocumentUriProvider;
@Configuration static class TestConf {
@Bean LanguageId defaultLanguageId() {
return LanguageId.BOOT_PROPERTIES;
@@ -275,7 +306,7 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest {
assertContains("\"name\": \"foo.counter\"", Files.toString(metadataFile.toFile(), Charset.forName("UTF8")));
}
@Ignore @Test public void testHyperlinkTargets() throws Exception {
@Test public void testHyperlinkTargets() throws Exception {
System.out.println(">>> testHyperlinkTargets");
IJavaProject p = createPredefinedMavenProject("tricky-getters-boot-1.3.1-app");
useProject(p);
@@ -286,20 +317,260 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest {
"flyway.init-sqls=a,b,c\n"
);
editor.assertLinkTargets("server",
"org.springframework.boot.autoconfigure.web.ServerProperties.setPort(Integer)"
assertLinkTargets(editor, "server", p,
method("org.springframework.boot.autoconfigure.web.ServerProperties", "setPort", "java.lang.Integer"));
assertLinkTargets(editor, "data", p,
method("org.springframework.boot.autoconfigure.jdbc.DataSourceConfigMetadata", "hikariDataSource"),
method("org.springframework.boot.autoconfigure.jdbc.DataSourceConfigMetadata", "tomcatDataSource"),
method("org.springframework.boot.autoconfigure.jdbc.DataSourceConfigMetadata", "dbcpDataSource")
);
editor.assertLinkTargets("data",
"org.springframework.boot.autoconfigure.jdbc.DataSourceConfigMetadata.hikariDataSource()",
"org.springframework.boot.autoconfigure.jdbc.DataSourceConfigMetadata.tomcatDataSource()",
"org.springframework.boot.autoconfigure.jdbc.DataSourceConfigMetadata.dbcpDataSource()"
);
editor.assertLinkTargets("flyway",
"org.springframework.boot.autoconfigure.flyway.FlywayProperties.setInitSqls(List<String>)");
assertLinkTargets(editor, "flyway", p, method("org.springframework.boot.autoconfigure.flyway.FlywayProperties", "setInitSqls", "java.util.List"));
System.out.println("<<< testHyperlinkTargets");
}
@Ignore @Test public void testHyperlinkTargetsLoggingLevel() throws Exception {
void assertLinkTargets(Editor editor, String hoverOver, IJavaProject project, JavaMethod... methods) throws Exception {
Set<Location> expectedLocations = Arrays.stream(methods).map(method -> {
try {
return getLocation(project, method);
} catch (Exception e) {
throw new IllegalStateException(e);
}
}).collect(Collectors.toSet());
editor.assertLinkTargets(hoverOver, expectedLocations);
}
void assertLinkTargets(Editor editor, String hoverOver, IJavaProject project, String typeFqName) throws Exception {
Location expectedLocation = getLocation(project, typeFqName);
System.out.println("Expected Location: " + expectedLocation);
editor.assertLinkTargets(hoverOver, ImmutableSet.of(expectedLocation));
}
void assertLinkTargets(Editor editor, String hoverOver, IJavaProject project, JavaField field) throws Exception {
Location expectedLocation = getLocation(project, field);
System.out.println("Expected Location: " + expectedLocation);
editor.assertLinkTargets(hoverOver, ImmutableSet.of(expectedLocation));
}
static private JavaMethod method(String fqClassName, String methodName, String... params) {
return new JavaMethod(fqClassName, methodName, params);
}
static private JavaField field(String fqClassName, String name) {
return new JavaField(fqClassName, name);
}
static class JavaMethod {
public final String fqName;
public final String methodName;
public final String[] params;
public JavaMethod(String fqClassName, String methodName, String... params) {
this.fqName = fqClassName;
this.methodName = methodName;
this.params = params;
}
@Override
public String toString() {
return "JavaMethod [fqName=" + fqName + ", methodName=" + methodName + ", params=" + Arrays.toString(params)
+ "]";
}
}
static class JavaField {
public final String fqName;
public final String fieldName;
public JavaField(String fqName, String fieldName) {
super();
this.fqName = fqName;
this.fieldName = fieldName;
}
@Override
public String toString() {
return "JavaField [fqName=" + fqName + ", fieldName=" + fieldName + "]";
}
}
private Location getLocation(IJavaProject project, String fqName) throws Exception {
Location loc = new Location();
Optional<URL> sourceUrl = SourceLinks.source(project, fqName);
if (sourceUrl.isPresent()) {
URI docUri = javaDocumentUriProvider.docUri(project, fqName);
loc.setUri(docUri.toString());
String typeName = fqName.substring(fqName.lastIndexOf('.') + 1);
URI sourceUri = sourceUrl.get().toURI();
Range r = cuCache.withCompilationUnit(project, sourceUri, (cu) -> {
try {
TextDocument doc = new TextDocument(sourceUrl.get().toString(), LanguageId.JAVA);
doc.setText(cuCache.fetchContent(sourceUri));
AtomicReference<Range> range = new AtomicReference<>(null);
cu.accept(new ASTVisitor() {
private boolean proceessTypeNode(TextDocument doc, String typeName,
AtomicReference<Range> range, AbstractTypeDeclaration node) {
SimpleName nameNode = node.getName();
if (nameNode.getIdentifier().equals(typeName)) {
try {
range.set(doc.toRange(nameNode.getStartPosition(), nameNode.getLength()));
return false;
} catch (BadLocationException e) {
throw new IllegalStateException(e);
}
}
return true;
}
@Override
public boolean visit(TypeDeclaration node) {
return proceessTypeNode(doc, typeName, range, node);
}
@Override
public boolean visit(EnumDeclaration node) {
return proceessTypeNode(doc, typeName, range, node);
}
});
return range.get();
} catch (Exception e) {
throw new IllegalStateException(e);
}
});
if (r == null) {
throw new IllegalStateException("Couldn't find " + fqName);
}
loc.setRange(r);
}
return loc;
}
private Location getLocation(IJavaProject project, JavaMethod method) throws Exception {
Location loc = new Location();
Optional<URL> sourceUrl = SourceLinks.source(project, method.fqName);
if (sourceUrl.isPresent()) {
URI docUri = javaDocumentUriProvider.docUri(project, method.fqName);
loc.setUri(docUri.toString());
URI sourceUri = sourceUrl.get().toURI();
Range r = cuCache.withCompilationUnit(project, sourceUri, (cu) -> {
try {
AtomicReference<Range> range = new AtomicReference<>(null);
TextDocument doc = new TextDocument(sourceUrl.get().toString(), LanguageId.JAVA);
doc.setText(cuCache.fetchContent(sourceUri));
cu.accept(new ASTVisitor() {
@Override
public boolean visit(MethodDeclaration node) {
SimpleName nameNode = node.getName();
if (nameNode.getIdentifier().equals(method.methodName)) {
if (node.parameters().size() != method.params.length) {
return false;
}
int i = 0;
for (Object _p : node.parameters()) {
if (_p instanceof SingleVariableDeclaration) {
SingleVariableDeclaration p = (SingleVariableDeclaration) _p;
String fqName = p.getType().resolveBinding().getErasure().getQualifiedName();
if (!fqName.equals(method.params[i++])) {
return false;
}
} else {
return false;
}
}
try {
range.set(doc.toRange(nameNode.getStartPosition(), nameNode.getLength()));
} catch (BadLocationException e) {
throw new IllegalStateException(e);
}
}
return false;
}
});
return range.get();
} catch (Exception e) {
throw new IllegalStateException(e);
}
});
if (r == null) {
throw new IllegalStateException("Couldn't find " + method);
}
loc.setRange(r);
}
return loc;
}
private Location getLocation(IJavaProject project, JavaField field) throws Exception {
Location loc = new Location();
Optional<URL> sourceUrl = SourceLinks.source(project, field.fqName);
if (sourceUrl.isPresent()) {
URI sourceUri = sourceUrl.get().toURI();
URI docUri = javaDocumentUriProvider.docUri(project, field.fqName);
loc.setUri(docUri.toString());
Range r = cuCache.withCompilationUnit(project, sourceUri, (cu) -> {
try {
AtomicReference<Range> range = new AtomicReference<>(null);
TextDocument doc = new TextDocument(sourceUrl.get().toString(), LanguageId.JAVA);
doc.setText(cuCache.fetchContent(sourceUri));
cu.accept(new ASTVisitor() {
boolean foundType = false;
@Override
public boolean visit(EnumConstantDeclaration node) {
if (foundType) {
SimpleName nameNode = node.getName();
if (nameNode.getIdentifier().equals(field.fieldName)) {
try {
range.set(doc.toRange(nameNode.getStartPosition(), nameNode.getLength()));
} catch (BadLocationException e) {
throw new IllegalStateException(e);
}
}
}
return true;
}
@Override
public boolean visit(EnumDeclaration node) {
if (node.getName().getIdentifier()
.equals(field.fqName.substring(field.fqName.lastIndexOf('.') + 1))) {
foundType = true;
return true;
}
return false;
}
});
return range.get();
} catch (Exception e) {
throw new IllegalStateException(e);
}
});
if (r == null) {
throw new IllegalStateException("Couldn't find " + field);
}
loc.setRange(r);
}
return loc;
}
@Test public void testHyperlinkTargetsLoggingLevel() throws Exception {
System.out.println(">>> testHyperlinkTargetsLoggingLevel");
IJavaProject p = createPredefinedMavenProject("tricky-getters-boot-1.3.1-app");
@@ -308,9 +579,8 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest {
Editor editor = newEditor(
"logging.level.com.acme=INFO\n"
);
editor.assertLinkTargets("level",
"org.springframework.boot.logging.LoggingApplicationListener"
);
assertLinkTargets(editor, "level", p, "org.springframework.boot.logging.LoggingApplicationListener");
System.out.println("<<< testHyperlinkTargetsLoggingLevel");
}
@@ -1422,16 +1692,17 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest {
);
}
@Ignore @Test public void testClassReferenceInValueLink() throws Exception {
@Test public void testClassReferenceInValueLink() throws Exception {
Editor editor;
useProject(createPredefinedMavenProject("empty-boot-1.3.0-with-mongo"));
MavenJavaProject project = createPredefinedMavenProject("empty-boot-1.3.0-with-mongo");
useProject(project);
editor = newEditor(
"#stuff\n" +
"spring.data.mongodb.field-naming-strategy=org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy\n" +
"#more stuff"
);
editor.assertLinkTargets("org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy", "org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy");
assertLinkTargets(editor, "org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy", project, "org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy");
//Linking should also work for types that aren't valid based on the constraints
editor = newEditor(
@@ -1439,7 +1710,7 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest {
"spring.data.mongodb.field-naming-strategy=java.lang.String\n" +
"#more stuff"
);
editor.assertLinkTargets("java.lang.String", "java.lang.String");
assertLinkTargets(editor, "java.lang.String", project, "java.lang.String");
}
@Test public void testCommaListReconcile() throws Exception {
@@ -1601,9 +1872,9 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest {
editor.assertHoverContains("red", "Hot and delicious");
}
@Ignore @Test public void testEnumInValueLink() throws Exception {
useProject(createPredefinedMavenProject("enums-boot-1.3.2-app"));
@Test public void testEnumInValueLink() throws Exception {
MavenJavaProject project = createPredefinedMavenProject("enums-boot-1.3.2-app");
useProject(project);
data("my.background", "demo.Color", null, "Color to use as default background.");
Editor editor;
@@ -1611,12 +1882,25 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest {
editor = newEditor(
"my.background: RED"
);
editor.assertLinkTargets("RED", "demo.Color.RED");
assertLinkTargets(editor, "RED", project, field("demo.Color", "RED"));
editor = newEditor(
"my.background=red"
);
editor.assertLinkTargets("red", "demo.Color.RED");
assertLinkTargets(editor, "red", project, field("demo.Color", "RED"));
}
@Test public void testEnumInPojoField() throws Exception {
MavenJavaProject project = createPredefinedMavenProject("enum-def-nav");
useProject(project);
Editor editor;
editor = newEditor(
"my.screen.background=green"
);
assertLinkTargets(editor, "background", project, method("com.example.demo.MyProperties$Screen", "getScreen"));
assertLinkTargets(editor, "green", project, field("com.example.demo.Color", "GREEN"));
}
@Test public void testNoHoverForUnrecognizedProperty() throws Exception {

View File

@@ -0,0 +1 @@
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.5.4/apache-maven-3.5.4-bin.zip

View File

@@ -0,0 +1,286 @@
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Maven2 Start Up Batch script
#
# Required ENV vars:
# ------------------
# JAVA_HOME - location of a JDK home dir
#
# Optional ENV vars
# -----------------
# M2_HOME - location of maven2's installed home dir
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
# e.g. to debug Maven itself, use
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
# ----------------------------------------------------------------------------
if [ -z "$MAVEN_SKIP_RC" ] ; then
if [ -f /etc/mavenrc ] ; then
. /etc/mavenrc
fi
if [ -f "$HOME/.mavenrc" ] ; then
. "$HOME/.mavenrc"
fi
fi
# OS specific support. $var _must_ be set to either true or false.
cygwin=false;
darwin=false;
mingw=false
case "`uname`" in
CYGWIN*) cygwin=true ;;
MINGW*) mingw=true;;
Darwin*) darwin=true
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
if [ -z "$JAVA_HOME" ]; then
if [ -x "/usr/libexec/java_home" ]; then
export JAVA_HOME="`/usr/libexec/java_home`"
else
export JAVA_HOME="/Library/Java/Home"
fi
fi
;;
esac
if [ -z "$JAVA_HOME" ] ; then
if [ -r /etc/gentoo-release ] ; then
JAVA_HOME=`java-config --jre-home`
fi
fi
if [ -z "$M2_HOME" ] ; then
## resolve links - $0 may be a link to maven's home
PRG="$0"
# need this for relative symlinks
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG="`dirname "$PRG"`/$link"
fi
done
saveddir=`pwd`
M2_HOME=`dirname "$PRG"`/..
# make it fully qualified
M2_HOME=`cd "$M2_HOME" && pwd`
cd "$saveddir"
# echo Using m2 at $M2_HOME
fi
# For Cygwin, ensure paths are in UNIX format before anything is touched
if $cygwin ; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --unix "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
fi
# For Mingw, ensure paths are in UNIX format before anything is touched
if $mingw ; then
[ -n "$M2_HOME" ] &&
M2_HOME="`(cd "$M2_HOME"; pwd)`"
[ -n "$JAVA_HOME" ] &&
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
# TODO classpath?
fi
if [ -z "$JAVA_HOME" ]; then
javaExecutable="`which javac`"
if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
# readlink(1) is not available as standard on Solaris 10.
readLink=`which readlink`
if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
if $darwin ; then
javaHome="`dirname \"$javaExecutable\"`"
javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
else
javaExecutable="`readlink -f \"$javaExecutable\"`"
fi
javaHome="`dirname \"$javaExecutable\"`"
javaHome=`expr "$javaHome" : '\(.*\)/bin'`
JAVA_HOME="$javaHome"
export JAVA_HOME
fi
fi
fi
if [ -z "$JAVACMD" ] ; then
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
else
JAVACMD="`which java`"
fi
fi
if [ ! -x "$JAVACMD" ] ; then
echo "Error: JAVA_HOME is not defined correctly." >&2
echo " We cannot execute $JAVACMD" >&2
exit 1
fi
if [ -z "$JAVA_HOME" ] ; then
echo "Warning: JAVA_HOME environment variable is not set."
fi
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
# traverses directory structure from process work directory to filesystem root
# first directory with .mvn subdirectory is considered project base directory
find_maven_basedir() {
if [ -z "$1" ]
then
echo "Path not specified to find_maven_basedir"
return 1
fi
basedir="$1"
wdir="$1"
while [ "$wdir" != '/' ] ; do
if [ -d "$wdir"/.mvn ] ; then
basedir=$wdir
break
fi
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
if [ -d "${wdir}" ]; then
wdir=`cd "$wdir/.."; pwd`
fi
# end of workaround
done
echo "${basedir}"
}
# concatenates all lines of a file
concat_lines() {
if [ -f "$1" ]; then
echo "$(tr -s '\n' ' ' < "$1")"
fi
}
BASE_DIR=`find_maven_basedir "$(pwd)"`
if [ -z "$BASE_DIR" ]; then
exit 1;
fi
##########################################################################################
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
# This allows using the maven wrapper in projects that prohibit checking in binary data.
##########################################################################################
if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found .mvn/wrapper/maven-wrapper.jar"
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
fi
jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
while IFS="=" read key value; do
case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
esac
done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
if [ "$MVNW_VERBOSE" = true ]; then
echo "Downloading from: $jarUrl"
fi
wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
if command -v wget > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found wget ... using wget"
fi
wget "$jarUrl" -O "$wrapperJarPath"
elif command -v curl > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found curl ... using curl"
fi
curl -o "$wrapperJarPath" "$jarUrl"
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Falling back to using Java to download"
fi
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
if [ -e "$javaClass" ]; then
if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Compiling MavenWrapperDownloader.java ..."
fi
# Compiling the Java class
("$JAVA_HOME/bin/javac" "$javaClass")
fi
if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
# Running the downloader
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Running MavenWrapperDownloader.java ..."
fi
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
fi
fi
fi
fi
##########################################################################################
# End of extension
##########################################################################################
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
if [ "$MVNW_VERBOSE" = true ]; then
echo $MAVEN_PROJECTBASEDIR
fi
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
# For Cygwin, switch paths to Windows format before running java
if $cygwin; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --path --windows "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
fi
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
exec "$JAVACMD" \
$MAVEN_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
"-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"

View File

@@ -0,0 +1,161 @@
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM http://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Maven2 Start Up Batch script
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@REM
@REM Optional ENV vars
@REM M2_HOME - location of maven2's installed home dir
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
@REM e.g. to debug Maven itself, use
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
@REM ----------------------------------------------------------------------------
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
@echo off
@REM set title of command window
title %0
@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
@REM set %HOME% to equivalent of $HOME
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
@REM Execute a user defined script before this one
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
:skipRcPre
@setlocal
set ERROR_CODE=0
@REM To isolate internal variables from possible post scripts, we use another setlocal
@setlocal
@REM ==== START VALIDATION ====
if not "%JAVA_HOME%" == "" goto OkJHome
echo.
echo Error: JAVA_HOME not found in your environment. >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
:OkJHome
if exist "%JAVA_HOME%\bin\java.exe" goto init
echo.
echo Error: JAVA_HOME is set to an invalid directory. >&2
echo JAVA_HOME = "%JAVA_HOME%" >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
@REM ==== END VALIDATION ====
:init
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
@REM Fallback to current working directory if not found.
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
set EXEC_DIR=%CD%
set WDIR=%EXEC_DIR%
:findBaseDir
IF EXIST "%WDIR%"\.mvn goto baseDirFound
cd ..
IF "%WDIR%"=="%CD%" goto baseDirNotFound
set WDIR=%CD%
goto findBaseDir
:baseDirFound
set MAVEN_PROJECTBASEDIR=%WDIR%
cd "%EXEC_DIR%"
goto endDetectBaseDir
:baseDirNotFound
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
cd "%EXEC_DIR%"
:endDetectBaseDir
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
@setlocal EnableExtensions EnableDelayedExpansion
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
:endReadAdditionalConfig
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO (
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
)
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
if exist %WRAPPER_JAR% (
echo Found %WRAPPER_JAR%
) else (
echo Couldn't find %WRAPPER_JAR%, downloading it ...
echo Downloading from: %DOWNLOAD_URL%
powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"
echo Finished downloading %WRAPPER_JAR%
)
@REM End of extension
%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error
goto end
:error
set ERROR_CODE=1
:end
@endlocal & set ERROR_CODE=%ERROR_CODE%
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
@REM check for post script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
:skipRcPost
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
if "%MAVEN_BATCH_PAUSE%" == "on" pause
if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
exit /B %ERROR_CODE%

View File

@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>enum-def-nav</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>enum-def-nav</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,7 @@
package com.example.demo;
public enum Color {
RED,
GREEN,
BLUE
}

View File

@@ -0,0 +1,12 @@
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class EnumDefNavApplication {
public static void main(String[] args) {
SpringApplication.run(EnumDefNavApplication.class, args);
}
}

View File

@@ -0,0 +1,65 @@
package com.example.demo;
import java.util.List;
import java.util.Map;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("my")
public class MyProperties {
public static class Screen {
private Color foreground;
private Color background;
public Color getForeground() {
return foreground;
}
public void setForeground(Color foreground) {
this.foreground = foreground;
}
public Color getBackground() {
return background;
}
public void setBackground(Color background) {
this.background = background;
}
}
private Screen screen;
private Screen[] screenArray;
private List<Screen> screenList;
private Map<Color, Screen> screenMap;
public Screen getScreen() {
return screen;
}
public void setScreen(Screen screen) {
this.screen = screen;
}
public Screen[] getScreenArray() {
return screenArray;
}
public void setScreenArray(Screen[] screenArray) {
this.screenArray = screenArray;
}
public List<Screen> getScreenList() {
return screenList;
}
public void setScreenList(List<Screen> screenList) {
this.screenList = screenList;
}
public Map<Color, Screen> getScreenMap() {
return screenMap;
}
public void setScreenMap(Map<Color, Screen> screenMap) {
this.screenMap = screenMap;
}
}

View File

@@ -0,0 +1,16 @@
package com.example.demo;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class EnumDefNavApplicationTests {
@Test
public void contextLoads() {
}
}