/* * ******************************************************************************** * Copyright (C) 2007-2009, International Business Machines Corporation and others. * All Rights Reserved. * ******************************************************************************** */ package com.ibm.icu.impl; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import com.ibm.icu.lang.UCharacter; import com.ibm.icu.text.UTF16; /** * TextTrieMap is a trie implementation for supporting * fast prefix match for the key. */ public class TextTrieMap { /** * Constructs a TextTrieMap object. * * @param ignoreCase true to use case insensitive match */ public TextTrieMap(boolean ignoreCase) { this.ignoreCase = ignoreCase; } /** * Adds the text key and its associated object in this object. * * @param text The text. * @param o The object associated with the text. */ public synchronized void put(String text, V o) { CharacterNode node = root; for (int i = 0; i < text.length(); i++) { int ch = UTF16.charAt(text, i); node = node.addChildNode(ch); if (UTF16.getCharCount(ch) == 2) { i++; } } node.addObject(o); } /** * Gets an iterator of the objects associated with the * longest prefix matching string key. * * @param text The text to be matched with prefixes. * @return An iterator of the objects associated with * the longest prefix matching matching key, or null * if no matching entry is found. */ public Iterator get(String text) { return get(text, 0); } /** * Gets an iterator of the objects associated with the * longest prefix matching string key starting at the * specified position. * * @param text The text to be matched with prefixes. * @param start The start index of of the text * @return An iterator of the objects associated with the * longest prefix matching matching key, or null if no * matching entry is found. */ public Iterator get(String text, int start) { LongestMatchHandler handler = new LongestMatchHandler(); find(text, start, handler); return handler.getMatches(); } public void find(String text, ResultHandler handler) { find(text, 0, handler); } public void find(String text, int start, ResultHandler handler) { find(root, text, start, start, handler); } /* * Find an iterator of the objects associated with the * longest prefix matching string key under the specified node. * * @param node The character node in this trie. * @param text The text to be matched with prefixes. * @param start The start index within the text. * @param index The current index within the text. * @param handler The result handler, ResultHandler#handlePrefixMatch * is called when any prefix match is found. */ private synchronized void find(CharacterNode node, String text, int start, int index, ResultHandler handler) { Iterator itr = node.iterator(); if (itr != null) { if (!handler.handlePrefixMatch(index - start, itr)) { return; } } if (index < text.length()) { List childNodes = node.getChildNodes(); if (childNodes == null) { return; } int ch = UTF16.charAt(text, index); int chLen = UTF16.getCharCount(ch); for (int i = 0; i < childNodes.size(); i++) { CharacterNode child = childNodes.get(i); if (compare(ch, child.getCharacter())) { find(child, text, start, index + chLen, handler); break; } } } } /** * A private method used for comparing two characters. * * @param ch1 The first character. * @param ch2 The second character. * @return true if the first character matches the second. */ private boolean compare(int ch1, int ch2) { if (ch1 == ch2) { return true; } else if (ignoreCase) { if (UCharacter.toLowerCase(ch1) == UCharacter.toLowerCase(ch2)) { return true; } else if (UCharacter.toUpperCase(ch1) == UCharacter.toUpperCase(ch2)) { return true; } } return false; } // The root node of this trie private CharacterNode root = new CharacterNode(0); // Character matching option boolean ignoreCase; /** * Inner class representing a character node in the trie. */ private class CharacterNode { int character; List children; List objlist; /** * Constructs a node for the character. * * @param ch The character associated with this node. */ public CharacterNode(int ch) { character = ch; } /** * Gets the character associated with this node. * * @return The character */ public int getCharacter() { return character; } /** * Adds the object to the node. * * @param obj The object set in the leaf node. */ public void addObject(V obj) { if (objlist == null) { objlist = new LinkedList(); } objlist.add(obj); } /** * Gets an iterator of the objects associated with * the leaf node. * * @return The iterator or null if no objects are * associated with this node. */ public Iterator iterator() { if (objlist == null) { return null; } return objlist.iterator(); } /** * Adds a child node for the character under this character * node in the trie. When the matching child node already * exists, the reference of the existing child node is * returned. * * @param ch The character associated with a child node. * @return The child node. */ public CharacterNode addChildNode(int ch) { if (children == null) { children = new ArrayList(); CharacterNode newNode = new CharacterNode(ch); children.add(newNode); return newNode; } CharacterNode node = null; for (int i = 0; i < children.size(); i++) { CharacterNode cur = children.get(i); if (compare(ch, cur.getCharacter())) { node = cur; break; } } if (node == null) { node = new CharacterNode(ch); children.add(node); } return node; } /** * Gets the list of child nodes under this node. * * @return The list of child nodes. */ public List getChildNodes() { return children; } } /** * Callback handler for processing prefix matches used by * find method. */ public interface ResultHandler { /** * Handles a prefix key match * * @param matchLength Matched key's length * @param values An iterator of the objects associated with the matched key * @return Return true to continue the search in the trie, false to quit. */ public boolean handlePrefixMatch(int matchLength, Iterator values); } private static class LongestMatchHandler implements ResultHandler { private Iterator matches = null; private int length = 0; public boolean handlePrefixMatch(int matchLength, Iterator values) { if (matchLength > length) { length = matchLength; matches = values; } return true; } public Iterator getMatches() { return matches; } } }