|
|
|
|
@@ -4,11 +4,14 @@ import java.io.File;
|
|
|
|
|
import java.io.FileInputStream;
|
|
|
|
|
import java.io.IOException;
|
|
|
|
|
import java.io.InputStream;
|
|
|
|
|
import java.nio.file.Path;
|
|
|
|
|
import java.util.Arrays;
|
|
|
|
|
import java.util.Collection;
|
|
|
|
|
import java.util.HashMap;
|
|
|
|
|
import java.util.Iterator;
|
|
|
|
|
import java.util.List;
|
|
|
|
|
import java.util.Map;
|
|
|
|
|
import java.util.Optional;
|
|
|
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
|
|
|
import java.util.concurrent.ExecutionException;
|
|
|
|
|
import java.util.stream.Collectors;
|
|
|
|
|
import java.util.stream.Stream;
|
|
|
|
|
@@ -24,7 +27,9 @@ import org.springframework.ide.vscode.commons.java.IField;
|
|
|
|
|
import org.springframework.ide.vscode.commons.java.IJavadocProvider;
|
|
|
|
|
import org.springframework.ide.vscode.commons.java.IMethod;
|
|
|
|
|
import org.springframework.ide.vscode.commons.java.IType;
|
|
|
|
|
import org.springframework.ide.vscode.commons.java.IJavaProject.TypeFilter;
|
|
|
|
|
import org.springframework.ide.vscode.commons.javadoc.IJavadoc;
|
|
|
|
|
import org.springframework.ide.vscode.commons.util.FuzzyMatcher;
|
|
|
|
|
import org.springframework.ide.vscode.commons.util.Log;
|
|
|
|
|
|
|
|
|
|
import com.google.common.base.Supplier;
|
|
|
|
|
@@ -32,17 +37,13 @@ import com.google.common.base.Suppliers;
|
|
|
|
|
import com.google.common.cache.Cache;
|
|
|
|
|
import com.google.common.cache.CacheBuilder;
|
|
|
|
|
|
|
|
|
|
import reactor.core.publisher.Flux;
|
|
|
|
|
import reactor.core.scheduler.Schedulers;
|
|
|
|
|
import reactor.util.function.Tuple2;
|
|
|
|
|
import reactor.util.function.Tuples;
|
|
|
|
|
|
|
|
|
|
public class JandexIndex {
|
|
|
|
|
|
|
|
|
|
private static class Entry<K, V> {
|
|
|
|
|
K key;
|
|
|
|
|
V value;
|
|
|
|
|
Entry(K key, V value) {
|
|
|
|
|
this.key = key;
|
|
|
|
|
this.value = value;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@FunctionalInterface
|
|
|
|
|
public static interface IndexFileFinder {
|
|
|
|
|
File findIndexFile(File jarFile);
|
|
|
|
|
@@ -77,10 +78,12 @@ public class JandexIndex {
|
|
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
private Supplier<List<Entry<File, IndexView>>> index;
|
|
|
|
|
private Map<File, Supplier<Optional<IndexView>>> index;
|
|
|
|
|
|
|
|
|
|
private JavadocProviderFactory javadocProviderFactory;
|
|
|
|
|
|
|
|
|
|
private Map<File, Supplier<List<Tuple2<String, IType>>>> knownTypes;
|
|
|
|
|
|
|
|
|
|
private Cache<File, IJavadocProvider> javadocProvidersCache = CacheBuilder.newBuilder().build();
|
|
|
|
|
|
|
|
|
|
private JandexIndex[] baseIndex;
|
|
|
|
|
@@ -93,26 +96,25 @@ public class JandexIndex {
|
|
|
|
|
return javadocProviderFactory;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public JandexIndex(Stream<Path> classpathEntries, IndexFileFinder indexFileFinder, JavadocProviderFactory javadocProviderFactory, JandexIndex... baseIndex) {
|
|
|
|
|
public JandexIndex(Collection<File> classpathEntries, IndexFileFinder indexFileFinder, JavadocProviderFactory javadocProviderFactory, JandexIndex... baseIndex) {
|
|
|
|
|
this.baseIndex = baseIndex;
|
|
|
|
|
index = Suppliers.memoize(() -> buildIndex(classpathEntries, indexFileFinder).collect(Collectors.toList()));
|
|
|
|
|
this.index = new ConcurrentHashMap<>();
|
|
|
|
|
this.knownTypes = new HashMap<>();
|
|
|
|
|
this.javadocProviderFactory = javadocProviderFactory;
|
|
|
|
|
classpathEntries.forEach(file -> {
|
|
|
|
|
index.put(file, Suppliers.memoize(() -> createIndex(file, indexFileFinder)));
|
|
|
|
|
knownTypes.put(file, Suppliers.memoize(() -> getKnownTypesStream(file).collect(Collectors.toList())));
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Stream<Entry<File, IndexView>> buildIndex(Stream<Path> classpathEntries, IndexFileFinder indexFileFinder) {
|
|
|
|
|
return classpathEntries
|
|
|
|
|
.map(entry -> entry.toFile())
|
|
|
|
|
.map(file -> {
|
|
|
|
|
Optional<IndexView> index = Optional.empty();
|
|
|
|
|
if (file.isFile() && file.getName().endsWith(".jar")) {
|
|
|
|
|
index = indexJar(file, indexFileFinder);
|
|
|
|
|
} else if (file.isDirectory()) {
|
|
|
|
|
index = indexFolder(file);
|
|
|
|
|
}
|
|
|
|
|
return new Entry<>(file, index);
|
|
|
|
|
})
|
|
|
|
|
.filter(e -> e.value.isPresent())
|
|
|
|
|
.map(e -> new Entry<>(e.key, e.value.get()));
|
|
|
|
|
private Optional<IndexView> createIndex(File file, IndexFileFinder indexFileFinder) {
|
|
|
|
|
if (file.isFile() && file.getName().endsWith(".jar")) {
|
|
|
|
|
return indexJar(file, indexFileFinder);
|
|
|
|
|
} else if (file.isDirectory()) {
|
|
|
|
|
return indexFolder(file);
|
|
|
|
|
} else {
|
|
|
|
|
return Optional.empty();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static Optional<IndexView> indexFolder(File folder) {
|
|
|
|
|
@@ -181,7 +183,7 @@ public class JandexIndex {
|
|
|
|
|
public IType findType(String fqName) {
|
|
|
|
|
return getClassByName(DotName.createSimple(fqName));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
IType getClassByName(DotName fqName) {
|
|
|
|
|
// First look for type in the base index array
|
|
|
|
|
return (baseIndex == null ? Stream.<IType>empty()
|
|
|
|
|
@@ -191,24 +193,71 @@ public class JandexIndex {
|
|
|
|
|
.filter(type -> type != null)
|
|
|
|
|
.findFirst()
|
|
|
|
|
// If not found look at indices owned by this JandexIndex instance
|
|
|
|
|
.orElseGet(() -> index.get().stream()
|
|
|
|
|
.map(e -> new Entry<>(e.key, e.value.getClassByName(fqName)))
|
|
|
|
|
.filter(e -> e.value != null)
|
|
|
|
|
.orElseGet(() -> streamOfIndices()
|
|
|
|
|
.map(e -> Tuples.of(e.getT1(), e.getT2().getClassByName(fqName)))
|
|
|
|
|
.filter(e -> e.getT2() != null)
|
|
|
|
|
.map(e -> createType(e))
|
|
|
|
|
.findFirst()
|
|
|
|
|
.orElse(null));
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private IType createType(Entry<File, ClassInfo> match) {
|
|
|
|
|
File classpathResource = match.key;
|
|
|
|
|
private IType createType(Tuple2<File, ClassInfo> match) {
|
|
|
|
|
File classpathResource = match.getT1();
|
|
|
|
|
IJavadocProvider javadocProvider = null;
|
|
|
|
|
try {
|
|
|
|
|
javadocProvider = javadocProvidersCache.get(match.key, () -> javadocProviderFactory == null ? ABSENT_JAVADOC_PROVIDER : javadocProviderFactory.createJavadocProvider(classpathResource));
|
|
|
|
|
javadocProvider = javadocProvidersCache.get(classpathResource, () -> javadocProviderFactory == null ? ABSENT_JAVADOC_PROVIDER : javadocProviderFactory.createJavadocProvider(classpathResource));
|
|
|
|
|
} catch (ExecutionException e) {
|
|
|
|
|
Log.log(e);
|
|
|
|
|
}
|
|
|
|
|
return Wrappers.wrap(this, match.value, javadocProvider);
|
|
|
|
|
return Wrappers.wrap(this, match.getT2(), javadocProvider);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private Stream<Tuple2<File, IndexView>> streamOfIndices() {
|
|
|
|
|
return index.entrySet().parallelStream().map(e -> Tuples.of(e.getKey(), e.getValue().get())).filter(t -> t.getT2().isPresent()).map(t -> Tuples.of(t.getT1(), t.getT2().get()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Stream<Tuple2<String, IType>> getKnownTypesStream(File file) {
|
|
|
|
|
Optional<IndexView> indexView = index.get(file).get();
|
|
|
|
|
if (indexView.isPresent()) {
|
|
|
|
|
return indexView.get().getKnownClasses().parallelStream().map(info -> Tuples.of(info.name().toString(), createType(Tuples.of(file, info))));
|
|
|
|
|
}
|
|
|
|
|
return Stream.empty();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Flux<Tuple2<IType, Double>> fuzzySearchTypes(String searchTerm, TypeFilter typeFilter) {
|
|
|
|
|
Flux<Tuple2<IType, Double>> flux = Flux.fromIterable(knownTypes.values())
|
|
|
|
|
.publishOn(Schedulers.parallel())
|
|
|
|
|
.flatMap(s -> Flux.fromIterable(s.get()))
|
|
|
|
|
.filter(t -> typeFilter == null || typeFilter.accept(t.getT2()))
|
|
|
|
|
.map(t -> Tuples.of(t.getT2(), FuzzyMatcher.matchScore(searchTerm, t.getT1())))
|
|
|
|
|
.filter(t -> t.getT2() != 0.0);
|
|
|
|
|
if (baseIndex == null) {
|
|
|
|
|
return flux;
|
|
|
|
|
} else {
|
|
|
|
|
return Flux.merge(flux, Flux.fromArray(baseIndex).flatMap(index -> index.fuzzySearchTypes(searchTerm, typeFilter)));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Flux<IType> allSubtypesOf(IType type) {
|
|
|
|
|
DotName name = DotName.createSimple(type.getFullyQualifiedName());
|
|
|
|
|
Flux<IType> flux = Flux.fromIterable(index.keySet())
|
|
|
|
|
.publishOn(Schedulers.parallel())
|
|
|
|
|
.flatMap(file -> {
|
|
|
|
|
Optional<IndexView> optional = index.get(file).get();
|
|
|
|
|
if (optional.isPresent()) {
|
|
|
|
|
return Flux.fromIterable(type.isInterface() ? optional.get().getAllKnownImplementors(name) : optional.get().getAllKnownSubclasses(name))
|
|
|
|
|
.publishOn(Schedulers.parallel())
|
|
|
|
|
.map(info -> createType(Tuples.of(file, info)));
|
|
|
|
|
} else {
|
|
|
|
|
return Flux.empty();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
if (baseIndex == null) {
|
|
|
|
|
return flux;
|
|
|
|
|
} else {
|
|
|
|
|
return Flux.merge(flux, Flux.fromArray(baseIndex).flatMap(index -> index.allSubtypesOf(type)));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|