org.owasp.esapi.codecs.HashTrie Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of esapi Show documentation
Show all versions of esapi Show documentation
The Enterprise Security API (ESAPI) project is an OWASP project
to create simple strong security controls for every web platform.
Security controls are not simple to build. You can read about the
hundreds of pitfalls for unwary developers on the OWASP website. By
providing developers with a set of strong controls, we aim to
eliminate some of the complexity of creating secure web applications.
This can result in significant cost savings across the SDLC.
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 extends CharSequence, ? extends T> map)
{
for(Map.Entry extends CharSequence, ? extends T> 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