1 // Copyright 2007 Google Inc. All Rights Reserved.
3 package com
.google
.appengine
.api
.datastore
;
5 import com
.google
.common
.collect
.Maps
;
10 * Trie implementation supporting CharSequences as prefixes.
12 * <p>Prefixes are sequences of characters, and the set of allowed characters is
13 * specified as a range of sequential characters. By default, any seven-bit
14 * character may appear in a prefix, and so the trie is a 128-ary tree.
16 * @param <T> the type of values in the trie.
20 private final char rangeOffset
;
21 private final int rangeSize
;
23 private final Node
<T
> root
;
25 /** Constructs a trie for holding strings of seven-bit characters. */
29 root
= new Node
<T
>(rangeSize
);
33 * Constructs a trie for holding strings of characters. The set of characters
34 * allowed in prefixes is given by the range [rangeOffset, lastCharInRange],
37 PrefixTrie(char firstCharInRange
, char lastCharInRange
) {
38 this.rangeOffset
= firstCharInRange
;
39 this.rangeSize
= lastCharInRange
- firstCharInRange
+ 1;
42 throw new IllegalArgumentException("Char range must include some chars");
45 root
= new Node
<T
>(rangeSize
);
49 * Maps prefix to value, which must not be null.
51 * @return the previous value stored for this prefix, or {@code null} if none
53 * @throws IllegalArgumentException if prefix is an empty string, or
54 * contains a character outside the range of legal prefix characters.
56 T
put(CharSequence prefix
, T value
) {
58 throw new NullPointerException();
60 return putInternal(prefix
, value
);
63 private T
putInternal(CharSequence prefix
, T value
) {
64 Node
<T
> current
= root
;
65 for (int i
= 0; i
< prefix
.length(); i
++) {
66 int nodeIndex
= prefix
.charAt(i
) - rangeOffset
;
68 Node
<T
> next
= current
.next
[nodeIndex
];
70 next
= current
.next
[nodeIndex
] = new Node
<T
>(rangeSize
);
73 } catch (ArrayIndexOutOfBoundsException e
) {
74 throw new IllegalArgumentException(
75 "'" + prefix
.charAt(i
) + "' is not a legal prefix character.");
78 T oldValue
= current
.value
;
79 current
.value
= value
;
84 * Finds a prefix that matches {@code s} and returns the mapped value. If
85 * multiple prefixes in the map match {@code s}, the longest match wins.
87 * @return value for prefix matching {@code s}, or {@code null} if none match
89 T
get(CharSequence s
) {
90 Node
<T
> deepestWithValue
= root
;
91 Node
<T
> current
= root
;
92 for (int i
= 0; i
< s
.length(); i
++) {
93 int nodeIndex
= s
.charAt(i
) - rangeOffset
;
94 if (nodeIndex
< 0 || rangeSize
<= nodeIndex
) {
97 current
= current
.next
[nodeIndex
];
98 if (current
== null) {
101 if (current
.value
!= null) {
102 deepestWithValue
= current
;
105 return deepestWithValue
.value
;
109 * Removes an exact prefix stored in the map, returning the old value,
110 * or null if it did not exist. Inverse operation of put(prefix).
112 * @return the previous value stored for this prefix, or {@code null} if none
114 * @throws IllegalArgumentException if prefix is an empty string, or
115 * contains a character outside the range of legal prefix characters.
117 T
remove(CharSequence prefix
) {
118 return putInternal(prefix
, null);
122 * Returns a Map containing the same data as this structure.
124 * <p>This implementation constructs and populates an entirely new map rather
125 * than providing a map view on the trie, so this is mostly useful for
128 * @return a Map mapping each prefix to its corresponding value.
130 Map
<String
, T
> toMap() {
131 Map
<String
, T
> map
= Maps
.newLinkedHashMap();
132 addEntries(root
, new StringBuilder(), map
);
137 * Adds to the given map all entries at or below the given node.
139 * @param builder a StringBuilder containing the prefix for the given node.
141 private void addEntries(
142 Node
<T
> node
, StringBuilder builder
, Map
<String
, T
> map
) {
143 if (node
.value
!= null) {
144 map
.put(builder
.toString(), node
.value
);
147 for (int i
= 0; i
< node
.next
.length
; i
++) {
148 Node
<T
> next
= node
.next
[i
];
150 builder
.append((char) (i
+ rangeOffset
));
151 addEntries(next
, builder
, map
);
152 builder
.deleteCharAt(builder
.length() - 1);
157 private static class Node
<T
> {
159 final Node
<T
>[] next
;
161 @SuppressWarnings({"rawtypes", "unchecked"})
162 Node(int numChildren
) {
163 next
= new Node
[numChildren
];