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:
@@ -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 |
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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-");
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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`]");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -22,11 +22,4 @@ public class MultipleConditionals {
|
||||
public Hello hi() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnNotWebApplication
|
||||
public Hello missing() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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')
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user