/*
* Copyright 2010 NCHOVY
*
* 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.krakenapps.util;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Queue;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicReference;
public class DirectoryMap<T> implements ConcurrentMap<String, T> {
private Node<T> root = new Node<T>(0);
@Override
public T put(String key, T value) {
String[] path = key.split("/");
return root.put(path, value);
}
@Override
public T get(Object key) {
if (!String.class.isInstance(key))
return null;
String[] path = ((String) key).split("/");
return root.get(path);
}
@Override
public T putIfAbsent(String key, T value) {
String[] path = key.split("/");
return root.putIfAbsent(path, value);
}
@Override
public int size() {
return root.size();
}
/**
* Get all items of selected directory.
*
* @param path
* selected directory.
*
*/
public Set<Entry<String, T>> getItems(String path) {
Node<T> current = currentDir(path);
if (current == null)
return Collections.emptySet();
TreeMap<String, T> m = new TreeMap<String, T>(current.itemMap);
return m.entrySet();
}
public Set<String> keySet(String path) {
Node<T> current = currentDir(path);
if (current == null)
return Collections.emptySet();
return new TreeSet<String>(current.subnodeMap.keySet());
}
private Node<T> currentDir(String keyDir) {
String[] dirs = keyDir.split("/");
Node<T> current = root;
for (String dir : dirs) {
if (!current.subnodeMap.containsKey(dir))
return null;
current = current.subnodeMap.get(dir);
}
return current;
}
public Set<Entry<String, T>> entrySet(String query) {
return root.entrySet(query);
}
@Override
public Set<Entry<String, T>> entrySet() {
return entrySet("*");
}
@Override
public T remove(Object key) {
if (!String.class.isInstance(key))
return null;
return root.remove(((String) key).split("/"));
}
@Override
public boolean remove(Object key, Object value) {
if (!String.class.isInstance(key))
return false;
return root.remove(((String) key).split("/"), value);
}
/**
* Remove all elements in the specified path and its subs.
*
* @return true if successfully remove the node, false otherwise.
*/
public boolean removeNode(String path) {
if (path == null)
return false;
return root.removeNode(path.split("/"));
}
@Override
public T replace(String key, T value) {
return root.replace(key.split("/"), value);
}
@Override
public boolean replace(String key, T oldValue, T newValue) {
return root.replace(key.split("/"), oldValue, newValue);
}
@Override
public void clear() {
root = new Node<T>(0);
}
@Override
public boolean containsKey(Object key) {
if (!String.class.isInstance(key))
return false;
String[] path = ((String) key).split("/");
return root.containsKey(path);
}
@Override
public boolean containsValue(Object value) {
return root.containsValue(value);
}
@Override
public boolean isEmpty() {
return root.size() == 0;
}
@Override
public Set<String> keySet() {
return root.keySet();
}
@Override
public void putAll(Map<? extends String, ? extends T> m) {
for (Entry<? extends String, ? extends T> e : m.entrySet()) {
root.put(e.getKey().split("/"), e.getValue());
}
}
@Override
public Collection<T> values() {
return root.values();
}
@Override
public String toString() {
return root.toString();
}
private static class Node<T> {
private ConcurrentMap<String, T> itemMap = new ConcurrentHashMap<String, T>();
private ConcurrentMap<String, Node<T>> subnodeMap = new ConcurrentHashMap<String, Node<T>>();
private ConcurrentMap<String, NodeGuard<T>> nodeGuardMap = new ConcurrentHashMap<String, NodeGuard<T>>();
private int depth;
public Node(int depth) {
this.depth = depth;
}
public Collection<T> values() {
Collection<T> collection = new ArrayList<T>(itemMap.values());
for (Entry<String, Node<T>> e : subnodeMap.entrySet()) {
collection.addAll(e.getValue().values());
}
return collection;
}
public Set<String> keySet() {
Set<String> set = new HashSet<String>(itemMap.keySet());
for (Entry<String, Node<T>> e : subnodeMap.entrySet()) {
String dir = e.getKey();
for (String subs : e.getValue().keySet()) {
set.add(dir + "/" + subs);
}
}
return set;
}
public boolean containsKey(String[] path) {
if (depth == path.length - 1) {
return itemMap.containsKey(path[depth]);
} else {
Node<T> sub = subnodeMap.get(path[depth]);
if (sub != null) {
return sub.containsKey(path);
} else {
return false;
}
}
}
public boolean containsValue(Object value) {
if (itemMap.containsValue(value))
return true;
for (Entry<String, Node<T>> e : subnodeMap.entrySet()) {
if (e.getValue().containsValue(value))
return true;
}
return false;
}
public boolean remove(String[] path, Object value) {
if (depth == path.length - 1) {
return itemMap.remove(path[depth], value);
} else {
Node<T> sub = subnodeMap.get(path[depth]);
if (sub != null) {
boolean isRemoved = sub.remove(path, value);
if (isRemoved && sub.size() == 0) {
removeSubdir(path, sub);
}
return isRemoved;
} else {
return false;
}
}
}
public T remove(String[] path) {
if (depth == path.length - 1) {
return itemMap.remove(path[depth]);
} else {
Node<T> sub = subnodeMap.get(path[depth]);
if (sub != null) {
T removedItem = sub.remove(path);
if (removedItem != null && sub.size() == 0) {
removeSubdir(path, sub);
}
return removedItem;
} else {
return null;
}
}
}
public boolean removeNode(String[] path) {
if (depth == path.length - 1) {
return subnodeMap.remove(path[depth]) != null;
} else {
return subnodeMap.get(path[depth]).removeNode(path);
}
}
public T get(String[] path) {
if (depth == path.length - 1) {
return itemMap.get(path[depth]);
} else {
if (!subnodeMap.containsKey(path[depth])) {
return null;
}
Node<T> sub = subnodeMap.get(path[depth]);
return sub.get(path);
}
}
public T put(String[] path, T value) {
if (depth == path.length - 1) {
return itemMap.put(path[depth], value);
} else {
if (itemMap.containsKey(path[depth])) {
throw new InvalidHierarchyException(String.format("there exists key (%s, depth: %d)", path[depth],
depth));
}
if (nodeGuardMap.containsKey(path[depth])) {
NodeGuard<T> nodeGuard = nodeGuardMap.get(path[depth]);
if (nodeGuard != null) {
if (nodeGuard.offer(new NodeItem<T>(path, value)))
return null;
}
}
if (!subnodeMap.containsKey(path[depth])) {
subnodeMap.putIfAbsent(path[depth], new Node<T>(this.depth + 1));
}
Node<T> sub = subnodeMap.get(path[depth]);
return sub.put(path, value);
}
}
public T putIfAbsent(String[] path, T value) {
if (depth == path.length - 1) {
return itemMap.putIfAbsent(path[depth], value);
} else {
if (nodeGuardMap.containsKey(path[depth])) {
NodeGuard<T> nodeGuard = nodeGuardMap.get(path[depth]);
if (nodeGuard != null) {
if (nodeGuard.offer(new NodeItem<T>(path, value)))
return null;
}
}
if (!subnodeMap.containsKey(path[depth])) {
subnodeMap.putIfAbsent(path[depth], new Node<T>(this.depth + 1));
}
Node<T> sub = subnodeMap.get(path[depth]);
return sub.putIfAbsent(path, value);
}
}
public T replace(String[] path, T value) {
if (depth == path.length - 1) {
return itemMap.replace(path[depth], value);
} else {
if (!subnodeMap.containsKey(path[depth])) {
return null;
}
Node<T> sub = subnodeMap.get(path[depth]);
return sub.replace(path, value);
}
}
public boolean replace(String[] path, T oldValue, T newValue) {
if (depth == path.length - 1) {
return itemMap.replace(path[depth], oldValue, newValue);
} else {
if (!subnodeMap.containsKey(path[depth])) {
return false;
}
Node<T> sub = subnodeMap.get(path[depth]);
return sub.replace(path, oldValue, newValue);
}
}
public Set<Entry<String, T>> entrySet() {
return new NodeEntrySet<T>(this, "*");
}
public Set<Entry<String, T>> entrySet(String query) {
return new NodeEntrySet<T>(this, query);
}
public int size() {
int size = itemMap.size();
for (Entry<String, Node<T>> e : subnodeMap.entrySet()) {
size += e.getValue().size();
}
return size;
}
private void removeSubdir(String[] path, Node<T> sub) {
// raise node guard
if (nodeGuardMap.containsKey(path[depth]))
return;
NodeGuard<T> guard = new NodeGuard<T>();
if (nodeGuardMap.putIfAbsent(path[depth], guard) != null)
return;
// guard completed
// unlink sub-node
subnodeMap.remove(path[depth]);
// remove sub-node
nodeGuardMap.remove(path[depth], guard);
// stop guard
guard.close();
// if any element exists between subnode and guard, re-insert it.
if (sub.size() > 0)
for (Entry<String, T> e : sub.entrySet()) {
put(e.getKey().split("/"), e.getValue());
}
while (guard.peek() != null) {
NodeItem<T> item = guard.poll();
put(item.path, item.item);
}
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((itemMap == null) ? 0 : itemMap.entrySet().hashCode());
result = prime * result + ((subnodeMap == null) ? 0 : subnodeMap.entrySet().hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Node<?> other = (Node<?>) obj;
if (itemMap == null) {
if (other.itemMap != null)
return false;
} else if (!itemMap.equals(other.itemMap))
return false;
if (subnodeMap == null) {
if (other.subnodeMap != null)
return false;
} else if (!subnodeMap.equals(other.subnodeMap))
return false;
return true;
}
@Override
public String toString() {
return "[" + itemMap + ";" + subnodeMap + "]";
}
}
private static class NodeGuard<T> {
private AtomicReference<Queue<NodeItem<T>>> queueHolder = new AtomicReference<Queue<NodeItem<T>>>();
private volatile boolean isClosed = false;
public boolean offer(NodeItem<T> node) {
if (isClosed)
return false;
if (queueHolder.get() == null) {
queueHolder.compareAndSet(null, new ConcurrentLinkedQueue<NodeItem<T>>());
}
return queueHolder.get().offer(node);
}
public NodeItem<T> poll() {
if (queueHolder.get() == null)
return null;
return queueHolder.get().poll();
}
public NodeItem<T> peek() {
if (queueHolder.get() == null)
return null;
return queueHolder.get().peek();
}
public void close() {
isClosed = true;
}
}
private static class NodeItem<T> {
String[] path;
T item;
public NodeItem(String[] path, T item) {
this.path = path;
this.item = item;
}
}
private static class NodeEntrySet<T> extends AbstractSet<Entry<String, T>> {
private Node<T> node;
private String[] query;
public NodeEntrySet(Node<T> node, String query) {
if (query == null)
throw new IllegalArgumentException("query must be not null");
this.node = node;
this.query = query.split("/");
}
@Override
public Iterator<Entry<String, T>> iterator() {
return new NodeIterator<T>(node, query, 0);
}
@Override
public int size() {
if ((query.length == 1) && "*".equals(query[0]))
return node.size();
int size = 0;
for (Iterator<Entry<String, T>> iter = iterator(); iter.hasNext(); iter.next())
size++;
return size;
}
}
private static class NodeIterator<T> implements Iterator<Entry<String, T>> {
private String[] query;
private int depth;
private Iterator<Entry<String, T>> iter;
private Iterator<Entry<String, Node<T>>> subdirIter;
private Entry<String, T> next = null;
private Entry<String, Node<T>> nextNode = null;
private NodeIterator<T> nextNodeIter = null;
public NodeIterator(Node<T> node, String[] query, int depth) {
this.query = query;
this.depth = depth;
iter = node.itemMap.entrySet().iterator();
subdirIter = node.subnodeMap.entrySet().iterator();
}
@Override
public boolean hasNext() {
int lastQueryIndex = query.length - 1;
if (depth >= lastQueryIndex) {
while (next == null && iter.hasNext()) {
next = iter.next();
if (!match(next.getKey())) {
next = null;
}
}
}
if (next == null) {
while ((nextNodeIter == null || !nextNodeIter.hasNext()) && subdirIter.hasNext()) {
nextNode = subdirIter.next();
if (!match(nextNode.getKey())) {
nextNode = null;
} else {
nextNodeIter = new NodeIterator<T>(nextNode.getValue(), query, depth + 1);
}
}
}
return ((nextNodeIter != null && nextNodeIter.hasNext()) || next != null);
}
@Override
public Entry<String, T> next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
if (nextNodeIter != null) {
Entry<String, T> e = nextNodeIter.next();
return new SubdirEntry<T>(nextNode.getKey() + "/" + e.getKey(), e.getValue());
}
Entry<String, T> current = next;
next = null;
return current;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
private boolean match(String s) {
String query;
if (depth < this.query.length) {
query = this.query[depth];
} else if ("*".equals(this.query[this.query.length - 1])) {
query = "*";
} else {
throw new RuntimeException("Matching has some problem.");
}
// System.out.println(Arrays.toString(this.query)+":"+depth+"==>"+query);
if (query.contains("*")) {
return getMatcher(query).isMatch(s);
} else {
return query.equals(s);
}
}
}
private static class SubdirEntry<T> implements Entry<String, T> {
private String key;
private T value;
public SubdirEntry(String key, T value) {
this.key = key;
this.value = value;
}
@Override
public String getKey() {
return key;
}
@Override
public T getValue() {
return value;
}
@Override
public T setValue(T value) {
throw new UnsupportedOperationException();
}
@Override
public String toString() {
return key + "=" + value;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((key == null) ? 0 : key.hashCode());
result = prime * result + ((value == null) ? 0 : value.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
SubdirEntry<?> other = (SubdirEntry<?>) obj;
if (key == null) {
if (other.key != null)
return false;
} else if (!key.equals(other.key))
return false;
if (value == null) {
if (other.value != null)
return false;
} else if (!value.equals(other.value))
return false;
return true;
}
}
public static ConcurrentHashMap<String, WildcardPathMatcher> matchers = new ConcurrentHashMap<String, WildcardPathMatcher>();
public static WildcardPathMatcher getMatcher(String query) {
if (matchers.containsKey(query)) {
return matchers.get(query);
} else {
matchers.putIfAbsent(query, new WildcardPathMatcher(query));
return matchers.get(query);
}
}
}