Refactor, fix and simplify classpath infrastructure

This commit is contained in:
Kris De Volder
2018-04-30 10:10:56 -07:00
parent aa1cb03f12
commit 16a56a5fdc
69 changed files with 1518 additions and 993 deletions

View File

@@ -11,58 +11,73 @@
package org.springframework.ide.vscode.commons.jandex;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.springframework.ide.vscode.commons.java.ClasspathIndex;
import org.springframework.ide.vscode.commons.java.IClasspath;
import org.springframework.ide.vscode.commons.java.IClasspathUtil;
import org.springframework.ide.vscode.commons.java.IJavadocProvider;
import org.springframework.ide.vscode.commons.java.IType;
import org.springframework.ide.vscode.commons.javadoc.JavaDocProviders;
import org.springframework.ide.vscode.commons.languageserver.jdt.ls.Classpath.CPE;
import org.springframework.ide.vscode.commons.util.CollectorUtil;
import org.springframework.ide.vscode.commons.util.FileObserver;
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;
import reactor.core.Disposable;
import reactor.core.Disposables;
import reactor.core.publisher.Flux;
import reactor.util.function.Tuple2;
/**
* Classpath with Jandex Java index for searching types
*
*
* @author Alex Boyko
*
*/
public abstract class JandexClasspath implements IClasspath {
public final class JandexClasspath implements ClasspathIndex {
public static JavadocProviderTypes providerType = JavadocProviderTypes.HTML;
public enum JavadocProviderTypes {
// JAVA_PARSER, //Used to be based on githb java parser. If need something back that can extract docs from source code, we have to implement
// based on JDT parser. But at the moment this wasn't being used so just got removed.
HTML
}
private Supplier<JandexIndex> javaIndex;
public JandexClasspath() {
private final IClasspath classpath;
private final FileObserver fileObserver;
public JandexClasspath(IClasspath classpath, FileObserver fileObserver) {
this.fileObserver = fileObserver;
this.classpath = classpath;
this.javaIndex = Suppliers.synchronizedSupplier(Suppliers.memoize(() -> createIndex()));
}
protected JandexIndex createIndex() {
Collection<Path> classpathEntries = ImmutableList.of();
attachFolderListeners();
Collection<File> classpathEntries = ImmutableList.of();
try {
classpathEntries = getClasspathEntryPaths();
for (Path path : classpathEntries) {
System.out.println(path);
}
classpathEntries = IClasspathUtil.getBinaryRoots(classpath);
} catch (Exception e) {
Log.log(e);
}
return new JandexIndex(classpathEntries.stream().map(p -> p.toFile()).collect(Collectors.toList()), jarFile -> findIndexFile(jarFile), classpathResource -> {
return new JandexIndex(classpathEntries, jarFile -> findIndexFile(jarFile), classpathResource -> {
switch (providerType) {
// case JAVA_PARSER:
// return createParserJavadocProvider(classpathResource);
@@ -73,23 +88,63 @@ public abstract class JandexClasspath implements IClasspath {
}
}, getBaseIndices());
}
private Disposable.Composite subscriptions = Disposables.composite();
private void attachFolderListeners() {
synchronized (this) {
subscriptions.dispose();
subscriptions = Disposables.composite();
}
for (File cpe : IClasspathUtil.getBinaryRoots(classpath)) {
if (!cpe.toString().endsWith(".jar")) {
final List<String> rebuildGlobPattern = Arrays.asList(cpe.toString().replace(File.separator, "/") + "/**/*.class");
Disposable disposable = fileObserver.onAnyChange(rebuildGlobPattern, (uri) -> reindex());
subscriptions.add(disposable);
}
}
}
@Override
public void dispose() {
Composite toDispose = subscriptions;
subscriptions = null;
if (toDispose!=null) {
toDispose.dispose();
}
}
private IJavadocProvider createHtmlJavdocProvider(File binaryClasspathRoot) {
CPE cpe = IClasspathUtil.findEntryForBinaryRoot(classpath, binaryClasspathRoot);
return JavaDocProviders.createFor(cpe);
}
@Override
public Optional<URL> sourceContainer(File binaryClasspathRoot) {
CPE cpe = IClasspathUtil.findEntryForBinaryRoot(classpath, binaryClasspathRoot);
return cpe == null ? Optional.empty() : Optional.ofNullable(cpe.getSourceContainerUrl());
}
protected JandexIndex[] getBaseIndices() {
return new JandexIndex[0];
}
@Override
public IType findType(String fqName) {
return javaIndex.get().findType(fqName);
}
@Override
public Flux<Tuple2<IType, Double>> fuzzySearchTypes(String searchTerm, Predicate<IType> typeFilter) {
return javaIndex.get().fuzzySearchTypes(searchTerm, typeFilter);
}
@Override
public Flux<Tuple2<String, Double>> fuzzySearchPackages(String searchTerm) {
return javaIndex.get().fuzzySearchPackages(searchTerm);
}
@Override
public Flux<IType> allSubtypesOf(IType type) {
return javaIndex.get().allSubtypesOf(type);
}
@@ -101,21 +156,35 @@ public abstract class JandexClasspath implements IClasspath {
}
return new File(indexFolder.toString(), jarFile.getName() + "-" + jarFile.lastModified() + ".jdx");
}
protected File getIndexFolder() {
return JandexIndex.getIndexFolder();
}
@Override
public Optional<File> findClasspathResourceContainer(String fqName) {
return javaIndex.get().findClasspathResourceForType(fqName);
}
@Override
public void reindex() {
private void reindex() {
this.javaIndex = Suppliers.synchronizedSupplier(Suppliers.memoize(() -> createIndex()));
}
abstract protected IJavadocProvider createHtmlJavdocProvider(File classpathResource);
@Override
public ImmutableList<String> getClasspathResources() {
return IClasspathUtil.getSourceFolders(classpath)
.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();
}
})
.collect(CollectorUtil.toImmutableList());
}
}

View File

@@ -10,19 +10,23 @@
*******************************************************************************/
package org.springframework.ide.vscode.commons.java;
import java.net.URI;
import java.nio.file.Path;
import org.springframework.ide.vscode.commons.util.FileObserver;
/**
* 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 {
public abstract class AbstractJavaProject extends JavaProject {
final protected Path projectDataCache;
public AbstractJavaProject(Path projectDataCache) {
public AbstractJavaProject(FileObserver fileObserver, URI loactionUri, Path projectDataCache, IClasspath classpath) {
super(fileObserver, loactionUri, classpath);
this.projectDataCache = projectDataCache;
}

View File

@@ -10,6 +10,7 @@
*******************************************************************************/
package org.springframework.ide.vscode.commons.java;
import java.io.File;
import java.nio.file.Path;
import org.springframework.ide.vscode.commons.util.Log;
@@ -20,7 +21,7 @@ public class BootProjectUtil {
try {
IClasspath cp = jp.getClasspath();
if (cp!=null) {
return cp.getClasspathEntryPaths().stream().anyMatch(cpe -> isBootEntry(cpe));
return IClasspathUtil.getBinaryRoots(cp).stream().anyMatch(cpe -> isBootEntry(cpe));
}
} catch (Exception e) {
Log.log(e);
@@ -28,8 +29,8 @@ public class BootProjectUtil {
return false;
}
private static boolean isBootEntry(Path cpe) {
String name = cpe.getFileName().toString();
private static boolean isBootEntry(File cpe) {
String name = cpe.getName();
return name.endsWith(".jar") && name.startsWith("spring-boot");
}

View File

@@ -10,39 +10,50 @@
*******************************************************************************/
package org.springframework.ide.vscode.commons.java;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.commons.languageserver.jdt.ls.Classpath.CPE;
import org.springframework.ide.vscode.commons.util.Log;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableSet;
public class ClasspathData {
public class ClasspathData implements IClasspath {
private static final Logger log = LoggerFactory.getLogger(ClasspathData.class);
final public static ClasspathData EMPTY_CLASSPATH_DATA = new ClasspathData(
null,
Collections.emptySet()
);
final public static ClasspathData EMPTY_CLASSPATH_DATA = new ClasspathData(null, Collections.emptySet(),
Collections.emptySet(), null);
private String name;
private Set<CPE> classpathEntries;
private Set<String> classpathResources;
private String outputFolder;
public ClasspathData() {
}
public ClasspathData() {}
public ClasspathData(String name, Set<CPE> classpathEntries, Set<String> classpathResources, String outputFolder) {
public ClasspathData(String name, Collection<CPE> classpathEntries) {
this.name = name;
this.classpathEntries = classpathEntries;
this.classpathResources = classpathResources;
this.outputFolder = outputFolder;
this.classpathEntries = ImmutableSet.copyOf(classpathEntries);
}
public static ClasspathData from(IClasspath d) {
Collection<CPE> entries = null;
try {
entries = d.getClasspathEntries();
} catch (Exception e) {
log.error("", e);
}
return new ClasspathData(
d.getName(),
entries==null ? ImmutableSet.of() : entries
);
}
@Override
public String getName() {
return name;
}
@@ -51,6 +62,7 @@ public class ClasspathData {
this.name = name;
}
@Override
public Set<CPE> getClasspathEntries() {
return classpathEntries;
}
@@ -59,47 +71,38 @@ public class ClasspathData {
this.classpathEntries = classpathEntries;
}
public Set<String> getClasspathResources() {
return classpathResources;
}
public void setClasspathResources(Set<String> classpathResources) {
this.classpathResources = classpathResources;
}
public String getOutputFolder() {
return outputFolder;
}
public void setOutputFolder(String outputFolder) {
this.outputFolder = outputFolder;
}
public static ClasspathData getEmptyClasspathData() {
return EMPTY_CLASSPATH_DATA;
}
@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;
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((classpathEntries == null) ? 0 : classpathEntries.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
public static ClasspathData from(String name, Collection<CPE> classpathEntries,
Collection<String> classpathResources, Path outputFolder) {
return new ClasspathData(name,
new LinkedHashSet<>(classpathEntries),
new LinkedHashSet<>(classpathResources),
outputFolder==null ? null : outputFolder.toString()
);
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ClasspathData other = (ClasspathData) obj;
if (classpathEntries == null) {
if (other.classpathEntries != null)
return false;
} else if (!classpathEntries.equals(other.classpathEntries))
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}

View File

@@ -11,32 +11,22 @@
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.util.Collections;
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.fasterxml.jackson.databind.ObjectMapper;
public class ClasspathFileBasedCache {
public static final ClasspathFileBasedCache NULL = new ClasspathFileBasedCache(null);
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";
final private File file;
public ClasspathFileBasedCache(File file) {
super();
this.file = file;
@@ -63,7 +53,7 @@ public class ClasspathFileBasedCache {
}
}
}
public boolean isCached() {
return file != null && file.exists();
}
@@ -80,7 +70,7 @@ public class ClasspathFileBasedCache {
return ClasspathData.EMPTY_CLASSPATH_DATA;
}
public void delete() {
if (file != null && file.exists()) {
file.delete();

View File

@@ -0,0 +1,44 @@
/*******************************************************************************
* Copyright (c) 2018 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.commons.java;
import java.io.File;
import java.net.URL;
import java.util.Optional;
import java.util.function.Predicate;
import com.google.common.collect.ImmutableList;
import reactor.core.Disposable;
import reactor.core.publisher.Flux;
import reactor.util.function.Tuple2;
public interface ClasspathIndex extends Disposable {
IType findType(String fqName);
Flux<Tuple2<IType, Double>> fuzzySearchTypes(String searchTerm, Predicate<IType> typeFilter);
Flux<Tuple2<String, Double>> fuzzySearchPackages(String searchTerm);
Flux<IType> allSubtypesOf(IType type);
Optional<File> findClasspathResourceContainer(String fqName);
//Maybe the stuff below is another interface? Something that provides operations
// on classpaths?
Optional<URL> sourceContainer(File binaryClasspathRoot);
/**
* Classpath resources paths relative to the source folder path
* @return classpath resource relative paths
*/
ImmutableList<String> getClasspathResources();
}

View File

@@ -10,14 +10,8 @@
*******************************************************************************/
package org.springframework.ide.vscode.commons.java;
import java.io.File;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Predicate;
import org.springframework.ide.vscode.commons.languageserver.jdt.ls.Classpath.CPE;
import org.springframework.ide.vscode.commons.util.Assert;
@@ -25,44 +19,41 @@ import org.springframework.ide.vscode.commons.util.Assert;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList;
import reactor.core.publisher.Flux;
import reactor.util.function.Tuple2;
/**
*
*
* This wrapper around a classpath manages classpath data from and to a file-based cache (e.g. ".sts4-cache/classpath-data.json") with classpath data obtained
* from a project (e.g., maven or gradle project) through an "update" operation.
*
* from a project (e.g., maven or gradle project) through an "update" operation.
*
* The cached classpath data is written to the file and loaded from it when instance of this classpath is created
*
*
* NOTE: Classpath data may not be available until an actual update is requested on this wrapper.
*
*
* As the wrapper is a classpath itself ,it delegates to the underlying classpath for classpath operations (e.g. getting classpath entries, resources, etc..). However, the data may not
* be available until update is performed.
*
*
* 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>
*
*
*
*
* 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 class DelegatingCachedClasspath implements IClasspath {
private AtomicReference<ClasspathData> cachedData;
private Callable<T> classpathCreator;
private AtomicReference<T> cachedClasspath;
private Callable<IClasspath> classpathCreator;
private AtomicReference<IClasspath> cachedClasspath;
private final ClasspathFileBasedCache fileBasedCache;
public DelegatingCachedClasspath(Callable<T> delegateCreator, ClasspathFileBasedCache fileCache) {
public DelegatingCachedClasspath(Callable<IClasspath> delegateCreator, ClasspathFileBasedCache fileCache) {
super();
Assert.isLegal(delegateCreator != null);
this.fileBasedCache = fileCache != null ? fileCache : ClasspathFileBasedCache.NULL;
@@ -74,8 +65,8 @@ public class DelegatingCachedClasspath<T extends IClasspath> implements IClasspa
private ClasspathData loadFileBasedCache(ClasspathFileBasedCache fileCache) {
return fileCache != null ? fileCache.load() : ClasspathData.EMPTY_CLASSPATH_DATA;
}
public T delegate() {
public IClasspath delegate() {
return cachedClasspath.get();
}
@@ -84,26 +75,15 @@ public class DelegatingCachedClasspath<T extends IClasspath> implements IClasspa
return cachedData.get().getName();
}
@Override
public Path getOutputFolder() {
String of = cachedData.get().getOutputFolder();
return of == null ? null : Paths.get(of);
}
@Override
public ImmutableList<CPE> getClasspathEntries() throws Exception {
return ImmutableList.copyOf(cachedData.get().getClasspathEntries());
}
@Override
public ImmutableList<String> getClasspathResources() {
return ImmutableList.copyOf(cachedData.get().getClasspathResources());
}
public boolean isCached() {
return fileBasedCache.isCached();
}
public boolean update() throws Exception {
try {
final ClasspathData newData = createClasspathData();
@@ -119,74 +99,20 @@ public class DelegatingCachedClasspath<T extends IClasspath> implements IClasspa
throw e;
}
}
@Override
public boolean exists() {
T t = cachedClasspath.get();
return t != null && t.exists();
}
@Override
public IType findType(String fqName) {
T t = cachedClasspath.get();
return t == null ? null : t.findType(fqName);
}
@Override
public Flux<Tuple2<IType, Double>> fuzzySearchTypes(String searchTerm, Predicate<IType> typeFilter) {
T t = cachedClasspath.get();
return t == null ? Flux.empty() : t.fuzzySearchTypes(searchTerm, typeFilter);
}
@Override
public Flux<Tuple2<String, Double>> fuzzySearchPackages(String searchTerm) {
T t = cachedClasspath.get();
return t == null ? Flux.empty() : t.fuzzySearchPackages(searchTerm);
}
@Override
public Flux<IType> allSubtypesOf(IType type) {
T t = cachedClasspath.get();
return t == null ? Flux.empty() : t.allSubtypesOf(type);
}
@Override
public ClasspathData createClasspathData() throws Exception {
T newDelegate = classpathCreator.call();
private ClasspathData createClasspathData() throws Exception {
IClasspath newDelegate = classpathCreator.call();
cachedClasspath.set(newDelegate);
if (newDelegate != null) {
ClasspathData data = newDelegate.createClasspathData();
ClasspathData data = createClasspathData(newDelegate);
if (data != null) {
return data;
}
}
}
return ClasspathData.EMPTY_CLASSPATH_DATA;
}
@Override
public ImmutableList<String> getSourceFolders() {
T t = cachedClasspath.get();
return t == null ? ImmutableList.of() : t.getSourceFolders();
private ClasspathData createClasspathData(IClasspath d) {
return ClasspathData.from(d);
}
@Override
public Optional<File> findClasspathResourceContainer(String fqName) {
T t = cachedClasspath.get();
return t == null ? Optional.empty() : t.findClasspathResourceContainer(fqName);
}
@Override
public void reindex() {
T t = cachedClasspath.get();
if (t != null) {
t.reindex();
}
}
@Override
public Optional<URL> sourceContainer(File classpathResource) {
T t = cachedClasspath.get();
return t == null ? Optional.empty() : t.sourceContainer(classpathResource);
}
}

View File

@@ -10,88 +10,30 @@
*******************************************************************************/
package org.springframework.ide.vscode.commons.java;
import java.io.File;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Optional;
import java.util.function.Predicate;
import org.springframework.ide.vscode.commons.languageserver.jdt.ls.Classpath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.commons.languageserver.jdt.ls.Classpath.CPE;
import com.google.common.collect.ImmutableList;
import reactor.core.publisher.Flux;
import reactor.util.function.Tuple2;
/**
* Classpath for a Java artifact
*
*
* @author Kris De Volder
* @author Alex Boyko
*
*/
public interface IClasspath {
String getName();
boolean exists();
IType findType(String fqName);
Flux<Tuple2<IType, Double>> fuzzySearchTypes(String searchTerm, Predicate<IType> typeFilter);
Flux<Tuple2<String, Double>> fuzzySearchPackages(String searchTerm);
Flux<IType> allSubtypesOf(IType type);
Path getOutputFolder();
public static final Logger log = LoggerFactory.getLogger(IClasspath.class);
String getName();
/**
* Classpath entries paths
*
*
* @return collection of classpath entries in a form file/folder paths
* @throws Exception
*/
Collection<CPE> getClasspathEntries() throws Exception;
/**
* Classpath resources paths relative to the source folder path
* @return classpath resource relative paths
*/
ImmutableList<String> getClasspathResources();
ImmutableList<String> getSourceFolders();
Optional<File> findClasspathResourceContainer(String fqName);
ClasspathData createClasspathData() throws Exception;
void reindex();
Optional<URL> sourceContainer(File classpathResource);
@Deprecated
default Collection<Path> getClasspathEntryPaths() throws Exception {
LinkedHashSet<Path> entries = new LinkedHashSet<>();
for (CPE cpe : this.getClasspathEntries()) {
if (Classpath.ENTRY_KIND_BINARY.equals(cpe.getKind())) {
entries.add(Paths.get(cpe.getPath()));
} else if (Classpath.ENTRY_KIND_SOURCE.equals(cpe.getKind())) {
String of = cpe.getOutputFolder();
if (of!=null) {
entries.add(Paths.get(cpe.getOutputFolder()));
} else {
Path op = getOutputFolder();
if (op!=null) {
entries.add(op);
}
}
}
}
return ImmutableList.copyOf(entries);
}
}

View File

@@ -0,0 +1,118 @@
/*******************************************************************************
* Copyright (c) 2018 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.commons.java;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.commons.languageserver.jdt.ls.Classpath;
import org.springframework.ide.vscode.commons.languageserver.jdt.ls.Classpath.CPE;
import org.springframework.ide.vscode.commons.util.Assert;
import com.google.common.collect.ImmutableList;
public class IClasspathUtil {
private static final Logger log = LoggerFactory.getLogger(IClasspath.class);
public static CPE findEntryForBinaryRoot(IClasspath cp, File binaryClasspathtRoot) {
try {
for (CPE cpe : cp.getClasspathEntries()) {
if (correspondsToBinaryLocation(cpe, binaryClasspathtRoot)) {
return cpe;
}
}
} catch (Exception e) {
log.error("", e);
}
return null;
}
public static List<File> getBinaryRoots(IClasspath cp) {
ImmutableList.Builder<File> roots = ImmutableList.builder();
try {
for (CPE cpe : cp.getClasspathEntries()) {
File loc = binaryLocation(cpe);
if (loc!=null) {
roots.add(loc);
}
}
} catch (Exception e) {
log.error("", e);
}
return roots.build();
}
private static boolean correspondsToBinaryLocation(CPE cpe, File classpathEntryFile) {
classpathEntryFile = canonicalFile(classpathEntryFile);
File canonicalFile = binaryLocation(cpe);
return Objects.equals(canonicalFile, classpathEntryFile);
}
private static File binaryLocation(CPE cpe) {
switch (cpe.getKind()) {
case Classpath.ENTRY_KIND_BINARY:
return canonicalFile(cpe.getPath());
case Classpath.ENTRY_KIND_SOURCE:
return canonicalFile(cpe.getOutputFolder());
default:
throw new IllegalStateException("Missing switch case?");
}
}
private static File canonicalFile(String _f) {
if (_f!=null) {
File f = new File(_f);
return canonicalFile(f);
}
return null;
}
private static File canonicalFile(File f) {
try {
return f.getCanonicalFile();
} catch (IOException e) {
return f.getAbsoluteFile();
}
}
public static Stream<File> getSourceFolders(IClasspath classpath) {
try {
if (classpath != null) {
return classpath.getClasspathEntries().stream().filter(Classpath::isSource)
.map(cpe -> new File(cpe.getPath()));
}
} catch (Exception e) {
log.error("", e);
}
return Stream.empty();
}
public static Stream<File> getOutputFolders(IClasspath classpath) {
try {
return classpath.getClasspathEntries().stream()
.filter(Classpath::isSource)
.map(cpe -> new File(cpe.getOutputFolder()));
} catch (Exception e) {
log.error("", e);
}
return Stream.empty();
}
}

View File

@@ -10,27 +10,57 @@
*******************************************************************************/
package org.springframework.ide.vscode.commons.java;
import java.io.File;
import java.net.URL;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;
import org.springframework.ide.vscode.commons.javadoc.IJavadoc;
import reactor.core.publisher.Flux;
import reactor.util.function.Tuple2;
public interface IJavaProject extends IJavaElement {
final static String PROJECT_CACHE_FOLDER = ".sts4-cache";
IClasspath getClasspath();
ClasspathIndex getIndex();
@Override
default String getElementName() {
return getClasspath().getName();
}
@Override
default IJavadoc getJavaDoc() {
return null;
default IType findType(String fqName) {
return getIndex().findType(fqName);
}
@Override
default boolean exists() {
return getClasspath().exists();
default Flux<IType> allSubtypesOf(IType targetType) {
return getIndex().allSubtypesOf(targetType);
}
default Flux<Tuple2<IType, Double>> fuzzySearchTypes(String searchTerm, Predicate<IType> typeFilter) {
return getIndex().fuzzySearchTypes(searchTerm, typeFilter);
}
default Optional<URL> sourceContainer(File classpathResource) {
return getIndex().sourceContainer(classpathResource);
}
default List<String> getClasspathResources() {
return getIndex().getClasspathResources();
}
default Optional<File> findClasspathResourceContainer(String fqName) {
return getIndex().findClasspathResourceContainer(fqName);
}
@Override
default IJavadoc getJavaDoc() {
//?? why is this here ??
return null;
}
}

View File

@@ -0,0 +1,67 @@
/*******************************************************************************
* Copyright (c) 2018 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.commons.java;
import java.io.File;
import java.net.URI;
import org.springframework.ide.vscode.commons.jandex.JandexClasspath;
import org.springframework.ide.vscode.commons.util.FileObserver;
import reactor.core.Disposable;
public class JavaProject implements IJavaProject, Disposable {
private final IClasspath classpath;
private ClasspathIndex index;
private URI uri;
private final FileObserver fileObserver;
public JavaProject(FileObserver fileObserver, URI uri, IClasspath classpath) {
super();
this.classpath = classpath;
this.fileObserver = fileObserver;
}
@Override
public IClasspath getClasspath() {
return classpath;
}
@Override
public synchronized ClasspathIndex getIndex() {
if (index==null) {
index = new JandexClasspath(classpath, fileObserver);
}
return index;
}
public URI getLocationUri() {
return uri;
}
@Override
public void dispose() {
Disposable toDispose = null;
synchronized (this) {
toDispose = index;
index = null;
}
if (toDispose!=null) {
toDispose.dispose();
}
}
@Override
public boolean exists() {
return new File(uri).exists();
}
}

View File

@@ -0,0 +1,39 @@
/*******************************************************************************
* Copyright (c) 2018 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.commons.javadoc;
import java.net.URL;
import org.springframework.ide.vscode.commons.java.IJavadocProvider;
import org.springframework.ide.vscode.commons.languageserver.jdt.ls.Classpath;
import org.springframework.ide.vscode.commons.languageserver.jdt.ls.Classpath.CPE;
public class JavaDocProviders {
public static IJavadocProvider createFor(CPE classpathEntry) {
if (classpathEntry!=null && classpathEntry.getJavadocContainerUrl() != null) {
URL containerUrl = classpathEntry.getJavadocContainerUrl();
TypeUrlProviderFromContainerUrl urlProvider = isJarUrl(containerUrl)
? TypeUrlProviderFromContainerUrl.JAR_JAVADOC_URL_PROVIDER
: TypeUrlProviderFromContainerUrl.JAVADOC_FOLDER_URL_SUPPLIER;
return new HtmlJavadocProvider(
type -> urlProvider.url(classpathEntry.getJavadocContainerUrl(), type.getFullyQualifiedName())
);
}
return null;
}
private static boolean isJarUrl(URL containerUrl) {
return containerUrl.toString().endsWith(".jar");
}
}

View File

@@ -15,44 +15,44 @@ import java.net.URL;
import java.nio.file.Paths;
@FunctionalInterface
public interface SourceUrlProviderFromSourceContainer {
public interface TypeUrlProviderFromContainerUrl {
static String extractTopLevelType(String fqName) {
int innerTypeIdx = fqName.indexOf('$');
return innerTypeIdx > 0 ? fqName.substring(0, innerTypeIdx) : fqName;
}
public static final SourceUrlProviderFromSourceContainer JAR_SOURCE_URL_PROVIDER = (sourceContainerUrl, fqName) -> {
StringBuilder sourceUrlStr = new StringBuilder();
sourceUrlStr.append("jar:");
sourceUrlStr.append(sourceContainerUrl);
sourceUrlStr.append("!");
sourceUrlStr.append('/');
sourceUrlStr.append(extractTopLevelType(fqName).replaceAll("\\.", "/"));
sourceUrlStr.append(".java");
return new URL(sourceUrlStr.toString());
public static final TypeUrlProviderFromContainerUrl JAR_SOURCE_URL_PROVIDER = (jarSourceUrl, fqName) -> {
StringBuilder urlStr = new StringBuilder();
urlStr.append("jar:");
urlStr.append(jarSourceUrl);
urlStr.append("!");
urlStr.append('/');
urlStr.append(extractTopLevelType(fqName).replaceAll("\\.", "/"));
urlStr.append(".java");
return new URL(urlStr.toString());
};
public static final SourceUrlProviderFromSourceContainer SOURCE_FOLDER_URL_SUPPLIER = (sourceContainerUrl, fqName) -> {
public static final TypeUrlProviderFromContainerUrl SOURCE_FOLDER_URL_SUPPLIER = (sourceContainerUrl, fqName) -> {
return Paths.get(sourceContainerUrl.toURI()).resolve(extractTopLevelType(fqName).replaceAll("\\.", "/") + ".java").toUri().toURL();
};
public static final SourceUrlProviderFromSourceContainer JAR_JAVADOC_URL_PROVIDER = (javadocContainerUrl, fqName) -> {
StringBuilder sourceUrlStr = new StringBuilder();
sourceUrlStr.append("jar:");
sourceUrlStr.append(javadocContainerUrl);
sourceUrlStr.append("!");
sourceUrlStr.append('/');
public static final TypeUrlProviderFromContainerUrl JAR_JAVADOC_URL_PROVIDER = (javadocContainerUrl, fqName) -> {
StringBuilder urlStr = new StringBuilder();
urlStr.append("jar:");
urlStr.append(javadocContainerUrl);
urlStr.append("!");
urlStr.append('/');
// Inner classes are in separate Top.Nesting1.Nesting2.Nesting3.MyType.html files
sourceUrlStr.append(fqName.replaceAll("\\.", "/").replaceAll("\\$", "."));
sourceUrlStr.append(".html");
return new URL(sourceUrlStr.toString());
urlStr.append(fqName.replaceAll("\\.", "/").replaceAll("\\$", "."));
urlStr.append(".html");
return new URL(urlStr.toString());
};
public static final SourceUrlProviderFromSourceContainer JAVADOC_FOLDER_URL_SUPPLIER = (sourceContainerUrl, fqName) -> {
String urlStr = sourceContainerUrl.toString();
public static final TypeUrlProviderFromContainerUrl JAVADOC_FOLDER_URL_SUPPLIER = (javadocContainerUrl, fqName) -> {
String urlStr = javadocContainerUrl.toString();
StringBuilder sb = new StringBuilder(urlStr);
if (!urlStr.endsWith("/")) {
sb.append('/');
@@ -62,6 +62,6 @@ public interface SourceUrlProviderFromSourceContainer {
return new URL(sb.toString());
};
URL sourceUrl(URL sourceContainerUrl, String fqName) throws Exception;
URL url(URL containerUrl, String fqName) throws Exception;
}

View File

@@ -60,14 +60,6 @@ public abstract class AbstractFileToProjectCache<P extends IJavaProject> extends
notifyProjectDeleted(project);
dispose();
}));
Path outputFolder = project.getClasspath().getOutputFolder();
if (outputFolder != null) {
final List<String> rebuildGlobPattern = Arrays.asList(outputFolder.toString().replace(File.separator, "/") + "/**/*.class");
subscriptions.add(getFileObserver().onFileChanged(rebuildGlobPattern, (uri) -> project.getClasspath().reindex()));
subscriptions.add(getFileObserver().onFileCreated(rebuildGlobPattern, (uri) -> project.getClasspath().reindex()));
subscriptions.add(getFileObserver().onFileDeleted(rebuildGlobPattern, (uri) -> project.getClasspath().reindex()));
}
}
private void dispose() {

View File

@@ -0,0 +1,103 @@
/*******************************************************************************
* Copyright (c) 2018 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.commons.jandex;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import java.io.File;
import java.util.function.BiConsumer;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.springframework.ide.vscode.commons.java.ClasspathData;
import org.springframework.ide.vscode.commons.languageserver.jdt.ls.Classpath.CPE;
import org.springframework.ide.vscode.commons.util.BasicFileObserver;
import org.springframework.ide.vscode.commons.util.FileObserver;
import com.google.common.collect.ImmutableList;
import com.google.common.io.Files;
public class JandexClasspathTest {
@Rule public TemporaryFolder folder = new TemporaryFolder();
class TestProject {
String name;
File root;
File testClassesFolder;
File outputFolder;
BasicFileObserver fileObserver = new BasicFileObserver();
TestProject(String name) throws Exception {
this.name = name;
this.root = new File(JandexClasspathTest.class.getResource("/" + name ).toURI());
testClassesFolder = new File(root, "bin");
this.outputFolder = folder.newFolder();
}
void createClass(String fqName) throws Exception {
String relativePath = fqName.replace('.', '/')+".class";
File classFile = new File(testClassesFolder, relativePath);
File target = new File(outputFolder, relativePath);
target.getParentFile().mkdirs();
Files.copy(classFile, target);
fileObserver.notifyFileCreated(target.toURI().toString());
}
ClasspathData getClasspath() {
return new ClasspathData(name, ImmutableList.of(
CPE.source(new File(root, "src"), outputFolder)
));
}
JandexClasspath getJandexClasspath() {
return new JandexClasspath(getClasspath(), fileObserver);
}
public void deleteClass(String fqName, BiConsumer<BasicFileObserver, String> eventNoficator) {
String relativePath = fqName.replace('.', '/')+".class";
File classFile = new File(outputFolder, relativePath);
classFile.delete();
eventNoficator.accept(fileObserver, classFile.toURI().toString());
}
public void deleteClass(String fqName) {
deleteClass(fqName, (fileObserver, path) -> fileObserver.notifyFileDeleted(path));
}
}
@Test public void classfileChangesShouldTriggerReindexing() throws Exception {
TestProject project = new TestProject("simple-java-project");
project.createClass("demo.Hello");
JandexClasspath subject = project.getJandexClasspath();
assertNotNull(subject.findType("demo.Hello"));
assertNull(subject.findType("demo.Goodbye"));
project.createClass("demo.Goodbye");
assertNotNull(subject.findType("demo.Hello"));
assertNotNull(subject.findType("demo.Goodbye"));
project.deleteClass("demo.Hello");
assertNull(subject.findType("demo.Hello"));
assertNotNull(subject.findType("demo.Goodbye"));
project.deleteClass("demo.Goodbye", (fileObserver, deletedFile) -> fileObserver.notifyFileChanged(deletedFile));
assertNull(subject.findType("demo.Hello"));
assertNull(subject.findType("demo.Goodbye"));
}
}

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<classpathentry kind="src" path="src"/>
<classpathentry kind="output" path="bin"/>
</classpath>

View File

@@ -0,0 +1,2 @@
!bin/
!*.class

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>simple-java-project</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

View File

@@ -0,0 +1,11 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=1.8
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.source=1.8

View File

@@ -0,0 +1,5 @@
package demo;
public class Goodbye {
}

View File

@@ -0,0 +1,5 @@
package demo;
public class Hello {
}