Remove commons-boot
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,44 @@
|
||||
/*******************************************************************************
|
||||
* 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;
|
||||
|
||||
import org.springframework.ide.vscode.boot.java.BootJavaLanguageServerComponents;
|
||||
import org.springframework.ide.vscode.boot.properties.BootPropertiesLanguageServerComponents;
|
||||
import org.springframework.ide.vscode.commons.languageserver.composable.ComposableLanguageServer;
|
||||
import org.springframework.ide.vscode.commons.languageserver.composable.CompositeLanguageServerComponents;
|
||||
import org.springframework.ide.vscode.commons.languageserver.composable.LanguageServerComponents;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.LSFactory;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
|
||||
|
||||
public class BootLanguageServer<C extends LanguageServerComponents> extends ComposableLanguageServer<C> {
|
||||
|
||||
private BootLanguageServer(String extensionId, LSFactory<C> _components) {
|
||||
super(extensionId, _components);
|
||||
}
|
||||
|
||||
public static ComposableLanguageServer<CompositeLanguageServerComponents> create(LSFactory<BootLanguageServerParams> params) {
|
||||
return new ComposableLanguageServer<>("vscode-boot", s -> {
|
||||
CompositeLanguageServerComponents.Builder components = new CompositeLanguageServerComponents.Builder();
|
||||
components.add(new BootPropertiesLanguageServerComponents(s, params));
|
||||
components.add(new BootJavaLanguageServerComponents(s, params));
|
||||
return components.build(s);
|
||||
});
|
||||
}
|
||||
|
||||
public static ComposableLanguageServer<BootPropertiesLanguageServerComponents> createProperties(LSFactory<BootLanguageServerParams> params) {
|
||||
return new ComposableLanguageServer<>("vscode-boot-properties", s -> new BootPropertiesLanguageServerComponents(s, params));
|
||||
}
|
||||
|
||||
public static ComposableLanguageServer<BootJavaLanguageServerComponents> createJava(LSFactory<BootLanguageServerParams> params) {
|
||||
return new ComposableLanguageServer<>("vscode-boot-java", s -> new BootJavaLanguageServerComponents(s, params));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 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;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.eclipse.lsp4j.TextDocumentIdentifier;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.RunningAppProvider;
|
||||
import org.springframework.ide.vscode.boot.java.utils.SpringLiveHoverWatchdog;
|
||||
import org.springframework.ide.vscode.boot.metadata.DefaultSpringPropertyIndexProvider;
|
||||
import org.springframework.ide.vscode.boot.metadata.SpringPropertyIndexProvider;
|
||||
import org.springframework.ide.vscode.boot.metadata.types.TypeUtil;
|
||||
import org.springframework.ide.vscode.boot.metadata.types.TypeUtilProvider;
|
||||
import org.springframework.ide.vscode.commons.gradle.GradleCore;
|
||||
import org.springframework.ide.vscode.commons.gradle.GradleProjectCache;
|
||||
import org.springframework.ide.vscode.commons.gradle.GradleProjectFinder;
|
||||
import org.springframework.ide.vscode.commons.java.BootProjectUtil;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.CompositeJavaProjectFinder;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.CompositeProjectOvserver;
|
||||
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.LSFactory;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
|
||||
import org.springframework.ide.vscode.commons.maven.MavenCore;
|
||||
import org.springframework.ide.vscode.commons.maven.java.MavenProjectCache;
|
||||
import org.springframework.ide.vscode.commons.maven.java.MavenProjectFinder;
|
||||
import org.springframework.ide.vscode.commons.util.Assert;
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
|
||||
/**
|
||||
* Parameters for creating Boot Properties language server
|
||||
*
|
||||
* @author Alex Boyko
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class BootLanguageServerParams {
|
||||
|
||||
//Shared
|
||||
public final JavaProjectFinder projectFinder;
|
||||
public final ProjectObserver projectObserver;
|
||||
public final SpringPropertyIndexProvider indexProvider;
|
||||
|
||||
//Boot Properies
|
||||
public final TypeUtilProvider typeUtilProvider;
|
||||
|
||||
//Boot Java
|
||||
public final RunningAppProvider runningAppProvider;
|
||||
public final Duration watchDogInterval;
|
||||
|
||||
public BootLanguageServerParams(
|
||||
JavaProjectFinder projectFinder,
|
||||
ProjectObserver projectObserver,
|
||||
SpringPropertyIndexProvider indexProvider,
|
||||
TypeUtilProvider typeUtilProvider,
|
||||
RunningAppProvider runningAppProvider,
|
||||
Duration watchDogInterval
|
||||
) {
|
||||
super();
|
||||
Assert.isNotNull(projectObserver); // null is bad should be ProjectObserver.NULL
|
||||
this.projectFinder = projectFinder;
|
||||
this.projectObserver = projectObserver;
|
||||
this.indexProvider = indexProvider;
|
||||
this.typeUtilProvider = typeUtilProvider;
|
||||
this.runningAppProvider = runningAppProvider;
|
||||
this.watchDogInterval = watchDogInterval;
|
||||
}
|
||||
|
||||
public static LSFactory<BootLanguageServerParams> createDefault() {
|
||||
return (SimpleLanguageServer server) -> {
|
||||
// Initialize project finders, project caches and project observers
|
||||
CompositeJavaProjectFinder javaProjectFinder = new CompositeJavaProjectFinder();
|
||||
MavenProjectCache mavenProjectCache = new MavenProjectCache(server, MavenCore.getDefault(), true, Paths.get(IJavaProject.PROJECT_CACHE_FOLDER));
|
||||
javaProjectFinder.addJavaProjectFinder(new MavenProjectFinder(mavenProjectCache));
|
||||
|
||||
GradleProjectCache gradleProjectCache = new GradleProjectCache(server, GradleCore.getDefault(), true, Paths.get(IJavaProject.PROJECT_CACHE_FOLDER));
|
||||
javaProjectFinder.addJavaProjectFinder(new GradleProjectFinder(gradleProjectCache));
|
||||
|
||||
CompositeProjectOvserver projectObserver = new CompositeProjectOvserver(Arrays.asList(mavenProjectCache, gradleProjectCache));
|
||||
|
||||
DefaultSpringPropertyIndexProvider indexProvider = new DefaultSpringPropertyIndexProvider(javaProjectFinder, projectObserver);
|
||||
indexProvider.setProgressService(server.getProgressService());
|
||||
|
||||
return new BootLanguageServerParams(
|
||||
javaProjectFinder.filter(BootProjectUtil::isBootProject),
|
||||
projectObserver,
|
||||
indexProvider,
|
||||
(IDocument doc) -> new TypeUtil(javaProjectFinder.find(new TextDocumentIdentifier(doc.getUri()))),
|
||||
RunningAppProvider.DEFAULT,
|
||||
SpringLiveHoverWatchdog.DEFAULT_INTERVAL
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
public static LSFactory<BootLanguageServerParams> createTestDefault(SpringPropertyIndexProvider indexProvider, TypeUtilProvider typeUtilProvider) {
|
||||
return (SimpleLanguageServer server) -> {
|
||||
// Initialize project finders, project caches and project observers
|
||||
CompositeJavaProjectFinder javaProjectFinder = new CompositeJavaProjectFinder();
|
||||
MavenProjectCache mavenProjectCache = new MavenProjectCache(server, MavenCore.getDefault(), false, null);
|
||||
mavenProjectCache.setAlwaysFireEventOnFileChanged(true);
|
||||
javaProjectFinder.addJavaProjectFinder(new MavenProjectFinder(mavenProjectCache));
|
||||
|
||||
GradleProjectCache gradleProjectCache = new GradleProjectCache(server, GradleCore.getDefault(), false, null);
|
||||
gradleProjectCache.setAlwaysFireEventOnFileChanged(true);
|
||||
javaProjectFinder.addJavaProjectFinder(new GradleProjectFinder(gradleProjectCache));
|
||||
|
||||
CompositeProjectOvserver projectObserver = new CompositeProjectOvserver(Arrays.asList(mavenProjectCache, gradleProjectCache));
|
||||
|
||||
return new BootLanguageServerParams(
|
||||
javaProjectFinder.filter(BootProjectUtil::isBootProject),
|
||||
projectObserver,
|
||||
indexProvider,
|
||||
typeUtilProvider,
|
||||
RunningAppProvider.NULL,
|
||||
SpringLiveHoverWatchdog.DEFAULT_INTERVAL
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
public static LSFactory<BootLanguageServerParams> createTestDefault() {
|
||||
return (SimpleLanguageServer server) -> {
|
||||
// Initialize project finders, project caches and project observers
|
||||
CompositeJavaProjectFinder javaProjectFinder = new CompositeJavaProjectFinder();
|
||||
MavenProjectCache mavenProjectCache = new MavenProjectCache(server, MavenCore.getDefault(), false, null);
|
||||
mavenProjectCache.setAlwaysFireEventOnFileChanged(true);
|
||||
javaProjectFinder.addJavaProjectFinder(new MavenProjectFinder(mavenProjectCache));
|
||||
|
||||
GradleProjectCache gradleProjectCache = new GradleProjectCache(server, GradleCore.getDefault(), false, null);
|
||||
gradleProjectCache.setAlwaysFireEventOnFileChanged(true);
|
||||
javaProjectFinder.addJavaProjectFinder(new GradleProjectFinder(gradleProjectCache));
|
||||
|
||||
CompositeProjectOvserver projectObserver = new CompositeProjectOvserver(Arrays.asList(mavenProjectCache, gradleProjectCache));
|
||||
|
||||
DefaultSpringPropertyIndexProvider indexProvider = new DefaultSpringPropertyIndexProvider(javaProjectFinder, projectObserver);
|
||||
indexProvider.setProgressService(server.getProgressService());
|
||||
|
||||
return new BootLanguageServerParams(
|
||||
javaProjectFinder.filter(BootProjectUtil::isBootProject),
|
||||
projectObserver,
|
||||
indexProvider,
|
||||
(IDocument doc) -> new TypeUtil(javaProjectFinder.find(new TextDocumentIdentifier(doc.getUri()))),
|
||||
RunningAppProvider.NULL,
|
||||
SpringLiveHoverWatchdog.DEFAULT_INTERVAL
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015, 2016 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.common;
|
||||
|
||||
import org.eclipse.lsp4j.CompletionItemKind;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.ScoreableProposal;
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.YType;
|
||||
|
||||
public abstract class AbstractPropertyProposal extends ScoreableProposal {
|
||||
|
||||
@Override
|
||||
public String getDetail() {
|
||||
return niceTypeName(getType());
|
||||
}
|
||||
|
||||
protected final IDocument fDoc;
|
||||
private final DocumentEdits proposalApplier;
|
||||
private boolean isDeprecated = false;
|
||||
|
||||
public AbstractPropertyProposal(IDocument doc, DocumentEdits applier) {
|
||||
this.proposalApplier = applier;
|
||||
this.fDoc = doc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLabel() {
|
||||
return getBaseDisplayString();
|
||||
}
|
||||
|
||||
// public IRegion getSelection(IDocument document) {
|
||||
// try {
|
||||
// return proposalApplier.getSelection(document);
|
||||
// } catch (Exception e) {
|
||||
// Log.log(e);
|
||||
// return null;
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
// public String getDisplayString() {
|
||||
// StyledString styledText = getStyledDisplayString();
|
||||
// return styledText.getString();
|
||||
// }
|
||||
|
||||
// public Image getImage() {
|
||||
// return null;
|
||||
// }
|
||||
|
||||
// public IContextInformation getContextInformation() {
|
||||
// return null;
|
||||
// }
|
||||
|
||||
// @Override
|
||||
// public StyledString getStyledDisplayString() {
|
||||
// StyledString result = new StyledString();
|
||||
// result = result.append(super.getStyledDisplayString());
|
||||
// YType type = getType();
|
||||
// if (type!=null) {
|
||||
// String typeStr = niceTypeName(type);
|
||||
// result.append(" : "+typeStr, StyledString.DECORATIONS_STYLER);
|
||||
// }
|
||||
// return result;
|
||||
// }
|
||||
|
||||
protected boolean isDeprecated() {
|
||||
return isDeprecated;
|
||||
}
|
||||
public void deprecate() {
|
||||
if (!isDeprecated()) {
|
||||
deemphasize(DEEMP_DEPRECATION);
|
||||
isDeprecated = true;
|
||||
}
|
||||
}
|
||||
protected abstract YType getType();
|
||||
protected abstract String getHighlightPattern();
|
||||
protected abstract String getBaseDisplayString();
|
||||
protected abstract String niceTypeName(YType type);
|
||||
|
||||
@Override
|
||||
public CompletionItemKind getKind() {
|
||||
return CompletionItemKind.Field;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getBaseDisplayString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final DocumentEdits getTextEdit() {
|
||||
return this.proposalApplier;
|
||||
}
|
||||
|
||||
// @Override
|
||||
// public void apply(IDocument document) {
|
||||
// try {
|
||||
// proposalApplier.apply(document);
|
||||
// } catch (Exception e) {
|
||||
// EditorSupportActivator.log(e);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016-2017 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.common;
|
||||
|
||||
import static org.springframework.ide.vscode.commons.util.StringUtil.camelCaseToHyphens;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.ide.vscode.boot.metadata.PropertyInfo;
|
||||
import org.springframework.ide.vscode.boot.metadata.hints.HintProvider;
|
||||
import org.springframework.ide.vscode.boot.metadata.hints.HintProviders;
|
||||
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.TypeParser;
|
||||
import org.springframework.ide.vscode.boot.metadata.types.TypeUtil;
|
||||
import org.springframework.ide.vscode.boot.metadata.types.TypeUtil.EnumCaseMode;
|
||||
import org.springframework.ide.vscode.boot.properties.reconcile.PropertyNavigator;
|
||||
import org.springframework.ide.vscode.commons.util.CollectionUtil;
|
||||
import org.springframework.ide.vscode.commons.util.FuzzyMap;
|
||||
import org.springframework.ide.vscode.commons.util.Log;
|
||||
import org.springframework.ide.vscode.commons.util.text.DocumentRegion;
|
||||
import org.springframework.ide.vscode.commons.util.text.LanguageId;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
public class CommonLanguageTools {
|
||||
|
||||
public static final Pattern SPACES = Pattern.compile(
|
||||
"(\\s|\\\\\\s)*"
|
||||
);
|
||||
|
||||
public static boolean isValuePrefixChar(char c) {
|
||||
return !Character.isWhitespace(c) && c!=',';
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the value type for a give propertyName.
|
||||
*/
|
||||
public static Type getValueType(FuzzyMap<PropertyInfo> index, TypeUtil typeUtil, String propertyName) {
|
||||
try {
|
||||
PropertyInfo prop = index.get(propertyName);
|
||||
if (prop!=null) {
|
||||
return TypeParser.parse(prop.getType());
|
||||
} else {
|
||||
prop = CommonLanguageTools.findLongestValidProperty(index, propertyName);
|
||||
if (prop!=null) {
|
||||
TextDocument doc = new TextDocument(null, LanguageId.PLAINTEXT);
|
||||
doc.setText(propertyName);
|
||||
PropertyNavigator navigator = new PropertyNavigator(doc, null, typeUtil, new DocumentRegion(doc, 0, doc.getLength()));
|
||||
return navigator.navigate(prop.getId().length(), TypeParser.parse(prop.getType()));
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.log(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Collection<StsValueHint> getValueHints(FuzzyMap<PropertyInfo> index, TypeUtil typeUtil, String query, String propertyName, EnumCaseMode caseMode) {
|
||||
Type type = getValueType(index, 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);
|
||||
}
|
||||
List<StsValueHint> allHints = new ArrayList<>();
|
||||
{
|
||||
Collection<StsValueHint> hints = typeUtil.getHintValues(type, query, caseMode);
|
||||
if (CollectionUtil.hasElements(hints)) {
|
||||
allHints.addAll(hints);
|
||||
}
|
||||
}
|
||||
{
|
||||
PropertyInfo prop = index.findLongestCommonPrefixEntry(propertyName);
|
||||
if (prop!=null) {
|
||||
HintProvider hintProvider = prop.getHints(typeUtil, false);
|
||||
if (!HintProviders.isNull(hintProvider)) {
|
||||
allHints.addAll(hintProvider.getValueHints(query));
|
||||
}
|
||||
}
|
||||
}
|
||||
return allHints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the longest known property that is a prefix of the given name. Here prefix does not mean
|
||||
* 'string prefix' but a prefix in the sense of treating '.' as a kind of separators. So
|
||||
* 'prefix' is not allowed to end in the middle of a 'segment'.
|
||||
*/
|
||||
public static PropertyInfo findLongestValidProperty(FuzzyMap<PropertyInfo> index, String name) {
|
||||
int bracketPos = name.indexOf('[');
|
||||
int endPos = bracketPos>=0?bracketPos:name.length();
|
||||
PropertyInfo prop = null;
|
||||
String prefix = null;
|
||||
while (endPos>0 && prop==null) {
|
||||
prefix = name.substring(0, endPos);
|
||||
String canonicalPrefix = camelCaseToHyphens(prefix);
|
||||
prop = index.get(canonicalPrefix);
|
||||
if (prop==null) {
|
||||
endPos = name.lastIndexOf('.', endPos-1);
|
||||
}
|
||||
}
|
||||
if (prop!=null) {
|
||||
//We should meet caller's expectation that matched properties returned by this method
|
||||
// match the names exactly even if we found them using relaxed name matching.
|
||||
return prop.withId(prefix);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016-2017 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.common;
|
||||
|
||||
import static org.springframework.ide.vscode.commons.util.Renderables.bold;
|
||||
import static org.springframework.ide.vscode.commons.util.Renderables.concat;
|
||||
import static org.springframework.ide.vscode.commons.util.Renderables.italic;
|
||||
import static org.springframework.ide.vscode.commons.util.Renderables.lineBreak;
|
||||
import static org.springframework.ide.vscode.commons.util.Renderables.link;
|
||||
import static org.springframework.ide.vscode.commons.util.Renderables.paragraph;
|
||||
import static org.springframework.ide.vscode.commons.util.Renderables.strikeThrough;
|
||||
import static org.springframework.ide.vscode.commons.util.Renderables.text;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.ide.vscode.boot.configurationmetadata.Deprecation;
|
||||
import org.springframework.ide.vscode.boot.metadata.PropertyInfo;
|
||||
import org.springframework.ide.vscode.commons.util.Renderable;
|
||||
import org.springframework.ide.vscode.commons.util.Renderables;
|
||||
import org.springframework.ide.vscode.commons.util.StringUtil;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableList.Builder;
|
||||
|
||||
public class InformationTemplates {
|
||||
|
||||
public static Renderable createHover(PropertyInfo info) {
|
||||
Deprecation deprecation = createDeprecation(info);
|
||||
Renderable description = info.getDescription() == null ? null : text(info.getDescription());
|
||||
return InformationTemplates.createHover(info.getId(), info.getType(), info.getDefaultValue(), description, deprecation);
|
||||
}
|
||||
|
||||
public static Renderable createCompletionDocumentation(PropertyInfo info) {
|
||||
Deprecation deprecation = createDeprecation(info);
|
||||
Renderable description = info.getDescription() == null ? null : text(info.getDescription());
|
||||
return InformationTemplates.createCompletionDocumentation(description, info.getDefaultValue(), deprecation);
|
||||
}
|
||||
|
||||
public static Renderable createHover(String id, String type, Object defaultValue, Renderable description, Deprecation deprecation) {
|
||||
Builder<Renderable> renderableBuilder = ImmutableList.builder();
|
||||
|
||||
renderId(renderableBuilder, id, deprecation);
|
||||
|
||||
if (type==null) {
|
||||
type = Object.class.getName();
|
||||
}
|
||||
renderableBuilder.add(lineBreak());
|
||||
actionLink(renderableBuilder, type);
|
||||
|
||||
String deflt = formatDefaultValue(defaultValue);
|
||||
if (deflt!=null) {
|
||||
renderableBuilder.add(lineBreak());
|
||||
renderableBuilder.add(lineBreak());
|
||||
defaultValueRenderable(renderableBuilder, deflt);
|
||||
}
|
||||
|
||||
if (deprecation != null) {
|
||||
renderableBuilder.add(lineBreak());
|
||||
renderableBuilder.add(lineBreak());
|
||||
depreactionRenderable(renderableBuilder, deprecation);
|
||||
}
|
||||
|
||||
if (description!=null) {
|
||||
renderableBuilder.add(lineBreak());
|
||||
descriptionRenderable(renderableBuilder, description);
|
||||
}
|
||||
|
||||
return concat(renderableBuilder.build());
|
||||
}
|
||||
|
||||
public static Renderable createCompletionDocumentation(Renderable description, Object defaultValue, Deprecation deprecation) {
|
||||
Builder<Renderable> renderableBuilder = ImmutableList.builder();
|
||||
|
||||
if (description!=null) {
|
||||
descriptionRenderable(renderableBuilder, description);
|
||||
}
|
||||
|
||||
String deflt = formatDefaultValue(defaultValue);
|
||||
if (deflt!=null) {
|
||||
if (description != null) {
|
||||
renderableBuilder.add(lineBreak());
|
||||
}
|
||||
defaultValueRenderable(renderableBuilder, deflt);
|
||||
}
|
||||
|
||||
if (deprecation != null) {
|
||||
if (description != null) {
|
||||
renderableBuilder.add(lineBreak());
|
||||
}
|
||||
depreactionRenderable(renderableBuilder, deprecation);
|
||||
}
|
||||
|
||||
|
||||
ImmutableList<Renderable> pieces = renderableBuilder.build();
|
||||
|
||||
// Special case when there is no description, default value and deprecation data -> return `null` documentation.
|
||||
if (pieces.size() == 1 && pieces.get(0) == Renderables.NO_DESCRIPTION) {
|
||||
pieces = ImmutableList.of();
|
||||
}
|
||||
return pieces.isEmpty() ? null : concat(pieces);
|
||||
|
||||
}
|
||||
|
||||
private static Deprecation createDeprecation(PropertyInfo info) {
|
||||
Deprecation deprecation = null;
|
||||
if (info.isDeprecated()) {
|
||||
deprecation = new Deprecation();
|
||||
deprecation.setReason(info.getDeprecationReason());
|
||||
deprecation.setReplacement(info.getDeprecationReplacement());
|
||||
}
|
||||
return deprecation;
|
||||
}
|
||||
|
||||
private static void renderId(Builder<Renderable> renderableBuilder, String id, Deprecation deprecation) {
|
||||
if (deprecation == null) {
|
||||
renderableBuilder.add(bold(text(id)));
|
||||
} else {
|
||||
renderableBuilder.add(strikeThrough(text(id)));
|
||||
String replacement = deprecation.getReplacement();
|
||||
if (StringUtil.hasText(replacement)) {
|
||||
renderableBuilder.add(text(" -> " + replacement));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String formatDefaultValue(Object defaultValue) {
|
||||
if (defaultValue!=null) {
|
||||
if (defaultValue instanceof String) {
|
||||
return (String) defaultValue;
|
||||
} else if (defaultValue instanceof Number) {
|
||||
return ((Number)defaultValue).toString();
|
||||
} else if (defaultValue instanceof Boolean) {
|
||||
return Boolean.toString((Boolean) defaultValue);
|
||||
} else if (defaultValue instanceof Object[]) {
|
||||
return StringUtil.arrayToCommaDelimitedString((Object[]) defaultValue);
|
||||
} else if (defaultValue instanceof Collection<?>) {
|
||||
return StringUtil.collectionToCommaDelimitedString((Collection<?>) defaultValue);
|
||||
} else {
|
||||
//no idea what it is but try 'toString' and hope for the best
|
||||
return defaultValue.toString();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an 'action' link and adds it to the html buffer. When the user clicks the given
|
||||
* link then the provided runnable is to be executed.
|
||||
*/
|
||||
private static void actionLink(Builder<Renderable> renderableBuilder, String displayString) {
|
||||
renderableBuilder.add(link(displayString, "null"));
|
||||
}
|
||||
|
||||
private static void defaultValueRenderable(Builder<Renderable> renderableBuilder, String defaultValue) {
|
||||
renderableBuilder.add(text("Default: "));
|
||||
renderableBuilder.add(italic(text(defaultValue)));
|
||||
}
|
||||
|
||||
private static void depreactionRenderable(Builder<Renderable> renderableBuilder, Deprecation deprecation) {
|
||||
String reason = deprecation.getReason();
|
||||
if (StringUtil.hasText(reason)) {
|
||||
renderableBuilder.add(bold(text("Deprecated: ")));
|
||||
renderableBuilder.add(text(reason));
|
||||
} else {
|
||||
renderableBuilder.add(bold(text("Deprecated!")));
|
||||
}
|
||||
}
|
||||
|
||||
private static void descriptionRenderable(Builder<Renderable> renderableBuilder, Renderable description) {
|
||||
renderableBuilder.add(paragraph(description));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2014-2017 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.common;
|
||||
|
||||
import org.eclipse.lsp4j.CompletionItemKind;
|
||||
import org.springframework.ide.vscode.boot.metadata.PropertyInfo;
|
||||
import org.springframework.ide.vscode.boot.metadata.types.Type;
|
||||
import org.springframework.ide.vscode.boot.metadata.types.TypeParser;
|
||||
import org.springframework.ide.vscode.boot.metadata.types.TypeUtil;
|
||||
import org.springframework.ide.vscode.boot.metadata.types.TypedProperty;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.ScoreableProposal;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
import org.springframework.ide.vscode.commons.util.FuzzyMap.Match;
|
||||
import org.springframework.ide.vscode.commons.util.Renderable;
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
import org.springframework.ide.vscode.commons.yaml.hover.YPropertyInfoTemplates;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.YType;
|
||||
|
||||
public class PropertyCompletionFactory {
|
||||
|
||||
public ICompletionProposal valueProposal(String value, String query, String niceTypeName, double score, DocumentEdits edits, Renderable info) {
|
||||
return new ScoreableProposal() {
|
||||
|
||||
@Override
|
||||
public DocumentEdits getTextEdit() {
|
||||
return edits;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLabel() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletionItemKind getKind() {
|
||||
return CompletionItemKind.Value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getBaseScore() {
|
||||
return score;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDetail() {
|
||||
return niceTypeName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Renderable getDocumentation() {
|
||||
return info;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public ScoreableProposal property(IDocument doc, DocumentEdits applier, Match<PropertyInfo> prop, TypeUtil typeUtil) {
|
||||
return new PropertyProposal(doc, applier, prop, typeUtil);
|
||||
}
|
||||
|
||||
public ScoreableProposal beanProperty(IDocument doc, final String contextProperty, final Type contextType, final String pattern, final TypedProperty property, final double score, DocumentEdits applier, final TypeUtil typeUtil) {
|
||||
AbstractPropertyProposal proposal = new AbstractPropertyProposal(doc, applier) {
|
||||
|
||||
@Override
|
||||
public Renderable getDocumentation() {
|
||||
return YPropertyInfoTemplates.createCompletionDocumentation(contextProperty, contextType, property);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getBaseDisplayString() {
|
||||
return property.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getHighlightPattern() {
|
||||
return pattern;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Type getType() {
|
||||
return property.getType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getBaseScore() {
|
||||
return score;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String niceTypeName(YType type) {
|
||||
return typeUtil.niceTypeName((Type) type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLabel() {
|
||||
return getBaseDisplayString();
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
if (property.isDeprecated()) {
|
||||
proposal.deprecate();
|
||||
}
|
||||
return proposal;
|
||||
}
|
||||
|
||||
private JavaProjectFinder documentContextFinder;
|
||||
|
||||
public PropertyCompletionFactory(JavaProjectFinder documentContextFinder) {
|
||||
this.documentContextFinder = documentContextFinder;
|
||||
}
|
||||
|
||||
private class PropertyProposal extends AbstractPropertyProposal {
|
||||
private Match<PropertyInfo> match;
|
||||
private Type type;
|
||||
private TypeUtil typeUtil;
|
||||
|
||||
public PropertyProposal(IDocument doc, DocumentEdits applier, Match<PropertyInfo> match,
|
||||
TypeUtil typeUtil) {
|
||||
super(doc, applier);
|
||||
this.typeUtil = typeUtil;
|
||||
this.match = match;
|
||||
if (match.data.isDeprecated()) {
|
||||
deprecate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getBaseDisplayString() {
|
||||
return match.data.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getBaseScore() {
|
||||
return match.score;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Type getType() {
|
||||
if (type==null) {
|
||||
type = TypeParser.parse(match.data.getType());
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getHighlightPattern() {
|
||||
return match.getPattern();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String niceTypeName(YType type) {
|
||||
return typeUtil.niceTypeName(((Type)type));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLabel() {
|
||||
return getBaseDisplayString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Renderable getDocumentation() {
|
||||
return InformationTemplates.createCompletionDocumentation(match.data);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016-2017 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.common;
|
||||
|
||||
import org.springframework.ide.vscode.boot.metadata.types.TypeUtil.BeanPropertyNameMode;
|
||||
import org.springframework.ide.vscode.boot.metadata.types.TypeUtil.EnumCaseMode;
|
||||
|
||||
/**
|
||||
* Config object that determines some aspects of how the freedom of 'relaxed name binding'
|
||||
* are taken into account when generating content-assist completions.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class RelaxedNameConfig {
|
||||
|
||||
public static final RelaxedNameConfig ALIASSED = new RelaxedNameConfig(EnumCaseMode.ALIASED, BeanPropertyNameMode.ALIASED);
|
||||
public static final RelaxedNameConfig COMPLETION_DEFAULTS = new RelaxedNameConfig(EnumCaseMode.LOWER_CASE, BeanPropertyNameMode.HYPHENATED);
|
||||
|
||||
private EnumCaseMode enumMode = EnumCaseMode.LOWER_CASE;
|
||||
private BeanPropertyNameMode beanMode = BeanPropertyNameMode.HYPHENATED;
|
||||
|
||||
public RelaxedNameConfig(EnumCaseMode enumMode, BeanPropertyNameMode beanMode) {
|
||||
this.enumMode = enumMode;
|
||||
this.beanMode = beanMode;
|
||||
}
|
||||
public EnumCaseMode getEnumMode() {
|
||||
return enumMode;
|
||||
}
|
||||
public void setEnumMode(EnumCaseMode preferredEnumCompletions) {
|
||||
this.enumMode = preferredEnumCompletions;
|
||||
}
|
||||
public BeanPropertyNameMode getBeanMode() {
|
||||
return beanMode;
|
||||
}
|
||||
public void setBeanMode(BeanPropertyNameMode preferredBeanCompletions) {
|
||||
this.beanMode = preferredBeanCompletions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "RelaxedNameConfig("+enumMode+", "+beanMode+")";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
package org.springframework.ide.vscode.boot.configurationmetadata;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Gather a collection of {@link ConfigurationMetadataProperty properties} that are
|
||||
* sharing a {@link #getId() common prefix}. Provide access to all the
|
||||
* {@link ConfigurationMetadataSource sources} that have contributed properties to the
|
||||
* group.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 1.3.0
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class ConfigurationMetadataGroup implements Serializable {
|
||||
|
||||
private final String id;
|
||||
|
||||
private final Map<String, ConfigurationMetadataSource> sources = new HashMap<String, ConfigurationMetadataSource>();
|
||||
|
||||
private final Map<String, ConfigurationMetadataProperty> properties = new HashMap<String, ConfigurationMetadataProperty>();
|
||||
|
||||
public ConfigurationMetadataGroup(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the id of the group, used as a common prefix for all properties associated
|
||||
* to it.
|
||||
* @return the id of the group
|
||||
*/
|
||||
public String getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link ConfigurationMetadataSource sources} defining the properties of
|
||||
* this group.
|
||||
* @return the sources of the group
|
||||
*/
|
||||
public Map<String, ConfigurationMetadataSource> getSources() {
|
||||
return this.sources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link ConfigurationMetadataProperty properties} defined in this group.
|
||||
* <p>
|
||||
* A property may appear more than once for a given source, potentially with
|
||||
* conflicting type or documentation. This is a "merged" view of the properties of
|
||||
* this group.
|
||||
* @return the properties of the group
|
||||
* @see ConfigurationMetadataSource#getProperties()
|
||||
*/
|
||||
public Map<String, ConfigurationMetadataProperty> getProperties() {
|
||||
return this.properties;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2012-2016 the original author or authors.
|
||||
*
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
package org.springframework.ide.vscode.boot.configurationmetadata;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A raw view of a hint used for parsing only.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 1.3.0
|
||||
*/
|
||||
class ConfigurationMetadataHint {
|
||||
|
||||
private static final String KEY_SUFFIX = ".keys";
|
||||
|
||||
private static final String VALUE_SUFFIX = ".values";
|
||||
|
||||
private String id;
|
||||
|
||||
private final List<ValueHint> valueHints = new ArrayList<ValueHint>();
|
||||
|
||||
private final List<ValueProvider> valueProviders = new ArrayList<ValueProvider>();
|
||||
|
||||
public boolean isMapKeyHints() {
|
||||
return (this.id != null && this.id.endsWith(KEY_SUFFIX));
|
||||
}
|
||||
|
||||
public boolean isMapValueHints() {
|
||||
return (this.id != null && this.id.endsWith(VALUE_SUFFIX));
|
||||
}
|
||||
|
||||
public String resolveId() {
|
||||
if (isMapKeyHints()) {
|
||||
return this.id.substring(0, this.id.length() - KEY_SUFFIX.length());
|
||||
}
|
||||
if (isMapValueHints()) {
|
||||
return this.id.substring(0, this.id.length() - VALUE_SUFFIX.length());
|
||||
}
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public List<ValueHint> getValueHints() {
|
||||
return this.valueHints;
|
||||
}
|
||||
|
||||
public List<ValueProvider> getValueProviders() {
|
||||
return this.valueProviders;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2012-2016 the original author or authors.
|
||||
*
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
package org.springframework.ide.vscode.boot.configurationmetadata;
|
||||
|
||||
/**
|
||||
* An extension of {@link ConfigurationMetadataProperty} that provides a reference to its
|
||||
* source.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 1.3.0
|
||||
*/
|
||||
class ConfigurationMetadataItem extends ConfigurationMetadataProperty {
|
||||
|
||||
private String sourceType;
|
||||
|
||||
private String sourceMethod;
|
||||
|
||||
/**
|
||||
* The class name of the source that contributed this property. For example, if the
|
||||
* property was from a class annotated with {@code @ConfigurationProperties} this
|
||||
* attribute would contain the fully qualified name of that class.
|
||||
* @return the source type
|
||||
*/
|
||||
public String getSourceType() {
|
||||
return this.sourceType;
|
||||
}
|
||||
|
||||
public void setSourceType(String sourceType) {
|
||||
this.sourceType = sourceType;
|
||||
}
|
||||
|
||||
/**
|
||||
* The full name of the method (including parenthesis and argument types) that
|
||||
* contributed this property. For example, the name of a getter in a
|
||||
* {@code @ConfigurationProperties} annotated class.
|
||||
* @return the source method
|
||||
*/
|
||||
public String getSourceMethod() {
|
||||
return this.sourceMethod;
|
||||
}
|
||||
|
||||
public void setSourceMethod(String sourceMethod) {
|
||||
this.sourceMethod = sourceMethod;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* Copyright 2012-2016 the original author or authors.
|
||||
*
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
package org.springframework.ide.vscode.boot.configurationmetadata;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Define a configuration property. Each property is fully identified by its
|
||||
* {@link #getId() id} which is composed of a namespace prefix (the
|
||||
* {@link ConfigurationMetadataGroup#getId() group id}), if any and the {@link #getName()
|
||||
* name} of the property.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 1.3.0
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class ConfigurationMetadataProperty implements Serializable {
|
||||
|
||||
private String id;
|
||||
|
||||
private String name;
|
||||
|
||||
private String type;
|
||||
|
||||
private String description;
|
||||
|
||||
private String shortDescription;
|
||||
|
||||
private Object defaultValue;
|
||||
|
||||
private final Hints hints = new Hints();
|
||||
|
||||
private Deprecation deprecation;
|
||||
|
||||
/**
|
||||
* The full identifier of the property, in lowercase dashed form (e.g.
|
||||
* my.group.simple-property)
|
||||
* @return the property id
|
||||
*/
|
||||
public String getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the property, in lowercase dashed form (e.g. simple-property). If this
|
||||
* item does not belong to any group, the id is returned.
|
||||
* @return the property name
|
||||
*/
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* The class name of the data type of the property. For example,
|
||||
* {@code java.lang.String}.
|
||||
* <p>
|
||||
* For consistency, the type of a primitive is specified using its wrapper
|
||||
* counterpart, i.e. {@code boolean} becomes {@code java.lang.Boolean}. If the type
|
||||
* holds generic information, these are provided as well, i.e. a {@code HashMap} of
|
||||
* String to Integer would be defined as {@code java.util.HashMap
|
||||
* <java.lang.String,java.lang.Integer>}.
|
||||
* <p>
|
||||
* Note that this class may be a complex type that gets converted from a String as
|
||||
* values are bound.
|
||||
* @return the property type
|
||||
*/
|
||||
public String getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* A description of the property, if any. Can be multi-lines.
|
||||
* @return the property description
|
||||
* @see #getShortDescription()
|
||||
*/
|
||||
public String getDescription() {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
/**
|
||||
* A single-line, single-sentence description of this property, if any.
|
||||
* @return the property short description
|
||||
* @see #getDescription()
|
||||
*/
|
||||
public String getShortDescription() {
|
||||
return this.shortDescription;
|
||||
}
|
||||
|
||||
public void setShortDescription(String shortDescription) {
|
||||
this.shortDescription = shortDescription;
|
||||
}
|
||||
|
||||
/**
|
||||
* The default value, if any.
|
||||
* @return the default value
|
||||
*/
|
||||
public Object getDefaultValue() {
|
||||
return this.defaultValue;
|
||||
}
|
||||
|
||||
public void setDefaultValue(Object defaultValue) {
|
||||
this.defaultValue = defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the hints of this item.
|
||||
* @return the hints
|
||||
*/
|
||||
public Hints getHints() {
|
||||
return this.hints;
|
||||
}
|
||||
|
||||
/**
|
||||
* The list of well-defined values, if any. If no extra {@link ValueProvider provider}
|
||||
* is specified, these values are to be considered a closed-set of the available
|
||||
* values for this item.
|
||||
* @return the value hints
|
||||
* @see #getHints()
|
||||
*/
|
||||
@Deprecated
|
||||
public List<ValueHint> getValueHints() {
|
||||
return this.hints.getValueHints();
|
||||
}
|
||||
|
||||
/**
|
||||
* The value providers that are applicable to this item. Only one
|
||||
* {@link ValueProvider} is enabled for an item: the first in the list that is
|
||||
* supported should be used.
|
||||
* @return the value providers
|
||||
* @see #getHints()
|
||||
*/
|
||||
@Deprecated
|
||||
public List<ValueProvider> getValueProviders() {
|
||||
return this.hints.getValueProviders();
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link Deprecation} for this property, if any.
|
||||
* @return the deprecation
|
||||
* @see #isDeprecated()
|
||||
*/
|
||||
public Deprecation getDeprecation() {
|
||||
return this.deprecation;
|
||||
}
|
||||
|
||||
public void setDeprecation(Deprecation deprecation) {
|
||||
this.deprecation = deprecation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify if the property is deprecated.
|
||||
* @return if the property is deprecated
|
||||
* @see #getDeprecation()
|
||||
*/
|
||||
public boolean isDeprecated() {
|
||||
return this.deprecation != null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
package org.springframework.ide.vscode.boot.configurationmetadata;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A repository of configuration metadata.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 1.3.0
|
||||
*/
|
||||
public interface ConfigurationMetadataRepository {
|
||||
|
||||
/**
|
||||
* Defines the name of the "root" group, that is the group that gathers all the
|
||||
* properties that aren't attached to a specific group.
|
||||
*/
|
||||
String ROOT_GROUP = "_ROOT_GROUP_";
|
||||
|
||||
/**
|
||||
* Return the groups, indexed by id.
|
||||
* @return all configuration meta-data groups
|
||||
*/
|
||||
Map<String, ConfigurationMetadataGroup> getAllGroups();
|
||||
|
||||
/**
|
||||
* Return the properties, indexed by id.
|
||||
* @return all configuration meta-data properties
|
||||
*/
|
||||
Map<String, ConfigurationMetadataProperty> getAllProperties();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
/*
|
||||
* Copyright 2012-2016 the original author or authors.
|
||||
*
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
package org.springframework.ide.vscode.boot.configurationmetadata;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.ide.eclipse.org.json.JSONException;
|
||||
|
||||
/**
|
||||
* Load a {@link ConfigurationMetadataRepository} from the content of arbitrary
|
||||
* resource(s).
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 1.3.0
|
||||
*/
|
||||
public final class ConfigurationMetadataRepositoryJsonBuilder {
|
||||
|
||||
/**
|
||||
* UTF-8 Charset.
|
||||
*/
|
||||
public static final Charset UTF_8 = Charset.forName("UTF-8");
|
||||
|
||||
private Charset defaultCharset = UTF_8;
|
||||
|
||||
private final JsonReader reader = new JsonReader();
|
||||
|
||||
private final List<RawConfigurationMetadata> rawDatas = new ArrayList<>();
|
||||
|
||||
private ConfigurationMetadataRepositoryJsonBuilder(Charset defaultCharset) {
|
||||
this.defaultCharset = defaultCharset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the content of a {@link ConfigurationMetadataRepository} defined by the
|
||||
* specified {@link InputStream} json document using the default charset. If this
|
||||
* metadata repository holds items that were loaded previously, these are ignored.
|
||||
* <p>
|
||||
* Leaves the stream open when done.
|
||||
* @param origin optional information object to help identify where the inputstream came from
|
||||
* @param inputStream the source input stream
|
||||
* @return this builder
|
||||
* @throws IOException in case of I/O errors
|
||||
*/
|
||||
public ConfigurationMetadataRepositoryJsonBuilder withJsonResource(
|
||||
Object origin, InputStream inputStream) throws IOException {
|
||||
return withJsonResource(origin, inputStream, this.defaultCharset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the content of a {@link ConfigurationMetadataRepository} defined by the
|
||||
* specified {@link InputStream} json document using the specified {@link Charset}. If
|
||||
* this metadata repository holds items that were loaded previously, these are
|
||||
* ignored.
|
||||
* <p>
|
||||
* Leaves the stream open when done.
|
||||
* @param origin optional information object to help identify where the inputstream came from
|
||||
* @param inputStream the source input stream
|
||||
* @param charset the charset of the input
|
||||
* @return this builder
|
||||
* @throws IOException in case of I/O errors
|
||||
*/
|
||||
public ConfigurationMetadataRepositoryJsonBuilder withJsonResource(
|
||||
Object origin, InputStream inputStream, Charset charset) throws IOException {
|
||||
if (inputStream == null) {
|
||||
throw new IllegalArgumentException("InputStream must not be null.");
|
||||
}
|
||||
this.rawDatas.add(parseRaw(origin, inputStream, charset));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a {@link ConfigurationMetadataRepository} with the current state of this
|
||||
* builder.
|
||||
* @return this builder
|
||||
*/
|
||||
public ConfigurationMetadataRepository build() {
|
||||
SimpleConfigurationMetadataRepository result = new SimpleConfigurationMetadataRepository();
|
||||
result.include(create(rawDatas));
|
||||
return result;
|
||||
}
|
||||
|
||||
private RawConfigurationMetadata parseRaw(Object origin, InputStream in, Charset charset)
|
||||
throws IOException {
|
||||
try {
|
||||
return this.reader.read(origin, in, charset);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalArgumentException(
|
||||
"Failed to read configuration " + "metadata", ex);
|
||||
}
|
||||
catch (JSONException ex) {
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid configuration " + "metadata document", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private SimpleConfigurationMetadataRepository create(
|
||||
Iterable<RawConfigurationMetadata> metadatas) {
|
||||
SimpleConfigurationMetadataRepository repository = new SimpleConfigurationMetadataRepository();
|
||||
|
||||
for (RawConfigurationMetadata metadata : metadatas) {
|
||||
repository.add(metadata.getSources());
|
||||
}
|
||||
for (RawConfigurationMetadata metadata : metadatas) {
|
||||
for (ConfigurationMetadataItem item : metadata.getItems()) {
|
||||
ConfigurationMetadataSource source = getSource(metadata, item);
|
||||
repository.add(item, source);
|
||||
}
|
||||
}
|
||||
for (RawConfigurationMetadata metadata : metadatas) {
|
||||
Map<String, ConfigurationMetadataProperty> allProperties = repository
|
||||
.getAllProperties();
|
||||
for (ConfigurationMetadataHint hint : metadata.getHints()) {
|
||||
ConfigurationMetadataProperty property = allProperties.get(hint.getId());
|
||||
if (property != null) {
|
||||
addValueHints(property, hint);
|
||||
}
|
||||
else {
|
||||
String id = hint.resolveId();
|
||||
property = allProperties.get(id);
|
||||
if (property != null) {
|
||||
if (hint.isMapKeyHints()) {
|
||||
addMapHints(property, hint);
|
||||
}
|
||||
else {
|
||||
addValueHints(property, hint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return repository;
|
||||
}
|
||||
|
||||
private void addValueHints(ConfigurationMetadataProperty property,
|
||||
ConfigurationMetadataHint hint) {
|
||||
addAll(property.getHints().getValueHints(), hint.getValueHints());
|
||||
property.getHints().getValueProviders().addAll(hint.getValueProviders());
|
||||
}
|
||||
|
||||
private void addMapHints(ConfigurationMetadataProperty property,
|
||||
ConfigurationMetadataHint hint) {
|
||||
addAll(property.getHints().getKeyHints(), hint.getValueHints());
|
||||
property.getHints().getKeyProviders().addAll(hint.getValueProviders());
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a bunch of hints to a list, but guard against duplicates.
|
||||
*/
|
||||
private void addAll(List<ValueHint> existing, List<ValueHint> toAdd) {
|
||||
if (existing.isEmpty()) {
|
||||
existing.addAll(toAdd);
|
||||
} else if (toAdd.isEmpty()) {
|
||||
//nothing to add
|
||||
} else {
|
||||
Set<Object> existingValues = existing
|
||||
.stream()
|
||||
.map((hint) -> ""+hint.getValue())
|
||||
.collect(Collectors.toSet());
|
||||
for (ValueHint hint : toAdd) {
|
||||
if (!existingValues.contains(""+hint.getValue())) {
|
||||
existing.add(hint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ConfigurationMetadataSource getSource(RawConfigurationMetadata metadata,
|
||||
ConfigurationMetadataItem item) {
|
||||
if (item.getSourceType() != null) {
|
||||
return metadata.getSource(item.getSourceType());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new builder instance using {@link #UTF_8} as the default charset and the
|
||||
* specified json resource.
|
||||
* @param inputStreams the source input streams
|
||||
* @return a new {@link ConfigurationMetadataRepositoryJsonBuilder} instance.
|
||||
* @throws IOException on error
|
||||
*/
|
||||
public static ConfigurationMetadataRepositoryJsonBuilder create(
|
||||
InputStream... inputStreams) throws IOException {
|
||||
ConfigurationMetadataRepositoryJsonBuilder builder = create();
|
||||
for (InputStream inputStream : inputStreams) {
|
||||
builder = builder.withJsonResource(null, inputStream);
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new builder instance using {@link #UTF_8} as the default charset.
|
||||
* @return a new {@link ConfigurationMetadataRepositoryJsonBuilder} instance.
|
||||
*/
|
||||
public static ConfigurationMetadataRepositoryJsonBuilder create() {
|
||||
return create(UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new builder instance using the specified default {@link Charset}.
|
||||
* @param defaultCharset the default charset to use
|
||||
* @return a new {@link ConfigurationMetadataRepositoryJsonBuilder} instance.
|
||||
*/
|
||||
public static ConfigurationMetadataRepositoryJsonBuilder create(
|
||||
Charset defaultCharset) {
|
||||
return new ConfigurationMetadataRepositoryJsonBuilder(defaultCharset);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
package org.springframework.ide.vscode.boot.configurationmetadata;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A source of configuration metadata. Also defines where the source is declared, for
|
||||
* instance if it is defined as a {@code @Bean}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 1.3.0
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class ConfigurationMetadataSource implements Serializable {
|
||||
|
||||
private String groupId;
|
||||
|
||||
private String type;
|
||||
|
||||
private String description;
|
||||
|
||||
private String shortDescription;
|
||||
|
||||
private String sourceType;
|
||||
|
||||
private String sourceMethod;
|
||||
|
||||
private final Map<String, ConfigurationMetadataProperty> properties = new HashMap<String, ConfigurationMetadataProperty>();
|
||||
|
||||
/**
|
||||
* The identifier of the group to which this source is associated.
|
||||
* @return the group id
|
||||
*/
|
||||
public String getGroupId() {
|
||||
return this.groupId;
|
||||
}
|
||||
|
||||
void setGroupId(String groupId) {
|
||||
this.groupId = groupId;
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of the source. Usually this is the fully qualified name of a class that
|
||||
* defines configuration items. This class may or may not be available at runtime.
|
||||
* @return the type
|
||||
*/
|
||||
public String getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* A description of this source, if any. Can be multi-lines.
|
||||
* @return the description
|
||||
* @see #getShortDescription()
|
||||
*/
|
||||
public String getDescription() {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
/**
|
||||
* A single-line, single-sentence description of this source, if any.
|
||||
* @return the short description
|
||||
* @see #getDescription()
|
||||
*/
|
||||
public String getShortDescription() {
|
||||
return this.shortDescription;
|
||||
}
|
||||
|
||||
public void setShortDescription(String shortDescription) {
|
||||
this.shortDescription = shortDescription;
|
||||
}
|
||||
|
||||
/**
|
||||
* The type where this source is defined. This can be identical to the
|
||||
* {@link #getType() type} if the source is self-defined.
|
||||
* @return the source type
|
||||
*/
|
||||
public String getSourceType() {
|
||||
return this.sourceType;
|
||||
}
|
||||
|
||||
void setSourceType(String sourceType) {
|
||||
this.sourceType = sourceType;
|
||||
}
|
||||
|
||||
/**
|
||||
* The method name that defines this source, if any.
|
||||
* @return the source method
|
||||
*/
|
||||
public String getSourceMethod() {
|
||||
return this.sourceMethod;
|
||||
}
|
||||
|
||||
void setSourceMethod(String sourceMethod) {
|
||||
this.sourceMethod = sourceMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the properties defined by this source.
|
||||
* @return the properties
|
||||
*/
|
||||
public Map<String, ConfigurationMetadataProperty> getProperties() {
|
||||
return this.properties;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
package org.springframework.ide.vscode.boot.configurationmetadata;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Indicate that a property is deprecated. Provide additional information about the
|
||||
* deprecation.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 1.3.0
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class Deprecation implements Serializable {
|
||||
|
||||
private String reason;
|
||||
|
||||
private String replacement;
|
||||
|
||||
/**
|
||||
* A reason why the related property is deprecated, if any. Can be multi-lines.
|
||||
* @return the deprecation reason
|
||||
*/
|
||||
public String getReason() {
|
||||
return this.reason;
|
||||
}
|
||||
|
||||
public void setReason(String reason) {
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
/**
|
||||
* The full name of the property that replaces the related deprecated property, if
|
||||
* any.
|
||||
* @return the replacement property name
|
||||
*/
|
||||
public String getReplacement() {
|
||||
return this.replacement;
|
||||
}
|
||||
|
||||
public void setReplacement(String replacement) {
|
||||
this.replacement = replacement;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Deprecation{" + "reason='" + this.reason + '\'' + ", replacement='"
|
||||
+ this.replacement + '\'' + '}';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
package org.springframework.ide.vscode.boot.configurationmetadata;
|
||||
|
||||
import java.text.BreakIterator;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Utility to extract a description.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class DescriptionExtractor {
|
||||
|
||||
private static final String NEW_LINE = System.getProperty("line.separator");
|
||||
|
||||
public String getShortDescription(String description) {
|
||||
if (description == null) {
|
||||
return null;
|
||||
}
|
||||
int dot = description.indexOf(".");
|
||||
if (dot != -1) {
|
||||
BreakIterator breakIterator = BreakIterator.getSentenceInstance(Locale.US);
|
||||
breakIterator.setText(description);
|
||||
String text = description
|
||||
.substring(breakIterator.first(), breakIterator.next()).trim();
|
||||
return removeSpaceBetweenLine(text);
|
||||
}
|
||||
else {
|
||||
String[] lines = description.split(NEW_LINE);
|
||||
return lines[0].trim();
|
||||
}
|
||||
}
|
||||
|
||||
private String removeSpaceBetweenLine(String text) {
|
||||
String[] lines = text.split(NEW_LINE);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String line : lines) {
|
||||
sb.append(line.trim()).append(" ");
|
||||
}
|
||||
return sb.toString().trim();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright 2012-2016 the original author or authors.
|
||||
*
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
package org.springframework.ide.vscode.boot.configurationmetadata;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Hints of an item to provide the list of values and/or the name of the provider
|
||||
* responsible to identify suitable values. If the type of the related item is a
|
||||
* {@link java.util.Map} it can have both key and value hints.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public class Hints {
|
||||
|
||||
private final List<ValueHint> keyHints = new ArrayList<ValueHint>();
|
||||
|
||||
private final List<ValueProvider> keyProviders = new ArrayList<ValueProvider>();
|
||||
|
||||
private final List<ValueHint> valueHints = new ArrayList<ValueHint>();
|
||||
|
||||
private final List<ValueProvider> valueProviders = new ArrayList<ValueProvider>();
|
||||
|
||||
/**
|
||||
* The list of well-defined keys, if any. Only applicable if the type of the related
|
||||
* item is a {@link java.util.Map}. If no extra {@link ValueProvider provider} is
|
||||
* specified, these values are to be considered a closed-set of the available keys for
|
||||
* the map.
|
||||
* @return the key hints
|
||||
*/
|
||||
public List<ValueHint> getKeyHints() {
|
||||
return this.keyHints;
|
||||
}
|
||||
|
||||
/**
|
||||
* The value providers that are applicable to the keys of this item. Only applicable
|
||||
* if the type of the related item is a {@link java.util.Map}. Only one
|
||||
* {@link ValueProvider} is enabled for a key: the first in the list that is supported
|
||||
* should be used.
|
||||
* @return the key providers
|
||||
*/
|
||||
public List<ValueProvider> getKeyProviders() {
|
||||
return this.keyProviders;
|
||||
}
|
||||
|
||||
/**
|
||||
* The list of well-defined values, if any. If no extra {@link ValueProvider provider}
|
||||
* is specified, these values are to be considered a closed-set of the available
|
||||
* values for this item.
|
||||
* @return the value hints
|
||||
*/
|
||||
public List<ValueHint> getValueHints() {
|
||||
return this.valueHints;
|
||||
}
|
||||
|
||||
/**
|
||||
* The value providers that are applicable to this item. Only one
|
||||
* {@link ValueProvider} is enabled for an item: the first in the list that is
|
||||
* supported should be used.
|
||||
* @return the value providers
|
||||
*/
|
||||
public List<ValueProvider> getValueProviders() {
|
||||
return this.valueProviders;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
package org.springframework.ide.vscode.boot.configurationmetadata;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.ide.eclipse.org.json.JSONArray;
|
||||
import org.springframework.ide.eclipse.org.json.JSONObject;
|
||||
|
||||
/**
|
||||
* Read standard json metadata format as {@link ConfigurationMetadataRepository}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 1.3.0
|
||||
*/
|
||||
class JsonReader {
|
||||
|
||||
private static final int BUFFER_SIZE = 4096;
|
||||
|
||||
private final DescriptionExtractor descriptionExtractor = new DescriptionExtractor();
|
||||
|
||||
public RawConfigurationMetadata read(Object origin, InputStream in, Charset charset)
|
||||
throws IOException {
|
||||
JSONObject json = readJson(in, charset);
|
||||
List<ConfigurationMetadataSource> groups = parseAllSources(json);
|
||||
List<ConfigurationMetadataItem> items = parseAllItems(json);
|
||||
List<ConfigurationMetadataHint> hints = parseAllHints(json);
|
||||
return new RawConfigurationMetadata(origin, groups, items, hints);
|
||||
}
|
||||
|
||||
private List<ConfigurationMetadataSource> parseAllSources(JSONObject root) {
|
||||
List<ConfigurationMetadataSource> result = new ArrayList<ConfigurationMetadataSource>();
|
||||
if (!root.has("groups")) {
|
||||
return result;
|
||||
}
|
||||
JSONArray sources = root.getJSONArray("groups");
|
||||
for (int i = 0; i < sources.length(); i++) {
|
||||
JSONObject source = sources.getJSONObject(i);
|
||||
result.add(parseSource(source));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<ConfigurationMetadataItem> parseAllItems(JSONObject root) {
|
||||
List<ConfigurationMetadataItem> result = new ArrayList<ConfigurationMetadataItem>();
|
||||
if (!root.has("properties")) {
|
||||
return result;
|
||||
}
|
||||
JSONArray items = root.getJSONArray("properties");
|
||||
for (int i = 0; i < items.length(); i++) {
|
||||
JSONObject item = items.getJSONObject(i);
|
||||
result.add(parseItem(item));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<ConfigurationMetadataHint> parseAllHints(JSONObject root) {
|
||||
List<ConfigurationMetadataHint> result = new ArrayList<ConfigurationMetadataHint>();
|
||||
if (!root.has("hints")) {
|
||||
return result;
|
||||
}
|
||||
JSONArray items = root.getJSONArray("hints");
|
||||
for (int i = 0; i < items.length(); i++) {
|
||||
JSONObject item = items.getJSONObject(i);
|
||||
result.add(parseHint(item));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private ConfigurationMetadataSource parseSource(JSONObject json) {
|
||||
ConfigurationMetadataSource source = new ConfigurationMetadataSource();
|
||||
source.setGroupId(json.getString("name"));
|
||||
source.setType(json.optString("type", null));
|
||||
String description = json.optString("description", null);
|
||||
source.setDescription(description);
|
||||
source.setShortDescription(
|
||||
this.descriptionExtractor.getShortDescription(description));
|
||||
source.setSourceType(json.optString("sourceType", null));
|
||||
source.setSourceMethod(json.optString("sourceMethod", null));
|
||||
return source;
|
||||
}
|
||||
|
||||
private ConfigurationMetadataItem parseItem(JSONObject json) {
|
||||
ConfigurationMetadataItem item = new ConfigurationMetadataItem();
|
||||
item.setId(json.getString("name"));
|
||||
item.setType(json.optString("type", null));
|
||||
String description = json.optString("description", null);
|
||||
item.setDescription(description);
|
||||
item.setShortDescription(
|
||||
this.descriptionExtractor.getShortDescription(description));
|
||||
item.setDefaultValue(readItemValue(json.opt("defaultValue")));
|
||||
item.setDeprecation(parseDeprecation(json));
|
||||
item.setSourceType(json.optString("sourceType", null));
|
||||
item.setSourceMethod(json.optString("sourceMethod", null));
|
||||
return item;
|
||||
}
|
||||
|
||||
private ConfigurationMetadataHint parseHint(JSONObject json) {
|
||||
ConfigurationMetadataHint hint = new ConfigurationMetadataHint();
|
||||
hint.setId(json.getString("name"));
|
||||
if (json.has("values")) {
|
||||
JSONArray values = json.getJSONArray("values");
|
||||
for (int i = 0; i < values.length(); i++) {
|
||||
JSONObject value = values.getJSONObject(i);
|
||||
ValueHint valueHint = new ValueHint();
|
||||
valueHint.setValue(readItemValue(value.get("value")));
|
||||
String description = value.optString("description", null);
|
||||
valueHint.setDescription(description);
|
||||
valueHint.setShortDescription(
|
||||
this.descriptionExtractor.getShortDescription(description));
|
||||
hint.getValueHints().add(valueHint);
|
||||
}
|
||||
}
|
||||
if (json.has("providers")) {
|
||||
JSONArray providers = json.getJSONArray("providers");
|
||||
for (int i = 0; i < providers.length(); i++) {
|
||||
JSONObject provider = providers.getJSONObject(i);
|
||||
ValueProvider valueProvider = new ValueProvider();
|
||||
valueProvider.setName(provider.getString("name"));
|
||||
if (provider.has("parameters")) {
|
||||
JSONObject parameters = provider.getJSONObject("parameters");
|
||||
Iterator<?> keys = parameters.keys();
|
||||
while (keys.hasNext()) {
|
||||
String key = (String) keys.next();
|
||||
valueProvider.getParameters().put(key,
|
||||
readItemValue(parameters.get(key)));
|
||||
}
|
||||
}
|
||||
hint.getValueProviders().add(valueProvider);
|
||||
}
|
||||
}
|
||||
return hint;
|
||||
}
|
||||
|
||||
private Deprecation parseDeprecation(JSONObject object) {
|
||||
if (object.has("deprecation")) {
|
||||
JSONObject deprecationJsonObject = object.getJSONObject("deprecation");
|
||||
Deprecation deprecation = new Deprecation();
|
||||
deprecation.setReason(deprecationJsonObject.optString("reason", null));
|
||||
deprecation
|
||||
.setReplacement(deprecationJsonObject.optString("replacement", null));
|
||||
return deprecation;
|
||||
}
|
||||
return (object.optBoolean("deprecated") ? new Deprecation() : null);
|
||||
}
|
||||
|
||||
private Object readItemValue(Object value) {
|
||||
if (value instanceof JSONArray) {
|
||||
JSONArray array = (JSONArray) value;
|
||||
Object[] content = new Object[array.length()];
|
||||
for (int i = 0; i < array.length(); i++) {
|
||||
content[i] = array.get(i);
|
||||
}
|
||||
return content;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private JSONObject readJson(InputStream in, Charset charset) throws IOException {
|
||||
try {
|
||||
StringBuilder out = new StringBuilder();
|
||||
InputStreamReader reader = new InputStreamReader(in, charset);
|
||||
char[] buffer = new char[BUFFER_SIZE];
|
||||
int bytesRead = -1;
|
||||
while ((bytesRead = reader.read(buffer)) != -1) {
|
||||
out.append(buffer, 0, bytesRead);
|
||||
}
|
||||
return new JSONObject(out.toString());
|
||||
}
|
||||
finally {
|
||||
in.close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
The source code in this package is taken from here:
|
||||
|
||||
https://github.com/spring-projects/spring-boot/tree/fca6dbaf09c32202d9d958f815221aad54b9fc7b/spring-boot-tools/spring-boot-configuration-metadata/src/main/java/org/springframework/boot/configurationmetadata
|
||||
|
||||
Notes:
|
||||
- This commit is from the master branch at a point in time where boot team is working on Boot 1.4.x on that branch.
|
||||
|
||||
There are currently no modifications being made to that code at all to accomodate STS. So it may now be possible to consume it as a proper dependency.
|
||||
However, keep in mind that we are using a modified copy of 'org.json' to allow controlling key order in json maps. So that probably
|
||||
complicates things.
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright 2012-2016 the original author or authors.
|
||||
*
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
package org.springframework.ide.vscode.boot.configurationmetadata;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A raw metadata structure. Used to initialize a {@link ConfigurationMetadataRepository}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 1.3.0
|
||||
*/
|
||||
class RawConfigurationMetadata {
|
||||
|
||||
private final Object origin;
|
||||
|
||||
private final List<ConfigurationMetadataSource> sources;
|
||||
|
||||
private final List<ConfigurationMetadataItem> items;
|
||||
|
||||
private final List<ConfigurationMetadataHint> hints;
|
||||
|
||||
RawConfigurationMetadata(Object parsedFrom,
|
||||
List<ConfigurationMetadataSource> sources,
|
||||
List<ConfigurationMetadataItem> items,
|
||||
List<ConfigurationMetadataHint> hints) {
|
||||
this.origin = parsedFrom;
|
||||
this.sources = new ArrayList<ConfigurationMetadataSource>(sources);
|
||||
this.items = new ArrayList<ConfigurationMetadataItem>(items);
|
||||
this.hints = new ArrayList<ConfigurationMetadataHint>(hints);
|
||||
for (ConfigurationMetadataItem item : this.items) {
|
||||
resolveName(item);
|
||||
}
|
||||
}
|
||||
|
||||
public List<ConfigurationMetadataSource> getSources() {
|
||||
return this.sources;
|
||||
}
|
||||
|
||||
public ConfigurationMetadataSource getSource(String type) {
|
||||
for (ConfigurationMetadataSource source : this.sources) {
|
||||
if (type.equals(source.getType())) {
|
||||
return source;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<ConfigurationMetadataItem> getItems() {
|
||||
return this.items;
|
||||
}
|
||||
|
||||
public List<ConfigurationMetadataHint> getHints() {
|
||||
return this.hints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the name of an item against this instance.
|
||||
* @param item the item to resolve
|
||||
* @see ConfigurationMetadataProperty#setName(String)
|
||||
*/
|
||||
private void resolveName(ConfigurationMetadataItem item) {
|
||||
item.setName(item.getId()); // fallback
|
||||
if (item.getSourceType() == null) {
|
||||
return;
|
||||
}
|
||||
ConfigurationMetadataSource source = getSource(item.getSourceType());
|
||||
if (source != null) {
|
||||
String groupId = source.getGroupId();
|
||||
String dottedPrefix = groupId + ".";
|
||||
String id = item.getId();
|
||||
if (hasLength(groupId) && id.startsWith(dottedPrefix)) {
|
||||
String name = id.substring(dottedPrefix.length(), id.length());
|
||||
item.setName(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean hasLength(String string) {
|
||||
return (string != null && string.length() > 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (origin!=null) {
|
||||
return "RawConfigurationMetadata("+origin+")";
|
||||
}
|
||||
return super.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
package org.springframework.ide.vscode.boot.configurationmetadata;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* The default {@link ConfigurationMetadataRepository} implementation.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 1.3.0
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class SimpleConfigurationMetadataRepository
|
||||
implements ConfigurationMetadataRepository, Serializable {
|
||||
|
||||
private final Map<String, ConfigurationMetadataGroup> allGroups = new HashMap<String, ConfigurationMetadataGroup>();
|
||||
|
||||
@Override
|
||||
public Map<String, ConfigurationMetadataGroup> getAllGroups() {
|
||||
return Collections.unmodifiableMap(this.allGroups);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, ConfigurationMetadataProperty> getAllProperties() {
|
||||
Map<String, ConfigurationMetadataProperty> properties = new HashMap<String, ConfigurationMetadataProperty>();
|
||||
for (ConfigurationMetadataGroup group : this.allGroups.values()) {
|
||||
properties.putAll(group.getProperties());
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the specified {@link ConfigurationMetadataSource sources}.
|
||||
* @param sources the sources to add
|
||||
*/
|
||||
public void add(Collection<ConfigurationMetadataSource> sources) {
|
||||
for (ConfigurationMetadataSource source : sources) {
|
||||
String groupId = source.getGroupId();
|
||||
ConfigurationMetadataGroup group = this.allGroups.get(groupId);
|
||||
if (group == null) {
|
||||
group = new ConfigurationMetadataGroup(groupId);
|
||||
this.allGroups.put(groupId, group);
|
||||
}
|
||||
String sourceType = source.getType();
|
||||
if (sourceType != null) {
|
||||
putIfAbsent(group.getSources(), sourceType, source);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a {@link ConfigurationMetadataProperty} with the
|
||||
* {@link ConfigurationMetadataSource source} that defines it, if any.
|
||||
* @param property the property to add
|
||||
* @param source the source
|
||||
*/
|
||||
public void add(ConfigurationMetadataProperty property,
|
||||
ConfigurationMetadataSource source) {
|
||||
if (source != null) {
|
||||
putIfAbsent(source.getProperties(), property.getId(), property);
|
||||
}
|
||||
putIfAbsent(getGroup(source).getProperties(), property.getId(), property);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the content of the specified repository to this repository.
|
||||
* @param repository the repository to include
|
||||
*/
|
||||
public void include(ConfigurationMetadataRepository repository) {
|
||||
for (ConfigurationMetadataGroup group : repository.getAllGroups().values()) {
|
||||
ConfigurationMetadataGroup existingGroup = this.allGroups.get(group.getId());
|
||||
if (existingGroup == null) {
|
||||
this.allGroups.put(group.getId(), group);
|
||||
}
|
||||
else {
|
||||
// Merge properties
|
||||
for (Map.Entry<String, ConfigurationMetadataProperty> entry : group
|
||||
.getProperties().entrySet()) {
|
||||
putIfAbsent(existingGroup.getProperties(), entry.getKey(),
|
||||
entry.getValue());
|
||||
}
|
||||
// Merge sources
|
||||
for (Map.Entry<String, ConfigurationMetadataSource> entry : group
|
||||
.getSources().entrySet()) {
|
||||
putIfAbsent(existingGroup.getSources(), entry.getKey(),
|
||||
entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private ConfigurationMetadataGroup getGroup(ConfigurationMetadataSource source) {
|
||||
if (source == null) {
|
||||
ConfigurationMetadataGroup rootGroup = this.allGroups.get(ROOT_GROUP);
|
||||
if (rootGroup == null) {
|
||||
rootGroup = new ConfigurationMetadataGroup(ROOT_GROUP);
|
||||
this.allGroups.put(ROOT_GROUP, rootGroup);
|
||||
}
|
||||
return rootGroup;
|
||||
}
|
||||
return this.allGroups.get(source.getGroupId());
|
||||
}
|
||||
|
||||
private <V> void putIfAbsent(Map<String, V> map, String key, V value) {
|
||||
if (!map.containsKey(key)) {
|
||||
map.put(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
package org.springframework.ide.vscode.boot.configurationmetadata;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Hint for a value a given property may have. Provide the value and an optional
|
||||
* description.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 1.3.0
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class ValueHint implements Serializable, Cloneable {
|
||||
|
||||
public static ValueHint withValue(Object value) {
|
||||
ValueHint hint = new ValueHint();
|
||||
hint.setValue(value);
|
||||
return hint;
|
||||
}
|
||||
|
||||
public ValueHint prefixWith(String prefix) {
|
||||
try {
|
||||
ValueHint clone = (ValueHint) this.clone();
|
||||
clone.setValue(prefix+value);
|
||||
return clone;
|
||||
} catch (CloneNotSupportedException e) {
|
||||
//This is supposed to be impossble.
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private Object value;
|
||||
|
||||
private String description;
|
||||
|
||||
private String shortDescription;
|
||||
|
||||
/**
|
||||
* Return the hint value.
|
||||
* @return the value
|
||||
*/
|
||||
public Object getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public void setValue(Object value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* A description of this value, if any. Can be multi-lines.
|
||||
* @return the description
|
||||
* @see #getShortDescription()
|
||||
*/
|
||||
public String getDescription() {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
/**
|
||||
* A single-line, single-sentence description of this hint, if any.
|
||||
* @return the short description
|
||||
* @see #getDescription()
|
||||
*/
|
||||
public String getShortDescription() {
|
||||
return this.shortDescription;
|
||||
}
|
||||
|
||||
public void setShortDescription(String shortDescription) {
|
||||
this.shortDescription = shortDescription;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ValueHint{" + "value=" + this.value + ", description='" + this.description
|
||||
+ '\'' + '}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
package org.springframework.ide.vscode.boot.configurationmetadata;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Define a component that is able to provide the values of a property.
|
||||
* <p>
|
||||
* Each provider is defined by a {@code name} and can have an arbitrary number of
|
||||
* {@code parameters}. The available providers are defined in the Spring Boot
|
||||
* documentation.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 1.3.0
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class ValueProvider implements Serializable {
|
||||
|
||||
private String name;
|
||||
|
||||
private final Map<String, Object> parameters = new LinkedHashMap<String, Object>();
|
||||
|
||||
/**
|
||||
* Return the name of the provider.
|
||||
* @return the name
|
||||
*/
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the parameters.
|
||||
* @return the parameters
|
||||
*/
|
||||
public Map<String, Object> getParameters() {
|
||||
return this.parameters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ValueProvider{" + "name='" + this.name + ", parameters=" + this.parameters
|
||||
+ '}';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Spring Boot configuration meta-data parser.
|
||||
*/
|
||||
package org.springframework.ide.vscode.boot.configurationmetadata;
|
||||
@@ -0,0 +1,53 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 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;
|
||||
|
||||
/**
|
||||
* Constants containing various fully-qualified annotation names.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class Annotations {
|
||||
public static final String BEAN = "org.springframework.context.annotation.Bean";
|
||||
public static final String PROFILE = "org.springframework.context.annotation.Profile";
|
||||
public static final String CONDITIONAL = "org.springframework.context.annotation.Conditional";
|
||||
|
||||
public static final String COMPONENT = "org.springframework.stereotype.Component";
|
||||
public static final String REPOSITORY = "org.springframework.stereotype.Repository";
|
||||
|
||||
public static final String AUTOWIRED = "org.springframework.beans.factory.annotation.Autowired";
|
||||
|
||||
public static final String SPRING_REQUEST_MAPPING = "org.springframework.web.bind.annotation.RequestMapping";
|
||||
public static final String SPRING_GET_MAPPING = "org.springframework.web.bind.annotation.GetMapping";
|
||||
public static final String SPRING_POST_MAPPING = "org.springframework.web.bind.annotation.PostMapping";
|
||||
public static final String SPRING_PUT_MAPPING = "org.springframework.web.bind.annotation.PutMapping";
|
||||
public static final String SPRING_DELETE_MAPPING = "org.springframework.web.bind.annotation.DeleteMapping";
|
||||
public static final String SPRING_PATCH_MAPPING = "org.springframework.web.bind.annotation.PatchMapping";
|
||||
|
||||
public static final String CONDITIONAL_ON_BEAN = "org.springframework.boot.autoconfigure.condition.ConditionalOnBean";
|
||||
public static final String CONDITIONAL_ON_MISSING_BEAN = "org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean";
|
||||
public static final String CONDITIONAL_ON_PROPERTY = "org.springframework.boot.autoconfigure.condition.ConditionalOnProperty";
|
||||
public static final String CONDITIONAL_ON_RESOURCE = "org.springframework.boot.autoconfigure.condition.ConditionalOnResource";
|
||||
public static final String CONDITIONAL_ON_CLASS = "org.springframework.boot.autoconfigure.condition.ConditionalOnClass";
|
||||
public static final String CONDITIONAL_ON_MISSING_CLASS = "org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass";
|
||||
public static final String CONDITIONAL_ON_CLOUD_PLATFORM = "org.springframework.boot.autoconfigure.condition.ConditionalOnCloudPlatform";
|
||||
public static final String CONDITIONAL_ON_WEB_APPLICATION = "org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication";
|
||||
public static final String CONDITIONAL_ON_NOT_WEB_APPLICATION = "org.springframework.boot.autoconfigure.condition.ConditionalOnNotWebApplication";
|
||||
public static final String CONDITIONAL_ON_ENABLED_INFO_CONTRIBUTOR = "org.springframework.boot.actuate.autoconfigure.ConditionalOnEnabledInfoContributor";
|
||||
public static final String CONDITIONAL_ON_ENABLED_RESOURCE_CHAIN = "org.springframework.boot.autoconfigure.web.ConditionalOnEnabledResourceChain";
|
||||
public static final String CONDITIONAL_ON_ENABLED_ENDPOINT = "org.springframework.boot.actuate.condition.ConditionalOnEnabledEndpoint";
|
||||
public static final String CONDITIONAL_ON_ENABLED_HEALTH_INDICATOR = "org.springframework.boot.actuate.autoconfigure.ConditionalOnEnabledHealthIndicator";
|
||||
public static final String CONDITIONAL_ON_EXPRESSION = "org.springframework.boot.autoconfigure.condition.ConditionalOnExpression";
|
||||
public static final String CONDITIONAL_ON_JAVA = "org.springframework.boot.autoconfigure.condition.ConditionalOnJava";
|
||||
public static final String CONDITIONAL_ON_JNDI = "org.springframework.boot.autoconfigure.condition.ConditionalOnJndi";
|
||||
public static final String CONDITIONAL_ON_SINGLE_CANDIDATE = "org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 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;
|
||||
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.Settings;
|
||||
import org.springframework.ide.vscode.commons.util.Log;
|
||||
|
||||
/**
|
||||
* Boot-Java LS settings
|
||||
*
|
||||
* @author Alex Boyko
|
||||
*
|
||||
*/
|
||||
public class BootJavaConfig {
|
||||
|
||||
private Settings settings = new Settings(null);
|
||||
|
||||
public boolean isBootHintsEnabled() {
|
||||
Boolean enabled = (Boolean) settings.getProperty("boot-java", "boot-hints", "on");
|
||||
return enabled == null || enabled.booleanValue();
|
||||
}
|
||||
|
||||
public void handleConfigurationChange(Settings newConfig) {
|
||||
Log.info("Settings received: "+newConfig);
|
||||
this.settings = newConfig;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,340 @@
|
||||
/*******************************************************************************
|
||||
* 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
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.lsp4j.CompletionItemKind;
|
||||
import org.eclipse.lsp4j.InitializeParams;
|
||||
import org.springframework.ide.vscode.boot.BootLanguageServerParams;
|
||||
import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchyAwareLookup;
|
||||
import org.springframework.ide.vscode.boot.java.autowired.AutowiredHoverProvider;
|
||||
import org.springframework.ide.vscode.boot.java.beans.BeansSymbolProvider;
|
||||
import org.springframework.ide.vscode.boot.java.beans.ComponentSymbolProvider;
|
||||
import org.springframework.ide.vscode.boot.java.conditionals.ConditionalsLiveHoverProvider;
|
||||
import org.springframework.ide.vscode.boot.java.data.DataRepositorySymbolProvider;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.BootJavaCodeLensEngine;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.BootJavaCompletionEngine;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.BootJavaDocumentSymbolHandler;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.BootJavaHoverProvider;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.BootJavaReferencesHandler;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.BootJavaWorkspaceSymbolHandler;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.CompletionProvider;
|
||||
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.livehover.ActiveProfilesProvider;
|
||||
import org.springframework.ide.vscode.boot.java.livehover.BeanInjectedIntoHoverProvider;
|
||||
import org.springframework.ide.vscode.boot.java.livehover.ComponentInjectionsHoverProvider;
|
||||
import org.springframework.ide.vscode.boot.java.requestmapping.LiveAppURLSymbolProvider;
|
||||
import org.springframework.ide.vscode.boot.java.requestmapping.RequestMappingHoverProvider;
|
||||
import org.springframework.ide.vscode.boot.java.requestmapping.RequestMappingSymbolProvider;
|
||||
import org.springframework.ide.vscode.boot.java.requestmapping.WebfluxRouterSymbolProvider;
|
||||
import org.springframework.ide.vscode.boot.java.scope.ScopeCompletionProcessor;
|
||||
import org.springframework.ide.vscode.boot.java.snippets.JavaSnippet;
|
||||
import org.springframework.ide.vscode.boot.java.snippets.JavaSnippetContext;
|
||||
import org.springframework.ide.vscode.boot.java.snippets.JavaSnippetManager;
|
||||
import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache;
|
||||
import org.springframework.ide.vscode.boot.java.utils.SpringIndexer;
|
||||
import org.springframework.ide.vscode.boot.java.utils.SpringLiveHoverWatchdog;
|
||||
import org.springframework.ide.vscode.boot.java.value.ValueCompletionProcessor;
|
||||
import org.springframework.ide.vscode.boot.java.value.ValueHoverProvider;
|
||||
import org.springframework.ide.vscode.boot.java.value.ValuePropertyReferencesProvider;
|
||||
import org.springframework.ide.vscode.boot.metadata.SpringPropertyIndexProvider;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionEngine;
|
||||
import org.springframework.ide.vscode.commons.languageserver.composable.LanguageServerComponents;
|
||||
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.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;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleWorkspaceService;
|
||||
import org.springframework.ide.vscode.commons.util.text.LanguageId;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
/**
|
||||
* Language Server for Spring Boot Application Properties files
|
||||
*
|
||||
* @author Martin Lippert
|
||||
*/
|
||||
public class BootJavaLanguageServerComponents implements LanguageServerComponents {
|
||||
|
||||
private static final Set<LanguageId> LANGUAGES = ImmutableSet.of(LanguageId.JAVA);
|
||||
|
||||
private final SimpleLanguageServer server;
|
||||
private final BootLanguageServerParams serverParams;
|
||||
private final SpringIndexer indexer;
|
||||
private final SpringPropertyIndexProvider propertyIndexProvider;
|
||||
private final SpringLiveHoverWatchdog liveHoverWatchdog;
|
||||
private final ProjectObserver projectObserver;
|
||||
private final BootJavaConfig config;
|
||||
private final CompilationUnitCache cuCache;
|
||||
|
||||
private JavaProjectFinder projectFinder;
|
||||
private BootJavaHoverProvider hoverProvider;
|
||||
|
||||
public BootJavaLanguageServerComponents(SimpleLanguageServer server, LSFactory<BootLanguageServerParams> _params) {
|
||||
this.server = server;
|
||||
this.serverParams = _params.create(server);
|
||||
|
||||
this.config = new BootJavaConfig();
|
||||
|
||||
projectFinder = serverParams.projectFinder;
|
||||
projectObserver = serverParams.projectObserver;
|
||||
cuCache = new CompilationUnitCache(projectFinder, server.getTextDocumentService(), projectObserver);
|
||||
|
||||
propertyIndexProvider = serverParams.indexProvider;
|
||||
|
||||
SimpleWorkspaceService workspaceService = server.getWorkspaceService();
|
||||
SimpleTextDocumentService documents = server.getTextDocumentService();
|
||||
|
||||
ReferencesHandler referencesHandler = createReferenceHandler(server, projectFinder);
|
||||
documents.onReferences(referencesHandler);
|
||||
|
||||
indexer = createAnnotationIndexer(server, serverParams);
|
||||
documents.onDidSave(params -> {
|
||||
String docURI = params.getDocument().getId().getUri();
|
||||
String content = params.getDocument().get();
|
||||
indexer.updateDocument(docURI, content);
|
||||
});
|
||||
|
||||
documents.onDocumentSymbol(new BootJavaDocumentSymbolHandler(indexer));
|
||||
workspaceService.onWorkspaceSymbol(new BootJavaWorkspaceSymbolHandler(indexer,
|
||||
new LiveAppURLSymbolProvider(serverParams.runningAppProvider)));
|
||||
|
||||
// BootJavaCodeLensEngine codeLensHandler = createCodeLensEngine(server, projectFinder);
|
||||
// documents.onCodeLens(codeLensHandler::createCodeLenses);
|
||||
// documents.onCodeLensResolve(codeLensHandler::resolveCodeLens);
|
||||
|
||||
hoverProvider = createHoverHandler(projectFinder, serverParams.runningAppProvider);
|
||||
liveHoverWatchdog = new SpringLiveHoverWatchdog(server, hoverProvider, serverParams.runningAppProvider,
|
||||
projectFinder, projectObserver, serverParams.watchDogInterval);
|
||||
documents.onDidChangeContent(params -> {
|
||||
TextDocument doc = params.getDocument();
|
||||
// if (testHightlighter != null) {
|
||||
// getClient().highlight(new HighlightParams(params.getDocument().getId(), testHightlighter.apply(doc)));
|
||||
// } else {
|
||||
liveHoverWatchdog.watchDocument(doc.getUri());
|
||||
liveHoverWatchdog.update(doc.getUri(), null);
|
||||
// }
|
||||
});
|
||||
|
||||
documents.onDidClose(doc -> {
|
||||
// if (testHightlighter != null) {
|
||||
// getClient().highlight(new HighlightParams(doc.getId(), testHightlighter.apply(doc)));
|
||||
// } else {
|
||||
liveHoverWatchdog.unwatchDocument(doc.getUri());
|
||||
// }
|
||||
});
|
||||
|
||||
workspaceService.onDidChangeConfiguraton(settings -> {
|
||||
config.handleConfigurationChange(settings);
|
||||
if (config.isBootHintsEnabled()) {
|
||||
liveHoverWatchdog.enableHighlights();
|
||||
} else {
|
||||
liveHoverWatchdog.disableHighlights();
|
||||
}
|
||||
});
|
||||
|
||||
server.onInitialize(this::initialize);
|
||||
server.onInitialized(this::initialized);
|
||||
server.onShutdown(this::shutdown);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ICompletionEngine getCompletionEngine() {
|
||||
return createCompletionEngine(projectFinder, propertyIndexProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HoverHandler getHoverProvider() {
|
||||
return hoverProvider;
|
||||
}
|
||||
|
||||
private void initialize(InitializeParams params) {
|
||||
this.indexer.initialize(server.getWorkspaceRoots());
|
||||
}
|
||||
|
||||
private void initialized() {
|
||||
this.indexer.serverInitialized();
|
||||
// TODO: due to a missing message from lsp4e this "initialized" is not called in
|
||||
// the LSP4E case
|
||||
// if this gets fixed, the code should move here (from "initialize" above)
|
||||
|
||||
// this.indexer.initialize(this.getWorkspaceRoot());
|
||||
// this.liveHoverWatchdog.start();
|
||||
}
|
||||
|
||||
private void shutdown() {
|
||||
this.liveHoverWatchdog.shutdown();
|
||||
this.indexer.shutdown();
|
||||
this.cuCache.dispose();
|
||||
}
|
||||
|
||||
protected ICompletionEngine createCompletionEngine(JavaProjectFinder javaProjectFinder,
|
||||
SpringPropertyIndexProvider indexProvider) {
|
||||
Map<String, CompletionProvider> providers = new HashMap<>();
|
||||
providers.put(org.springframework.ide.vscode.boot.java.scope.Constants.SPRING_SCOPE,
|
||||
new ScopeCompletionProcessor());
|
||||
providers.put(org.springframework.ide.vscode.boot.java.value.Constants.SPRING_VALUE,
|
||||
new ValueCompletionProcessor(indexProvider));
|
||||
|
||||
JavaSnippetManager snippetManager = new JavaSnippetManager(server::createSnippetBuilder);
|
||||
snippetManager.add(
|
||||
new JavaSnippet("RequestMapping method", JavaSnippetContext.BOOT_MEMBERS, CompletionItemKind.Method,
|
||||
ImmutableList.of("org.springframework.web.bind.annotation.RequestMapping",
|
||||
"org.springframework.web.bind.annotation.RequestMethod",
|
||||
"org.springframework.web.bind.annotation.RequestParam"),
|
||||
"@RequestMapping(value=\"${path}\", method=RequestMethod.${GET})\n"
|
||||
+ "public ${SomeData} ${requestMethodName}(@RequestParam ${String} ${param}) {\n"
|
||||
+ " return new ${SomeData}(${cursor});\n" + "}\n"));
|
||||
snippetManager
|
||||
.add(new JavaSnippet("GetMapping method", JavaSnippetContext.BOOT_MEMBERS, CompletionItemKind.Method,
|
||||
ImmutableList.of("org.springframework.web.bind.annotation.GetMapping",
|
||||
"org.springframework.web.bind.annotation.RequestParam"),
|
||||
"@GetMapping(value=\"${path}\")\n"
|
||||
+ "public ${SomeData} ${getMethodName}(@RequestParam ${String} ${param}) {\n"
|
||||
+ " return new ${SomeData}(${cursor});\n" + "}\n"));
|
||||
snippetManager.add(new JavaSnippet("PostMapping method", JavaSnippetContext.BOOT_MEMBERS,
|
||||
CompletionItemKind.Method,
|
||||
ImmutableList.of("org.springframework.web.bind.annotation.PostMapping",
|
||||
"org.springframework.web.bind.annotation.RequestBody"),
|
||||
"@PostMapping(value=\"${path}\")\n"
|
||||
+ "public ${SomeEnityData} ${postMethodName}(@RequestBody ${SomeEnityData} ${entity}) {\n"
|
||||
+ " //TODO: process POST request\n" + " ${cursor}\n" + " return ${entity};\n" + "}\n"));
|
||||
snippetManager.add(new JavaSnippet("PutMapping method", JavaSnippetContext.BOOT_MEMBERS,
|
||||
CompletionItemKind.Method,
|
||||
ImmutableList.of("org.springframework.web.bind.annotation.PutMapping",
|
||||
"org.springframework.web.bind.annotation.RequestBody",
|
||||
"org.springframework.web.bind.annotation.PathVariable"),
|
||||
"@PutMapping(value=\"${path}/{${id}}\")\n"
|
||||
+ "public ${SomeEnityData} ${putMethodName}(@PathVariable ${pvt:String} ${id}, @RequestBody ${SomeEnityData} ${entity}) {\n"
|
||||
+ " //TODO: process PUT request\n" + " ${cursor}\n" + " return ${entity};\n" + "}"));
|
||||
return new BootJavaCompletionEngine(this, providers, snippetManager);
|
||||
}
|
||||
|
||||
protected BootJavaHoverProvider createHoverHandler(JavaProjectFinder javaProjectFinder,
|
||||
RunningAppProvider runningAppProvider) {
|
||||
AnnotationHierarchyAwareLookup<HoverProvider> providers = new AnnotationHierarchyAwareLookup<>();
|
||||
|
||||
providers.put(org.springframework.ide.vscode.boot.java.value.Constants.SPRING_VALUE, new ValueHoverProvider());
|
||||
|
||||
providers.put(Annotations.SPRING_REQUEST_MAPPING, new RequestMappingHoverProvider());
|
||||
providers.put(Annotations.SPRING_GET_MAPPING, new RequestMappingHoverProvider());
|
||||
providers.put(Annotations.SPRING_POST_MAPPING, new RequestMappingHoverProvider());
|
||||
providers.put(Annotations.SPRING_PUT_MAPPING, new RequestMappingHoverProvider());
|
||||
providers.put(Annotations.SPRING_DELETE_MAPPING, new RequestMappingHoverProvider());
|
||||
providers.put(Annotations.SPRING_PATCH_MAPPING, new RequestMappingHoverProvider());
|
||||
providers.put(Annotations.PROFILE, new ActiveProfilesProvider());
|
||||
|
||||
providers.put(Annotations.AUTOWIRED, new AutowiredHoverProvider(this));
|
||||
providers.put(Annotations.COMPONENT, new ComponentInjectionsHoverProvider(this));
|
||||
providers.put(Annotations.BEAN, new BeanInjectedIntoHoverProvider(this));
|
||||
|
||||
providers.put(Annotations.CONDITIONAL, new ConditionalsLiveHoverProvider());
|
||||
providers.put(Annotations.CONDITIONAL_ON_BEAN, new ConditionalsLiveHoverProvider());
|
||||
providers.put(Annotations.CONDITIONAL_ON_MISSING_BEAN, new ConditionalsLiveHoverProvider());
|
||||
providers.put(Annotations.CONDITIONAL_ON_PROPERTY, new ConditionalsLiveHoverProvider());
|
||||
providers.put(Annotations.CONDITIONAL_ON_RESOURCE, new ConditionalsLiveHoverProvider());
|
||||
providers.put(Annotations.CONDITIONAL_ON_CLASS, new ConditionalsLiveHoverProvider());
|
||||
providers.put(Annotations.CONDITIONAL_ON_MISSING_CLASS, new ConditionalsLiveHoverProvider());
|
||||
providers.put(Annotations.CONDITIONAL_ON_CLOUD_PLATFORM, new ConditionalsLiveHoverProvider());
|
||||
providers.put(Annotations.CONDITIONAL_ON_WEB_APPLICATION, new ConditionalsLiveHoverProvider());
|
||||
providers.put(Annotations.CONDITIONAL_ON_NOT_WEB_APPLICATION, new ConditionalsLiveHoverProvider());
|
||||
providers.put(Annotations.CONDITIONAL_ON_ENABLED_INFO_CONTRIBUTOR, new ConditionalsLiveHoverProvider());
|
||||
providers.put(Annotations.CONDITIONAL_ON_ENABLED_RESOURCE_CHAIN, new ConditionalsLiveHoverProvider());
|
||||
providers.put(Annotations.CONDITIONAL_ON_ENABLED_ENDPOINT, new ConditionalsLiveHoverProvider());
|
||||
providers.put(Annotations.CONDITIONAL_ON_ENABLED_HEALTH_INDICATOR, new ConditionalsLiveHoverProvider());
|
||||
providers.put(Annotations.CONDITIONAL_ON_EXPRESSION, new ConditionalsLiveHoverProvider());
|
||||
providers.put(Annotations.CONDITIONAL_ON_JAVA, new ConditionalsLiveHoverProvider());
|
||||
providers.put(Annotations.CONDITIONAL_ON_JNDI, new ConditionalsLiveHoverProvider());
|
||||
providers.put(Annotations.CONDITIONAL_ON_SINGLE_CANDIDATE, new ConditionalsLiveHoverProvider());
|
||||
|
||||
return new BootJavaHoverProvider(this, javaProjectFinder, providers, runningAppProvider);
|
||||
}
|
||||
|
||||
protected SpringIndexer createAnnotationIndexer(SimpleLanguageServer server, BootLanguageServerParams params) {
|
||||
AnnotationHierarchyAwareLookup<SymbolProvider> providers = new AnnotationHierarchyAwareLookup<>();
|
||||
providers.put(Annotations.SPRING_REQUEST_MAPPING, new RequestMappingSymbolProvider());
|
||||
providers.put(Annotations.SPRING_GET_MAPPING, new RequestMappingSymbolProvider());
|
||||
providers.put(Annotations.SPRING_POST_MAPPING, new RequestMappingSymbolProvider());
|
||||
providers.put(Annotations.SPRING_PUT_MAPPING, new RequestMappingSymbolProvider());
|
||||
providers.put(Annotations.SPRING_DELETE_MAPPING, new RequestMappingSymbolProvider());
|
||||
providers.put(Annotations.SPRING_PATCH_MAPPING, new RequestMappingSymbolProvider());
|
||||
|
||||
providers.put(Annotations.BEAN, new BeansSymbolProvider());
|
||||
providers.put(Annotations.COMPONENT, new ComponentSymbolProvider());
|
||||
|
||||
providers.put(Annotations.REPOSITORY, new DataRepositorySymbolProvider());
|
||||
providers.put("", new WebfluxRouterSymbolProvider());
|
||||
|
||||
return new SpringIndexer(server, params, providers);
|
||||
}
|
||||
|
||||
protected ReferencesHandler createReferenceHandler(SimpleLanguageServer server, JavaProjectFinder projectFinder) {
|
||||
Map<String, ReferenceProvider> providers = new HashMap<>();
|
||||
providers.put(org.springframework.ide.vscode.boot.java.value.Constants.SPRING_VALUE,
|
||||
new ValuePropertyReferencesProvider(server));
|
||||
|
||||
return new BootJavaReferencesHandler(server, projectFinder, providers);
|
||||
}
|
||||
|
||||
protected BootJavaCodeLensEngine createCodeLensEngine(SimpleLanguageServer server,
|
||||
JavaProjectFinder projectFinder) {
|
||||
return new BootJavaCodeLensEngine(server, projectFinder);
|
||||
}
|
||||
|
||||
public ProjectObserver getProjectObserver() {
|
||||
return projectObserver;
|
||||
}
|
||||
|
||||
public JavaProjectFinder getProjectFinder() {
|
||||
return projectFinder;
|
||||
}
|
||||
|
||||
public SpringIndexer getSpringIndexer() {
|
||||
return indexer;
|
||||
}
|
||||
|
||||
public SpringPropertyIndexProvider getSpringPropertyIndexProvider() {
|
||||
return propertyIndexProvider;
|
||||
}
|
||||
|
||||
public BootJavaConfig getConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
public CompilationUnitCache getCompilationUnitCache() {
|
||||
return cuCache;
|
||||
}
|
||||
|
||||
public SimpleTextDocumentService getTextDocumentService() {
|
||||
return server.getTextDocumentService();
|
||||
}
|
||||
|
||||
public BootLanguageServerParams getServerParams() {
|
||||
return this.serverParams;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<LanguageId> getInterestingLanguages() {
|
||||
return LANGUAGES;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 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;
|
||||
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver;
|
||||
|
||||
public class ProjectServices {
|
||||
|
||||
public final JavaProjectFinder finder;
|
||||
public final ProjectObserver observer;
|
||||
|
||||
public ProjectServices(JavaProjectFinder finder, ProjectObserver observer) {
|
||||
super();
|
||||
this.finder = finder;
|
||||
this.observer = observer;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 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;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.eclipse.lsp4j.Range;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
|
||||
import org.springframework.ide.vscode.commons.util.BadLocationException;
|
||||
import org.springframework.ide.vscode.commons.util.Log;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
/**
|
||||
* Sample implementation of a 'highlighter' that can be used with {@link SimpleLanguageServer}.highlightWith.
|
||||
* <p>
|
||||
* Finds every occurence of a given word and highlights it.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class WordHighlighter implements Function<TextDocument, List<Range>> {
|
||||
|
||||
private final String word;
|
||||
|
||||
public WordHighlighter(String word) {
|
||||
super();
|
||||
this.word = word;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Range> apply(TextDocument doc) {
|
||||
String text = doc.get();
|
||||
int wordStart = text.indexOf(word);
|
||||
ImmutableList.Builder<Range> highlights = ImmutableList.builder();
|
||||
while (wordStart>=0) {
|
||||
try {
|
||||
highlights.add(doc.toRange(wordStart, word.length()));
|
||||
} catch (BadLocationException e) {
|
||||
Log.log(e);
|
||||
}
|
||||
wordStart = text.indexOf(word, wordStart+word.length());
|
||||
}
|
||||
return highlights.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017, 2018 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.annotations;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.core.dom.Annotation;
|
||||
import org.eclipse.jdt.core.dom.IAnnotationBinding;
|
||||
import org.eclipse.jdt.core.dom.ITypeBinding;
|
||||
import org.springframework.ide.vscode.commons.util.CollectorUtil;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
/**
|
||||
* Utility class for working with annotation and discovering / understanding their
|
||||
* 'inheritance' structure.
|
||||
* <p>
|
||||
* Provides methods to ask questions about inheritance between annotations.
|
||||
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public abstract class AnnotationHierarchies {
|
||||
|
||||
private AnnotationHierarchies() {
|
||||
}
|
||||
|
||||
protected static boolean ignoreAnnotation(String fqname) {
|
||||
return fqname.startsWith("java."); //mostly intended to capture java.lang.annotation.* types. But really it should be
|
||||
//safe to ignore any type defined by the JRE since it can't possibly be inheriting from a spring annotation.
|
||||
};
|
||||
|
||||
public static Collection<ITypeBinding> getDirectSuperAnnotations(ITypeBinding typeBinding) {
|
||||
IAnnotationBinding[] annotations = typeBinding.getAnnotations();
|
||||
if (annotations!=null && annotations.length!=0) {
|
||||
ImmutableList.Builder<ITypeBinding> superAnnotations = ImmutableList.builder();
|
||||
for (IAnnotationBinding ab : annotations) {
|
||||
ITypeBinding sa = ab.getAnnotationType();
|
||||
if (sa!=null) {
|
||||
if (!ignoreAnnotation(sa.getQualifiedName())) {
|
||||
superAnnotations.add(sa);
|
||||
}
|
||||
}
|
||||
}
|
||||
return superAnnotations.build();
|
||||
}
|
||||
return ImmutableList.of();
|
||||
}
|
||||
|
||||
public static Set<String> getTransitiveSuperAnnotations(ITypeBinding typeBinding) {
|
||||
Set<String> seen = new HashSet<>();
|
||||
findTransitiveSupers(typeBinding, seen).collect(Collectors.toList());
|
||||
return seen;
|
||||
}
|
||||
|
||||
public static Stream<ITypeBinding> findTransitiveSupers(ITypeBinding typeBinding, Set<String> seen) {
|
||||
String qname = typeBinding.getQualifiedName();
|
||||
if (seen.add(qname)) {
|
||||
return Stream.concat(
|
||||
Stream.of(typeBinding),
|
||||
getDirectSuperAnnotations(typeBinding).stream().flatMap(superBinding ->
|
||||
findTransitiveSupers(superBinding, seen)
|
||||
)
|
||||
);
|
||||
}
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
public static boolean isSubtypeOf(Annotation annotation, String fqAnnotationTypeName) {
|
||||
ITypeBinding annotationType = annotation.resolveTypeBinding();
|
||||
if (annotationType!=null) {
|
||||
return findTransitiveSupers(annotationType, new HashSet<>())
|
||||
.anyMatch(superType -> superType.getQualifiedName().equals(fqAnnotationTypeName));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static Collection<ITypeBinding> getMetaAnnotations(ITypeBinding actualAnnotation, Predicate<String> isKeyAnnotationName) {
|
||||
Stream<ITypeBinding> allSupers = findTransitiveSupers(actualAnnotation, new HashSet<>())
|
||||
.skip(1); //Don't include 'actualAnnotation' itself.
|
||||
return allSupers
|
||||
.filter(candidate -> isMetaAnnotation(candidate, isKeyAnnotationName))
|
||||
.collect(CollectorUtil.toImmutableList());
|
||||
}
|
||||
|
||||
private static boolean isMetaAnnotation(ITypeBinding candidate, Predicate<String> isKeyAnnotationName) {
|
||||
return findTransitiveSupers(candidate, new HashSet<>())
|
||||
.anyMatch(sa -> isKeyAnnotationName.test(sa.getQualifiedName()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017, 2018 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.annotations;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ITypeBinding;
|
||||
import org.springframework.ide.vscode.commons.util.Assert;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
/**
|
||||
* A Map-like utilty that allows putting and getting values associated with
|
||||
* annotation types.
|
||||
* <p>
|
||||
* The lookup is 'hierarchy aware' which means that is able to associate values
|
||||
* with a given type and all its subtypes all at once.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class AnnotationHierarchyAwareLookup<T> {
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private static class Binding<T> {
|
||||
T value;
|
||||
boolean isOverriding;
|
||||
public Binding(T value, boolean isOverriding) {
|
||||
this.isOverriding = isOverriding;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Associates fq anotation type name to a Binding.
|
||||
*/
|
||||
private Map<String, Binding<T>> bindings = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Associates a value with a given annotation type (and all its subtypes implicitly).
|
||||
*
|
||||
* @param fqName Fully qualified type name for the annotation.
|
||||
* @param overrideSuperTypes Determines whether the binding has 'override' behavior. Override behavior
|
||||
* means that this binding stops the search for additional bindings associated
|
||||
* with a super type. If override behavior is disabled the search will continue
|
||||
* so that values associated with supertypes will also be found and returned
|
||||
* in addition to the more specific binding.
|
||||
* @param value
|
||||
*/
|
||||
public void put(String fqName, boolean overrideSuperTypes, T value) {
|
||||
Assert.isLegal(bindings.get(fqName)==null, "Multiple bindings to the same fqName are not supported");
|
||||
bindings.put(fqName, new Binding<>(value, overrideSuperTypes));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all associations applicable to a given annotationType. Note that a single 'put' binding for
|
||||
* a supertype can result in mutiple applicable associations for single annotation type because
|
||||
* a single put actually creates associations for a type and all its subtypes implicitly.
|
||||
* <p>
|
||||
* So, for example:
|
||||
* <code>
|
||||
* AnnotationHierarchyAwareLookup registry = new AnnotationHierarchyAwareLookup<String>();
|
||||
* registry.put("spring.annotation.Component", "ComponentProvider");
|
||||
* </code>
|
||||
* Now, assuming that RestController is a sub annotation of Controller which is a subtype of Component,
|
||||
* then if we call `get(...typeBinding of RestController...`) we will get back a collection of 3 elements:
|
||||
* ("spring.annotation.Component", "ComponentProvider"),
|
||||
* ("spring.annotation.Controller", "ComponentProvider")
|
||||
* ("spring.annotation.RestController", "ComponentProvider")
|
||||
* <p>
|
||||
* This reflects the fact that a binding for ComponentProvider also can function as a provider for Controller
|
||||
* and RestController; and that RestController in turn can be interpreted as a specialized Component or Controller.
|
||||
* Therefore we would expect in a situation where a concrete annotation of type RestController is found in the AST,
|
||||
* a symbol provider for Components should be asked to produce symbols for Component, Controller and RestController,
|
||||
* so should result in 3 separate calls to the symbols provider.
|
||||
*/
|
||||
public Collection<T> get(ITypeBinding annotationType) {
|
||||
ImmutableList.Builder<T> found = ImmutableList.builder();
|
||||
findElements(annotationType, new LinkedHashSet<>(), found::add);
|
||||
return found.build();
|
||||
}
|
||||
|
||||
public Collection<T> getAll() {
|
||||
ImmutableList.Builder<T> found = ImmutableList.builder();
|
||||
Collection<Binding<T>> values = bindings.values();
|
||||
values.forEach(binding -> found.add(binding.value));
|
||||
return found.build();
|
||||
}
|
||||
|
||||
private void findElements(ITypeBinding typeBinding, HashSet<String> seen, Consumer<T> requestor) {
|
||||
String qname = typeBinding.getQualifiedName();
|
||||
if (seen.add(qname)) {
|
||||
Binding<T> binding = bindings.get(qname);
|
||||
boolean isOverriding = false;
|
||||
if (binding!=null) {
|
||||
requestor.accept(binding.value);
|
||||
isOverriding = binding.isOverriding;
|
||||
}
|
||||
if (!isOverriding) {
|
||||
for (ITypeBinding superAnnotation : AnnotationHierarchies.getDirectSuperAnnotations(typeBinding)) {
|
||||
findElements(superAnnotation, seen, requestor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void put(String annotationName, T value) {
|
||||
put(annotationName, true, value);
|
||||
}
|
||||
|
||||
public boolean containsKey(String fqName) {
|
||||
return bindings.containsKey(fqName);
|
||||
}
|
||||
|
||||
// private static int indent = 0;
|
||||
//
|
||||
// private static void debug(int indent, String msg) {
|
||||
// for (int i = 0; i < indent; i++) {
|
||||
// System.out.print(" ");
|
||||
// }
|
||||
// System.out.println(msg);
|
||||
// }
|
||||
// private static int debug_in(String msg) {
|
||||
// for (int i = 0; i < indent; i++) {
|
||||
// System.out.print(" ");
|
||||
// }
|
||||
// System.out.println(">> "+msg);
|
||||
// return indent ++;
|
||||
// }
|
||||
// private static void debug_out(String msg) {
|
||||
// indent --;
|
||||
// for (int i = 0; i < indent; i++) {
|
||||
// System.out.print(" ");
|
||||
// }
|
||||
// System.out.println("<< "+msg);
|
||||
// }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017, 2018 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.autowired;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.Annotation;
|
||||
import org.eclipse.jdt.core.dom.ITypeBinding;
|
||||
import org.eclipse.jdt.core.dom.TypeDeclaration;
|
||||
import org.eclipse.lsp4j.Hover;
|
||||
import org.eclipse.lsp4j.Range;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
||||
import org.springframework.ide.vscode.boot.java.Annotations;
|
||||
import org.springframework.ide.vscode.boot.java.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.livehover.ComponentInjectionsHoverProvider;
|
||||
import org.springframework.ide.vscode.boot.java.livehover.LiveHoverUtils;
|
||||
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;
|
||||
import org.springframework.ide.vscode.commons.boot.app.cli.livebean.LiveBeansModel;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
|
||||
import org.springframework.ide.vscode.commons.util.Log;
|
||||
import org.springframework.ide.vscode.commons.util.StringUtil;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
*/
|
||||
public class AutowiredHoverProvider implements HoverProvider {
|
||||
|
||||
private BootJavaLanguageServerComponents server;
|
||||
|
||||
public AutowiredHoverProvider(BootJavaLanguageServerComponents server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Range> getLiveHoverHints(Annotation annotation, TextDocument doc, SpringBootApp[] runningApps) {
|
||||
try {
|
||||
LiveBean definedBean = getDefinedBean(annotation);
|
||||
if (definedBean != null) {
|
||||
for (SpringBootApp app : runningApps) {
|
||||
try {
|
||||
List<LiveBean> relevantBeans = LiveHoverUtils.findRelevantBeans(app, definedBean).collect(Collectors.toList());
|
||||
|
||||
if (!relevantBeans.isEmpty()) {
|
||||
for (LiveBean bean : relevantBeans) {
|
||||
String[] dependencies = bean.getDependencies();
|
||||
if (dependencies != null && dependencies.length > 0) {
|
||||
Range hoverRange = doc.toRange(annotation.getStartPosition(), annotation.getLength());
|
||||
return ImmutableList.of(hoverRange);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
Log.log(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
Log.log(e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Hover provideHover(ASTNode node, Annotation annotation, ITypeBinding type, int offset,
|
||||
TextDocument doc, IJavaProject project, SpringBootApp[] runningApps) {
|
||||
if (runningApps.length > 0) {
|
||||
|
||||
StringBuilder hover = new StringBuilder();
|
||||
|
||||
LiveBean definedBean = getDefinedBean(annotation);
|
||||
if (definedBean != null) {
|
||||
|
||||
hover.append("**Injection report for " + LiveHoverUtils.showBean(definedBean) + "**\n\n");
|
||||
|
||||
boolean hasInterestingApp = false;
|
||||
boolean hasAutowiring = false;
|
||||
|
||||
for (SpringBootApp app : runningApps) {
|
||||
LiveBeansModel beans = app.getBeans();
|
||||
List<LiveBean> relevantBeans = LiveHoverUtils.findRelevantBeans(app, definedBean).collect(Collectors.toList());
|
||||
|
||||
if (!relevantBeans.isEmpty()) {
|
||||
if (!hasInterestingApp) {
|
||||
hasInterestingApp = true;
|
||||
} else {
|
||||
hover.append("\n\n");
|
||||
}
|
||||
hover.append(LiveHoverUtils.niceAppName(app) + ":");
|
||||
|
||||
for (LiveBean bean : relevantBeans) {
|
||||
hover.append("\n\n");
|
||||
hasAutowiring |= addAutomaticallyWired(hover, annotation, beans, bean, project);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hasInterestingApp && hasAutowiring) {
|
||||
return new Hover(ImmutableList.of(Either.forLeft(hover.toString())));
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private LiveBean getDefinedBean(Annotation autowiredAnnotation) {
|
||||
TypeDeclaration declaringType = ASTUtils.findDeclaringType(autowiredAnnotation);
|
||||
if (declaringType != null) {
|
||||
for (Annotation annotation : ASTUtils.getAnnotations(declaringType)) {
|
||||
if (AnnotationHierarchies.isSubtypeOf(annotation, Annotations.COMPONENT)) {
|
||||
return ComponentInjectionsHoverProvider.getDefinedBeanForComponent(annotation);
|
||||
}
|
||||
}
|
||||
//TODO: handler below is an attempt to do something that may work in many cases, but is probably
|
||||
// missing logics for special cases where annotation attributes on the declaring type matter.
|
||||
ITypeBinding beanType = declaringType.resolveBinding();
|
||||
if (beanType!=null) {
|
||||
String beanTypeName = beanType.getName();
|
||||
if (StringUtil.hasText(beanTypeName)) {
|
||||
return LiveBean.builder()
|
||||
.id(Character.toLowerCase(beanTypeName.charAt(0)) + beanTypeName.substring(1))
|
||||
.type(beanTypeName)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean addAutomaticallyWired(StringBuilder hover, Annotation annotation, LiveBeansModel beans, LiveBean bean, IJavaProject project) {
|
||||
boolean result = false;
|
||||
String[] dependencies = bean.getDependencies();
|
||||
|
||||
if (dependencies != null && dependencies.length > 0) {
|
||||
result = true;
|
||||
hover.append(LiveHoverUtils.showBean(bean) + " got autowired with:\n\n");
|
||||
|
||||
boolean firstDependency = true;
|
||||
for (String injectedBean : dependencies) {
|
||||
if (!firstDependency) {
|
||||
hover.append("\n");
|
||||
}
|
||||
List<LiveBean> dependencyBeans = beans.getBeansOfName(injectedBean);
|
||||
for (LiveBean dependencyBean : dependencyBeans) {
|
||||
hover.append("- " + LiveHoverUtils.showBeanWithResource(server, dependencyBean, " ", project));
|
||||
}
|
||||
firstDependency = false;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Hover provideHover(ASTNode node, TypeDeclaration typeDeclaration, ITypeBinding type, int offset,
|
||||
TextDocument doc, IJavaProject project, SpringBootApp[] runningApps) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Range> getLiveHoverHints(TypeDeclaration typeDeclaration, TextDocument doc,
|
||||
SpringBootApp[] runningApps) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017, 2018 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.beans;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.Annotation;
|
||||
import org.eclipse.jdt.core.dom.ITypeBinding;
|
||||
import org.eclipse.jdt.core.dom.MethodDeclaration;
|
||||
import org.eclipse.jdt.core.dom.ParameterizedType;
|
||||
import org.eclipse.jdt.core.dom.StringLiteral;
|
||||
import org.eclipse.jdt.core.dom.Type;
|
||||
import org.eclipse.jdt.core.dom.TypeDeclaration;
|
||||
import org.eclipse.lsp4j.Location;
|
||||
import org.eclipse.lsp4j.SymbolInformation;
|
||||
import org.eclipse.lsp4j.SymbolKind;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.SymbolProvider;
|
||||
import org.springframework.ide.vscode.boot.java.utils.ASTUtils;
|
||||
import org.springframework.ide.vscode.boot.java.utils.FunctionUtils;
|
||||
import org.springframework.ide.vscode.commons.util.BadLocationException;
|
||||
import org.springframework.ide.vscode.commons.util.Log;
|
||||
import org.springframework.ide.vscode.commons.util.text.DocumentRegion;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
import reactor.util.function.Tuple2;
|
||||
import reactor.util.function.Tuple3;
|
||||
import reactor.util.function.Tuples;
|
||||
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class BeansSymbolProvider implements SymbolProvider {
|
||||
|
||||
private static final String[] NAME_ATTRIBUTES = {"value", "name"};
|
||||
|
||||
@Override
|
||||
public Collection<SymbolInformation> getSymbols(Annotation node, ITypeBinding annotationType, Collection<ITypeBinding> metaAnnotations, TextDocument doc) {
|
||||
boolean isFunction = isFunctionBean(node);
|
||||
|
||||
ImmutableList.Builder<SymbolInformation> symbols = ImmutableList.builder();
|
||||
String beanType = getBeanType(node);
|
||||
for (Tuple2<String, DocumentRegion> nameAndRegion : getBeanNames(node, doc)) {
|
||||
try {
|
||||
symbols.add(new SymbolInformation(
|
||||
beanLabel(isFunction, nameAndRegion.getT1(), beanType, "@Bean"),
|
||||
SymbolKind.Interface,
|
||||
new Location(doc.getUri(), doc.toRange(nameAndRegion.getT2()))
|
||||
));
|
||||
} catch (BadLocationException e) {
|
||||
Log.log(e);
|
||||
}
|
||||
}
|
||||
return symbols.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<SymbolInformation> getSymbols(TypeDeclaration typeDeclaration, TextDocument doc) {
|
||||
// this checks function beans that are defined as implementations of Function interfaces
|
||||
Tuple3<String, String, DocumentRegion> functionBean = FunctionUtils.getFunctionBean(typeDeclaration, doc);
|
||||
if (functionBean != null) {
|
||||
try {
|
||||
SymbolInformation symbol = new SymbolInformation(
|
||||
beanLabel(true, functionBean.getT1(), functionBean.getT2(), null),
|
||||
SymbolKind.Interface,
|
||||
new Location(doc.getUri(), doc.toRange(functionBean.getT3())));
|
||||
return ImmutableList.of(symbol);
|
||||
} catch (BadLocationException e) {
|
||||
Log.log(e);
|
||||
}
|
||||
}
|
||||
return ImmutableList.of();
|
||||
}
|
||||
|
||||
protected Collection<Tuple2<String, DocumentRegion>> getBeanNames(Annotation node, TextDocument doc) {
|
||||
Collection<StringLiteral> beanNameNodes = getBeanNameLiterals(node);
|
||||
|
||||
if (beanNameNodes != null && !beanNameNodes.isEmpty()) {
|
||||
ImmutableList.Builder<Tuple2<String,DocumentRegion>> namesAndRegions = ImmutableList.builder();
|
||||
for (StringLiteral nameNode : beanNameNodes) {
|
||||
String name = ASTUtils.getLiteralValue(nameNode);
|
||||
namesAndRegions.add(Tuples.of(name, ASTUtils.stringRegion(doc, nameNode)));
|
||||
}
|
||||
return namesAndRegions.build();
|
||||
}
|
||||
else {
|
||||
ASTNode parent = node.getParent();
|
||||
if (parent instanceof MethodDeclaration) {
|
||||
MethodDeclaration method = (MethodDeclaration) parent;
|
||||
return ImmutableList.of(Tuples.of(
|
||||
method.getName().toString(),
|
||||
ASTUtils.nameRegion(doc, node)
|
||||
));
|
||||
}
|
||||
return ImmutableList.of();
|
||||
}
|
||||
}
|
||||
|
||||
protected String beanLabel(boolean isFunctionBean, String beanName, String beanType, String markerString) {
|
||||
StringBuilder symbolLabel = new StringBuilder();
|
||||
symbolLabel.append('@');
|
||||
symbolLabel.append(isFunctionBean ? '>' : '+');
|
||||
symbolLabel.append(' ');
|
||||
symbolLabel.append('\'');
|
||||
symbolLabel.append(beanName);
|
||||
symbolLabel.append('\'');
|
||||
|
||||
markerString = markerString != null && markerString.length() > 0 ? " (" + markerString + ") " : " ";
|
||||
symbolLabel.append(markerString);
|
||||
|
||||
symbolLabel.append(beanType);
|
||||
return symbolLabel.toString();
|
||||
}
|
||||
|
||||
protected Collection<StringLiteral> getBeanNameLiterals(Annotation node) {
|
||||
ImmutableList.Builder<StringLiteral> literals = ImmutableList.builder();
|
||||
for (String attrib : NAME_ATTRIBUTES) {
|
||||
ASTUtils.getAttribute(node, attrib).ifPresent((valueExp) -> {
|
||||
literals.addAll(ASTUtils.getExpressionValueAsListOfLiterals(valueExp));
|
||||
});
|
||||
}
|
||||
return literals.build();
|
||||
}
|
||||
|
||||
protected String getBeanType(Annotation node) {
|
||||
ASTNode parent = node.getParent();
|
||||
if (parent instanceof MethodDeclaration) {
|
||||
MethodDeclaration method = (MethodDeclaration) parent;
|
||||
String returnType = method.getReturnType2().resolveBinding().getName();
|
||||
return returnType;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isFunctionBean(Annotation node) {
|
||||
ASTNode parent = node.getParent();
|
||||
if (parent instanceof MethodDeclaration) {
|
||||
MethodDeclaration method = (MethodDeclaration) parent;
|
||||
|
||||
String returnType = null;
|
||||
if (method.getReturnType2().isParameterizedType()) {
|
||||
ParameterizedType paramType = (ParameterizedType) method.getReturnType2();
|
||||
Type type = paramType.getType();
|
||||
ITypeBinding typeBinding = type.resolveBinding();
|
||||
returnType = typeBinding.getBinaryName();
|
||||
}
|
||||
else {
|
||||
returnType = method.getReturnType2().resolveBinding().getQualifiedName();
|
||||
}
|
||||
|
||||
return FunctionUtils.FUNCTION_FUNCTION_TYPE.equals(returnType) || FunctionUtils.FUNCTION_CONSUMER_TYPE.equals(returnType)
|
||||
|| FunctionUtils.FUNCTION_SUPPLIER_TYPE.equals(returnType);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<SymbolInformation> getSymbols(MethodDeclaration methodDeclaration, TextDocument doc) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017, 2018 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.beans;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.Annotation;
|
||||
import org.eclipse.jdt.core.dom.ITypeBinding;
|
||||
import org.eclipse.jdt.core.dom.MethodDeclaration;
|
||||
import org.eclipse.jdt.core.dom.TypeDeclaration;
|
||||
import org.eclipse.lsp4j.Location;
|
||||
import org.eclipse.lsp4j.SymbolInformation;
|
||||
import org.eclipse.lsp4j.SymbolKind;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.SymbolProvider;
|
||||
import org.springframework.ide.vscode.commons.util.BadLocationException;
|
||||
import org.springframework.ide.vscode.commons.util.Log;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class ComponentSymbolProvider implements SymbolProvider {
|
||||
|
||||
@Override
|
||||
public Collection<SymbolInformation> getSymbols(Annotation node, ITypeBinding annotationType, Collection<ITypeBinding> metaAnnotations, TextDocument doc) {
|
||||
try {
|
||||
return ImmutableList.of(
|
||||
createSymbol(node, annotationType, metaAnnotations, doc)
|
||||
);
|
||||
}
|
||||
catch (Exception e) {
|
||||
Log.log(e);
|
||||
}
|
||||
return ImmutableList.of();
|
||||
}
|
||||
|
||||
protected SymbolInformation createSymbol(Annotation node, ITypeBinding annotationType, Collection<ITypeBinding> metaAnnotations, TextDocument doc) throws BadLocationException {
|
||||
String annotationTypeName = annotationType.getName();
|
||||
Collection<String> metaAnnotationNames = metaAnnotations.stream()
|
||||
.map(ITypeBinding::getName)
|
||||
.collect(Collectors.toList());
|
||||
String beanName = getBeanName(node);
|
||||
String beanType = getBeanType(node);
|
||||
|
||||
SymbolInformation symbol = new SymbolInformation(
|
||||
beanLabel("+", annotationTypeName, metaAnnotationNames, beanName, beanType), SymbolKind.Interface,
|
||||
new Location(doc.getUri(), doc.toRange(node.getStartPosition(), node.getLength())));
|
||||
return symbol;
|
||||
}
|
||||
|
||||
protected String beanLabel(String searchPrefix, String annotationTypeName, Collection<String> metaAnnotationNames, String beanName, String beanType) {
|
||||
StringBuilder symbolLabel = new StringBuilder();
|
||||
symbolLabel.append("@");
|
||||
symbolLabel.append(searchPrefix);
|
||||
symbolLabel.append(' ');
|
||||
symbolLabel.append('\'');
|
||||
symbolLabel.append(beanName);
|
||||
symbolLabel.append('\'');
|
||||
symbolLabel.append(" (@");
|
||||
symbolLabel.append(annotationTypeName);
|
||||
if (!metaAnnotationNames.isEmpty()) {
|
||||
symbolLabel.append(" <: ");
|
||||
boolean first = true;
|
||||
for (String ma : metaAnnotationNames) {
|
||||
if (!first) {
|
||||
symbolLabel.append(", ");
|
||||
}
|
||||
symbolLabel.append("@");
|
||||
symbolLabel.append(ma);
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
symbolLabel.append(") ");
|
||||
symbolLabel.append(beanType);
|
||||
return symbolLabel.toString();
|
||||
}
|
||||
|
||||
private String getBeanName(Annotation node) {
|
||||
ASTNode parent = node.getParent();
|
||||
if (parent instanceof TypeDeclaration) {
|
||||
TypeDeclaration type = (TypeDeclaration) parent;
|
||||
|
||||
String beanName = type.getName().toString();
|
||||
if (beanName.length() > 0 && Character.isUpperCase(beanName.charAt(0))) {
|
||||
beanName = Character.toLowerCase(beanName.charAt(0)) + beanName.substring(1);
|
||||
}
|
||||
return beanName;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String getBeanType(Annotation node) {
|
||||
ASTNode parent = node.getParent();
|
||||
if (parent instanceof TypeDeclaration) {
|
||||
TypeDeclaration type = (TypeDeclaration) parent;
|
||||
String returnType = type.resolveBinding().getName();
|
||||
return returnType;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<SymbolInformation> getSymbols(TypeDeclaration typeDeclaration, TextDocument doc) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<SymbolInformation> getSymbols(MethodDeclaration methodDeclaration, TextDocument doc) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017, 2018 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.conditionals;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.Annotation;
|
||||
import org.eclipse.jdt.core.dom.IMethodBinding;
|
||||
import org.eclipse.jdt.core.dom.ITypeBinding;
|
||||
import org.eclipse.jdt.core.dom.MethodDeclaration;
|
||||
import org.eclipse.jdt.core.dom.TypeDeclaration;
|
||||
import org.eclipse.lsp4j.Hover;
|
||||
import org.eclipse.lsp4j.MarkedString;
|
||||
import org.eclipse.lsp4j.Range;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.HoverProvider;
|
||||
import org.springframework.ide.vscode.boot.java.livehover.LiveHoverUtils;
|
||||
import org.springframework.ide.vscode.commons.boot.app.cli.LiveConditional;
|
||||
import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.util.Log;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
/**
|
||||
*
|
||||
* Provides live hovers and hints for @ConditionalOn... Spring Boot annotations
|
||||
* from running spring boot apps.
|
||||
*/
|
||||
public class ConditionalsLiveHoverProvider implements HoverProvider {
|
||||
|
||||
@Override
|
||||
public Hover provideHover(ASTNode node, Annotation annotation, ITypeBinding type, int offset,
|
||||
TextDocument doc, IJavaProject project, SpringBootApp[] runningApps) {
|
||||
return provideHover(annotation, doc, runningApps);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Range> getLiveHoverHints(Annotation annotation, TextDocument doc, SpringBootApp[] runningApps) {
|
||||
try {
|
||||
Optional<List<LiveConditional>> val = getMatchedLiveConditionals(annotation, runningApps);
|
||||
if (val.isPresent()) {
|
||||
Range hoverRange = doc.toRange(annotation.getStartPosition(), annotation.getLength());
|
||||
return ImmutableList.of(hoverRange);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.log(e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private Optional<List<LiveConditional>> getMatchedLiveConditionals(Annotation annotation,
|
||||
SpringBootApp[] runningApps) throws Exception {
|
||||
if (runningApps != null) {
|
||||
List<LiveConditional> all = new ArrayList<>();
|
||||
|
||||
for (SpringBootApp springBootApp : runningApps) {
|
||||
springBootApp.getLiveConditionals().ifPresent((conditionals) -> {
|
||||
conditionals.stream().forEach((conditional) -> {
|
||||
if (matchesAnnotation(annotation, conditional)) {
|
||||
all.add(conditional);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
if (!all.isEmpty()) {
|
||||
return Optional.of(all);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private Hover provideHover(Annotation annotation, TextDocument doc,
|
||||
SpringBootApp[] runningApps) {
|
||||
|
||||
try {
|
||||
List<Either<String, MarkedString>> hoverContent = new ArrayList<>();
|
||||
Optional<List<LiveConditional>> val = getMatchedLiveConditionals(annotation, runningApps);
|
||||
|
||||
if (val.isPresent()) {
|
||||
addHoverContent(val.get(), hoverContent);
|
||||
}
|
||||
|
||||
Range hoverRange = doc.toRange(annotation.getStartPosition(), annotation.getLength());
|
||||
Hover hover = new Hover();
|
||||
|
||||
hover.setContents(hoverContent);
|
||||
hover.setRange(hoverRange);
|
||||
|
||||
return hover;
|
||||
} catch (Exception e) {
|
||||
Log.log(e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void addHoverContent(List<LiveConditional> conditionals, List<Either<String, MarkedString>> hoverContent)
|
||||
throws Exception {
|
||||
for (int i = 0; i < conditionals.size(); i++) {
|
||||
LiveConditional conditional = conditionals.get(i);
|
||||
hoverContent.add(Either.forLeft(conditional.getMessage()));
|
||||
hoverContent.add(Either.forLeft(LiveHoverUtils.niceAppName(conditional.getProcessId(), conditional.getProcessName())));
|
||||
|
||||
if (i < conditionals.size() - 1) {
|
||||
hoverContent.add(Either.forLeft("---"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param annotation
|
||||
* @param jsonKey
|
||||
* @return true if the annotation matches the information in the json key from
|
||||
* the running app.
|
||||
*/
|
||||
protected boolean matchesAnnotation(Annotation annotation, LiveConditional liveConditional) {
|
||||
|
||||
// First check that the annotation matches the live conditional annotation
|
||||
String annotationName = annotation.resolveTypeBinding().getName();
|
||||
if (!liveConditional.getMessage().contains(annotationName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that Java type in annotation in editor matches Java information in the live Conditional
|
||||
ASTNode parent = annotation.getParent();
|
||||
String typeInfo = liveConditional.getTypeInfo();
|
||||
|
||||
if (parent instanceof MethodDeclaration) {
|
||||
MethodDeclaration methodDec = (MethodDeclaration) parent;
|
||||
IMethodBinding binding = methodDec.resolveBinding();
|
||||
String annotationDeclaringClassName = binding.getDeclaringClass().getName();
|
||||
String annotationMethodName = binding.getName();
|
||||
return typeInfo.contains(annotationDeclaringClassName) && typeInfo.contains(annotationMethodName);
|
||||
} else if (parent instanceof TypeDeclaration) {
|
||||
TypeDeclaration typeDec = (TypeDeclaration) parent;
|
||||
String annotationDeclaringClassName = typeDec.resolveBinding().getName();
|
||||
return typeInfo.contains(annotationDeclaringClassName);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Hover provideHover(ASTNode node, TypeDeclaration typeDeclaration, ITypeBinding type, int offset,
|
||||
TextDocument doc, IJavaProject project, SpringBootApp[] runningApps) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Range> getLiveHoverHints(TypeDeclaration typeDeclaration, TextDocument doc,
|
||||
SpringBootApp[] runningApps) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.data;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.eclipse.jdt.core.dom.Annotation;
|
||||
import org.eclipse.jdt.core.dom.ITypeBinding;
|
||||
import org.eclipse.jdt.core.dom.MethodDeclaration;
|
||||
import org.eclipse.jdt.core.dom.TypeDeclaration;
|
||||
import org.eclipse.lsp4j.Location;
|
||||
import org.eclipse.lsp4j.SymbolInformation;
|
||||
import org.eclipse.lsp4j.SymbolKind;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.SymbolProvider;
|
||||
import org.springframework.ide.vscode.boot.java.utils.ASTUtils;
|
||||
import org.springframework.ide.vscode.commons.util.BadLocationException;
|
||||
import org.springframework.ide.vscode.commons.util.Log;
|
||||
import org.springframework.ide.vscode.commons.util.text.DocumentRegion;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
import reactor.util.function.Tuple4;
|
||||
import reactor.util.function.Tuples;
|
||||
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
*/
|
||||
public class DataRepositorySymbolProvider implements SymbolProvider {
|
||||
|
||||
private static final String REPOSITORY_TYPE = "org.springframework.data.repository.Repository";
|
||||
|
||||
|
||||
@Override
|
||||
public Collection<SymbolInformation> getSymbols(Annotation node, ITypeBinding annotationType, Collection<ITypeBinding> metaAnnotations, TextDocument doc) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<SymbolInformation> getSymbols(TypeDeclaration typeDeclaration, TextDocument doc) {
|
||||
// this checks spring data repository beans that are defined as extensions of the repository interface
|
||||
Tuple4<String, String, String, DocumentRegion> repositoryBean = getRepositoryBean(typeDeclaration, doc);
|
||||
if (repositoryBean != null) {
|
||||
try {
|
||||
SymbolInformation symbol = new SymbolInformation(
|
||||
beanLabel(true, repositoryBean.getT1(), repositoryBean.getT2(), repositoryBean.getT3()),
|
||||
SymbolKind.Interface,
|
||||
new Location(doc.getUri(), doc.toRange(repositoryBean.getT4())));
|
||||
return ImmutableList.of(symbol);
|
||||
} catch (BadLocationException e) {
|
||||
Log.log(e);
|
||||
}
|
||||
}
|
||||
return ImmutableList.of();
|
||||
}
|
||||
|
||||
protected String beanLabel(boolean isFunctionBean, String beanName, String beanType, String markerString) {
|
||||
StringBuilder symbolLabel = new StringBuilder();
|
||||
symbolLabel.append("@+");
|
||||
symbolLabel.append(' ');
|
||||
symbolLabel.append('\'');
|
||||
symbolLabel.append(beanName);
|
||||
symbolLabel.append('\'');
|
||||
|
||||
markerString = markerString != null && markerString.length() > 0 ? " (" + markerString + ") " : " ";
|
||||
symbolLabel.append(markerString);
|
||||
|
||||
symbolLabel.append(beanType);
|
||||
return symbolLabel.toString();
|
||||
}
|
||||
|
||||
private static Tuple4<String, String, String, DocumentRegion> getRepositoryBean(TypeDeclaration typeDeclaration, TextDocument doc) {
|
||||
ITypeBinding resolvedType = typeDeclaration.resolveBinding();
|
||||
|
||||
if (resolvedType != null) {
|
||||
return getRepositoryBean(typeDeclaration, doc, resolvedType);
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static Tuple4<String, String, String, DocumentRegion> getRepositoryBean(TypeDeclaration typeDeclaration, TextDocument doc,
|
||||
ITypeBinding resolvedType) {
|
||||
|
||||
ITypeBinding[] interfaces = resolvedType.getInterfaces();
|
||||
for (ITypeBinding resolvedInterface : interfaces) {
|
||||
String simplifiedType = null;
|
||||
if (resolvedInterface.isParameterizedType()) {
|
||||
simplifiedType = resolvedInterface.getBinaryName();
|
||||
}
|
||||
else {
|
||||
simplifiedType = resolvedType.getQualifiedName();
|
||||
}
|
||||
|
||||
if (REPOSITORY_TYPE.equals(simplifiedType)) {
|
||||
String beanName = getBeanName(typeDeclaration);
|
||||
String beanType = resolvedInterface.getName();
|
||||
|
||||
String domainType = null;
|
||||
if (resolvedInterface.isParameterizedType()) {
|
||||
ITypeBinding[] typeParameters = resolvedInterface.getTypeArguments();
|
||||
if (typeParameters != null && typeParameters.length > 0) {
|
||||
domainType = typeParameters[0].getName();
|
||||
}
|
||||
}
|
||||
DocumentRegion region = ASTUtils.nodeRegion(doc, typeDeclaration.getName());
|
||||
|
||||
return Tuples.of(beanName, beanType, domainType, region);
|
||||
}
|
||||
else {
|
||||
Tuple4<String, String, String, DocumentRegion> result = getRepositoryBean(typeDeclaration, doc, resolvedInterface);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ITypeBinding superclass = resolvedType.getSuperclass();
|
||||
if (superclass != null) {
|
||||
return getRepositoryBean(typeDeclaration, doc, superclass);
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static String getBeanName(TypeDeclaration typeDeclaration) {
|
||||
String beanName = typeDeclaration.getName().toString();
|
||||
if (beanName.length() > 0 && Character.isUpperCase(beanName.charAt(0))) {
|
||||
beanName = Character.toLowerCase(beanName.charAt(0)) + beanName.substring(1);
|
||||
}
|
||||
return beanName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<SymbolInformation> getSymbols(MethodDeclaration methodDeclaration, TextDocument doc) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 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.handlers;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.eclipse.lsp4j.CodeLens;
|
||||
import org.eclipse.lsp4j.CodeLensParams;
|
||||
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.util.text.TextDocument;
|
||||
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
*/
|
||||
public class BootJavaCodeLensEngine {
|
||||
|
||||
private final SimpleLanguageServer server;
|
||||
// private final JavaProjectFinder projectFinder;
|
||||
|
||||
public BootJavaCodeLensEngine(SimpleLanguageServer server, JavaProjectFinder projectFinder) {
|
||||
this.server = server;
|
||||
// this.projectFinder = projectFinder;
|
||||
}
|
||||
|
||||
public CompletableFuture<List<? extends CodeLens>> createCodeLenses(CodeLensParams params) {
|
||||
SimpleTextDocumentService documents = server.getTextDocumentService();
|
||||
String docURI = params.getTextDocument().getUri();
|
||||
|
||||
if (documents.get(docURI) != null) {
|
||||
TextDocument doc = documents.get(docURI).copy();
|
||||
try {
|
||||
CompletableFuture<List<? extends CodeLens>> codeLensesResult = provideCodeLenses(doc);
|
||||
if (codeLensesResult != null) {
|
||||
return codeLensesResult;
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
}
|
||||
}
|
||||
|
||||
return SimpleTextDocumentService.NO_CODELENS;
|
||||
}
|
||||
|
||||
private CompletableFuture<List<? extends CodeLens>> provideCodeLenses(TextDocument doc) {
|
||||
List<CodeLens> result = new ArrayList<>();
|
||||
/**
|
||||
CodeLens codeLens = new CodeLens();
|
||||
|
||||
Range range = new Range();
|
||||
range.setStart(new Position(5, 0));
|
||||
range.setEnd(new Position(5, 10));
|
||||
codeLens.setRange(range);
|
||||
|
||||
Command command = new Command("my first awesome code lens", "my first code lens command");
|
||||
codeLens.setCommand(command);
|
||||
|
||||
codeLens.setData("some data");
|
||||
|
||||
result.add(codeLens); */
|
||||
return CompletableFuture.completedFuture(result);
|
||||
}
|
||||
|
||||
public CompletableFuture<CodeLens> resolveCodeLens(CodeLens unresolved) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017, 2018 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.handlers;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.Annotation;
|
||||
import org.eclipse.jdt.core.dom.ITypeBinding;
|
||||
import org.eclipse.jdt.core.dom.NodeFinder;
|
||||
import org.springframework.ide.vscode.boot.java.BootJavaLanguageServerComponents;
|
||||
import org.springframework.ide.vscode.boot.java.snippets.JavaSnippetManager;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionEngine;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
*/
|
||||
public class BootJavaCompletionEngine implements ICompletionEngine {
|
||||
|
||||
private Map<String, CompletionProvider> completionProviders;
|
||||
private JavaSnippetManager snippets;
|
||||
private BootJavaLanguageServerComponents server;
|
||||
|
||||
public BootJavaCompletionEngine(BootJavaLanguageServerComponents server, Map<String, CompletionProvider> specificProviders, JavaSnippetManager snippets) {
|
||||
this.server = server;
|
||||
this.completionProviders = specificProviders;
|
||||
this.snippets = snippets;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ICompletionProposal> getCompletions(TextDocument document, int offset) throws Exception {
|
||||
return server.getCompilationUnitCache().withCompilationUnit(document, cu -> {
|
||||
if (cu != null) {
|
||||
ASTNode node = NodeFinder.perform(cu, offset, 0);
|
||||
|
||||
if (node != null) {
|
||||
Collection<ICompletionProposal> completions = new ArrayList<ICompletionProposal>();
|
||||
completions.addAll(collectCompletionsForAnnotations(node, offset, document));
|
||||
completions.addAll(snippets.getCompletions(document, offset, node, cu));
|
||||
return completions;
|
||||
}
|
||||
}
|
||||
|
||||
return Collections.emptyList();
|
||||
});
|
||||
}
|
||||
|
||||
private Collection<ICompletionProposal> collectCompletionsForAnnotations(ASTNode node, int offset, IDocument doc) {
|
||||
Annotation annotation = null;
|
||||
ASTNode exactNode = node;
|
||||
|
||||
while (node != null && !(node instanceof Annotation)) {
|
||||
node = node.getParent();
|
||||
}
|
||||
|
||||
if (node != null) {
|
||||
annotation = (Annotation) node;
|
||||
ITypeBinding type = annotation.resolveTypeBinding();
|
||||
if (type != null) {
|
||||
String qualifiedName = type.getQualifiedName();
|
||||
if (qualifiedName != null) {
|
||||
CompletionProvider provider = this.completionProviders.get(qualifiedName);
|
||||
if (provider != null) {
|
||||
return provider.provideCompletions(exactNode, annotation, type, offset, doc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 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.handlers;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.lsp4j.DocumentSymbolParams;
|
||||
import org.eclipse.lsp4j.SymbolInformation;
|
||||
import org.springframework.ide.vscode.boot.java.utils.SpringIndexer;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.DocumentSymbolHandler;
|
||||
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
*/
|
||||
public class BootJavaDocumentSymbolHandler implements DocumentSymbolHandler {
|
||||
|
||||
private SpringIndexer indexer;
|
||||
|
||||
public BootJavaDocumentSymbolHandler(SpringIndexer indexer) {
|
||||
this.indexer = indexer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends SymbolInformation> handle(DocumentSymbolParams params) {
|
||||
return indexer.getSymbols(params.getTextDocument().getUri());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,294 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017, 2018 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.handlers;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.ASTVisitor;
|
||||
import org.eclipse.jdt.core.dom.Annotation;
|
||||
import org.eclipse.jdt.core.dom.ITypeBinding;
|
||||
import org.eclipse.jdt.core.dom.MarkerAnnotation;
|
||||
import org.eclipse.jdt.core.dom.NodeFinder;
|
||||
import org.eclipse.jdt.core.dom.NormalAnnotation;
|
||||
import org.eclipse.jdt.core.dom.SimpleName;
|
||||
import org.eclipse.jdt.core.dom.SingleMemberAnnotation;
|
||||
import org.eclipse.jdt.core.dom.TypeDeclaration;
|
||||
import org.eclipse.lsp4j.Hover;
|
||||
import org.eclipse.lsp4j.Range;
|
||||
import org.eclipse.lsp4j.TextDocumentIdentifier;
|
||||
import org.eclipse.lsp4j.TextDocumentPositionParams;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
||||
import org.springframework.ide.vscode.boot.java.BootJavaLanguageServerComponents;
|
||||
import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchyAwareLookup;
|
||||
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.java.IClasspath;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.HoverHandler;
|
||||
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.DocumentRegion;
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
*/
|
||||
public class BootJavaHoverProvider implements HoverHandler {
|
||||
|
||||
private JavaProjectFinder projectFinder;
|
||||
private BootJavaLanguageServerComponents server;
|
||||
private AnnotationHierarchyAwareLookup<HoverProvider> hoverProviders;
|
||||
private RunningAppProvider runningAppProvider;
|
||||
|
||||
public BootJavaHoverProvider(BootJavaLanguageServerComponents server, JavaProjectFinder projectFinder, AnnotationHierarchyAwareLookup<HoverProvider> specificProviders, RunningAppProvider runningAppProvider) {
|
||||
this.server = server;
|
||||
this.projectFinder = projectFinder;
|
||||
this.hoverProviders = specificProviders;
|
||||
this.runningAppProvider = runningAppProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Hover> handle(TextDocumentPositionParams params) {
|
||||
SimpleTextDocumentService documents = server.getTextDocumentService();
|
||||
if (documents.get(params) != null) {
|
||||
TextDocument doc = documents.get(params).copy();
|
||||
try {
|
||||
int offset = doc.toOffset(params.getPosition());
|
||||
Hover hoverResult = provideHover(doc, offset);
|
||||
if (hoverResult != null) {
|
||||
return CompletableFuture.completedFuture(hoverResult);
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
}
|
||||
}
|
||||
|
||||
return SimpleTextDocumentService.NO_HOVER;
|
||||
}
|
||||
|
||||
public Range[] getLiveHoverHints(final TextDocument document, final SpringBootApp[] runningBootApps) {
|
||||
return server.getCompilationUnitCache().withCompilationUnit(document, cu -> {
|
||||
Collection<Range> result = new HashSet<>();
|
||||
try {
|
||||
if (cu != null) {
|
||||
cu.accept(new ASTVisitor() {
|
||||
|
||||
@Override
|
||||
public boolean visit(TypeDeclaration node) {
|
||||
try {
|
||||
extractLiveHintsForType(node, document, runningBootApps, result);
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return super.visit(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(SingleMemberAnnotation node) {
|
||||
try {
|
||||
extractLiveHintsForAnnotation(node, document, runningBootApps, result);
|
||||
} catch (Exception e) {
|
||||
Log.log(e);
|
||||
}
|
||||
|
||||
return super.visit(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(NormalAnnotation node) {
|
||||
try {
|
||||
extractLiveHintsForAnnotation(node, document, runningBootApps, result);
|
||||
} catch (Exception e) {
|
||||
Log.log(e);
|
||||
}
|
||||
|
||||
return super.visit(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(MarkerAnnotation node) {
|
||||
try {
|
||||
extractLiveHintsForAnnotation(node, document, runningBootApps, result);
|
||||
} catch (Exception e) {
|
||||
Log.log(e);
|
||||
}
|
||||
|
||||
return super.visit(node);
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.log(e);
|
||||
}
|
||||
return result.toArray(new Range[result.size()]);
|
||||
});
|
||||
}
|
||||
|
||||
protected void extractLiveHintsForType(TypeDeclaration typeDeclaration, TextDocument doc, SpringBootApp[] runningApps, Collection<Range> result) {
|
||||
Collection<HoverProvider> providers = this.hoverProviders.getAll();
|
||||
if (!providers.isEmpty()) {
|
||||
for (HoverProvider provider : providers) {
|
||||
getProject(doc).ifPresent(project -> {
|
||||
if (hasActuatorDependency(project)) {
|
||||
Collection<Range> hints = provider.getLiveHoverHints(typeDeclaration, doc, runningApps);
|
||||
if (hints!=null) {
|
||||
result.addAll(hints);
|
||||
}
|
||||
} else {
|
||||
//Do nothing... we don't want a highlight for the 'no actuator warning'
|
||||
//ASTUtils.nameRange(doc, annotation).ifPresent(result::add);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void extractLiveHintsForAnnotation(Annotation annotation, TextDocument doc, SpringBootApp[] runningApps, Collection<Range> result) {
|
||||
ITypeBinding type = annotation.resolveTypeBinding();
|
||||
if (type != null) {
|
||||
if (runningApps.length > 0) {
|
||||
for (HoverProvider provider : this.hoverProviders.get(type)) {
|
||||
getProject(doc).ifPresent(project -> {
|
||||
if (hasActuatorDependency(project)) {
|
||||
Collection<Range> hints = provider.getLiveHoverHints(annotation, doc, runningApps);
|
||||
if (hints!=null) {
|
||||
result.addAll(hints);
|
||||
}
|
||||
} else {
|
||||
//Do nothing... we don't want a highlight for the 'no actuator warning'
|
||||
//ASTUtils.nameRange(doc, annotation).ifPresent(result::add);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Hover provideHover(TextDocument document, int offset) throws Exception {
|
||||
IJavaProject project = getProject(document).orElse(null);
|
||||
if (project!=null) {
|
||||
return server.getCompilationUnitCache().withCompilationUnit(document, cu -> {
|
||||
ASTNode node = NodeFinder.perform(cu, offset, 0);
|
||||
if (node != null) {
|
||||
return provideHover(node, offset, document, project);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Hover provideHover(ASTNode node, int offset, TextDocument doc, IJavaProject project) {
|
||||
|
||||
// look for spring annotations first
|
||||
ASTNode annotationNode = node;
|
||||
while (annotationNode != null && !(annotationNode instanceof Annotation)) {
|
||||
annotationNode = annotationNode.getParent();
|
||||
}
|
||||
if (annotationNode != null) {
|
||||
return provideHoverForAnnotation(node, (Annotation) annotationNode, offset, doc, project);
|
||||
}
|
||||
|
||||
// then do additional AST node coverage
|
||||
if (node instanceof SimpleName && node.getParent() instanceof TypeDeclaration) {
|
||||
return provideHoverForTypeDeclaration(node, (TypeDeclaration) node.getParent(), offset, doc, project);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private Hover provideHoverForAnnotation(ASTNode exactNode, Annotation annotation, int offset, TextDocument doc, IJavaProject project) {
|
||||
ITypeBinding type = annotation.resolveTypeBinding();
|
||||
if (type != null) {
|
||||
SpringBootApp[] runningApps = getRunningSpringApps(project);
|
||||
if (runningApps.length > 0) {
|
||||
for (HoverProvider provider : this.hoverProviders.get(type)) {
|
||||
Hover hover = provider.provideHover(exactNode, annotation, type, offset, doc, project, runningApps);
|
||||
if (hover!=null) {
|
||||
//TODO: compose multiple hovers somehow instead of just returning the first one?
|
||||
return hover;
|
||||
}
|
||||
}
|
||||
//Only reaching here if we didn't get a hover.
|
||||
if (!hasActuatorDependency(project)) {
|
||||
DocumentRegion region = ASTUtils.nameRegion(doc, annotation);
|
||||
if (region.containsOffset(offset)) {
|
||||
return actuatorWarning(project);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Hover provideHoverForTypeDeclaration(ASTNode exactNode, TypeDeclaration typeDeclaration, int offset, TextDocument doc, IJavaProject project) {
|
||||
SpringBootApp[] runningApps = getRunningSpringApps(project);
|
||||
if (runningApps.length > 0) {
|
||||
ITypeBinding type = typeDeclaration.resolveBinding();
|
||||
|
||||
for (HoverProvider provider : this.hoverProviders.getAll()) {
|
||||
Hover hover = provider.provideHover(exactNode, typeDeclaration, type, offset, doc, project, runningApps);
|
||||
if (hover!=null) {
|
||||
//TODO: compose multiple hovers somehow instead of just returning the first one?
|
||||
return hover;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Hover actuatorWarning(IJavaProject project) {
|
||||
String hoverText =
|
||||
"**No live hover information available**.\n"+
|
||||
"\n" +
|
||||
"Live hover providers use various `spring-boot-actuator` endpoints to retrieve information. "+
|
||||
"Consider adding `spring-boot-actuator` as a dependency to your project `"+project.getElementName()+"`";
|
||||
return new Hover(ImmutableList.of(Either.forLeft(hoverText)));
|
||||
}
|
||||
|
||||
private boolean hasActuatorDependency(IJavaProject project) {
|
||||
try {
|
||||
IClasspath classpath = project.getClasspath();
|
||||
if (classpath!=null) {
|
||||
return classpath.getClasspathEntries().stream().anyMatch(cpe -> {
|
||||
String name = cpe.getFileName().toString();
|
||||
return name.startsWith("spring-boot-actuator-");
|
||||
});
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.log(e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private Optional<IJavaProject> getProject(IDocument doc) {
|
||||
return this.projectFinder.find(new TextDocumentIdentifier(doc.getUri()));
|
||||
}
|
||||
|
||||
private SpringBootApp[] getRunningSpringApps(IJavaProject project) {
|
||||
try {
|
||||
return runningAppProvider.getAllRunningSpringApps().toArray(new SpringBootApp[0]);
|
||||
} catch (Exception e) {
|
||||
Log.log(e);
|
||||
return new SpringBootApp[0];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016-2017 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.handlers;
|
||||
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.IReconcileEngine;
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
*/
|
||||
public class BootJavaReconcileEngine implements IReconcileEngine {
|
||||
|
||||
@Override
|
||||
public void reconcile(IDocument doc, IProblemCollector problemCollector) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 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.handlers;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.core.JavaCore;
|
||||
import org.eclipse.jdt.core.dom.AST;
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.ASTParser;
|
||||
import org.eclipse.jdt.core.dom.Annotation;
|
||||
import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||
import org.eclipse.jdt.core.dom.ITypeBinding;
|
||||
import org.eclipse.jdt.core.dom.NodeFinder;
|
||||
import org.eclipse.lsp4j.Location;
|
||||
import org.eclipse.lsp4j.ReferenceParams;
|
||||
import org.eclipse.lsp4j.TextDocumentIdentifier;
|
||||
import org.springframework.ide.vscode.commons.java.IClasspath;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
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;
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
*/
|
||||
public class BootJavaReferencesHandler implements ReferencesHandler {
|
||||
|
||||
private JavaProjectFinder projectFinder;
|
||||
private SimpleLanguageServer server;
|
||||
private Map<String, ReferenceProvider> referenceProviders;
|
||||
|
||||
public BootJavaReferencesHandler(SimpleLanguageServer server, JavaProjectFinder projectFinder, Map<String, ReferenceProvider> specificProviders) {
|
||||
this.server = server;
|
||||
this.projectFinder = projectFinder;
|
||||
this.referenceProviders = specificProviders;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<? extends Location>> handle(ReferenceParams params) {
|
||||
SimpleTextDocumentService documents = server.getTextDocumentService();
|
||||
TextDocument doc = documents.get(params).copy();
|
||||
if (doc != null) {
|
||||
try {
|
||||
int offset = doc.toOffset(params.getPosition());
|
||||
CompletableFuture<List<? extends Location>> referencesResult = provideReferences(doc, offset);
|
||||
if (referencesResult != null) {
|
||||
return referencesResult;
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
}
|
||||
}
|
||||
|
||||
return SimpleTextDocumentService.NO_REFERENCES;
|
||||
}
|
||||
|
||||
private CompletableFuture<List<? extends Location>> provideReferences(TextDocument document, int offset) throws Exception {
|
||||
ASTParser parser = ASTParser.newParser(AST.JLS9);
|
||||
Map<String, String> options = JavaCore.getOptions();
|
||||
JavaCore.setComplianceOptions(JavaCore.VERSION_1_8, options);
|
||||
parser.setCompilerOptions(options);
|
||||
parser.setKind(ASTParser.K_COMPILATION_UNIT);
|
||||
parser.setStatementsRecovery(true);
|
||||
parser.setBindingsRecovery(true);
|
||||
parser.setResolveBindings(true);
|
||||
|
||||
String[] classpathEntries = getClasspathEntries(document);
|
||||
String[] sourceEntries = new String[] {};
|
||||
parser.setEnvironment(classpathEntries, sourceEntries, null, true);
|
||||
|
||||
String docURI = document.getUri();
|
||||
String unitName = docURI.substring(docURI.lastIndexOf("/"));
|
||||
parser.setUnitName(unitName);
|
||||
parser.setSource(document.get(0, document.getLength()).toCharArray());
|
||||
|
||||
CompilationUnit cu = (CompilationUnit) parser.createAST(null);
|
||||
ASTNode node = NodeFinder.perform(cu, offset, 0);
|
||||
|
||||
if (node != null) {
|
||||
return provideReferencesForAnnotation(node, offset, document);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private CompletableFuture<List<? extends Location>> provideReferencesForAnnotation(ASTNode node, int offset, TextDocument doc) {
|
||||
Annotation annotation = null;
|
||||
|
||||
while (node != null && !(node instanceof Annotation)) {
|
||||
node = node.getParent();
|
||||
}
|
||||
|
||||
if (node != null) {
|
||||
annotation = (Annotation) node;
|
||||
ITypeBinding type = annotation.resolveTypeBinding();
|
||||
if (type != null) {
|
||||
String qualifiedName = type.getQualifiedName();
|
||||
if (qualifiedName != null) {
|
||||
ReferenceProvider provider = this.referenceProviders.get(qualifiedName);
|
||||
if (provider != null) {
|
||||
return provider.provideReferences(node, annotation, type, offset, doc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private String[] getClasspathEntries(IDocument doc) throws Exception {
|
||||
IJavaProject project = this.projectFinder.find(new TextDocumentIdentifier(doc.getUri())).get();
|
||||
IClasspath classpath = project.getClasspath();
|
||||
Stream<Path> classpathEntries = classpath.getClasspathEntries().stream();
|
||||
return classpathEntries
|
||||
.filter(path -> path.toFile().exists())
|
||||
.map(path -> path.toAbsolutePath().toString()).toArray(String[]::new);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 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.handlers;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.lsp4j.SymbolInformation;
|
||||
import org.eclipse.lsp4j.WorkspaceSymbolParams;
|
||||
import org.springframework.ide.vscode.boot.java.requestmapping.LiveAppURLSymbolProvider;
|
||||
import org.springframework.ide.vscode.boot.java.utils.SpringIndexer;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.WorkspaceSymbolHandler;
|
||||
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
*/
|
||||
public class BootJavaWorkspaceSymbolHandler implements WorkspaceSymbolHandler {
|
||||
|
||||
private final SpringIndexer indexer;
|
||||
private final LiveAppURLSymbolProvider liveAppSymbolProvider;
|
||||
|
||||
public BootJavaWorkspaceSymbolHandler(SpringIndexer indexer, LiveAppURLSymbolProvider liveAppSymbolProvider) {
|
||||
this.indexer = indexer;
|
||||
this.liveAppSymbolProvider = liveAppSymbolProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends SymbolInformation> handle(WorkspaceSymbolParams params) {
|
||||
if (params.getQuery() != null && params.getQuery().startsWith("//")) {
|
||||
return liveAppSymbolProvider.getSymbols(params.getQuery());
|
||||
}
|
||||
else {
|
||||
return indexer.getAllSymbols(params.getQuery());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 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.handlers;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.Annotation;
|
||||
import org.eclipse.jdt.core.dom.ITypeBinding;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
*/
|
||||
public interface CompletionProvider {
|
||||
|
||||
Collection<ICompletionProposal> provideCompletions(ASTNode node, Annotation annotation, ITypeBinding type, int offset, IDocument doc);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017, 2018 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.handlers;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.Annotation;
|
||||
import org.eclipse.jdt.core.dom.ITypeBinding;
|
||||
import org.eclipse.jdt.core.dom.TypeDeclaration;
|
||||
import org.eclipse.lsp4j.Hover;
|
||||
import org.eclipse.lsp4j.Range;
|
||||
import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
*/
|
||||
public interface HoverProvider {
|
||||
|
||||
Hover provideHover(ASTNode node, Annotation annotation, ITypeBinding type, int offset, TextDocument doc, IJavaProject project, SpringBootApp[] runningApps);
|
||||
Hover provideHover(ASTNode node, TypeDeclaration typeDeclaration, ITypeBinding type, int offset, TextDocument doc, IJavaProject project, SpringBootApp[] runningApps);
|
||||
|
||||
Collection<Range> getLiveHoverHints(Annotation annotation, TextDocument doc, SpringBootApp[] runningApps);
|
||||
Collection<Range> getLiveHoverHints(TypeDeclaration typeDeclaration, TextDocument doc, SpringBootApp[] runningApps);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 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.handlers;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.Annotation;
|
||||
import org.eclipse.jdt.core.dom.ITypeBinding;
|
||||
import org.eclipse.lsp4j.Location;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
*/
|
||||
public interface ReferenceProvider {
|
||||
|
||||
CompletableFuture<List<? extends Location>> provideReferences(ASTNode node, Annotation annotation,
|
||||
ITypeBinding type, int offset, TextDocument doc);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 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.handlers;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
public interface RunningAppProvider {
|
||||
|
||||
public static final RunningAppProvider DEFAULT = SpringBootApp::getAllRunningSpringApps;
|
||||
public static final RunningAppProvider NULL = () -> ImmutableList.of();
|
||||
|
||||
Collection<SpringBootApp> getAllRunningSpringApps() throws Exception;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017, 2018 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.handlers;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.eclipse.jdt.core.dom.Annotation;
|
||||
import org.eclipse.jdt.core.dom.ITypeBinding;
|
||||
import org.eclipse.jdt.core.dom.MethodDeclaration;
|
||||
import org.eclipse.jdt.core.dom.TypeDeclaration;
|
||||
import org.eclipse.lsp4j.SymbolInformation;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public interface SymbolProvider {
|
||||
|
||||
Collection<SymbolInformation> getSymbols(Annotation node, ITypeBinding typeBinding, Collection<ITypeBinding> metaAnnotations, TextDocument doc);
|
||||
Collection<SymbolInformation> getSymbols(TypeDeclaration typeDeclaration, TextDocument doc);
|
||||
Collection<SymbolInformation> getSymbols(MethodDeclaration methodDeclaration, TextDocument doc);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,558 @@
|
||||
/*******************************************************************************
|
||||
* Derived from:
|
||||
* org.eclipse.jdt.core.dom.rewrite.ImportRewrite
|
||||
*
|
||||
* for use in STS4, where IProject and ICompilationUnit are not available when parsing a Java source.
|
||||
*
|
||||
* Original license:
|
||||
*
|
||||
* Copyright (c) 2000, 2016 IBM Corporation and others.
|
||||
* 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:
|
||||
* IBM Corporation - initial API and implementation
|
||||
* John Glassmyer <jogl@google.com> - import group sorting is broken - https://bugs.eclipse.org/430303
|
||||
* Lars Vogel <Lars.Vogel@vogella.com> - Contributions for
|
||||
* Bug 473178
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.jdt.imports;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.core.runtime.IProgressMonitor;
|
||||
import org.eclipse.jdt.core.ICompilationUnit;
|
||||
import org.eclipse.jdt.core.compiler.CharOperation;
|
||||
import org.eclipse.jdt.core.dom.ASTParser;
|
||||
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
|
||||
import org.eclipse.jdt.core.dom.Comment;
|
||||
import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||
import org.eclipse.jdt.core.dom.ImportDeclaration;
|
||||
import org.eclipse.jdt.core.dom.PackageDeclaration;
|
||||
import org.eclipse.jdt.core.dom.PrimitiveType;
|
||||
import org.eclipse.jdt.core.dom.SimpleName;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits;
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
|
||||
|
||||
/**
|
||||
* The {@link ImportRewrite} helps updating imports following a import order and on-demand imports threshold as configured by a project.
|
||||
* <p>
|
||||
* The import rewrite is created on a compilation unit and collects references to types that are added or removed. When adding imports, e.g. using
|
||||
* {@link #addImport(String)}, the import rewrite evaluates if the type can be imported and returns the a reference to the type that can be used in code.
|
||||
* This reference is either unqualified if the import could be added, or fully qualified if the import failed due to a conflict with another element of the same name.
|
||||
* </p>
|
||||
* <p>
|
||||
* On {@link #rewriteImports(IProgressMonitor)} the rewrite translates these descriptions into
|
||||
* text edits that can then be applied to the original source. The rewrite infrastructure tries to generate minimal text changes and only
|
||||
* works on the import statements. It is possible to combine the result of an import rewrite with the result of a {@link org.eclipse.jdt.core.dom.rewrite.ASTRewrite}
|
||||
* as long as no import statements are modified by the AST rewrite.
|
||||
* </p>
|
||||
* <p>The options controlling the import order and on-demand thresholds are:
|
||||
* <ul><li>{@link #setImportOrder(String[])} specifies the import groups and their preferred order</li>
|
||||
* <li>{@link #setOnDemandImportThreshold(int)} specifies the number of imports in a group needed for a on-demand import statement (star import)</li>
|
||||
* <li>{@link #setStaticOnDemandImportThreshold(int)} specifies the number of static imports in a group needed for a on-demand import statement (star import)</li>
|
||||
*</ul>
|
||||
* This class is not intended to be subclassed.
|
||||
* </p>
|
||||
* @since 3.2
|
||||
*/
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
public final class ImportRewrite {
|
||||
|
||||
/**
|
||||
* A {@link ImportRewrite.ImportRewriteContext} can optionally be used in e.g. {@link ImportRewrite#addImport(String, ImportRewrite.ImportRewriteContext)} to
|
||||
* give more information about the types visible in the scope. These types can be for example inherited inner types where it is
|
||||
* unnecessary to add import statements for.
|
||||
*
|
||||
* </p>
|
||||
* <p>
|
||||
* This class can be implemented by clients.
|
||||
* </p>
|
||||
*/
|
||||
public static abstract class ImportRewriteContext {
|
||||
|
||||
/**
|
||||
* Result constant signaling that the given element is know in the context.
|
||||
*/
|
||||
public final static int RES_NAME_FOUND= 1;
|
||||
|
||||
/**
|
||||
* Result constant signaling that the given element is not know in the context.
|
||||
*/
|
||||
public final static int RES_NAME_UNKNOWN= 2;
|
||||
|
||||
/**
|
||||
* Result constant signaling that the given element is conflicting with an other element in the context.
|
||||
*/
|
||||
public final static int RES_NAME_CONFLICT= 3;
|
||||
|
||||
/**
|
||||
* Result constant signaling that the given element must be imported explicitly (and must not be folded into
|
||||
* an on-demand import or filtered as an implicit import).
|
||||
*
|
||||
* @since 3.11
|
||||
*/
|
||||
public final static int RES_NAME_UNKNOWN_NEEDS_EXPLICIT_IMPORT= 4;
|
||||
|
||||
/**
|
||||
* Kind constant specifying that the element is a type import.
|
||||
*/
|
||||
public final static int KIND_TYPE= 1;
|
||||
|
||||
/**
|
||||
* Kind constant specifying that the element is a static field import.
|
||||
*/
|
||||
public final static int KIND_STATIC_FIELD= 2;
|
||||
|
||||
/**
|
||||
* Kind constant specifying that the element is a static method import.
|
||||
*/
|
||||
public final static int KIND_STATIC_METHOD= 3;
|
||||
|
||||
/**
|
||||
* Searches for the given element in the context and reports if the element is known ({@link #RES_NAME_FOUND}),
|
||||
* unknown ({@link #RES_NAME_UNKNOWN}), unknown in the context but known to require an explicit import
|
||||
* ({@link #RES_NAME_UNKNOWN_NEEDS_EXPLICIT_IMPORT}), or if its name conflicts ({@link #RES_NAME_CONFLICT})
|
||||
* with an other element.
|
||||
*
|
||||
* @param qualifier The qualifier of the element, can be package or the qualified name of a type
|
||||
* @param name The simple name of the element; either a type, method or field name or * for on-demand imports.
|
||||
* @param kind The kind of the element. Can be either {@link #KIND_TYPE}, {@link #KIND_STATIC_FIELD} or
|
||||
* {@link #KIND_STATIC_METHOD}. Implementors should be prepared for new, currently unspecified kinds and return
|
||||
* {@link #RES_NAME_UNKNOWN} by default.
|
||||
* @return Returns the result of the lookup. Can be either {@link #RES_NAME_FOUND}, {@link #RES_NAME_UNKNOWN},
|
||||
* {@link #RES_NAME_CONFLICT}, or {@link #RES_NAME_UNKNOWN_NEEDS_EXPLICIT_IMPORT}.
|
||||
*/
|
||||
public abstract int findInContext(String qualifier, String name, int kind);
|
||||
}
|
||||
|
||||
private static final char STATIC_PREFIX= 's';
|
||||
private static final char NORMAL_PREFIX= 'n';
|
||||
|
||||
private final ImportRewriteContext defaultContext;
|
||||
|
||||
private final CompilationUnit astRoot;
|
||||
|
||||
private final boolean restoreExistingImports;
|
||||
private final List existingImports;
|
||||
|
||||
|
||||
private List<String> addedImports;
|
||||
|
||||
/**
|
||||
* Simple names of non-static imports which must not be reduced into on-demand imports
|
||||
* or filtered out as implicit.
|
||||
*/
|
||||
private Set<String> typeExplicitSimpleNames;
|
||||
|
||||
|
||||
private boolean filterImplicitImports;
|
||||
private boolean useContextToFilterImplicitImports;
|
||||
|
||||
|
||||
/**
|
||||
* Creates an {@link ImportRewrite} from an AST ({@link CompilationUnit}). The AST has to be created from an
|
||||
* {@link ICompilationUnit}, that means {@link ASTParser#setSource(ICompilationUnit)} has been used when creating the
|
||||
* AST. If <code>restoreExistingImports</code> is <code>true</code>, all existing imports are kept, and new imports
|
||||
* will be inserted at best matching locations. If <code>restoreExistingImports</code> is <code>false</code>, the
|
||||
* existing imports will be removed and only the newly added imports will be created.
|
||||
* <p>
|
||||
* Note that this method is more efficient than using {@link #create(ICompilationUnit, boolean)} if an AST is already available.
|
||||
* </p>
|
||||
* @param astRoot the AST root node to create the imports for
|
||||
* @param restoreExistingImports specifies if the existing imports should be kept or removed.
|
||||
* @return the created import rewriter.
|
||||
* @throws IllegalArgumentException thrown when the passed AST is null or was not created from a compilation unit.
|
||||
*/
|
||||
public static ImportRewrite create(CompilationUnit astRoot, boolean restoreExistingImports) {
|
||||
if (astRoot == null) {
|
||||
throw new IllegalArgumentException("AST must not be null"); //$NON-NLS-1$
|
||||
}
|
||||
|
||||
List existingImport= null;
|
||||
if (restoreExistingImports) {
|
||||
existingImport= new ArrayList();
|
||||
List imports= astRoot.imports();
|
||||
for (int i= 0; i < imports.size(); i++) {
|
||||
ImportDeclaration curr= (ImportDeclaration) imports.get(i);
|
||||
StringBuffer buf= new StringBuffer();
|
||||
buf.append(curr.isStatic() ? STATIC_PREFIX : NORMAL_PREFIX).append(curr.getName().getFullyQualifiedName());
|
||||
if (curr.isOnDemand()) {
|
||||
if (buf.length() > 1)
|
||||
buf.append('.');
|
||||
buf.append('*');
|
||||
}
|
||||
existingImport.add(buf.toString());
|
||||
}
|
||||
}
|
||||
return new ImportRewrite(astRoot, existingImport);
|
||||
}
|
||||
|
||||
private ImportRewrite(CompilationUnit astRoot, List existingImports) {
|
||||
this.astRoot= astRoot; // might be null
|
||||
if (existingImports != null) {
|
||||
this.existingImports= existingImports;
|
||||
this.restoreExistingImports= !existingImports.isEmpty();
|
||||
} else {
|
||||
this.existingImports= new ArrayList();
|
||||
this.restoreExistingImports= false;
|
||||
}
|
||||
this.filterImplicitImports= true;
|
||||
// consider that no contexts are used
|
||||
this.useContextToFilterImplicitImports = false;
|
||||
|
||||
this.defaultContext= new ImportRewriteContext() {
|
||||
@Override
|
||||
public int findInContext(String qualifier, String name, int kind) {
|
||||
return findInImports(qualifier, name, kind);
|
||||
}
|
||||
};
|
||||
this.addedImports= new ArrayList<>();
|
||||
this.typeExplicitSimpleNames = new HashSet<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default rewrite context that only knows about the imported types. Clients
|
||||
* can write their own context and use the default context for the default behavior.
|
||||
* @return the default import rewrite context.
|
||||
*/
|
||||
public ImportRewriteContext getDefaultImportRewriteContext() {
|
||||
return this.defaultContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies that implicit imports (for types in <code>java.lang</code>, types in the same package as the rewrite
|
||||
* compilation unit, and types in the compilation unit's main type) should not be created, except if necessary to
|
||||
* resolve an on-demand import conflict.
|
||||
* <p>
|
||||
* The filter is enabled by default.
|
||||
* </p>
|
||||
* <p>
|
||||
* Note: {@link #setUseContextToFilterImplicitImports(boolean)} can be used to filter implicit imports
|
||||
* when a context is used.
|
||||
* </p>
|
||||
*
|
||||
* @param filterImplicitImports
|
||||
* if <code>true</code>, implicit imports will be filtered
|
||||
*
|
||||
* @see #setUseContextToFilterImplicitImports(boolean)
|
||||
*/
|
||||
public void setFilterImplicitImports(boolean filterImplicitImports) {
|
||||
this.filterImplicitImports= filterImplicitImports;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether a context should be used to properly filter implicit imports.
|
||||
* <p>
|
||||
* By default, the option is disabled to preserve pre-3.6 behavior.
|
||||
* </p>
|
||||
* <p>
|
||||
* When this option is set, the context passed to the <code>addImport*(...)</code> methods is used to determine
|
||||
* whether an import can be filtered because the type is implicitly visible. Note that too many imports
|
||||
* may be kept if this option is set and <code>addImport*(...)</code> methods are called without a context.
|
||||
* </p>
|
||||
*
|
||||
* @param useContextToFilterImplicitImports the given setting
|
||||
*
|
||||
* @see #setFilterImplicitImports(boolean)
|
||||
* @since 3.6
|
||||
*/
|
||||
public void setUseContextToFilterImplicitImports(boolean useContextToFilterImplicitImports) {
|
||||
this.useContextToFilterImplicitImports = useContextToFilterImplicitImports;
|
||||
}
|
||||
|
||||
private static int compareImport(char prefix, String qualifier, String name, String curr) {
|
||||
if (curr.charAt(0) != prefix || !curr.endsWith(name)) {
|
||||
return ImportRewriteContext.RES_NAME_UNKNOWN;
|
||||
}
|
||||
|
||||
curr= curr.substring(1); // remove the prefix
|
||||
|
||||
if (curr.length() == name.length()) {
|
||||
if (qualifier.length() == 0) {
|
||||
return ImportRewriteContext.RES_NAME_FOUND;
|
||||
}
|
||||
return ImportRewriteContext.RES_NAME_CONFLICT;
|
||||
}
|
||||
// at this place: curr.length > name.length
|
||||
|
||||
int dotPos= curr.length() - name.length() - 1;
|
||||
if (curr.charAt(dotPos) != '.') {
|
||||
return ImportRewriteContext.RES_NAME_UNKNOWN;
|
||||
}
|
||||
if (qualifier.length() != dotPos || !curr.startsWith(qualifier)) {
|
||||
return ImportRewriteContext.RES_NAME_CONFLICT;
|
||||
}
|
||||
return ImportRewriteContext.RES_NAME_FOUND;
|
||||
}
|
||||
|
||||
/**
|
||||
* Not API, package visibility as accessed from an anonymous type
|
||||
*/
|
||||
/* package */ final int findInImports(String qualifier, String name, int kind) {
|
||||
boolean allowAmbiguity= (kind == ImportRewriteContext.KIND_STATIC_METHOD) || (name.length() == 1 && name.charAt(0) == '*');
|
||||
List imports= this.existingImports;
|
||||
char prefix= (kind == ImportRewriteContext.KIND_TYPE) ? NORMAL_PREFIX : STATIC_PREFIX;
|
||||
|
||||
for (int i= imports.size() - 1; i >= 0 ; i--) {
|
||||
String curr= (String) imports.get(i);
|
||||
int res= compareImport(prefix, qualifier, name, curr);
|
||||
if (res != ImportRewriteContext.RES_NAME_UNKNOWN) {
|
||||
if (!allowAmbiguity || res == ImportRewriteContext.RES_NAME_FOUND) {
|
||||
if (prefix != STATIC_PREFIX) {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String packageName = getPackageName();
|
||||
if (kind == ImportRewriteContext.KIND_TYPE) {
|
||||
if (this.filterImplicitImports && this.useContextToFilterImplicitImports) {
|
||||
|
||||
// [STS4] No ICompilationUnit available as there is no class file or associated IJavaElement available for the source
|
||||
|
||||
// String mainTypeSimpleName= JavaCore.removeJavaLikeExtension(this.compilationUnit.getElementName());
|
||||
// String mainTypeName= Util.concatenateName(packageName, mainTypeSimpleName, '.');
|
||||
// if (qualifier.equals(packageName)
|
||||
// || mainTypeName.equals(Util.concatenateName(qualifier, name, '.'))) {
|
||||
// return ImportRewriteContext.RES_NAME_FOUND;
|
||||
// }
|
||||
|
||||
if (this.astRoot != null) {
|
||||
List<AbstractTypeDeclaration> types = this.astRoot.types();
|
||||
int nTypes = types.size();
|
||||
for (int i = 0; i < nTypes; i++) {
|
||||
AbstractTypeDeclaration type = types.get(i);
|
||||
SimpleName simpleName = type.getName();
|
||||
if (simpleName.getIdentifier().equals(name)) {
|
||||
return qualifier.equals(packageName)
|
||||
? ImportRewriteContext.RES_NAME_FOUND
|
||||
: ImportRewriteContext.RES_NAME_CONFLICT;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
// [STS4] No ICompilationUnit available as there is no class file or associated IJavaElement available for the source
|
||||
// try {
|
||||
// IType[] types = this.compilationUnit.getTypes();
|
||||
// int nTypes = types.length;
|
||||
// for (int i = 0; i < nTypes; i++) {
|
||||
// IType type = types[i];
|
||||
// String typeName = type.getElementName();
|
||||
// if (typeName.equals(name)) {
|
||||
// return qualifier.equals(packageName)
|
||||
// ? ImportRewriteContext.RES_NAME_FOUND
|
||||
// : ImportRewriteContext.RES_NAME_CONFLICT;
|
||||
// }
|
||||
// }
|
||||
// } catch (JavaModelException e) {
|
||||
// // don't want to throw an exception here
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ImportRewriteContext.RES_NAME_UNKNOWN;
|
||||
}
|
||||
|
||||
private String getPackageName() {
|
||||
// [STS4] No ICompilationUnit available as there is no class file or associated IJavaElement available for the source
|
||||
// this.compilationUnit.getParent().getElementName();
|
||||
return this.astRoot.getPackage().getName().getFullyQualifiedName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new import to the rewriter's record and returns a type reference that can be used
|
||||
* in the code. The type binding can only be an array or non-generic type.
|
||||
* <p>
|
||||
* No imports are added for types that are already known. If a import for a type is recorded to be removed, this record is discarded instead.
|
||||
* </p>
|
||||
* <p>
|
||||
* The content of the compilation unit itself is actually not modified
|
||||
* in any way by this method; rather, the rewriter just records that a new import has been added.
|
||||
* </p>
|
||||
* @param qualifiedTypeName the qualified type name of the type to be added
|
||||
* @param context an optional context that knows about types visible in the current scope or <code>null</code>
|
||||
* to use the default context only using the available imports.
|
||||
* @return a type reference for the given qualified type name. The type name is a simple name if an import could be used,
|
||||
* or else a qualified name if an import conflict prevented an import.
|
||||
*/
|
||||
public String addImport(String qualifiedTypeName, ImportRewriteContext context) {
|
||||
int angleBracketOffset= qualifiedTypeName.indexOf('<');
|
||||
if (angleBracketOffset != -1) {
|
||||
return internalAddImport(qualifiedTypeName.substring(0, angleBracketOffset), context) + qualifiedTypeName.substring(angleBracketOffset);
|
||||
}
|
||||
int bracketOffset= qualifiedTypeName.indexOf('[');
|
||||
if (bracketOffset != -1) {
|
||||
return internalAddImport(qualifiedTypeName.substring(0, bracketOffset), context) + qualifiedTypeName.substring(bracketOffset);
|
||||
}
|
||||
return internalAddImport(qualifiedTypeName, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new import to the rewriter's record and returns a type reference that can be used
|
||||
* in the code. The type binding can only be an array or non-generic type.
|
||||
* <p>
|
||||
* No imports are added for types that are already known. If a import for a type is recorded to be removed, this record is discarded instead.
|
||||
* </p>
|
||||
* <p>
|
||||
* The content of the compilation unit itself is actually not modified
|
||||
* in any way by this method; rather, the rewriter just records that a new import has been added.
|
||||
* </p>
|
||||
* @param qualifiedTypeName the qualified type name of the type to be added
|
||||
* @return a type reference for the given qualified type name. The type name is a simple name if an import could be used,
|
||||
* or else a qualified name if an import conflict prevented an import.
|
||||
*/
|
||||
public String addImport(String qualifiedTypeName) {
|
||||
return addImport(qualifiedTypeName, this.defaultContext);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private String internalAddImport(String fullTypeName, ImportRewriteContext context) {
|
||||
int idx= fullTypeName.lastIndexOf('.');
|
||||
String typeContainerName, typeName;
|
||||
if (idx != -1) {
|
||||
typeContainerName= fullTypeName.substring(0, idx);
|
||||
typeName= fullTypeName.substring(idx + 1);
|
||||
} else {
|
||||
typeContainerName= ""; //$NON-NLS-1$
|
||||
typeName= fullTypeName;
|
||||
}
|
||||
|
||||
if (typeContainerName.length() == 0 && PrimitiveType.toCode(typeName) != null) {
|
||||
return fullTypeName;
|
||||
}
|
||||
|
||||
if (context == null)
|
||||
context= this.defaultContext;
|
||||
|
||||
int res= context.findInContext(typeContainerName, typeName, ImportRewriteContext.KIND_TYPE);
|
||||
if (res == ImportRewriteContext.RES_NAME_CONFLICT) {
|
||||
return fullTypeName;
|
||||
}
|
||||
if (res == ImportRewriteContext.RES_NAME_UNKNOWN) {
|
||||
addEntry(NORMAL_PREFIX + fullTypeName);
|
||||
}
|
||||
if (res == ImportRewriteContext.RES_NAME_UNKNOWN_NEEDS_EXPLICIT_IMPORT) {
|
||||
addEntry(NORMAL_PREFIX + fullTypeName);
|
||||
this.typeExplicitSimpleNames.add(typeName);
|
||||
}
|
||||
return typeName;
|
||||
}
|
||||
|
||||
private void addEntry(String entry) {
|
||||
this.existingImports.add(entry);
|
||||
|
||||
this.addedImports.add(entry);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns all non-static imports that are recorded to be added.
|
||||
*
|
||||
* @return the imports recorded to be added.
|
||||
*/
|
||||
public String[] getAddedImports() {
|
||||
return filterFromList(this.addedImports, NORMAL_PREFIX);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns <code>true</code> if imports have been recorded to be added or removed.
|
||||
* @return boolean returns if any changes to imports have been recorded.
|
||||
*/
|
||||
public boolean hasRecordedChanges() {
|
||||
return !this.restoreExistingImports
|
||||
|| !this.addedImports.isEmpty();
|
||||
}
|
||||
|
||||
private static String[] filterFromList(List<String> imports, char prefix) {
|
||||
if (imports == null) {
|
||||
return CharOperation.NO_STRINGS;
|
||||
}
|
||||
List<String> res= new ArrayList<>();
|
||||
for (String curr : imports) {
|
||||
if (prefix == curr.charAt(0)) {
|
||||
res.add(curr.substring(1));
|
||||
}
|
||||
}
|
||||
return res.toArray(new String[res.size()]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reads the positions of each existing import declaration along with any associated comments,
|
||||
* and returns these in a list whose iteration order reflects the existing order of the imports
|
||||
* in the compilation unit.
|
||||
*/
|
||||
private int getAddedImportsInsertLocation() {
|
||||
List<ImportDeclaration> importDeclarations = astRoot.imports();
|
||||
|
||||
if (importDeclarations == null) {
|
||||
importDeclarations = Collections.emptyList();
|
||||
}
|
||||
|
||||
List<Comment> comments = astRoot.getCommentList();
|
||||
|
||||
int currentCommentIndex = 0;
|
||||
|
||||
// Skip over package and file header comments (see https://bugs.eclipse.org/121428).
|
||||
ImportDeclaration firstImport = importDeclarations.get(0);
|
||||
PackageDeclaration packageDeclaration = astRoot.getPackage();
|
||||
int firstImportStartPosition = packageDeclaration == null
|
||||
? firstImport.getStartPosition()
|
||||
: astRoot.getExtendedStartPosition(packageDeclaration)
|
||||
+ astRoot.getExtendedLength(packageDeclaration);
|
||||
while (currentCommentIndex < comments.size()
|
||||
&& comments.get(currentCommentIndex).getStartPosition() < firstImportStartPosition) {
|
||||
currentCommentIndex++;
|
||||
}
|
||||
|
||||
int previousExtendedEndPosition = -1;
|
||||
for (ImportDeclaration currentImport : importDeclarations) {
|
||||
int extendedEndPosition = astRoot.getExtendedStartPosition(currentImport)
|
||||
+ astRoot.getExtendedLength(currentImport);
|
||||
|
||||
int commentAfterImportIndex = currentCommentIndex;
|
||||
while (commentAfterImportIndex < comments.size()
|
||||
&& comments.get(commentAfterImportIndex).getStartPosition() < extendedEndPosition) {
|
||||
commentAfterImportIndex++;
|
||||
}
|
||||
|
||||
|
||||
currentCommentIndex = commentAfterImportIndex;
|
||||
previousExtendedEndPosition = extendedEndPosition;
|
||||
}
|
||||
|
||||
return previousExtendedEndPosition;
|
||||
}
|
||||
|
||||
public DocumentEdits createEdit(IDocument doc) {
|
||||
DocumentEdits edits = null;
|
||||
StringBuffer buffer = new StringBuffer();
|
||||
|
||||
String[] createdImprts = getAddedImports();
|
||||
if (createdImprts != null && createdImprts.length >0) {
|
||||
edits =new DocumentEdits(doc);
|
||||
buffer.append('\n');
|
||||
for (String imp : createdImprts) {
|
||||
buffer.append("import ");
|
||||
buffer.append(imp);
|
||||
buffer.append(';');
|
||||
buffer.append('\n');
|
||||
}
|
||||
edits.insert(getAddedImportsInsertLocation(), buffer.toString());
|
||||
|
||||
}
|
||||
return edits;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2018 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.links;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Optional;
|
||||
import java.util.Stack;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.eclipse.jdt.core.dom.ASTVisitor;
|
||||
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
|
||||
import org.eclipse.jdt.core.dom.AnnotationTypeDeclaration;
|
||||
import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||
import org.eclipse.jdt.core.dom.TypeDeclaration;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ide.vscode.boot.java.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.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.javadoc.SourceUrlProviderFromSourceContainer;
|
||||
import org.springframework.ide.vscode.commons.util.text.Region;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
import com.google.common.base.Supplier;
|
||||
import com.google.common.base.Suppliers;
|
||||
|
||||
/**
|
||||
* Base logic for {@link SourceLinks} independent of any client
|
||||
*
|
||||
* @author Alex Boyko
|
||||
*
|
||||
*/
|
||||
public abstract class AbstractSourceLinks implements SourceLinks {
|
||||
|
||||
private static Supplier<Logger> LOG = Suppliers.memoize(() -> LoggerFactory.getLogger(AbstractSourceLinks.class));
|
||||
|
||||
private BootJavaLanguageServerComponents server;
|
||||
|
||||
protected AbstractSourceLinks(BootJavaLanguageServerComponents server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> sourceLinkUrlForFQName(IJavaProject project, String fqName) {
|
||||
Optional<File> classpathResource = project.getClasspath().findClasspathResourceContainer(fqName);
|
||||
if (classpathResource.isPresent()) {
|
||||
File file = classpathResource.get();
|
||||
if (file.isDirectory()) {
|
||||
return javaSourceLinkUrl(project, fqName, file);
|
||||
} else if (file.getName().endsWith(JAR)) {
|
||||
return jarSourceLinkUrl(project, fqName, file);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> sourceLinkUrlForClasspathResource(IJavaProject project, String path) {
|
||||
int idx = path.lastIndexOf(CLASS);
|
||||
if (idx >= 0) {
|
||||
Path p = Paths.get(path.substring(0, idx));
|
||||
return sourceLinkUrlForFQName(project, p.toString().replace(File.separator, "."));
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private Optional<String> javaSourceLinkUrl(IJavaProject project, String fqName, File containerFolder) {
|
||||
IClasspath classpath = project.getClasspath();
|
||||
if (containerFolder.toPath().startsWith(classpath.getOutputFolder())) {
|
||||
return project.getClasspath().getSourceFolders().stream()
|
||||
.map(sourceFolder -> {
|
||||
try {
|
||||
return Paths.get(sourceFolder).toUri().toURL();
|
||||
} catch (MalformedURLException e) {
|
||||
LOG.get().warn("Failed to convert source folder " + sourceFolder + "to URI." + fqName, e);
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.map(url -> {
|
||||
try {
|
||||
return SourceUrlProviderFromSourceContainer.SOURCE_FOLDER_URL_SUPPLIER.sourceUrl(url, fqName);
|
||||
} catch (Exception e) {
|
||||
LOG.get().warn("Failed to determine source URL from url=" + url + " fqName=" + fqName, e);
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.map(url -> {
|
||||
try {
|
||||
return Paths.get(url.toURI());
|
||||
} catch (URISyntaxException e) {
|
||||
LOG.get().warn("Failed to convert URL " + url + " to path." + fqName, e);
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter(sourcePath -> sourcePath != null && Files.exists(sourcePath))
|
||||
.findFirst()
|
||||
.map(sourcePath -> javaSourceLinkUrl(project, sourcePath, fqName));
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private String javaSourceLinkUrl(IJavaProject project, Path sourcePath, String fqName) {
|
||||
Optional<String> linkOptional = sourceLinkForResourcePath(sourcePath);
|
||||
if (linkOptional.isPresent()) {
|
||||
Optional<String> positionLink = findCUForJavaSourceFile(sourcePath).map(cu -> positionLink(cu, fqName));
|
||||
return positionLink.isPresent() ? linkOptional.get() + positionLink.get() : linkOptional.get();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
abstract protected String positionLink(CompilationUnit cu, String fqName);
|
||||
|
||||
private Optional<CompilationUnit> findCUForJavaSourceFile(Path resourcePath) {
|
||||
Optional<CompilationUnit> cu = findCUfromCache(resourcePath.toUri().toString());
|
||||
if (cu == null) {
|
||||
try {
|
||||
char[] bytes = new String(Files.readAllBytes(resourcePath), Charset.defaultCharset()).toCharArray();
|
||||
String uri = resourcePath.toUri().toString();
|
||||
String unitName = resourcePath.getFileName().toString();
|
||||
cu = Optional.ofNullable(CompilationUnitCache.parse(bytes, uri, unitName, new String[0]));
|
||||
} catch (Exception e) {
|
||||
LOG.get().warn("Failed to create CompilationUnit from " + resourcePath, e);
|
||||
cu = Optional.empty();
|
||||
}
|
||||
}
|
||||
return cu;
|
||||
}
|
||||
|
||||
private Optional<CompilationUnit> findCUfromCache(String uri) {
|
||||
Optional<CompilationUnit> cu = null;
|
||||
if (server != null && server.getCompilationUnitCache() != null) {
|
||||
TextDocument doc = server.getTextDocumentService().get(uri);
|
||||
if (doc != null) {
|
||||
cu = server.getCompilationUnitCache().withCompilationUnit(doc, compilationUnit -> compilationUnit == null ? null : Optional.of(compilationUnit));
|
||||
}
|
||||
}
|
||||
return cu;
|
||||
}
|
||||
|
||||
abstract protected Optional<String> jarUrl(IJavaProject project, String fqName, File jarFile);
|
||||
|
||||
private Optional<String> jarSourceLinkUrl(IJavaProject project, String fqName, File jarFile) {
|
||||
return jarUrl(project, fqName, jarFile).map(sourceUrl -> {
|
||||
Optional<String> positionLink = findCUForFQNameFromJar(project, jarFile, sourceUrl, fqName).map(cu -> positionLink(cu, fqName));
|
||||
return positionLink.isPresent() ? sourceUrl + positionLink.get() : sourceUrl;
|
||||
});
|
||||
}
|
||||
|
||||
private Optional<CompilationUnit> findCUForFQNameFromJar(IJavaProject project, File jarFile, String clientSourceUri, String fqName) {
|
||||
Optional<CompilationUnit> cu = findCUfromCache(clientSourceUri);
|
||||
if (cu == null) {
|
||||
cu = project.getClasspath().sourceContainer(jarFile)
|
||||
.map(url -> {
|
||||
try {
|
||||
return SourceUrlProviderFromSourceContainer.JAR_SOURCE_URL_PROVIDER.sourceUrl(url, fqName);
|
||||
} catch (Exception e) {
|
||||
LOG.get().warn("Failed to determine source URL from url=" + url + " fqName=" + fqName, e);
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.map(sourceUrl -> {
|
||||
InputStream openStream = null;
|
||||
try {
|
||||
openStream = sourceUrl.openStream();
|
||||
char[] bytes = IOUtils.toCharArray(openStream);
|
||||
String uri = sourceUrl.toURI().toString();
|
||||
String unitName = fqName;
|
||||
return CompilationUnitCache.parse(bytes, uri, unitName, new String[0]);
|
||||
} catch (Exception e) {
|
||||
LOG.get().warn("Failed to create CompilationUnit from " + sourceUrl, e);
|
||||
return null;
|
||||
} finally {
|
||||
if (openStream != null) {
|
||||
try {
|
||||
openStream.close();
|
||||
} catch (IOException e) {
|
||||
LOG.get().error("Failed to close stream from " + sourceUrl, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return cu;
|
||||
}
|
||||
|
||||
protected Region findTypeRegion(CompilationUnit cu, String fqName) {
|
||||
if (cu == null) {
|
||||
return null;
|
||||
}
|
||||
int[] values = new int[] {0, -1};
|
||||
int lastDotIndex = fqName.lastIndexOf('.');
|
||||
String packageName = fqName.substring(0, lastDotIndex);
|
||||
String typeName = fqName.substring(lastDotIndex + 1);
|
||||
if (packageName.equals(cu.getPackage().getName().getFullyQualifiedName())) {
|
||||
Stack<String> visitedType = new Stack<>();
|
||||
cu.accept(new ASTVisitor() {
|
||||
|
||||
private boolean visitDeclaration(AbstractTypeDeclaration node) {
|
||||
visitedType.push(node.getName().getIdentifier());
|
||||
if (values[1] < 0) {
|
||||
if (String.join("$", visitedType.toArray(new String[visitedType.size()])).equals(typeName)) {
|
||||
values[0] = node.getName().getStartPosition();
|
||||
values[1] = node.getName().getLength();
|
||||
}
|
||||
}
|
||||
return values[1] < 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(TypeDeclaration node) {
|
||||
return visitDeclaration(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(AnnotationTypeDeclaration node) {
|
||||
return visitDeclaration(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endVisit(AnnotationTypeDeclaration node) {
|
||||
visitedType.pop();
|
||||
super.endVisit(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endVisit(TypeDeclaration node) {
|
||||
visitedType.pop();
|
||||
super.endVisit(node);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
return values[1] < 0 ? null : new Region(values[0], values[1]);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2018 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.links;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.ide.vscode.boot.java.BootJavaLanguageServerComponents;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.LspClient;
|
||||
|
||||
/**
|
||||
* Factory for creating {@link SourceLinks}
|
||||
*
|
||||
* @author Alex Boyko
|
||||
*
|
||||
*/
|
||||
public final class SourceLinkFactory {
|
||||
|
||||
private static final SourceLinks NO_SOURCE_LINKS = new SourceLinks() {
|
||||
|
||||
@Override
|
||||
public Optional<String> sourceLinkUrlForFQName(IJavaProject project, String fqName) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> sourceLinkUrlForClasspathResource(IJavaProject project, String path) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> sourceLinkForResourcePath(Path path) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates {@link SourceLinks} for specific server based on client type
|
||||
* @param server the boot LS
|
||||
* @return appropriate source links object
|
||||
*/
|
||||
public static SourceLinks createSourceLinks(BootJavaLanguageServerComponents server) {
|
||||
switch (LspClient.currentClient()) {
|
||||
case VSCODE:
|
||||
return new VSCodeSourceLinks(server);
|
||||
default:
|
||||
return NO_SOURCE_LINKS;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2018 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.links;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
|
||||
/**
|
||||
* Instance is able to provide client specific URL links to navigate to a
|
||||
* specific file on the client given various types of data such as fully
|
||||
* qualified java type name or classpath resource or just some file resource
|
||||
* given by its path.
|
||||
*
|
||||
* @author Alex Boyko
|
||||
*
|
||||
*/
|
||||
public interface SourceLinks {
|
||||
|
||||
static final String JAR = ".jar";
|
||||
static final String CLASS = ".class";
|
||||
|
||||
/**
|
||||
* Creates link to source file defining the type passed with it's fully qualified name
|
||||
* @param project Java project in the context of which source file link is calculated
|
||||
* @param fqName type's fully qualified name
|
||||
* @return the link URL optional
|
||||
*/
|
||||
Optional<String> sourceLinkUrlForFQName(IJavaProject project, String fqName);
|
||||
|
||||
/**
|
||||
* Creates link to source file corresponding to a classpath resource
|
||||
* @param project project Java project in the context of which source file link is calculated
|
||||
* @param path the path to the classpath resource
|
||||
* @return the link URL optional
|
||||
*/
|
||||
Optional<String> sourceLinkUrlForClasspathResource(IJavaProject project, String path);
|
||||
|
||||
/**
|
||||
* Creates link to a file specified by it's path
|
||||
* @param path the resource path
|
||||
* @return the link URL optional
|
||||
*/
|
||||
Optional<String> sourceLinkForResourcePath(Path path);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2018 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.links;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ide.vscode.boot.java.BootJavaLanguageServerComponents;
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> sourceLinkForResourcePath(Path path) {
|
||||
return Optional.of(path.toUri().toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String positionLink(CompilationUnit cu, String fqName) {
|
||||
if (cu != null) {
|
||||
Region region = findTypeRegion(cu, fqName);
|
||||
if (region != null) {
|
||||
int column = cu.getColumnNumber(region.getOffset());
|
||||
int line = cu.getLineNumber(region.getOffset());
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append('#');
|
||||
sb.append(line);
|
||||
sb.append(',');
|
||||
sb.append(column + 1); // 1-based columns?
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<String> jarUrl(IJavaProject project, String fqName, File jarFile) {
|
||||
try {
|
||||
int lastDotIndex = fqName.lastIndexOf('.');
|
||||
String packageName = fqName.substring(0, lastDotIndex);
|
||||
String typeName = fqName.substring(lastDotIndex + 1);
|
||||
String jarFileName = jarFile.getName();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("jdt://contents/");
|
||||
sb.append(jarFileName);
|
||||
sb.append("/");
|
||||
sb.append(packageName);
|
||||
sb.append("/");
|
||||
sb.append(typeName);
|
||||
sb.append(CLASS);
|
||||
sb.append("?");
|
||||
|
||||
StringBuilder query = new StringBuilder();
|
||||
query.append("=");
|
||||
query.append(project.getElementName());
|
||||
query.append("/");
|
||||
String convertedPath = jarFile.toString().replace(File.separator, "\\/");
|
||||
query.append(convertedPath);
|
||||
query.append("<");
|
||||
query.append(packageName);
|
||||
query.append("(");
|
||||
query.append(typeName);
|
||||
query.append(CLASS);
|
||||
|
||||
sb.append(URLEncoder.encode(query.toString(), "UTF8"));
|
||||
|
||||
return Optional.of(sb.toString());
|
||||
} catch (Throwable t) {
|
||||
LOG.get().warn("Failed creating source URI for jar " + jarFile + " type " + fqName + " in the context of project " + project.getElementName(), t);
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017, 2018 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.livehover;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.Annotation;
|
||||
import org.eclipse.jdt.core.dom.ITypeBinding;
|
||||
import org.eclipse.lsp4j.Hover;
|
||||
import org.eclipse.lsp4j.Range;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
||||
import org.springframework.ide.vscode.boot.java.BootJavaLanguageServerComponents;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.HoverProvider;
|
||||
import org.springframework.ide.vscode.boot.java.utils.ASTUtils;
|
||||
import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp;
|
||||
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.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
|
||||
import org.springframework.ide.vscode.commons.util.Log;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
public abstract class AbstractInjectedIntoHoverProvider implements HoverProvider {
|
||||
|
||||
protected BootJavaLanguageServerComponents server;
|
||||
|
||||
public AbstractInjectedIntoHoverProvider(BootJavaLanguageServerComponents server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Range> getLiveHoverHints(Annotation annotation, TextDocument doc, SpringBootApp[] runningApps) {
|
||||
// Highlight if any running app contains an instance of this component
|
||||
try {
|
||||
if (runningApps.length > 0) {
|
||||
LiveBean definedBean = getDefinedBean(annotation);
|
||||
if (definedBean != null) {
|
||||
if (Stream.of(runningApps).anyMatch(app -> LiveHoverUtils.hasRelevantBeans(app, definedBean))) {
|
||||
Optional<Range> nameRange = ASTUtils.nameRange(doc, annotation);
|
||||
if (nameRange.isPresent()) {
|
||||
return ImmutableList.of(nameRange.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.log(e);
|
||||
}
|
||||
return ImmutableList.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Hover provideHover(ASTNode node, Annotation annotation, ITypeBinding type, int offset,
|
||||
TextDocument doc, IJavaProject project, SpringBootApp[] runningApps) {
|
||||
if (runningApps.length > 0) {
|
||||
|
||||
LiveBean definedBean = getDefinedBean(annotation);
|
||||
if (definedBean != null) {
|
||||
StringBuilder hover = new StringBuilder();
|
||||
hover.append("**Injection report for " + LiveHoverUtils.showBean(definedBean) + "**\n\n");
|
||||
|
||||
boolean hasInterestingApp = false;
|
||||
for (SpringBootApp app : runningApps) {
|
||||
LiveBeansModel beans = app.getBeans();
|
||||
List<LiveBean> relevantBeans = LiveHoverUtils.findRelevantBeans(app, definedBean).collect(Collectors.toList());
|
||||
|
||||
if (!relevantBeans.isEmpty()) {
|
||||
if (!hasInterestingApp) {
|
||||
hasInterestingApp = true;
|
||||
} else {
|
||||
hover.append("\n\n");
|
||||
}
|
||||
hover.append(LiveHoverUtils.niceAppName(app) + ":");
|
||||
|
||||
for (LiveBean bean : relevantBeans) {
|
||||
addInjectedInto(definedBean, hover, beans, bean, project);
|
||||
addAutomaticallyWiredContructor(hover, annotation, beans, bean, project);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hasInterestingApp) {
|
||||
return new Hover(ImmutableList.of(Either.forLeft(hover.toString())));
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected abstract LiveBean getDefinedBean(Annotation annotation);
|
||||
|
||||
protected void addAutomaticallyWiredContructor(StringBuilder hover, Annotation annotation, LiveBeansModel beans, LiveBean bean, IJavaProject project) {
|
||||
//This doesn't really belong here, but it accomodates Martin's additional logic to handle implicitly
|
||||
//@Autowired constructor.
|
||||
//This does nothing by default as its really only relevant to @Component annotation report.
|
||||
}
|
||||
|
||||
protected void addInjectedInto(LiveBean definedBean, StringBuilder hover, LiveBeansModel beans, LiveBean bean, IJavaProject project) {
|
||||
hover.append("\n\n");
|
||||
List<LiveBean> dependers = beans.getBeansDependingOn(bean.getId());
|
||||
if (dependers.isEmpty()) {
|
||||
hover.append(LiveHoverUtils.showBean(bean) + " exists but is **Not injected anywhere**\n");
|
||||
} else {
|
||||
hover.append(LiveHoverUtils.showBean(bean) + " injected into:\n\n");
|
||||
boolean firstDependency = true;
|
||||
for (LiveBean dependingBean : dependers) {
|
||||
if (!firstDependency) {
|
||||
hover.append("\n");
|
||||
}
|
||||
hover.append("- " + LiveHoverUtils.showBeanWithResource(server, dependingBean, " ", project));
|
||||
firstDependency = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017, 2018 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.livehover;
|
||||
|
||||
import static org.springframework.ide.vscode.boot.java.utils.ASTUtils.nameRange;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.ASTVisitor;
|
||||
import org.eclipse.jdt.core.dom.Annotation;
|
||||
import org.eclipse.jdt.core.dom.ITypeBinding;
|
||||
import org.eclipse.jdt.core.dom.StringLiteral;
|
||||
import org.eclipse.jdt.core.dom.TypeDeclaration;
|
||||
import org.eclipse.lsp4j.Hover;
|
||||
import org.eclipse.lsp4j.Range;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.HoverProvider;
|
||||
import org.springframework.ide.vscode.boot.java.utils.ASTUtils;
|
||||
import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.util.Log;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableList.Builder;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
/**
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class ActiveProfilesProvider implements HoverProvider {
|
||||
|
||||
@Override
|
||||
public Hover provideHover(
|
||||
ASTNode node,
|
||||
Annotation annotation,
|
||||
ITypeBinding type,
|
||||
int offset,
|
||||
TextDocument doc, IJavaProject project, SpringBootApp[] runningApps
|
||||
) {
|
||||
if (runningApps.length>0) {
|
||||
StringBuilder markdown = new StringBuilder();
|
||||
markdown.append("**Active Profiles**\n\n");
|
||||
boolean hasInterestingApp = false;
|
||||
for (SpringBootApp app : runningApps) {
|
||||
List<String> profiles = app.getActiveProfiles();
|
||||
if (profiles==null) {
|
||||
markdown.append(niceAppName(app)+" : _Unknown_\n\n");
|
||||
} else {
|
||||
hasInterestingApp = true;
|
||||
if (profiles.isEmpty()) {
|
||||
markdown.append(niceAppName(app)+" : _None_\n\n");
|
||||
} else {
|
||||
markdown.append(niceAppName(app)+" :\n");
|
||||
for (String profile : profiles) {
|
||||
markdown.append("- "+profile+"\n");
|
||||
}
|
||||
markdown.append("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hasInterestingApp) {
|
||||
return new Hover(
|
||||
ImmutableList.of(Either.forLeft(markdown.toString()))
|
||||
);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String niceAppName(SpringBootApp app) {
|
||||
return "Process [PID="+app.getProcessID()+", name=`"+app.getProcessName()+"`]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Range> getLiveHoverHints(Annotation annotation, TextDocument doc, SpringBootApp[] runningApps) {
|
||||
if (runningApps.length > 0) {
|
||||
Builder<Range> ranges = ImmutableList.builder();
|
||||
nameRange(doc, annotation).ifPresent(ranges::add);
|
||||
|
||||
Set<String> allActiveProfiles = getAllActiveProfiles(runningApps);
|
||||
annotation.accept(new ASTVisitor() {
|
||||
@Override
|
||||
public boolean visit(StringLiteral node) {
|
||||
String value = ASTUtils.getLiteralValue(node);
|
||||
if (value!=null && allActiveProfiles.contains(value)) {
|
||||
rangeOf(doc, node).ifPresent(ranges::add);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
return ranges.build();
|
||||
}
|
||||
return ImmutableList.of();
|
||||
}
|
||||
|
||||
private static Set<String> getAllActiveProfiles(SpringBootApp[] runningApps) {
|
||||
ImmutableSet.Builder<String> builder = ImmutableSet.builder();
|
||||
for (SpringBootApp app : runningApps) {
|
||||
List<String> profiles = app.getActiveProfiles();
|
||||
if (profiles!=null) {
|
||||
builder.addAll(app.getActiveProfiles());
|
||||
}
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private static Optional<Range> rangeOf(TextDocument doc, StringLiteral node) {
|
||||
try {
|
||||
int start = node.getStartPosition();
|
||||
int end = start + node.getLength();
|
||||
if (doc.getSafeChar(start)=='"') {
|
||||
start++;
|
||||
}
|
||||
if (doc.getSafeChar(end-1)=='"') {
|
||||
end--;
|
||||
}
|
||||
return Optional.of(doc.toRange(start, end-start));
|
||||
} catch (Exception e) {
|
||||
Log.log(e);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Hover provideHover(ASTNode node, TypeDeclaration typeDeclaration, ITypeBinding type, int offset,
|
||||
TextDocument doc, IJavaProject project, SpringBootApp[] runningApps) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Range> getLiveHoverHints(TypeDeclaration typeDeclaration, TextDocument doc,
|
||||
SpringBootApp[] runningApps) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017, 2018 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.livehover;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.Annotation;
|
||||
import org.eclipse.jdt.core.dom.ITypeBinding;
|
||||
import org.eclipse.jdt.core.dom.MethodDeclaration;
|
||||
import org.eclipse.jdt.core.dom.TypeDeclaration;
|
||||
import org.eclipse.lsp4j.Hover;
|
||||
import org.eclipse.lsp4j.Range;
|
||||
import org.springframework.ide.vscode.boot.java.BootJavaLanguageServerComponents;
|
||||
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;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
|
||||
import org.springframework.ide.vscode.commons.util.Optionals;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
public class BeanInjectedIntoHoverProvider extends AbstractInjectedIntoHoverProvider {
|
||||
|
||||
public BeanInjectedIntoHoverProvider(BootJavaLanguageServerComponents server) {
|
||||
super(server);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LiveBean getDefinedBean(Annotation annotation) {
|
||||
MethodDeclaration beanMethod = ASTUtils.getAnnotatedMethod(annotation);
|
||||
if (beanMethod!=null) {
|
||||
Optional<String> beanId = getBeanId(annotation, beanMethod);
|
||||
if (beanId.isPresent()) {
|
||||
//TODO: we could try to be more precise here and determine the bean type from the
|
||||
//ITypeBinding beanType = null; //null means unknown
|
||||
// method signature, however, this will typically give us a more abstract type than
|
||||
// the actual bean type at runtime. So if we we do that we have to deal with that
|
||||
// somehow. Therefore, for the time being we leave the beanType as `unknown` and
|
||||
// so do not use the bean type in determining relevant beans.
|
||||
// Type unresolvedBeanType = beanMethod.getReturnType2();
|
||||
// if (unresolvedBeanType!=null) {
|
||||
// beanType = unresolvedBeanType.resolveBinding();
|
||||
// }
|
||||
return LiveBean.builder()
|
||||
.id(beanId.get())
|
||||
// .type(type)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Optional<String> getBeanId(Annotation annotation, MethodDeclaration beanMethod) {
|
||||
//Note: must handle all these cases:
|
||||
// @Bean
|
||||
// @Bean("beanId")
|
||||
// @Bean({"beanId", "alias1"})
|
||||
// @Bean(value="beanId")
|
||||
// @Bean(value={"beanId", "alias1"})
|
||||
// @Bean(name="beanId", ...)
|
||||
// @Bean(name={"beanId", "alias1"}, ...)
|
||||
return Optionals.tryInOrder(
|
||||
() -> ASTUtils.getAttribute(annotation, "value").flatMap(ASTUtils::getFirstString),
|
||||
() -> ASTUtils.getAttribute(annotation, "name").flatMap(ASTUtils::getFirstString),
|
||||
() -> Optional.ofNullable(beanMethod.getName().getIdentifier())
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Hover provideHover(ASTNode node, TypeDeclaration typeDeclaration, ITypeBinding type, int offset,
|
||||
TextDocument doc, IJavaProject project, SpringBootApp[] runningApps) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Range> getLiveHoverHints(TypeDeclaration typeDeclaration, TextDocument doc,
|
||||
SpringBootApp[] runningApps) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017, 2018 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.livehover;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.Annotation;
|
||||
import org.eclipse.jdt.core.dom.ITypeBinding;
|
||||
import org.eclipse.jdt.core.dom.MarkerAnnotation;
|
||||
import org.eclipse.jdt.core.dom.MethodDeclaration;
|
||||
import org.eclipse.jdt.core.dom.TypeDeclaration;
|
||||
import org.eclipse.lsp4j.Hover;
|
||||
import org.eclipse.lsp4j.Range;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
||||
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.utils.ASTUtils;
|
||||
import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp;
|
||||
import org.springframework.ide.vscode.commons.boot.app.cli.livebean.LiveBean;
|
||||
import org.springframework.ide.vscode.commons.boot.app.cli.livebean.LiveBeansModel;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.util.Log;
|
||||
import org.springframework.ide.vscode.commons.util.StringUtil;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
public class ComponentInjectionsHoverProvider extends AbstractInjectedIntoHoverProvider {
|
||||
|
||||
public ComponentInjectionsHoverProvider(BootJavaLanguageServerComponents server) {
|
||||
super(server);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addAutomaticallyWiredContructor(StringBuilder hover, Annotation annotation, LiveBeansModel beans, LiveBean bean, IJavaProject project) {
|
||||
TypeDeclaration typeDecl = ASTUtils.findDeclaringType(annotation);
|
||||
if (typeDecl != null) {
|
||||
MethodDeclaration[] constructors = ASTUtils.findConstructors(typeDecl);
|
||||
|
||||
if (constructors != null && constructors.length == 1 && !hasAutowiredAnnotation(constructors[0])) {
|
||||
String[] dependencies = bean.getDependencies();
|
||||
|
||||
if (dependencies != null && dependencies.length > 0) {
|
||||
hover.append("\n\n");
|
||||
hover.append(LiveHoverUtils.showBean(bean) + " got autowired with:\n\n");
|
||||
|
||||
boolean firstDependency = true;
|
||||
for (String injectedBean : dependencies) {
|
||||
if (!firstDependency) {
|
||||
hover.append("\n");
|
||||
}
|
||||
List<LiveBean> dependencyBeans = beans.getBeansOfName(injectedBean);
|
||||
for (LiveBean dependencyBean : dependencyBeans) {
|
||||
hover.append("- " + LiveHoverUtils.showBeanWithResource(server, dependencyBean, " ", project));
|
||||
}
|
||||
firstDependency = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasAutowiredAnnotation(MethodDeclaration constructor) {
|
||||
List<?> modifiers = constructor.modifiers();
|
||||
for (Object modifier : modifiers) {
|
||||
if (modifier instanceof MarkerAnnotation) {
|
||||
ITypeBinding typeBinding = ((MarkerAnnotation) modifier).resolveTypeBinding();
|
||||
if (typeBinding != null && typeBinding.getQualifiedName().equals(Annotations.AUTOWIRED)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LiveBean getDefinedBean(Annotation annotation) {
|
||||
return getDefinedBeanForComponent(annotation);
|
||||
}
|
||||
|
||||
public static LiveBean getDefinedBeanForComponent(Annotation annotation) {
|
||||
//Move to ASTUtils?
|
||||
TypeDeclaration declaringType = ASTUtils.getAnnotatedType(annotation);
|
||||
return getDefinedBeanForType(declaringType, annotation);
|
||||
}
|
||||
|
||||
private static LiveBean getDefinedBeanForType(TypeDeclaration declaringType, Annotation annotation) {
|
||||
if (declaringType != null) {
|
||||
ITypeBinding beanType = declaringType.resolveBinding();
|
||||
if (beanType != null) {
|
||||
String id = getBeanId(annotation, beanType);
|
||||
if (StringUtil.hasText(id)) {
|
||||
return LiveBean.builder().id(id).type(beanType.getQualifiedName()).build();
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String getBeanId(Annotation annotation, ITypeBinding beanType) {
|
||||
return ASTUtils.getAttribute(annotation, "value").flatMap(ASTUtils::getFirstString)
|
||||
.orElseGet(() -> {
|
||||
String typeName = beanType.getName();
|
||||
|
||||
ITypeBinding declaringClass = beanType.getDeclaringClass();
|
||||
while (declaringClass != null) {
|
||||
typeName = declaringClass.getName() + "." + typeName;
|
||||
declaringClass = declaringClass.getDeclaringClass();
|
||||
}
|
||||
|
||||
if (StringUtil.hasText(typeName)) {
|
||||
return Character.toLowerCase(typeName.charAt(0)) + typeName.substring(1);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Range> getLiveHoverHints(TypeDeclaration typeDeclaration, TextDocument doc,
|
||||
SpringBootApp[] runningApps) {
|
||||
if (runningApps.length > 0 && !isComponentAnnotatedType(typeDeclaration)) {
|
||||
try {
|
||||
LiveBean definedBean = getDefinedBeanForType(typeDeclaration, null);
|
||||
if (definedBean != null) {
|
||||
if (Stream.of(runningApps).anyMatch(app -> LiveHoverUtils.hasRelevantBeans(app, definedBean))) {
|
||||
Optional<Range> nameRange = Optional.of(ASTUtils.nodeRegion(doc, typeDeclaration.getName()).asRange());
|
||||
if (nameRange.isPresent()) {
|
||||
return ImmutableList.of(nameRange.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.log(e);
|
||||
}
|
||||
}
|
||||
return ImmutableList.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Hover provideHover(ASTNode node, TypeDeclaration typeDeclaration, ITypeBinding type, int offset,
|
||||
TextDocument doc, IJavaProject project, SpringBootApp[] runningApps) {
|
||||
|
||||
if (runningApps.length > 0 && !isComponentAnnotatedType(typeDeclaration)) {
|
||||
|
||||
LiveBean definedBean = getDefinedBeanForType(typeDeclaration, null);
|
||||
if (definedBean != null) {
|
||||
StringBuilder hover = new StringBuilder();
|
||||
hover.append("**Injection report for " + LiveHoverUtils.showBean(definedBean) + "**\n\n");
|
||||
|
||||
boolean hasInterestingApp = false;
|
||||
for (SpringBootApp app : runningApps) {
|
||||
LiveBeansModel beans = app.getBeans();
|
||||
List<LiveBean> relevantBeans = LiveHoverUtils.findRelevantBeans(app, definedBean).collect(Collectors.toList());
|
||||
|
||||
if (!relevantBeans.isEmpty()) {
|
||||
if (!hasInterestingApp) {
|
||||
hasInterestingApp = true;
|
||||
} else {
|
||||
hover.append("\n\n");
|
||||
}
|
||||
hover.append(LiveHoverUtils.niceAppName(app) + ":");
|
||||
|
||||
for (LiveBean bean : relevantBeans) {
|
||||
addInjectedInto(definedBean, hover, beans, bean, project);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hasInterestingApp) {
|
||||
return new Hover(ImmutableList.of(Either.forLeft(hover.toString())));
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isComponentAnnotatedType(TypeDeclaration typeDeclaration) {
|
||||
List<?> modifiers = typeDeclaration.modifiers();
|
||||
for (Object modifier : modifiers) {
|
||||
if (modifier instanceof Annotation) {
|
||||
ITypeBinding typeBinding = ((Annotation) modifier).resolveTypeBinding();
|
||||
return isComponentAnnotation(typeBinding);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isComponentAnnotation(ITypeBinding type) {
|
||||
Set<String> transitiveSuperAnnotations = AnnotationHierarchies.getTransitiveSuperAnnotations(type);
|
||||
for (String annotationType : transitiveSuperAnnotations) {
|
||||
if (Annotations.COMPONENT.equals(annotationType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017, 2018 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.livehover;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
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;
|
||||
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.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
|
||||
import org.springframework.ide.vscode.commons.util.Renderables;
|
||||
import org.springframework.ide.vscode.commons.util.StringUtil;
|
||||
|
||||
public class LiveHoverUtils {
|
||||
|
||||
public static String showBean(LiveBean bean) {
|
||||
StringBuilder buf = new StringBuilder("Bean [id: " + bean.getId());
|
||||
String type = bean.getType(true);
|
||||
if (type != null) {
|
||||
buf.append(", type: `" + type + "`");
|
||||
}
|
||||
buf.append(']');
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
public static String showBeanWithResource(BootJavaLanguageServerComponents server, LiveBean bean, String indentStr, IJavaProject project) {
|
||||
String newline = " \n"+indentStr; //Note: the double space before newline makes markdown see it as a real line break
|
||||
|
||||
String type = bean.getType(true);
|
||||
|
||||
StringBuilder buf = new StringBuilder("Bean: ");
|
||||
buf.append(bean.getId());
|
||||
SourceLinks sourceLinks = SourceLinkFactory.createSourceLinks(server);
|
||||
if (type != null) {
|
||||
// Try creating a URL link to open source for the type
|
||||
buf.append(newline);
|
||||
buf.append("Type: ");
|
||||
Optional<String> url = sourceLinks.sourceLinkUrlForFQName(project, type);
|
||||
if (url.isPresent()) {
|
||||
buf.append(Renderables.link(type, url.get()).toMarkdown());
|
||||
} else {
|
||||
buf.append("`" + type + "`");
|
||||
}
|
||||
}
|
||||
String resource = bean.getResource();
|
||||
if (StringUtil.hasText(resource)) {
|
||||
buf.append(newline);
|
||||
buf.append("Resource: ");
|
||||
buf.append(showResource(sourceLinks, resource, project));
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
public static String showResource(SourceLinks sourceLinks, String resource, IJavaProject project) {
|
||||
return new SpringResource(sourceLinks, resource, project).toMarkdown();
|
||||
}
|
||||
|
||||
public static String niceAppName(SpringBootApp app) {
|
||||
return niceAppName(app.getProcessID() ,app.getProcessName());
|
||||
}
|
||||
|
||||
public static String niceAppName(String processId, String processName) {
|
||||
return "Process [PID=" + processId + ", name=`" + processName + "`]";
|
||||
}
|
||||
|
||||
public static boolean hasRelevantBeans(SpringBootApp app, LiveBean definedBean) {
|
||||
return findRelevantBeans(app, definedBean).findAny().isPresent();
|
||||
}
|
||||
|
||||
public static Stream<LiveBean> findRelevantBeans(SpringBootApp app, LiveBean definedBean) {
|
||||
LiveBeansModel beansModel = app.getBeans();
|
||||
if (beansModel != null) {
|
||||
Stream<LiveBean> relevantBeans = beansModel.getBeansOfName(definedBean.getId()).stream();
|
||||
String type = definedBean.getType();
|
||||
if (type != null) {
|
||||
relevantBeans = relevantBeans.filter(bean -> type.equals(bean.getType(true)));
|
||||
}
|
||||
return relevantBeans;
|
||||
}
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 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.requestmapping;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.lsp4j.Location;
|
||||
import org.eclipse.lsp4j.Position;
|
||||
import org.eclipse.lsp4j.Range;
|
||||
import org.eclipse.lsp4j.SymbolInformation;
|
||||
import org.eclipse.lsp4j.SymbolKind;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.RunningAppProvider;
|
||||
import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp;
|
||||
import org.springframework.ide.vscode.commons.util.Log;
|
||||
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
*/
|
||||
public class LiveAppURLSymbolProvider {
|
||||
|
||||
private final RunningAppProvider runningAppProvider;
|
||||
|
||||
public LiveAppURLSymbolProvider(RunningAppProvider runningAppProvider) {
|
||||
this.runningAppProvider = runningAppProvider;
|
||||
}
|
||||
|
||||
public List<? extends SymbolInformation> getSymbols(String query) {
|
||||
System.out.println(query);
|
||||
|
||||
List<SymbolInformation> result = new ArrayList<>();
|
||||
|
||||
try {
|
||||
SpringBootApp[] runningApps = runningAppProvider.getAllRunningSpringApps().toArray(new SpringBootApp[0]);
|
||||
for (SpringBootApp app : runningApps) {
|
||||
try {
|
||||
String host = app.getHost();
|
||||
String port = app.getPort();
|
||||
Stream<String> urls = app.getRequestMappings().stream()
|
||||
.flatMap(rm -> Arrays.stream(rm.getSplitPath()))
|
||||
.map(path -> UrlUtil.createUrl(host, port, path));
|
||||
urls.forEach(url -> result.add(new SymbolInformation(url, SymbolKind.Method, new Location(url, new Range(new Position(0, 0), new Position(0, 1))))));
|
||||
}
|
||||
catch (Exception e) {
|
||||
Log.log(e);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.log(e);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 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.requestmapping;
|
||||
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
*/
|
||||
public class RequestMappingCompletionProcessor {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017, 2018 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.requestmapping;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.Annotation;
|
||||
import org.eclipse.jdt.core.dom.IMethodBinding;
|
||||
import org.eclipse.jdt.core.dom.ITypeBinding;
|
||||
import org.eclipse.jdt.core.dom.MethodDeclaration;
|
||||
import org.eclipse.jdt.core.dom.TypeDeclaration;
|
||||
import org.eclipse.lsp4j.Hover;
|
||||
import org.eclipse.lsp4j.MarkedString;
|
||||
import org.eclipse.lsp4j.Range;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.HoverProvider;
|
||||
import org.springframework.ide.vscode.boot.java.livehover.LiveHoverUtils;
|
||||
import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp;
|
||||
import org.springframework.ide.vscode.commons.boot.app.cli.requestmappings.RequestMapping;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.util.BadLocationException;
|
||||
import org.springframework.ide.vscode.commons.util.Log;
|
||||
import org.springframework.ide.vscode.commons.util.Renderable;
|
||||
import org.springframework.ide.vscode.commons.util.Renderables;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
import reactor.util.function.Tuple2;
|
||||
import reactor.util.function.Tuples;
|
||||
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
*/
|
||||
public class RequestMappingHoverProvider implements HoverProvider {
|
||||
|
||||
@Override
|
||||
public Hover provideHover(ASTNode node, Annotation annotation,
|
||||
ITypeBinding type, int offset, TextDocument doc, IJavaProject project, SpringBootApp[] runningApps) {
|
||||
return provideHover(annotation, doc, runningApps);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Range> getLiveHoverHints(Annotation annotation, TextDocument doc, SpringBootApp[] runningApps) {
|
||||
try {
|
||||
if (runningApps.length > 0) {
|
||||
List<Tuple2<RequestMapping, SpringBootApp>> val = getRequestMappingMethodFromRunningApp(annotation, runningApps);
|
||||
if (!val.isEmpty()) {
|
||||
Range hoverRange = doc.toRange(annotation.getStartPosition(), annotation.getLength());
|
||||
return ImmutableList.of(hoverRange);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (BadLocationException e) {
|
||||
Log.log(e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private Hover provideHover(Annotation annotation, TextDocument doc, SpringBootApp[] runningApps) {
|
||||
|
||||
try {
|
||||
List<Either<String, MarkedString>> hoverContent = new ArrayList<>();
|
||||
|
||||
List<Tuple2<RequestMapping, SpringBootApp>> val = getRequestMappingMethodFromRunningApp(annotation, runningApps);
|
||||
|
||||
if (!val.isEmpty()) {
|
||||
addHoverContent(val, hoverContent);
|
||||
}
|
||||
|
||||
Range hoverRange = doc.toRange(annotation.getStartPosition(), annotation.getLength());
|
||||
Hover hover = new Hover();
|
||||
|
||||
hover.setContents(hoverContent);
|
||||
hover.setRange(hoverRange);
|
||||
|
||||
return hover;
|
||||
} catch (Exception e) {
|
||||
Log.log(e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private List<Tuple2<RequestMapping, SpringBootApp>> getRequestMappingMethodFromRunningApp(Annotation annotation,
|
||||
SpringBootApp[] runningApps) {
|
||||
|
||||
List<Tuple2<RequestMapping, SpringBootApp>> results = new ArrayList<>();
|
||||
try {
|
||||
for (SpringBootApp app : runningApps) {
|
||||
Collection<RequestMapping> mappings = app.getRequestMappings();
|
||||
if (mappings != null && !mappings.isEmpty()) {
|
||||
mappings.stream()
|
||||
.filter(rm -> methodMatchesAnnotation(annotation, rm))
|
||||
.map(rm -> Tuples.of(rm, app))
|
||||
.findFirst().ifPresent(t -> results.add(t));
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.log(e);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
private boolean methodMatchesAnnotation(Annotation annotation, RequestMapping rm) {
|
||||
String rqClassName = rm.getFullyQualifiedClassName();
|
||||
|
||||
if (rqClassName != null) {
|
||||
int chop = rqClassName.indexOf("$$EnhancerBySpringCGLIB$$");
|
||||
if (chop >= 0) {
|
||||
rqClassName = rqClassName.substring(0, chop);
|
||||
}
|
||||
|
||||
rqClassName = rqClassName.replace('$', '.');
|
||||
|
||||
ASTNode parent = annotation.getParent();
|
||||
if (parent instanceof MethodDeclaration) {
|
||||
MethodDeclaration methodDec = (MethodDeclaration) parent;
|
||||
IMethodBinding binding = methodDec.resolveBinding();
|
||||
return binding.getDeclaringClass().getQualifiedName().equals(rqClassName)
|
||||
&& binding.getName().equals(rm.getMethodName())
|
||||
&& Arrays.equals(Arrays.stream(binding.getParameterTypes())
|
||||
.map(t -> t.getTypeDeclaration().getQualifiedName())
|
||||
.toArray(String[]::new),
|
||||
rm.getMethodParameters());
|
||||
// } else if (parent instanceof TypeDeclaration) {
|
||||
// TypeDeclaration typeDec = (TypeDeclaration) parent;
|
||||
// return typeDec.resolveBinding().getQualifiedName().equals(rqClassName);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void addHoverContent(List<Tuple2<RequestMapping, SpringBootApp>> mappingMethods, List<Either<String, MarkedString>> hoverContent) throws Exception {
|
||||
for (int i = 0; i < mappingMethods.size(); i++) {
|
||||
Tuple2<RequestMapping, SpringBootApp> mappingMethod = mappingMethods.get(i);
|
||||
|
||||
SpringBootApp app = mappingMethod.getT2();
|
||||
String port = mappingMethod.getT2().getPort();
|
||||
String host = mappingMethod.getT2().getHost();
|
||||
|
||||
List<Renderable> renderableUrls = Arrays.stream(mappingMethod.getT1().getSplitPath()).flatMap(path -> {
|
||||
String url = UrlUtil.createUrl(host, port, path);
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("[");
|
||||
builder.append(url);
|
||||
builder.append("]");
|
||||
builder.append("(");
|
||||
builder.append(url);
|
||||
builder.append(")");
|
||||
return Stream.of(Renderables.text(builder.toString()), Renderables.lineBreak());
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// Remove the last line break
|
||||
renderableUrls.remove(renderableUrls.size() - 1);
|
||||
|
||||
hoverContent.add(Either.forLeft(Renderables.concat(renderableUrls).toMarkdown()));
|
||||
hoverContent.add(Either.forLeft(LiveHoverUtils.niceAppName(app)));
|
||||
if (i < mappingMethods.size() - 1) {
|
||||
// Three dashes == line separator in Markdown
|
||||
hoverContent.add(Either.forLeft("---"));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Hover provideHover(ASTNode node, TypeDeclaration typeDeclaration, ITypeBinding type, int offset,
|
||||
TextDocument doc, IJavaProject project, SpringBootApp[] runningApps) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Range> getLiveHoverHints(TypeDeclaration typeDeclaration, TextDocument doc,
|
||||
SpringBootApp[] runningApps) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017, 2018 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.requestmapping;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.Annotation;
|
||||
import org.eclipse.jdt.core.dom.Expression;
|
||||
import org.eclipse.jdt.core.dom.ITypeBinding;
|
||||
import org.eclipse.jdt.core.dom.MemberValuePair;
|
||||
import org.eclipse.jdt.core.dom.MethodDeclaration;
|
||||
import org.eclipse.jdt.core.dom.NormalAnnotation;
|
||||
import org.eclipse.jdt.core.dom.SingleMemberAnnotation;
|
||||
import org.eclipse.jdt.core.dom.TypeDeclaration;
|
||||
import org.eclipse.lsp4j.Location;
|
||||
import org.eclipse.lsp4j.SymbolInformation;
|
||||
import org.eclipse.lsp4j.SymbolKind;
|
||||
import org.springframework.ide.vscode.boot.java.Annotations;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.SymbolProvider;
|
||||
import org.springframework.ide.vscode.boot.java.utils.ASTUtils;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
*/
|
||||
public class RequestMappingSymbolProvider implements SymbolProvider {
|
||||
|
||||
@Override
|
||||
public Collection<SymbolInformation> getSymbols(Annotation node, ITypeBinding annotationType, Collection<ITypeBinding> metaAnnotations, TextDocument doc) {
|
||||
if (node.getParent() instanceof MethodDeclaration) {
|
||||
try {
|
||||
Location location = new Location(doc.getUri(), doc.toRange(node.getStartPosition(), node.getLength()));
|
||||
String[] path = getPath(node);
|
||||
String[] parentPath = getParentPath(node);
|
||||
String[] method = getMethod(node);
|
||||
|
||||
String methodStr = method == null || method.length == 0 ? "" : String.join(",", method);
|
||||
|
||||
return (parentPath == null ? Stream.of("") : Arrays.stream(parentPath)).filter(Objects::nonNull)
|
||||
.flatMap(parent -> (path == null ? Stream.<String>empty() : Arrays.stream(path))
|
||||
.filter(Objects::nonNull).map(p -> {
|
||||
String separator = !parent.endsWith("/") && !p.startsWith("/") ? "/" : "";
|
||||
String resultPath = parent + separator + p;
|
||||
if (resultPath.endsWith("/")) {
|
||||
resultPath = resultPath.substring(0, resultPath.length() - 1);
|
||||
}
|
||||
return resultPath.startsWith("/") ? resultPath : "/" + resultPath;
|
||||
}))
|
||||
.map(p -> "@" + p + (methodStr.isEmpty() ? "" : " -- " + methodStr))
|
||||
.map(symbolLabel -> new SymbolInformation(symbolLabel, SymbolKind.Interface, location))
|
||||
.collect(Collectors.toList());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String[] getMethod(Annotation node) {
|
||||
String[] methods = null;
|
||||
|
||||
if (node.isNormalAnnotation()) {
|
||||
NormalAnnotation normNode = (NormalAnnotation) node;
|
||||
List<?> values = normNode.values();
|
||||
for (Iterator<?> iterator = values.iterator(); iterator.hasNext();) {
|
||||
Object object = iterator.next();
|
||||
if (object instanceof MemberValuePair) {
|
||||
MemberValuePair pair = (MemberValuePair) object;
|
||||
String valueName = pair.getName().getIdentifier();
|
||||
if (valueName != null && valueName.equals("method")) {
|
||||
Expression expression = pair.getValue();
|
||||
methods = ASTUtils.getExpressionValueAsArray(expression);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (node instanceof SingleMemberAnnotation) {
|
||||
methods = getRequestMethod((SingleMemberAnnotation)node);
|
||||
}
|
||||
|
||||
if (methods == null && node.getParent() instanceof MethodDeclaration) {
|
||||
Annotation parentAnnotation = getParentAnnotation(node);
|
||||
if (parentAnnotation != null) {
|
||||
methods = getMethod(parentAnnotation);
|
||||
}
|
||||
}
|
||||
|
||||
return methods;
|
||||
}
|
||||
|
||||
private String[] getPath(Annotation node) {
|
||||
if (node.isNormalAnnotation()) {
|
||||
NormalAnnotation normNode = (NormalAnnotation) node;
|
||||
List<?> values = normNode.values();
|
||||
for (Iterator<?> iterator = values.iterator(); iterator.hasNext();) {
|
||||
Object object = iterator.next();
|
||||
if (object instanceof MemberValuePair) {
|
||||
MemberValuePair pair = (MemberValuePair) object;
|
||||
String valueName = pair.getName().getIdentifier();
|
||||
if (valueName != null && (valueName.equals("value") || valueName.equals("path"))) {
|
||||
Expression expression = pair.getValue();
|
||||
return ASTUtils.getExpressionValueAsArray(expression);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (node.isSingleMemberAnnotation()) {
|
||||
SingleMemberAnnotation singleNode = (SingleMemberAnnotation) node;
|
||||
Expression expression = singleNode.getValue();
|
||||
return ASTUtils.getExpressionValueAsArray(expression);
|
||||
}
|
||||
|
||||
return new String[] { "" };
|
||||
}
|
||||
|
||||
private String[] getParentPath(Annotation node) {
|
||||
Annotation parentAnnotation = getParentAnnotation(node);
|
||||
return parentAnnotation == null ? null : getPath(parentAnnotation);
|
||||
}
|
||||
|
||||
private Annotation getParentAnnotation(Annotation node) {
|
||||
ASTNode parent = node.getParent() != null ? node.getParent().getParent() : null;
|
||||
while (parent != null && !(parent instanceof TypeDeclaration)) {
|
||||
parent = parent.getParent();
|
||||
}
|
||||
|
||||
if (parent != null) {
|
||||
TypeDeclaration type = (TypeDeclaration) parent;
|
||||
List<?> modifiers = type.modifiers();
|
||||
Iterator<?> iterator = modifiers.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Object modifier = iterator.next();
|
||||
if (modifier instanceof Annotation) {
|
||||
Annotation annotation = (Annotation) modifier;
|
||||
ITypeBinding resolvedType = annotation.resolveTypeBinding();
|
||||
String annotationType = resolvedType.getQualifiedName();
|
||||
if (annotationType != null && Annotations.SPRING_REQUEST_MAPPING.equals(annotationType)) {
|
||||
return annotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String[] getRequestMethod(SingleMemberAnnotation annotation) {
|
||||
ITypeBinding type = annotation.resolveTypeBinding();
|
||||
if (type != null) {
|
||||
switch (type.getQualifiedName()) {
|
||||
case Annotations.SPRING_GET_MAPPING:
|
||||
return new String[] { "GET" };
|
||||
case Annotations.SPRING_POST_MAPPING:
|
||||
return new String[] { "POST" };
|
||||
case Annotations.SPRING_DELETE_MAPPING:
|
||||
return new String[] { "DELETE" };
|
||||
case Annotations.SPRING_PUT_MAPPING:
|
||||
return new String[] { "PUT" };
|
||||
case Annotations.SPRING_PATCH_MAPPING:
|
||||
return new String[] { "PATCH" };
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<SymbolInformation> getSymbols(TypeDeclaration typeDeclaration, TextDocument doc) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<SymbolInformation> getSymbols(MethodDeclaration methodDeclaration, TextDocument doc) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 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.requestmapping;
|
||||
|
||||
public class UrlUtil {
|
||||
|
||||
/**
|
||||
* Creates http URL string based on host, port and path
|
||||
* @param host
|
||||
* @param port
|
||||
* @param path
|
||||
* @return the resultant URL
|
||||
*/
|
||||
public static String createUrl(String host, String port, String path) {
|
||||
if (path==null) {
|
||||
path = "";
|
||||
}
|
||||
if (host!=null) {
|
||||
if (port != null) {
|
||||
if (!path.startsWith("/")) {
|
||||
path = "/" +path;
|
||||
}
|
||||
return "http://"+host+":"+port+path;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*******************************************************************************
|
||||
* 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.requestmapping;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.ASTVisitor;
|
||||
import org.eclipse.jdt.core.dom.IMethodBinding;
|
||||
import org.eclipse.jdt.core.dom.MethodInvocation;
|
||||
import org.eclipse.jdt.core.dom.QualifiedName;
|
||||
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
*/
|
||||
public class WebfluxMethodFinder extends ASTVisitor {
|
||||
|
||||
private String method;
|
||||
private ASTNode root;
|
||||
|
||||
public WebfluxMethodFinder(ASTNode root) {
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
public String getMethod() {
|
||||
return method;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(MethodInvocation node) {
|
||||
boolean visitChildren = true;
|
||||
|
||||
if (node != this.root) {
|
||||
IMethodBinding methodBinding = node.resolveMethodBinding();
|
||||
|
||||
if (WebfluxUtils.REQUEST_PREDICATES_TYPE.equals(methodBinding.getDeclaringClass().getBinaryName())) {
|
||||
String name = methodBinding.getName();
|
||||
if (name != null && WebfluxUtils.REQUEST_PREDICATE_HTTPMETHOD_METHODS.contains(name)) {
|
||||
method = name;
|
||||
}
|
||||
else if (name != null && WebfluxUtils.REQUEST_PREDICATE_METHOD_METHOD.equals(name)) {
|
||||
method = extractMethodValue(node);
|
||||
}
|
||||
}
|
||||
|
||||
if (WebfluxUtils.isRouteMethodInvocation(methodBinding)) {
|
||||
visitChildren = false;
|
||||
}
|
||||
}
|
||||
return visitChildren;
|
||||
}
|
||||
|
||||
private String extractMethodValue(MethodInvocation node) {
|
||||
List<?> arguments = node.arguments();
|
||||
if (arguments != null && arguments.size() > 0) {
|
||||
Object object = arguments.get(0);
|
||||
if (object instanceof QualifiedName) {
|
||||
QualifiedName qualifiedName = (QualifiedName) object;
|
||||
if (qualifiedName.getName() != null) {
|
||||
return qualifiedName.getName().toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.java.requestmapping;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.ASTVisitor;
|
||||
import org.eclipse.jdt.core.dom.IMethodBinding;
|
||||
import org.eclipse.jdt.core.dom.MethodInvocation;
|
||||
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
*/
|
||||
public class WebfluxPathFinder extends ASTVisitor {
|
||||
|
||||
private String path;
|
||||
private ASTNode root;
|
||||
|
||||
public WebfluxPathFinder(ASTNode root) {
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(MethodInvocation node) {
|
||||
boolean visitChildren = true;
|
||||
|
||||
if (node != this.root) {
|
||||
IMethodBinding methodBinding = node.resolveMethodBinding();
|
||||
|
||||
if (WebfluxUtils.REQUEST_PREDICATES_TYPE.equals(methodBinding.getDeclaringClass().getBinaryName())) {
|
||||
String name = methodBinding.getName();
|
||||
if (name != null && WebfluxUtils.REQUEST_PREDICATE_ALL_PATH_METHODS.contains(name)) {
|
||||
path = WebfluxUtils.extractPath(node);
|
||||
}
|
||||
}
|
||||
|
||||
if (WebfluxUtils.isRouteMethodInvocation(methodBinding)) {
|
||||
visitChildren = false;
|
||||
}
|
||||
|
||||
}
|
||||
return visitChildren;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
/*******************************************************************************
|
||||
* 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.requestmapping;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.ASTVisitor;
|
||||
import org.eclipse.jdt.core.dom.Annotation;
|
||||
import org.eclipse.jdt.core.dom.Block;
|
||||
import org.eclipse.jdt.core.dom.IMethodBinding;
|
||||
import org.eclipse.jdt.core.dom.ITypeBinding;
|
||||
import org.eclipse.jdt.core.dom.MethodDeclaration;
|
||||
import org.eclipse.jdt.core.dom.MethodInvocation;
|
||||
import org.eclipse.jdt.core.dom.Type;
|
||||
import org.eclipse.jdt.core.dom.TypeDeclaration;
|
||||
import org.eclipse.lsp4j.Location;
|
||||
import org.eclipse.lsp4j.SymbolInformation;
|
||||
import org.eclipse.lsp4j.SymbolKind;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.SymbolProvider;
|
||||
import org.springframework.ide.vscode.commons.util.BadLocationException;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
*/
|
||||
public class WebfluxRouterSymbolProvider implements SymbolProvider {
|
||||
|
||||
@Override
|
||||
public Collection<SymbolInformation> getSymbols(Annotation node, ITypeBinding typeBinding,
|
||||
Collection<ITypeBinding> metaAnnotations, TextDocument doc) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<SymbolInformation> getSymbols(TypeDeclaration typeDeclaration, TextDocument doc) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<SymbolInformation> getSymbols(MethodDeclaration methodDeclaration, TextDocument doc) {
|
||||
Type returnType = methodDeclaration.getReturnType2();
|
||||
if (returnType != null) {
|
||||
ITypeBinding resolvedBinding = returnType.resolveBinding();
|
||||
if (resolvedBinding != null) {
|
||||
if (WebfluxUtils.ROUTER_FUNCTION_TYPE.equals(resolvedBinding.getBinaryName())) {
|
||||
return getSymbolsForRouterFunction(methodDeclaration, doc);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Collection<SymbolInformation> getSymbolsForRouterFunction(MethodDeclaration methodDeclaration,
|
||||
TextDocument doc) {
|
||||
List<SymbolInformation> result = new ArrayList<>();
|
||||
|
||||
Block body = methodDeclaration.getBody();
|
||||
body.accept(new ASTVisitor() {
|
||||
|
||||
@Override
|
||||
public boolean visit(MethodInvocation node) {
|
||||
IMethodBinding methodBinding = node.resolveMethodBinding();
|
||||
|
||||
if (WebfluxUtils.isRouteMethodInvocation(methodBinding)) {
|
||||
extractMappingSymbol(node, doc, result);
|
||||
}
|
||||
|
||||
return super.visit(node);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected void extractMappingSymbol(MethodInvocation node, TextDocument doc, List<SymbolInformation> result) {
|
||||
String foundPath = extractPathFromRouterFunction(node);
|
||||
String path = extractPath(node, foundPath);
|
||||
String httpMethod = extractMethod(node);
|
||||
|
||||
int methodNameStart = node.getName().getStartPosition();
|
||||
int invocationStart = node.getStartPosition();
|
||||
|
||||
if (path != null && path.length() > 0) {
|
||||
try {
|
||||
Location location = new Location(doc.getUri(), doc.toRange(methodNameStart, node.getLength() - (methodNameStart - invocationStart)));
|
||||
String label = "@" + (path.startsWith("/") ? path : ("/" + path)) + (httpMethod == null || httpMethod.isEmpty() ? "" : " -- " + httpMethod);
|
||||
result.add(new SymbolInformation(label, SymbolKind.Interface, location));
|
||||
} catch (BadLocationException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String extractPathFromRouterFunction(MethodInvocation routerInvocation) {
|
||||
WebfluxPathFinder pathFinder = new WebfluxPathFinder(routerInvocation);
|
||||
routerInvocation.accept(pathFinder);
|
||||
|
||||
String path = pathFinder.getPath();
|
||||
if (path == null) path = "";
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
private String extractPath(ASTNode node, String path) {
|
||||
if (node == null || node instanceof TypeDeclaration) {
|
||||
return path;
|
||||
}
|
||||
|
||||
if (node instanceof MethodInvocation) {
|
||||
MethodInvocation methodInvocation = (MethodInvocation) node;
|
||||
IMethodBinding methodBinding = methodInvocation.resolveMethodBinding();
|
||||
|
||||
if (WebfluxUtils.ROUTER_FUNCTIONS_TYPE.equals(methodBinding.getDeclaringClass().getBinaryName())) {
|
||||
String name = methodBinding.getName();
|
||||
if ("nest".equals(name)) {
|
||||
List<?> arguments = methodInvocation.arguments();
|
||||
for (Object argument : arguments) {
|
||||
if (argument instanceof MethodInvocation) {
|
||||
MethodInvocation nestedMethod = (MethodInvocation) argument;
|
||||
IMethodBinding nestedMethodBinding = nestedMethod.resolveMethodBinding();
|
||||
|
||||
String nestedMethodName = nestedMethodBinding.getName();
|
||||
if ("path".equals(nestedMethodName)) {
|
||||
String additionalPath = WebfluxUtils.extractPath(nestedMethod);
|
||||
if (additionalPath != null && additionalPath.length() > 0) {
|
||||
path = additionalPath + path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return extractPath(node.getParent(), path);
|
||||
}
|
||||
|
||||
private String extractMethod(MethodInvocation routerInvocation) {
|
||||
WebfluxMethodFinder methodFinder = new WebfluxMethodFinder(routerInvocation);
|
||||
routerInvocation.accept(methodFinder);
|
||||
|
||||
String method = methodFinder.getMethod();
|
||||
return method;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*******************************************************************************
|
||||
* 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.requestmapping;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.core.dom.IMethodBinding;
|
||||
import org.eclipse.jdt.core.dom.MethodInvocation;
|
||||
import org.eclipse.jdt.core.dom.StringLiteral;
|
||||
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
*/
|
||||
public class WebfluxUtils {
|
||||
|
||||
public static final String ROUTER_FUNCTION_TYPE = "org.springframework.web.reactive.function.server.RouterFunction";
|
||||
public static final String ROUTER_FUNCTIONS_TYPE = "org.springframework.web.reactive.function.server.RouterFunctions";
|
||||
public static final String REQUEST_PREDICATES_TYPE = "org.springframework.web.reactive.function.server.RequestPredicates";
|
||||
|
||||
public static final String REQUEST_PREDICATE_PATH_METHOD = "path";
|
||||
public static final String REQUEST_PREDICATE_METHOD_METHOD = "method";
|
||||
|
||||
public static final Set<String> REQUEST_PREDICATE_HTTPMETHOD_METHODS = new HashSet<>(Arrays.asList("GET", "POST", "DELETE", "PUT", "PATCH", "HEAD", "OPTIONS"));
|
||||
public static final Set<String> REQUEST_PREDICATE_ALL_PATH_METHODS = new HashSet<>(Arrays.asList(REQUEST_PREDICATE_PATH_METHOD, "GET", "POST", "DELETE", "PUT", "PATCH", "HEAD", "OPTIONS"));
|
||||
|
||||
|
||||
public static String extractPath(MethodInvocation node) {
|
||||
List<?> arguments = node.arguments();
|
||||
if (arguments != null && arguments.size() > 0) {
|
||||
Object object = arguments.get(0);
|
||||
if (object instanceof StringLiteral) {
|
||||
String path = ((StringLiteral) object).getLiteralValue();
|
||||
return path;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static boolean isRouteMethodInvocation(IMethodBinding methodBinding) {
|
||||
if (ROUTER_FUNCTIONS_TYPE.equals(methodBinding.getDeclaringClass().getBinaryName())) {
|
||||
String name = methodBinding.getName();
|
||||
if ("route".equals(name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (ROUTER_FUNCTION_TYPE.equals(methodBinding.getDeclaringClass().getBinaryName())) {
|
||||
String name = methodBinding.getName();
|
||||
if ("andRoute".equals(name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 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.scope;
|
||||
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
*/
|
||||
public class Constants {
|
||||
|
||||
public static final String SPRING_SCOPE = "org.springframework.context.annotation.Scope";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 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.scope;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.Annotation;
|
||||
import org.eclipse.jdt.core.dom.ITypeBinding;
|
||||
import org.eclipse.jdt.core.dom.MemberValuePair;
|
||||
import org.eclipse.jdt.core.dom.SimpleName;
|
||||
import org.eclipse.jdt.core.dom.StringLiteral;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.CompletionProvider;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
*/
|
||||
public class ScopeCompletionProcessor implements CompletionProvider {
|
||||
|
||||
@Override
|
||||
public Collection<ICompletionProposal> provideCompletions(ASTNode node, Annotation annotation, ITypeBinding type,
|
||||
int offset, IDocument doc) {
|
||||
|
||||
List<ICompletionProposal> result = new ArrayList<>();
|
||||
|
||||
try {
|
||||
if (node instanceof SimpleName && node.getParent() instanceof MemberValuePair) {
|
||||
MemberValuePair memberPair = (MemberValuePair) node.getParent();
|
||||
|
||||
// case: @Scope(value=<*>)
|
||||
if ("value".equals(memberPair.getName().toString()) && memberPair.getValue().toString().equals("$missing$")) {
|
||||
for (ScopeNameCompletion completion : ScopeNameCompletionProposal.COMPLETIONS) {
|
||||
ICompletionProposal proposal = new ScopeNameCompletionProposal(completion, doc, offset, offset, "");
|
||||
result.add(proposal);
|
||||
}
|
||||
}
|
||||
}
|
||||
// case: @Scope(<*>)
|
||||
else if (node == annotation && doc.get(offset - 1, 2).endsWith("()")) {
|
||||
for (ScopeNameCompletion completion : ScopeNameCompletionProposal.COMPLETIONS) {
|
||||
ICompletionProposal proposal = new ScopeNameCompletionProposal(completion, doc, offset, offset, "");
|
||||
result.add(proposal);
|
||||
}
|
||||
}
|
||||
else if (node instanceof StringLiteral && node.getParent() instanceof Annotation) {
|
||||
// case: @Scope("...")
|
||||
if (node.toString().startsWith("\"") && node.toString().endsWith("\"")) {
|
||||
String prefix = doc.get(node.getStartPosition(), offset - node.getStartPosition());
|
||||
for (ScopeNameCompletion completion : ScopeNameCompletionProposal.COMPLETIONS) {
|
||||
if (completion.getValue().startsWith(prefix)) {
|
||||
ICompletionProposal proposal = new ScopeNameCompletionProposal(completion, doc, node.getStartPosition(), node.getStartPosition() + node.getLength(), prefix);
|
||||
result.add(proposal);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (node instanceof StringLiteral && node.getParent() instanceof MemberValuePair) {
|
||||
MemberValuePair memberPair = (MemberValuePair) node.getParent();
|
||||
|
||||
// case: @Scope(value=<*>)
|
||||
if ("value".equals(memberPair.getName().toString()) && node.toString().startsWith("\"") && node.toString().endsWith("\"")) {
|
||||
String prefix = doc.get(node.getStartPosition(), offset - node.getStartPosition());
|
||||
for (ScopeNameCompletion completion : ScopeNameCompletionProposal.COMPLETIONS) {
|
||||
if (completion.getValue().startsWith(prefix)) {
|
||||
ICompletionProposal proposal = new ScopeNameCompletionProposal(completion, doc, node.getStartPosition(), node.getStartPosition() + node.getLength(), prefix);
|
||||
result.add(proposal);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 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.scope;
|
||||
|
||||
import org.eclipse.lsp4j.CompletionItemKind;
|
||||
import org.springframework.ide.vscode.commons.util.Renderable;
|
||||
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
*/
|
||||
public class ScopeNameCompletion {
|
||||
|
||||
private final String label;
|
||||
private final String detail;
|
||||
private final Renderable documentation;
|
||||
private final CompletionItemKind kind;
|
||||
private final String value;
|
||||
|
||||
public ScopeNameCompletion(String value, String label, String detail, Renderable documentation, CompletionItemKind kind) {
|
||||
super();
|
||||
this.value = value;
|
||||
this.label = label;
|
||||
this.detail = detail;
|
||||
this.documentation = documentation;
|
||||
this.kind = kind;
|
||||
}
|
||||
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
public String getDetail() {
|
||||
return detail;
|
||||
}
|
||||
|
||||
public CompletionItemKind getKind() {
|
||||
return kind;
|
||||
}
|
||||
|
||||
public Renderable getDocumentation() {
|
||||
return documentation;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 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.scope;
|
||||
|
||||
import org.eclipse.lsp4j.CompletionItemKind;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
|
||||
import org.springframework.ide.vscode.commons.util.Renderable;
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
*/
|
||||
public class ScopeNameCompletionProposal implements ICompletionProposal {
|
||||
|
||||
public static final ScopeNameCompletion[] COMPLETIONS = new ScopeNameCompletion[] {
|
||||
new ScopeNameCompletion("\"prototype\"", "prototype", "prototype scope", null, CompletionItemKind.Value),
|
||||
new ScopeNameCompletion("\"singleton\"", "singleton", "singleton scope (default)", null, CompletionItemKind.Value),
|
||||
new ScopeNameCompletion("\"request\"", "request", "request scope", null, CompletionItemKind.Value),
|
||||
new ScopeNameCompletion("\"session\"", "session", "session scope", null, CompletionItemKind.Value),
|
||||
new ScopeNameCompletion("\"globalSession\"", "globalSession", "globalSession scope", null, CompletionItemKind.Value),
|
||||
new ScopeNameCompletion("\"application\"", "application", "application scope", null, CompletionItemKind.Value),
|
||||
new ScopeNameCompletion("\"websocket\"", "websocket", "websocket scope", null, CompletionItemKind.Value)
|
||||
};
|
||||
|
||||
private final IDocument doc;
|
||||
private final int startOffset;
|
||||
private final int endOffset;
|
||||
private final ScopeNameCompletion completion;
|
||||
private final String prefix;
|
||||
|
||||
public ScopeNameCompletionProposal(ScopeNameCompletion completion, IDocument doc, int startOffset, int endOffset, String prefix) {
|
||||
this.completion = completion;
|
||||
this.doc = doc;
|
||||
this.startOffset = startOffset;
|
||||
this.endOffset = endOffset;
|
||||
this.prefix = prefix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLabel() {
|
||||
return completion.getLabel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletionItemKind getKind() {
|
||||
return completion.getKind();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DocumentEdits getTextEdit() {
|
||||
DocumentEdits edits = new DocumentEdits(doc);
|
||||
edits.replace(startOffset + prefix.length(), endOffset, completion.getValue().substring(prefix.length()));
|
||||
return edits;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDetail() {
|
||||
return completion.getDetail();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Renderable getDocumentation() {
|
||||
return completion.getDocumentation();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 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.snippets;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||
import org.eclipse.lsp4j.CompletionItemKind;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SnippetBuilder;
|
||||
import org.springframework.ide.vscode.commons.util.text.DocumentRegion;
|
||||
|
||||
import com.google.common.base.Supplier;
|
||||
|
||||
public class JavaSnippet {
|
||||
|
||||
private JavaSnippetContext context;
|
||||
|
||||
private String name;
|
||||
|
||||
private String template;
|
||||
|
||||
private List<String> imports;
|
||||
|
||||
private CompletionItemKind kind;
|
||||
|
||||
public JavaSnippet(String name, JavaSnippetContext context, CompletionItemKind kind, List<String> imports,
|
||||
String template) {
|
||||
super();
|
||||
this.context = context;
|
||||
this.name = name;
|
||||
this.template = template;
|
||||
this.imports = imports;
|
||||
this.kind = kind;
|
||||
}
|
||||
|
||||
public Optional<ICompletionProposal> generateCompletion(Supplier<SnippetBuilder> snippetBuilderFactory,
|
||||
DocumentRegion query, ASTNode node, CompilationUnit cu) {
|
||||
|
||||
if (context.appliesTo(node)) {
|
||||
return Optional.of(
|
||||
new JavaSnippetCompletion(snippetBuilderFactory,
|
||||
query,
|
||||
cu,
|
||||
this
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public String getTemplate() {
|
||||
return this.template;
|
||||
}
|
||||
|
||||
public Optional<List<String>> getImports() {
|
||||
return Optional.of(this.imports);
|
||||
}
|
||||
|
||||
public CompletionItemKind getKind() {
|
||||
return kind;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 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.snippets;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.IndentUtil;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SnippetBuilder;
|
||||
import org.springframework.ide.vscode.commons.util.text.DocumentRegion;
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
|
||||
import com.google.common.base.Supplier;
|
||||
|
||||
/**
|
||||
* Respobsible for converting eclipse-like template string into lsp snippet text.
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class JavaSnippetBuilder{
|
||||
|
||||
private Supplier<SnippetBuilder> snippetBuilderFactory;
|
||||
|
||||
private static final Pattern PLACE_HOLDER = Pattern.compile("\\$\\{(.+?)\\}");
|
||||
|
||||
public JavaSnippetBuilder(Supplier<SnippetBuilder> snippetBuilderFactory) {
|
||||
this.snippetBuilderFactory = snippetBuilderFactory;
|
||||
}
|
||||
|
||||
public DocumentEdits createEdit(DocumentRegion query, String template) {
|
||||
IDocument doc = query.getDocument();
|
||||
IndentUtil indentUtil = new IndentUtil(doc);
|
||||
|
||||
DocumentEdits edit = new DocumentEdits(doc);
|
||||
String snippet = createSnippet(template);
|
||||
|
||||
String referenceIndent = indentUtil.getReferenceIndent(query.getStart(), doc);
|
||||
|
||||
if (!referenceIndent.contains("\t")) {
|
||||
snippet = indentUtil.covertTabsToSpace(snippet);
|
||||
}
|
||||
String indentedSnippet = indentUtil.applyIndentation(snippet, referenceIndent);
|
||||
|
||||
edit.replace(query.getStart(), query.getEnd(), indentedSnippet);
|
||||
return edit;
|
||||
}
|
||||
|
||||
private String createSnippet(String template) {
|
||||
Matcher matcher = PLACE_HOLDER.matcher(template);
|
||||
int start = 0;
|
||||
SnippetBuilder snippet = snippetBuilderFactory.get();
|
||||
while (matcher.find(start)) {
|
||||
int matchStart = matcher.start();
|
||||
snippet.text(template.substring(start, matchStart));
|
||||
int matchEnd = matcher.end();
|
||||
String placeHolderImage = template.substring(matcher.start(1), matcher.end(1));
|
||||
int colon = placeHolderImage.indexOf(':');
|
||||
String id, value;
|
||||
if (colon>=0) {
|
||||
id = placeHolderImage.substring(0, colon);
|
||||
value = placeHolderImage.substring(colon+1);
|
||||
} else {
|
||||
id = placeHolderImage;
|
||||
value = id;
|
||||
}
|
||||
snippet.placeHolder(id, value);
|
||||
start = matchEnd;
|
||||
}
|
||||
snippet.text(template.substring(start));
|
||||
return snippet.build().toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 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.snippets;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||
import org.eclipse.lsp4j.CompletionItemKind;
|
||||
import org.springframework.ide.vscode.boot.java.jdt.imports.ImportRewrite;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SnippetBuilder;
|
||||
import org.springframework.ide.vscode.commons.util.Renderable;
|
||||
import org.springframework.ide.vscode.commons.util.Renderables;
|
||||
import org.springframework.ide.vscode.commons.util.text.DocumentRegion;
|
||||
|
||||
import com.google.common.base.Supplier;
|
||||
|
||||
public class JavaSnippetCompletion implements ICompletionProposal{
|
||||
|
||||
private DocumentRegion query;
|
||||
private JavaSnippet javaSnippet;
|
||||
private Supplier<SnippetBuilder> snippetBuilderFactory;
|
||||
private CompilationUnit cu;
|
||||
|
||||
public JavaSnippetCompletion(Supplier<SnippetBuilder> snippetBuilderFactory, DocumentRegion query, CompilationUnit cu, JavaSnippet javaSnippet) {
|
||||
this.snippetBuilderFactory = snippetBuilderFactory;
|
||||
this.query = query;
|
||||
this.cu = cu;
|
||||
this.javaSnippet = javaSnippet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLabel() {
|
||||
return javaSnippet.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletionItemKind getKind() {
|
||||
return javaSnippet.getKind();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DocumentEdits getTextEdit() {
|
||||
return new JavaSnippetBuilder(snippetBuilderFactory).createEdit(query, javaSnippet.getTemplate());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDetail() {
|
||||
return "Snippet";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Renderable getDocumentation() {
|
||||
return Renderables.NO_DESCRIPTION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<DocumentEdits> getAdditionalEdit() {
|
||||
ImportRewrite rewrite = ImportRewrite.create(cu, true);
|
||||
|
||||
javaSnippet.getImports().ifPresent((imprts ->
|
||||
{
|
||||
for (String imprt : imprts) {
|
||||
rewrite.addImport(imprt);
|
||||
}
|
||||
}));
|
||||
|
||||
DocumentEdits edit = rewrite.createEdit(query.getDocument());
|
||||
|
||||
return edit != null ? Optional.of(edit) : Optional.empty();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 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.snippets;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.TypeDeclaration;
|
||||
|
||||
public interface JavaSnippetContext {
|
||||
JavaSnippetContext BOOT_MEMBERS = (node) -> node instanceof TypeDeclaration;
|
||||
|
||||
boolean appliesTo(ASTNode node);
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 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.snippets;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.PrefixFinder;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SnippetBuilder;
|
||||
import org.springframework.ide.vscode.commons.util.FuzzyMatcher;
|
||||
import org.springframework.ide.vscode.commons.util.text.DocumentRegion;
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
|
||||
import com.google.common.base.Supplier;
|
||||
|
||||
public class JavaSnippetManager {
|
||||
|
||||
private List<JavaSnippet> snippets = new ArrayList<>();
|
||||
private Supplier<SnippetBuilder> snippetBuilderFactory;
|
||||
|
||||
private static PrefixFinder PREFIX_FINDER = new PrefixFinder() {
|
||||
|
||||
@Override
|
||||
protected boolean isPrefixChar(char c) {
|
||||
return Character.isJavaIdentifierPart(c);
|
||||
}
|
||||
};
|
||||
|
||||
public JavaSnippetManager(Supplier<SnippetBuilder> snippetBuilderFactory) {
|
||||
this.snippetBuilderFactory = snippetBuilderFactory;
|
||||
}
|
||||
|
||||
public void add(JavaSnippet javaSnippet) {
|
||||
snippets.add(javaSnippet);
|
||||
|
||||
}
|
||||
|
||||
public Collection<ICompletionProposal> getCompletions(IDocument doc, int offset, ASTNode node, CompilationUnit cu) {
|
||||
Collection<ICompletionProposal> completions = new ArrayList<>();
|
||||
|
||||
DocumentRegion query = PREFIX_FINDER.getPrefixRegion(doc, offset);
|
||||
|
||||
for (JavaSnippet javaSnippet : snippets) {
|
||||
if (FuzzyMatcher.matchScore(query.toString(), javaSnippet.getName()) != 0) {
|
||||
javaSnippet.generateCompletion(snippetBuilderFactory, query, node, cu)
|
||||
.ifPresent((completion) -> completions.add(completion));
|
||||
}
|
||||
}
|
||||
|
||||
return completions;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<!-- These are the original templates. This file will be deleted. It just here for 'inspiration' -->
|
||||
<templates>
|
||||
<template autoinsert="true"
|
||||
id="org.springframework.ide.eclipse.boot.templates.RequestMapping"
|
||||
name="RequestMapping method" context="boot-members" description="RequestMapping method"
|
||||
enabled="true">${x:import(org.springframework.web.bind.annotation.RequestMapping,
|
||||
org.springframework.web.bind.annotation.RequestMethod,
|
||||
org.springframework.web.bind.annotation.RequestParam)}@RequestMapping(value="${path}",
|
||||
method=RequestMethod.${GET})
|
||||
public ${SomeData} ${requestMethodName}(@RequestParam ${String} ${param}) {
|
||||
return new ${SomeData}(${cursor});
|
||||
}
|
||||
</template>
|
||||
<template autoinsert="true"
|
||||
id="org.springframework.ide.eclipse.boot.templates.GetMapping" name="GetMapping method"
|
||||
context="boot-members" description="GetMapping method" enabled="true">
|
||||
${x:import(org.springframework.web.bind.annotation.GetMapping,
|
||||
org.springframework.web.bind.annotation.RequestParam)}@GetMapping(value="${path}")
|
||||
public ${SomeData} ${getMethodName}(@RequestParam ${String} ${param})
|
||||
{
|
||||
return new ${SomeData}(${cursor});
|
||||
}
|
||||
</template>
|
||||
<template autoinsert="true"
|
||||
id="org.springframework.ide.eclipse.boot.templates.PostMapping" name="PostMapping method"
|
||||
context="boot-members" description="PostMapping method" enabled="true">
|
||||
${x:import(org.springframework.web.bind.annotation.PostMapping,
|
||||
org.springframework.web.bind.annotation.RequestBody)}@PostMapping(value="${path}")
|
||||
public ${SomeEnityData} ${postMethodName}(@RequestBody
|
||||
${SomeEnityData} ${entity}) {
|
||||
//TODO: process POST request
|
||||
${cursor}
|
||||
return ${entity};
|
||||
}
|
||||
</template>
|
||||
<template autoinsert="true"
|
||||
id="org.springframework.ide.eclipse.boot.templates.PutMapping" name="PutMapping method"
|
||||
context="boot-members" description="PutMapping method" enabled="true">
|
||||
${x:import(org.springframework.web.bind.annotation.PutMapping,
|
||||
org.springframework.web.bind.annotation.RequestBody,
|
||||
org.springframework.web.bind.annotation.PathVariable)}@PutMapping(value="${path}/{${id}}")
|
||||
public ${SomeEnityData} ${putMethodName}(@PathVariable
|
||||
${pvt:link(String,int,long)} ${id}, @RequestBody ${SomeEnityData}
|
||||
${entity}) {
|
||||
//TODO: process PUT request
|
||||
${cursor}
|
||||
return ${entity};
|
||||
}
|
||||
</template>
|
||||
</templates>
|
||||
@@ -0,0 +1,245 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017, 2018 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* 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.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.Annotation;
|
||||
import org.eclipse.jdt.core.dom.ArrayInitializer;
|
||||
import org.eclipse.jdt.core.dom.Expression;
|
||||
import org.eclipse.jdt.core.dom.ITypeBinding;
|
||||
import org.eclipse.jdt.core.dom.MemberValuePair;
|
||||
import org.eclipse.jdt.core.dom.MethodDeclaration;
|
||||
import org.eclipse.jdt.core.dom.NormalAnnotation;
|
||||
import org.eclipse.jdt.core.dom.QualifiedName;
|
||||
import org.eclipse.jdt.core.dom.SimpleName;
|
||||
import org.eclipse.jdt.core.dom.SingleMemberAnnotation;
|
||||
import org.eclipse.jdt.core.dom.StringLiteral;
|
||||
import org.eclipse.jdt.core.dom.TypeDeclaration;
|
||||
import org.eclipse.lsp4j.Range;
|
||||
import org.springframework.ide.vscode.commons.util.CollectorUtil;
|
||||
import org.springframework.ide.vscode.commons.util.Log;
|
||||
import org.springframework.ide.vscode.commons.util.text.DocumentRegion;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
public class ASTUtils {
|
||||
|
||||
public static DocumentRegion nameRegion(TextDocument doc, Annotation annotation) {
|
||||
int start = annotation.getTypeName().getStartPosition();
|
||||
int end = start + annotation.getTypeName().getLength();
|
||||
if (doc.getSafeChar(start - 1) == '@') {
|
||||
start--;
|
||||
}
|
||||
return new DocumentRegion(doc, start, end);
|
||||
}
|
||||
|
||||
public static Optional<Range> nameRange(TextDocument doc, Annotation annotation) {
|
||||
try {
|
||||
return Optional.of(nameRegion(doc, annotation).asRange());
|
||||
} catch (Exception e) {
|
||||
Log.log(e);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
public static DocumentRegion stringRegion(TextDocument doc, StringLiteral node) {
|
||||
DocumentRegion nodeRegion = nodeRegion(doc, node);
|
||||
if (nodeRegion.startsWith("\"")) {
|
||||
nodeRegion = nodeRegion.subSequence(1);
|
||||
}
|
||||
if (nodeRegion.endsWith("\"")) {
|
||||
nodeRegion = nodeRegion.subSequence(0, nodeRegion.getLength()-1);
|
||||
}
|
||||
return nodeRegion;
|
||||
}
|
||||
|
||||
|
||||
public static DocumentRegion nodeRegion(TextDocument doc, ASTNode node) {
|
||||
int start = node.getStartPosition();
|
||||
int end = start + node.getLength();
|
||||
return new DocumentRegion(doc, start, end);
|
||||
}
|
||||
|
||||
public static Optional<Expression> getAttribute(Annotation annotation, String name) {
|
||||
if (annotation != null) {
|
||||
try {
|
||||
if (annotation.isSingleMemberAnnotation() && name.equals("value")) {
|
||||
SingleMemberAnnotation sma = (SingleMemberAnnotation) annotation;
|
||||
return Optional.ofNullable(sma.getValue());
|
||||
} else if (annotation.isNormalAnnotation()) {
|
||||
NormalAnnotation na = (NormalAnnotation) annotation;
|
||||
Object attributeObjs = na.getStructuralProperty(NormalAnnotation.VALUES_PROPERTY);
|
||||
if (attributeObjs instanceof List) {
|
||||
for (Object atrObj : (List<?>)attributeObjs) {
|
||||
if (atrObj instanceof MemberValuePair) {
|
||||
MemberValuePair mvPair = (MemberValuePair) atrObj;
|
||||
if (name.equals(mvPair.getName().getIdentifier())) {
|
||||
return Optional.ofNullable(mvPair.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.log(e);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* For case where a expression can be either a String or a array of Strings and
|
||||
* we are interested in the first element of the array. (I.e. typical case
|
||||
* when annotation attribute is of type String[] (because Java allows using a single
|
||||
* value as a convenient syntax for writing an array of length 1 in that case.
|
||||
*/
|
||||
public static Optional<String> getFirstString(Expression exp) {
|
||||
if (exp instanceof StringLiteral) {
|
||||
return Optional.ofNullable(getLiteralValue((StringLiteral) exp));
|
||||
} else if (exp instanceof ArrayInitializer) {
|
||||
ArrayInitializer array = (ArrayInitializer) exp;
|
||||
Object objs = array.getStructuralProperty(ArrayInitializer.EXPRESSIONS_PROPERTY);
|
||||
if (objs instanceof List) {
|
||||
List<?> list = (List<?>) objs;
|
||||
if (!list.isEmpty()) {
|
||||
Object firstObj = list.get(0);
|
||||
if (firstObj instanceof Expression) {
|
||||
return getFirstString((Expression) firstObj);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public static TypeDeclaration findDeclaringType(Annotation annotation) {
|
||||
ASTNode node = annotation;
|
||||
while (node != null && !(node instanceof TypeDeclaration)) {
|
||||
node = node.getParent();
|
||||
}
|
||||
|
||||
return node != null ? (TypeDeclaration) node : null;
|
||||
}
|
||||
|
||||
public static MethodDeclaration[] findConstructors(TypeDeclaration typeDecl) {
|
||||
List<MethodDeclaration> constructors = new ArrayList<>();
|
||||
|
||||
MethodDeclaration[] methods = typeDecl.getMethods();
|
||||
for (MethodDeclaration methodDeclaration : methods) {
|
||||
if (methodDeclaration.isConstructor()) {
|
||||
constructors.add(methodDeclaration);
|
||||
}
|
||||
}
|
||||
|
||||
return constructors.toArray(new MethodDeclaration[constructors.size()]);
|
||||
}
|
||||
|
||||
|
||||
public static MethodDeclaration getAnnotatedMethod(Annotation annotation) {
|
||||
ASTNode parent = annotation.getParent();
|
||||
if (parent instanceof MethodDeclaration) {
|
||||
return (MethodDeclaration)parent;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static TypeDeclaration getAnnotatedType(Annotation annotation) {
|
||||
ASTNode parent = annotation.getParent();
|
||||
if (parent instanceof TypeDeclaration) {
|
||||
return (TypeDeclaration)parent;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String getLiteralValue(StringLiteral node) {
|
||||
synchronized (node.getAST()) {
|
||||
return node.getLiteralValue();
|
||||
}
|
||||
}
|
||||
|
||||
public static String getExpressionValueAsString(Expression exp) {
|
||||
if (exp instanceof StringLiteral) {
|
||||
return getLiteralValue((StringLiteral) exp);
|
||||
} else if (exp instanceof QualifiedName) {
|
||||
return getExpressionValueAsString(((QualifiedName) exp).getName());
|
||||
} else if (exp instanceof SimpleName) {
|
||||
return ((SimpleName) exp).getIdentifier();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static String[] getExpressionValueAsArray(Expression exp) {
|
||||
if (exp instanceof ArrayInitializer) {
|
||||
ArrayInitializer array = (ArrayInitializer) exp;
|
||||
return ((List<Expression>) array.expressions()).stream().map(e -> getExpressionValueAsString(e))
|
||||
.filter(Objects::nonNull).toArray(String[]::new);
|
||||
} else {
|
||||
String rm = getExpressionValueAsString(exp);
|
||||
if (rm != null) {
|
||||
return new String[] { rm };
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static List<StringLiteral> getExpressionValueAsListOfLiterals(Expression exp) {
|
||||
if (exp instanceof ArrayInitializer) {
|
||||
ArrayInitializer array = (ArrayInitializer) exp;
|
||||
return ((List<Expression>) array.expressions()).stream()
|
||||
.flatMap(e -> e instanceof StringLiteral
|
||||
? Stream.of((StringLiteral)e)
|
||||
: Stream.empty()
|
||||
)
|
||||
.collect(CollectorUtil.toImmutableList());
|
||||
} else if (exp instanceof StringLiteral){
|
||||
return ImmutableList.of((StringLiteral)exp);
|
||||
}
|
||||
return ImmutableList.of();
|
||||
}
|
||||
|
||||
|
||||
public static Collection<Annotation> getAnnotations(TypeDeclaration declaringType) {
|
||||
Object modifiersObj = declaringType.getStructuralProperty(TypeDeclaration.MODIFIERS2_PROPERTY);
|
||||
if (modifiersObj instanceof List) {
|
||||
ImmutableList.Builder<Annotation> annotations = ImmutableList.builder();
|
||||
for (Object node : (List<?>)modifiersObj) {
|
||||
if (node instanceof Annotation) {
|
||||
annotations.add((Annotation) node);
|
||||
}
|
||||
}
|
||||
return annotations.build();
|
||||
}
|
||||
return ImmutableList.of();
|
||||
}
|
||||
|
||||
|
||||
public static String getAnnotationType(Annotation annotation) {
|
||||
ITypeBinding binding = annotation.resolveTypeBinding();
|
||||
if (binding!=null) {
|
||||
return binding.getQualifiedName();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017, 2018 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* 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;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
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.springframework.ide.vscode.commons.java.IClasspath;
|
||||
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 {
|
||||
|
||||
private JavaProjectFinder projectFinder;
|
||||
private ProjectObserver projectObserver;
|
||||
private Cache<URI, CompilationUnit> uriToCu;
|
||||
private Cache<IJavaProject, Set<URI>> projectToDocs;
|
||||
private ProjectObserver.Listener projectListener;
|
||||
|
||||
private ReadLock readLock;
|
||||
private WriteLock writeLock;
|
||||
|
||||
public CompilationUnitCache(JavaProjectFinder projectFinder, SimpleTextDocumentService documentService, ProjectObserver projectObserver) {
|
||||
this.projectFinder = projectFinder;
|
||||
this.projectObserver = projectObserver;
|
||||
projectListener = new CUProjectListener();
|
||||
|
||||
uriToCu = CacheBuilder.newBuilder().build();
|
||||
projectToDocs = CacheBuilder.newBuilder().build();
|
||||
|
||||
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
|
||||
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 (this.projectObserver != null) {
|
||||
this.projectObserver.addListener(projectListener);
|
||||
}
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
if (projectObserver != null) {
|
||||
projectObserver.removeListener(projectListener);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a CompiationUnitn AST from the cache and passes it to a requestor callback, applying
|
||||
* proper thread synchronization around the requestor.
|
||||
* <p>
|
||||
* Warning: Callers should take care to do all AST processing inside of the requestor callback and
|
||||
* 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!
|
||||
*/
|
||||
public <T> T withCompilationUnit(TextDocument document, Function<CompilationUnit, T> requestor) {
|
||||
URI uri = URI.create(document.getUri());
|
||||
IJavaProject project = projectFinder.find(document.getId()).orElse(null);
|
||||
|
||||
if (project != null) {
|
||||
|
||||
readLock.lock();
|
||||
CompilationUnit cu = null;
|
||||
|
||||
try {
|
||||
cu = uriToCu.get(uri, () -> {
|
||||
CompilationUnit cUnit = parse(document, project);
|
||||
projectToDocs.get(project, () -> new HashSet<>()).add(URI.create(document.getUri()));
|
||||
return cUnit;
|
||||
});
|
||||
if (cu != null) {
|
||||
projectToDocs.get(project, () -> new HashSet<>()).add(URI.create(document.getUri()));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.log(e);
|
||||
} finally {
|
||||
readLock.unlock();
|
||||
}
|
||||
|
||||
if (cu != null) {
|
||||
try {
|
||||
synchronized (cu.getAST()) {
|
||||
return requestor.apply(cu);
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
Log.log(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return requestor.apply(null);
|
||||
}
|
||||
|
||||
private void invalidateCuForJavaFile(String uriStr) {
|
||||
URI uri = URI.create(uriStr);
|
||||
writeLock.lock();
|
||||
try {
|
||||
uriToCu.invalidate(uri);
|
||||
} finally {
|
||||
writeLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public static CompilationUnit parse(TextDocument document, IJavaProject project) throws Exception {
|
||||
String[] classpathEntries = getClasspathEntries(document, project);
|
||||
String docURI = document.getUri();
|
||||
String unitName = docURI.substring(docURI.lastIndexOf("/"));
|
||||
char[] source = document.get(0, document.getLength()).toCharArray();
|
||||
return parse(source, docURI, unitName, classpathEntries);
|
||||
}
|
||||
|
||||
public static CompilationUnit parse(char[] source, String docURI, String unitName, String[] classpathEntries) throws Exception {
|
||||
ASTParser parser = ASTParser.newParser(AST.JLS9);
|
||||
Map<String, String> options = JavaCore.getOptions();
|
||||
JavaCore.setComplianceOptions(JavaCore.VERSION_1_8, options);
|
||||
parser.setCompilerOptions(options);
|
||||
parser.setKind(ASTParser.K_COMPILATION_UNIT);
|
||||
parser.setStatementsRecovery(true);
|
||||
parser.setBindingsRecovery(true);
|
||||
parser.setResolveBindings(true);
|
||||
|
||||
String[] sourceEntries = new String[] {};
|
||||
parser.setEnvironment(classpathEntries, sourceEntries, null, true);
|
||||
|
||||
parser.setUnitName(unitName);
|
||||
parser.setSource(source);
|
||||
|
||||
CompilationUnit cu = (CompilationUnit) parser.createAST(null);
|
||||
|
||||
return cu;
|
||||
}
|
||||
|
||||
private static String[] getClasspathEntries(TextDocument document, IJavaProject project) throws Exception {
|
||||
if (project == null) {
|
||||
return new String[0];
|
||||
} else {
|
||||
IClasspath classpath = project.getClasspath();
|
||||
Stream<Path> classpathEntries = classpath.getClasspathEntries().stream();
|
||||
return classpathEntries
|
||||
.filter(path -> path.toFile().exists())
|
||||
.map(path -> path.toAbsolutePath().toString()).toArray(String[]::new);
|
||||
}
|
||||
}
|
||||
|
||||
private void invalidateProject(IJavaProject project) {
|
||||
Set<URI> docUris = projectToDocs.getIfPresent(project);
|
||||
if (docUris != null) {
|
||||
writeLock.lock();
|
||||
try {
|
||||
uriToCu.invalidateAll(docUris);
|
||||
projectToDocs.invalidate(project);
|
||||
} finally {
|
||||
writeLock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class CUProjectListener implements ProjectObserver.Listener {
|
||||
|
||||
@Override
|
||||
public void created(IJavaProject project) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changed(IJavaProject project) {
|
||||
invalidateProject(project);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleted(IJavaProject project) {
|
||||
invalidateProject(project);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*******************************************************************************
|
||||
* 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.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ITypeBinding;
|
||||
import org.eclipse.jdt.core.dom.Modifier;
|
||||
import org.eclipse.jdt.core.dom.TypeDeclaration;
|
||||
import org.springframework.ide.vscode.commons.util.text.DocumentRegion;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
import reactor.util.function.Tuple3;
|
||||
import reactor.util.function.Tuples;
|
||||
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
*/
|
||||
public class FunctionUtils {
|
||||
|
||||
public static final String FUNCTION_FUNCTION_TYPE = Function.class.getName();
|
||||
public static final String FUNCTION_CONSUMER_TYPE = Consumer.class.getName();
|
||||
public static final String FUNCTION_SUPPLIER_TYPE = Supplier.class.getName();
|
||||
|
||||
public static Tuple3<String, String, DocumentRegion> getFunctionBean(TypeDeclaration typeDeclaration, TextDocument doc) {
|
||||
ITypeBinding resolvedType = typeDeclaration.resolveBinding();
|
||||
|
||||
if (resolvedType != null && !resolvedType.isInterface() && !isAbstractClass(typeDeclaration, resolvedType)) {
|
||||
return getFunctionBean(typeDeclaration, doc, resolvedType);
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static Tuple3<String, String, DocumentRegion> getFunctionBean(TypeDeclaration typeDeclaration, TextDocument doc,
|
||||
ITypeBinding resolvedType) {
|
||||
|
||||
ITypeBinding[] interfaces = resolvedType.getInterfaces();
|
||||
for (ITypeBinding resolvedInterface : interfaces) {
|
||||
String simplifiedType = null;
|
||||
if (resolvedInterface.isParameterizedType()) {
|
||||
simplifiedType = resolvedInterface.getBinaryName();
|
||||
}
|
||||
else {
|
||||
simplifiedType = resolvedType.getQualifiedName();
|
||||
}
|
||||
|
||||
if (FUNCTION_FUNCTION_TYPE.equals(simplifiedType) || FUNCTION_CONSUMER_TYPE.equals(simplifiedType)
|
||||
|| FUNCTION_SUPPLIER_TYPE.equals(simplifiedType)) {
|
||||
String beanName = getBeanName(typeDeclaration);
|
||||
String beanType = resolvedInterface.getName();
|
||||
DocumentRegion region = ASTUtils.nodeRegion(doc, typeDeclaration.getName());
|
||||
|
||||
return Tuples.of(beanName, beanType, region);
|
||||
}
|
||||
else {
|
||||
Tuple3<String, String, DocumentRegion> result = getFunctionBean(typeDeclaration, doc, resolvedInterface);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ITypeBinding superclass = resolvedType.getSuperclass();
|
||||
if (superclass != null) {
|
||||
return getFunctionBean(typeDeclaration, doc, superclass);
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected static String getBeanName(TypeDeclaration typeDeclaration) {
|
||||
String beanName = typeDeclaration.getName().toString();
|
||||
if (beanName.length() > 0 && Character.isUpperCase(beanName.charAt(0))) {
|
||||
beanName = Character.toLowerCase(beanName.charAt(0)) + beanName.substring(1);
|
||||
}
|
||||
return beanName;
|
||||
}
|
||||
|
||||
protected static boolean isAbstractClass(TypeDeclaration typeDeclaration, ITypeBinding resolvedType) {
|
||||
List<?> modifiers = typeDeclaration.modifiers();
|
||||
for (Object object : modifiers) {
|
||||
if (object instanceof Modifier) {
|
||||
if (((Modifier) object).isAbstract()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,674 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017, 2018 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.utils;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
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.ASTVisitor;
|
||||
import org.eclipse.jdt.core.dom.Annotation;
|
||||
import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||
import org.eclipse.jdt.core.dom.FileASTRequestor;
|
||||
import org.eclipse.jdt.core.dom.ITypeBinding;
|
||||
import org.eclipse.jdt.core.dom.MarkerAnnotation;
|
||||
import org.eclipse.jdt.core.dom.MethodDeclaration;
|
||||
import org.eclipse.jdt.core.dom.NormalAnnotation;
|
||||
import org.eclipse.jdt.core.dom.SingleMemberAnnotation;
|
||||
import org.eclipse.jdt.core.dom.TypeDeclaration;
|
||||
import org.eclipse.lsp4j.Location;
|
||||
import org.eclipse.lsp4j.SymbolInformation;
|
||||
import org.eclipse.lsp4j.SymbolKind;
|
||||
import org.eclipse.lsp4j.TextDocumentIdentifier;
|
||||
import org.eclipse.lsp4j.WorkspaceFolder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ide.vscode.boot.BootLanguageServerParams;
|
||||
import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchies;
|
||||
import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchyAwareLookup;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.SymbolProvider;
|
||||
import org.springframework.ide.vscode.commons.java.IClasspath;
|
||||
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.java.ProjectObserver.Listener;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleWorkspaceService;
|
||||
import org.springframework.ide.vscode.commons.util.StringUtil;
|
||||
import org.springframework.ide.vscode.commons.util.UriUtil;
|
||||
import org.springframework.ide.vscode.commons.util.text.LanguageId;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
*/
|
||||
public class SpringIndexer {
|
||||
|
||||
private final SimpleLanguageServer server;
|
||||
private final BootLanguageServerParams params;
|
||||
private final JavaProjectFinder projectFinder;
|
||||
private final AnnotationHierarchyAwareLookup<SymbolProvider> symbolProviders;
|
||||
|
||||
private final List<SymbolInformation> symbols;
|
||||
private final ConcurrentMap<String, List<SymbolInformation>> symbolsByDoc;
|
||||
|
||||
private final Thread updateWorker;
|
||||
private final BlockingQueue<WorkerItem> updateQueue;
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(SpringIndexer.class);
|
||||
|
||||
private final Listener projectListener = new Listener() {
|
||||
|
||||
@Override
|
||||
public void created(IJavaProject project) {
|
||||
log.debug("project created event: {}", project.getElementName());
|
||||
refresh();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changed(IJavaProject project) {
|
||||
log.debug("project changed event: {}", project.getElementName());
|
||||
refresh();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleted(IJavaProject project) {
|
||||
log.debug("project deleted event: {}", project.getElementName());
|
||||
refresh();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
private volatile InitializeItem lastInitializeItem;
|
||||
|
||||
public SpringIndexer(SimpleLanguageServer server, BootLanguageServerParams params, AnnotationHierarchyAwareLookup<SymbolProvider> specificProviders) {
|
||||
this.server = server;
|
||||
this.params = params;
|
||||
this.projectFinder = params.projectFinder;
|
||||
this.symbolProviders = specificProviders;
|
||||
|
||||
this.symbols = Collections.synchronizedList(new ArrayList<>());
|
||||
this.symbolsByDoc = new ConcurrentHashMap<>();
|
||||
|
||||
this.updateQueue = new LinkedBlockingQueue<>();
|
||||
this.updateWorker = new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
while (true) {
|
||||
WorkerItem workerItem = updateQueue.take();
|
||||
workerItem.run();
|
||||
}
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
// ignore
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}, "Spring Annotation Index Update Worker");
|
||||
updateWorker.start();
|
||||
|
||||
getWorkspaceService().onDidChangeWorkspaceFolders(evt -> {
|
||||
log.debug("workspace roots have changed event arrived - added: " + evt.getEvent().getAdded() + " - removed: " + evt.getEvent().getRemoved());
|
||||
refresh();
|
||||
});
|
||||
|
||||
if (getProjectObserver() != null) {
|
||||
getProjectObserver().addListener(projectListener);
|
||||
}
|
||||
}
|
||||
|
||||
private ProjectObserver getProjectObserver() {
|
||||
return params.projectObserver;
|
||||
}
|
||||
|
||||
public void serverInitialized() {
|
||||
List<String> globPattern = Arrays.asList("**/*.java");
|
||||
getWorkspaceService().getFileObserver().onFileDeleted(globPattern, (file) -> {
|
||||
deleteDocument(new TextDocumentIdentifier(file).getUri());
|
||||
});
|
||||
getWorkspaceService().getFileObserver().onFileCreated(globPattern, (file) -> {
|
||||
createDocument(new TextDocumentIdentifier(file).getUri());
|
||||
});
|
||||
}
|
||||
|
||||
private SimpleWorkspaceService getWorkspaceService() {
|
||||
return server.getServer().getWorkspaceService();
|
||||
}
|
||||
|
||||
public CompletableFuture<Void> initialize(Collection<WorkspaceFolder> workspaceRoots) {
|
||||
synchronized(this) {
|
||||
try {
|
||||
if (lastInitializeItem != null && !lastInitializeItem.getFuture().isDone()) {
|
||||
lastInitializeItem.getFuture().cancel(false);
|
||||
}
|
||||
|
||||
lastInitializeItem = new InitializeItem(workspaceRoots.toArray(new WorkspaceFolder[workspaceRoots.size()]));
|
||||
updateQueue.put(lastInitializeItem);
|
||||
return lastInitializeItem.getFuture();
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.error("{}", e);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isInitializing() {
|
||||
return lastInitializeItem != null && !lastInitializeItem.getFuture().isDone();
|
||||
}
|
||||
|
||||
public void waitForInitializeTask() {
|
||||
synchronized (this) {
|
||||
if (lastInitializeItem != null) {
|
||||
try {
|
||||
lastInitializeItem.getFuture().get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void refresh() {
|
||||
synchronized (this) {
|
||||
symbols.clear();
|
||||
symbolsByDoc.clear();
|
||||
|
||||
Collection<WorkspaceFolder> roots = server.getWorkspaceRoots();
|
||||
log.debug("refresh spring indexer for roots: {}", roots.toString());
|
||||
initialize(roots);
|
||||
}
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
try {
|
||||
synchronized(this) {
|
||||
if (updateWorker != null && updateWorker.isAlive()) {
|
||||
updateWorker.interrupt();
|
||||
}
|
||||
|
||||
if (getProjectObserver() != null) {
|
||||
getProjectObserver().removeListener(projectListener);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("{}", e);
|
||||
}
|
||||
}
|
||||
|
||||
public CompletableFuture<Void> updateDocument(String docURI, String content) {
|
||||
synchronized(this) {
|
||||
if (docURI.endsWith(".java") && lastInitializeItem != null) {
|
||||
try {
|
||||
Optional<IJavaProject> maybeProject = projectFinder.find(new TextDocumentIdentifier(docURI));
|
||||
if (maybeProject.isPresent()) {
|
||||
String[] classpathEntries = getClasspathEntries(maybeProject.get());
|
||||
|
||||
UpdateItem updateItem = new UpdateItem(docURI, content, classpathEntries);
|
||||
updateQueue.put(updateItem);
|
||||
return updateItem.getFuture();
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.error("{}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public CompletableFuture<Void> deleteDocument(String deletedDocURI) {
|
||||
synchronized(this) {
|
||||
try {
|
||||
DeleteItem deleteItem = new DeleteItem(deletedDocURI);
|
||||
updateQueue.put(deleteItem);
|
||||
return deleteItem.getFuture();
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.error("{}", e);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public CompletableFuture<Void> createDocument(String docURI) {
|
||||
synchronized(this) {
|
||||
if (docURI.endsWith(".java") && lastInitializeItem != null) {
|
||||
try {
|
||||
Optional<IJavaProject> maybeProject = projectFinder.find(new TextDocumentIdentifier(docURI));
|
||||
if (maybeProject.isPresent()) {
|
||||
String[] classpathEntries = getClasspathEntries(maybeProject.get());
|
||||
|
||||
String content = FileUtils.readFileToString(new File(new URI(docURI)));
|
||||
UpdateItem updateItem = new UpdateItem(docURI, content, classpathEntries);
|
||||
updateQueue.put(updateItem);
|
||||
return updateItem.getFuture();
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.error("{}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<SymbolInformation> getAllSymbols(String query) {
|
||||
waitForInitializeTask();
|
||||
|
||||
if (query != null && query.length() > 0) {
|
||||
return searchMatchingSymbols(this.symbols, query);
|
||||
} else {
|
||||
return this.symbols;
|
||||
}
|
||||
}
|
||||
|
||||
public List<? extends SymbolInformation> getSymbols(String docURI) {
|
||||
waitForInitializeTask();
|
||||
return this.symbolsByDoc.get(docURI);
|
||||
}
|
||||
|
||||
private List<SymbolInformation> searchMatchingSymbols(List<SymbolInformation> allsymbols, String query) {
|
||||
waitForInitializeTask();
|
||||
return allsymbols.stream()
|
||||
.filter(symbol -> StringUtil.containsCharactersCaseInsensitive(symbol.getName(), query))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private void scanFiles(WorkspaceFolder directory) {
|
||||
try {
|
||||
Map<Optional<IJavaProject>, List<String>> projects = Files.walk(Paths.get(new URI(directory.getUri())))
|
||||
.filter(path -> path.getFileName().toString().endsWith(".java"))
|
||||
.filter(Files::isRegularFile)
|
||||
.map(path -> path.toAbsolutePath().toString())
|
||||
.collect(Collectors.groupingBy((javaFile) -> projectFinder.find(new TextDocumentIdentifier(new File(javaFile).toURI().toString()))));
|
||||
|
||||
projects.forEach((maybeProject, files) -> maybeProject.ifPresent(project -> scanProject(project, files.toArray(new String[0]))));
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void scanProject(IJavaProject project, String[] files) {
|
||||
try {
|
||||
ASTParser parser = ASTParser.newParser(AST.JLS9);
|
||||
String[] classpathEntries = getClasspathEntries(project);
|
||||
|
||||
scanFiles(parser, files, classpathEntries);
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void scanFile(String docURI, String content, String[] classpathEntries) throws Exception {
|
||||
ASTParser parser = ASTParser.newParser(AST.JLS9);
|
||||
Map<String, String> options = JavaCore.getOptions();
|
||||
JavaCore.setComplianceOptions(JavaCore.VERSION_1_8, options);
|
||||
parser.setCompilerOptions(options);
|
||||
parser.setKind(ASTParser.K_COMPILATION_UNIT);
|
||||
parser.setStatementsRecovery(true);
|
||||
parser.setBindingsRecovery(true);
|
||||
parser.setResolveBindings(true);
|
||||
parser.setIgnoreMethodBodies(false);
|
||||
|
||||
String[] sourceEntries = new String[] {};
|
||||
parser.setEnvironment(classpathEntries, sourceEntries, null, true);
|
||||
|
||||
String unitName = docURI.substring(docURI.lastIndexOf("/"));
|
||||
parser.setUnitName(unitName);
|
||||
parser.setSource(content.toCharArray());
|
||||
|
||||
CompilationUnit cu = (CompilationUnit) parser.createAST(null);
|
||||
|
||||
if (cu != null) {
|
||||
List<SymbolInformation> oldSymbols = symbolsByDoc.remove(docURI);
|
||||
if (oldSymbols != null) {
|
||||
symbols.removeAll(oldSymbols);
|
||||
}
|
||||
|
||||
AtomicReference<TextDocument> docRef = new AtomicReference<>();
|
||||
scanAST(cu, docURI, docRef, content);
|
||||
}
|
||||
}
|
||||
|
||||
private void scanFiles(ASTParser parser, String[] javaFiles, String[] classpathEntries) throws Exception {
|
||||
|
||||
Map<String, String> options = JavaCore.getOptions();
|
||||
JavaCore.setComplianceOptions(JavaCore.VERSION_1_8, options);
|
||||
parser.setCompilerOptions(options);
|
||||
parser.setKind(ASTParser.K_COMPILATION_UNIT);
|
||||
parser.setStatementsRecovery(true);
|
||||
parser.setBindingsRecovery(true);
|
||||
parser.setResolveBindings(true);
|
||||
parser.setIgnoreMethodBodies(false);
|
||||
|
||||
String[] sourceEntries = new String[] {};
|
||||
parser.setEnvironment(classpathEntries, sourceEntries, null, true);
|
||||
|
||||
FileASTRequestor requestor = new FileASTRequestor() {
|
||||
@Override
|
||||
public void acceptAST(String sourceFilePath, CompilationUnit cu) {
|
||||
String docURI = UriUtil.toUri(new File(sourceFilePath)).toString();
|
||||
AtomicReference<TextDocument> docRef = new AtomicReference<>();
|
||||
scanAST(cu, docURI, docRef, null);
|
||||
}
|
||||
};
|
||||
|
||||
parser.createASTs(javaFiles, null, new String[0], requestor, null);
|
||||
}
|
||||
|
||||
private void scanAST(final CompilationUnit cu, final String docURI, AtomicReference<TextDocument> docRef, final String content) {
|
||||
cu.accept(new ASTVisitor() {
|
||||
|
||||
@Override
|
||||
public boolean visit(TypeDeclaration node) {
|
||||
try {
|
||||
extractSymbolInformation(node, docURI, docRef, content);
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return super.visit(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(MethodDeclaration node) {
|
||||
try {
|
||||
extractSymbolInformation(node, docURI, docRef, content);
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return super.visit(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(SingleMemberAnnotation node) {
|
||||
try {
|
||||
extractSymbolInformation(node, docURI, docRef, content);
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return super.visit(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(NormalAnnotation node) {
|
||||
try {
|
||||
extractSymbolInformation(node, docURI, docRef, content);
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return super.visit(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(MarkerAnnotation node) {
|
||||
try {
|
||||
extractSymbolInformation(node, docURI, docRef, content);
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return super.visit(node);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void extractSymbolInformation(TypeDeclaration typeDeclaration, String docURI, AtomicReference<TextDocument> docRef, String content) throws Exception {
|
||||
Collection<SymbolProvider> providers = symbolProviders.getAll();
|
||||
if (!providers.isEmpty()) {
|
||||
TextDocument doc = getTempTextDocument(docURI, docRef, content);
|
||||
for (SymbolProvider provider : providers) {
|
||||
Collection<SymbolInformation> sbls = provider.getSymbols(typeDeclaration, doc);
|
||||
if (sbls != null) {
|
||||
sbls.forEach(symbol -> {
|
||||
symbols.add(symbol);
|
||||
symbolsByDoc.computeIfAbsent(docURI, s -> new ArrayList<SymbolInformation>()).add(symbol);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void extractSymbolInformation(MethodDeclaration methodDeclaration, String docURI, AtomicReference<TextDocument> docRef, String content) throws Exception {
|
||||
Collection<SymbolProvider> providers = symbolProviders.getAll();
|
||||
if (!providers.isEmpty()) {
|
||||
TextDocument doc = getTempTextDocument(docURI, docRef, content);
|
||||
for (SymbolProvider provider : providers) {
|
||||
Collection<SymbolInformation> sbls = provider.getSymbols(methodDeclaration, doc);
|
||||
if (sbls != null) {
|
||||
sbls.forEach(symbol -> {
|
||||
symbols.add(symbol);
|
||||
symbolsByDoc.computeIfAbsent(docURI, s -> new ArrayList<SymbolInformation>()).add(symbol);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void extractSymbolInformation(Annotation node, String docURI, AtomicReference<TextDocument> docRef, String content) throws Exception {
|
||||
ITypeBinding typeBinding = node.resolveTypeBinding();
|
||||
|
||||
if (typeBinding != null) {
|
||||
Collection<SymbolProvider> providers = symbolProviders.get(typeBinding);
|
||||
Collection<ITypeBinding> metaAnnotations = AnnotationHierarchies.getMetaAnnotations(typeBinding, symbolProviders::containsKey);
|
||||
if (!providers.isEmpty()) {
|
||||
TextDocument doc = getTempTextDocument(docURI, docRef, content);
|
||||
for (SymbolProvider provider : providers) {
|
||||
Collection<SymbolInformation> sbls = provider.getSymbols(node, typeBinding, metaAnnotations, doc);
|
||||
if (sbls != null) {
|
||||
sbls.forEach(symbol -> {
|
||||
symbols.add(symbol);
|
||||
symbolsByDoc.computeIfAbsent(docURI, s -> new ArrayList<SymbolInformation>()).add(symbol);
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
SymbolInformation symbol = provideDefaultSymbol(node, docURI, docRef, content);
|
||||
if (symbol != null) {
|
||||
symbols.add(symbol);
|
||||
symbolsByDoc.computeIfAbsent(docURI, s -> new ArrayList<SymbolInformation>()).add(symbol);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private TextDocument getTempTextDocument(String docURI, AtomicReference<TextDocument> docRef, String content) throws Exception {
|
||||
TextDocument doc = docRef.get();
|
||||
if (doc == null) {
|
||||
doc = createTempTextDocument(docURI, content);
|
||||
docRef.set(doc);
|
||||
}
|
||||
return doc;
|
||||
}
|
||||
|
||||
private TextDocument createTempTextDocument(String docURI, String content) throws Exception {
|
||||
if (content == null) {
|
||||
Path path = Paths.get(new URI(docURI));
|
||||
content = new String(Files.readAllBytes(path));
|
||||
}
|
||||
|
||||
TextDocument doc = new TextDocument(docURI, LanguageId.PLAINTEXT, 0, content);
|
||||
return doc;
|
||||
}
|
||||
|
||||
private SymbolInformation provideDefaultSymbol(Annotation node, String docURI, AtomicReference<TextDocument> docRef, String content) {
|
||||
try {
|
||||
ITypeBinding type = node.resolveTypeBinding();
|
||||
if (type != null) {
|
||||
String qualifiedName = type.getQualifiedName();
|
||||
if (qualifiedName != null && qualifiedName.startsWith("org.springframework")) {
|
||||
TextDocument doc = getTempTextDocument(docURI, docRef, content);
|
||||
SymbolInformation symbol = new SymbolInformation(node.toString(), SymbolKind.Interface,
|
||||
new Location(doc.getUri(), doc.toRange(node.getStartPosition(), node.getLength())));
|
||||
return symbol;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private String[] getClasspathEntries(IJavaProject project) throws Exception {
|
||||
IClasspath classpath = project.getClasspath();
|
||||
Stream<Path> classpathEntries = classpath.getClasspathEntries().stream();
|
||||
return classpathEntries
|
||||
.filter(path -> path.toFile().exists())
|
||||
.map(path -> path.toAbsolutePath().toString()).toArray(String[]::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* inner class to capture items for the update worker
|
||||
*/
|
||||
private interface WorkerItem {
|
||||
|
||||
public void run();
|
||||
public CompletableFuture<Void> getFuture();
|
||||
|
||||
}
|
||||
|
||||
private class InitializeItem implements WorkerItem {
|
||||
|
||||
private final WorkspaceFolder[] workspaceRoots;
|
||||
private final CompletableFuture<Void> future;
|
||||
|
||||
public InitializeItem(WorkspaceFolder[] workspaceRoots) {
|
||||
log.debug("initialze spring indexer task created for roots: " + Arrays.toString(workspaceRoots));
|
||||
|
||||
this.workspaceRoots = workspaceRoots;
|
||||
this.future = new CompletableFuture<Void>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> getFuture() {
|
||||
return future;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (!future.isCancelled()) {
|
||||
log.debug("initialze spring indexer task started for roots: " + Arrays.toString(workspaceRoots));
|
||||
|
||||
for (WorkspaceFolder root : workspaceRoots) {
|
||||
SpringIndexer.this.scanFiles(root);
|
||||
}
|
||||
|
||||
log.debug("initialze spring indexer task completed for roots: " + Arrays.toString(workspaceRoots));
|
||||
|
||||
future.complete(null);
|
||||
}
|
||||
else {
|
||||
log.debug("initialze spring indexer task canceled for roots: " + Arrays.toString(workspaceRoots));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class UpdateItem implements WorkerItem {
|
||||
|
||||
private final String docURI;
|
||||
private final String content;
|
||||
private final String[] classpathEntries;
|
||||
|
||||
private final CompletableFuture<Void> future;
|
||||
|
||||
public UpdateItem(String docURI, String content, String[] classpathEntries) {
|
||||
this.docURI = docURI;
|
||||
this.content = content;
|
||||
this.classpathEntries = classpathEntries;
|
||||
this.future = new CompletableFuture<Void>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> getFuture() {
|
||||
return future;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
SpringIndexer.this.scanFile(docURI, content, classpathEntries);
|
||||
} catch (Exception e) {
|
||||
log.error("{}", e);
|
||||
}
|
||||
future.complete(null);
|
||||
}
|
||||
}
|
||||
|
||||
private class DeleteItem implements WorkerItem {
|
||||
|
||||
private final String docURI;
|
||||
private final CompletableFuture<Void> future;
|
||||
|
||||
public DeleteItem(String docURI) {
|
||||
this.docURI = docURI;
|
||||
this.future = new CompletableFuture<Void>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> getFuture() {
|
||||
return future;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
List<SymbolInformation> oldSymbols = symbolsByDoc.remove(docURI);
|
||||
if (oldSymbols != null) {
|
||||
symbols.removeAll(oldSymbols);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("{}", e);
|
||||
}
|
||||
future.complete(null);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017, 2018 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* 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.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.concurrent.ConcurrentSkipListSet;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.lsp4j.Range;
|
||||
import org.eclipse.lsp4j.TextDocumentIdentifier;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.BootJavaHoverProvider;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.HoverProvider;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.RunningAppProvider;
|
||||
import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.HighlightParams;
|
||||
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.HoverHandler;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
|
||||
import org.springframework.ide.vscode.commons.util.Log;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
*/
|
||||
public class SpringLiveHoverWatchdog {
|
||||
|
||||
|
||||
public static final Duration DEFAULT_INTERVAL = Duration.ofMillis(5000);
|
||||
|
||||
private final long POLLING_INTERVAL_MILLISECONDS;
|
||||
private final Set<String> watchedDocs;
|
||||
private final SimpleLanguageServer server;
|
||||
private final BootJavaHoverProvider hoverProvider;
|
||||
private RunningAppProvider runningAppProvider;
|
||||
|
||||
private boolean highlightsEnabled = true;
|
||||
|
||||
private Timer timer;
|
||||
|
||||
private JavaProjectFinder projectFinder;
|
||||
|
||||
private void refreshEnablement() {
|
||||
boolean shouldEnable = highlightsEnabled && hasInterestingProject(watchedDocs.stream());
|
||||
if (shouldEnable) {
|
||||
start();
|
||||
} else {
|
||||
shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasInterestingProject(Stream<String> uris) {
|
||||
return uris.anyMatch(uri -> projectFinder.find(new TextDocumentIdentifier(uri)).isPresent());
|
||||
}
|
||||
|
||||
public SpringLiveHoverWatchdog(
|
||||
SimpleLanguageServer server,
|
||||
BootJavaHoverProvider hoverProvider,
|
||||
RunningAppProvider runningAppProvider,
|
||||
JavaProjectFinder projectFinder,
|
||||
ProjectObserver projectChanges,
|
||||
Duration pollingInterval
|
||||
) {
|
||||
this.POLLING_INTERVAL_MILLISECONDS = pollingInterval == null ? DEFAULT_INTERVAL.toMillis() : pollingInterval.toMillis();
|
||||
this.server = server;
|
||||
this.hoverProvider = hoverProvider;
|
||||
this.runningAppProvider = runningAppProvider;
|
||||
this.projectFinder = projectFinder;
|
||||
this.watchedDocs = new ConcurrentSkipListSet<>();
|
||||
projectChanges.addListener(new ProjectObserver.Listener() {
|
||||
|
||||
@Override
|
||||
public void deleted(IJavaProject project) {
|
||||
refreshEnablement();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void created(IJavaProject project) {
|
||||
refreshEnablement();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changed(IJavaProject project) {
|
||||
refreshEnablement();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private synchronized void start() {
|
||||
if (highlightsEnabled && timer == null) {
|
||||
Log.debug("Starting SpringLiveHoverWatchdog");
|
||||
this.timer = new Timer();
|
||||
|
||||
TimerTask task = new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
update();
|
||||
}
|
||||
};
|
||||
|
||||
timer.schedule(task, 0, POLLING_INTERVAL_MILLISECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void shutdown() {
|
||||
if (timer != null) {
|
||||
Log.info("Shutting down SpringLiveHoverWatchdog");
|
||||
timer.cancel();
|
||||
timer = null;
|
||||
watchedDocs.forEach(uri -> cleanupLiveHints(uri));
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void watchDocument(String docURI) {
|
||||
this.watchedDocs.add(docURI);
|
||||
refreshEnablement();
|
||||
}
|
||||
|
||||
public synchronized void unwatchDocument(String docURI) {
|
||||
this.watchedDocs.remove(docURI);
|
||||
cleanupLiveHints(docURI);
|
||||
|
||||
if (watchedDocs.size() == 0) {
|
||||
cleanupResources();
|
||||
}
|
||||
refreshEnablement();
|
||||
}
|
||||
|
||||
public void update(String docURI, SpringBootApp[] runningBootApps) {
|
||||
if (highlightsEnabled) {
|
||||
|
||||
try {
|
||||
if (runningBootApps == null) {
|
||||
runningBootApps = runningAppProvider.getAllRunningSpringApps().toArray(new SpringBootApp[0]);
|
||||
}
|
||||
|
||||
if (runningBootApps != null && runningBootApps.length > 0) {
|
||||
TextDocument doc = this.server.getTextDocumentService().get(docURI);
|
||||
if (doc != null) {
|
||||
Range[] ranges = this.hoverProvider.getLiveHoverHints(doc, runningBootApps);
|
||||
publishLiveHints(docURI, ranges);
|
||||
}
|
||||
}
|
||||
else {
|
||||
cleanupLiveHints(docURI);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void update() {
|
||||
if (this.watchedDocs.size() > 0) {
|
||||
try {
|
||||
SpringBootApp[] runningBootApps = runningAppProvider.getAllRunningSpringApps().toArray(new SpringBootApp[0]);
|
||||
for (String docURI : watchedDocs) {
|
||||
update(docURI, runningBootApps);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void publishLiveHints(String docURI, Range[] ranges) {
|
||||
server.getClient().highlight(new HighlightParams(new TextDocumentIdentifier(docURI), Arrays.asList(ranges)));
|
||||
}
|
||||
|
||||
private void cleanupLiveHints(String docURI) {
|
||||
publishLiveHints(docURI, new Range[0]);
|
||||
}
|
||||
|
||||
private void cleanupResources() {
|
||||
// TODO: close and cleanup open JMX connections and cached data
|
||||
}
|
||||
|
||||
public synchronized void enableHighlights() {
|
||||
if (!highlightsEnabled) {
|
||||
highlightsEnabled = true;
|
||||
refreshEnablement();
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void disableHighlights() {
|
||||
if (highlightsEnabled) {
|
||||
highlightsEnabled = false;
|
||||
refreshEnablement();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017, 2018 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* 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.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Optional;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.ide.vscode.boot.java.links.SourceLinks;
|
||||
import org.springframework.ide.vscode.commons.java.IClasspath;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.util.Renderables;
|
||||
|
||||
/**
|
||||
* Helper class, represents parsed info from a Resource, and provide method(s) to
|
||||
* display it somehow.
|
||||
*/
|
||||
public class SpringResource {
|
||||
|
||||
private static final String FILE = "file";
|
||||
private static final String CLASS_PATH_RESOURCE = "class path resource";
|
||||
|
||||
private SourceLinks sourceLinks;
|
||||
private String type;
|
||||
private String path;
|
||||
private IJavaProject project;
|
||||
|
||||
private static final Pattern BRACKETS = Pattern.compile("\\[[^\\]]*\\]");
|
||||
|
||||
private static final String ID_PATTERN = "\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*";
|
||||
private static final String REGEX_FQCN = ID_PATTERN + "(\\." + ID_PATTERN + ")*";
|
||||
|
||||
public SpringResource(SourceLinks sourceLinks, String toParse, IJavaProject project) {
|
||||
this.sourceLinks = sourceLinks;
|
||||
this.project = project;
|
||||
Matcher matcher = BRACKETS.matcher(toParse);
|
||||
if (matcher.find()) {
|
||||
type = toParse.substring(0, matcher.start()).trim();
|
||||
path = toParse.substring(matcher.start()+1, matcher.end()-1);
|
||||
} else if (Pattern.matches(REGEX_FQCN, toParse)) {
|
||||
// Resource is fully qualified Java type name
|
||||
type = CLASS_PATH_RESOURCE;
|
||||
path = toParse.replace('.','/') + SourceLinks.CLASS;
|
||||
} else {
|
||||
path = toParse;
|
||||
}
|
||||
}
|
||||
|
||||
public String toMarkdown() {
|
||||
if (type==null) {
|
||||
return path; //path is just the raw text in this case
|
||||
}
|
||||
Optional<String> linkUrl;
|
||||
switch (type) {
|
||||
case FILE:
|
||||
String relativePath = projectRelativePath(path);
|
||||
if (relativePath != path && path.endsWith(SourceLinks.CLASS)) {
|
||||
linkUrl = sourceLinks.sourceLinkUrlForClasspathResource(project, relativePath);
|
||||
} else {
|
||||
linkUrl = sourceLinks.sourceLinkForResourcePath(Paths.get(path));
|
||||
}
|
||||
// not a project relative path
|
||||
return linkUrl.isPresent() ? Renderables.link(relativePath, linkUrl.get()).toMarkdown()
|
||||
: "`" + projectRelativePath(path) + "`";
|
||||
case CLASS_PATH_RESOURCE:
|
||||
linkUrl = sourceLinks.sourceLinkUrlForClasspathResource(project, path);
|
||||
return linkUrl.isPresent() ? Renderables.link(path, linkUrl.get()).toMarkdown() : "`"+path+"`";
|
||||
default:
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
private String projectRelativePath(String pathStr) {
|
||||
Path path = Paths.get(pathStr);
|
||||
IClasspath classpath = project.getClasspath();
|
||||
Path outputFolder = classpath.getOutputFolder();
|
||||
if (path.startsWith(outputFolder)) {
|
||||
return outputFolder.relativize(path).toString();
|
||||
}
|
||||
return pathStr;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 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.value;
|
||||
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
*/
|
||||
public class Constants {
|
||||
|
||||
public static final String SPRING_VALUE = "org.springframework.beans.factory.annotation.Value";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 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.value;
|
||||
|
||||
import static org.springframework.ide.vscode.commons.util.StringUtil.camelCaseToHyphens;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.Annotation;
|
||||
import org.eclipse.jdt.core.dom.ITypeBinding;
|
||||
import org.eclipse.jdt.core.dom.MemberValuePair;
|
||||
import org.eclipse.jdt.core.dom.SimpleName;
|
||||
import org.eclipse.jdt.core.dom.StringLiteral;
|
||||
import org.springframework.ide.vscode.boot.configurationmetadata.ConfigurationMetadataProperty;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.CompletionProvider;
|
||||
import org.springframework.ide.vscode.boot.metadata.PropertyInfo;
|
||||
import org.springframework.ide.vscode.boot.metadata.SpringPropertyIndexProvider;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
|
||||
import org.springframework.ide.vscode.commons.util.BadLocationException;
|
||||
import org.springframework.ide.vscode.commons.util.FuzzyMap;
|
||||
import org.springframework.ide.vscode.commons.util.FuzzyMap.Match;
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
*/
|
||||
public class ValueCompletionProcessor implements CompletionProvider {
|
||||
|
||||
private final SpringPropertyIndexProvider indexProvider;
|
||||
|
||||
public ValueCompletionProcessor(SpringPropertyIndexProvider indexProvider) {
|
||||
this.indexProvider = indexProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ICompletionProposal> provideCompletions(ASTNode node, Annotation annotation, ITypeBinding type,
|
||||
int offset, IDocument doc) {
|
||||
|
||||
List<ICompletionProposal> result = new ArrayList<>();
|
||||
|
||||
try {
|
||||
FuzzyMap<PropertyInfo> index = indexProvider.getIndex(doc);
|
||||
|
||||
// case: @Value(<*>)
|
||||
if (node == annotation && doc.get(offset - 1, 2).endsWith("()")) {
|
||||
List<Match<PropertyInfo>> matches = findMatches("", index);
|
||||
|
||||
for (Match<PropertyInfo> match : matches) {
|
||||
|
||||
DocumentEdits edits = new DocumentEdits(doc);
|
||||
edits.replace(offset, offset, "\"${" + match.data.getId() + "}\"");
|
||||
|
||||
ValuePropertyKeyProposal proposal = new ValuePropertyKeyProposal(edits, match.data.getId(), match.data.getName(), null);
|
||||
result.add(proposal);
|
||||
}
|
||||
}
|
||||
// case: @Value(prefix<*>)
|
||||
else if (node instanceof SimpleName && node.getParent() instanceof Annotation) {
|
||||
computeProposalsForSimpleName(node, result, offset, doc, index);
|
||||
}
|
||||
// case: @Value(value=<*>)
|
||||
else if (node instanceof SimpleName && node.getParent() instanceof MemberValuePair
|
||||
&& "value".equals(((MemberValuePair)node.getParent()).getName().toString())) {
|
||||
computeProposalsForSimpleName(node, result, offset, doc, index);
|
||||
}
|
||||
// case: @Value("prefix<*>")
|
||||
else if (node instanceof StringLiteral && node.getParent() instanceof Annotation) {
|
||||
if (node.toString().startsWith("\"") && node.toString().endsWith("\"")) {
|
||||
computeProposalsForStringLiteral(node, result, offset, doc, index);
|
||||
}
|
||||
}
|
||||
// case: @Value(value="prefix<*>")
|
||||
else if (node instanceof StringLiteral && node.getParent() instanceof MemberValuePair
|
||||
&& "value".equals(((MemberValuePair)node.getParent()).getName().toString())) {
|
||||
if (node.toString().startsWith("\"") && node.toString().endsWith("\"")) {
|
||||
computeProposalsForStringLiteral(node, result, offset, doc, index);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void computeProposalsForSimpleName(ASTNode node, List<ICompletionProposal> completions, int offset,
|
||||
IDocument doc, FuzzyMap<PropertyInfo> index) {
|
||||
String prefix = identifyPropertyPrefix(node.toString(), offset - node.getStartPosition());
|
||||
|
||||
int startOffset = node.getStartPosition();
|
||||
int endOffset = node.getStartPosition() + node.getLength();
|
||||
|
||||
String proposalPrefix = "\"";
|
||||
String proposalPostfix = "\"";
|
||||
|
||||
List<Match<PropertyInfo>> matches = findMatches(prefix, index);
|
||||
|
||||
for (Match<PropertyInfo> match : matches) {
|
||||
|
||||
DocumentEdits edits = new DocumentEdits(doc);
|
||||
edits.replace(startOffset, endOffset, proposalPrefix + "${" + match.data.getId() + "}" + proposalPostfix);
|
||||
|
||||
ValuePropertyKeyProposal proposal = new ValuePropertyKeyProposal(edits, match.data.getId(), match.data.getName(), null);
|
||||
completions.add(proposal);
|
||||
}
|
||||
}
|
||||
|
||||
private void computeProposalsForStringLiteral(ASTNode node, List<ICompletionProposal> completions, int offset,
|
||||
IDocument doc, FuzzyMap<PropertyInfo> index) throws BadLocationException {
|
||||
String prefix = identifyPropertyPrefix(doc.get(node.getStartPosition() + 1, offset - (node.getStartPosition() + 1)), offset - (node.getStartPosition() + 1));
|
||||
|
||||
int startOffset = offset - prefix.length();
|
||||
int endOffset = offset;
|
||||
|
||||
String prePrefix = doc.get(node.getStartPosition() + 1, offset - prefix.length() - node.getStartPosition() - 1);
|
||||
|
||||
String preCompletion;
|
||||
if (prePrefix.endsWith("${")) {
|
||||
preCompletion = "";
|
||||
}
|
||||
else if (prePrefix.endsWith("$")) {
|
||||
preCompletion = "{";
|
||||
}
|
||||
else {
|
||||
preCompletion = "${";
|
||||
}
|
||||
|
||||
String fullNodeContent = doc.get(node.getStartPosition(), node.getLength());
|
||||
String postCompletion = isClosingBracketMissing(fullNodeContent + preCompletion) ? "}" : "";
|
||||
|
||||
List<Match<PropertyInfo>> matches = findMatches(prefix, index);
|
||||
|
||||
for (Match<PropertyInfo> match : matches) {
|
||||
|
||||
DocumentEdits edits = new DocumentEdits(doc);
|
||||
edits.replace(startOffset, endOffset, preCompletion + match.data.getId() + postCompletion);
|
||||
|
||||
ValuePropertyKeyProposal proposal = new ValuePropertyKeyProposal(edits, match.data.getId(), match.data.getName(), null);
|
||||
completions.add(proposal);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isClosingBracketMissing(String fullNodeContent) {
|
||||
int bracketOpens = 0;
|
||||
|
||||
for (int i = 0; i < fullNodeContent.length(); i++) {
|
||||
if (fullNodeContent.charAt(i) == '{') {
|
||||
bracketOpens++;
|
||||
}
|
||||
else if (fullNodeContent.charAt(i) == '}') {
|
||||
bracketOpens--;
|
||||
}
|
||||
}
|
||||
|
||||
return bracketOpens > 0;
|
||||
}
|
||||
|
||||
public String identifyPropertyPrefix(String nodeContent, int offset) {
|
||||
String result = nodeContent.substring(0, offset);
|
||||
|
||||
int i = offset - 1;
|
||||
while (i >= 0) {
|
||||
char c = nodeContent.charAt(i);
|
||||
if (c == '}' || c == '{' || c == '$' || c == '#') {
|
||||
result = result.substring(i + 1, offset);
|
||||
break;
|
||||
}
|
||||
i--;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<Match<PropertyInfo>> findMatches(String prefix, FuzzyMap<PropertyInfo> index) {
|
||||
List<Match<PropertyInfo>> matches = index.find(camelCaseToHyphens(prefix));
|
||||
return matches;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017, 2018 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.value;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.Annotation;
|
||||
import org.eclipse.jdt.core.dom.ITypeBinding;
|
||||
import org.eclipse.jdt.core.dom.MemberValuePair;
|
||||
import org.eclipse.jdt.core.dom.NodeFinder;
|
||||
import org.eclipse.jdt.core.dom.StringLiteral;
|
||||
import org.eclipse.jdt.core.dom.TypeDeclaration;
|
||||
import org.eclipse.lsp4j.Hover;
|
||||
import org.eclipse.lsp4j.Range;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
||||
import org.json.JSONObject;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.HoverProvider;
|
||||
import org.springframework.ide.vscode.boot.java.livehover.LiveHoverUtils;
|
||||
import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
*/
|
||||
public class ValueHoverProvider implements HoverProvider {
|
||||
|
||||
@Override
|
||||
public Hover provideHover(ASTNode node, Annotation annotation, ITypeBinding type, int offset,
|
||||
TextDocument doc, IJavaProject project, SpringBootApp[] runningApps) {
|
||||
|
||||
try {
|
||||
ASTNode exactNode = NodeFinder.perform(node, offset, 0);
|
||||
|
||||
// case: @Value("prefix<*>")
|
||||
if (exactNode != null && exactNode instanceof StringLiteral && exactNode.getParent() instanceof Annotation) {
|
||||
if (exactNode.toString().startsWith("\"") && exactNode.toString().endsWith("\"")) {
|
||||
return provideHover(exactNode.toString(), offset - exactNode.getStartPosition(), exactNode.getStartPosition(), doc, runningApps);
|
||||
}
|
||||
}
|
||||
// case: @Value(value="prefix<*>")
|
||||
else if (exactNode != null && exactNode instanceof StringLiteral && exactNode.getParent() instanceof MemberValuePair
|
||||
&& "value".equals(((MemberValuePair)exactNode.getParent()).getName().toString())) {
|
||||
if (exactNode.toString().startsWith("\"") && exactNode.toString().endsWith("\"")) {
|
||||
return provideHover(exactNode.toString(), offset - exactNode.getStartPosition(), exactNode.getStartPosition(), doc, runningApps);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Range> getLiveHoverHints(Annotation annotation, TextDocument doc, SpringBootApp[] runningApps) {
|
||||
return null;
|
||||
}
|
||||
|
||||
private Hover provideHover(String value, int offset, int nodeStartOffset, TextDocument doc, SpringBootApp[] runningApps) {
|
||||
|
||||
try {
|
||||
LocalRange range = getPropertyRange(value, offset);
|
||||
if (range != null) {
|
||||
String propertyKey = value.substring(range.getStart(), range.getEnd());
|
||||
|
||||
if (propertyKey != null) {
|
||||
Map<SpringBootApp, JSONObject> allProperties = getPropertiesFromProcesses(runningApps);
|
||||
|
||||
StringBuilder hover = new StringBuilder();
|
||||
|
||||
for (SpringBootApp app : allProperties.keySet()) {
|
||||
JSONObject properties = allProperties.get(app);
|
||||
Iterator<?> keys = properties.keys();
|
||||
while (keys.hasNext()) {
|
||||
String key = (String) keys.next();
|
||||
if (properties.get(key) instanceof JSONObject) {
|
||||
JSONObject props = properties.getJSONObject(key);
|
||||
|
||||
if (props.has(propertyKey)) {
|
||||
String propertyValue = props.getString(propertyKey);
|
||||
|
||||
hover.append(propertyKey + " : " + propertyValue);
|
||||
hover.append(" (from: " + key + ")\n\n");
|
||||
hover.append(LiveHoverUtils.niceAppName(app));
|
||||
hover.append("\n\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hover.length() > 0) {
|
||||
Range hoverRange = doc.toRange(nodeStartOffset + range.getStart(), range.getEnd() - range.getStart());
|
||||
Hover result = new Hover(ImmutableList.of(Either.forLeft(hover.toString())));
|
||||
result.setRange(hoverRange);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Map<SpringBootApp, JSONObject> getPropertiesFromProcesses(SpringBootApp[] runningApps) {
|
||||
Map<SpringBootApp, JSONObject> result = new HashMap<>();
|
||||
|
||||
try {
|
||||
for (SpringBootApp app : runningApps) {
|
||||
String environment = app.getEnvironment();
|
||||
if (environment != null) {
|
||||
JSONObject env = new JSONObject(environment);
|
||||
if (env != null) {
|
||||
result.put(app, env);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public String getPropertyKey(String value, int offset) {
|
||||
LocalRange range = getPropertyRange(value, offset);
|
||||
if (range != null) {
|
||||
return value.substring(range.getStart(), range.getEnd());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public LocalRange getPropertyRange(String value, int offset) {
|
||||
int start = -1;
|
||||
int end = -1;
|
||||
|
||||
for (int i = offset - 1; i >= 0; i--) {
|
||||
if (value.charAt(i) == '{') {
|
||||
start = i + 1;
|
||||
break;
|
||||
}
|
||||
else if (value.charAt(i) == '}') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for(int i = offset; i < value.length(); i++) {
|
||||
if (value.charAt(i) == '{' || value.charAt(i) == '$') {
|
||||
break;
|
||||
}
|
||||
else if (value.charAt(i) == '}') {
|
||||
end = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (start > 0 && start < value.length() && end > 0 && end <= value.length() && start < end) {
|
||||
return new LocalRange(start, end);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static class LocalRange {
|
||||
private int start;
|
||||
private int end;
|
||||
|
||||
public LocalRange(int start, int end) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
public int getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
public int getEnd() {
|
||||
return end;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Hover provideHover(ASTNode node, TypeDeclaration typeDeclaration, ITypeBinding type, int offset,
|
||||
TextDocument doc, IJavaProject project, SpringBootApp[] runningApps) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Range> getLiveHoverHints(TypeDeclaration typeDeclaration, TextDocument doc,
|
||||
SpringBootApp[] runningApps) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 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.value;
|
||||
|
||||
import org.eclipse.lsp4j.CompletionItemKind;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
|
||||
import org.springframework.ide.vscode.commons.util.Renderable;
|
||||
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
*/
|
||||
public class ValuePropertyKeyProposal implements ICompletionProposal {
|
||||
|
||||
private DocumentEdits edits;
|
||||
private String label;
|
||||
private String detail;
|
||||
private Renderable documentation;
|
||||
|
||||
public ValuePropertyKeyProposal(DocumentEdits edits, String label, String detail, Renderable documentation) {
|
||||
this.edits = edits;
|
||||
this.label = label;
|
||||
this.detail = detail;
|
||||
this.documentation = documentation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLabel() {
|
||||
return this.label;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletionItemKind getKind() {
|
||||
return CompletionItemKind.Property;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DocumentEdits getTextEdit() {
|
||||
return this.edits;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDetail() {
|
||||
return this.detail;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Renderable getDocumentation() {
|
||||
return this.documentation;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017, 2018 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.value;
|
||||
|
||||
import static org.springframework.ide.vscode.commons.yaml.ast.NodeUtil.asScalar;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.Annotation;
|
||||
import org.eclipse.jdt.core.dom.ITypeBinding;
|
||||
import org.eclipse.jdt.core.dom.MemberValuePair;
|
||||
import org.eclipse.jdt.core.dom.StringLiteral;
|
||||
import org.eclipse.lsp4j.Location;
|
||||
import org.eclipse.lsp4j.Position;
|
||||
import org.eclipse.lsp4j.Range;
|
||||
import org.eclipse.lsp4j.WorkspaceFolder;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.ReferenceProvider;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
|
||||
import org.springframework.ide.vscode.commons.util.BadLocationException;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
import org.springframework.ide.vscode.commons.yaml.ast.YamlASTProvider;
|
||||
import org.springframework.ide.vscode.commons.yaml.ast.YamlFileAST;
|
||||
import org.springframework.ide.vscode.commons.yaml.ast.YamlParser;
|
||||
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.Parser;
|
||||
import org.springframework.ide.vscode.java.properties.parser.PropertiesAst.KeyValuePair;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
import org.yaml.snakeyaml.nodes.MappingNode;
|
||||
import org.yaml.snakeyaml.nodes.Node;
|
||||
import org.yaml.snakeyaml.nodes.NodeId;
|
||||
import org.yaml.snakeyaml.nodes.NodeTuple;
|
||||
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
*/
|
||||
public class ValuePropertyReferencesProvider implements ReferenceProvider {
|
||||
|
||||
private SimpleLanguageServer languageServer;
|
||||
|
||||
public ValuePropertyReferencesProvider(SimpleLanguageServer server) {
|
||||
this.languageServer = server;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<? extends Location>> provideReferences(ASTNode node, Annotation annotation,
|
||||
ITypeBinding type, int offset, TextDocument doc) {
|
||||
|
||||
try {
|
||||
// case: @Value("prefix<*>")
|
||||
if (node instanceof StringLiteral && node.getParent() instanceof Annotation) {
|
||||
if (node.toString().startsWith("\"") && node.toString().endsWith("\"")) {
|
||||
return provideReferences(node.toString(), offset - node.getStartPosition(), node.getStartPosition(), doc);
|
||||
}
|
||||
}
|
||||
// case: @Value(value="prefix<*>")
|
||||
else if (node instanceof StringLiteral && node.getParent() instanceof MemberValuePair
|
||||
&& "value".equals(((MemberValuePair)node.getParent()).getName().toString())) {
|
||||
if (node.toString().startsWith("\"") && node.toString().endsWith("\"")) {
|
||||
return provideReferences(node.toString(), offset - node.getStartPosition(), node.getStartPosition(), doc);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private CompletableFuture<List<? extends Location>> provideReferences(String value, int offset, int nodeStartOffset, TextDocument doc) {
|
||||
|
||||
try {
|
||||
LocalRange range = getPropertyRange(value, offset);
|
||||
if (range != null) {
|
||||
String propertyKey = value.substring(range.getStart(), range.getEnd());
|
||||
if (propertyKey != null && propertyKey.length() > 0) {
|
||||
return findReferencesFromPropertyFiles(languageServer.getWorkspaceRoots(), propertyKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public CompletableFuture<List<? extends Location>> findReferencesFromPropertyFiles(
|
||||
Collection<WorkspaceFolder> workspaceRoots,
|
||||
String propertyKey
|
||||
) {
|
||||
for (WorkspaceFolder workspaceFolder : workspaceRoots) {
|
||||
try {
|
||||
Path workspaceRoot = Paths.get(new URI(workspaceFolder.getUri()));
|
||||
try (Stream<Path> walk = Files.walk(workspaceRoot)) {
|
||||
List<Location> locations = walk
|
||||
.filter(path -> isPropertiesFile(path))
|
||||
.filter(path -> path.toFile().isFile())
|
||||
.map(path -> findReferences(path, propertyKey))
|
||||
.flatMap(Collection::stream)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return CompletableFuture.completedFuture(locations);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isPropertiesFile(Path path) {
|
||||
Path fileName = path.getFileName();
|
||||
|
||||
if (fileName.toString().endsWith(".properties") || path.toString().endsWith(".yml")) {
|
||||
return fileName.toString().contains("application");
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private List<Location> findReferences(Path path, String propertyKey) {
|
||||
String filePath = path.toString();
|
||||
if (filePath.endsWith(".properties")) {
|
||||
return findReferencesInPropertiesFile(filePath, propertyKey);
|
||||
}
|
||||
else if (filePath.endsWith(".yml")) {
|
||||
return findReferencesInYMLFile(filePath, propertyKey);
|
||||
}
|
||||
return new ArrayList<Location>();
|
||||
}
|
||||
|
||||
private List<Location> findReferencesInYMLFile(String filePath, String propertyKey) {
|
||||
List<Location> foundLocations = new ArrayList<>();
|
||||
|
||||
try {
|
||||
String fileContent = FileUtils.readFileToString(new File(filePath));
|
||||
|
||||
Yaml yaml = new Yaml();
|
||||
YamlASTProvider parser = new YamlParser(yaml);
|
||||
|
||||
URI docURI = Paths.get(filePath).toUri();
|
||||
TextDocument doc = new TextDocument(docURI.toString(), null);
|
||||
doc.setText(fileContent);
|
||||
YamlFileAST ast = parser.getAST(doc);
|
||||
|
||||
List<Node> nodes = ast.getNodes();
|
||||
if (nodes != null && !nodes.isEmpty()) {
|
||||
for (Node node : nodes) {
|
||||
Node foundNode = findNode(node, "", propertyKey);
|
||||
if (foundNode != null) {
|
||||
|
||||
Position start = new Position();
|
||||
start.setLine(foundNode.getStartMark().getLine());
|
||||
start.setCharacter(foundNode.getStartMark().getColumn());
|
||||
|
||||
Position end = new Position();
|
||||
end.setLine(foundNode.getEndMark().getLine());
|
||||
end.setCharacter(foundNode.getEndMark().getColumn());
|
||||
|
||||
Range range = new Range();
|
||||
range.setStart(start);
|
||||
range.setEnd(end);
|
||||
|
||||
Location location = new Location(docURI.toString(), range);
|
||||
foundLocations.add(location);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return foundLocations;
|
||||
}
|
||||
|
||||
protected Node findNode(Node node, String prefix, String propertyKey) {
|
||||
if (node.getNodeId().equals(NodeId.mapping)) {
|
||||
for (NodeTuple entry : ((MappingNode)node).getValue()) {
|
||||
Node keyNode = entry.getKeyNode();
|
||||
String key = asScalar(keyNode);
|
||||
|
||||
String combinedKey = prefix.length() > 0 ? prefix + "." + key : key;
|
||||
|
||||
if (combinedKey != null && combinedKey.equals(propertyKey)) {
|
||||
return keyNode;
|
||||
}
|
||||
else {
|
||||
Node recursive = findNode(entry.getValueNode(), combinedKey, propertyKey);
|
||||
if (recursive != null) {
|
||||
return recursive;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private List<Location> findReferencesInPropertiesFile(String filePath, String propertyKey) {
|
||||
List<Location> foundLocations = new ArrayList<>();
|
||||
|
||||
try {
|
||||
String fileContent = FileUtils.readFileToString(new File(filePath));
|
||||
|
||||
Parser parser = new AntlrParser();
|
||||
ParseResults parseResults = parser.parse(fileContent);
|
||||
|
||||
if (parseResults != null && parseResults.ast != null) {
|
||||
parseResults.ast.getNodes(KeyValuePair.class).forEach(pair -> {
|
||||
if (pair.getKey() != null && pair.getKey().decode().equals(propertyKey)) {
|
||||
URI docURI = Paths.get(filePath).toUri();
|
||||
TextDocument doc = new TextDocument(docURI.toString(), null);
|
||||
doc.setText(fileContent);
|
||||
|
||||
try {
|
||||
int line = doc.getLineOfOffset(pair.getKey().getOffset());
|
||||
int startInLine = pair.getKey().getOffset() - doc.getLineOffset(line);
|
||||
int endInLine = startInLine + (pair.getKey().getLength());
|
||||
|
||||
Position start = new Position();
|
||||
start.setLine(line);
|
||||
start.setCharacter(startInLine);
|
||||
|
||||
Position end = new Position();
|
||||
end.setLine(line);
|
||||
end.setCharacter(endInLine);
|
||||
|
||||
Range range = new Range();
|
||||
range.setStart(start);
|
||||
range.setEnd(end);
|
||||
|
||||
Location location = new Location(docURI.toString(), range);
|
||||
foundLocations.add(location);
|
||||
|
||||
} catch (BadLocationException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return foundLocations;
|
||||
}
|
||||
|
||||
public LocalRange getPropertyRange(String value, int offset) {
|
||||
int start = -1;
|
||||
int end = -1;
|
||||
|
||||
for (int i = offset - 1; i >= 0; i--) {
|
||||
if (value.charAt(i) == '{') {
|
||||
start = i + 1;
|
||||
break;
|
||||
}
|
||||
else if (value.charAt(i) == '}') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for(int i = offset; i < value.length(); i++) {
|
||||
if (value.charAt(i) == '{' || value.charAt(i) == '$') {
|
||||
break;
|
||||
}
|
||||
else if (value.charAt(i) == '}') {
|
||||
end = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (start > 0 && start < value.length() && end > 0 && end <= value.length() && start < end) {
|
||||
return new LocalRange(start, end);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static class LocalRange {
|
||||
private int start;
|
||||
private int end;
|
||||
|
||||
public LocalRange(int start, int end) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
public int getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
public int getEnd() {
|
||||
return end;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016 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.metadata;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.springframework.ide.vscode.boot.metadata.ValueProviderRegistry.ValueProviderStrategy;
|
||||
import org.springframework.ide.vscode.boot.metadata.hints.StsValueHint;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.util.FuzzyMatcher;
|
||||
import org.springframework.ide.vscode.commons.util.Log;
|
||||
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.CacheLoader.InvalidCacheLoadException;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.util.function.Tuple2;
|
||||
import reactor.util.function.Tuples;
|
||||
|
||||
/**
|
||||
* A abstract {@link ValueProviderStrategy} that is mean to help speedup successive invocations of
|
||||
* content assist with a similar 'query' string.
|
||||
* <p>
|
||||
* This implementation is meant to be used for providers that use potentially lenghty/expensive searches
|
||||
* to determine hints. Since content assist hints are requested by Eclipse CA framework directly on
|
||||
* the UI thread, they can not simply perform a lengthy search and block UI thread until it finished.
|
||||
* <p>
|
||||
* This implementation therefore does the following:
|
||||
* <ul>
|
||||
* <li>Limit the duration of time spent on the UI thread.
|
||||
* <li>Cache results of searches for a limited time.
|
||||
* <li>Speedup queries for successive queries by using the already cached result of a similar (prefix) query.
|
||||
* <li>When the time spent on UI thread waiting for a current search exceeds the allowed time limit,
|
||||
* return immediately with whatever results have been found so far.
|
||||
* </ul>
|
||||
*
|
||||
* TODO: rather than an abstract class this should really be 'Wrapper' class that delegates to another
|
||||
* {@link ValueProviderStrategy} and adds a cache in front of it.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public abstract class CachingValueProvider implements ValueProviderStrategy {
|
||||
|
||||
private static final Duration DEFAULT_TIMEOUT = Duration.ofMillis(1000);
|
||||
|
||||
/**
|
||||
* Content assist is called inside UI thread and so doing something lenghty things
|
||||
* like a JavaSearch will block the UI thread completely freezing the UI. So, we
|
||||
* only return as many results as can be obtained within this hard TIMEOUT limit.
|
||||
*/
|
||||
public static Duration TIMEOUT = DEFAULT_TIMEOUT;
|
||||
|
||||
/**
|
||||
* The maximum number of results returned for a single request. Used to limit the
|
||||
* values that are cached per entry.
|
||||
*/
|
||||
private int MAX_RESULTS = 500;
|
||||
|
||||
private Cache<Tuple2<String,String>, CacheEntry> cache = createCache();
|
||||
|
||||
private class CacheEntry {
|
||||
boolean isComplete = false;
|
||||
int count = 0;
|
||||
Flux<StsValueHint> values;
|
||||
|
||||
public CacheEntry(String query, Flux<StsValueHint> producer) {
|
||||
values = producer
|
||||
.take(MAX_RESULTS)
|
||||
.cache(MAX_RESULTS);
|
||||
values.subscribe(); // create infinite demand so that we actually force cache entries to be fetched upto the max.
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CacheEntry [isComplete=" + isComplete + ", count=" + count + "]";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Flux<StsValueHint> getValues(IJavaProject javaProject, String query) {
|
||||
Tuple2<String, String> key = key(javaProject, query);
|
||||
CacheEntry cached = null;
|
||||
try {
|
||||
cached = cache.get(key, () -> new CacheEntry(query, getValuesIncremental(javaProject, query)));
|
||||
} catch (ExecutionException e) {
|
||||
Log.log(e);
|
||||
}
|
||||
return cached.values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to use an already cached, complete result for a query that is a prefix of the current query to speed things up.
|
||||
* <p>
|
||||
* Falls back on doing a full-blown search if there's no usable 'prefix-query' in the cache.
|
||||
*/
|
||||
private Flux<StsValueHint> getValuesIncremental(IJavaProject javaProject, String query) {
|
||||
// debug("trying to solve "+query+" incrementally");
|
||||
String subquery = query;
|
||||
while (subquery.length()>=1) {
|
||||
subquery = subquery.substring(0, subquery.length()-1);
|
||||
CacheEntry cached = null;
|
||||
try {
|
||||
cached = cache.get(key(javaProject, subquery), () -> null);
|
||||
} catch (ExecutionException | InvalidCacheLoadException e) {
|
||||
// Log.log(e);
|
||||
}
|
||||
if (cached!=null) {
|
||||
// debug("cached "+subquery+": "+cached);
|
||||
if (cached.isComplete) {
|
||||
return cached.values
|
||||
// .doOnNext((hint) -> debug("filter["+query+"]: "+hint.getValue()))
|
||||
.filter((hint) -> 0!=FuzzyMatcher.matchScore(query, hint.getValue().toString()));
|
||||
} else {
|
||||
// debug("subquery "+subquery+" cached but is incomplete");
|
||||
}
|
||||
}
|
||||
}
|
||||
// debug("full search for: "+query);
|
||||
return getValuesAsync(javaProject, query);
|
||||
}
|
||||
|
||||
protected abstract Flux<StsValueHint> getValuesAsync(IJavaProject javaProject, String query);
|
||||
|
||||
private Tuple2<String,String> key(IJavaProject javaProject, String query) {
|
||||
return Tuples.of(javaProject==null?null:javaProject.getElementName(), query);
|
||||
}
|
||||
|
||||
protected <K,V> Cache<K,V> createCache() {
|
||||
return CacheBuilder.newBuilder().expireAfterWrite(1, TimeUnit.MINUTES).expireAfterAccess(1, TimeUnit.MINUTES).build();
|
||||
}
|
||||
|
||||
public static void restoreDefaults() {
|
||||
TIMEOUT = DEFAULT_TIMEOUT;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016-2017 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.metadata;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.ide.vscode.boot.metadata.ValueProviderRegistry.ValueProviderStrategy;
|
||||
import org.springframework.ide.vscode.boot.metadata.hints.StsValueHint;
|
||||
import org.springframework.ide.vscode.commons.java.Flags;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.java.IType;
|
||||
import org.springframework.ide.vscode.commons.util.Log;
|
||||
import org.springframework.ide.vscode.commons.util.StringUtil;
|
||||
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
/**
|
||||
* Provides the algorithm for 'class-reference' valueProvider.
|
||||
* <p>
|
||||
* See: https://github.com/spring-projects/spring-boot/blob/master/spring-boot-docs/src/main/asciidoc/appendix-configuration-metadata.adoc
|
||||
*
|
||||
* @author Kris De Volder
|
||||
* @author Alex Boyko
|
||||
*/
|
||||
public class ClassReferenceProvider extends CachingValueProvider {
|
||||
|
||||
/**
|
||||
* Default value for the 'concrete' parameter.
|
||||
*/
|
||||
private static final boolean DEFAULT_CONCRETE = true;
|
||||
|
||||
private static final ClassReferenceProvider UNTARGETTED_INSTANCE = new ClassReferenceProvider(null, DEFAULT_CONCRETE);
|
||||
|
||||
public static final Function<Map<String, Object>, ValueProviderStrategy> FACTORY = applyOn(
|
||||
1, TimeUnit.MINUTES,
|
||||
(params) -> {
|
||||
String target = getTarget(params);
|
||||
Boolean concrete = getConcrete(params);
|
||||
if (target!=null || concrete!=null) {
|
||||
if (concrete==null) {
|
||||
concrete = DEFAULT_CONCRETE;
|
||||
}
|
||||
return new ClassReferenceProvider(target, concrete);
|
||||
}
|
||||
return UNTARGETTED_INSTANCE;
|
||||
}
|
||||
);
|
||||
|
||||
private static <K,V> Function<K,V> applyOn(long duration, TimeUnit unit, Function<K,V> func) {
|
||||
Cache<K,V> cache = CacheBuilder.newBuilder().expireAfterAccess(duration, unit).expireAfterWrite(duration, unit).build();
|
||||
return (k) -> {
|
||||
try {
|
||||
return cache.get(k, () -> func.apply(k));
|
||||
} catch (ExecutionException e) {
|
||||
Log.log(e);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static String getTarget(Map<String, Object> params) {
|
||||
if (params!=null) {
|
||||
Object obj = params.get("target");
|
||||
if (obj instanceof String) {
|
||||
String target = (String) obj;
|
||||
if (StringUtil.hasText(target)) {
|
||||
return target;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean isAbstract(IType type) {
|
||||
try {
|
||||
return type.isInterface() || Flags.isAbstract(type.getFlags());
|
||||
} catch (Exception e) {
|
||||
Log.log(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static Boolean getConcrete(Map<String, Object> params) {
|
||||
try {
|
||||
if (params!=null) {
|
||||
Object obj = params.get("concrete");
|
||||
if (obj instanceof String) {
|
||||
String concrete = (String) obj;
|
||||
return Boolean.valueOf(concrete);
|
||||
} else if (obj instanceof Boolean) {
|
||||
return (Boolean) obj;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.log(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional, fully qualified name of the 'target' type. Suggested hints should be a subtype of this type.
|
||||
*/
|
||||
private String target;
|
||||
|
||||
/**
|
||||
* Optional parameter, whether only concrete types should be suggested. Default value is true.
|
||||
*/
|
||||
private boolean concrete;
|
||||
|
||||
private ClassReferenceProvider(String target, boolean concrete) {
|
||||
this.target = target;
|
||||
this.concrete = concrete;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Flux<StsValueHint> getValuesAsync(IJavaProject javaProject, String query) {
|
||||
IType targetType = target == null || target.isEmpty() ? javaProject.getClasspath().findType("java.lang.Object") : javaProject.getClasspath().findType(target);
|
||||
if (targetType == null) {
|
||||
return Flux.empty();
|
||||
}
|
||||
Set<IType> allSubclasses = javaProject.getClasspath()
|
||||
.allSubtypesOf(targetType)
|
||||
.filter(t -> Flags.isPublic(t.getFlags()) && !concrete || !isAbstract(t))
|
||||
.collect(Collectors.toSet())
|
||||
.block();
|
||||
if (allSubclasses.isEmpty()) {
|
||||
return Flux.empty();
|
||||
} else {
|
||||
return javaProject.getClasspath()
|
||||
.fuzzySearchTypes(query, type -> allSubclasses.contains(type))
|
||||
.collectSortedList((o1, o2) -> o2.getT2().compareTo(o1.getT2()))
|
||||
.flatMapIterable(l -> l)
|
||||
.map(t -> StsValueHint.create(t.getT1()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016, 2017 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.metadata;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.lsp4j.TextDocumentIdentifier;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.ProgressService;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver;
|
||||
import org.springframework.ide.vscode.commons.util.FuzzyMap;
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
|
||||
public class DefaultSpringPropertyIndexProvider implements SpringPropertyIndexProvider {
|
||||
|
||||
private static final FuzzyMap<PropertyInfo> EMPTY_INDEX = new SpringPropertyIndex(null, null);
|
||||
|
||||
private JavaProjectFinder javaProjectFinder;
|
||||
private SpringPropertiesIndexManager indexManager;
|
||||
|
||||
private ProgressService progressService = (id, msg) -> { /*ignore*/ };
|
||||
|
||||
public DefaultSpringPropertyIndexProvider(JavaProjectFinder javaProjectFinder, ProjectObserver projectObserver) {
|
||||
this.javaProjectFinder = javaProjectFinder;
|
||||
this.indexManager = new SpringPropertiesIndexManager(ValueProviderRegistry.getDefault(), projectObserver);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FuzzyMap<PropertyInfo> getIndex(IDocument doc) {
|
||||
Optional<IJavaProject> jp = javaProjectFinder.find(new TextDocumentIdentifier(doc.getUri()));
|
||||
if (jp.isPresent()) {
|
||||
return indexManager.get(jp.get(), progressService);
|
||||
}
|
||||
return EMPTY_INDEX;
|
||||
}
|
||||
|
||||
public void setProgressService(ProgressService progressService) {
|
||||
this.progressService = progressService;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015 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.metadata;
|
||||
|
||||
import static org.springframework.ide.vscode.commons.util.StringUtil.hasText;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.ide.vscode.commons.util.FuzzyMap;
|
||||
import org.springframework.ide.vscode.commons.util.FuzzyMap.Match;
|
||||
import org.springframework.ide.vscode.commons.util.StringUtil;
|
||||
|
||||
/**
|
||||
* An index navigator allows selecting subset of a property index as if
|
||||
* navigating the index by selecting on a property
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class IndexNavigator {
|
||||
|
||||
//Possible opitmization: we could cache prefix match candidate and extended match candidate
|
||||
// since it is assumed that the index is immutable for the lifetime of
|
||||
// the index navigator.
|
||||
|
||||
private static final char NAV_CHAR = '.';
|
||||
|
||||
/**
|
||||
* Property access in this navigator are interpreted relative
|
||||
* to this prefix
|
||||
*/
|
||||
private String prefix = null;
|
||||
private FuzzyMap<PropertyInfo> index;
|
||||
|
||||
private IndexNavigator(FuzzyMap<PropertyInfo> index) {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
private IndexNavigator(FuzzyMap<PropertyInfo> index, String prefix) {
|
||||
this.index = index;
|
||||
this.prefix = prefix;
|
||||
}
|
||||
|
||||
public static IndexNavigator with(FuzzyMap<PropertyInfo> index) {
|
||||
return new IndexNavigator(index);
|
||||
}
|
||||
|
||||
public IndexNavigator selectSubProperty(String name) {
|
||||
return new IndexNavigator(index, join(prefix, name));
|
||||
}
|
||||
|
||||
protected String join(String prefix, String postfix) {
|
||||
if (!hasText(prefix)) {
|
||||
return postfix;
|
||||
} else {
|
||||
return prefix + NAV_CHAR + postfix;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return property info that is an exact match with the current prefix or
|
||||
* null if there's no exact match
|
||||
*/
|
||||
public PropertyInfo getExactMatch() {
|
||||
if (prefix!=null) {
|
||||
PropertyInfo candidate = index.findLongestCommonPrefixEntry(prefix);
|
||||
if (candidate!=null && candidate.getId().equals(prefix)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a property that has the current prefix as a 'true' prefix. A true prefix
|
||||
* is a String that has the current prefix as a prefix and continues onward with
|
||||
* a navigation operation.
|
||||
*/
|
||||
public PropertyInfo getExtensionCandidate() {
|
||||
//If current prefix is null then all entries in the index are candidates since
|
||||
// the index is at the 'root' of the tree and we don't need a '.' to navigate
|
||||
String extendedPrefix = prefix==null?"":prefix + NAV_CHAR;
|
||||
PropertyInfo candidate = index.findLongestCommonPrefixEntry(extendedPrefix);
|
||||
if (candidate.getId().startsWith(extendedPrefix)) {
|
||||
return candidate;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getPrefix() {
|
||||
return prefix;
|
||||
}
|
||||
|
||||
public List<Match<PropertyInfo>> findMatching(String query) {
|
||||
if (!StringUtil.hasText(prefix)) {
|
||||
return index.find(query);
|
||||
} else {
|
||||
String dottedPrefix = prefix +".";
|
||||
List<Match<PropertyInfo>> candidates = index.find(dottedPrefix + query);
|
||||
if (!candidates.isEmpty()) {
|
||||
//TODO: we can do better than this using treemap to narrow based on
|
||||
// prefix
|
||||
List<Match<PropertyInfo>> matches = new ArrayList<Match<PropertyInfo>>(candidates.size());
|
||||
for (Match<PropertyInfo> match : candidates) {
|
||||
if (match.data.getId().startsWith(dottedPrefix)){
|
||||
matches.add(match);
|
||||
}
|
||||
}
|
||||
return matches;
|
||||
}
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "IndexNavigator("+prefix+")";
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return getExactMatch()==null && getExtensionCandidate()==null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016-2017 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.metadata;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.ide.vscode.boot.metadata.ValueProviderRegistry.ValueProviderStrategy;
|
||||
import org.springframework.ide.vscode.boot.metadata.hints.StsValueHint;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.util.function.Tuples;
|
||||
|
||||
/**
|
||||
* Provides the algorithm for 'logger-name' valueProvider.
|
||||
* <p>
|
||||
* See: https://github.com/spring-projects/spring-boot/blob/master/spring-boot-docs/src/main/asciidoc/appendix-configuration-metadata.adoc
|
||||
*
|
||||
* @author Kris De Volder
|
||||
* @author Alex Boyko
|
||||
*/
|
||||
public class LoggerNameProvider extends CachingValueProvider {
|
||||
|
||||
private static final ValueProviderStrategy INSTANCE = new LoggerNameProvider();
|
||||
public static final Function<Map<String, Object>, ValueProviderStrategy> FACTORY = (params) -> INSTANCE;
|
||||
|
||||
@Override
|
||||
protected Flux<StsValueHint> getValuesAsync(IJavaProject javaProject, String query) {
|
||||
return Flux.concat(
|
||||
javaProject.getClasspath()
|
||||
.fuzzySearchPackages(query)
|
||||
.map(t -> Tuples.of(StsValueHint.create(t.getT1()), t.getT2())),
|
||||
javaProject.getClasspath()
|
||||
.fuzzySearchTypes(query, null)
|
||||
.map(t -> Tuples.of(StsValueHint.create(t.getT1()), t.getT2()))
|
||||
)
|
||||
.collectSortedList((o1, o2) -> o2.getT2().compareTo(o1.getT2()))
|
||||
.flatMapIterable(l -> l)
|
||||
.map(t -> t.getT1());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015 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.metadata;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
import org.springframework.ide.eclipse.org.json.JSONArray;
|
||||
import org.springframework.ide.eclipse.org.json.JSONObject;
|
||||
|
||||
/**
|
||||
* Helper class to manipulate data in a file presumed to contain
|
||||
* spring-boot configuration data.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
* @author Alex Boyko
|
||||
*/
|
||||
public class MetadataManipulator {
|
||||
|
||||
private abstract class Content {
|
||||
public abstract String toString();
|
||||
public abstract void addProperty(JSONObject jsonObject) throws Exception;
|
||||
}
|
||||
|
||||
/**
|
||||
* Content was parse as JSONObject.
|
||||
*/
|
||||
private class ParsedContent extends Content {
|
||||
|
||||
private JSONObject object;
|
||||
|
||||
public ParsedContent(JSONObject o) {
|
||||
this.object = o;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return object.toString(indentFactor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addProperty(JSONObject propertyData) throws Exception {
|
||||
JSONArray properties = object.getJSONArray("properties");
|
||||
properties.put(properties.length(), propertyData);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Content that is 'unparsed' and just a bunch of text.
|
||||
* Used only as a fallback when data in file can't
|
||||
* be parsed.
|
||||
* <p>
|
||||
* This content is manipulated by string manipulation.
|
||||
* It is less reliable, but can be done even if the
|
||||
* file data is not parseable.
|
||||
*/
|
||||
private class RawContent extends Content {
|
||||
|
||||
private StringBuilder doc;
|
||||
|
||||
public RawContent(String content) {
|
||||
this.doc = new StringBuilder(content);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return doc.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addProperty(JSONObject propertyData) throws Exception {
|
||||
int insertAt = findLast(']');
|
||||
if (insertAt<0) {
|
||||
//although we're not looking for much, we didn't find it!
|
||||
//Funky file contents. Let's just insert something at end of file in a 'best effort' spirit.
|
||||
insertAt = doc.length();
|
||||
}
|
||||
insert(insertAt, "\n");
|
||||
|
||||
insert(insertAt, propertyData.toString(indentFactor));
|
||||
|
||||
int insertComma = findInsertCommaPos(insertAt);
|
||||
if (insertComma>=0) {
|
||||
insert(insertComma, ",");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maybe we need to add a comma in front of the new entry. This
|
||||
* method finds if/where to stick this comma.
|
||||
* @throws Exception
|
||||
*/
|
||||
private int findInsertCommaPos(int pos) throws Exception {
|
||||
pos--;
|
||||
while (pos>=0 && Character.isWhitespace(doc.charAt(pos))) {
|
||||
pos--;
|
||||
}
|
||||
if (pos>=0) {
|
||||
char c = doc.charAt(pos);
|
||||
if (c == '}') {
|
||||
//Add a comma after a '}'
|
||||
return pos+1;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private int insert(int insertAt, String str) throws Exception {
|
||||
if (insertAt < doc.length()) {
|
||||
doc.replace(insertAt, insertAt, str);
|
||||
} else {
|
||||
doc.append(str);
|
||||
}
|
||||
return insertAt + str.length();
|
||||
}
|
||||
|
||||
private int findLast(char toFind) throws Exception {
|
||||
int pos = doc.length()-1;
|
||||
while (pos>=0 && doc.charAt(pos)!=toFind) {
|
||||
pos--;
|
||||
}
|
||||
//We got here either because
|
||||
// - we found char at pos or..
|
||||
// - we reached position *before* start of file (i.e. -1)
|
||||
return pos;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public interface ContentStore {
|
||||
String getContents() throws Exception;
|
||||
void setContents(String content) throws Exception;
|
||||
}
|
||||
|
||||
private static final String INITIAL_CONTENT =
|
||||
"{\"properties\": [\n" +
|
||||
"]}";
|
||||
|
||||
private static final String ENCODING = "UTF8";
|
||||
private ContentStore contentStore;
|
||||
private Content fContent;
|
||||
private int indentFactor = 2;
|
||||
|
||||
public MetadataManipulator(ContentStore contentStore) {
|
||||
this.contentStore = contentStore;
|
||||
}
|
||||
|
||||
public MetadataManipulator(final File file) {
|
||||
this(new ContentStore() {
|
||||
|
||||
@Override
|
||||
public String getContents() throws Exception {
|
||||
return new String(Files.readAllBytes(Paths.get(file.toURI())), ENCODING);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContents(String content) throws Exception {
|
||||
Files.write(Paths.get(file.toURI()), content.getBytes(ENCODING));
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
private Content getContent() throws Exception {
|
||||
if (fContent==null) {
|
||||
fContent = readContent();
|
||||
}
|
||||
return fContent;
|
||||
}
|
||||
|
||||
private Content readContent() throws Exception {
|
||||
String content = contentStore.getContents();
|
||||
if (content.trim().isEmpty()) {
|
||||
JSONObject o = initialContent();
|
||||
return new ParsedContent(o);
|
||||
} else {
|
||||
try {
|
||||
return new ParsedContent(new JSONObject(content));
|
||||
} catch (Exception e) {
|
||||
//couldn't parse?
|
||||
return new RawContent(content);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void addDefaultInfo(String propertyName) throws Exception {
|
||||
getContent().addProperty(createDefaultData(propertyName));
|
||||
}
|
||||
|
||||
private JSONObject createDefaultData(String propertyName) throws Exception {
|
||||
JSONObject obj = new JSONObject(new LinkedHashMap<String, Object>());
|
||||
obj.put("name", propertyName);
|
||||
obj.put("type", String.class.getName());
|
||||
obj.put("description", "A description for '"+propertyName+"'");
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the initial content (must be generated rather than being a constant to respect newline conventions
|
||||
* on user's system.
|
||||
*/
|
||||
private JSONObject initialContent() throws Exception {
|
||||
return new JSONObject(INITIAL_CONTENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* After manipulating the data, use this to persist changes back to the file.
|
||||
*/
|
||||
public void save() throws Exception {
|
||||
contentStore.setContents(getContent().toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the 'reliable' manipulations can be used (which is the case
|
||||
* only if the data in the file is valid json).
|
||||
*/
|
||||
public boolean isReliable() throws Exception {
|
||||
return getContent() instanceof ParsedContent;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016-2017 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.metadata;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.zip.ZipEntry;
|
||||
|
||||
import org.springframework.ide.vscode.boot.configurationmetadata.ConfigurationMetadataRepository;
|
||||
import org.springframework.ide.vscode.boot.configurationmetadata.ConfigurationMetadataRepositoryJsonBuilder;
|
||||
import org.springframework.ide.vscode.commons.java.IClasspath;
|
||||
import org.springframework.ide.vscode.commons.util.Log;
|
||||
|
||||
public class PropertiesLoader {
|
||||
|
||||
private static final String MAIN_SPRING_CONFIGURATION_METADATA_JSON = "META-INF/spring-configuration-metadata.json";
|
||||
|
||||
public static final String ADDITIONAL_SPRING_CONFIGURATION_METADATA_JSON = "META-INF/additional-spring-configuration-metadata.json";
|
||||
|
||||
/**
|
||||
* The default classpath location for config metadata loaded when scanning .jar files on the classpath.
|
||||
*/
|
||||
public static final String[] JAR_META_DATA_LOCATIONS = {
|
||||
MAIN_SPRING_CONFIGURATION_METADATA_JSON
|
||||
//Not scanning 'additional' metadata because it integrated already in the main data.
|
||||
};
|
||||
|
||||
/**
|
||||
* The default classpath location for config metadata loaded when scanning project output folders.
|
||||
*/
|
||||
public static final String[] PROJECT_META_DATA_LOCATIONS = {
|
||||
MAIN_SPRING_CONFIGURATION_METADATA_JSON,
|
||||
ADDITIONAL_SPRING_CONFIGURATION_METADATA_JSON
|
||||
};
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(PropertiesLoader.class.getName());
|
||||
|
||||
private ConfigurationMetadataRepositoryJsonBuilder builder = ConfigurationMetadataRepositoryJsonBuilder.create();
|
||||
|
||||
public ConfigurationMetadataRepository load(IClasspath classPath) {
|
||||
try {
|
||||
classPath.getClasspathEntries().forEach(entry -> {
|
||||
//Log.info("Indexing "+entry);
|
||||
File fileEntry = entry.toFile();
|
||||
if (fileEntry.exists()) {
|
||||
if (fileEntry.isDirectory()) {
|
||||
loadFromOutputFolder(entry);
|
||||
} else {
|
||||
loadFromJar(entry);
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
LOG.log(Level.SEVERE, "Failed to retrieve classpath", e);
|
||||
}
|
||||
ConfigurationMetadataRepository repository = builder.build();
|
||||
return repository;
|
||||
}
|
||||
|
||||
private void loadFromOutputFolder(Path outputFolderPath) {
|
||||
if (outputFolderPath != null && Files.exists(outputFolderPath)) {
|
||||
Arrays.stream(PROJECT_META_DATA_LOCATIONS).forEach(mdLoc -> {
|
||||
loadFromJsonFile(outputFolderPath.resolve(mdLoc));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void loadFromJsonFile(Path mdf) {
|
||||
if (Files.exists(mdf)) {
|
||||
InputStream is = null;
|
||||
try {
|
||||
is = Files.newInputStream(mdf);
|
||||
loadFromInputStream(mdf, is);
|
||||
} catch (Exception e) {
|
||||
LOG.log(Level.SEVERE, "Error loading file '" + mdf + "'", e);
|
||||
} finally {
|
||||
if (is!=null) {
|
||||
try {
|
||||
is.close();
|
||||
} catch (IOException e) {
|
||||
//ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void loadFromJar(Path f) {
|
||||
JarFile jarFile = null;
|
||||
try {
|
||||
jarFile = new JarFile(f.toFile());
|
||||
//jarDump(jarFile);
|
||||
for (String loc : JAR_META_DATA_LOCATIONS) {
|
||||
ZipEntry e = jarFile.getEntry(loc);
|
||||
if (e!=null) {
|
||||
loadFrom(jarFile, e);
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
LOG.log(Level.SEVERE, "Error loading JAR file", e);
|
||||
} finally {
|
||||
if (jarFile!=null) {
|
||||
try {
|
||||
jarFile.close();
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void loadFrom(JarFile jarFile, ZipEntry ze) {
|
||||
InputStream is = null;
|
||||
try {
|
||||
is = jarFile.getInputStream(ze);
|
||||
loadFromInputStream(jarFile.getName()+"["+ze.getName()+"]", is);
|
||||
} catch (Throwable e) {
|
||||
LOG.log(Level.SEVERE, "Error loading JAR file", e);
|
||||
} finally {
|
||||
if (is!=null) {
|
||||
try {
|
||||
is.close();
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void loadFromInputStream(Object origin, InputStream is) throws IOException {
|
||||
builder.withJsonResource(origin, is);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2014-2016 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.metadata;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.ide.vscode.boot.configurationmetadata.ConfigurationMetadataProperty;
|
||||
import org.springframework.ide.vscode.boot.configurationmetadata.ConfigurationMetadataSource;
|
||||
import org.springframework.ide.vscode.boot.configurationmetadata.Deprecation;
|
||||
import org.springframework.ide.vscode.boot.configurationmetadata.ValueHint;
|
||||
import org.springframework.ide.vscode.boot.configurationmetadata.ValueProvider;
|
||||
import org.springframework.ide.vscode.boot.metadata.ValueProviderRegistry.ValueProviderStrategy;
|
||||
import org.springframework.ide.vscode.boot.metadata.hints.HintProvider;
|
||||
import org.springframework.ide.vscode.boot.metadata.hints.HintProviders;
|
||||
import org.springframework.ide.vscode.boot.metadata.types.Type;
|
||||
import org.springframework.ide.vscode.boot.metadata.types.TypeParser;
|
||||
import org.springframework.ide.vscode.boot.metadata.types.TypeUtil;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableList.Builder;
|
||||
|
||||
/**
|
||||
* Information about a spring property, basically, this is the same as
|
||||
*
|
||||
* {@link ConfigurationMetadataProperty} but augmented with information
|
||||
* about {@link ConfigurationMetadataSource}s that declare the property.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class PropertyInfo {
|
||||
|
||||
/**
|
||||
* Identifies a 'Source'. This is essentially the sames as {@link ConfigurationMetadataSource}.
|
||||
* We could use {@link ConfigurationMetadataSource} directly, but this only contains
|
||||
* the info that we actually use so takes less memory.
|
||||
*/
|
||||
public static class PropertySource {
|
||||
private final String sourceType;
|
||||
private final String sourceMethod;
|
||||
public PropertySource(ConfigurationMetadataSource source) {
|
||||
String st = source.getSourceType();
|
||||
this.sourceType = st!=null?st:source.getType();
|
||||
this.sourceMethod = source.getSourceMethod();
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return sourceType+"::"+sourceMethod;
|
||||
}
|
||||
public String getSourceType() {
|
||||
return sourceType;
|
||||
}
|
||||
public String getSourceMethod() {
|
||||
return sourceMethod;
|
||||
}
|
||||
}
|
||||
|
||||
final private String id;
|
||||
private String type;
|
||||
final private String name;
|
||||
final private Object defaultValue;
|
||||
final private String description;
|
||||
private List<PropertySource> sources;
|
||||
private Deprecation deprecation;
|
||||
private ImmutableList<ValueHint> valueHints;
|
||||
private ImmutableList<ValueHint> keyHints;
|
||||
private ValueProviderStrategy valueProvider;
|
||||
private ValueProviderStrategy keyProvider;
|
||||
|
||||
public PropertyInfo(String id, String type, String name,
|
||||
Object defaultValue, String description,
|
||||
Deprecation deprecation,
|
||||
List<ValueHint> valueHints,
|
||||
List<ValueHint> keyHints,
|
||||
ValueProviderStrategy valueProvider,
|
||||
ValueProviderStrategy keyProvider,
|
||||
List<PropertySource> sources) {
|
||||
super();
|
||||
this.id = id;
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
this.defaultValue = defaultValue;
|
||||
this.description = description;
|
||||
this.deprecation = deprecation;
|
||||
this.valueHints = valueHints==null?null:ImmutableList.copyOf(valueHints);
|
||||
this.keyHints = keyHints==null?null:ImmutableList.copyOf(keyHints);
|
||||
this.valueProvider = valueProvider;
|
||||
this.keyProvider = keyProvider;
|
||||
this.sources = sources;
|
||||
}
|
||||
public PropertyInfo(ValueProviderRegistry valueProviders, ConfigurationMetadataProperty prop) {
|
||||
this(
|
||||
prop.getId(),
|
||||
prop.getType(),
|
||||
prop.getName(),
|
||||
prop.getDefaultValue(),
|
||||
prop.getDescription(),
|
||||
prop.getDeprecation(),
|
||||
prop.getHints().getValueHints(),
|
||||
prop.getHints().getKeyHints(),
|
||||
valueProviders.resolve(prop.getHints().getValueProviders()),
|
||||
valueProviders.resolve(prop.getHints().getKeyProviders()),
|
||||
null
|
||||
);
|
||||
for (ValueProvider h : prop.getHints().getValueProviders()) {
|
||||
if (h.getName().equals("handle-as")) {
|
||||
handleAs(h.getParameters().get("target"));
|
||||
}
|
||||
}
|
||||
}
|
||||
private void handleAs(Object targetObject) {
|
||||
// debug("handle-as "+this.getId()+" -> "+targetObject);
|
||||
if (targetObject instanceof String) {
|
||||
this.type = (String)targetObject;
|
||||
}
|
||||
}
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
public Object getDefaultValue() {
|
||||
return defaultValue;
|
||||
}
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public HintProvider getHints(TypeUtil typeUtil, boolean dimensionAware) {
|
||||
Type type = TypeParser.parse(this.type);
|
||||
if (TypeUtil.isMap(type)) {
|
||||
return HintProviders.forMap(keyHints(typeUtil), valueHints(typeUtil), TypeUtil.getDomainType(type), dimensionAware);
|
||||
} else if (TypeUtil.isSequencable(type)) {
|
||||
if (dimensionAware) {
|
||||
if (TypeUtil.isSequencable(type)) {
|
||||
return HintProviders.forDomainAt(valueHints(typeUtil), TypeUtil.getDimensionality(type));
|
||||
} else {
|
||||
return HintProviders.forHere(valueHints(typeUtil));
|
||||
}
|
||||
} else {
|
||||
return HintProviders.forAllValueContexts(valueHints(typeUtil));
|
||||
}
|
||||
} else {
|
||||
return HintProviders.forHere(valueHints(typeUtil));
|
||||
}
|
||||
}
|
||||
|
||||
private HintProvider keyHints(TypeUtil typeUtil) {
|
||||
return HintProviders.basic(typeUtil.getJavaProject(), keyHints, keyProvider);
|
||||
}
|
||||
|
||||
private HintProvider valueHints(TypeUtil typeUtil) {
|
||||
return HintProviders.basic(typeUtil.getJavaProject(), valueHints, valueProvider);
|
||||
}
|
||||
|
||||
public List<PropertySource> getSources() {
|
||||
if (sources!=null) {
|
||||
return sources;
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PropertyInfo("+getId()+")";
|
||||
}
|
||||
public void addSource(ConfigurationMetadataSource source) {
|
||||
if (sources==null) {
|
||||
sources = new ArrayList<PropertySource>();
|
||||
}
|
||||
sources.add(new PropertySource(source));
|
||||
}
|
||||
|
||||
public PropertyInfo withId(String alias) {
|
||||
if (alias.equals(id)) {
|
||||
return this;
|
||||
}
|
||||
return new PropertyInfo(alias, type, name, defaultValue, description, deprecation, valueHints, keyHints, valueProvider, keyProvider, sources);
|
||||
}
|
||||
|
||||
public void setDeprecation(Deprecation d) {
|
||||
this.deprecation = d;
|
||||
}
|
||||
|
||||
public boolean isDeprecated() {
|
||||
return deprecation!=null;
|
||||
}
|
||||
|
||||
public String getDeprecationReason() {
|
||||
return deprecation == null ? null : deprecation.getReason();
|
||||
}
|
||||
|
||||
public String getDeprecationReplacement() {
|
||||
return deprecation == null ? null : deprecation.getReplacement();
|
||||
}
|
||||
|
||||
public void addValueHints(List<ValueHint> hints) {
|
||||
Builder<ValueHint> builder = ImmutableList.builder();
|
||||
builder.addAll(valueHints);
|
||||
builder.addAll(hints);
|
||||
valueHints = builder.build();
|
||||
}
|
||||
public void addKeyHints(List<ValueHint> hints) {
|
||||
Builder<ValueHint> builder = ImmutableList.builder();
|
||||
builder.addAll(keyHints);
|
||||
builder.addAll(hints);
|
||||
keyHints = builder.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*******************************************************************************
|
||||
* 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
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
|
||||
package org.springframework.ide.vscode.boot.metadata;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.ide.vscode.boot.metadata.ValueProviderRegistry.ValueProviderStrategy;
|
||||
import org.springframework.ide.vscode.boot.metadata.hints.StsValueHint;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
/**
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class ResourceHintProvider implements ValueProviderStrategy {
|
||||
|
||||
private static String[] CLASSPATH_PREFIXES = {
|
||||
"classpath:",
|
||||
"classpath*:"
|
||||
};
|
||||
|
||||
private static final String[] URL_PREFIXES = new String[] {
|
||||
"classpath:",
|
||||
"classpath*:",
|
||||
"file:",
|
||||
"http://",
|
||||
"https://"
|
||||
};
|
||||
|
||||
@Override
|
||||
public Flux<StsValueHint> getValues(IJavaProject javaProject, String query) {
|
||||
for (String prefix : CLASSPATH_PREFIXES) {
|
||||
if (query.startsWith(prefix)) {
|
||||
return classpathHints
|
||||
.getValues(javaProject, query.substring(prefix.length()))
|
||||
.map((hint) -> hint.prefixWith(prefix));
|
||||
}
|
||||
}
|
||||
return Flux.fromIterable(urlPrefixHints);
|
||||
}
|
||||
|
||||
final private ImmutableList<StsValueHint> urlPrefixHints = ImmutableList.copyOf(
|
||||
Arrays.stream(URL_PREFIXES)
|
||||
.map(StsValueHint::create)
|
||||
.collect(Collectors.toList())
|
||||
);
|
||||
|
||||
private ClasspathHints classpathHints = new ClasspathHints();
|
||||
|
||||
private static class ClasspathHints extends CachingValueProvider {
|
||||
@Override
|
||||
protected Flux<StsValueHint> getValuesAsync(IJavaProject javaProject, String query) {
|
||||
return Flux.fromStream(javaProject.getClasspath().getClasspathResources().stream().distinct().map(r -> r.replaceAll("\\\\", "/")).map(StsValueHint::create));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2014, 2017 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.metadata;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import org.springframework.ide.vscode.boot.metadata.util.Listener;
|
||||
import org.springframework.ide.vscode.boot.metadata.util.ListenerManager;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.ProgressService;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver;
|
||||
import org.springframework.ide.vscode.commons.util.Log;
|
||||
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
|
||||
/**
|
||||
* Support for Reconciling, Content Assist and Hover Text in spring properties
|
||||
* file all make use of a per-project index of spring properties metadata extracted
|
||||
* from project's classpath. This Index manager is responsible for keeping at most
|
||||
* one index per-project and to keep the index up-to-date.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class SpringPropertiesIndexManager extends ListenerManager<Listener<SpringPropertiesIndexManager>> {
|
||||
|
||||
private Cache<IJavaProject, SpringPropertyIndex> indexes;
|
||||
private final ValueProviderRegistry valueProviders;
|
||||
private static int progressIdCt = 0;
|
||||
|
||||
public SpringPropertiesIndexManager(ValueProviderRegistry valueProviders, ProjectObserver projectObserver) {
|
||||
this.valueProviders = valueProviders;
|
||||
this.indexes = CacheBuilder.newBuilder().build();
|
||||
if (projectObserver != null) {
|
||||
projectObserver.addListener(new ProjectObserver.Listener() {
|
||||
|
||||
@Override
|
||||
public void created(IJavaProject project) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changed(IJavaProject project) {
|
||||
indexes.invalidate(project);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleted(IJavaProject project) {
|
||||
indexes.invalidate(project);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public synchronized SpringPropertyIndex get(IJavaProject project, ProgressService progressService) {
|
||||
try {
|
||||
return indexes.get(project, () -> initIndex(project, progressService));
|
||||
} catch (ExecutionException e) {
|
||||
Log.log(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private SpringPropertyIndex initIndex(IJavaProject project, ProgressService progressService) {
|
||||
Log.info("Indexing Spring Boot Properties for "+project.getElementName());
|
||||
|
||||
String progressId = getProgressId();
|
||||
if (progressService != null) {
|
||||
progressService.progressEvent(progressId, "Indexing Spring Boot Properties...");
|
||||
}
|
||||
|
||||
SpringPropertyIndex index = new SpringPropertyIndex(valueProviders, project.getClasspath());
|
||||
|
||||
if (progressService != null) {
|
||||
progressService.progressEvent(progressId, null);
|
||||
}
|
||||
|
||||
Log.info("Indexing Spring Boot Properties for "+project.getElementName()+" DONE");
|
||||
Log.info("Indexed "+index.size()+" properties.");
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
public synchronized void clear() {
|
||||
if (indexes!=null) {
|
||||
indexes.invalidateAll();
|
||||
for (Listener<SpringPropertiesIndexManager> l : getListeners()) {
|
||||
l.changed(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static synchronized String getProgressId() {
|
||||
return DefaultSpringPropertyIndexProvider.class.getName()+ (progressIdCt++);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015 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.metadata;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.ide.vscode.boot.configurationmetadata.ConfigurationMetadataGroup;
|
||||
import org.springframework.ide.vscode.boot.configurationmetadata.ConfigurationMetadataProperty;
|
||||
import org.springframework.ide.vscode.boot.configurationmetadata.ConfigurationMetadataRepository;
|
||||
import org.springframework.ide.vscode.boot.configurationmetadata.ConfigurationMetadataSource;
|
||||
import org.springframework.ide.vscode.commons.java.IClasspath;
|
||||
import org.springframework.ide.vscode.commons.util.FuzzyMap;
|
||||
import org.springframework.ide.vscode.commons.util.StringUtil;
|
||||
|
||||
public class SpringPropertyIndex extends FuzzyMap<PropertyInfo> {
|
||||
|
||||
private ValueProviderRegistry valueProviders;
|
||||
|
||||
public SpringPropertyIndex(ValueProviderRegistry valueProviders, IClasspath projectPath) {
|
||||
this.valueProviders = valueProviders;
|
||||
if (projectPath!=null) {
|
||||
// try {
|
||||
PropertiesLoader loader = new PropertiesLoader();
|
||||
ConfigurationMetadataRepository metadata = loader.load(projectPath);
|
||||
//^^^ Should be done in bg? It seems fast enough for now.
|
||||
|
||||
Collection<ConfigurationMetadataProperty> allEntries = metadata.getAllProperties().values();
|
||||
for (ConfigurationMetadataProperty item : allEntries) {
|
||||
add(new PropertyInfo(valueProviders, item));
|
||||
}
|
||||
|
||||
for (ConfigurationMetadataGroup group : metadata.getAllGroups().values()) {
|
||||
for (ConfigurationMetadataSource source : group.getSources().values()) {
|
||||
for (ConfigurationMetadataProperty prop : source.getProperties().values()) {
|
||||
PropertyInfo info = get(prop.getId());
|
||||
info.addSource(source);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// System.out.println(">>> spring properties metadata loaded "+this.size()+" items===");
|
||||
// dumpAsTestData();
|
||||
// System.out.println(">>> spring properties metadata loaded "+this.size()+" items===");
|
||||
// } catch (Exception e) {
|
||||
// LOG.log
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
public void add(ConfigurationMetadataProperty propertyInfo) {
|
||||
add(new PropertyInfo(valueProviders, propertyInfo));
|
||||
}
|
||||
|
||||
/**
|
||||
* Dumps out 'test data' based on the current contents of the index. This is not meant to be
|
||||
* used in 'production' code. The idea is to call this method during development to dump a
|
||||
* 'snapshot' of the index onto System.out. The data is printed in a forma so that it can be easily
|
||||
* pasted/used into JUNit testing code.
|
||||
*/
|
||||
public void dumpAsTestData() {
|
||||
List<Match<PropertyInfo>> allData = this.find("");
|
||||
for (Match<PropertyInfo> match : allData) {
|
||||
PropertyInfo d = match.data;
|
||||
System.out.println("data("
|
||||
+dumpString(d.getId())+", "
|
||||
+dumpString(d.getType())+", "
|
||||
+dumpString(d.getDefaultValue())+", "
|
||||
+dumpString(d.getDescription()) +");"
|
||||
);
|
||||
// for (PropertySource source : d.getSources()) {
|
||||
// String st = source.getSourceType();
|
||||
// String sm = source.getSourceMethod();
|
||||
// if (sm!=null) {
|
||||
// System.out.println(d.getId() +" from: "+st+"::"+sm);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
private String dumpString(Object v) {
|
||||
if (v==null) {
|
||||
return "null";
|
||||
}
|
||||
return dumpString(""+v);
|
||||
}
|
||||
|
||||
private String dumpString(String s) {
|
||||
if (s==null) {
|
||||
return "null";
|
||||
} else {
|
||||
StringBuilder buf = new StringBuilder("\"");
|
||||
for (char c : s.toCharArray()) {
|
||||
switch (c) {
|
||||
case '\r':
|
||||
buf.append("\\r");
|
||||
break;
|
||||
case '\n':
|
||||
buf.append("\\n");
|
||||
break;
|
||||
case '\\':
|
||||
buf.append("\\\\");
|
||||
break;
|
||||
case '\"':
|
||||
buf.append("\\\"");
|
||||
break;
|
||||
default:
|
||||
buf.append(c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
buf.append("\"");
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getKey(PropertyInfo entry) {
|
||||
return entry.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the longest known property that is a prefix of the given name. Here prefix does not mean
|
||||
* 'string prefix' but a prefix in the sense of treating '.' as a kind of separators. So
|
||||
* 'prefix' is not allowed to end in the middle of a 'segment'.
|
||||
*/
|
||||
public static PropertyInfo findLongestValidProperty(FuzzyMap<PropertyInfo> index, String name) {
|
||||
int bracketPos = name.indexOf('[');
|
||||
int endPos = bracketPos>=0?bracketPos:name.length();
|
||||
PropertyInfo prop = null;
|
||||
String prefix = null;
|
||||
while (endPos>0 && prop==null) {
|
||||
prefix = name.substring(0, endPos);
|
||||
String canonicalPrefix = StringUtil.camelCaseToHyphens(prefix);
|
||||
prop = index.get(canonicalPrefix);
|
||||
if (prop==null) {
|
||||
endPos = name.lastIndexOf('.', endPos-1);
|
||||
}
|
||||
}
|
||||
if (prop!=null) {
|
||||
//We should meet caller's expectation that matched properties returned by this method
|
||||
// match the names exactly even if we found them using relaxed name matching.
|
||||
return prop.withId(prefix);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user