/*
* SonarQube
* Copyright (C) 2009-2017 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.scanner.storage;
import com.google.common.collect.Sets;
import com.persistit.Exchange;
import com.persistit.Key;
import com.persistit.KeyFilter;
import com.persistit.exception.PersistitException;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Set;
import javax.annotation.CheckForNull;
import org.apache.commons.lang.builder.ToStringBuilder;
/**
* <p>
* This storage is not thread-safe, due to direct usage of {@link com.persistit.Exchange}
* </p>
*/
public class Storage<V> {
private final String name;
private final Exchange exchange;
Storage(String name, Exchange exchange) {
this.name = name;
this.exchange = exchange;
}
public Storage<V> put(Object key, V value) {
resetKey(key);
return doPut(value);
}
public Storage<V> put(Object firstKey, Object secondKey, V value) {
resetKey(firstKey, secondKey);
return doPut(value);
}
public Storage<V> put(Object firstKey, Object secondKey, Object thirdKey, V value) {
resetKey(firstKey, secondKey, thirdKey);
return doPut(value);
}
public Storage<V> put(Object[] key, V value) {
resetKey(key);
return doPut(value);
}
private Storage<V> doPut(V value) {
try {
exchange.getValue().put(value);
exchange.store();
return this;
} catch (Exception e) {
throw new IllegalStateException("Fail to put element in the storage '" + name + "'", e);
}
}
/**
* Returns the value object associated with keys, or null if not found.
*/
public V get(Object key) {
resetKey(key);
return doGet();
}
/**
* Returns the value object associated with keys, or null if not found.
*/
@CheckForNull
public V get(Object firstKey, Object secondKey) {
resetKey(firstKey, secondKey);
return doGet();
}
/**
* Returns the value object associated with keys, or null if not found.
*/
@CheckForNull
public V get(Object firstKey, Object secondKey, Object thirdKey) {
resetKey(firstKey, secondKey, thirdKey);
return doGet();
}
/**
* Returns the value object associated with keys, or null if not found.
*/
@CheckForNull
public V get(Object[] key) {
resetKey(key);
return doGet();
}
@SuppressWarnings("unchecked")
@CheckForNull
private V doGet() {
try {
exchange.fetch();
if (!exchange.getValue().isDefined()) {
return null;
}
return (V) exchange.getValue().get();
} catch (Exception e) {
// TODO add parameters to message
throw new IllegalStateException("Fail to get element from cache " + name, e);
}
}
public boolean containsKey(Object key) {
resetKey(key);
return doContainsKey();
}
public boolean containsKey(Object firstKey, Object secondKey) {
resetKey(firstKey, secondKey);
return doContainsKey();
}
public boolean containsKey(Object firstKey, Object secondKey, Object thirdKey) {
resetKey(firstKey, secondKey, thirdKey);
return doContainsKey();
}
public boolean containsKey(Object[] key) {
resetKey(key);
return doContainsKey();
}
private boolean doContainsKey() {
try {
exchange.fetch();
return exchange.isValueDefined();
} catch (Exception e) {
// TODO add parameters to message
throw new IllegalStateException("Fail to check if element is in cache " + name, e);
}
}
public boolean remove(Object key) {
resetKey(key);
return doRemove();
}
public boolean remove(Object firstKey, Object secondKey) {
resetKey(firstKey, secondKey);
return doRemove();
}
public boolean remove(Object firstKey, Object secondKey, Object thirdKey) {
resetKey(firstKey, secondKey, thirdKey);
return doRemove();
}
public boolean remove(Object[] key) {
resetKey(key);
return doRemove();
}
private boolean doRemove() {
try {
return exchange.remove();
} catch (Exception e) {
// TODO add parameters to message
throw new IllegalStateException("Fail to get element from cache " + name, e);
}
}
/**
* Removes everything in the specified group.
*
* @param group The group name.
*/
public Storage<V> clear(Object key) {
resetKey(key);
return doClear();
}
public Storage<V> clear(Object firstKey, Object secondKey) {
resetKey(firstKey, secondKey);
return doClear();
}
public Storage<V> clear(Object firstKey, Object secondKey, Object thirdKey) {
resetKey(firstKey, secondKey, thirdKey);
return doClear();
}
public Storage<V> clear(Object[] key) {
resetKey(key);
return doClear();
}
private Storage<V> doClear() {
try {
Key to = new Key(exchange.getKey());
to.append(Key.AFTER);
exchange.removeKeyRange(exchange.getKey(), to);
return this;
} catch (Exception e) {
throw new IllegalStateException("Fail to clear values from cache " + name, e);
}
}
/**
* Clears the default as well as all group caches.
*/
public void clear() {
try {
exchange.clear();
exchange.removeAll();
} catch (Exception e) {
throw new IllegalStateException("Fail to clear cache", e);
}
}
/**
* Returns the set of cache keys associated with this group.
* TODO implement a lazy-loading equivalent with Iterator/Iterable
*
* @param group The group.
* @return The set of cache keys for this group.
*/
@SuppressWarnings("rawtypes")
public Set keySet(Object key) {
try {
Set<Object> keys = Sets.newLinkedHashSet();
exchange.clear();
Exchange iteratorExchange = new Exchange(exchange);
iteratorExchange.append(key);
iteratorExchange.append(Key.BEFORE);
while (iteratorExchange.next(false)) {
keys.add(iteratorExchange.getKey().indexTo(-1).decode());
}
return keys;
} catch (Exception e) {
throw new IllegalStateException("Fail to get keys from cache " + name, e);
}
}
@SuppressWarnings("rawtypes")
public Set keySet(Object firstKey, Object secondKey) {
try {
Set<Object> keys = Sets.newLinkedHashSet();
exchange.clear();
Exchange iteratorExchange = new Exchange(exchange);
iteratorExchange.append(firstKey);
iteratorExchange.append(secondKey);
iteratorExchange.append(Key.BEFORE);
while (iteratorExchange.next(false)) {
keys.add(iteratorExchange.getKey().indexTo(-1).decode());
}
return keys;
} catch (Exception e) {
throw new IllegalStateException("Fail to get keys from cache " + name, e);
}
}
/**
* Returns the set of keys associated with this cache.
*
* @return The set containing the keys for this cache.
*/
public Set<Object> keySet() {
try {
Set<Object> keys = Sets.newLinkedHashSet();
exchange.clear();
Exchange iteratorExchange = new Exchange(exchange);
iteratorExchange.append(Key.BEFORE);
while (iteratorExchange.next(false)) {
keys.add(iteratorExchange.getKey().indexTo(-1).decode());
}
return keys;
} catch (Exception e) {
throw new IllegalStateException("Fail to get keys from cache " + name, e);
}
}
/**
* Lazy-loading values for given keys
*/
public Iterable<V> values(Object firstKey, Object secondKey) {
return new ValueIterable<>(exchange, firstKey, secondKey);
}
/**
* Lazy-loading values for a given key
*/
public Iterable<V> values(Object firstKey) {
return new ValueIterable<>(exchange, firstKey);
}
/**
* Lazy-loading values
*/
public Iterable<V> values() {
return new ValueIterable<>(exchange);
}
public Iterable<Entry<V>> entries() {
return new EntryIterable<>(exchange);
}
public Iterable<Entry<V>> entries(Object firstKey) {
return new EntryIterable<>(exchange, firstKey);
}
private void resetKey(Object key) {
exchange.clear();
exchange.append(key);
}
private void resetKey(Object first, Object second) {
exchange.clear();
exchange.append(first).append(second);
}
private void resetKey(Object first, Object second, Object third) {
exchange.clear();
exchange.append(first).append(second).append(third);
}
private void resetKey(Object[] keys) {
exchange.clear();
for (Object o : keys) {
exchange.append(o);
}
}
//
// LAZY ITERATORS AND ITERABLES
//
private static class ValueIterable<T> implements Iterable<T> {
private final Exchange originExchange;
private final Object[] keys;
private ValueIterable(Exchange originExchange, Object... keys) {
this.originExchange = originExchange;
this.keys = keys;
}
@Override
public Iterator<T> iterator() {
originExchange.clear();
KeyFilter filter = new KeyFilter();
for (Object key : keys) {
originExchange.append(key);
filter = filter.append(KeyFilter.simpleTerm(key));
}
originExchange.append(Key.BEFORE);
Exchange iteratorExchange = new Exchange(originExchange);
return new ValueIterator<>(iteratorExchange, filter);
}
}
private static class ValueIterator<T> implements Iterator<T> {
private final Exchange exchange;
private final KeyFilter keyFilter;
private ValueIterator(Exchange exchange, KeyFilter keyFilter) {
this.exchange = exchange;
this.keyFilter = keyFilter;
}
@Override
public boolean hasNext() {
try {
return exchange.hasNext(keyFilter);
} catch (PersistitException e) {
throw new IllegalStateException(e);
}
}
@SuppressWarnings("unchecked")
@Override
public T next() {
try {
exchange.next(keyFilter);
} catch (PersistitException e) {
throw new IllegalStateException(e);
}
if (exchange.getValue().isDefined()) {
return (T) exchange.getValue().get();
}
throw new NoSuchElementException();
}
@Override
public void remove() {
throw new UnsupportedOperationException("Removing an item is not supported");
}
}
private static class EntryIterable<T> implements Iterable<Entry<T>> {
private final Exchange originExchange;
private final Object[] keys;
private EntryIterable(Exchange originExchange, Object... keys) {
this.originExchange = originExchange;
this.keys = keys;
}
@Override
public Iterator<Entry<T>> iterator() {
originExchange.clear();
KeyFilter filter = new KeyFilter();
for (Object key : keys) {
originExchange.append(key);
filter = filter.append(KeyFilter.simpleTerm(key));
}
originExchange.append(Key.BEFORE);
Exchange iteratorExchange = new Exchange(originExchange);
return new EntryIterator<>(iteratorExchange, filter);
}
}
private static class EntryIterator<T> implements Iterator<Entry<T>> {
private final Exchange exchange;
private final KeyFilter keyFilter;
private EntryIterator(Exchange exchange, KeyFilter keyFilter) {
this.exchange = exchange;
this.keyFilter = keyFilter;
}
@Override
public boolean hasNext() {
try {
return exchange.hasNext(keyFilter);
} catch (PersistitException e) {
throw new IllegalStateException(e);
}
}
@SuppressWarnings("unchecked")
@Override
public Entry<T> next() {
try {
exchange.next(keyFilter);
} catch (PersistitException e) {
throw new IllegalStateException(e);
}
if (exchange.getValue().isDefined()) {
T value = (T) exchange.getValue().get();
Key key = exchange.getKey();
Object[] array = new Object[key.getDepth()];
for (int i = 0; i < key.getDepth(); i++) {
array[i] = key.indexTo(i - key.getDepth()).decode();
}
return new Entry<>(array, value);
}
throw new NoSuchElementException();
}
@Override
public void remove() {
throw new UnsupportedOperationException("Removing an item is not supported");
}
}
public static class Entry<V> {
private final Object[] key;
private final V value;
Entry(Object[] key, V value) {
this.key = key;
this.value = value;
}
public Object[] key() {
return key;
}
public V value() {
return value;
}
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this);
}
}
}