PT #164216122: JDT LS search performance improvement 2

This commit is contained in:
BoykoAlex
2019-03-05 17:33:48 -05:00
parent a72fd15878
commit dc1e2708f9
28 changed files with 1331 additions and 876 deletions

View File

@@ -91,7 +91,7 @@ public class JavaLangugeClientTest {
.javaSearchTypes(new JavaSearchParams(project.getLocationURI().toString(), "util.Map", true, true))
.get(100, TimeUnit.SECONDS);
assertNotNull(data);
assertTrue(data.size() > 500);
assertEquals(500, data.size());
List<String> closeMatches = data.stream().filter(t -> t.contains("util.Map")).collect(Collectors.toList());
assertEquals(2, closeMatches.size());
assertNotNull(closeMatches.stream().filter(t -> "java.util.Map".equals(t)).findFirst().orElse(null));
@@ -116,9 +116,9 @@ public class JavaLangugeClientTest {
.javaSearchTypes(new JavaSearchParams(project.getLocationURI().toString(), "", true, false))
.get(1000, TimeUnit.SECONDS);
assertNotNull(data);
assertTrue(data.size() > 10000);
assertEquals(500, data.size());
}
@Test
public void searchPackagesIncludingSysLibs() throws Exception {
List<String> packages = client.javaSearchPackages(new JavaSearchParams(project.getLocationURI().toString(), "java.lang", true, true)).get(30, TimeUnit.SECONDS);
@@ -136,7 +136,7 @@ public class JavaLangugeClientTest {
@Test
public void searchAllPackagesExcludingSysLibs() throws Exception {
List<String> packages = client.javaSearchPackages(new JavaSearchParams(project.getLocationURI().toString(), "", true, false)).get(30, TimeUnit.SECONDS);
assertTrue(packages.size() > 1000);
assertEquals(500, packages.size());
}
@Test

View File

@@ -75,7 +75,7 @@ import org.springframework.ide.vscode.commons.protocol.java.TypeData;
import org.springframework.tooling.jdt.ls.commons.Logger;
import org.springframework.tooling.jdt.ls.commons.classpath.ReusableClasspathListenerHandler;
import org.springframework.tooling.jdt.ls.commons.java.JavaData;
import org.springframework.tooling.jdt.ls.commons.java.JavaSearch;
import org.springframework.tooling.jdt.ls.commons.java.JavaFluxSearch;
import org.springframework.tooling.jdt.ls.commons.java.TypeHierarchy;
import org.springframework.tooling.jdt.ls.commons.javadoc.JavadocUtils;
import org.springframework.tooling.jdt.ls.commons.resources.ResourceUtils;
@@ -126,7 +126,7 @@ public class STS4LanguageClientImpl extends LanguageClientImpl implements STS4La
final private JavaData javaData = new JavaData(STS4LanguageClientImpl::label , Logger.forEclipsePlugin(LanguageServerCommonsActivator::getInstance));
final private JavaSearch javaSearch = new JavaSearch(Logger.forEclipsePlugin(LanguageServerCommonsActivator::getInstance));
final private JavaFluxSearch javaFluxSearch = new JavaFluxSearch(Logger.forEclipsePlugin(LanguageServerCommonsActivator::getInstance));
final private TypeHierarchy typeHierarchy = new TypeHierarchy(Logger.forEclipsePlugin(LanguageServerCommonsActivator::getInstance), javaData);
@@ -443,11 +443,11 @@ public class STS4LanguageClientImpl extends LanguageClientImpl implements STS4La
public CompletableFuture<List<String>> javaSearchTypes(JavaSearchParams params) {
return CompletableFuture.supplyAsync(() -> {
try {
return javaSearch.fuzzySearchTypes(URI.create(params.getProjectUri()), params.getTerm(),
params.isIncludeBinaries(), params.isIncludeSystemLibs()).collect(Collectors.toList());
List<String> types = javaFluxSearch.fuzzySearchTypes(params);
return types;
} catch (Exception e) {
LanguageServerCommonsActivator.logError(e,
"Failed to search type with term '" + params.getTerm() + "' in project " + params.getProjectUri());
LanguageServerCommonsActivator.logError(e, "Failed to search type with term '" + params.getTerm()
+ "' in project " + params.getProjectUri());
return Collections.emptyList();
}
});
@@ -457,10 +457,10 @@ public class STS4LanguageClientImpl extends LanguageClientImpl implements STS4La
public CompletableFuture<List<String>> javaSearchPackages(JavaSearchParams params) {
return CompletableFuture.supplyAsync(() -> {
try {
return javaSearch.fuzzySearchPackages(URI.create(params.getProjectUri()), params.getTerm(),
params.isIncludeBinaries(), params.isIncludeSystemLibs()).collect(Collectors.toList());
return javaFluxSearch.fuzzySearchPackages(params);
} catch (Exception e) {
LanguageServerCommonsActivator.logError(e, "Failed to search package with term '" + params.getTerm() +"' in project " + params.getProjectUri());
LanguageServerCommonsActivator.logError(e, "Failed to search package with term '" + params.getTerm()
+ "' in project " + params.getProjectUri());
return Collections.emptyList();
}
});

View File

@@ -43,6 +43,8 @@ import reactor.util.function.Tuples;
public class JdtLsIndex implements ClasspathIndex {
private static final long SEARCH_TIMEOUT = 700;
private static final Logger log = LoggerFactory.getLogger(JdtLsIndex.class);
private final STS4LanguageClient client;
@@ -90,7 +92,7 @@ public class JdtLsIndex implements ClasspathIndex {
@Override
public Flux<Tuple2<String, Double>> fuzzySearchTypes(String searchTerm, boolean includeBinaries, boolean includeSystemLibs) {
JavaSearchParams searchParams = new JavaSearchParams(projectUri.toString(), searchTerm, includeBinaries, includeSystemLibs);
JavaSearchParams searchParams = new JavaSearchParams(projectUri.toString(), searchTerm, includeBinaries, includeSystemLibs, SEARCH_TIMEOUT);
return Mono.fromFuture(client.javaSearchTypes(searchParams))
.flatMapMany(results -> Flux.fromIterable(results).publishOn(Schedulers.parallel()))
.filter(Objects::nonNull)
@@ -100,7 +102,7 @@ public class JdtLsIndex implements ClasspathIndex {
@Override
public Flux<Tuple2<String, Double>> fuzzySearchPackages(String searchTerm, boolean includeBinaries, boolean includeSystemLibs) {
JavaSearchParams searchParams = new JavaSearchParams(projectUri.toString(), searchTerm, includeBinaries, includeSystemLibs);
JavaSearchParams searchParams = new JavaSearchParams(projectUri.toString(), searchTerm, includeBinaries, includeSystemLibs, SEARCH_TIMEOUT);
return Mono.fromFuture(client.javaSearchPackages(searchParams))
.flatMapMany(results -> Flux.fromIterable(results).publishOn(Schedulers.parallel()))
.filter(Objects::nonNull)

View File

@@ -16,19 +16,25 @@ public class JavaSearchParams {
private String term;
private boolean includeBinaries;
private boolean includeSystemLibs;
private long timeLimit = -1;
public JavaSearchParams(String projectUri, String term) {
this(projectUri, term, true, false);
}
public JavaSearchParams(String projectUri, String term, boolean includeBinaries, boolean includeSystemLibs) {
public JavaSearchParams(String projectUri, String term, boolean includeBinaries, boolean includeSystemLibs, long timeLimit) {
super();
this.projectUri = projectUri;
this.term = term;
this.includeBinaries = includeBinaries;
this.includeSystemLibs = includeSystemLibs;
this.setTimeLimit(timeLimit);
}
public JavaSearchParams(String projectUri, String term, boolean includeBinaries, boolean includeSystemLibs) {
this(projectUri, term, includeBinaries, includeSystemLibs, -1);
}
public String getProjectUri() {
return projectUri;
}
@@ -61,5 +67,12 @@ public class JavaSearchParams {
this.includeSystemLibs = includeSystemLibs;
}
public long getTimeLimit() {
return timeLimit;
}
public void setTimeLimit(long timeLimit) {
this.timeLimit = timeLimit;
}
}

View File

@@ -13,7 +13,9 @@ Require-Bundle: org.eclipse.core.runtime,
org.eclipse.m2e.jdt,
org.eclipse.m2e.core,
org.eclipse.lsp4j,
org.eclipse.lsp4j.jsonrpc
org.eclipse.lsp4j.jsonrpc,
io.projectreactor.reactor-core,
org.reactivestreams.reactive-streams
Export-Package: org.springframework.ide.vscode.commons.protocol,
org.springframework.ide.vscode.commons.protocol.java,
org.springframework.tooling.jdt.ls.commons,

View File

@@ -38,12 +38,25 @@
</filesets>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.1.1</version>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<stripVersion>true</stripVersion>
<outputDirectory>${project.build.directory}/dependencies</outputDirectory>
<overWriteReleases>true</overWriteReleases>
<overWriteSnapshots>true</overWriteSnapshots>
</configuration>
</execution>
<execution>
<id>copy</id>
<phase>initialize</phase>

View File

@@ -0,0 +1,119 @@
/*******************************************************************************
* Copyright (c) 2019 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.tooling.jdt.ls.commons.java;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.core.IJavaProject;
import org.springframework.tooling.jdt.ls.commons.Logger;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader.InvalidCacheLoadException;
import reactor.core.publisher.Flux;
import reactor.util.function.Tuple2;
import reactor.util.function.Tuples;
public abstract class CachingFluxJavaSearch<T> implements FluxSearch<T> {
private int MAX_RESULTS = 500;
final protected Logger logger;
final protected boolean includeBinaries;
final protected boolean includeSystemLibs;
private Cache<Tuple2<String,String>, CacheEntry> cache = createCache();
private class CacheEntry {
boolean isComplete = false;
int count = 0;
Flux<T> values;
public CacheEntry(String query, Flux<T> producer) {
values = producer
.doOnNext(t -> count++)
.doOnComplete(() -> isComplete = true)
.take(MAX_RESULTS)
.cache(MAX_RESULTS);
values.subscribe(); // create infinite demand so that we actually force cache entries to be fetched upto the max.
}
@Override
public String toString() {
return "CacheEntry [isComplete=" + isComplete + ", count=" + count + "]";
}
}
public CachingFluxJavaSearch(Logger logger, boolean includeBinaries, boolean includeSystemLibs) {
this.logger = logger;
this.includeBinaries = includeBinaries;
this.includeSystemLibs = includeSystemLibs;
}
@Override
public final Flux<T> search(IJavaProject javaProject, String query) {
Tuple2<String, String> key = key(javaProject, query);
CacheEntry cached = null;
try {
cached = cache.get(key, () -> new CacheEntry(query, getValuesIncremental(javaProject, query)));
} catch (ExecutionException e) {
logger.log(e);
}
return cached.values;
}
/**
* Tries to use an already cached, complete result for a query that is a prefix of the current query to speed things up.
* <p>
* Falls back on doing a full-blown search if there's no usable 'prefix-query' in the cache.
*/
private Flux<T> getValuesIncremental(IJavaProject javaProject, String query) {
// debug("trying to solve "+query+" incrementally");
String subquery = query;
while (subquery.length()>=1) {
subquery = subquery.substring(0, subquery.length()-1);
CacheEntry cached = null;
try {
cached = cache.get(key(javaProject, subquery), () -> null);
} catch (ExecutionException | InvalidCacheLoadException e) {
// Log.log(e);
}
if (cached!=null) {
// debug("cached "+subquery+": "+cached);
if (cached.isComplete) {
return cached.values
// .doOnNext((hint) -> debug("filter["+query+"]: "+hint.getValue()))
.filter((result) -> 0!=FuzzyMatcher.matchScore(query, stringValue(result)));
} else {
// debug("subquery "+subquery+" cached but is incomplete");
}
}
}
// debug("full search for: "+query);
return getValuesAsync(javaProject, query);
}
protected abstract Flux<T> getValuesAsync(IJavaProject javaProject, String query);
protected abstract String stringValue(T t);
private Tuple2<String,String> key(IJavaProject javaProject, String query) {
return Tuples.of(javaProject==null?null:javaProject.getElementName(), query);
}
protected <K,V> Cache<K,V> createCache() {
return CacheBuilder.newBuilder().expireAfterWrite(1, TimeUnit.MINUTES).expireAfterAccess(1, TimeUnit.MINUTES).build();
}
}

View File

@@ -12,20 +12,21 @@ package org.springframework.tooling.jdt.ls.commons.java;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.search.IJavaSearchConstants;
import org.eclipse.jdt.core.search.IJavaSearchScope;
import org.eclipse.jdt.core.search.SearchEngine;
import org.eclipse.jdt.core.search.SearchMatch;
@@ -34,6 +35,10 @@ import org.eclipse.jdt.core.search.SearchPattern;
import org.eclipse.jdt.core.search.SearchRequestor;
import org.springframework.tooling.jdt.ls.commons.Logger;
import reactor.core.publisher.Flux;
import reactor.core.publisher.ReplayProcessor;
import reactor.util.concurrent.Queues;
/**
* Helper class to perform a search using Eclipse JDT search engine returning
* the search results as a Flux.
@@ -53,7 +58,7 @@ import org.springframework.tooling.jdt.ls.commons.Logger;
*
* @author Kris De Volder
*/
public class StreamJdtSearch {
public class FluxJdtSearch {
private static final boolean DEBUG = (""+Platform.getLocation()).contains("kdvolder");
@@ -67,23 +72,31 @@ public class StreamJdtSearch {
private IJavaSearchScope scope = SearchEngine.createWorkspaceScope();
private SearchPattern pattern = null;
private SearchParticipant[] participants = new SearchParticipant[] {SearchEngine.getDefaultSearchParticipant()};
private int bufferSize = Queues.SMALL_BUFFER_SIZE;
private boolean useSystemJob = false;
private int jobPriority = Job.INTERACTIVE;
private Logger logger;
public StreamJdtSearch(Logger logger) {
public FluxJdtSearch(Logger logger) {
this.logger = logger;
}
public StreamJdtSearch engine(SearchEngine engine) {
public FluxJdtSearch engine(SearchEngine engine) {
this.engine = engine;
return this;
}
public StreamJdtSearch scope(IJavaSearchScope scope) {
public FluxJdtSearch scope(IJavaSearchScope scope) {
this.scope = scope;
return this;
}
public StreamJdtSearch pattern(SearchPattern pattern) {
public FluxJdtSearch bufferSize(int bufferSize) {
this.bufferSize = bufferSize;
return this;
}
public FluxJdtSearch pattern(SearchPattern pattern) {
this.pattern = pattern;
return this;
}
@@ -104,13 +117,20 @@ public class StreamJdtSearch {
return SearchEngine.createJavaSearchScope(new IJavaElement[] {javaProject}, includeMask);
}
class Requestor extends SearchRequestor {
/**
* Implementation of {@link SearchRequestor} that emits search results to an {@link ReplayProcessor}
* with replay capability.
*
* @author Kris De Volder
*/
class FluxSearchRequestor extends SearchRequestor {
private boolean isCanceled = false;
private Stream.Builder<SearchMatch> results = Stream.builder();
private ReplayProcessor<SearchMatch> emitter = ReplayProcessor.<SearchMatch>create(bufferSize);
private Flux<SearchMatch> flux = emitter.doOnCancel(() -> isCanceled=true);
public Stream<SearchMatch> asStream() {
return results.build();
public Flux<SearchMatch> asFlux() {
return flux;
}
@Override
@@ -120,13 +140,16 @@ public class StreamJdtSearch {
//Stop searching
throw new OperationCanceledException();
}
results.add(match);
emitter.onNext(match);
}
public void cancel() {
isCanceled = true;
}
public void done() {
emitter.onComplete();
}
}
protected SearchEngine searchEngine() {
@@ -137,24 +160,34 @@ public class StreamJdtSearch {
return new SearchParticipant[] {SearchEngine.getDefaultSearchParticipant()};
}
public Stream<SearchMatch> search() {
public Flux<SearchMatch> search() {
validate();
if (scope==null) {
return Stream.of();
return Flux.empty();
}
final Requestor requestor = new Requestor();
long start = System.currentTimeMillis();
debug("Starting search for '" + pattern + "'");
try {
searchEngine().search(pattern, participants, scope, requestor, new NullProgressMonitor());
} catch (Exception e) {
debug("Canceled search for: " + pattern);
debug(" exception: " + e.getMessage());
long duration = System.currentTimeMillis() - start;
debug(" duration: " + duration + " ms");
requestor.cancel();
}
return requestor.asStream();
final FluxSearchRequestor requestor = new FluxSearchRequestor();
Job job = new Job("Search for "+pattern) {
@Override
protected IStatus run(IProgressMonitor monitor) {
long start = System.currentTimeMillis();
debug("Starting search for '"+pattern+"'");
try {
searchEngine().search(pattern, participants, scope, requestor, monitor);
requestor.done();
} catch (Exception e) {
debug("Canceled search for: "+pattern);
debug(" exception: "+e.getMessage());
long duration = System.currentTimeMillis() - start;
debug(" duration: "+duration+" ms");
requestor.cancel();
}
return Status.OK_STATUS;
}
};
job.setSystem(useSystemJob);
job.setPriority(jobPriority);
job.schedule();
return requestor.asFlux();
}
private void validate() {
@@ -164,6 +197,7 @@ public class StreamJdtSearch {
// Assert.isNotNull(scope, "scope");
Assert.isNotNull(pattern, "pattern");
Assert.isNotNull(participants, "participants");
Assert.isLegal(bufferSize > 0);
}
public IJavaSearchScope workspaceScope(boolean includeBinaries) {
@@ -186,44 +220,4 @@ public class StreamJdtSearch {
}
}
public static String toWildCardPattern(String query) {
StringBuilder builder = new StringBuilder("*");
for (char c : query.toCharArray()) {
builder.append(c);
builder.append('*');
}
return builder.toString();
}
public static SearchPattern toPackagePattern(String wildCardedQuery) {
int searchFor = IJavaSearchConstants.PACKAGE;
int limitTo = IJavaSearchConstants.DECLARATIONS;
int matchRule = SearchPattern.R_PATTERN_MATCH;
return SearchPattern.createPattern(wildCardedQuery, searchFor, limitTo, matchRule);
}
public static SearchPattern toClassPattern(String wildCardedQuery) {
int searchFor = IJavaSearchConstants.CLASS;
int limitTo = IJavaSearchConstants.DECLARATIONS;
int matchRule = SearchPattern.R_PATTERN_MATCH;
return SearchPattern.createPattern(wildCardedQuery, searchFor, limitTo, matchRule);
}
public static SearchPattern toTypePattern(String wildCardedQuery) {
int searchFor = IJavaSearchConstants.TYPE;
int limitTo = IJavaSearchConstants.DECLARATIONS;
int matchRule = SearchPattern.R_PATTERN_MATCH;
return SearchPattern.createPattern(wildCardedQuery, searchFor, limitTo, matchRule);
}
public static String toProperTypeQuery(String query) {
int idx = query.lastIndexOf('.');
if (idx > 0 && idx < query.length() - 1 && Character.isLowerCase(query.charAt(idx + 1))) {
return query + '.';
} else {
return query;
}
}
}

View File

@@ -0,0 +1,32 @@
/*******************************************************************************
* Copyright (c) 2019 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.tooling.jdt.ls.commons.java;
import java.time.Duration;
import java.util.List;
import org.eclipse.jdt.core.IJavaProject;
import reactor.core.publisher.Flux;
public interface FluxSearch<T> {
Flux<T> search(IJavaProject project, String searchTerm);
default List<T> searchWithLimits(IJavaProject javaProject, String searchTerm, long timeLimit) {
Flux<T> flux = this.search(javaProject, searchTerm);
if (timeLimit > 0) {
flux = flux.take(Duration.ofMillis(timeLimit));
}
return flux.collectList().block();
}
}

View File

@@ -0,0 +1,75 @@
/*******************************************************************************
* Copyright (c) 2019 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.tooling.jdt.ls.commons.java;
/**
* @author Kris De Volder
*/
class FuzzyMatcher {
/**
* Match given pattern with a given data. The data is considered a 'match' for the
* pattern if all characters in the pattern can be found in the data, in the
* same order but with possible 'gaps' in between.
* <p>
* The function returns 0. when the pattern doesn't match the data and a non-zero
* 'score' when it does. The higher the score, the better the match is considered to
* be.
*/
public static double matchScore(CharSequence pattern, String data) {
int ppos = 0; //pos of next char in pattern to look for
int dpos = 0; //pos of next char in data not yet matched
int gaps = 0; //number of 'gaps' in the match. A gap is any non-empty run of consecutive characters in the data that are not used by the match
int skips = 0; //number of skipped characters. This is the sum of the length of all the gaps.
int plen = pattern.length();
int dlen = data.length();
if (plen>dlen) {
return 0.0;
}
while (ppos<plen) {
if (dpos>=dlen) {
//still chars left in pattern but no more data
return 0.0;
}
char c = pattern.charAt(ppos++);
int foundCharAt = data.indexOf(c, dpos);
if (foundCharAt>=0) {
if (foundCharAt>dpos) {
gaps++;
skips+=foundCharAt-dpos;
}
dpos = foundCharAt+1;
} else {
return 0.0;
}
}
//end of pattern reached. All matched.
if (dpos<dlen) {
//data left over
//gaps++; don't count end skipped chars as a real 'gap'. Otherwise we
//tend to favor matches at the end of the string over matches in the middle.
skips+=dlen-dpos; //but do count the extra chars at end => more extra = worse score
}
return score(gaps, skips, pattern);
}
private static double score(int gaps, int skips, CharSequence pattern) {
if (gaps==0) {
//gaps == 0 means a prefix match, ignore 'skips' at end of String and just sort
// alphabetic (see STS-4049)
return 0.5+pattern.length(); //all scored equally, assumes using a 'stable' sorter.
} else {
double badness = 1+gaps + skips/1000.0; // higher is worse
return 1.0/badness + pattern.length(); //higher is better
}
}
}

View File

@@ -0,0 +1,56 @@
/*******************************************************************************
* Copyright (c) 2019 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.tooling.jdt.ls.commons.java;
import java.net.URI;
import java.util.List;
import org.eclipse.jdt.core.IJavaProject;
import org.springframework.ide.vscode.commons.protocol.java.JavaSearchParams;
import org.springframework.tooling.jdt.ls.commons.Logger;
import org.springframework.tooling.jdt.ls.commons.resources.ResourceUtils;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import reactor.util.function.Tuple2;
import reactor.util.function.Tuples;
public class JavaFluxSearch {
final private Logger logger;
final private Cache<Tuple2<Boolean, Boolean>, PackageFluxSearch> packageSearchCache = CacheBuilder.newBuilder().build();
final private Cache<Tuple2<Boolean, Boolean>, TypeFluxSearch> typeSearchCache = CacheBuilder.newBuilder().build();
public JavaFluxSearch(Logger logger) {
super();
this.logger = logger;
}
public List<String> fuzzySearchPackages(JavaSearchParams params) throws Exception {
URI projectUri = params.getProjectUri() == null ? null : URI.create(params.getProjectUri());
IJavaProject javaProject = projectUri == null ? null : ResourceUtils.getJavaProject(projectUri);
PackageFluxSearch fluxPackageSearch = packageSearchCache.get(
Tuples.of(params.isIncludeBinaries(), params.isIncludeSystemLibs()), () -> new PackageFluxSearch(logger, params.isIncludeBinaries(), params.isIncludeSystemLibs())
);
return fluxPackageSearch.searchWithLimits(javaProject, params.getTerm(), params.getTimeLimit());
}
public List<String> fuzzySearchTypes(JavaSearchParams params) throws Exception {
URI projectUri = params.getProjectUri() == null ? null : URI.create(params.getProjectUri());
IJavaProject javaProject = projectUri == null ? null : ResourceUtils.getJavaProject(projectUri);
TypeFluxSearch fluxTypeSearch = typeSearchCache.get(
Tuples.of(params.isIncludeBinaries(), params.isIncludeSystemLibs()), () -> new TypeFluxSearch(logger, params.isIncludeBinaries(), params.isIncludeSystemLibs())
);
return fluxTypeSearch.searchWithLimits(javaProject, params.getTerm(), params.getTimeLimit());
}
}

View File

@@ -1,55 +0,0 @@
/*******************************************************************************
* Copyright (c) 2019 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.tooling.jdt.ls.commons.java;
import java.net.URI;
import java.util.stream.Stream;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IType;
import org.springframework.tooling.jdt.ls.commons.Logger;
import org.springframework.tooling.jdt.ls.commons.resources.ResourceUtils;
public class JavaSearch {
private Logger logger;
public JavaSearch(Logger logger) {
super();
this.logger = logger;
}
public Stream<String> fuzzySearchPackages(URI projectUri, String searchTerm, boolean includeBinaries, boolean includeSystemLibs) throws Exception {
IJavaProject javaProject = projectUri == null ? null : ResourceUtils.getJavaProject(projectUri);
return new StreamJdtSearch(logger)
.scope(StreamJdtSearch.searchScope(javaProject, includeBinaries, includeSystemLibs))
.pattern(StreamJdtSearch.toPackagePattern(StreamJdtSearch.toWildCardPattern(searchTerm)))
.search()
.parallel()
.map(match -> match.getElement())
.filter(o -> o instanceof IPackageFragment)
.map(p -> ((IPackageFragment)p).getElementName());
}
public Stream<String> fuzzySearchTypes(URI projectUri, String searchTerm, boolean includeBinaries, boolean includeSystemLibs) throws Exception {
IJavaProject javaProject = projectUri == null ? null : ResourceUtils.getJavaProject(projectUri);
return new StreamJdtSearch(logger)
.scope(StreamJdtSearch.searchScope(javaProject, includeBinaries, includeSystemLibs))
.pattern(StreamJdtSearch.toTypePattern(StreamJdtSearch.toWildCardPattern(StreamJdtSearch.toProperTypeQuery(searchTerm))))
.search()
.parallel()
.map(match -> match.getElement())
.filter(o -> o instanceof IType)
.map(e -> ((IType) e).getFullyQualifiedName());
}
}

View File

@@ -0,0 +1,51 @@
/*******************************************************************************
* Copyright (c) 2019 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.tooling.jdt.ls.commons.java;
import static org.springframework.tooling.jdt.ls.commons.java.SearchUtils.searchScope;
import static org.springframework.tooling.jdt.ls.commons.java.SearchUtils.toPackagePattern;
import static org.springframework.tooling.jdt.ls.commons.java.SearchUtils.toWildCardPattern;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.JavaModelException;
import org.springframework.tooling.jdt.ls.commons.Logger;
import reactor.core.publisher.Flux;
public class PackageFluxSearch extends CachingFluxJavaSearch<String> {
public PackageFluxSearch(Logger logger, boolean includeBinaries, boolean includeSystemLibs) {
super(logger, includeBinaries, includeSystemLibs);
}
@Override
protected Flux<String> getValuesAsync(IJavaProject javaProject, String searchTerm) {
try {
return new FluxJdtSearch(logger)
.scope(searchScope(javaProject, includeBinaries, includeSystemLibs))
.pattern(toPackagePattern(toWildCardPattern(searchTerm)))
.search()
.map(match -> match.getElement())
.filter(o -> o instanceof IPackageFragment)
.map(p -> ((IPackageFragment)p).getElementName());
} catch (JavaModelException e) {
logger.log(e);
return Flux.empty();
}
}
@Override
protected String stringValue(String t) {
return t;
}
}

View File

@@ -0,0 +1,78 @@
/*******************************************************************************
* Copyright (c) 2019 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.tooling.jdt.ls.commons.java;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.search.IJavaSearchConstants;
import org.eclipse.jdt.core.search.IJavaSearchScope;
import org.eclipse.jdt.core.search.SearchEngine;
import org.eclipse.jdt.core.search.SearchPattern;
public class SearchUtils {
public static String toWildCardPattern(String query) {
StringBuilder builder = new StringBuilder("*");
for (char c : query.toCharArray()) {
builder.append(c);
builder.append('*');
}
return builder.toString();
}
public static SearchPattern toPackagePattern(String wildCardedQuery) {
int searchFor = IJavaSearchConstants.PACKAGE;
int limitTo = IJavaSearchConstants.DECLARATIONS;
int matchRule = SearchPattern.R_PATTERN_MATCH;
return SearchPattern.createPattern(wildCardedQuery, searchFor, limitTo, matchRule);
}
public static SearchPattern toClassPattern(String wildCardedQuery) {
int searchFor = IJavaSearchConstants.CLASS;
int limitTo = IJavaSearchConstants.DECLARATIONS;
int matchRule = SearchPattern.R_PATTERN_MATCH;
return SearchPattern.createPattern(wildCardedQuery, searchFor, limitTo, matchRule);
}
public static SearchPattern toTypePattern(String wildCardedQuery) {
int searchFor = IJavaSearchConstants.TYPE;
int limitTo = IJavaSearchConstants.DECLARATIONS;
int matchRule = SearchPattern.R_PATTERN_MATCH;
return SearchPattern.createPattern(wildCardedQuery, searchFor, limitTo, matchRule);
}
public static String toProperTypeQuery(String query) {
int idx = query.lastIndexOf('.');
if (idx > 0 && idx < query.length() - 1 && Character.isLowerCase(query.charAt(idx + 1))) {
return query + '.';
} else {
return query;
}
}
/**
* Create a search scope that includes a given project and its dependencies.
*/
public static IJavaSearchScope searchScope(IJavaProject javaProject, boolean includeBinaries, boolean includeSystemLibs) throws JavaModelException {
int includeMask =
IJavaSearchScope.REFERENCED_PROJECTS |
IJavaSearchScope.SOURCES;
if (includeBinaries) {
includeMask = includeMask | IJavaSearchScope.APPLICATION_LIBRARIES;
}
if (includeSystemLibs) {
includeMask = includeMask | IJavaSearchScope.SYSTEM_LIBRARIES;
}
return SearchEngine.createJavaSearchScope(new IJavaElement[] {javaProject}, includeMask);
}
}

View File

@@ -0,0 +1,51 @@
/*******************************************************************************
* Copyright (c) 2019 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.tooling.jdt.ls.commons.java;
import static org.springframework.tooling.jdt.ls.commons.java.SearchUtils.searchScope;
import static org.springframework.tooling.jdt.ls.commons.java.SearchUtils.toProperTypeQuery;
import static org.springframework.tooling.jdt.ls.commons.java.SearchUtils.toTypePattern;
import static org.springframework.tooling.jdt.ls.commons.java.SearchUtils.toWildCardPattern;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IType;
import org.springframework.tooling.jdt.ls.commons.Logger;
import reactor.core.publisher.Flux;
public class TypeFluxSearch extends CachingFluxJavaSearch<String> {
public TypeFluxSearch(Logger logger, boolean includeBinaries, boolean includeSystemLibs) {
super(logger, includeBinaries, includeSystemLibs);
}
@Override
protected Flux<String> getValuesAsync(IJavaProject javaProject, String searchTerm) {
try {
return new FluxJdtSearch(logger)
.scope(searchScope(javaProject, includeBinaries, includeSystemLibs))
.pattern(toTypePattern(toWildCardPattern(toProperTypeQuery(searchTerm))))
.search()
.map(match -> match.getElement())
.filter(o -> o instanceof IType)
.map(e -> ((IType) e).getFullyQualifiedName());
} catch (Exception e) {
logger.log(e);
return Flux.empty();
}
}
@Override
protected String stringValue(String t) {
return t;
}
}

View File

@@ -13,7 +13,7 @@ package org.springframework.tooling.jdt.ls.extension;
import org.eclipse.jdt.ls.core.internal.HoverInfoProvider;
import org.springframework.tooling.jdt.ls.commons.Logger;
import org.springframework.tooling.jdt.ls.commons.java.JavaData;
import org.springframework.tooling.jdt.ls.commons.java.JavaSearch;
import org.springframework.tooling.jdt.ls.commons.java.JavaFluxSearch;
import org.springframework.tooling.jdt.ls.commons.java.TypeHierarchy;
import com.google.common.base.Supplier;
@@ -26,7 +26,7 @@ public class JavaHelpers {
final public static Supplier<JavaData> DATA = Suppliers.memoize(() -> new JavaData(element -> HoverInfoProvider.computeSignature(element).getValue(), logger));
final public static Supplier<JavaSearch> SEARCH = Suppliers.memoize(() -> new JavaSearch(logger));
final public static Supplier<JavaFluxSearch> SEARCH = Suppliers.memoize(() -> new JavaFluxSearch(logger));
final public static Supplier<TypeHierarchy> HIERARCHY = Suppliers.memoize(() -> new TypeHierarchy(logger, DATA.get()));

View File

@@ -10,30 +10,27 @@
*******************************************************************************/
package org.springframework.tooling.jdt.ls.extension;
import java.net.URI;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.ls.core.internal.IDelegateCommandHandler;
import org.springframework.ide.vscode.commons.protocol.java.JavaSearchParams;
import com.google.gson.Gson;
@SuppressWarnings("restriction")
public class SearchHandler implements IDelegateCommandHandler {
private Gson gson = new Gson();
@SuppressWarnings("unchecked")
@Override
public Object executeCommand(String commandId, List<Object> arguments, IProgressMonitor monitor) throws Exception {
Map<String, Object> obj = (Map<String, Object>) arguments.get(0);
String projectUri = (String) obj.get("projectUri");
String term = (String) obj.get("term");
Boolean includeBinaries = (Boolean) obj.get("includeBinaries");
Boolean includeSystemLibs = (Boolean) obj.get("includeSystemLibs");
JavaSearchParams params = gson.fromJson(gson.toJson(arguments.get(0)), JavaSearchParams.class);
switch (commandId) {
case "sts.java.search.types":
return JavaHelpers.SEARCH.get().fuzzySearchTypes(URI.create(projectUri), term, includeBinaries, includeSystemLibs).collect(Collectors.toList());
return JavaHelpers.SEARCH.get().fuzzySearchTypes(params);
case "sts.java.search.packages":
return JavaHelpers.SEARCH.get().fuzzySearchPackages(URI.create(projectUri), term, includeBinaries, includeSystemLibs).collect(Collectors.toList());
return JavaHelpers.SEARCH.get().fuzzySearchPackages(params);
default:
return null;
}

View File

@@ -57,6 +57,7 @@
<signing.skip>true</signing.skip>
<signing.alias>pivotal</signing.alias>
<signing.keystore>~/.keytool/pivotal.jks</signing.keystore>
<misc.p2.repo.version>3.9.4.201901081830</misc.p2.repo.version>
</properties>
<profiles>
@@ -87,6 +88,11 @@
<!--URL copied from jdt.ls target platform file, needed to satisfy some of its dependencies -->
<url>https://download.jboss.org/jbosstools/updates/m2e-extensions/m2e-apt/1.5.0-2018-08-14_20-24-39-H12/</url>
</repository>
<repository>
<id>p2-thirdparty-bundles</id>
<layout>p2</layout>
<url>http://dist.springsource.com/release/TOOLS/third-party/misc-p2-repo/${misc.p2.repo.version}</url>
</repository>
</repositories>
<build>
<plugins>

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2016 Pivotal, Inc.
* Copyright (c) 2016, 2019 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
@@ -14,11 +14,12 @@ import java.time.Duration;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.boot.metadata.ValueProviderRegistry.ValueProviderStrategy;
import org.springframework.ide.vscode.boot.metadata.hints.StsValueHint;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.util.FuzzyMatcher;
import org.springframework.ide.vscode.commons.util.Log;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
@@ -52,6 +53,8 @@ import reactor.util.function.Tuples;
*/
public abstract class CachingValueProvider implements ValueProviderStrategy {
private static final Logger log = LoggerFactory.getLogger(CachingValueProvider.class);
private static final Duration DEFAULT_TIMEOUT = Duration.ofMillis(1000);
/**
@@ -76,6 +79,8 @@ public abstract class CachingValueProvider implements ValueProviderStrategy {
public CacheEntry(String query, Flux<StsValueHint> producer) {
values = producer
.doOnNext(t -> count++)
.doOnComplete(() -> isComplete = true)
.take(MAX_RESULTS)
.cache(MAX_RESULTS);
values.subscribe(); // create infinite demand so that we actually force cache entries to be fetched upto the max.
@@ -95,7 +100,7 @@ public abstract class CachingValueProvider implements ValueProviderStrategy {
try {
cached = cache.get(key, () -> new CacheEntry(query, getValuesIncremental(javaProject, query)));
} catch (ExecutionException e) {
Log.log(e);
log.error("{}", e);
}
return cached.values;
}

View File

@@ -37,7 +37,7 @@ import reactor.util.function.Tuples;
* @author Kris De Volder
* @author Alex Boyko
*/
public class LoggerNameProvider extends CachingValueProvider {
public class LoggerNameProvider implements ValueProviderStrategy {
private static final String LOGGING_GROUPS_PREFIX = "logging.group.";
private final ProjectBasedPropertyIndexProvider adhocProperties;
@@ -56,6 +56,11 @@ public class LoggerNameProvider extends CachingValueProvider {
};
}
@Override
public Flux<StsValueHint> getValues(IJavaProject javaProject, String query) {
return getValuesAsync(javaProject, query);
}
Collection<String> loggerGroupNames(IJavaProject jp) {
Builder<String> builder = ImmutableSet.builder();
if (adhocProperties!=null && includeGroups) {
@@ -75,8 +80,7 @@ public class LoggerNameProvider extends CachingValueProvider {
return builder.build();
}
@Override
protected Flux<StsValueHint> getValuesAsync(IJavaProject javaProject, String query) {
private Flux<StsValueHint> getValuesAsync(IJavaProject javaProject, String query) {
return Flux.concat(
Flux.fromIterable(loggerGroupNames(javaProject))
.map(loggerName -> Tuples.of(StsValueHint.create(loggerName), FuzzyMatcher.matchScore(query, loggerName)))

View File

@@ -105,7 +105,7 @@ public class LoggerNameProviderTest {
public void incrementalResults() throws Exception {
String fullQuery = "jboss";
CachingValueProvider p = create();
LoggerNameProvider p = create();
for (int i = 0; i <= fullQuery.length(); i++) {
String query = fullQuery.substring(0, i);
List<String> results = getResults(p, query);
@@ -154,7 +154,7 @@ public class LoggerNameProviderTest {
return buf.toString();
}
private List<String> getResults(CachingValueProvider p, String query) {
private List<String> getResults(LoggerNameProvider p, String query) {
return p.getValues(project, query).toStream()
.map((h) -> h.getValue().toString())
.collect(Collectors.toList());

View File

@@ -19,5 +19,9 @@ cd ${workdir}/../../../headless-services/jdt-ls-extension
cp org.springframework.tooling.jdt.ls.extension/target/*.jar ${workdir}/jars/jdt-ls-extension.jar
cp org.springframework.tooling.jdt.ls.commons/target/*.jar ${workdir}/jars/jdt-ls-commons.jar
# Copy Reactor dependency bundles
cp org.springframework.tooling.jdt.ls.commons/target/dependencies/io.projectreactor.reactor-core.jar ${workdir}/jars/
cp org.springframework.tooling.jdt.ls.commons/target/dependencies/org.reactivestreams.reactive-streams.jar ${workdir}/jars/
cd ${workdir}/..
yarn

View File

@@ -16,8 +16,8 @@ export const JAVA_TYPE_REQUEST_TYPE = 'sts/javaType';
export const JAVADOC_HOVER_LINK_REQUEST_TYPE = 'sts/javadocHoverLink';
export const JAVA_LOCATION_REQUEST_TYPE = 'sts/javaLocation';
export const JAVADOC_REQUEST_TYPE = 'sts/javadoc';
export const SEARCH_TYPES_REQUEST_TYPE = 'sts/searchJavaTypes';
export const SEACH_PACKAGES_REQUEST_TYPE = 'sts/searchJavaPackages';
export const SEARCH_TYPES_REQUEST_TYPE = 'sts/javaSearchTypes';
export const SEACH_PACKAGES_REQUEST_TYPE = 'sts/javaSearchPackages';
export const SUBTYPES_REQUEST_TYPE = 'sts/javaSubTypes';
export const SUPERTYPES_REQUEST_TYPE = 'sts/javaSuperTypes';

View File

@@ -18,6 +18,8 @@ export class BootJavaExtension implements JavaExtensionContribution {
getExtensionBundles() {
const jarFolderPath = path.resolve(__dirname, '../../jars');
return [
path.resolve(jarFolderPath, 'io.projectreactor.reactor-core.jar'),
path.resolve(jarFolderPath, 'org.reactivestreams.reactive-streams.jar'),
path.resolve(jarFolderPath, 'jdt-ls-commons.jar'),
path.resolve(jarFolderPath, 'jdt-ls-extension.jar')
];

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -32,6 +32,8 @@
],
"contributes": {
"javaExtensions": [
"./jars/io.projectreactor.reactor-core.jar",
"./jars/org.reactivestreams.reactive-streams.jar",
"./jars/jdt-ls-commons.jar",
"./jars/jdt-ls-extension.jar"
],

View File

@@ -23,3 +23,7 @@ cp target/*.jar ${workdir}/jars
cd ${workdir}/../../headless-services/jdt-ls-extension
cp org.springframework.tooling.jdt.ls.extension/target/*.jar ${workdir}/jars/jdt-ls-extension.jar
cp org.springframework.tooling.jdt.ls.commons/target/*.jar ${workdir}/jars/jdt-ls-commons.jar
# Copy Reactor dependency bundles
cp org.springframework.tooling.jdt.ls.commons/target/dependencies/io.projectreactor.reactor-core.jar ${workdir}/jars/
cp org.springframework.tooling.jdt.ls.commons/target/dependencies/org.reactivestreams.reactive-streams.jar ${workdir}/jars/