RadixTreeImpl.java revision 525ad75b9d6b8623143155d5d2f675508da415b8
1/*
2The MIT License
3
4Copyright (c) 2008 Tahseen Ur Rehman, Javid Jamae
5
6http://code.google.com/p/radixtree/
7
8Permission is hereby granted, free of charge, to any person obtaining a copy
9of this software and associated documentation files (the "Software"), to deal
10in the Software without restriction, including without limitation the rights
11to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12copies of the Software, and to permit persons to whom the Software is
13furnished to do so, subject to the following conditions:
14
15The above copyright notice and this permission notice shall be included in
16all copies or substantial portions of the Software.
17
18THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24THE SOFTWARE.
25*/
26
27package ds.tree;
28
29import java.util.ArrayList;
30import java.util.Formattable;
31import java.util.Formatter;
32import java.util.Iterator;
33import java.util.LinkedList;
34import 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 */
43public 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     */
52    public RadixTreeImpl() {
53        root = new RadixTreeNode<T>();
54        root.setKey("");
55        size = 0;
56    }
57
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
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
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     */
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     */
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
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
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
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
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     */
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     */
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
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
379    public void display() {
380        formatNodeTo(new Formatter(System.out), 0, root);
381    }
382
383    @Deprecated
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     */
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	 */
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     */
431	public String complete(String prefix) {
432		return complete(prefix, root, "");
433	}
434
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}