This repository has been archived by the owner on Jul 13, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 127
/
CachingValueProvider.java
153 lines (133 loc) · 5.66 KB
/
CachingValueProvider.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
/*******************************************************************************
* Copyright (c) 2016 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.eclipse.boot.properties.editor.metadata;
import java.time.Duration;
import org.eclipse.jdt.core.IJavaProject;
import org.springframework.ide.eclipse.boot.properties.editor.metadata.ValueProviderRegistry.ValueProviderStrategy;
import org.springframework.ide.eclipse.editor.support.util.FuzzyMatcher;
import org.springsource.ide.eclipse.commons.frameworks.core.internal.cache.Cache;
import org.springsource.ide.eclipse.commons.frameworks.core.internal.cache.LimitedTimeCache;
import reactor.core.publisher.Flux;
import reactor.util.function.Tuple2;
import reactor.util.function.Tuples;
/**
* A abstract {@link ValueProviderStrategy} that is mean to help speedup successive invocations of
* content assist with a similar 'query' string.
* <p>
* This implementation is meant to be used for providers that use potentially lenghty/expensive searches
* to determine hints. Since content assist hints are requested by Eclipse CA framework directly on
* the UI thread, they can not simply perform a lengthy search and block UI thread until it finished.
* <p>
* This implementation therefore does the following:
* <ul>
* <li>Limit the duration of time spent on the UI thread.
* <li>Cache results of searches for a limited time.
* <li>Speedup queries for successive queries by using the already cached result of a similar (prefix) query.
* <li>When the time spent on UI thread waiting for a current search exceeds the allowed time limit,
* return immediately with whatever results have been found so far.
* </ul>
*
* TODO: rather than an abstract class this should really be 'Wrapper' class that delegates to another
* {@link ValueProviderStrategy} and adds a cache in front of it.
*
* @author Kris De Volder
*/
public abstract class CachingValueProvider implements ValueProviderStrategy {
// protected static final boolean DEBUG = (""+Platform.getLocation()).contains("kdvolder");
//
// protected static void debug(String string) {
// if (DEBUG) {
// System.out.println(string);
// }
// }
private static final Duration DEFAULT_TIMEOUT = Duration.ofMillis(1000);
/**
* Content assist is called inside UI thread and so doing something lenghty things
* like a JavaSearch will block the UI thread completely freezing the UI. So, we
* only return as many results as can be obtained within this hard TIMEOUT limit.
*/
public static Duration TIMEOUT = DEFAULT_TIMEOUT;
/**
* The maximum number of results returned for a single request. Used to limit the
* values that are cached per entry.
*/
private int MAX_RESULTS = 500;
private Cache<Tuple2<String,String>, CacheEntry> cache = createCache();
private class CacheEntry {
boolean isComplete = false;
int count = 0;
Flux<StsValueHint> values;
public CacheEntry(String query, Flux<StsValueHint> producer) {
values = producer
// .doOnNext((e) -> {
// count++;
// debug("onNext["+query+":"+count+"]: "+e.getValue().toString());
// })
// .doOnComplete(() -> {
// debug("onComplete["+query+":"+count+"]");
// 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 + "]";
}
}
@Override
public final Flux<StsValueHint> getValues(IJavaProject javaProject, String query) {
// debug("CA query: "+query);
Tuple2<String, String> key = key(javaProject, query);
CacheEntry cached = cache.get(key);
if (cached==null) {
cache.put(key, cached = new CacheEntry(query, getValuesIncremental(javaProject, query)));
}
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<StsValueHint> 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 = cache.get(key(javaProject, subquery));
if (cached!=null) {
System.out.println("cached "+subquery+": "+cached);
if (cached.isComplete) {
// debug("filtering "+subquery+" -> "+query);
return cached.values
// .doOnNext((hint) -> debug("filter["+query+"]: "+hint.getValue()))
.filter((hint) -> 0!=FuzzyMatcher.matchScore(query, hint.getValue().toString()));
} else {
// debug("subquery "+subquery+" cached but is incomplete");
}
}
}
// debug("full search for: "+query);
return getValuesAsycn(javaProject, query);
}
protected abstract Flux<StsValueHint> getValuesAsycn(IJavaProject javaProject, String query);
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 new LimitedTimeCache<>(Duration.ofMinutes(1));
}
public static void restoreDefaults() {
TIMEOUT = DEFAULT_TIMEOUT;
}
}