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

Conflicts:
	headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/StringUtil.java
This commit is contained in:
Kris De Volder
2017-11-16 15:03:53 -08:00
42 changed files with 1097 additions and 259 deletions

View File

@@ -0,0 +1,40 @@
# WARNING this script doesn't work, although it seems like it should.
# The equivalent of this script works on windows imagemagick.
# See makeWinIcon.bat
# Althoug it doesn't work committing this file to git anyway
# for future reference. Maybe it can be fixed.
#Shell script to convert a image into win icon format. The script uses
# unix commandline tool 'imagemagick' and the 'specs' for the images to
# insert into the .ico file are as follows (according to Martin Lippert)
#
# - the 16x, 32x, and 48x needs to be in the file twice - with 32bit color and with 8bit color
# - the 256x icon has to be 32bit only, but has to be uncompressed
# code from here: https://github.com/neo4j-contrib/neoclipse/pull/56
# This command can be used to check whether the contents of the .ico file
# looks ok:
#
# identify -format '%f %p/%n %m %C/%Q %r %G %A %z\n' sts.ico
convert sts256.png -compress none \
\( -clone 0 -resize 16x16 -compress none \) \
\( -clone 0 -resize 24x24 -compress none \) \
\( -clone 0 -resize 32x32 -compress none \) \
\( -clone 0 -resize 48x48 -compress none \) \
\( -clone 0 -resize 16x16 -colors 256 -compress none \) \
\( -clone 0 -resize 24x24 -colors 256 -compress none \) \
\( -clone 0 -resize 32x32 -colors 256 -compress none \) \
\( -clone 0 -resize 48x48 -colors 256 -compress none \) \
\( -clone 0 -resize 256x256 -compress none \) \
-delete 0 sts.ico
convert sts512.png -compress none \
\( -clone 0 -resize 256x256 -compress none \) \
-delete 0 sts.ico
convert sts256.png -compress none sts.ico
identify -format '%f %p/%n %m %C/%Q %r %G %A %z\n' sts.ico

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 286 KiB

View File

