All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.owasp.esapi.codecs.HashTrie Maven / Gradle / Ivy

package org.owasp.esapi.codecs;

import java.io.IOException;
import java.io.PushbackReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.owasp.esapi.util.NullSafe;

/**
 * Trie implementation for CharSequence keys. This uses HashMaps for each
 * level instead of the traditional array. This is done as with unicode,
 * each level's array would be 64k entries.
 *
 * NOTE:
*
    *
  • @see java.util.Map.remove(Object) is not supported.
  • *
  • * If deletion support is added, the max key length will need work or removal. *
  • *
  • Null values are not supported.
  • *
* * @author Ed Schaller */ public class HashTrie implements Trie { private static class Entry implements Map.Entry { private CharSequence key; private T value; Entry(CharSequence key, T value) { this.key = key; this.value = value; } /** * Convenience instantiator. * @param key The key for the new instance * @param keyLength The length of the key to use * @param value The value for the new instance * @return null if key or value is null * new Entry(key,value) if {@link CharSequence#length()} == keyLength * new Entry(key.subSequence(0,keyLength),value) otherwise */ static Entry newInstanceIfNeeded(CharSequence key, int keyLength, T value) { if(value == null || key == null) return null; if(key.length() > keyLength) key = key.subSequence(0,keyLength); return new Entry(key,value); } /** * Convenience instantiator. * @param key The key for the new instance * @param value The value for the new instance * @return null if key or value is null * new Entry(key,value) otherwise */ static Entry newInstanceIfNeeded(CharSequence key, T value) { if(value == null || key == null) return null; return new Entry(key,value); } /*************/ /* Map.Entry */ /*************/ public CharSequence getKey() { return key; } public T getValue() { return value; } public T setValue(T value) { throw new UnsupportedOperationException(); } /********************/ /* java.lang.Object */ /********************/ public boolean equals(Map.Entry other) { return (NullSafe.equals(key, other.getKey()) && NullSafe.equals(value, other.getValue())); } @Override public boolean equals(Object o) { if(o instanceof Map.Entry) return equals((Map.Entry)o); return false; } @Override public int hashCode() { return NullSafe.hashCode(key) ^ NullSafe.hashCode(value); } @Override public String toString() { return NullSafe.toString(key) + " => " + NullSafe.toString(value); } } /** * Node inside the trie. */ private static class Node { private T value = null; private Map> nextMap; /** * Create a new Map for a node level. This is here so * that if the underlying * Map implementation needs to * be switched it is easily done. * @return A new Map for use. */ private static Map> newNodeMap() { return new HashMap>(); } /** * Create a new Map for a node level. This is here so * that if the underlying * Map implementation needs to * be switched it is easily done. * @param prev Previous map to use to populate the * new map. * @return A new Map for use. */ private static Map> newNodeMap(Map> prev) { return new HashMap>(prev); } /** * Set the value for the key terminated at this node. * @param value The value for this key. */ void setValue(T value) { this.value = value; } /** * Get the node for the specified character. * @param ch The next character to look for. * @return The node requested or null if it is not * present. */ Node getNextNode(Character ch) { if(nextMap == null) return null; return nextMap.get(ch); } /** * Recursively add a key. * @param key The key being added. * @param pos The position in key that is being handled * at this level. */ T put(CharSequence key, int pos, T addValue) { Node nextNode; Character ch; T old; if(key.length() == pos) { // at terminating node old = value; setValue(addValue); return old; } ch = key.charAt(pos); if(nextMap == null) { nextMap = newNodeMap(); nextNode = new Node(); nextMap.put(ch, nextNode); } else if((nextNode = nextMap.get(ch))==null) { nextNode = new Node(); nextMap.put(ch,nextNode); } return nextNode.put(key,pos+1,addValue); } /** * Recursively lookup a key's value. * @param key The key being looked up. * @param pos The position in the key that is being * looked up at this level. * @return The value associated with the key or null if none exists. */ T get(CharSequence key, int pos) { Node nextNode; if(key.length() <= pos) // <= instead of == just in case return value; // no value is null which is also not found if((nextNode = getNextNode(key.charAt(pos)))==null) return null; return nextNode.get(key,pos+1); } /** * Recursively lookup the longest key match. * @param key The key being looked up. * @param pos The position in the key that is being * looked up at this level. * @return The Entry associated with the longest key match or null if none exists. */ Entry getLongestMatch(CharSequence key, int pos) { Node nextNode; Entry ret; if(key.length() <= pos) // <= instead of == just in case return Entry.newInstanceIfNeeded(key,value); if((nextNode = getNextNode(key.charAt(pos)))==null) { // last in trie... return ourselves return Entry.newInstanceIfNeeded(key,pos,value); } if((ret = nextNode.getLongestMatch(key, pos+1))!=null) return ret; return Entry.newInstanceIfNeeded(key,pos,value); } /** * Recursively lookup the longest key match. * @param keyIn Where to read the key from * @param key The key that is being looked up at this level. * @return The Entry associated with the longest key * match or null if none exists. */ Entry getLongestMatch(PushbackReader keyIn, StringBuilder key) throws IOException { Node nextNode; Entry ret; int c; char ch; int prevLen; // read next key char and append to key... if((c = keyIn.read())<0) // end of input, return what we have currently return Entry.newInstanceIfNeeded(key,value); ch = (char)c; prevLen = key.length(); key.append(ch); if((nextNode = getNextNode(ch))==null) { // last in trie... return ourselves return Entry.newInstanceIfNeeded(key,value); } if((ret = nextNode.getLongestMatch(keyIn, key))!=null) return ret; // undo reading of key char and appending to key... key.setLength(prevLen); keyIn.unread(c); return Entry.newInstanceIfNeeded(key,value); } /** * Recursively rebuild the internal maps. */ void remap() { if(nextMap == null) return; nextMap = newNodeMap(nextMap); for(Node node : nextMap.values()) node.remap(); } /** * Recursively search for a value. * @param toFind The value to search for * @return true if the value was found * false otherwise */ boolean containsValue(Object toFind) { if(value != null && toFind.equals(value)) return true; if(nextMap == null) return false; for(Node node : nextMap.values()) if(node.containsValue(toFind)) return true; return false; } /** * Recursively build values. * @param values List being built. * @return true if the value was found * false otherwise */ Collection values(Collection values) { if(value != null) values.add(value); if(nextMap == null) return values; for(Node node : nextMap.values()) node.values(values); return values; } /** * Recursively build a key set. * @param key StringBuilder with our key. * @param keys Set to add to * @return keys with additions */ Set keySet(StringBuilder key, Set keys) { int len = key.length(); if(value != null) // MUST toString here keys.add(key.toString()); if(nextMap != null && nextMap.size() > 0) { key.append('X'); for(Map.Entry> entry : nextMap.entrySet()) { key.setCharAt(len,entry.getKey()); entry.getValue().keySet(key,keys); } key.setLength(len); } return keys; } /** * Recursively build a entry set. * @param key StringBuilder with our key. * @param entries Set to add to * @return entries with additions */ Set> entrySet(StringBuilder key, Set> entries) { int len = key.length(); if(value != null) // MUST toString here entries.add(new Entry(key.toString(),value)); if(nextMap != null && nextMap.size() > 0) { key.append('X'); for(Map.Entry> entry : nextMap.entrySet()) { key.setCharAt(len,entry.getKey()); entry.getValue().entrySet(key,entries); } key.setLength(len); } return entries; } } private Node root; private int maxKeyLen; private int size; public HashTrie() { clear(); } /** * Get the key value entry who's key is the longest prefix match. * @param key The key to lookup * @return Entry with the longest matching key. */ public Map.Entry getLongestMatch(CharSequence key) { if(root == null || key == null) return null; return root.getLongestMatch(key, 0); } /** * Get the key value entry who's key is the longest prefix match. * @param keyIn Pushback reader to read the key from. This should * have a buffer at least as large as {@link #getMaxKeyLength()} * or an IOException may be thrown backing up. * @return Entry with the longest matching key. * @throws IOException if keyIn.read() or keyIn.unread() does. */ public Map.Entry getLongestMatch(PushbackReader keyIn) throws IOException { if(root == null || keyIn == null) return null; return root.getLongestMatch(keyIn, new StringBuilder()); } /** * Get the maximum key length. * @return max key length. */ public int getMaxKeyLength() { return maxKeyLen; } /*****************/ /* java.util.Map */ /*****************/ /** * Clear all entries. */ public void clear() { root = null; maxKeyLen = -1; size = 0; } /** {@inheritDoc} */ public boolean containsKey(Object key) { return (get(key) != null); } /** {@inheritDoc} */ public boolean containsValue(Object value) { if(root == null) return false; return root.containsValue(value); } /** * Add mapping. * @param key The mapping's key. * @value value The mapping's value * @throws NullPointerException if key or value is null. */ public T put(CharSequence key, T value) throws NullPointerException { int len; T old; if(key == null) throw new NullPointerException("Null keys are not handled"); if(value == null) throw new NullPointerException("Null values are not handled"); if(root == null) root = new Node(); if((old = root.put(key,0,value))!=null) return old; // after in case of replacement if((len = key.length()) > maxKeyLen) maxKeyLen = len; size++; return null; } /** * Remove a entry. * @return previous value * @throws UnsupportedOperationException always. */ public T remove(Object key) throws UnsupportedOperationException { throw new UnsupportedOperationException(); } /** {@inheritDoc} */ public void putAll(Map map) { for(Map.Entry entry : map.entrySet()) put(entry.getKey(),entry.getValue()); } /** {@inheritDoc} */ public Set keySet() { Set keys = new HashSet(size); if(root == null) return keys; return root.keySet(new StringBuilder(), keys); } /** {@inheritDoc} */ public Collection values() { ArrayList values = new ArrayList(size()); if(root == null) return values; return root.values(values); } /** {@inheritDoc} */ public Set> entrySet() { Set> entries = new HashSet>(size()); if(root == null) return entries; return root.entrySet(new StringBuilder(), entries); } /** * Get the value for a key. * @param key The key to look up. * @return The value for key or null if the key is not found. */ public T get(Object key) { if(root == null || key == null) return null; if(!(key instanceof CharSequence)) return null; return root.get((CharSequence)key,0); } /** * Get the number of entries. * @return the number or entries. */ public int size() { return size; } /** {@inheritDoc} */ @Override public boolean equals(Object other) { if(other == null) return false; if(!(other instanceof Map)) return false; // per spec return entrySet().equals(((Map)other).entrySet()); } /** {@inheritDoc} */ @Override public int hashCode() { // per spec return entrySet().hashCode(); } /** {@inheritDoc} */ @Override public String toString() { StringBuilder sb; boolean first; if(isEmpty()) return "{}"; sb = new StringBuilder(); first = true; sb.append("{ "); for(Map.Entry entry : entrySet()) { if(first) first = false; else sb.append(", "); sb.append(entry.toString()); } sb.append(" }"); return sb.toString(); } /** {@inheritDoc} */ public boolean isEmpty() { return(size() == 0); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy