package photato.helpers;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class PartialStringIndex<T> {
private static class Node<T> {
public final char c;
public final Set<T> values;
public int weight;
public Node<T> firstChild;
public Node<T> neighbor;
public Node() {
this('*');
}
public Node(char c) {
this.c = c;
this.values = new HashSet<>();
this.weight = 1;
}
public Node<T> findChild(char c) {
if (this.firstChild == null) {
return null;
} else {
Node<T> n = this.firstChild;
while (n != null && n.c != c) {
n = n.neighbor;
}
return n;
}
}
public void addChild(char c, Node<T> newChild) {
if (this.firstChild == null) {
this.firstChild = newChild;
} else {
Node<T> lastChild = this.firstChild;
while (lastChild.neighbor != null) {
lastChild = lastChild.neighbor;
}
lastChild.neighbor = newChild;
}
}
public void removeChild(char c) {
Node<T> previous = null;
Node<T> current = this.firstChild;
while (current != null) {
if (current.c == c) {
if (current == this.firstChild) {
this.firstChild = current.neighbor;
} else {
previous.neighbor = current.neighbor;
}
break;
} else {
previous = current;
current = current.neighbor;
}
}
}
}
private final ReentrantReadWriteLock lock;
private final Map<T, Set<String>> valuesMap;
private final boolean prefixOnlyMode;
private Node<T> rootNode;
public PartialStringIndex() {
this(false);
}
public PartialStringIndex(boolean prefixOnlyMode) {
this.rootNode = new Node<>();
this.valuesMap = new HashMap<>();
this.lock = new ReentrantReadWriteLock();
this.prefixOnlyMode = prefixOnlyMode;
}
public void add(String indexKey, T value) {
if (indexKey == null || indexKey.isEmpty()) {
throw new IllegalArgumentException("Key must not be empty");
}
if (value == null) {
throw new IllegalArgumentException("Value must not be null");
}
this.lock.writeLock().lock();
try {
this.rootNode.values.add(value);
if (!this.valuesMap.containsKey(value)) {
this.valuesMap.put(value, new HashSet<String>());
}
this.valuesMap.get(value).add(indexKey);
for (int i = 0; i < (this.prefixOnlyMode ? 1 : indexKey.length()); i++) {
Node<T> currentNode = this.rootNode;
for (int j = i; j < indexKey.length(); j++) {
char c = indexKey.charAt(j);
Node<T> child = currentNode.findChild(c);
if (child == null) {
Node<T> newNode = new Node<>(c);
currentNode.addChild(c, newNode);
currentNode = newNode;
} else {
child.weight++;
currentNode = child;
}
}
currentNode.values.add(value);
}
} finally {
this.lock.writeLock().unlock();
}
}
public Collection<T> findContains(String key) {
if (key == null || key.isEmpty()) {
throw new IllegalArgumentException("Key must not be empty");
}
this.lock.readLock().lock();
try {
Node<T> currentNode = this.rootNode;
for (int i = 0; i < key.length(); i++) {
char c = key.charAt(i);
Node node = currentNode.findChild(c);
if (node == null) {
return new HashSet<>();
} else {
currentNode = node;
}
}
// From now on, exploration of all nodes to add them to the result
Set<T> result = new HashSet<>();
Queue<Node<T>> toExplore = new LinkedList<>();
toExplore.add(currentNode);
while (!toExplore.isEmpty()) {
Node<T> n = toExplore.remove();
result.addAll(n.values);
Node child = n.firstChild;
while (child != null) {
toExplore.add(child);
child = child.neighbor;
}
}
return result;
} finally {
this.lock.readLock().unlock();
}
}
public void remove(T value) {
if (value == null) {
throw new IllegalArgumentException("Value must not be null");
}
this.lock.writeLock().lock();
try {
if (this.valuesMap.containsKey(value)) {
for (String key : this.valuesMap.get(value)) {
this.rootNode.values.remove(value);
for (int i = 0; i < (this.prefixOnlyMode ? 1 : key.length()); i++) {
Node<T> currentNode = this.rootNode;
for (int j = i; j < key.length(); j++) {
char c = key.charAt(j);
Node<T> n = currentNode.findChild(c);
if (n != null) {
n.weight--;
if (n.weight == 0) {
currentNode.removeChild(c);
break;
} else {
currentNode = n;
}
} else {
break;
}
}
currentNode.values.remove(value);
}
}
this.valuesMap.remove(value);
}
} finally {
this.lock.writeLock().unlock();
}
}
public Collection<T> values() {
this.lock.readLock().lock();
try {
return this.rootNode.values;
} finally {
this.lock.readLock().unlock();
}
}
public int size() {
this.lock.readLock().lock();
try {
return this.rootNode.values.size();
} finally {
this.lock.readLock().unlock();
}
}
public void clear() {
this.lock.writeLock().lock();
try {
this.rootNode = new Node<>();
this.valuesMap.clear();
} finally {
this.lock.writeLock().unlock();
}
}
}