@@ -237,6 +237,8 @@ public class GotoSymbolDialog extends PopupDialog {
// }
// });
pattern.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
pattern.setMessage(model.getSearchBoxHintMessage());
SwtConnect.connect(pattern, model.getSearchBox());
TreeViewer viewer = new TreeViewer(dialogArea, SWT.SINGLE);

View File

@@ -27,6 +27,7 @@ import com.google.common.collect.ImmutableSet;
public class GotoSymbolDialogModel {
private static final String SEARCH_BOX_HINT_MESSAGE = "@/ -> request mappings, @+ -> beans, @> -> functions, @ -> all spring elements";
private static final boolean DEBUG = false;//(""+Platform.getLocation()).contains("kdvolder");
private static void debug(String string) {
if (DEBUG) {
@@ -97,6 +98,10 @@ public class GotoSymbolDialogModel {
dependsOn(unfilteredSymbols);
}
private boolean containsCharactersCaseInsensitive(String symbol, String query) {
return containsCharacters(symbol.toLowerCase().toCharArray(), query.toLowerCase().toCharArray());
}
private boolean containsCharacters(char[] symbolChars, char[] queryChars) {
int symbolindex = 0;
int queryindex = 0;
@@ -113,9 +118,8 @@ public class GotoSymbolDialogModel {
@Override
protected ImmutableSet<SymbolInformation> compute() {
char[] query = searchBox.getValue().toCharArray();
ImmutableSet.Builder<SymbolInformation> builder = ImmutableSet.builder();
unfilteredSymbols.getValues().stream().filter(sym -> containsCharacters(sym.getName().toCharArray(), query)).forEach(builder::add);
unfilteredSymbols.getValues().stream().filter(sym -> containsCharactersCaseInsensitive(sym.getName(), searchBox.getValue())).forEach(builder::add);
return builder.build();
}
};
@@ -149,6 +153,10 @@ public class GotoSymbolDialogModel {
public LiveVariable<String> getSearchBox() {
return searchBox;
}
public String getSearchBoxHintMessage() {
return SEARCH_BOX_HINT_MESSAGE;
}
public LiveExpression<String> getStatus() {
return status;

View File

@@ -10,6 +10,7 @@
*******************************************************************************/
package org.springframework.ide.vscode.boot.java;
import java.nio.file.Paths;
import java.time.Duration;
import java.util.Arrays;
@@ -21,6 +22,7 @@ import org.springframework.ide.vscode.boot.metadata.SpringPropertyIndexProvider;
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.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;
@@ -60,10 +62,10 @@ public class BootJavaLanguageServerParams {
// Initialize project finders, project caches and project observers
FileObserver fileObserver = server.getWorkspaceService().getFileObserver();
CompositeJavaProjectFinder javaProjectFinder = new CompositeJavaProjectFinder();
MavenProjectCache mavenProjectCache = new MavenProjectCache(fileObserver, MavenCore.getDefault());
MavenProjectCache mavenProjectCache = new MavenProjectCache(fileObserver, MavenCore.getDefault(), true, Paths.get(IJavaProject.PROJECT_CACHE_FOLDER));
javaProjectFinder.addJavaProjectFinder(new MavenProjectFinder(mavenProjectCache));
GradleProjectCache gradleProjectCache = new GradleProjectCache(fileObserver, GradleCore.getDefault());
GradleProjectCache gradleProjectCache = new GradleProjectCache(fileObserver, GradleCore.getDefault(), true, Paths.get(IJavaProject.PROJECT_CACHE_FOLDER));
javaProjectFinder.addJavaProjectFinder(new GradleProjectFinder(gradleProjectCache));
CompositeProjectOvserver projectObserver = new CompositeProjectOvserver(Arrays.asList(mavenProjectCache, gradleProjectCache));
@@ -78,5 +80,29 @@ public class BootJavaLanguageServerParams {
};
}
public static LSFactory<BootJavaLanguageServerParams> createTestDefault() {
return (SimpleLanguageServer server) -> {
// Initialize project finders, project caches and project observers
FileObserver fileObserver = server.getWorkspaceService().getFileObserver();
CompositeJavaProjectFinder javaProjectFinder = new CompositeJavaProjectFinder();
MavenProjectCache mavenProjectCache = new MavenProjectCache(fileObserver, MavenCore.getDefault(), false, null);
mavenProjectCache.setAlwaysFireEventOnFileChanged(true);
javaProjectFinder.addJavaProjectFinder(new MavenProjectFinder(mavenProjectCache));
GradleProjectCache gradleProjectCache = new GradleProjectCache(fileObserver, GradleCore.getDefault(), false, null);
gradleProjectCache.setAlwaysFireEventOnFileChanged(true);
javaProjectFinder.addJavaProjectFinder(new GradleProjectFinder(gradleProjectCache));
CompositeProjectOvserver projectObserver = new CompositeProjectOvserver(Arrays.asList(mavenProjectCache, gradleProjectCache));
return new BootJavaLanguageServerParams(
javaProjectFinder.filter(BootProjectUtil::isBootProject),
projectObserver,
new DefaultSpringPropertyIndexProvider(javaProjectFinder, projectObserver),
RunningAppProvider.DEFAULT,
SpringLiveHoverWatchdog.DEFAULT_INTERVAL
);
};
}
}

View File

@@ -140,18 +140,18 @@ public class ConditionalsLiveHoverProvider implements HoverProvider {
// Check that Java type in annotation in editor matches Java information in the live Conditional
ASTNode parent = annotation.getParent();
String rawJsonKey = liveConditional.getPositiveMatchKey();
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 rawJsonKey.contains(annotationDeclaringClassName) && rawJsonKey.contains(annotationMethodName);
return typeInfo.contains(annotationDeclaringClassName) && typeInfo.contains(annotationMethodName);
} else if (parent instanceof TypeDeclaration) {
TypeDeclaration typeDec = (TypeDeclaration) parent;
String annotationDeclaringClassName = typeDec.resolveBinding().getName();
return rawJsonKey.contains(annotationDeclaringClassName);
return typeInfo.contains(annotationDeclaringClassName);
}
return false;
}

View File

@@ -110,7 +110,7 @@ public class BootJavaCompletionEngine implements ICompletionEngine {
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<Path> classpathEntries = classpath.getClasspathEntries().stream();
return classpathEntries
.filter(path -> path.toFile().exists())
.map(path -> path.toAbsolutePath().toString()).toArray(String[]::new);

View File

@@ -213,7 +213,7 @@ public class BootJavaHoverProvider implements HoverHandler {
try {
IClasspath classpath = project.getClasspath();
if (classpath!=null) {
return classpath.getClasspathEntries().anyMatch(cpe -> {
return classpath.getClasspathEntries().stream().anyMatch(cpe -> {
String name = cpe.getFileName().toString();
return name.startsWith("spring-boot-actuator-");
});

View File

@@ -126,7 +126,7 @@ public class BootJavaReferencesHandler implements ReferencesHandler {
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<Path> classpathEntries = classpath.getClasspathEntries().stream();
return classpathEntries
.filter(path -> path.toFile().exists())
.map(path -> path.toAbsolutePath().toString()).toArray(String[]::new);

View File

@@ -22,7 +22,7 @@ public class BootProjectUtil {
try {
IClasspath cp = jp.getClasspath();
if (cp!=null) {
return cp.getClasspathEntries().anyMatch(cpe -> isBootEntry(cpe));
return cp.getClasspathEntries().stream().anyMatch(cpe -> isBootEntry(cpe));
}
} catch (Exception e) {
Log.log(e);

View File

@@ -133,7 +133,7 @@ public final class CompilationUnitCache {
return new String[0];
} else {
IClasspath classpath = project.getClasspath();
Stream<Path> classpathEntries = classpath.getClasspathEntries();
Stream<Path> classpathEntries = classpath.getClasspathEntries().stream();
return classpathEntries
.filter(path -> path.toFile().exists())
.map(path -> path.toAbsolutePath().toString()).toArray(String[]::new);

View File

@@ -55,6 +55,7 @@ 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.Listener;
import org.springframework.ide.vscode.commons.util.Log;
import org.springframework.ide.vscode.commons.util.StringUtil;
import org.springframework.ide.vscode.commons.util.text.LanguageId;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
@@ -242,7 +243,7 @@ public class SpringIndexer {
try {
initializeTask.get();
return allsymbols.stream()
.filter(symbol -> containsCharacters(symbol.getName().toCharArray(), query.toCharArray()))
.filter(symbol -> StringUtil.containsCharactersCaseInsensitive(symbol.getName(), query))
.collect(Collectors.toList());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
@@ -251,20 +252,6 @@ public class SpringIndexer {
return null;
}
private boolean containsCharacters(char[] symbolChars, char[] queryChars) {
int symbolindex = 0;
int queryindex = 0;
while (queryindex < queryChars.length && symbolindex < symbolChars.length) {
if (symbolChars[symbolindex] == queryChars[queryindex]) {
queryindex++;
}
symbolindex++;
}
return queryindex == queryChars.length;
}
private void scanFiles(File directory) {
try {
Map<Optional<IJavaProject>, List<String>> projects = Files.walk(directory.toPath())
@@ -456,7 +443,7 @@ public class SpringIndexer {
private String[] getClasspathEntries(IJavaProject project) throws Exception {
IClasspath classpath = project.getClasspath();
Stream<Path> classpathEntries = classpath.getClasspathEntries();
Stream<Path> classpathEntries = classpath.getClasspathEntries().stream();
return classpathEntries
.filter(path -> path.toFile().exists())
.map(path -> path.toAbsolutePath().toString()).toArray(String[]::new);

View File

@@ -69,7 +69,7 @@ public class ConditionalsLiveHoverTest {
// Build a mock running boot app
mockAppProvider.builder().isSpringBootApp(true).port("1111").processId("22022").host("cfapps.io")
.processName("test-conditionals-live-hover")
.positiveMatchesJsonForLiveConditionals(
.liveConditionalsJson(
"{\"positiveMatches\":{\"ConditionalOnBeanConfig#hi\":[{\"condition\":\"OnBeanCondition\",\"message\":\"@ConditionalOnBean (types: example.Hello; SearchStrategy: all) found bean 'missing'\"}]}}")
.build();
@@ -93,7 +93,7 @@ public class ConditionalsLiveHoverTest {
// Build a mock running boot app
mockAppProvider.builder().isSpringBootApp(true).port("1111").processId("22022").host("cfapps.io")
.processName("test-conditionals-live-hover")
.positiveMatchesJsonForLiveConditionals(
.liveConditionalsJson(
"{\"positiveMatches\":{\"ConditionalOnMissingBeanConfig#missing\":[{\"condition\":\"OnBeanCondition\",\"message\":\"@ConditionalOnMissingBean (types: example.Hello; SearchStrategy: all) did not find any beans\"}]}}")
.build();
@@ -116,7 +116,7 @@ public class ConditionalsLiveHoverTest {
// Build a mock running boot app
mockAppProvider.builder().isSpringBootApp(true).port("1111").processId("22022").host("cfapps.io")
.processName("test-conditionals-live-hover")
.positiveMatchesJsonForLiveConditionals(
.liveConditionalsJson(
"{\"positiveMatches\":{\"HelloConfig#missing\":[{\"condition\":\"OnBeanCondition\",\"message\":\"@ConditionalOnMissingBean (types: example.Hello; SearchStrategy: all) did not find any beans\"}],\"HelloConfig2#hi\":[{\"condition\":\"OnBeanCondition\",\"message\":\"@ConditionalOnBean (types: example.Hello; SearchStrategy: all) found bean 'missing'\"}],\"MultipleConditionals#hi\":[{\"condition\":\"OnClassCondition\",\"message\":\"@ConditionalOnClass found required class; @ConditionalOnMissingClass did not find unwanted class\"},{\"condition\":\"OnWebApplicationCondition\",\"message\":\"@ConditionalOnWebApplication (required) found StandardServletEnvironment\"},{\"condition\":\"OnJavaCondition\",\"message\":\"@ConditionalOnJava (1.8 or newer) found 1.8\"},{\"condition\":\"OnExpressionCondition\",\"message\":\"@ConditionalOnExpression (#{true}) resulted in true\"},{\"condition\":\"OnBeanCondition\",\"message\":\"@ConditionalOnBean (types: example.Hello; SearchStrategy: all) found beans 'hi', 'missing'\"}]}}")
.build();
@@ -158,19 +158,19 @@ public class ConditionalsLiveHoverTest {
// Build a mock running boot app
mockAppProvider.builder().isSpringBootApp(true).port("1000").processId("70000").host("cfapps.io")
.processName("test-conditionals-live-hover")
.positiveMatchesJsonForLiveConditionals(
.liveConditionalsJson(
"{\"positiveMatches\":{\"ConditionalOnMissingBeanConfig#missing\":[{\"condition\":\"OnBeanCondition\",\"message\":\"@ConditionalOnMissingBean (types: example.Hello; SearchStrategy: all) did not find any beans\"}]}}")
.build();
mockAppProvider.builder().isSpringBootApp(true).port("1001").processId("80000").host("cfapps.io")
.processName("test-conditionals-live-hover")
.positiveMatchesJsonForLiveConditionals(
.liveConditionalsJson(
"{\"positiveMatches\":{\"ConditionalOnMissingBeanConfig#missing\":[{\"condition\":\"OnBeanCondition\",\"message\":\"@ConditionalOnMissingBean (types: example.Hello; SearchStrategy: all) did not find any beans\"}]}}")
.build();
mockAppProvider.builder().isSpringBootApp(true).port("1002").processId("90000").host("cfapps.io")
.processName("test-conditionals-live-hover")
.positiveMatchesJsonForLiveConditionals(
.liveConditionalsJson(
"{\"positiveMatches\":{\"ConditionalOnMissingBeanConfig#missing\":[{\"condition\":\"OnBeanCondition\",\"message\":\"@ConditionalOnMissingBean (types: example.Hello; SearchStrategy: all) did not find any beans\"}]}}")
.build();
@@ -218,7 +218,7 @@ public class ConditionalsLiveHoverTest {
// Build a mock running boot app
mockAppProvider.builder().isSpringBootApp(true).port("1000").processId("70000").host("cfapps.io")
.processName("test-conditionals-live-hover")
.positiveMatchesJsonForLiveConditionals(
.liveConditionalsJson(
"{\"positiveMatches\":{\"HelloConfig#missing\":[{\"condition\":\"OnBeanCondition\",\"message\":\"@ConditionalOnMissingBean (types: example.Hello; SearchStrategy: all) did not find any beans\"}],\"HelloConfig2#hi\":[{\"condition\":\"OnBeanCondition\",\"message\":\"@ConditionalOnBean (types: example.Hello; SearchStrategy: all) found bean 'missing'\"}],\"MultipleConditionals#hi\":[{\"condition\":\"OnClassCondition\",\"message\":\"@ConditionalOnClass found required class; @ConditionalOnMissingClass did not find unwanted class\"},{\"condition\":\"OnWebApplicationCondition\",\"message\":\"@ConditionalOnWebApplication (required) found StandardServletEnvironment\"},{\"condition\":\"OnJavaCondition\",\"message\":\"@ConditionalOnJava (1.8 or newer) found 1.8\"},{\"condition\":\"OnExpressionCondition\",\"message\":\"@ConditionalOnExpression (#{true}) resulted in true\"},{\"condition\":\"OnBeanCondition\",\"message\":\"@ConditionalOnBean (types: example.Hello; SearchStrategy: all) found beans 'hi', 'missing'\"}]}}")
.build();
@@ -264,7 +264,7 @@ public class ConditionalsLiveHoverTest {
// Build a mock running boot app
mockAppProvider.builder().isSpringBootApp(true).port("1000").processId("70000").host("cfapps.io")
.processName("test-conditionals-live-hover")
.positiveMatchesJsonForLiveConditionals(
.liveConditionalsJson(
"{\"positiveMatches\":{\"MultipleConditionalsPT152535713#hi\":[{\"condition\":\"OnWebApplicationCondition\",\"message\":\"@ConditionalOnWebApplication (required) found StandardServletEnvironment\"},{\"condition\":\"OnJavaCondition\",\"message\":\"@ConditionalOnJava (1.8 or newer) found 1.8\"}]}}")
.build();
@@ -290,7 +290,7 @@ public class ConditionalsLiveHoverTest {
}
@Test
public void testHighlights() throws Exception {
public void testHighlightsMethodConditionals() throws Exception {
File directory = new File(
ProjectsHarness.class.getResource("/test-projects/test-conditionals-live-hover/").toURI());
@@ -299,18 +299,131 @@ public class ConditionalsLiveHoverTest {
// Build a mock running boot app
mockAppProvider.builder().isSpringBootApp(true).port("1111").processId("22022").host("cfapps.io")
.processName("test-conditionals-live-hover")
.positiveMatchesJsonForLiveConditionals(
.liveConditionalsJson(
"{\"positiveMatches\":{\"HelloConfig#missing\":[{\"condition\":\"OnBeanCondition\",\"message\":\"@ConditionalOnMissingBean (types: example.Hello; SearchStrategy: all) did not find any beans\"}],\"HelloConfig2#hi\":[{\"condition\":\"OnBeanCondition\",\"message\":\"@ConditionalOnBean (types: example.Hello; SearchStrategy: all) found bean 'missing'\"}],\"MultipleConditionals#hi\":[{\"condition\":\"OnClassCondition\",\"message\":\"@ConditionalOnClass found required class; @ConditionalOnMissingClass did not find unwanted class\"},{\"condition\":\"OnWebApplicationCondition\",\"message\":\"@ConditionalOnWebApplication (required) found StandardServletEnvironment\"},{\"condition\":\"OnJavaCondition\",\"message\":\"@ConditionalOnJava (1.8 or newer) found 1.8\"},{\"condition\":\"OnExpressionCondition\",\"message\":\"@ConditionalOnExpression (#{true}) resulted in true\"},{\"condition\":\"OnBeanCondition\",\"message\":\"@ConditionalOnBean (types: example.Hello; SearchStrategy: all) found beans 'hi', 'missing'\"}]}}")
.build();
harness.intialize(directory);
String content = "package example;\n" +
"\n" +
"import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;\n" +
"import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;\n" +
"import org.springframework.boot.autoconfigure.condition.ConditionalOnJava;\n" +
"import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;\n" +
"import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;\n" +
"import org.springframework.boot.autoconfigure.condition.ConditionalOnNotWebApplication;\n" +
"import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;\n" +
"import org.springframework.context.annotation.Bean;\n" +
"import org.springframework.context.annotation.Configuration;\n" +
"\n" +
"@Configuration\n" +
"public class MultipleConditionals {\n" +
"\n" +
" @Bean\n" +
" @ConditionalOnBean\n" +
" @ConditionalOnWebApplication\n" +
" @ConditionalOnJava(value=ConditionalOnJava.JavaVersion.EIGHT)\n" +
" @ConditionalOnMissingClass\n" +
" @ConditionalOnExpression\n" +
" public Hello hi() {\n" +
" return null;\n" +
" }\n" +
"}";
Editor editor = harness.newEditorFromFileUri(docUri, LanguageId.JAVA);
Editor editor = harness.newEditor( LanguageId.JAVA, content, docUri);
editor.assertHighlights("@ConditionalOnBean", "@ConditionalOnWebApplication",
"@ConditionalOnJava(value=ConditionalOnJava.JavaVersion.EIGHT)", "@ConditionalOnMissingClass",
"@ConditionalOnExpression");
}
@Test
public void testHighlightsTypeConditionals() throws Exception {
File directory = new File(
ProjectsHarness.class.getResource("/test-projects/test-conditionals-live-hover/").toURI());
String docUri = "file://" + directory.getAbsolutePath() + "/src/main/java/example/MultipleConditionals.java";
// Build a mock running boot app
mockAppProvider.builder().isSpringBootApp(true).port("1111").processId("22022").host("cfapps.io")
.processName("test-conditionals-live-hover")
.liveConditionalsJson(
"{\"negativeMatches\": {\n" +
" \"MyConditionalComponent\": {\n" +
" \"notMatched\": [\n" +
" {\n" +
" \"condition\": \"OnClassCondition\",\n" +
" \"message\": \"@ConditionalOnClass did not find required class 'java.lang.String2'\"\n" +
" }\n" +
" ],\n" +
" \"matched\": []\n" +
" }\n"
+ "}\n"
+ "}")
.build();
harness.intialize(directory);
String content = "package com.example;\n" +
"\n" +
"import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;\n" +
"import org.springframework.stereotype.Component;\n" +
"\n" +
"@Component\n" +
"@ConditionalOnClass(name=\"java.lang.String2\")\n" +
"public class MyConditionalComponent {\n" +
"}";
Editor editor = harness.newEditor( LanguageId.JAVA, content, docUri);
editor.assertHighlights("@ConditionalOnClass(name=\"java.lang.String2\")");
}
@Test
public void testNegativeMatches() throws Exception {
File directory = new File(
ProjectsHarness.class.getResource("/test-projects/test-conditionals-live-hover/").toURI());
String docUri = "file://" + directory.getAbsolutePath() + "/src/main/java/example/MultipleConditionals.java";
// Build a mock running boot app
mockAppProvider.builder().isSpringBootApp(true).port("1111").processId("67950").host("cfapps.io")
.processName("test-conditionals-live-hover")
.liveConditionalsJson(
"{\"negativeMatches\": {\n" +
" \"MyConditionalComponent\": {\n" +
" \"notMatched\": [\n" +
" {\n" +
" \"condition\": \"OnClassCondition\",\n" +
" \"message\": \"@ConditionalOnClass did not find required class 'java.lang.String2'\"\n" +
" }\n" +
" ],\n" +
" \"matched\": []\n" +
" }\n"
+ "}\n"
+ "}")
.build();
harness.intialize(directory);
String content = "package com.example;\n" +
"\n" +
"import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;\n" +
"import org.springframework.stereotype.Component;\n" +
"\n" +
"@Component\n" +
"@ConditionalOnClass(name=\"java.lang.String2\")\n" +
"public class MyConditionalComponent {\n" +
"}";
Editor editor = harness.newEditor( LanguageId.JAVA, content, docUri);
editor.assertHoverContains("@ConditionalOnClass(name=\"java.lang.String2\")", "@ConditionalOnClass did not find required class 'java.lang.String2'\n" +
"\n" +
"Process [PID=67950, name=`test-conditionals-live-hover`]");
}
}

View File

@@ -43,7 +43,7 @@ public class BootLanguageServerHarness extends LanguageServerHarness<BootJavaLan
public static class Builder {
LSFactory<BootJavaLanguageServerParams> defaultsFactory = BootJavaLanguageServerParams.createDefault();
LSFactory<BootJavaLanguageServerParams> defaultsFactory = BootJavaLanguageServerParams.createTestDefault();
private JavaProjectFinder projectFinder = null;
private ProjectObserver projectObserver = null;
private SpringPropertyIndexProvider indexProvider = null;
@@ -92,7 +92,7 @@ public class BootLanguageServerHarness extends LanguageServerHarness<BootJavaLan
private BootLanguageServerHarness(Builder builder) throws Exception {
super(() -> {
LSFactory<BootJavaLanguageServerParams> params = (server) -> {
BootJavaLanguageServerParams defaults = BootJavaLanguageServerParams.createDefault().create(server);
BootJavaLanguageServerParams defaults = BootJavaLanguageServerParams.createTestDefault().create(server);
return new BootJavaLanguageServerParams(
builder.projectFinder==null?defaults.projectFinder:builder.projectFinder,
builder.projectObserver==null?defaults.projectObserver:builder.projectObserver,

View File

@@ -111,12 +111,7 @@ public class MockRunningAppProvider {
return this;
}
public MockAppBuilder autoConfigReport(String autoConfigReport) throws Exception {
when(app.getAutoConfigReport()).thenReturn(autoConfigReport);
return this;
}
public MockAppBuilder positiveMatchesJsonForLiveConditionals(String rawJson) throws Exception{
public MockAppBuilder liveConditionalsJson(String rawJson) throws Exception{
when(app.getLiveConditionals()).thenReturn(SpringBootApp.getLiveConditionals(rawJson, processId, processName));
return this;
}

View File

@@ -22,11 +22,4 @@ public class MultipleConditionals {
public Hello hi() {
return null;
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnNotWebApplication
public Hello missing() {
return null;
}
}

View File

@@ -10,6 +10,7 @@
*******************************************************************************/
package org.springframework.ide.vscode.boot;
import java.nio.file.Paths;
import java.util.Arrays;
import org.springframework.ide.vscode.boot.common.PropertyCompletionFactory;
@@ -25,6 +26,7 @@ import org.springframework.ide.vscode.boot.yaml.reconcile.ApplicationYamlReconci
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.IJavaProject;
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionEngine;
import org.springframework.ide.vscode.commons.languageserver.completion.VscodeCompletionEngineAdapter;
import org.springframework.ide.vscode.commons.languageserver.hover.HoverInfoProvider;
@@ -120,10 +122,10 @@ public class BootPropertiesLanguageServer extends SimpleLanguageServer {
documents.onHover(hoverEngine::getHover);
// Initialize project finders, project caches and project observers
MavenProjectCache mavenProjectCache = new MavenProjectCache(getWorkspaceService().getFileObserver(), MavenCore.getDefault());
MavenProjectCache mavenProjectCache = new MavenProjectCache(getWorkspaceService().getFileObserver(), MavenCore.getDefault(), true, Paths.get(IJavaProject.PROJECT_CACHE_FOLDER));
javaProjectFinder.addJavaProjectFinder(new MavenProjectFinder(mavenProjectCache));
GradleProjectCache gradleProjectCache = new GradleProjectCache(getWorkspaceService().getFileObserver(), GradleCore.getDefault());
GradleProjectCache gradleProjectCache = new GradleProjectCache(getWorkspaceService().getFileObserver(), GradleCore.getDefault(), true, Paths.get(IJavaProject.PROJECT_CACHE_FOLDER));
javaProjectFinder.addJavaProjectFinder(new GradleProjectFinder(gradleProjectCache));
projectObserver = new CompositeProjectOvserver(Arrays.asList(mavenProjectCache, gradleProjectCache));

View File

@@ -63,7 +63,7 @@ public class ResourceHintProvider implements ValueProviderStrategy {
private static class ClasspathHints extends CachingValueProvider {
@Override
protected Flux<StsValueHint> getValuesAsync(IJavaProject javaProject, String query) {
return Flux.fromStream(javaProject.getClasspath().getClasspathResources().distinct().map(StsValueHint::create));
return Flux.fromStream(javaProject.getClasspath().getClasspathResources().stream().distinct().map(StsValueHint::create));
}
}

View File

@@ -16,7 +16,7 @@ public class LiveConditional {
private String message;
private String processId;
private String processName;
private String positiveMatchKey;
private String typeInfo;
public LiveConditional() {
@@ -38,8 +38,8 @@ public class LiveConditional {
return processName;
}
public String getPositiveMatchKey() {
return positiveMatchKey;
public String getTypeInfo() {
return typeInfo;
}
public static class LiveConditionalBuilder {
@@ -67,13 +67,31 @@ public class LiveConditional {
}
/**
* This is a JSON key in "positiveMatches" element in the autoconfig report that contains information regarding
* the method that the conditional is applied to.
* @param positiveMatchKey
* Type information for which a conditional is applied to.
* <p/>
*
* Example:
* <p/>
* For this class:
* <p/>
* "@ConditionalOnClass(name="java.lang.String2")
* public class MyConditionalComponent {
* }"
* <p/>
* This is the "real" autoconfig JSON:
* <p/>
* "negativeMatches": { "MyConditionalComponent": { "notMatched": [ {
* "condition": "OnClassCondition", "message": "@ConditionalOnClass did not find
* required class 'java.lang.String2'" } ], "matched": [] }
* <p/>
* In this example, "MyConditionalComponent" information in the JSON indicates the type where the conditional is being applied to.
* <p/>
* Type info can also contain method information if a conditional annotation is applied to a method. Example: MyConditionalComponent#myBean)
* @param typeInfo
* @return
*/
public LiveConditionalBuilder positiveMatchKey(String positiveMatchKey) {
conditional.positiveMatchKey = positiveMatchKey;
public LiveConditionalBuilder typeInfo(String typeInfo) {
conditional.typeInfo = typeInfo;
return this;
}

View File

@@ -50,6 +50,8 @@ public class LiveConditionalParser {
if (StringUtil.hasText(autoConfigRecord)) {
getConditionalsFromPositiveMatches(autoConfigRecord).stream()
.forEach(conditional -> allConditionals.add(conditional));
getConditionalsFromNegativeMatches(autoConfigRecord).stream()
.forEach(conditional -> allConditionals.add(conditional));
}
if (!allConditionals.isEmpty()) {
return Optional.of(allConditionals);
@@ -61,9 +63,7 @@ public class LiveConditionalParser {
}
/**
* Fetches the "positiveMatches" element in the autoconfig report that contains conditional information.
* @param positiveMatchKey
* @return
* Fetches the "positiveMatches" element in the autoconfig report JSON that contains conditional information.
*/
private Optional<JSONObject> getPositiveMatchesJson(String autoConfigReport) {
JSONObject autoConfigJson = new JSONObject(autoConfigReport);
@@ -83,46 +83,100 @@ public class LiveConditionalParser {
return Optional.empty();
}
/**
* Fetches the "negativeMatches" element in the autoconfig report JSON that contains conditional information.
*/
private Optional<JSONObject> getNegativeMatchesJson(String autoConfigReport) {
JSONObject autoConfigJson = new JSONObject(autoConfigReport);
Iterator<String> keys = autoConfigJson.keys();
while (keys.hasNext()) {
String key = keys.next();
if ("negativeMatches".equals(key)) {
Object obj = autoConfigJson.get(key);
if (obj instanceof JSONObject) {
return Optional.of((JSONObject) obj);
}
}
}
return Optional.empty();
}
/**
* Fetches all the conditionals listed in the the "positiveMatches" element in the autoconfig report.
*
*/
private List<LiveConditional> getConditionalsFromPositiveMatches(String autoconfigReport) {
private List<LiveConditional> getConditionalsFromPositiveMatches(String autoConfigReport) {
List<LiveConditional> conditions = new ArrayList<>();
getPositiveMatchesJson(autoconfigReport).ifPresent((positiveMatches) -> {
Iterator<String> pMKeys = positiveMatches.keys();
while (pMKeys.hasNext()) {
getPositiveMatchesJson(autoConfigReport).ifPresent((matches) -> {
matches.keySet().stream().forEach(typeInfo -> {
// The positive match key contains the bean method information where conditional
// was applied to
String positiveMatchKey = pMKeys.next();
JSONArray matchList = (JSONArray) positiveMatches.get(positiveMatchKey);
matchList.forEach((match) -> {
if (match instanceof JSONObject) {
resolveConditional(positiveMatchKey, (JSONObject) match)
.ifPresent((condition) -> conditions.add(condition));
}
});
}
});
Object val = matches.get(typeInfo);
if (val instanceof JSONArray) {
JSONArray contentList = (JSONArray) val;
parseConditionalsFromContentList(conditions, typeInfo, contentList);
}
});
});
return conditions;
}
private Optional<LiveConditional> resolveConditional(String positiveMatchKey, JSONObject conditionalJson) {
if (conditionalJson != null) {
String condition = (String) conditionalJson.get("condition");
String message = (String) conditionalJson.get("message");
// We care about the message itself as it contains the actual annotation as well
// as the reason it matched
if (StringUtil.hasText(message)) {
return Optional.of(LiveConditional.builder().processId(appProcessId).processName(appProcessName)
.condition(condition).message(message).positiveMatchKey(positiveMatchKey).build());
}
}
return Optional.empty();
private List<LiveConditional> getConditionalsFromNegativeMatches(String autoConfigReport) {
List<LiveConditional> conditions = new ArrayList<>();
// The JSON structure being parsed is:
// "negativeMatches": {
// "MyConditionalComponent": {
// "notMatched": [
// {
// "condition": "OnClassCondition",
// "message": "@ConditionalOnClass did not find required class 'java.lang.String2'"
// }
// ],
// "matched": []
// }
getNegativeMatchesJson(autoConfigReport).ifPresent((matches) -> {
// The key in the "matches" JSON contains the live type information where the conditional was applied to
matches.keySet().stream().forEach(typeInfo -> {
// The positive match key contains the bean method information where conditional
// was applied to
Object val = matches.get(typeInfo);
if (val instanceof JSONObject) {
JSONObject negativeMatches = (JSONObject) val;
negativeMatches.keySet().stream().forEach((key) -> {
JSONArray contentList = (JSONArray) negativeMatches.get(key);
parseConditionalsFromContentList(conditions, typeInfo, contentList);
});
}
});
});
return conditions;
}
private void parseConditionalsFromContentList(List<LiveConditional> conditionals, String typeInfo,
JSONArray contentList) {
contentList.forEach((content) -> {
if (content instanceof JSONObject) {
JSONObject conditionalJson = (JSONObject) content;
String condition = (String) conditionalJson.get("condition");
String message = (String) conditionalJson.get("message");
// We care about the message itself as it contains the actual annotation as well
// as the reason it matched
if (StringUtil.hasText(message)) {
LiveConditional conditional = LiveConditional.builder().processId(appProcessId)
.processName(appProcessName).condition(condition).message(message).typeInfo(typeInfo)
.build();
conditionals.add(conditional);
}
}
});
}
public static Optional<List<LiveConditional>> parse(String autoConfigRecord, String appProcessId,
String appProcessName) {
return new LiveConditionalParser(autoConfigRecord, appProcessId, appProcessName).parse();

View File

@@ -251,7 +251,7 @@ public class SpringBootApp {
*/
public static Optional<List<LiveConditional>> getLiveConditionals(String autoConfigReport, String processId,
String processName) {
return new LiveConditionalParser(autoConfigReport, processId, processName).parse();
return LiveConditionalParser.parse(autoConfigReport, processId, processName);
}
public String getAutoConfigReport() throws Exception {

View File

@@ -11,9 +11,10 @@
package org.springframework.ide.vscode.commons.gradle;
import java.io.File;
import java.nio.file.Path;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.util.Log;
import org.springframework.ide.vscode.commons.java.AbstractJavaProject;
import org.springframework.ide.vscode.commons.java.DelegatingCachedClasspath;
/**
* Implementation of Gradle Java project
@@ -21,16 +22,18 @@ import org.springframework.ide.vscode.commons.util.Log;
* @author Alex Boyko
*
*/
public class GradleJavaProject implements IJavaProject {
public class GradleJavaProject extends AbstractJavaProject {
private GradleCore gradle;
private GradleProjectClasspath classpath;
private DelegatingCachedClasspath<GradleProjectClasspath> classpath;
private File projectDir;
public GradleJavaProject(GradleCore gradle, File projectDir) throws GradleException {
this.gradle = gradle;
public GradleJavaProject(GradleCore gradle, File projectDir, Path projectDataCache) {
super(projectDataCache);
this.projectDir = projectDir;
this.classpath = new GradleProjectClasspath(gradle, projectDir);
this.classpath = new DelegatingCachedClasspath<GradleProjectClasspath>(
() -> new GradleProjectClasspath(gradle, projectDir),
projectDataCache == null ? null : projectDataCache.resolve(DelegatingCachedClasspath.CLASSPATH_DATA_CACHE_FILE).toFile()
);
}
public File getLocation() {
@@ -38,16 +41,13 @@ public class GradleJavaProject implements IJavaProject {
}
@Override
public GradleProjectClasspath getClasspath() {
public DelegatingCachedClasspath<GradleProjectClasspath> getClasspath() {
return classpath;
}
void update() {
try {
this.classpath = new GradleProjectClasspath(gradle, projectDir);
} catch (GradleException e) {
Log.log(e);
}
boolean update() {
return classpath.update();
}
}

View File

@@ -11,6 +11,7 @@
package org.springframework.ide.vscode.commons.gradle;
import java.io.File;
import java.nio.file.Path;
import org.springframework.ide.vscode.commons.languageserver.java.AbstractFileToProjectCache;
import org.springframework.ide.vscode.commons.util.FileObserver;
@@ -25,19 +26,25 @@ public class GradleProjectCache extends AbstractFileToProjectCache<GradleJavaPro
private GradleCore gradle;
public GradleProjectCache(FileObserver fileObserver, GradleCore gradle) {
super(fileObserver);
public GradleProjectCache(FileObserver fileObserver, GradleCore gradle, boolean asyncUpdate, Path projectCacheFolder) {
super(fileObserver, asyncUpdate, projectCacheFolder);
this.gradle = gradle;
}
@Override
protected void update(GradleJavaProject project) {
project.update();
protected boolean update(GradleJavaProject project) {
return project.update();
}
@Override
protected GradleJavaProject createProject(File gradleBuild) throws Exception {
return new GradleJavaProject(gradle, gradleBuild.getParentFile());
File gradleFile = gradleBuild.getParentFile();
GradleJavaProject gradleJavaProject = new GradleJavaProject(gradle, gradleFile,
projectCacheFolder == null ? null : gradleFile.toPath().resolve(projectCacheFolder));
if (gradleJavaProject.getClasspath().isCached()) {
performUpdate(gradleJavaProject, asyncUpdate);
}
return gradleJavaProject;
}
}

View File

@@ -34,6 +34,7 @@ import org.springframework.ide.vscode.commons.util.Log;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
/**
* Implementation of {@link IClasspath} for Gradle projects
@@ -47,12 +48,19 @@ public class GradleProjectClasspath extends JandexClasspath {
private static final String JAVA_RUNTIME_VERSION = "java.runtime.version";
private static final String JAVA_BOOT_CLASS_PATH = "sun.boot.class.path";
private EclipseProject gradleProject;
private Supplier<EclipseProject> gradleProject;
private Supplier<BuildEnvironment> buildEnvironment;
public GradleProjectClasspath(GradleCore gradle, File projectDir) throws GradleException {
public GradleProjectClasspath(GradleCore gradle, File projectDir) {
super();
this.gradleProject = gradle.getModel(projectDir, EclipseProject.class);
this.gradleProject = Suppliers.memoize(() -> {
try {
return gradle.getModel(projectDir, EclipseProject.class);
} catch (GradleException e) {
Log.log(e);
return null;
}
});
this.buildEnvironment = Suppliers.memoize(() -> {
try {
return gradle.getModel(projectDir, BuildEnvironment.class);
@@ -84,7 +92,10 @@ public class GradleProjectClasspath extends JandexClasspath {
}
public EclipseProject getRootProject() {
EclipseProject root = this.gradleProject;
EclipseProject root = this.gradleProject.get();
if (root == null) {
return root;
}
while(root.getParent() != null) {
root = root.getParent();
}
@@ -92,15 +103,21 @@ public class GradleProjectClasspath extends JandexClasspath {
}
@Override
public Stream<Path> getClasspathEntries() throws Exception {
public ImmutableList<Path> getClasspathEntries() throws Exception {
EclipseProject root = getRootProject();
return Stream.concat(gradleProject.getClasspath().stream().map(dep -> dep.getFile().toPath()),
gradleProject.getProjectDependencies().stream()
.map(d -> findPeer(root, d.getTargetProject().getName()))
.filter(o -> o.isPresent())
.map(o -> o.get())
.map(p -> p.getProjectDirectory().toPath().resolve(p.getOutputLocation().getPath()))
);
EclipseProject project = gradleProject.get();
if (project == null) {
return ImmutableList.of();
} else {
ImmutableList<Path> classpathEntries = ImmutableList.copyOf(Stream.concat(project.getClasspath().stream().map(dep -> dep.getFile().toPath()),
project.getProjectDependencies().stream()
.map(d -> findPeer(root, d.getTargetProject().getName()))
.filter(o -> o.isPresent())
.map(o -> o.get())
.map(p -> p.getProjectDirectory().toPath().resolve(p.getOutputLocation().getPath()))
).collect(Collectors.toList()));
return classpathEntries;
}
}
private Optional<? extends EclipseProject> findPeer(EclipseProject root, String name) {
@@ -108,26 +125,33 @@ public class GradleProjectClasspath extends JandexClasspath {
}
@Override
public Stream<String> getClasspathResources() {
return gradleProject.getSourceDirectories().stream().map(sourceDirectory -> sourceDirectory.getDirectory()).flatMap(folder -> {
try {
return Files.walk(folder.toPath())
.filter(path -> Files.isRegularFile(path))
.map(path -> folder.toPath().relativize(path))
.map(relativePath -> relativePath.toString())
.filter(pathString -> !pathString.endsWith(".java") && !pathString.endsWith(".class"));
} catch (IOException e) {
return Stream.empty();
}
});
public ImmutableList<String> getClasspathResources() {
EclipseProject project = gradleProject.get();
if (project == null) {
return ImmutableList.of();
} else {
return ImmutableList.copyOf(project.getSourceDirectories().stream().map(sourceDirectory -> sourceDirectory.getDirectory()).flatMap(folder -> {
try {
return Files.walk(folder.toPath())
.filter(path -> Files.isRegularFile(path))
.map(path -> folder.toPath().relativize(path))
.map(relativePath -> relativePath.toString())
.filter(pathString -> !pathString.endsWith(".java") && !pathString.endsWith(".class"));
} catch (IOException e) {
return Stream.empty();
}
}).toArray(String[]::new));
}
}
public Path getOutputFolder() {
return gradleProject.getProjectDirectory().toPath().resolve(gradleProject.getOutputLocation().getPath());
EclipseProject project = gradleProject.get();
return project == null ? null : project.getProjectDirectory().toPath().resolve(project.getOutputLocation().getPath());
}
public String getName() {
return gradleProject.getName();
EclipseProject project = gradleProject.get();
return project == null ? null : project.getName();
}
public boolean exists() {
@@ -136,20 +160,23 @@ public class GradleProjectClasspath extends JandexClasspath {
@Override
protected IJavadocProvider createParserJavadocProvider(File classpathResource) {
if (classpathResource.isDirectory()) {
Optional<File> classpathFolder = gradleProject.getSourceDirectories().stream()
.map(dir -> dir.getDirectory())
.filter(dir -> classpathResource.toPath().startsWith(dir.toPath()))
.findFirst();
if (classpathFolder.isPresent()) {
return new ParserJavadocProvider(type -> {
return SourceUrlProviderFromSourceContainer.SOURCE_FOLDER_URL_SUPPLIER
.sourceUrl(classpathFolder.get().toURI().toURL(), type);
});
EclipseProject project = gradleProject.get();
if (project != null) {
if (classpathResource.isDirectory()) {
Optional<File> classpathFolder = project.getSourceDirectories().stream()
.map(dir -> dir.getDirectory())
.filter(dir -> classpathResource.toPath().startsWith(dir.toPath()))
.findFirst();
if (classpathFolder.isPresent()) {
return new ParserJavadocProvider(type -> {
return SourceUrlProviderFromSourceContainer.SOURCE_FOLDER_URL_SUPPLIER
.sourceUrl(classpathFolder.get().toURI().toURL(), type);
});
}
} else {
}
} else {
}
return null;
}
@@ -222,4 +249,13 @@ public class GradleProjectClasspath extends JandexClasspath {
return new File(JandexIndex.getIndexFolder().toString(), jarFile.getName() + "-" + suffix + ".jdx");
}
@Override
public boolean equals(Object obj) {
if (obj instanceof GradleProjectClasspath) {
return super.equals(obj);
}
return false;
}
}

View File

@@ -13,21 +13,26 @@ package org.springframework.ide.vscode.commons.gradle;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.assertj.core.util.Files;
import org.junit.Test;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver.Listener;
import org.springframework.ide.vscode.commons.util.BasicFileObserver;
import com.google.common.collect.ImmutableList;
/**
* Tests covering Gradle project data
*
@@ -36,15 +41,27 @@ import org.springframework.ide.vscode.commons.util.BasicFileObserver;
*/
public class GradleProjectTest {
private static void writeContent(File file, String content) throws IOException {
FileWriter writer = null;
try {
writer = new FileWriter(file);
writer.write(content);
} finally {
writer.close();
}
}
private GradleJavaProject getGradleProject(String projectName) throws Exception {
Path testProjectPath = Paths.get(GradleProjectTest.class.getResource("/" + projectName).toURI());
return new GradleJavaProject(GradleCore.getDefault(), testProjectPath.toFile());
GradleJavaProject gradleJavaProject = new GradleJavaProject(GradleCore.getDefault(), testProjectPath.toFile(), null);
gradleJavaProject.update();
return gradleJavaProject;
}
@Test
public void testEclipseGradleProject() throws Exception {
GradleJavaProject project = getGradleProject("empty-gradle-project");
Set<Path> calculatedClassPath = project.getClasspath().getClasspathEntries().collect(Collectors.toSet());
ImmutableList<Path> calculatedClassPath = project.getClasspath().getClasspathEntries();
assertEquals(48, calculatedClassPath.size());
}
@@ -57,7 +74,7 @@ public class GradleProjectTest {
@Test
public void gradleClasspathResource() throws Exception {
GradleJavaProject project = getGradleProject("test-app-1");
List<String> resources = project.getClasspath().getClasspathResources().collect(Collectors.toList());
List<String> resources = project.getClasspath().getClasspathResources();
assertArrayEquals(new String[] {"test-resource-1.txt"}, resources.toArray(new String[resources.size()]));
}
@@ -65,38 +82,57 @@ public class GradleProjectTest {
public void testGradleFileChanges() throws Exception {
Path testProjectPath = Paths.get(GradleProjectTest.class.getResource("/empty-gradle-project").toURI());
File gradleFile = testProjectPath.resolve(GradleCore.GRADLE_BUILD_FILE).toFile();
BasicFileObserver fileObserver = new BasicFileObserver();
GradleProjectCache manager = new GradleProjectCache(fileObserver, GradleCore.getDefault());
IJavaProject[] projectChanged = new IJavaProject[] { null };
IJavaProject[] projectDeleted = new IJavaProject[] { null };
manager.addListener(new Listener() {
@Override
public void created(IJavaProject project) {}
@Override
public void changed(IJavaProject project) {
projectChanged[0] = project;
}
@Override
public void deleted(IJavaProject project) {
projectDeleted[0] = project;
}
});
// Get the project from cache
GradleJavaProject cachedProject = manager.project(gradleFile);
assertNotNull(cachedProject);
String gradelFileContents = Files.contentOf(gradleFile, Charset.defaultCharset());
fileObserver.notifyFileChanged(gradleFile.toURI().toString());
assertEquals(cachedProject, projectChanged[0]);
fileObserver.notifyFileDeleted(gradleFile.toURI().toString());
assertEquals(cachedProject, projectDeleted[0]);
try {
BasicFileObserver fileObserver = new BasicFileObserver();
GradleProjectCache manager = new GradleProjectCache(fileObserver, GradleCore.getDefault(), false, null);
IJavaProject[] projectChanged = new IJavaProject[] { null };
IJavaProject[] projectDeleted = new IJavaProject[] { null };
// Get the project from cache
GradleJavaProject cachedProject = manager.project(gradleFile);
assertNotNull(cachedProject);
manager.addListener(new Listener() {
@Override
public void created(IJavaProject project) {}
@Override
public void changed(IJavaProject project) {
projectChanged[0] = project;
}
@Override
public void deleted(IJavaProject project) {
projectDeleted[0] = project;
}
});
ImmutableList<Path> calculatedClassPath = cachedProject.getClasspath().getClasspathEntries();
assertEquals(48, calculatedClassPath.size());
fileObserver.notifyFileChanged(gradleFile.toURI().toString());
assertNull(projectChanged[0]);
writeContent(gradleFile, Files.contentOf(testProjectPath.resolve("build.newgradle").toFile(), Charset.defaultCharset()));
fileObserver.notifyFileChanged(gradleFile.toURI().toString());
assertNotNull(projectChanged[0]);
assertEquals(cachedProject, projectChanged[0]);
calculatedClassPath = cachedProject.getClasspath().getClasspathEntries();
assertEquals(49, calculatedClassPath.size());
fileObserver.notifyFileDeleted(gradleFile.toURI().toString());
assertEquals(cachedProject, projectDeleted[0]);
} finally {
writeContent(gradleFile, gradelFileContents);
}
}
@Test
public void findGradleProjectWithStandardBuildFile() throws Exception {
GradleProjectFinder finder = new GradleProjectFinder(new GradleProjectCache(new BasicFileObserver(), GradleCore.getDefault()));
GradleProjectFinder finder = new GradleProjectFinder(new GradleProjectCache(new BasicFileObserver(), GradleCore.getDefault(), false, null));
File sourceFile = new File(GradleProjectTest.class.getResource("/test-app-1/src/main/java/Library.java").toURI());
Optional<IJavaProject> project = finder.find(sourceFile);
assertTrue(project.isPresent());
@@ -107,7 +143,7 @@ public class GradleProjectTest {
@Test
public void findGradleProjectWithNonStandardBuildFile() throws Exception {
GradleProjectFinder finder = new GradleProjectFinder(new GradleProjectCache(new BasicFileObserver(), GradleCore.getDefault()));
GradleProjectFinder finder = new GradleProjectFinder(new GradleProjectCache(new BasicFileObserver(), GradleCore.getDefault(), false, null));
File sourceFile = new File(GradleProjectTest.class.getResource("/test-app-2/src/main/java/Library.java").toURI());
Optional<IJavaProject> project = finder.find(sourceFile);
assertTrue(project.isPresent());

View File

@@ -0,0 +1,34 @@
buildscript {
ext {
springBootVersion = '1.5.1.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
jar {
baseName = 'empty-boot-1.4.0-web-app'
version = '0.0.1-SNAPSHOT'
}
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
compile('org.springframework.boot:spring-boot-starter-actuator')
compile('org.springframework.boot:spring-boot-starter-web')
compile('org.springframework.boot:spring-boot-devtools')
testCompile('org.springframework.boot:spring-boot-starter-test')
}

View File

@@ -46,5 +46,10 @@
<artifactId>reactor-core</artifactId>
<version>${reactor-version}</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20160810</version>
</dependency>
</dependencies>
</project>

View File

@@ -35,7 +35,7 @@ public abstract class JandexClasspath implements IClasspath {
protected JandexIndex createIndex() {
Stream<Path> classpathEntries = Stream.empty();
try {
classpathEntries = getClasspathEntries();
classpathEntries = getClasspathEntries().stream();
} catch (Exception e) {
Log.log(e);
}

View File

@@ -0,0 +1,29 @@
/*******************************************************************************
* 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.commons.java;
import java.nio.file.Path;
/**
* Abstract java project. Has a folder to store some project calculated data to speed up access
*
* @author Alex Boyko
*
*/
public abstract class AbstractJavaProject implements IJavaProject {
final protected Path projectDataCache;
public AbstractJavaProject(Path projectDataCache) {
this.projectDataCache = projectDataCache;
}
}

View File

@@ -0,0 +1,247 @@
/*******************************************************************************
* 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.commons.java;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.json.JSONArray;
import org.json.JSONObject;
import org.json.JSONTokener;
import org.springframework.ide.vscode.commons.util.Log;
import com.google.common.base.Objects;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableList;
import reactor.core.publisher.Flux;
import reactor.util.function.Tuple2;
/**
* A wrapper around the classpath created from a Java project using the data in the project file (maven, gradle)
* The wrapper caches some of classpath data such as
* <li> Classpath entries </li>
* <li> Classpath resources </li>
* <li> Output folder </li>
* <li> Projects' name </li>
*
* The cached classpath data is written to ".sts4-cache/classpath-data.json" and loadedd from it when intance of this classpath is created
*
* Implementation is somewhat experimental at the moment...
*
* @author Alex Boyko
*
* @param <T> a subclass of {@link IClasspath} the delegated to classpath created from current data
*/
public class DelegatingCachedClasspath<T extends IClasspath> implements IClasspath {
public static final String CLASSPATH_DATA_CACHE_FILE = "classpath-data.json";
private static final String OUTPUT_FOLDER_PROPERTY = "outputFolder";
private static final String CLASSPATH_RESOURCES_PROPERTY = "classpathResources";
private static final String CLASSPATH_ENTRIES_PROPERTY = "classpathEntries";
private static final String NAME_PROPERTY = "name";
protected static class ClasspathData {
final public String name;
final public Set<Path> classpathEntries;
final public Set<String> classpathResources;
final public Path outputFolder;
public ClasspathData(String name, Set<Path> classpathEntries, Set<String> classpathResources, Path outputFolder) {
this.name = name;
this.classpathEntries = classpathEntries;
this.classpathResources = classpathResources;
this.outputFolder = outputFolder;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof ClasspathData) {
ClasspathData other = (ClasspathData) obj;
try {
return Objects.equal(name, other.name)
&& Objects.equal(classpathEntries, other.classpathEntries)
&& Objects.equal(classpathResources, other.classpathResources)
&& Objects.equal(outputFolder, outputFolder);
} catch (Throwable t) {
Log.log(t);
}
}
return false;
}
}
private AtomicReference<ClasspathData> cachedData;
private Supplier<T> delegateCreator;
private AtomicReference<T> cachedDelegate;
final private File cacheFile;
public DelegatingCachedClasspath(Supplier<T> delegateCreator, File cacheFile) {
super();
this.cacheFile = cacheFile;
this.cachedDelegate = new AtomicReference<>(delegateCreator.get());
this.cachedData = new AtomicReference<>(init());
this.delegateCreator = delegateCreator;
if (!isCached()) {
update();
}
}
public T delegate() {
return cachedDelegate.get();
}
@Override
public String getName() {
return cachedData.get().name;
}
@Override
public Path getOutputFolder() {
return cachedData.get().outputFolder;
}
@Override
public ImmutableList<Path> getClasspathEntries() throws Exception {
return ImmutableList.copyOf(cachedData.get().classpathEntries);
}
@Override
public ImmutableList<String> getClasspathResources() {
return ImmutableList.copyOf(cachedData.get().classpathResources);
}
public boolean isCached() {
return cacheFile != null && cacheFile.exists();
}
private synchronized ClasspathData loadCachedData() {
if (cacheFile != null && cacheFile.exists()) {
try {
JSONObject json = new JSONObject(new JSONTokener(new FileInputStream(cacheFile)));
String name = json.getString(NAME_PROPERTY);
JSONArray classpathEntriesJson = json.optJSONArray(CLASSPATH_ENTRIES_PROPERTY);
JSONArray classpathResourcesJson = json.optJSONArray(CLASSPATH_RESOURCES_PROPERTY);
String outputFolderStr = json.optString(OUTPUT_FOLDER_PROPERTY);
return new ClasspathData(
name,
classpathEntriesJson == null ? Collections.emptySet() : classpathEntriesJson.toList().stream()
.filter(o -> o instanceof String)
.map(o -> (String) o)
.map(s -> new File(s).toPath())
.collect(Collectors.toSet()),
classpathResourcesJson == null ? Collections.emptySet() : classpathResourcesJson.toList().stream()
.filter(o -> o instanceof String)
.map(o -> (String) o)
.collect(Collectors.toSet()),
outputFolderStr == null ? null : new File(outputFolderStr).toPath()
);
} catch (Throwable e) {
Log.log(e);
}
}
return null;
}
private ClasspathData init() {
ClasspathData data = loadCachedData();
return data == null ? new ClasspathData(null, Collections.emptySet(), Collections.emptySet(), null) : data;
}
private synchronized void persistCachedData(ClasspathData data) {
if (cacheFile != null && data != null) {
FileWriter writer = null;
try {
Files.createDirectories(cacheFile.getParentFile().toPath());
JSONObject json = new JSONObject();
json.put(NAME_PROPERTY, data.name);
json.put(CLASSPATH_ENTRIES_PROPERTY, data.classpathEntries.stream().map(e -> e.toString()).collect(Collectors.toList()));
json.put(CLASSPATH_RESOURCES_PROPERTY, data.classpathResources);
json.put(OUTPUT_FOLDER_PROPERTY, data.outputFolder);
writer = new FileWriter(cacheFile);
json.write(writer);
} catch (IOException e) {
Log.log(e);
} finally {
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
Log.log(e);
}
}
}
}
}
public boolean update() {
final ClasspathData newData = createClasspathData();
if (!Objects.equal(cachedData.get(), newData)) {
cachedData.set(newData);
persistCachedData(newData);
return true;
}
return false;
}
@Override
public boolean exists() {
return cachedDelegate.get().exists();
}
@Override
public IType findType(String fqName) {
return cachedDelegate.get().findType(fqName);
}
@Override
public Flux<Tuple2<IType, Double>> fuzzySearchTypes(String searchTerm, Predicate<IType> typeFilter) {
return cachedDelegate.get().fuzzySearchTypes(searchTerm, typeFilter);
}
@Override
public Flux<Tuple2<String, Double>> fuzzySearchPackages(String searchTerm) {
return cachedDelegate.get().fuzzySearchPackages(searchTerm);
}
@Override
public Flux<IType> allSubtypesOf(IType type) {
return cachedDelegate.get().allSubtypesOf(type);
}
protected ClasspathData createClasspathData() {
T newDelegate = delegateCreator.get();
cachedDelegate.set(newDelegate);
try {
LinkedHashSet<Path> classpathEntries = new LinkedHashSet<>(newDelegate.getClasspathEntries());
return new ClasspathData(newDelegate.getName(), classpathEntries, new LinkedHashSet<>(newDelegate.getClasspathResources()), newDelegate.getOutputFolder());
} catch (Exception e) {
Log.log(e);
return new ClasspathData(newDelegate.getName(), Collections.emptySet(), new LinkedHashSet<>(newDelegate.getClasspathResources()), newDelegate.getOutputFolder());
}
}
}

View File

@@ -12,7 +12,8 @@ package org.springframework.ide.vscode.commons.java;
import java.nio.file.Path;
import java.util.function.Predicate;
import java.util.stream.Stream;
import com.google.common.collect.ImmutableList;
import reactor.core.publisher.Flux;
import reactor.util.function.Tuple2;
@@ -46,12 +47,12 @@ public interface IClasspath {
* @return collection of classpath entries in a form file/folder paths
* @throws Exception
*/
Stream<Path> getClasspathEntries() throws Exception;
ImmutableList<Path> getClasspathEntries() throws Exception;
/**
* Classpath resources paths relative to the source folder path
* @return classpath resource relative paths
*/
Stream<String> getClasspathResources();
ImmutableList<String> getClasspathResources();
}

View File

@@ -14,6 +14,8 @@ import org.springframework.ide.vscode.commons.javadoc.IJavadoc;
public interface IJavaProject extends IJavaElement {
final static String PROJECT_CACHE_FOLDER = ".sts4-cache";
IClasspath getClasspath();
@Override

View File

@@ -11,14 +11,16 @@
package org.springframework.ide.vscode.commons.languageserver.java;
import java.io.File;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.util.FileObserver;
/**
* Cache fo java projects. The key for the cache is a "project" specific file
* Cache for java projects. The key for the cache is a "project" specific file
*
* @author Alex Boyko
*
@@ -28,19 +30,26 @@ public abstract class AbstractFileToProjectCache<P extends IJavaProject> extends
private String changeSubscription;
private String deleteSubscription;
public AbstractFileToProjectCache(FileObserver fileObserver) {
protected boolean asyncUpdate;
protected final Path projectCacheFolder;
private boolean alwaysFireEventOnFileChanged;
public AbstractFileToProjectCache(FileObserver fileObserver, boolean asyncUpdate, Path projectCacheFolder) {
super(fileObserver);
this.projectCacheFolder = projectCacheFolder;
this.asyncUpdate = asyncUpdate;
}
final public void setAlwaysFireEventOnFileChanged(boolean alwaysFireEventOnFileChanged) {
this.alwaysFireEventOnFileChanged = alwaysFireEventOnFileChanged;
}
@Override
protected void attachListeners(File file, P project) {
super.attachListeners(file, project);
List<String> globPattern = Arrays.asList(file.toString());
changeSubscription = getFileObserver().onFileChanged(globPattern, (uri) -> {
update(project);
notifyProjectChanged(project);
});
changeSubscription = getFileObserver().onFileChanged(globPattern, (uri) -> performUpdate(project, asyncUpdate));
deleteSubscription = getFileObserver().onFileDeleted(globPattern, (uri) -> {
cache.invalidate(file);
notifyProjectDeleted(project);
@@ -49,6 +58,20 @@ public abstract class AbstractFileToProjectCache<P extends IJavaProject> extends
});
}
abstract protected void update(P project);
final protected void performUpdate(P project, boolean async) {
if (async) {
CompletableFuture.supplyAsync(() -> update(project)).thenAccept((changed) -> {
if (changed || alwaysFireEventOnFileChanged) {
notifyProjectChanged(project);
}
});
} else {
if (update(project) || alwaysFireEventOnFileChanged) {
notifyProjectChanged(project);
}
}
}
abstract protected boolean update(P project);
}

View File

@@ -11,8 +11,10 @@
package org.springframework.ide.vscode.commons.maven.java;
import java.io.File;
import java.nio.file.Path;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.java.AbstractJavaProject;
import org.springframework.ide.vscode.commons.java.DelegatingCachedClasspath;
import org.springframework.ide.vscode.commons.maven.MavenCore;
/**
@@ -21,21 +23,29 @@ import org.springframework.ide.vscode.commons.maven.MavenCore;
* @author Alex Boyko
*
*/
public class MavenJavaProject implements IJavaProject {
private MavenProjectClasspath classpath;
public MavenJavaProject(MavenCore maven, File pom) {
this.classpath = new MavenProjectClasspath(maven, pom);
public class MavenJavaProject extends AbstractJavaProject {
private DelegatingCachedClasspath<MavenProjectClasspath> classpath;
public MavenJavaProject(MavenCore maven, File pom, Path projectDataCache) {
super(projectDataCache);
this.classpath = new DelegatingCachedClasspath<>(
() -> new MavenProjectClasspath(maven, pom),
projectDataCache == null ? null : projectDataCache.resolve(DelegatingCachedClasspath.CLASSPATH_DATA_CACHE_FILE).toFile()
);
}
public MavenJavaProject(MavenCore maven, File pom) {
this(maven, pom, null);
}
@Override
public MavenProjectClasspath getClasspath() {
public DelegatingCachedClasspath<MavenProjectClasspath> getClasspath() {
return classpath;
}
void update(MavenCore maven) {
this.classpath = new MavenProjectClasspath(maven, classpath.getPomFile());
boolean update() {
return classpath.update();
}
}

View File

@@ -11,6 +11,7 @@
package org.springframework.ide.vscode.commons.maven.java;
import java.io.File;
import java.nio.file.Path;
import org.springframework.ide.vscode.commons.languageserver.java.AbstractFileToProjectCache;
import org.springframework.ide.vscode.commons.maven.MavenCore;
@@ -25,19 +26,24 @@ public class MavenProjectCache extends AbstractFileToProjectCache<MavenJavaProje
private MavenCore maven;
public MavenProjectCache(FileObserver fileObserver, MavenCore maven) {
super(fileObserver);
public MavenProjectCache(FileObserver fileObserver, MavenCore maven, boolean asyncUpdate, Path projectCacheFolder) {
super(fileObserver, asyncUpdate, projectCacheFolder);
this.maven = maven;
}
@Override
protected void update(MavenJavaProject project) {
project.update(maven);
protected boolean update(MavenJavaProject project) {
return project.update();
}
@Override
protected MavenJavaProject createProject(File pomFile) throws Exception {
return new MavenJavaProject(maven, pomFile);
MavenJavaProject mavenJavaProject = new MavenJavaProject(maven, pomFile,
projectCacheFolder == null ? null : pomFile.getParentFile().toPath().resolve(projectCacheFolder));
if (mavenJavaProject.getClasspath().isCached()) {
performUpdate(mavenJavaProject, asyncUpdate);
}
return mavenJavaProject;
}
}

View File

@@ -19,6 +19,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.maven.artifact.Artifact;
@@ -36,6 +37,7 @@ import org.springframework.ide.vscode.commons.util.Log;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
/**
* Classpath for a maven project
@@ -81,6 +83,10 @@ public class MavenProjectClasspath extends JandexClasspath {
return pom;
}
MavenCore maven() {
return maven;
}
public boolean exists() {
return pom.exists();
}
@@ -91,11 +97,13 @@ public class MavenProjectClasspath extends JandexClasspath {
}
@Override
public Stream<Path> getClasspathEntries() throws Exception {
public ImmutableList<Path> getClasspathEntries() throws Exception {
// return Stream.concat(maven.resolveDependencies(project, null).stream().map(artifact -> {
// return artifact.getFile().toPath();
// }), projectResolvedOutput());
return Stream.concat(projectDependencies().stream().map(a -> a.getFile().toPath()), projectOutput().stream().map(f -> f.toPath()));
ImmutableList<Path> classpathEntries = ImmutableList.copyOf(Stream.concat(projectDependencies().stream().map(a -> a.getFile().toPath()),
projectOutput().stream().map(f -> f.toPath())).collect(Collectors.toList()));
return classpathEntries;
}
private Set<Artifact> projectDependencies() {
@@ -123,12 +131,12 @@ public class MavenProjectClasspath extends JandexClasspath {
}
@Override
public Stream<String> getClasspathResources() {
public ImmutableList<String> getClasspathResources() {
MavenProject project = projectSupplier.get();
if (project == null) {
return Stream.empty();
return ImmutableList.of();
}
return project.getBuild().getResources().stream().flatMap(resource -> {
return ImmutableList.copyOf(project.getBuild().getResources().stream().filter(resource -> new File(resource.getDirectory()).exists()).flatMap(resource -> {
DirectoryScanner scanner = new DirectoryScanner();
scanner.setBasedir(resource.getDirectory());
if (resource.getIncludes() != null && !resource.getIncludes().isEmpty()) {
@@ -140,7 +148,7 @@ public class MavenProjectClasspath extends JandexClasspath {
scanner.setCaseSensitive(false);
scanner.scan();
return Arrays.stream(scanner.getIncludedFiles());
});
}).toArray(String[]::new));
}
/*
@@ -253,4 +261,20 @@ public class MavenProjectClasspath extends JandexClasspath {
}
}
@Override
public boolean equals(Object obj) {
if (obj instanceof MavenProjectClasspath) {
MavenProjectClasspath other = (MavenProjectClasspath) obj;
try {
if (pom.equals(other.pom)
&& projectSupplier.get().equals(other.projectSupplier.get())) {
return super.equals(obj);
}
} catch (Throwable t) {
Log.log(t);
}
}
return false;
}
}

View File

@@ -13,12 +13,15 @@ package org.springframework.ide.vscode.commons.maven.java.classpathfile;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.springframework.ide.vscode.commons.java.IClasspath;
import org.springframework.ide.vscode.commons.java.IType;
import org.springframework.ide.vscode.commons.maven.MavenCore;
import com.google.common.collect.ImmutableList;
import reactor.core.publisher.Flux;
import reactor.util.function.Tuple2;
@@ -37,15 +40,15 @@ public class FileClasspath implements IClasspath {
}
@Override
public Stream<Path> getClasspathEntries() throws Exception {
return Stream.concat(MavenCore.readClassPathFile(classpathFilePath),
public ImmutableList<Path> getClasspathEntries() throws Exception {
return ImmutableList.copyOf(Stream.concat(MavenCore.readClassPathFile(classpathFilePath),
Stream.of(classpathFilePath.getParent().resolve("target/classes"),
classpathFilePath.getParent().resolve("target/test-classes")));
classpathFilePath.getParent().resolve("target/test-classes"))).collect(Collectors.toList()));
}
@Override
public Stream<String> getClasspathResources() {
return Stream.empty();
public ImmutableList<String> getClasspathResources() {
return ImmutableList.of();
}
@Override

View File

@@ -12,6 +12,7 @@ package org.springframework.ide.vscode.commons.maven.java.classpathfile;
import java.io.File;
import java.nio.file.Paths;
import java.util.concurrent.CompletableFuture;
import org.springframework.ide.vscode.commons.java.IClasspath;
import org.springframework.ide.vscode.commons.java.IJavaProject;
@@ -67,8 +68,18 @@ public class JavaProjectWithClasspathFile implements IJavaProject {
return true;
}
void update() {
this.classpath = new FileClasspath(Paths.get(cpFile.toURI()));
CompletableFuture<Boolean> update() {
return CompletableFuture.supplyAsync(() -> doUpdate());
}
private synchronized boolean doUpdate() {
FileClasspath newClasspath = new FileClasspath(Paths.get(cpFile.toURI()));
if (newClasspath.equals(classpath)) {
return false;
} else {
this.classpath = newClasspath;
return true;
}
}
}

View File

@@ -18,12 +18,13 @@ import org.springframework.ide.vscode.commons.util.FileObserver;
public class JavaProjectWithClasspathFileCache extends AbstractFileToProjectCache<JavaProjectWithClasspathFile> {
public JavaProjectWithClasspathFileCache(FileObserver fileObserver) {
super(fileObserver);
super(fileObserver, false, null);
}
@Override
protected void update(JavaProjectWithClasspathFile project) {
project.update();
protected boolean update(JavaProjectWithClasspathFile project) {
project.update();
return true;
}
@Override

View File

@@ -12,11 +12,16 @@ package org.springframework.ide.vscode.commons.maven;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.assertj.core.util.Files;
import org.junit.Test;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver.Listener;
@@ -24,6 +29,8 @@ import org.springframework.ide.vscode.commons.maven.java.MavenJavaProject;
import org.springframework.ide.vscode.commons.maven.java.MavenProjectCache;
import org.springframework.ide.vscode.commons.util.BasicFileObserver;
import com.google.common.collect.ImmutableList;
/**
* Tests for {@link MavenProjectCache}
*
@@ -31,38 +38,66 @@ import org.springframework.ide.vscode.commons.util.BasicFileObserver;
*
*/
public class MavenProjectManagerTest {
private static void writeContent(File file, String content) throws IOException {
FileWriter writer = null;
try {
writer = new FileWriter(file);
writer.write(content);
} finally {
writer.close();
}
}
@Test
public void testPomFileChanges() throws Exception {
Path testProjectPath = Paths.get(DependencyTreeTest.class.getResource("/empty-boot-project-with-classpath-file").toURI());
File pomFile = testProjectPath.resolve(MavenCore.POM_XML).toFile();
BasicFileObserver fileObserver = new BasicFileObserver();
MavenProjectCache cache = new MavenProjectCache(fileObserver, MavenCore.getDefault());
IJavaProject[] projectChanged = new IJavaProject[] { null };
IJavaProject[] projectDeleted = new IJavaProject[] { null };
cache.addListener(new Listener() {
@Override
public void created(IJavaProject project) {}
String pomFileContents = Files.contentOf(pomFile, Charset.defaultCharset());
@Override
public void changed(IJavaProject project) {
projectChanged[0] = project;
}
@Override
public void deleted(IJavaProject project) {
projectDeleted[0] = project;
}
});
// Get the project from cache
MavenJavaProject cachedProject = cache.project(pomFile);
assertNotNull(cachedProject);
fileObserver.notifyFileChanged(pomFile.toURI().toString());
assertEquals(cachedProject, projectChanged[0]);
fileObserver.notifyFileDeleted(pomFile.toURI().toString());
assertEquals(cachedProject, projectDeleted[0]);
try {
BasicFileObserver fileObserver = new BasicFileObserver();
MavenProjectCache cache = new MavenProjectCache(fileObserver, MavenCore.getDefault(), false, null);
IJavaProject[] projectChanged = new IJavaProject[] { null };
IJavaProject[] projectDeleted = new IJavaProject[] { null };
cache.addListener(new Listener() {
@Override
public void created(IJavaProject project) {}
@Override
public void changed(IJavaProject project) {
projectChanged[0] = project;
}
@Override
public void deleted(IJavaProject project) {
projectDeleted[0] = project;
}
});
// Get the project from cache
MavenJavaProject cachedProject = cache.project(pomFile);
assertNotNull(cachedProject);
ImmutableList<Path> calculatedClassPath = cachedProject.getClasspath().getClasspathEntries();
assertEquals(48, calculatedClassPath.size());
fileObserver.notifyFileChanged(pomFile.toURI().toString());
assertNull(projectChanged[0]);
writeContent(pomFile, Files.contentOf(testProjectPath.resolve("pom.newxml").toFile(), Charset.defaultCharset()));
fileObserver.notifyFileChanged(pomFile.toURI().toString());
assertNotNull(projectChanged[0]);
assertEquals(cachedProject, projectChanged[0]);
calculatedClassPath = cachedProject.getClasspath().getClasspathEntries();
assertEquals(49, calculatedClassPath.size());
fileObserver.notifyFileDeleted(pomFile.toURI().toString());
assertEquals(cachedProject, projectDeleted[0]);
} finally {
//restore original content
writeContent(pomFile, pomFileContents);
}
}

View File

@@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>empty-boot-1.4.0-web-app</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>empty-boot-project-with-classpath-file</name>
<description>Empty Boot project generating classpath.txt file</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>build-classpath</id>
<phase>generate-sources</phase>
<goals>
<goal>build-classpath</goal>
</goals>
</execution>
</executions>
<configuration>
<outputFile>classpath.txt</outputFile>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -228,4 +228,22 @@ public class StringUtil {
}
return fullyQualifiedName;
}
public static boolean containsCharactersCaseInsensitive(String value, String query) {
return containsCharacters(value.toLowerCase().toCharArray(), query.toLowerCase().toCharArray());
}
public static boolean containsCharacters(char[] valChars, char[] queryChars) {
int symbolindex = 0;
int queryindex = 0;
while (queryindex < queryChars.length && symbolindex < valChars.length) {
if (valChars[symbolindex] == queryChars[queryindex]) {
queryindex++;
}
symbolindex++;
}
return queryindex == queryChars.length;
}
}