PT #151547534 Cache classpath in a file
This commit is contained in:
@@ -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
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -111,7 +111,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);
|
||||
|
||||
@@ -456,7 +456,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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user