/** * */ package ecologylab.collections; import java.util.ArrayList; import java.util.Set; import ecologylab.generic.Debug; /** * Recursive unit (bucket) for prefix pattern matching. * * @author andruid * */ public class PrefixPhrase<O extends Object> extends Debug { final String phrase; final PrefixPhrase parent; ChildPrefixMap childPhraseMap = new ChildPrefixMap(); private O mappedObject; public O getMappedObject() { return mappedObject; } public void setMappedObject(O mappedObject) { this.mappedObject = mappedObject; } /** * */ public PrefixPhrase(PrefixPhrase parent, String phrase) { this.parent = parent; this.phrase = phrase; } public PrefixPhrase add(String string, char separator) { return add(string, 0, separator); } protected PrefixPhrase add(String string, int start, char separator) { int end = string.length(); boolean terminate = false; if (start == end) terminate = true; else { if (string.charAt(start) == separator) start++; if (start == end) terminate = true; } if (terminate) { clear(); return this; } int nextSeparator = string.indexOf(separator, start); if (nextSeparator == -1) nextSeparator = end; if (nextSeparator > -1) { String phraseString = string.substring(start, nextSeparator); // extra round of messing with synch, because we need to know if we // are creating a new Phrase PrefixPhrase nextPrefixPhrase = getPrefix(this, phraseString); if (nextPrefixPhrase != null) { return nextPrefixPhrase.add(string, nextSeparator, separator); } else { // done! PrefixPhrase newTerminal = lookupChild(phraseString); //newTerminal.clear(); return newTerminal; // synchronized (this) // { // nextPrefixPhrase = getPrefix(this, phraseString); // if (nextPrefixPhrase == null) // { // nextPrefixPhrase = childPhraseMap.getOrCreateAndPutIfNew(phraseString, this); // result = nextPrefixPhrase; // } // } } } else { Debug.println("help! wrong block!!!"); // last segment return null; } } public boolean match(String string, char separator) { return match(string, 0, separator); } protected boolean match(String string, int start, char separator) { if (isTerminal()) return true; int end = string.length(); boolean terminate = false; if (start == end) terminate = true; else { if (string.charAt(start) == separator) start++; if (start == end) terminate = true; } if (terminate) { return false; } int nextSeparator = string.indexOf(separator, start); if (nextSeparator == -1) nextSeparator = end; // String phraseString = string.substring(start, nextSeparator); // PrefixPhrase nextPrefixPhrase = lookupChild(phraseString); PrefixPhrase nextPrefixPhrase = matchChild(string, start, nextSeparator); return (nextPrefixPhrase != null) ? nextPrefixPhrase.match(string, nextSeparator, separator) : false; } /** * Match child prefix by iterating, instead of using HashMap, to avoid allocating substring keys. * * @param source String to get key from. * @param start start of substring for key in string * @param end end of substring for key in string * * @return Matching PrefixPhrase for the substring key from source, or null if there is no match. */ private PrefixPhrase matchChild(String source, int start, int end) { // FIXME concurrent modification exception :-( PrefixPhrase wildcardPhrase = childPhraseMap.getWildcardMatch(); if (wildcardPhrase != null) return wildcardPhrase; Set<String> keySet = childPhraseMap.keySet(); for (String thatPhrase : keySet) { if (match(thatPhrase, source, start, end)) return childPhraseMap.get(thatPhrase); } return null; } /** * * @param target * @param source * @param start * @param end * * @return true if the substring of source running from start to end is the same String as target. */ private static boolean match(String target, String source, int start, int end) { int targetLength = target.length(); int sourceLength = end - start; if (targetLength != sourceLength) return false; for (int i=0; i<sourceLength; i++) { if (source.charAt(start++) != target.charAt(i)) return false; } return true; } /** * Seek the PrefixPhrase corresponding to the argument. * If it does not exist, return it. * <p/> * If it does exist, does it have 0 children? * If so, return null. No need to insert for the argument's phrase. * If not, return it. * * @param prefixPhrase * @return */ protected PrefixPhrase getPrefix(PrefixPhrase parent, String prefixPhrase) { PrefixPhrase preexistingPrefix = childPhraseMap.get(prefixPhrase); // will match wildcard specially, if this is called for boolean createNew = false; if (preexistingPrefix == null) { synchronized (childPhraseMap) { if (preexistingPrefix == null) { preexistingPrefix = new PrefixPhrase(parent, prefixPhrase); childPhraseMap.put(prefixPhrase, preexistingPrefix); createNew = true; } } } if (!createNew && preexistingPrefix.isTerminal()) return null; return preexistingPrefix; } protected PrefixPhrase lookupChild(String prefix) { return childPhraseMap.get(prefix); } protected void clear() { childPhraseMap.clear(); } public void toStringBuilder(StringBuilder buffy, char separator) { if (parent != null) { parent.toStringBuilder(buffy, separator); buffy.append(separator); } buffy.append(phrase); } /** * From this root, find eac the terminal children. * * @param buffy * @param separator */ void findTerminals(ArrayList<PrefixPhrase> phraseSet) { if (phrase == null) return; if (isTerminal()) { phraseSet.add(this); } else { for (PrefixPhrase childPhrase: childPhraseMap.values()) childPhrase.findTerminals(phraseSet); } } public int numChildren() { return childPhraseMap.size(); } /** * Is the end of a prefix. * * @return */ public boolean isTerminal() { return numChildren() == 0; } public ArrayList<String> values(char separator) { if (phrase == null) return new ArrayList<String>(0); ArrayList<PrefixPhrase> terminalPrefixPhrases = new ArrayList<PrefixPhrase>(); findTerminals(terminalPrefixPhrases); ArrayList<String> result = new ArrayList<String>(terminalPrefixPhrases.size()); StringBuilder buffy = new StringBuilder();//TODO StringBuilderUtils.acquire(buffy) for (PrefixPhrase thatPhrase : terminalPrefixPhrases) { buffy.setLength(0); thatPhrase.toStringBuilder(buffy, separator); result.add(buffy.substring(0, buffy.length())); } //TODO StringBuilderUtils.release(buffy) return result; } public String getMatchingPhrase(String purl,char seperator) { StringBuilder buffy = new StringBuilder();//TODO StringBuilderUtils.acquire(buffy) getMatchingPhrase(buffy, purl, seperator); String result = buffy.toString(); buffy.setLength(0); //TODO StringBuilderUtils.release(buffy) return result; } /** * This function returns the whole matching path which you have * followed to reach the PrefixPhrase. * @param purl * @param seperator * @return */ public void getMatchingPhrase(StringBuilder buffy, String purl,char seperator) { String returnValue=""; int seperatorIndex = purl.indexOf(seperator); if(seperatorIndex>0) { String key = purl.substring(0, seperatorIndex); String phrase = purl.substring(seperatorIndex+1,purl.length()); PrefixPhrase childPrefixPhrase= childPhraseMap.get(key); // now handled inside ChildPrefixMap // if(childPrefixPhrase==null) // { // // try getting it using wildcard as key // childPrefixPhrase = childPhraseMap.get("*"); // key="*"; // } if(childPrefixPhrase!=null) { buffy.append(returnValue).append(key).append(seperator); buffy.append(childPrefixPhrase.getMatchingPhrase(phrase, seperator)); } } } public PrefixPhrase getMatchingPrefix(String input, int start, char seperator) { if (isTerminal()) return this; int seperatorIndex = input.indexOf(seperator, start); if(seperatorIndex>0) { String key = input.substring(start, seperatorIndex); PrefixPhrase childPrefixPhrase = childPhraseMap.get(key); if (childPrefixPhrase!=null) { return (seperatorIndex < input.length()) ? childPrefixPhrase.getMatchingPrefix(input, seperatorIndex+1, seperator) : this; } } return null; } public void removePrefix(String prefix) { childPhraseMap.remove(prefix); } @Override public String toString() { String result = this.phrase; if (parent != null) result += " < " + parent; return result; } }