001 /*
002 * SonarQube, open source software quality management tool.
003 * Copyright (C) 2008-2014 SonarSource
004 * mailto:contact AT sonarsource DOT com
005 *
006 * SonarQube is free software; you can redistribute it and/or
007 * modify it under the terms of the GNU Lesser General Public
008 * License as published by the Free Software Foundation; either
009 * version 3 of the License, or (at your option) any later version.
010 *
011 * SonarQube is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014 * Lesser General Public License for more details.
015 *
016 * You should have received a copy of the GNU Lesser General Public License
017 * along with this program; if not, write to the Free Software Foundation,
018 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
019 */
020 package org.sonar.batch.index;
021
022 import com.google.common.collect.Sets;
023 import com.persistit.Exchange;
024 import com.persistit.Key;
025 import com.persistit.KeyFilter;
026 import com.persistit.exception.PersistitException;
027 import org.apache.commons.lang.builder.ToStringBuilder;
028
029 import javax.annotation.CheckForNull;
030
031 import java.util.Iterator;
032 import java.util.NoSuchElementException;
033 import java.util.Set;
034
035 /**
036 * <p>
037 * This cache is not thread-safe, due to direct usage of {@link com.persistit.Exchange}
038 * </p>
039 */
040 public class Cache<V> {
041
042 private final String name;
043 private final Exchange exchange;
044
045 Cache(String name, Exchange exchange) {
046 this.name = name;
047 this.exchange = exchange;
048 }
049
050 public Cache<V> put(Object key, V value) {
051 resetKey(key);
052 return doPut(value);
053 }
054
055 public Cache<V> put(Object firstKey, Object secondKey, V value) {
056 resetKey(firstKey, secondKey);
057 return doPut(value);
058 }
059
060 public Cache<V> put(Object firstKey, Object secondKey, Object thirdKey, V value) {
061 resetKey(firstKey, secondKey, thirdKey);
062 return doPut(value);
063 }
064
065 public Cache<V> put(Object[] key, V value) {
066 resetKey(key);
067 return doPut(value);
068 }
069
070 private Cache<V> doPut(V value) {
071 try {
072 exchange.getValue().put(value);
073 exchange.store();
074 return this;
075 } catch (Exception e) {
076 throw new IllegalStateException("Fail to put element in the cache " + name, e);
077 }
078 }
079
080 /**
081 * Returns the value object associated with keys, or null if not found.
082 */
083 public V get(Object key) {
084 resetKey(key);
085 return doGet();
086 }
087
088 /**
089 * Returns the value object associated with keys, or null if not found.
090 */
091 @CheckForNull
092 public V get(Object firstKey, Object secondKey) {
093 resetKey(firstKey, secondKey);
094 return doGet();
095 }
096
097 /**
098 * Returns the value object associated with keys, or null if not found.
099 */
100 @CheckForNull
101 public V get(Object firstKey, Object secondKey, Object thirdKey) {
102 resetKey(firstKey, secondKey, thirdKey);
103 return doGet();
104 }
105
106 /**
107 * Returns the value object associated with keys, or null if not found.
108 */
109 @CheckForNull
110 public V get(Object[] key) {
111 resetKey(key);
112 return doGet();
113 }
114
115 @SuppressWarnings("unchecked")
116 @CheckForNull
117 private V doGet() {
118 try {
119 exchange.fetch();
120 if (!exchange.getValue().isDefined()) {
121 return null;
122 }
123 return (V) exchange.getValue().get();
124 } catch (Exception e) {
125 // TODO add parameters to message
126 throw new IllegalStateException("Fail to get element from cache " + name, e);
127 }
128 }
129
130 public boolean containsKey(Object key) {
131 resetKey(key);
132 return doContainsKey();
133 }
134
135 public boolean containsKey(Object firstKey, Object secondKey) {
136 resetKey(firstKey, secondKey);
137 return doContainsKey();
138 }
139
140 public boolean containsKey(Object firstKey, Object secondKey, Object thirdKey) {
141 resetKey(firstKey, secondKey, thirdKey);
142 return doContainsKey();
143 }
144
145 public boolean containsKey(Object[] key) {
146 resetKey(key);
147 return doContainsKey();
148 }
149
150 private boolean doContainsKey() {
151 try {
152 exchange.fetch();
153 return exchange.isValueDefined();
154 } catch (Exception e) {
155 // TODO add parameters to message
156 throw new IllegalStateException("Fail to check if element is in cache " + name, e);
157 }
158 }
159
160 public boolean remove(Object key) {
161 resetKey(key);
162 return doRemove();
163 }
164
165 public boolean remove(Object firstKey, Object secondKey) {
166 resetKey(firstKey, secondKey);
167 return doRemove();
168 }
169
170 public boolean remove(Object firstKey, Object secondKey, Object thirdKey) {
171 resetKey(firstKey, secondKey, thirdKey);
172 return doRemove();
173 }
174
175 public boolean remove(Object[] key) {
176 resetKey(key);
177 return doRemove();
178 }
179
180 private boolean doRemove() {
181 try {
182 return exchange.remove();
183 } catch (Exception e) {
184 // TODO add parameters to message
185 throw new IllegalStateException("Fail to get element from cache " + name, e);
186 }
187 }
188
189 /**
190 * Removes everything in the specified group.
191 *
192 * @param group The group name.
193 */
194 public Cache<V> clear(Object key) {
195 resetKey(key);
196 return doClear();
197 }
198
199 public Cache<V> clear(Object firstKey, Object secondKey) {
200 resetKey(firstKey, secondKey);
201 return doClear();
202 }
203
204 public Cache<V> clear(Object firstKey, Object secondKey, Object thirdKey) {
205 resetKey(firstKey, secondKey, thirdKey);
206 return doClear();
207 }
208
209 public Cache<V> clear(Object[] key) {
210 resetKey(key);
211 return doClear();
212 }
213
214 private Cache<V> doClear() {
215 try {
216 Key to = new Key(exchange.getKey());
217 to.append(Key.AFTER);
218 exchange.removeKeyRange(exchange.getKey(), to);
219 return this;
220 } catch (Exception e) {
221 throw new IllegalStateException("Fail to clear values from cache " + name, e);
222 }
223 }
224
225 /**
226 * Clears the default as well as all group caches.
227 */
228 public void clear() {
229 try {
230 exchange.clear();
231 exchange.removeAll();
232 } catch (Exception e) {
233 throw new IllegalStateException("Fail to clear cache", e);
234 }
235 }
236
237 /**
238 * Returns the set of cache keys associated with this group.
239 * TODO implement a lazy-loading equivalent with Iterator/Iterable
240 *
241 * @param group The group.
242 * @return The set of cache keys for this group.
243 */
244 @SuppressWarnings("rawtypes")
245 public Set keySet(Object key) {
246 try {
247 Set<Object> keys = Sets.newLinkedHashSet();
248 exchange.clear();
249 Exchange iteratorExchange = new Exchange(exchange);
250 iteratorExchange.append(key);
251 iteratorExchange.append(Key.BEFORE);
252 while (iteratorExchange.next(false)) {
253 keys.add(iteratorExchange.getKey().indexTo(-1).decode());
254 }
255 return keys;
256 } catch (Exception e) {
257 throw new IllegalStateException("Fail to get keys from cache " + name, e);
258 }
259 }
260
261 @SuppressWarnings("rawtypes")
262 public Set keySet(Object firstKey, Object secondKey) {
263 try {
264 Set<Object> keys = Sets.newLinkedHashSet();
265 exchange.clear();
266 Exchange iteratorExchange = new Exchange(exchange);
267 iteratorExchange.append(firstKey);
268 iteratorExchange.append(secondKey);
269 iteratorExchange.append(Key.BEFORE);
270 while (iteratorExchange.next(false)) {
271 keys.add(iteratorExchange.getKey().indexTo(-1).decode());
272 }
273 return keys;
274 } catch (Exception e) {
275 throw new IllegalStateException("Fail to get keys from cache " + name, e);
276 }
277 }
278
279 /**
280 * Returns the set of keys associated with this cache.
281 *
282 * @return The set containing the keys for this cache.
283 */
284 public Set<Object> keySet() {
285 try {
286 Set<Object> keys = Sets.newLinkedHashSet();
287 exchange.clear();
288 Exchange iteratorExchange = new Exchange(exchange);
289 iteratorExchange.append(Key.BEFORE);
290 while (iteratorExchange.next(false)) {
291 keys.add(iteratorExchange.getKey().indexTo(-1).decode());
292 }
293 return keys;
294 } catch (Exception e) {
295 throw new IllegalStateException("Fail to get keys from cache " + name, e);
296 }
297 }
298
299 /**
300 * Lazy-loading values for given keys
301 */
302 public Iterable<V> values(Object firstKey, Object secondKey) {
303 return new ValueIterable<V>(exchange, firstKey, secondKey);
304 }
305
306 private IllegalStateException failToGetValues(Exception e) {
307 return new IllegalStateException("Fail to get values from cache " + name, e);
308 }
309
310 /**
311 * Lazy-loading values for a given key
312 */
313 public Iterable<V> values(Object firstKey) {
314 return new ValueIterable<V>(exchange, firstKey);
315 }
316
317 /**
318 * Lazy-loading values
319 */
320 public Iterable<V> values() {
321 return new ValueIterable<V>(exchange);
322 }
323
324 public Iterable<Entry<V>> entries() {
325 return new EntryIterable<V>(exchange);
326 }
327
328 public Iterable<Entry<V>> entries(Object firstKey) {
329 return new EntryIterable<V>(exchange, firstKey);
330 }
331
332 private void resetKey(Object key) {
333 exchange.clear();
334 exchange.append(key);
335 }
336
337 private void resetKey(Object first, Object second) {
338 exchange.clear();
339 exchange.append(first).append(second);
340 }
341
342 private void resetKey(Object first, Object second, Object third) {
343 exchange.clear();
344 exchange.append(first).append(second).append(third);
345 }
346
347 private void resetKey(Object[] keys) {
348 exchange.clear();
349 for (Object o : keys) {
350 exchange.append(o);
351 }
352 }
353
354 //
355 // LAZY ITERATORS AND ITERABLES
356 //
357
358 private static class ValueIterable<T> implements Iterable<T> {
359 private final Exchange originExchange;
360 private final Object[] keys;
361
362 private ValueIterable(Exchange originExchange, Object... keys) {
363 this.originExchange = originExchange;
364 this.keys = keys;
365 }
366
367 @Override
368 public Iterator<T> iterator() {
369 originExchange.clear();
370 KeyFilter filter = new KeyFilter();
371 for (Object key : keys) {
372 originExchange.append(key);
373 filter = filter.append(KeyFilter.simpleTerm(key));
374 }
375 originExchange.append(Key.BEFORE);
376 Exchange iteratorExchange = new Exchange(originExchange);
377 return new ValueIterator<T>(iteratorExchange, filter);
378 }
379 }
380
381 private static class ValueIterator<T> implements Iterator<T> {
382 private final Exchange exchange;
383 private final KeyFilter keyFilter;
384
385 private ValueIterator(Exchange exchange, KeyFilter keyFilter) {
386 this.exchange = exchange;
387 this.keyFilter = keyFilter;
388 }
389
390 @Override
391 public boolean hasNext() {
392 try {
393 return exchange.hasNext(keyFilter);
394 } catch (PersistitException e) {
395 throw new IllegalStateException(e);
396 }
397 }
398
399 @SuppressWarnings("unchecked")
400 @Override
401 public T next() {
402 try {
403 exchange.next(keyFilter);
404 } catch (PersistitException e) {
405 throw new IllegalStateException(e);
406 }
407 if (exchange.getValue().isDefined()) {
408 return (T) exchange.getValue().get();
409 }
410 throw new NoSuchElementException();
411 }
412
413 @Override
414 public void remove() {
415 throw new UnsupportedOperationException("Removing an item is not supported");
416 }
417 }
418
419 private static class EntryIterable<T> implements Iterable<Entry<T>> {
420 private final Exchange originExchange;
421 private final Object[] keys;
422
423 private EntryIterable(Exchange originExchange, Object... keys) {
424 this.originExchange = originExchange;
425 this.keys = keys;
426 }
427
428 @Override
429 public Iterator<Entry<T>> iterator() {
430 originExchange.clear();
431 KeyFilter filter = new KeyFilter();
432 for (Object key : keys) {
433 originExchange.append(key);
434 filter = filter.append(KeyFilter.simpleTerm(key));
435 }
436 originExchange.append(Key.BEFORE);
437 Exchange iteratorExchange = new Exchange(originExchange);
438 return new EntryIterator<T>(iteratorExchange, filter);
439 }
440 }
441
442 private static class EntryIterator<T> implements Iterator<Entry<T>> {
443 private final Exchange exchange;
444 private final KeyFilter keyFilter;
445
446 private EntryIterator(Exchange exchange, KeyFilter keyFilter) {
447 this.exchange = exchange;
448 this.keyFilter = keyFilter;
449 }
450
451 @Override
452 public boolean hasNext() {
453 try {
454 return exchange.hasNext(keyFilter);
455 } catch (PersistitException e) {
456 throw new IllegalStateException(e);
457 }
458 }
459
460 @SuppressWarnings("unchecked")
461 @Override
462 public Entry<T> next() {
463 try {
464 exchange.next(keyFilter);
465 } catch (PersistitException e) {
466 throw new IllegalStateException(e);
467 }
468 if (exchange.getValue().isDefined()) {
469 T value = (T) exchange.getValue().get();
470 Key key = exchange.getKey();
471 Object[] array = new Object[key.getDepth()];
472 for (int i = 0; i < key.getDepth(); i++) {
473 array[i] = key.indexTo(i - key.getDepth()).decode();
474 }
475 return new Entry<T>(array, value);
476 }
477 throw new NoSuchElementException();
478 }
479
480 @Override
481 public void remove() {
482 throw new UnsupportedOperationException("Removing an item is not supported");
483 }
484 }
485
486 public static class Entry<V> {
487 private final Object[] key;
488 private final V value;
489
490 Entry(Object[] key, V value) {
491 this.key = key;
492 this.value = value;
493 }
494
495 public Object[] key() {
496 return key;
497 }
498
499 @CheckForNull
500 public V value() {
501 return value;
502 }
503
504 @Override
505 public String toString() {
506 return ToStringBuilder.reflectionToString(this);
507 }
508 }
509
510 }