/*
* Copyright 2013, 2014 Deutsche Nationalbibliothek
*
* Licensed under the Apache License, Version 2.0 the "License";
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.culturegraph.mf.commons.tries;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
/**
* A simple Trie, which accepts a trailing wildcard
*
* @author Markus Michael Geipel
* @author Pascal Christoph
*
* @param <P>
* type of value stored
*/
public final class WildcardTrie<P> {
public static final char STAR_WILDCARD = '*';
public static final char Q_WILDCARD = '?';
public static final String OR_STRING = "|";
private static final Pattern OR_PATTERN = Pattern.compile(OR_STRING, Pattern.LITERAL);
private final Node<P> root = new Node<P>();
private Set<Node<P>> nodes = new HashSet<Node<P>>();
private Set<Node<P>> nextNodes = new HashSet<Node<P>>();
/**
* inserts keys into the try. Use '|' to concatenate. Use '*' (0,inf) and
* '?' (1,1) to express wildcards.
*
* @param keys
* @param value
*/
public void put(final String keys, final P value) {
if (keys.contains(OR_STRING)) {
final String[] keysSplit = OR_PATTERN.split(keys);
for (String string : keysSplit) {
simplyPut(string, value);
}
} else {
simplyPut(keys, value);
}
}
private void simplyPut(final String key, final P value) {
final int length = key.length();
Node<P> node = root;
Node<P> next = null;
for (int i = 0; i < length; ++i) {
next = node.getNext(key.charAt(i));
if (next == null) {
next = node.addNext(key.charAt(i));
}
node = next;
}
node.addValue(value);
}
public List<P> get(final String key) {
nodes.add(root);
final int length = key.length();
for (int i = 0; i < length; ++i) {
for (Node<P> node : nodes) {
Node<P> temp;
temp = node.getNext(key.charAt(i));
if (temp != null) {
nextNodes.add(temp);
}
temp = node.getNext(Q_WILDCARD);
if (temp != null) {
nextNodes.add(temp);
}
temp = node.getNext(STAR_WILDCARD);
if (temp != null) {
nextNodes.add(temp);
if (temp != node) {
temp = temp.getNext(key.charAt(i));
if (temp != null) {
nextNodes.add(temp);
}
}
}
}
nodes.clear();
final Set<Node<P>> temp = nodes;
nodes = nextNodes;
nextNodes = temp;
}
List<P> matches = Collections.emptyList();
for (Node<P> node : nodes) {
final Set<P> values = node.getValues();
if (!values.isEmpty()) {
if (matches == Collections.emptyList()) {
matches = new ArrayList<P>();
}
matches.addAll(values);
}
}
nodes.clear();
nextNodes.clear();
return matches;
}
/**
*
* @param <T>
*/
private final class Node<T> {
private Set<T> values = Collections.emptySet();
private final CharMap<Node<T>> links = new CharMap<Node<T>>();
protected Node() {
// nothing to do
}
public Node<T> addNext(final char key) {
final Node<T> next = new Node<T>();
links.put(key, next);
if (key == STAR_WILDCARD) {
next.links.put(STAR_WILDCARD, next);
}
return next;
}
public void addValue(final T value) {
if (values == Collections.emptySet()) {
values = new LinkedHashSet<T>();
}
this.values.add(value);
}
public Set<T> getValues() {
return values;
}
public Node<T> getNext(final char key) {
return links.get(key);
}
}
}