21
21
import java .util .Collection ;
22
22
import java .util .Collections ;
23
23
import java .util .Iterator ;
24
+ import java .util .HashSet ;
24
25
import java .util .List ;
25
26
import java .util .NoSuchElementException ;
26
27
import java .util .concurrent .ConcurrentHashMap ;
27
28
import java .util .concurrent .ConcurrentMap ;
29
+ import java .util .function .BiConsumer ;
30
+ import java .util .function .Predicate ;
28
31
import java .util .stream .Collectors ;
29
32
import java .util .stream .Stream ;
30
33
31
34
import com .google .common .base .Objects ;
32
35
import com .google .common .base .Preconditions ;
33
- import com .google .common .base .Throwables ;
34
36
35
37
import org .apache .spark .annotation .Private ;
36
38
43
45
public class InMemoryStore implements KVStore {
44
46
45
47
private Object metadata ;
46
- private ConcurrentMap < Class <?>, InstanceList > data = new ConcurrentHashMap <> ();
48
+ private InMemoryLists inMemoryLists = new InMemoryLists ();
47
49
48
50
@ Override
49
51
public <T > T getMetadata (Class <T > klass ) {
@@ -57,13 +59,13 @@ public void setMetadata(Object value) {
57
59
58
60
@ Override
59
61
public long count (Class <?> type ) {
60
- InstanceList list = data .get (type );
62
+ InstanceList <?> list = inMemoryLists .get (type );
61
63
return list != null ? list .size () : 0 ;
62
64
}
63
65
64
66
@ Override
65
67
public long count (Class <?> type , String index , Object indexedValue ) throws Exception {
66
- InstanceList list = data .get (type );
68
+ InstanceList <?> list = inMemoryLists .get (type );
67
69
int count = 0 ;
68
70
Object comparable = asKey (indexedValue );
69
71
KVTypeInfo .Accessor accessor = list .getIndexAccessor (index );
@@ -77,45 +79,51 @@ public long count(Class<?> type, String index, Object indexedValue) throws Excep
77
79
78
80
@ Override
79
81
public <T > T read (Class <T > klass , Object naturalKey ) {
80
- InstanceList list = data .get (klass );
81
- Object value = list != null ? list .get (naturalKey ) : null ;
82
+ InstanceList < T > list = inMemoryLists .get (klass );
83
+ T value = list != null ? list .get (naturalKey ) : null ;
82
84
if (value == null ) {
83
85
throw new NoSuchElementException ();
84
86
}
85
- return klass . cast ( value ) ;
87
+ return value ;
86
88
}
87
89
88
90
@ Override
89
91
public void write (Object value ) throws Exception {
90
- InstanceList list = data .computeIfAbsent (value .getClass (), key -> {
91
- try {
92
- return new InstanceList (key );
93
- } catch (Exception e ) {
94
- throw Throwables .propagate (e );
95
- }
96
- });
97
- list .put (value );
92
+ inMemoryLists .write (value );
98
93
}
99
94
100
95
@ Override
101
96
public void delete (Class <?> type , Object naturalKey ) {
102
- InstanceList list = data .get (type );
97
+ InstanceList <?> list = inMemoryLists .get (type );
103
98
if (list != null ) {
104
99
list .delete (naturalKey );
105
100
}
106
101
}
107
102
108
103
@ Override
109
104
public <T > KVStoreView <T > view (Class <T > type ){
110
- InstanceList list = data .get (type );
111
- return list != null ? list .view (type )
112
- : new InMemoryView <>(type , Collections .<T >emptyList (), null );
105
+ InstanceList <T > list = inMemoryLists .get (type );
106
+ return list != null ? list .view () : emptyView ();
113
107
}
114
108
115
109
@ Override
116
110
public void close () {
117
111
metadata = null ;
118
- data .clear ();
112
+ inMemoryLists .clear ();
113
+ }
114
+
115
+ @ Override
116
+ public <T > boolean removeAllByIndexValues (
117
+ Class <T > klass ,
118
+ String index ,
119
+ Collection <?> indexValues ) {
120
+ InstanceList <T > list = inMemoryLists .get (klass );
121
+
122
+ if (list != null ) {
123
+ return list .countingRemoveAllByIndexValues (index , indexValues ) > 0 ;
124
+ } else {
125
+ return false ;
126
+ }
119
127
}
120
128
121
129
@ SuppressWarnings ("unchecked" )
@@ -126,64 +134,150 @@ private static Comparable<Object> asKey(Object in) {
126
134
return (Comparable <Object >) in ;
127
135
}
128
136
129
- private static class InstanceList {
137
+ @ SuppressWarnings ("unchecked" )
138
+ private static <T > KVStoreView <T > emptyView () {
139
+ return (InMemoryView <T >) InMemoryView .EMPTY_VIEW ;
140
+ }
141
+
142
+ /**
143
+ * Encapsulates ConcurrentHashMap so that the typing in and out of the map strictly maps a
144
+ * class of type T to an InstanceList of type T.
145
+ */
146
+ private static class InMemoryLists {
147
+ private final ConcurrentMap <Class <?>, InstanceList <?>> data = new ConcurrentHashMap <>();
148
+
149
+ @ SuppressWarnings ("unchecked" )
150
+ public <T > InstanceList <T > get (Class <T > type ) {
151
+ return (InstanceList <T >) data .get (type );
152
+ }
153
+
154
+ @ SuppressWarnings ("unchecked" )
155
+ public <T > void write (T value ) throws Exception {
156
+ InstanceList <T > list =
157
+ (InstanceList <T >) data .computeIfAbsent (value .getClass (), InstanceList ::new );
158
+ list .put (value );
159
+ }
160
+
161
+ public void clear () {
162
+ data .clear ();
163
+ }
164
+ }
165
+
166
+ private static class InstanceList <T > {
167
+
168
+ /**
169
+ * A BiConsumer to control multi-entity removal. We use this in a forEach rather than an
170
+ * iterator because there is a bug in jdk8 which affects remove() on all concurrent map
171
+ * iterators. https://bugs.openjdk.java.net/browse/JDK-8078645
172
+ */
173
+ private static class CountingRemoveIfForEach <T > implements BiConsumer <Comparable <Object >, T > {
174
+ private final ConcurrentMap <Comparable <Object >, T > data ;
175
+ private final Predicate <? super T > filter ;
176
+
177
+ /**
178
+ * Keeps a count of the number of elements removed. This count is not currently surfaced
179
+ * to clients of KVStore as Java's generic removeAll() construct returns only a boolean,
180
+ * but I found it handy to have the count of elements removed while debugging; a count being
181
+ * no more complicated than a boolean, I've retained that behavior here, even though there
182
+ * is no current requirement.
183
+ */
184
+ private int count = 0 ;
185
+
186
+ CountingRemoveIfForEach (
187
+ ConcurrentMap <Comparable <Object >, T > data ,
188
+ Predicate <? super T > filter ) {
189
+ this .data = data ;
190
+ this .filter = filter ;
191
+ }
192
+
193
+ @ Override
194
+ public void accept (Comparable <Object > key , T value ) {
195
+ if (filter .test (value )) {
196
+ if (data .remove (key , value )) {
197
+ count ++;
198
+ }
199
+ }
200
+ }
201
+
202
+ public int count () { return count ; }
203
+ }
130
204
131
205
private final KVTypeInfo ti ;
132
206
private final KVTypeInfo .Accessor naturalKey ;
133
- private final ConcurrentMap <Comparable <Object >, Object > data ;
134
-
135
- private int size ;
207
+ private final ConcurrentMap <Comparable <Object >, T > data ;
136
208
137
- private InstanceList (Class <?> type ) throws Exception {
138
- this .ti = new KVTypeInfo (type );
209
+ private InstanceList (Class <?> klass ) {
210
+ this .ti = new KVTypeInfo (klass );
139
211
this .naturalKey = ti .getAccessor (KVIndex .NATURAL_INDEX_NAME );
140
212
this .data = new ConcurrentHashMap <>();
141
- this .size = 0 ;
142
213
}
143
214
144
215
KVTypeInfo .Accessor getIndexAccessor (String indexName ) {
145
216
return ti .getAccessor (indexName );
146
217
}
147
218
148
- public Object get (Object key ) {
219
+ int countingRemoveAllByIndexValues (String index , Collection <?> indexValues ) {
220
+ Predicate <? super T > filter = getPredicate (ti .getAccessor (index ), indexValues );
221
+ CountingRemoveIfForEach <T > callback = new CountingRemoveIfForEach <>(data , filter );
222
+
223
+ data .forEach (callback );
224
+ return callback .count ();
225
+ }
226
+
227
+ public T get (Object key ) {
149
228
return data .get (asKey (key ));
150
229
}
151
230
152
- public void put (Object value ) throws Exception {
153
- Preconditions .checkArgument (ti .type ().equals (value .getClass ()),
154
- "Unexpected type: %s" , value .getClass ());
155
- if (data .put (asKey (naturalKey .get (value )), value ) == null ) {
156
- size ++;
157
- }
231
+ public void put (T value ) throws Exception {
232
+ data .put (asKey (naturalKey .get (value )), value );
158
233
}
159
234
160
235
public void delete (Object key ) {
161
- if (data .remove (asKey (key )) != null ) {
162
- size --;
163
- }
236
+ data .remove (asKey (key ));
164
237
}
165
238
166
239
public int size () {
167
- return size ;
240
+ return data . size () ;
168
241
}
169
242
170
- @ SuppressWarnings ("unchecked" )
171
- public <T > InMemoryView <T > view (Class <T > type ) {
172
- Preconditions .checkArgument (ti .type ().equals (type ), "Unexpected type: %s" , type );
173
- Collection <T > all = (Collection <T >) data .values ();
174
- return new InMemoryView <>(type , all , ti );
243
+ public InMemoryView <T > view () {
244
+ return new InMemoryView <>(data .values (), ti );
245
+ }
246
+
247
+ private static <T > Predicate <? super T > getPredicate (
248
+ KVTypeInfo .Accessor getter ,
249
+ Collection <?> values ) {
250
+ if (Comparable .class .isAssignableFrom (getter .getType ())) {
251
+ HashSet <?> set = new HashSet <>(values );
252
+
253
+ return (value ) -> set .contains (indexValueForEntity (getter , value ));
254
+ } else {
255
+ HashSet <Comparable > set = new HashSet <>(values .size ());
256
+ for (Object key : values ) {
257
+ set .add (asKey (key ));
258
+ }
259
+ return (value ) -> set .contains (asKey (indexValueForEntity (getter , value )));
260
+ }
175
261
}
176
262
263
+ private static Object indexValueForEntity (KVTypeInfo .Accessor getter , Object entity ) {
264
+ try {
265
+ return getter .get (entity );
266
+ } catch (ReflectiveOperationException e ) {
267
+ throw new RuntimeException (e );
268
+ }
269
+ }
177
270
}
178
271
179
272
private static class InMemoryView <T > extends KVStoreView <T > {
273
+ private static final InMemoryView <?> EMPTY_VIEW =
274
+ new InMemoryView <>(Collections .emptyList (), null );
180
275
181
276
private final Collection <T > elements ;
182
277
private final KVTypeInfo ti ;
183
278
private final KVTypeInfo .Accessor natural ;
184
279
185
- InMemoryView (Class <T > type , Collection <T > elements , KVTypeInfo ti ) {
186
- super (type );
280
+ InMemoryView (Collection <T > elements , KVTypeInfo ti ) {
187
281
this .elements = elements ;
188
282
this .ti = ti ;
189
283
this .natural = ti != null ? ti .getAccessor (KVIndex .NATURAL_INDEX_NAME ) : null ;
@@ -195,34 +289,32 @@ public Iterator<T> iterator() {
195
289
return new InMemoryIterator <>(elements .iterator ());
196
290
}
197
291
198
- try {
199
- KVTypeInfo .Accessor getter = index != null ? ti .getAccessor (index ) : null ;
200
- int modifier = ascending ? 1 : -1 ;
292
+ KVTypeInfo .Accessor getter = index != null ? ti .getAccessor (index ) : null ;
293
+ int modifier = ascending ? 1 : -1 ;
201
294
202
- final List <T > sorted = copyElements ();
203
- Collections .sort (sorted , (e1 , e2 ) -> modifier * compare (e1 , e2 , getter ));
204
- Stream <T > stream = sorted .stream ();
295
+ final List <T > sorted = copyElements ();
296
+ sorted .sort ((e1 , e2 ) -> modifier * compare (e1 , e2 , getter ));
297
+ Stream <T > stream = sorted .stream ();
205
298
206
- if (first != null ) {
207
- stream = stream .filter (e -> modifier * compare (e , getter , first ) >= 0 );
208
- }
209
-
210
- if (last != null ) {
211
- stream = stream .filter (e -> modifier * compare (e , getter , last ) <= 0 );
212
- }
299
+ if (first != null ) {
300
+ Comparable <?> firstKey = asKey (first );
301
+ stream = stream .filter (e -> modifier * compare (e , getter , firstKey ) >= 0 );
302
+ }
213
303
214
- if (skip > 0 ) {
215
- stream = stream .skip (skip );
216
- }
304
+ if (last != null ) {
305
+ Comparable <?> lastKey = asKey (last );
306
+ stream = stream .filter (e -> modifier * compare (e , getter , lastKey ) <= 0 );
307
+ }
217
308
218
- if (max < sorted . size () ) {
219
- stream = stream .limit (( int ) max );
220
- }
309
+ if (skip > 0 ) {
310
+ stream = stream .skip ( skip );
311
+ }
221
312
222
- return new InMemoryIterator <>(stream .iterator ());
223
- } catch (Exception e ) {
224
- throw Throwables .propagate (e );
313
+ if (max < sorted .size ()) {
314
+ stream = stream .limit ((int ) max );
225
315
}
316
+
317
+ return new InMemoryIterator <>(stream .iterator ());
226
318
}
227
319
228
320
/**
@@ -232,9 +324,10 @@ private List<T> copyElements() {
232
324
if (parent != null ) {
233
325
KVTypeInfo .Accessor parentGetter = ti .getParentAccessor (index );
234
326
Preconditions .checkArgument (parentGetter != null , "Parent filter for non-child index." );
327
+ Comparable <?> parentKey = asKey (parent );
235
328
236
329
return elements .stream ()
237
- .filter (e -> compare (e , parentGetter , parent ) == 0 )
330
+ .filter (e -> compare (e , parentGetter , parentKey ) == 0 )
238
331
.collect (Collectors .toList ());
239
332
} else {
240
333
return new ArrayList <>(elements );
@@ -243,24 +336,23 @@ private List<T> copyElements() {
243
336
244
337
private int compare (T e1 , T e2 , KVTypeInfo .Accessor getter ) {
245
338
try {
246
- int diff = compare (e1 , getter , getter .get (e2 ));
339
+ int diff = compare (e1 , getter , asKey ( getter .get (e2 ) ));
247
340
if (diff == 0 && getter != natural ) {
248
- diff = compare (e1 , natural , natural .get (e2 ));
341
+ diff = compare (e1 , natural , asKey ( natural .get (e2 ) ));
249
342
}
250
343
return diff ;
251
- } catch (Exception e ) {
252
- throw Throwables . propagate (e );
344
+ } catch (ReflectiveOperationException e ) {
345
+ throw new RuntimeException (e );
253
346
}
254
347
}
255
348
256
- private int compare (T e1 , KVTypeInfo .Accessor getter , Object v2 ) {
349
+ private int compare (T e1 , KVTypeInfo .Accessor getter , Comparable <?> v2 ) {
257
350
try {
258
- return asKey (getter .get (e1 )).compareTo (asKey ( v2 ) );
259
- } catch (Exception e ) {
260
- throw Throwables . propagate (e );
351
+ return asKey (getter .get (e1 )).compareTo (v2 );
352
+ } catch (ReflectiveOperationException e ) {
353
+ throw new RuntimeException (e );
261
354
}
262
355
}
263
-
264
356
}
265
357
266
358
private static class InMemoryIterator <T > implements KVStoreIterator <T > {
0 commit comments