package xapi.collect;
import xapi.annotation.gwt.GwtIncompatible;
/**
* An optimized mapping structure for java packages; we are avoiding java.util
* collections, but still need a string mapping structure for source packages.
* Using fully qualified classnames as keys, this allows us to store a single
* in-memory repository of complete class structure. Since we know all keys will
* be in.package.form, we can create a relatively fast, light weight trie
* structure, capable of producing iterators that can descend subpackages.
* Adding support to map to methods and fields as well is simply a matter of
* joining the fully qualified class name to the field or method name; like
* com.foo.MyClass.@fieldName or com.foo.MyClass.#methodSig(Ljava/lang/Object;)
* This will prevent collisions, and will be implemented in subclasses
* elsewhere.
*
* @author "James X. Nelson (james@wetheinter.net)"
*/
@GwtIncompatible
public class PackageMap<T> {
// @KeepClass(arrayDepth=1, debugData="DEBUG", newInstance=NewInstanceStrategy.NONE)
public class PackageNode {
private PackageMap<T>.PackageNode[] subnodes;
private final PackageMap<T>.PackageNode parent;
private String fragment;
private T value;
protected PackageNode(final PackageNode parent) {
this.parent = parent;
}
private PackageNode(final String fragment) {
this.parent = this;
this.fragment = fragment;
}
@Override
public String toString() {
if (parent == root) {
return fragment;
} else {
return parent.toString()+"."+fragment;
}
}
}
@SuppressWarnings("unchecked")
private PackageMap<T>.PackageNode[] newArr(final int len) {
return new PackageMap.PackageNode[len];
// return X_Reflect.newArray(PackageNode.class, len);
}
protected final PackageMap<T>.PackageNode root = this.new PackageNode("");
public void add(final String pkg, final T item) {
if (pkg == null) {
throw new NullPointerException();
}
final String[] keys = pkg.split("[.]");
doAdd(0, keys, root, item);
}
@SuppressWarnings("unchecked")
private void doAdd(int pos, final String[] keys, PackageMap<T>.PackageNode node, final T item) {
if (pos < keys.length) {
PackageMap<T>.PackageNode into;
int insert;
// only block if multiple threads are on the same node.
synchro: //we don't want to recurse inside a synchronized block...
synchronized (node)
{
if (node.subnodes == null) {
// once we hit null, we know the rest of the keys will be null too
node.subnodes = newArr(1);
insert = 0;
} else {
final String key = keys[pos];
if (key.length() == 0) {
into = node;
break synchro; //eat empty .. or ./ keys
}
insert = node.subnodes.length;
// this package has nodes, so we need to do a get-or-create
for (int i = 0; i < node.subnodes.length; i++) {
final PackageMap<T>.PackageNode subnode = node.subnodes[i];
if (subnode == null) {
insert = i;
break;
}
if (subnode.fragment.equals(key)) {
// key matches, so we want to recurse,
//but we want to release our lock on node first.
into = subnode;
break synchro; //so we just break out of the synchro block
}
}
// no matches made, we need to extend our array and create the
//rest of the package chain w/out checks, as we know its empty
final PackageMap<T>.PackageNode[] newnodes = new PackageMap.PackageNode[insert * 2];
System.arraycopy(node.subnodes, 0, newnodes, 0, insert);
node.subnodes = newnodes;
}
//we didn't break, so we know the rest of the package chain is empty
PackageMap<T>.PackageNode newnode = this.new PackageNode(node);
newnode.fragment = keys[pos];
assert node.subnodes[insert] == null : "PackageMap key collision on "+node;
node.subnodes[insert] = newnode;
node = newnode;
//now recurse through all the keys we have left
for(;++pos<keys.length;) {
newnode = new PackageNode(node);
newnode.fragment = keys[pos];
assert node.subnodes == null : "Package not empty @ "+keys[pos]+" in "+node;
node.subnodes = newArr(1);
node.subnodes[0] = newnode;
node = newnode;
}
assert node.value == null : "Value not empty @ "+node;
node.value = item;
return;
} //end synchro
//if we didn't return, we need to recurse.
doAdd(pos + 1, keys, into, item);
} else {
// no new nodes; set the item, we're done.
node.value = item;
}
}
public T get(final String pkg) {
assert pkg != null : "Do not send nulls to PackageMap!";
PackageMap<T>.PackageNode node = root;
for (final String key : pkg.split("[.]")) {
loop: {
for (final PackageMap<T>.PackageNode subnode : node.subnodes) {
if (subnode.fragment.equals(key)) {
node = subnode;
break loop;
}
}
return null;//not found
}//end loop
}
return node.value;
}
}