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    }