• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2 The MIT License
3 
4 Copyright (c) 2008 Tahseen Ur Rehman, Javid Jamae
5 
6 http://code.google.com/p/radixtree/
7 
8 Permission is hereby granted, free of charge, to any person obtaining a copy
9 of this software and associated documentation files (the "Software"), to deal
10 in the Software without restriction, including without limitation the rights
11 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 copies of the Software, and to permit persons to whom the Software is
13 furnished to do so, subject to the following conditions:
14 
15 The above copyright notice and this permission notice shall be included in
16 all copies or substantial portions of the Software.
17 
18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 THE SOFTWARE.
25 */
26 
27 package ds.tree;
28 
29 import java.util.ArrayList;
30 import java.util.Formattable;
31 import java.util.Formatter;
32 import java.util.Iterator;
33 import java.util.LinkedList;
34 import java.util.Queue;
35 
36 /**
37  * Implementation for Radix tree {@link RadixTree}
38  *
39  * @author Tahseen Ur Rehman (tahseen.ur.rehman {at.spam.me.not} gmail.com)
40  * @author Javid Jamae
41  * @author Dennis Heidsiek
42  */
43 public class RadixTreeImpl<T> implements RadixTree<T>, Formattable {
44 
45     protected RadixTreeNode<T> root;
46 
47     protected long size;
48 
49     /**
50      * Create a Radix Tree with only the default node root.
51      */
RadixTreeImpl()52     public RadixTreeImpl() {
53         root = new RadixTreeNode<T>();
54         root.setKey("");
55         size = 0;
56     }
57 
find(String key)58     public T find(String key) {
59         Visitor<T,T> visitor = new VisitorImpl<T,T>() {
60 
61             public void visit(String key, RadixTreeNode<T> parent,
62                     RadixTreeNode<T> node) {
63                 if (node.isReal())
64                     result = node.getValue();
65             }
66         };
67 
68         visit(key, visitor);
69 
70         return visitor.getResult();
71     }
72 
replace(String key, final T value)73     public boolean replace(String key, final T value) {
74         Visitor<T,T> visitor = new VisitorImpl<T,T>() {
75             public void visit(String key, RadixTreeNode<T> parent, RadixTreeNode<T> node) {
76                 if (node.isReal()) {
77                     node.setValue(value);
78                     result = value;
79                 } else {
80                     result = null;
81                 }
82             }
83         };
84 
85         visit(key, visitor);
86 
87         return visitor.getResult() != null;
88     }
89 
delete(String key)90     public boolean delete(String key) {
91         Visitor<T, Boolean> visitor = new VisitorImpl<T, Boolean>(Boolean.FALSE) {
92             public void visit(String key, RadixTreeNode<T> parent,
93                     RadixTreeNode<T> node) {
94                 result = node.isReal();
95 
96                 // if it is a real node
97                 if (result) {
98                     // If there no children of the node we need to
99                     // delete it from the its parent children list
100                     if (node.getChildern().size() == 0) {
101                         Iterator<RadixTreeNode<T>> it = parent.getChildern()
102                                 .iterator();
103                         while (it.hasNext()) {
104                             if (it.next().getKey().equals(node.getKey())) {
105                                 it.remove();
106                                 break;
107                             }
108                         }
109 
110                         // if parent is not real node and has only one child
111                         // then they need to be merged.
112                         if (parent.getChildern().size() == 1
113                                 && parent.isReal() == false) {
114                             mergeNodes(parent, parent.getChildern().get(0));
115                         }
116                     } else if (node.getChildern().size() == 1) {
117                         // we need to merge the only child of this node with
118                         // itself
119                         mergeNodes(node, node.getChildern().get(0));
120                     } else { // we jus need to mark the node as non real.
121                         node.setReal(false);
122                     }
123                 }
124             }
125 
126             /**
127              * Merge a child into its parent node. Operation only valid if it is
128              * only child of the parent node and parent node is not a real node.
129              *
130              * @param parent
131              *            The parent Node
132              * @param child
133              *            The child Node
134              */
135             private void mergeNodes(RadixTreeNode<T> parent,
136                     RadixTreeNode<T> child) {
137                 parent.setKey(parent.getKey() + child.getKey());
138                 parent.setReal(child.isReal());
139                 parent.setValue(child.getValue());
140                 parent.setChildern(child.getChildern());
141             }
142 
143         };
144 
145         visit(key, visitor);
146 
147         if(visitor.getResult()) {
148             size--;
149         }
150         return visitor.getResult().booleanValue();
151     }
152 
153     /*
154      * (non-Javadoc)
155      * @see ds.tree.RadixTree#insert(java.lang.String, java.lang.Object)
156      */
insert(String key, T value)157     public void insert(String key, T value) throws DuplicateKeyException {
158         try {
159 			insert(key, root, value);
160 		} catch (DuplicateKeyException e) {
161 			// re-throw the exception with 'key' in the message
162 			throw new DuplicateKeyException("Duplicate key: '" + key + "'");
163 		}
164         size++;
165     }
166 
167     /**
168      * Recursively insert the key in the radix tree.
169      *
170      * @param key The key to be inserted
171      * @param node The current node
172      * @param value The value associated with the key
173      * @throws DuplicateKeyException If the key already exists in the database.
174      */
insert(String key, RadixTreeNode<T> node, T value)175     private void insert(String key, RadixTreeNode<T> node, T value)
176             throws DuplicateKeyException {
177 
178         int numberOfMatchingCharacters = node.getNumberOfMatchingCharacters(key);
179 
180         // we are either at the root node
181         // or we need to go down the tree
182         if (node.getKey().equals("") == true || numberOfMatchingCharacters == 0 || (numberOfMatchingCharacters < key.length() && numberOfMatchingCharacters >= node.getKey().length())) {
183             boolean flag = false;
184             String newText = key.substring(numberOfMatchingCharacters, key.length());
185             for (RadixTreeNode<T> child : node.getChildern()) {
186                 if (child.getKey().startsWith(newText.charAt(0) + "")) {
187                     flag = true;
188                     insert(newText, child, value);
189                     break;
190                 }
191             }
192 
193             // just add the node as the child of the current node
194             if (flag == false) {
195                 RadixTreeNode<T> n = new RadixTreeNode<T>();
196                 n.setKey(newText);
197                 n.setReal(true);
198                 n.setValue(value);
199 
200                 node.getChildern().add(n);
201             }
202         }
203         // there is a exact match just make the current node as data node
204         else if (numberOfMatchingCharacters == key.length() && numberOfMatchingCharacters == node.getKey().length()) {
205             if (node.isReal() == true) {
206                 throw new DuplicateKeyException("Duplicate key");
207             }
208 
209             node.setReal(true);
210             node.setValue(value);
211         }
212         // This node need to be split as the key to be inserted
213         // is a prefix of the current node key
214         else if (numberOfMatchingCharacters > 0 && numberOfMatchingCharacters < node.getKey().length()) {
215             RadixTreeNode<T> n1 = new RadixTreeNode<T>();
216             n1.setKey(node.getKey().substring(numberOfMatchingCharacters, node.getKey().length()));
217             n1.setReal(node.isReal());
218             n1.setValue(node.getValue());
219             n1.setChildern(node.getChildern());
220 
221             node.setKey(key.substring(0, numberOfMatchingCharacters));
222             node.setReal(false);
223             node.setChildern(new ArrayList<RadixTreeNode<T>>());
224             node.getChildern().add(n1);
225 
226             if(numberOfMatchingCharacters < key.length()) {
227 	            RadixTreeNode<T> n2 = new RadixTreeNode<T>();
228 	            n2.setKey(key.substring(numberOfMatchingCharacters, key.length()));
229 	            n2.setReal(true);
230 	            n2.setValue(value);
231 
232 	            node.getChildern().add(n2);
233             } else {
234                 node.setValue(value);
235                 node.setReal(true);
236             }
237         }
238         // this key need to be added as the child of the current node
239         else {
240             RadixTreeNode<T> n = new RadixTreeNode<T>();
241             n.setKey(node.getKey().substring(numberOfMatchingCharacters, node.getKey().length()));
242             n.setChildern(node.getChildern());
243             n.setReal(node.isReal());
244             n.setValue(node.getValue());
245 
246             node.setKey(key);
247             node.setReal(true);
248             node.setValue(value);
249 
250             node.getChildern().add(n);
251         }
252     }
253 
searchPrefix(String key, int recordLimit)254     public ArrayList<T> searchPrefix(String key, int recordLimit) {
255         ArrayList<T> keys = new ArrayList<T>();
256 
257         RadixTreeNode<T> node = searchPefix(key, root);
258 
259         if (node != null) {
260             if (node.isReal()) {
261                 keys.add(node.getValue());
262             }
263             getNodes(node, keys, recordLimit);
264         }
265 
266         return keys;
267     }
268 
getNodes(RadixTreeNode<T> parent, ArrayList<T> keys, int limit)269     private void getNodes(RadixTreeNode<T> parent, ArrayList<T> keys, int limit) {
270         Queue<RadixTreeNode<T>> queue = new LinkedList<RadixTreeNode<T>>();
271 
272         queue.addAll(parent.getChildern());
273 
274         while (!queue.isEmpty()) {
275             RadixTreeNode<T> node = queue.remove();
276             if (node.isReal() == true) {
277                 keys.add(node.getValue());
278             }
279 
280             if (keys.size() == limit) {
281                 break;
282             }
283 
284             queue.addAll(node.getChildern());
285         }
286     }
287 
searchPefix(String key, RadixTreeNode<T> node)288     private RadixTreeNode<T> searchPefix(String key, RadixTreeNode<T> node) {
289         RadixTreeNode<T> result = null;
290 
291         int numberOfMatchingCharacters = node.getNumberOfMatchingCharacters(key);
292 
293         if (numberOfMatchingCharacters == key.length() && numberOfMatchingCharacters <= node.getKey().length()) {
294             result = node;
295         } else if (node.getKey().equals("") == true
296                 || (numberOfMatchingCharacters < key.length() && numberOfMatchingCharacters >= node.getKey().length())) {
297             String newText = key.substring(numberOfMatchingCharacters, key.length());
298             for (RadixTreeNode<T> child : node.getChildern()) {
299                 if (child.getKey().startsWith(newText.charAt(0) + "")) {
300                     result = searchPefix(newText, child);
301                     break;
302                 }
303             }
304         }
305 
306         return result;
307     }
308 
contains(String key)309     public boolean contains(String key) {
310         Visitor<T, Boolean> visitor = new VisitorImpl<T,Boolean>(Boolean.FALSE) {
311             public void visit(String key, RadixTreeNode<T> parent,
312                     RadixTreeNode<T> node) {
313                 result = node.isReal();
314             }
315         };
316 
317         visit(key, visitor);
318 
319         return visitor.getResult().booleanValue();
320     }
321 
322     /**
323      * visit the node those key matches the given key
324      * @param key The key that need to be visited
325      * @param visitor The visitor object
326      */
visit(String key, Visitor<T, R> visitor)327     public <R> void visit(String key, Visitor<T, R> visitor) {
328         if (root != null) {
329             visit(key, visitor, null, root);
330         }
331     }
332 
333     /**
334      * recursively visit the tree based on the supplied "key". calls the Visitor
335      * for the node those key matches the given prefix
336      *
337      * @param prefix
338      *            The key o prefix to search in the tree
339      * @param visitor
340      *            The Visitor that will be called if a node with "key" as its
341      *            key is found
342      * @param node
343      *            The Node from where onward to search
344      */
visit(String prefix, Visitor<T, R> visitor, RadixTreeNode<T> parent, RadixTreeNode<T> node)345     private <R> void visit(String prefix, Visitor<T, R> visitor,
346             RadixTreeNode<T> parent, RadixTreeNode<T> node) {
347 
348         int numberOfMatchingCharacters = node.getNumberOfMatchingCharacters(prefix);
349 
350         // if the node key and prefix match, we found a match!
351         if (numberOfMatchingCharacters == prefix.length() && numberOfMatchingCharacters == node.getKey().length()) {
352             visitor.visit(prefix, parent, node);
353         } else if (node.getKey().equals("") == true // either we are at the
354                 // root
355                 || (numberOfMatchingCharacters < prefix.length() && numberOfMatchingCharacters >= node.getKey().length())) { // OR we need to
356             // traverse the childern
357             String newText = prefix.substring(numberOfMatchingCharacters, prefix.length());
358             for (RadixTreeNode<T> child : node.getChildern()) {
359                 // recursively search the child nodes
360                 if (child.getKey().startsWith(newText.charAt(0) + "")) {
361                     visit(newText, visitor, node, child);
362                     break;
363                 }
364             }
365         }
366     }
367 
getSize()368     public long getSize() {
369         return size;
370     }
371 
372     /**
373      * Display the Trie on console.
374      *
375      * WARNING! Do not use this for a large Trie, it's for testing purpose only.
376      * @see formatTo
377      */
378     @Deprecated
display()379     public void display() {
380         formatNodeTo(new Formatter(System.out), 0, root);
381     }
382 
383     @Deprecated
display(int level, RadixTreeNode<T> node)384     private void display(int level, RadixTreeNode<T> node) {
385         formatNodeTo(new Formatter(System.out), level, node);
386     }
387 
388     /**
389      * WARNING! Do not use this for a large Trie, it's for testing purpose only.
390      */
formatNodeTo(Formatter f, int level, RadixTreeNode<T> node)391     private void formatNodeTo(Formatter f, int level, RadixTreeNode<T> node) {
392         for (int i = 0; i < level; i++) {
393             f.format(" ");
394         }
395         f.format("|");
396         for (int i = 0; i < level; i++) {
397             f.format("-");
398         }
399 
400         if (node.isReal() == true)
401             f.format("%s[%s]*%n", node.getKey(),  node.getValue());
402         else
403             f.format("%s%n", node.getKey());
404 
405         for (RadixTreeNode<T> child : node.getChildern()) {
406             formatNodeTo(f, level + 1, child);
407         }
408 	}
409 
410 	/**
411 	 * Writes a textual representation of this tree to the given formatter.
412 	 *
413 	 * Currently, all options are simply ignored.
414 	 *
415      * WARNING! Do not use this for a large Trie, it's for testing purpose only.
416 	 */
formatTo(Formatter formatter, int flags, int width, int precision)417 	public void formatTo(Formatter formatter, int flags, int width, int precision) {
418 		formatNodeTo(formatter, 0, root);
419 	}
420 
421     /**
422      * Complete the a prefix to the point where ambiguity starts.
423      *
424      *  Example:
425      *  If a tree contain "blah1", "blah2"
426      *  complete("b") -> return "blah"
427      *
428      * @param prefix The prefix we want to complete
429      * @return The unambiguous completion of the string.
430      */
complete(String prefix)431 	public String complete(String prefix) {
432 		return complete(prefix, root, "");
433 	}
434 
complete(String key, RadixTreeNode<T> node, String base)435 	private String complete(String key, RadixTreeNode<T> node, String base) {
436         int i = 0;
437         int keylen = key.length();
438         int nodelen = node.getKey().length();
439 
440         while (i < keylen && i < nodelen) {
441             if (key.charAt(i) != node.getKey().charAt(i)) {
442                 break;
443             }
444             i++;
445         }
446 
447         if (i == keylen && i <= nodelen) {
448             return base + node.getKey();
449         }
450         else if (nodelen == 0 || (i < keylen && i >= nodelen)) {
451             String beginning = key.substring(0, i);
452             String ending = key.substring(i, keylen);
453             for (RadixTreeNode<T> child : node.getChildern()) {
454                 if (child.getKey().startsWith(ending.charAt(0) + "")) {
455                     return complete(ending, child, base + beginning);
456                 }
457             }
458         }
459 
460         return "";
461     }
462 }