Remove commons-boot

This commit is contained in:
Kris De Volder
2018-02-26 12:14:26 -08:00
parent e35350fe46
commit 27859bf670
431 changed files with 68 additions and 270 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 + '\'' + '}';
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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
+ '\'' + '}';
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,148 @@
/*******************************************************************************
* Copyright (c) 2018 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.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;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,253 @@
/*******************************************************************************
* Copyright (c) 2018 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.links;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;
import java.util.Stack;
import org.apache.commons.io.IOUtils;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.AnnotationTypeDeclaration;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.boot.java.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]);
}
}

View File

@@ -0,0 +1,62 @@
/*******************************************************************************
* Copyright (c) 2018 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.links;
import java.nio.file.Path;
import java.util.Optional;
import org.springframework.ide.vscode.boot.java.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;
}
}
}

View File

@@ -0,0 +1,55 @@
/*******************************************************************************
* Copyright (c) 2018 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.links;
import java.nio.file.Path;
import java.util.Optional;
import org.springframework.ide.vscode.commons.java.IJavaProject;
/**
* Instance is able to provide client specific URL links to navigate to a
* specific file on the client given various types of data such as fully
* qualified java type name or classpath resource or just some file resource
* given by its path.
*
* @author Alex Boyko
*
*/
public interface SourceLinks {
static final String JAR = ".jar";
static final String CLASS = ".class";
/**
* Creates link to source file defining the type passed with it's fully qualified name
* @param project Java project in the context of which source file link is calculated
* @param fqName type's fully qualified name
* @return the link URL optional
*/
Optional<String> sourceLinkUrlForFQName(IJavaProject project, String fqName);
/**
* Creates link to source file corresponding to a classpath resource
* @param project project Java project in the context of which source file link is calculated
* @param path the path to the classpath resource
* @return the link URL optional
*/
Optional<String> sourceLinkUrlForClasspathResource(IJavaProject project, String path);
/**
* Creates link to a file specified by it's path
* @param path the resource path
* @return the link URL optional
*/
Optional<String> sourceLinkForResourcePath(Path path);
}

View File

@@ -0,0 +1,103 @@
/*******************************************************************************
* Copyright (c) 2018 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.links;
import java.io.File;
import java.net.URLEncoder;
import java.nio.file.Path;
import java.util.Optional;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.boot.java.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();
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,56 @@
/*******************************************************************************
* Copyright (c) 2018 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.boot.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;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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