| /* |
| ******************************************************************************* |
| * Copyright (C) 2011-2014, International Business Machines |
| * Corporation and others. All Rights Reserved. |
| ******************************************************************************* |
| * created on: 2011jan05 |
| * created by: Markus W. Scherer |
| * ported from ICU4C stringtriebuilder.h/.cpp |
| */ |
| package com.ibm.icu.util; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| |
| /** |
| * Base class for string trie builder classes. |
| * |
| * <p>This class is not intended for public subclassing. |
| * |
| * @author Markus W. Scherer |
| * @stable ICU 4.8 |
| */ |
| public abstract class StringTrieBuilder { |
| /** |
| * Build options for BytesTrieBuilder and CharsTrieBuilder. |
| * @stable ICU 4.8 |
| */ |
| public enum Option { |
| /** |
| * Builds a trie quickly. |
| * @stable ICU 4.8 |
| */ |
| FAST, |
| /** |
| * Builds a trie more slowly, attempting to generate |
| * a shorter but equivalent serialization. |
| * This build option also uses more memory. |
| * |
| * <p>This option can be effective when many integer values are the same |
| * and string/byte sequence suffixes can be shared. |
| * Runtime speed is not expected to improve. |
| * @stable ICU 4.8 |
| */ |
| SMALL |
| } |
| |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| protected StringTrieBuilder() {} |
| |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| protected void addImpl(CharSequence s, int value) { |
| if(state!=State.ADDING) { |
| // Cannot add elements after building. |
| throw new IllegalStateException("Cannot add (string, value) pairs after build()."); |
| } |
| if(s.length()>0xffff) { |
| // Too long: Limited by iterator internals, and by builder recursion depth. |
| throw new IndexOutOfBoundsException("The maximum string length is 0xffff."); |
| } |
| if(root==null) { |
| root=createSuffixNode(s, 0, value); |
| } else { |
| root=root.add(this, s, 0, value); |
| } |
| } |
| |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| protected final void buildImpl(Option buildOption) { |
| switch(state) { |
| case ADDING: |
| if(root==null) { |
| throw new IndexOutOfBoundsException("No (string, value) pairs were added."); |
| } |
| if(buildOption==Option.FAST) { |
| state=State.BUILDING_FAST; |
| // Building "fast" is somewhat faster (25..50% in some test) |
| // because it makes registerNode() return the input node |
| // rather than checking for duplicates. |
| // As a result, we sometimes write larger trie serializations. |
| // |
| // In either case we need to fix-up linear-match nodes (for their maximum length) |
| // and branch nodes (turning dynamic branch nodes into trees of |
| // runtime-equivalent nodes), but the HashMap/hashCode()/equals() are omitted for |
| // nodes other than final values. |
| } else { |
| state=State.BUILDING_SMALL; |
| } |
| break; |
| case BUILDING_FAST: |
| case BUILDING_SMALL: |
| // Building must have failed. |
| throw new IllegalStateException("Builder failed and must be clear()ed."); |
| case BUILT: |
| return; // Nothing more to do. |
| } |
| // Implementation note: |
| // We really build three versions of the trie. |
| // The first is a fully dynamic trie, built successively by addImpl(). |
| // Then we call root.register() to turn it into a tree of nodes |
| // which is 1:1 equivalent to the runtime data structure. |
| // Finally, root.markRightEdgesFirst() and root.write() write that serialized form. |
| root=root.register(this); |
| root.markRightEdgesFirst(-1); |
| root.write(this); |
| state=State.BUILT; |
| } |
| |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| protected void clearImpl() { |
| strings.setLength(0); |
| nodes.clear(); |
| root=null; |
| state=State.ADDING; |
| } |
| |
| /** |
| * Makes sure that there is only one unique node registered that is |
| * equivalent to newNode, unless BUILDING_FAST. |
| * @param newNode Input node. The builder takes ownership. |
| * @return newNode if it is the first of its kind, or |
| * an equivalent node if newNode is a duplicate. |
| */ |
| private final Node registerNode(Node newNode) { |
| if(state==State.BUILDING_FAST) { |
| return newNode; |
| } |
| // BUILDING_SMALL |
| Node oldNode=nodes.get(newNode); |
| if(oldNode!=null) { |
| return oldNode; |
| } |
| // If put() returns a non-null value from an equivalent, previously |
| // registered node, then get() failed to find that and we will leak newNode. |
| oldNode=nodes.put(newNode, newNode); |
| assert(oldNode==null); |
| return newNode; |
| } |
| |
| /** |
| * Makes sure that there is only one unique FinalValueNode registered |
| * with this value. |
| * Avoids creating a node if the value is a duplicate. |
| * @param value A final value. |
| * @return A FinalValueNode with the given value. |
| */ |
| private final ValueNode registerFinalValue(int value) { |
| // We always register final values because while ADDING |
| // we do not know yet whether we will build fast or small. |
| lookupFinalValueNode.setFinalValue(value); |
| Node oldNode=nodes.get(lookupFinalValueNode); |
| if(oldNode!=null) { |
| return (ValueNode)oldNode; |
| } |
| ValueNode newNode=new ValueNode(value); |
| // If put() returns a non-null value from an equivalent, previously |
| // registered node, then get() failed to find that and we will leak newNode. |
| oldNode=nodes.put(newNode, newNode); |
| assert(oldNode==null); |
| return newNode; |
| } |
| |
| private static abstract class Node { |
| public Node() { |
| offset=0; |
| } |
| // hashCode() and equals() for use with registerNode() and the nodes hash. |
| @Override |
| public abstract int hashCode() /*const*/; |
| // Base class equals() compares the actual class types. |
| @Override |
| public boolean equals(Object other) { |
| return this==other || this.getClass()==other.getClass(); |
| } |
| /** |
| * Recursive method for adding a new (string, value) pair. |
| * Matches the remaining part of s from start, |
| * and adds a new node where there is a mismatch. |
| * @return this or a replacement Node |
| */ |
| public Node add(StringTrieBuilder builder, CharSequence s, int start, int sValue) { |
| return this; |
| } |
| /** |
| * Recursive method for registering unique nodes, |
| * after all (string, value) pairs have been added. |
| * Final-value nodes are pre-registered while add()ing (string, value) pairs. |
| * Other nodes created while add()ing registerNode() themselves later |
| * and might replace themselves with new types of nodes for write()ing. |
| * @return The registered version of this node which implements write(). |
| */ |
| public Node register(StringTrieBuilder builder) { return this; } |
| /** |
| * Traverses the Node graph and numbers branch edges, with rightmost edges first. |
| * This is to avoid writing a duplicate node twice. |
| * |
| * Branch nodes in this trie data structure are not symmetric. |
| * Most branch edges "jump" to other nodes but the rightmost branch edges |
| * just continue without a jump. |
| * Therefore, write() must write the rightmost branch edge last |
| * (trie units are written backwards), and must write it at that point even if |
| * it is a duplicate of a node previously written elsewhere. |
| * |
| * This function visits and marks right branch edges first. |
| * Edges are numbered with increasingly negative values because we share the |
| * offset field which gets positive values when nodes are written. |
| * A branch edge also remembers the first number for any of its edges. |
| * |
| * When a further-left branch edge has a number in the range of the rightmost |
| * edge's numbers, then it will be written as part of the required right edge |
| * and we can avoid writing it first. |
| * |
| * After root.markRightEdgesFirst(-1) the offsets of all nodes are negative |
| * edge numbers. |
| * |
| * @param edgeNumber The first edge number for this node and its sub-nodes. |
| * @return An edge number that is at least the maximum-negative |
| * of the input edge number and the numbers of this node and all of its sub-nodes. |
| */ |
| public int markRightEdgesFirst(int edgeNumber) { |
| if(offset==0) { |
| offset=edgeNumber; |
| } |
| return edgeNumber; |
| } |
| // write() must set the offset to a positive value. |
| public abstract void write(StringTrieBuilder builder); |
| // See markRightEdgesFirst. |
| public final void writeUnlessInsideRightEdge(int firstRight, int lastRight, |
| StringTrieBuilder builder) { |
| // Note: Edge numbers are negative, lastRight<=firstRight. |
| // If offset>0 then this node and its sub-nodes have been written already |
| // and we need not write them again. |
| // If this node is part of the unwritten right branch edge, |
| // then we wait until that is written. |
| if(offset<0 && (offset<lastRight || firstRight<offset)) { |
| write(builder); |
| } |
| } |
| public final int getOffset() /*const*/ { return offset; } |
| |
| protected int offset; |
| } |
| |
| // Used directly for final values, and as as a superclass for |
| // match nodes with intermediate values. |
| private static class ValueNode extends Node { |
| public ValueNode() {} |
| public ValueNode(int v) { |
| hasValue=true; |
| value=v; |
| } |
| public final void setValue(int v) { |
| assert(!hasValue); |
| hasValue=true; |
| value=v; |
| } |
| private void setFinalValue(int v) { |
| hasValue=true; |
| value=v; |
| } |
| @Override |
| public int hashCode() /*const*/ { |
| int hash=0x111111; |
| if(hasValue) { |
| hash=hash*37+value; |
| } |
| return hash; |
| } |
| @Override |
| public boolean equals(Object other) { |
| if(this==other) { |
| return true; |
| } |
| if(!super.equals(other)) { |
| return false; |
| } |
| ValueNode o=(ValueNode)other; |
| return hasValue==o.hasValue && (!hasValue || value==o.value); |
| } |
| @Override |
| public Node add(StringTrieBuilder builder, CharSequence s, int start, int sValue) { |
| if(start==s.length()) { |
| throw new IllegalArgumentException("Duplicate string."); |
| } |
| // Replace self with a node for the remaining string suffix and value. |
| ValueNode node=builder.createSuffixNode(s, start, sValue); |
| node.setValue(value); |
| return node; |
| } |
| @Override |
| public void write(StringTrieBuilder builder) { |
| offset=builder.writeValueAndFinal(value, true); |
| } |
| |
| protected boolean hasValue; |
| protected int value; |
| } |
| |
| private static final class IntermediateValueNode extends ValueNode { |
| public IntermediateValueNode(int v, Node nextNode) { |
| next=nextNode; |
| setValue(v); |
| } |
| @Override |
| public int hashCode() /*const*/ { |
| return (0x222222*37+value)*37+next.hashCode(); |
| } |
| @Override |
| public boolean equals(Object other) { |
| if(this==other) { |
| return true; |
| } |
| if(!super.equals(other)) { |
| return false; |
| } |
| IntermediateValueNode o=(IntermediateValueNode)other; |
| return next==o.next; |
| } |
| @Override |
| public int markRightEdgesFirst(int edgeNumber) { |
| if(offset==0) { |
| offset=edgeNumber=next.markRightEdgesFirst(edgeNumber); |
| } |
| return edgeNumber; |
| } |
| @Override |
| public void write(StringTrieBuilder builder) { |
| next.write(builder); |
| offset=builder.writeValueAndFinal(value, false); |
| } |
| |
| private Node next; |
| } |
| |
| private static final class LinearMatchNode extends ValueNode { |
| public LinearMatchNode(CharSequence builderStrings, int sOffset, int len, Node nextNode) { |
| strings=builderStrings; |
| stringOffset=sOffset; |
| length=len; |
| next=nextNode; |
| } |
| @Override |
| public int hashCode() /*const*/ { return hash; } |
| @Override |
| public boolean equals(Object other) { |
| if(this==other) { |
| return true; |
| } |
| if(!super.equals(other)) { |
| return false; |
| } |
| LinearMatchNode o=(LinearMatchNode)other; |
| if(length!=o.length || next!=o.next) { |
| return false; |
| } |
| for(int i=stringOffset, j=o.stringOffset, limit=stringOffset+length; i<limit; ++i, ++j) { |
| if(strings.charAt(i)!=strings.charAt(j)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| @Override |
| public Node add(StringTrieBuilder builder, CharSequence s, int start, int sValue) { |
| if(start==s.length()) { |
| if(hasValue) { |
| throw new IllegalArgumentException("Duplicate string."); |
| } else { |
| setValue(sValue); |
| return this; |
| } |
| } |
| int limit=stringOffset+length; |
| for(int i=stringOffset; i<limit; ++i, ++start) { |
| if(start==s.length()) { |
| // s is a prefix with a new value. Split self into two linear-match nodes. |
| int prefixLength=i-stringOffset; |
| LinearMatchNode suffixNode=new LinearMatchNode(strings, i, length-prefixLength, next); |
| suffixNode.setValue(sValue); |
| length=prefixLength; |
| next=suffixNode; |
| return this; |
| } |
| char thisChar=strings.charAt(i); |
| char newChar=s.charAt(start); |
| if(thisChar!=newChar) { |
| // Mismatch, insert a branch node. |
| DynamicBranchNode branchNode=new DynamicBranchNode(); |
| // Reuse this node for one of the remaining substrings, if any. |
| Node result, thisSuffixNode; |
| if(i==stringOffset) { |
| // Mismatch on first character, turn this node into a suffix. |
| if(hasValue) { |
| // Move the value for prefix length "start" to the new node. |
| branchNode.setValue(value); |
| value=0; |
| hasValue=false; |
| } |
| ++stringOffset; |
| --length; |
| thisSuffixNode= length>0 ? this : next; |
| // C++: if(length==0) { delete this; } |
| result=branchNode; |
| } else if(i==limit-1) { |
| // Mismatch on last character, keep this node for the prefix. |
| --length; |
| thisSuffixNode=next; |
| next=branchNode; |
| result=this; |
| } else { |
| // Mismatch on intermediate character, keep this node for the prefix. |
| int prefixLength=i-stringOffset; |
| ++i; // Suffix start offset (after thisChar). |
| thisSuffixNode=new LinearMatchNode( |
| strings, i, length-(prefixLength+1), next); |
| length=prefixLength; |
| next=branchNode; |
| result=this; |
| } |
| ValueNode newSuffixNode=builder.createSuffixNode(s, start+1, sValue); |
| branchNode.add(thisChar, thisSuffixNode); |
| branchNode.add(newChar, newSuffixNode); |
| return result; |
| } |
| } |
| // s matches all of this node's characters. |
| next=next.add(builder, s, start, sValue); |
| return this; |
| } |
| @Override |
| public Node register(StringTrieBuilder builder) { |
| next=next.register(builder); |
| // Break the linear-match sequence into chunks of at most kMaxLinearMatchLength. |
| int maxLinearMatchLength=builder.getMaxLinearMatchLength(); |
| while(length>maxLinearMatchLength) { |
| int nextOffset=stringOffset+length-maxLinearMatchLength; |
| length-=maxLinearMatchLength; |
| LinearMatchNode suffixNode= |
| new LinearMatchNode(strings, nextOffset, maxLinearMatchLength, next); |
| suffixNode.setHashCode(); |
| next=builder.registerNode(suffixNode); |
| } |
| Node result; |
| if(hasValue && !builder.matchNodesCanHaveValues()) { |
| int intermediateValue=value; |
| value=0; |
| hasValue=false; |
| setHashCode(); |
| result=new IntermediateValueNode(intermediateValue, builder.registerNode(this)); |
| } else { |
| setHashCode(); |
| result=this; |
| } |
| return builder.registerNode(result); |
| } |
| @Override |
| public int markRightEdgesFirst(int edgeNumber) { |
| if(offset==0) { |
| offset=edgeNumber=next.markRightEdgesFirst(edgeNumber); |
| } |
| return edgeNumber; |
| } |
| @Override |
| public void write(StringTrieBuilder builder) { |
| next.write(builder); |
| builder.write(stringOffset, length); |
| offset=builder.writeValueAndType(hasValue, value, builder.getMinLinearMatch()+length-1); |
| } |
| |
| // Must be called just before registerNode(this). |
| private void setHashCode() /*const*/ { |
| hash=(0x333333*37+length)*37+next.hashCode(); |
| if(hasValue) { |
| hash=hash*37+value; |
| } |
| for(int i=stringOffset, limit=stringOffset+length; i<limit; ++i) { |
| hash=hash*37+strings.charAt(i); |
| } |
| } |
| |
| private CharSequence strings; |
| private int stringOffset; |
| private int length; |
| private Node next; |
| private int hash; |
| } |
| |
| private static final class DynamicBranchNode extends ValueNode { |
| public DynamicBranchNode() {} |
| // c must not be in chars yet. |
| public void add(char c, Node node) { |
| int i=find(c); |
| chars.insert(i, c); |
| equal.add(i, node); |
| } |
| @Override |
| public Node add(StringTrieBuilder builder, CharSequence s, int start, int sValue) { |
| if(start==s.length()) { |
| if(hasValue) { |
| throw new IllegalArgumentException("Duplicate string."); |
| } else { |
| setValue(sValue); |
| return this; |
| } |
| } |
| char c=s.charAt(start++); |
| int i=find(c); |
| if(i<chars.length() && c==chars.charAt(i)) { |
| equal.set(i, equal.get(i).add(builder, s, start, sValue)); |
| } else { |
| chars.insert(i, c); |
| equal.add(i, builder.createSuffixNode(s, start, sValue)); |
| } |
| return this; |
| } |
| @Override |
| public Node register(StringTrieBuilder builder) { |
| Node subNode=register(builder, 0, chars.length()); |
| BranchHeadNode head=new BranchHeadNode(chars.length(), subNode); |
| Node result=head; |
| if(hasValue) { |
| if(builder.matchNodesCanHaveValues()) { |
| head.setValue(value); |
| } else { |
| result=new IntermediateValueNode(value, builder.registerNode(head)); |
| } |
| } |
| return builder.registerNode(result); |
| } |
| private Node register(StringTrieBuilder builder, int start, int limit) { |
| int length=limit-start; |
| if(length>builder.getMaxBranchLinearSubNodeLength()) { |
| // Branch on the middle unit. |
| int middle=start+length/2; |
| return builder.registerNode( |
| new SplitBranchNode( |
| chars.charAt(middle), |
| register(builder, start, middle), |
| register(builder, middle, limit))); |
| } |
| ListBranchNode listNode=new ListBranchNode(length); |
| do { |
| char c=chars.charAt(start); |
| Node node=equal.get(start); |
| if(node.getClass()==ValueNode.class) { |
| // Final value. |
| listNode.add(c, ((ValueNode)node).value); |
| } else { |
| listNode.add(c, node.register(builder)); |
| } |
| } while(++start<limit); |
| return builder.registerNode(listNode); |
| } |
| |
| private int find(char c) { |
| int start=0; |
| int limit=chars.length(); |
| while(start<limit) { |
| int i=(start+limit)/2; |
| char middleChar=chars.charAt(i); |
| if(c<middleChar) { |
| limit=i; |
| } else if(c==middleChar) { |
| return i; |
| } else { |
| start=i+1; |
| } |
| } |
| return start; |
| } |
| |
| private StringBuilder chars=new StringBuilder(); |
| private ArrayList<Node> equal=new ArrayList<Node>(); |
| } |
| |
| private static abstract class BranchNode extends Node { |
| public BranchNode() {} |
| @Override |
| public int hashCode() /*const*/ { return hash; } |
| |
| protected int hash; |
| protected int firstEdgeNumber; |
| } |
| |
| private static final class ListBranchNode extends BranchNode { |
| public ListBranchNode(int capacity) { |
| hash=0x444444*37+capacity; |
| equal=new Node[capacity]; |
| values=new int[capacity]; |
| units=new char[capacity]; |
| } |
| @Override |
| public boolean equals(Object other) { |
| if(this==other) { |
| return true; |
| } |
| if(!super.equals(other)) { |
| return false; |
| } |
| ListBranchNode o=(ListBranchNode)other; |
| for(int i=0; i<length; ++i) { |
| if(units[i]!=o.units[i] || values[i]!=o.values[i] || equal[i]!=o.equal[i]) { |
| return false; |
| } |
| } |
| return true; |
| } |
| @Override |
| public int hashCode() { |
| return super.hashCode(); |
| } |
| @Override |
| public int markRightEdgesFirst(int edgeNumber) { |
| if(offset==0) { |
| firstEdgeNumber=edgeNumber; |
| int step=0; |
| int i=length; |
| do { |
| Node edge=equal[--i]; |
| if(edge!=null) { |
| edgeNumber=edge.markRightEdgesFirst(edgeNumber-step); |
| } |
| // For all but the rightmost edge, decrement the edge number. |
| step=1; |
| } while(i>0); |
| offset=edgeNumber; |
| } |
| return edgeNumber; |
| } |
| @Override |
| public void write(StringTrieBuilder builder) { |
| // Write the sub-nodes in reverse order: The jump lengths are deltas from |
| // after their own positions, so if we wrote the minUnit sub-node first, |
| // then its jump delta would be larger. |
| // Instead we write the minUnit sub-node last, for a shorter delta. |
| int unitNumber=length-1; |
| Node rightEdge=equal[unitNumber]; |
| int rightEdgeNumber= rightEdge==null ? firstEdgeNumber : rightEdge.getOffset(); |
| do { |
| --unitNumber; |
| if(equal[unitNumber]!=null) { |
| equal[unitNumber].writeUnlessInsideRightEdge(firstEdgeNumber, rightEdgeNumber, builder); |
| } |
| } while(unitNumber>0); |
| // The maxUnit sub-node is written as the very last one because we do |
| // not jump for it at all. |
| unitNumber=length-1; |
| if(rightEdge==null) { |
| builder.writeValueAndFinal(values[unitNumber], true); |
| } else { |
| rightEdge.write(builder); |
| } |
| offset=builder.write(units[unitNumber]); |
| // Write the rest of this node's unit-value pairs. |
| while(--unitNumber>=0) { |
| int value; |
| boolean isFinal; |
| if(equal[unitNumber]==null) { |
| // Write the final value for the one string ending with this unit. |
| value=values[unitNumber]; |
| isFinal=true; |
| } else { |
| // Write the delta to the start position of the sub-node. |
| assert(equal[unitNumber].getOffset()>0); |
| value=offset-equal[unitNumber].getOffset(); |
| isFinal=false; |
| } |
| builder.writeValueAndFinal(value, isFinal); |
| offset=builder.write(units[unitNumber]); |
| } |
| } |
| // Adds a unit with a final value. |
| public void add(int c, int value) { |
| units[length]=(char)c; |
| equal[length]=null; |
| values[length]=value; |
| ++length; |
| hash=(hash*37+c)*37+value; |
| } |
| // Adds a unit which leads to another match node. |
| public void add(int c, Node node) { |
| units[length]=(char)c; |
| equal[length]=node; |
| values[length]=0; |
| ++length; |
| hash=(hash*37+c)*37+node.hashCode(); |
| } |
| |
| // Note: We could try to reduce memory allocations |
| // by replacing these per-node arrays with per-builder ArrayLists and |
| // (for units) a StringBuilder (or even use its strings for the units too). |
| // It remains to be seen whether that would improve performance. |
| private Node[] equal; // null means "has final value". |
| private int length; |
| private int[] values; |
| private char[] units; |
| } |
| |
| private static final class SplitBranchNode extends BranchNode { |
| public SplitBranchNode(char middleUnit, Node lessThanNode, Node greaterOrEqualNode) { |
| hash=((0x555555*37+middleUnit)*37+ |
| lessThanNode.hashCode())*37+greaterOrEqualNode.hashCode(); |
| unit=middleUnit; |
| lessThan=lessThanNode; |
| greaterOrEqual=greaterOrEqualNode; |
| } |
| @Override |
| public boolean equals(Object other) { |
| if(this==other) { |
| return true; |
| } |
| if(!super.equals(other)) { |
| return false; |
| } |
| SplitBranchNode o=(SplitBranchNode)other; |
| return unit==o.unit && lessThan==o.lessThan && greaterOrEqual==o.greaterOrEqual; |
| } |
| @Override |
| public int hashCode() { |
| return super.hashCode(); |
| } |
| @Override |
| public int markRightEdgesFirst(int edgeNumber) { |
| if(offset==0) { |
| firstEdgeNumber=edgeNumber; |
| edgeNumber=greaterOrEqual.markRightEdgesFirst(edgeNumber); |
| offset=edgeNumber=lessThan.markRightEdgesFirst(edgeNumber-1); |
| } |
| return edgeNumber; |
| } |
| @Override |
| public void write(StringTrieBuilder builder) { |
| // Encode the less-than branch first. |
| lessThan.writeUnlessInsideRightEdge(firstEdgeNumber, greaterOrEqual.getOffset(), builder); |
| // Encode the greater-or-equal branch last because we do not jump for it at all. |
| greaterOrEqual.write(builder); |
| // Write this node. |
| assert(lessThan.getOffset()>0); |
| builder.writeDeltaTo(lessThan.getOffset()); // less-than |
| offset=builder.write(unit); |
| } |
| |
| private char unit; |
| private Node lessThan; |
| private Node greaterOrEqual; |
| } |
| |
| // Branch head node, for writing the actual node lead unit. |
| private static final class BranchHeadNode extends ValueNode { |
| public BranchHeadNode(int len, Node subNode) { |
| length=len; |
| next=subNode; |
| } |
| @Override |
| public int hashCode() /*const*/ { |
| return (0x666666*37+length)*37+next.hashCode(); |
| } |
| @Override |
| public boolean equals(Object other) { |
| if(this==other) { |
| return true; |
| } |
| if(!super.equals(other)) { |
| return false; |
| } |
| BranchHeadNode o=(BranchHeadNode)other; |
| return length==o.length && next==o.next; |
| } |
| @Override |
| public int markRightEdgesFirst(int edgeNumber) { |
| if(offset==0) { |
| offset=edgeNumber=next.markRightEdgesFirst(edgeNumber); |
| } |
| return edgeNumber; |
| } |
| @Override |
| public void write(StringTrieBuilder builder) { |
| next.write(builder); |
| if(length<=builder.getMinLinearMatch()) { |
| offset=builder.writeValueAndType(hasValue, value, length-1); |
| } else { |
| builder.write(length-1); |
| offset=builder.writeValueAndType(hasValue, value, 0); |
| } |
| } |
| |
| private int length; |
| private Node next; // A branch sub-node. |
| } |
| |
| private ValueNode createSuffixNode(CharSequence s, int start, int sValue) { |
| ValueNode node=registerFinalValue(sValue); |
| if(start<s.length()) { |
| int offset=strings.length(); |
| strings.append(s, start, s.length()); |
| node=new LinearMatchNode(strings, offset, s.length()-start, node); |
| } |
| return node; |
| } |
| |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| protected abstract boolean matchNodesCanHaveValues() /*const*/; |
| |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| protected abstract int getMaxBranchLinearSubNodeLength() /*const*/; |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| protected abstract int getMinLinearMatch() /*const*/; |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| protected abstract int getMaxLinearMatchLength() /*const*/; |
| |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| protected abstract int write(int unit); |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| protected abstract int write(int offset, int length); |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| protected abstract int writeValueAndFinal(int i, boolean isFinal); |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| protected abstract int writeValueAndType(boolean hasValue, int value, int node); |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| protected abstract int writeDeltaTo(int jumpTarget); |
| |
| private enum State { |
| ADDING, BUILDING_FAST, BUILDING_SMALL, BUILT |
| } |
| private State state=State.ADDING; |
| |
| // Strings and sub-strings for linear-match nodes. |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| protected StringBuilder strings=new StringBuilder(); |
| private Node root; |
| |
| // Hash set of nodes, maps from nodes to integer 1. |
| private HashMap<Node, Node> nodes=new HashMap<Node, Node>(); |
| private ValueNode lookupFinalValueNode=new ValueNode(); |
| } |