package jenkins.util;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* Builds {@link TreeString}s that share common prefixes. Call
* {@link #intern(String)} and you get the {@link TreeString} that represents
* the same string, but as you interns more strings that share the same
* prefixes, those {@link TreeString}s that you get back start to share data.
* <p>
* Because the internal state of {@link TreeString}s get mutated as new strings
* are interned (to exploit new-found common prefixes), {@link TreeString}s
* returned from {@link #intern(String)} aren't thread-safe until
* {@link TreeStringBuilder} is disposed. That is, you have to make sure other
* threads don't see those {@link TreeString}s until you are done interning
* strings.
*
* @author Kohsuke Kawaguchi
* @since 1.473
*/
@SuppressWarnings({"PMD", "all"})
//CHECKSTYLE:OFF
public class TreeStringBuilder {
Child root = new Child(new TreeString());
private static class Child {
private final TreeString node;
private Map<String, Child> children = NO_CHILDREN;
private Child(final TreeString node) {
this.node = node;
}
/**
* Adds one edge and leaf to this tree node, or returns an existing node
* if any.
*/
public Child intern(final String s) {
if (s.length() == 0) {
return this;
}
makeWritable();
for (Map.Entry<String, Child> e : children.entrySet()) {
int plen = commonPrefix(e.getKey(), s);
if (plen > 0) {
if (plen < e.getKey().length()) {
// insert a node between this and e.value
Child c = e.getValue();
String prefix = s.substring(0, plen);
Child middle = c.split(prefix);
// add 'middle' instead of 'c'
children.remove(e.getKey());
children.put(prefix, middle);
return middle.intern(s.substring(plen));
}
else {// entire key is suffix
return e.getValue().intern(s.substring(plen));
}
}
}
// no common prefix. an entirely new node.
Child t = children.get(s);
if (t == null) {
children.put(s, t = new Child(new TreeString(node, s)));
}
return t;
}
/**
* Makes sure {@link #children} is writable.
*/
private void makeWritable() {
if (children == NO_CHILDREN) {
children = new HashMap<String, Child>();
}
}
/**
* Inserts a new node between this node and its parent, and returns that
* node. Newly inserted 'middle' node will have this node as its sole
* child.
*/
private Child split(final String prefix) {
String suffix = node.getLabel().substring(prefix.length());
Child middle = new Child(node.split(prefix));
middle.makeWritable();
middle.children.put(suffix, this);
return middle;
}
/**
* Returns the common prefix between two strings.
*/
private int commonPrefix(final String a, final String b) {
int m = Math.min(a.length(), b.length());
for (int i = 0; i < m; i++) {
if (a.charAt(i) != b.charAt(i)) {
return i;
}
}
return m;
}
/**
* Calls {@link TreeString#dedup(Map)} recursively.
*/
private void dedup(final Map<String, char[]> table) {
node.dedup(table);
for (Child child : children.values()) {
child.dedup(table);
}
}
}
/**
* Interns a string.
*/
public TreeString intern(final String s) {
if (s==null) return null;
return root.intern(s).node;
}
/**
* Interns a {@link TreeString} created elsewhere.
*/
public TreeString intern(final TreeString s) {
if (s==null) return null;
return root.intern(s.toString()).node;
}
/**
* Further reduces the memory footprint by finding the same labels across
* multiple {@link TreeString}s.
*/
public void dedup() {
root.dedup(new HashMap<String, char[]>());
}
/**
* Place holder that represents no child node, until one is added.
*/
private static final Map<String, Child> NO_CHILDREN = Collections.emptyMap();
}