package org.jacorb.notification.util;
/*
* JacORB - a free Java ORB
*
* Copyright (C) 1999-2014 Gerald Brose / The JacORB Team.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the Free
* Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author Alphonse Bendt
*/
public class DefaultWildcardMap implements WildcardMap
{
public static final int DEFAULT_TOPLEVEL_SIZE = 4;
private final EntryList topLevel_;
////////////////////////////////////////
public DefaultWildcardMap(int topLevelSize)
{
super();
topLevel_ = new EntryList(topLevelSize);
}
public DefaultWildcardMap()
{
this(DEFAULT_TOPLEVEL_SIZE);
}
////////////////////////////////////////
public void clear()
{
topLevel_.clear();
}
public Object remove(Object key)
{
char[] _key = key.toString().toCharArray();
return topLevel_.remove(_key, 0, _key.length);
}
public Object put(Object key, Object value)
{
char[] _key = key.toString().toCharArray();
return topLevel_.put(_key, 0, _key.length, value);
}
public Object getNoExpansion(Object key)
{
char[] _key = key.toString().toCharArray();
return topLevel_.getSingle(_key, 0, _key.length);
}
public Object[] getWithExpansion(Object key)
{
char[] _key = key.toString().toCharArray();
return topLevel_.getMultiple(_key, 0, _key.length);
}
public String toString()
{
return topLevel_.toString();
}
}
/**
* the idea for this implementation is based on extensible hashing and trie's. an EntryList maps
* Strings to values. common prefixes of Strings are only stored once. <br>
* See section 4.1.10 and section 4.2.5 in my masters thesis available at
* http://www.jacorb.org/docs/DAbendt-web.pdf (in german) for a broader description of what has been
* implemented here.
*/
class EntryList
{
private static final char WILDCARD_CHAR = '*';
private static final int DEFAULT_INITIAL_SIZE = 2;
private Pattern myPattern_;
final char[] key_;
private final int start_;
int end_;
private final int depth_;
private int splitted = 0;
private MapEntry myEntry_;
private EntryList[] entries_;
////////////////////////////////////////
// Constructors
EntryList(int size)
{
this(null, 0, 0, 0, null, size);
}
private EntryList(char[] key, int start, int end, int depth, MapEntry value)
{
this(key, start, end, depth, value, DEFAULT_INITIAL_SIZE);
}
private EntryList(char[] key, int start, int end, int depth, MapEntry entry, int size)
{
myEntry_ = entry;
key_ = key;
end_ = end;
start_ = start;
depth_ = depth;
entries_ = new EntryList[size];
initPattern(key_, start_, end_);
}
////////////////////////////////////////
/**
* check if this EntryList has an Entry associated
*/
private boolean hasEntry()
{
return myEntry_ != null;
}
void clear()
{
entries_ = new EntryList[DEFAULT_INITIAL_SIZE];
}
Object put(char[] key, int start, int length, Object value)
{
return put(new MapEntry(key, start, length, value));
}
/**
* add an Entry to this List.
*/
private Object put(MapEntry entry)
{
char _first = entry.key_[0];
ensureIndexIsAvailable(_first);
int _idx = computeHashIndex(_first);
if (entries_[_idx] == null)
{
entries_[_idx] = new EntryList(entry.key_, 0, entry.key_.length, 0, entry);
return null;
}
return entries_[_idx].put(entry.key_, 0, entry.key_.length, 0, entry, false);
}
Object put(char[] key, int start, int stop, int depth, MapEntry value, boolean addLeadingStar)
{
int _insertKeyLength = stop - start;
int _myKeyLength = end_ - start_;
int _prefixLength = findCommonPrefix(key, start, stop);
if (_prefixLength == _insertKeyLength)
{
if (endsWithStar())
{
splitEntryList(this, _prefixLength);
}
Object _old = null;
// overwrite
if (myEntry_ != null)
{
_old = myEntry_.value_;
}
myEntry_ = value;
return _old;
}
else if (_prefixLength < _myKeyLength)
{
splitEntryList(this, _prefixLength);
boolean _addStar = false;
if (endsWithStar())
{
_addStar = true;
}
put(key, start, stop, depth + _prefixLength, value, _addStar);
}
else
// (_prefixLength > _myKeyLength)
{
char _firstRemainingChar = key[start + _prefixLength];
ensureIndexIsAvailable(_firstRemainingChar);
int idx = computeHashIndex(_firstRemainingChar);
if (entries_[idx] == null)
{
entries_[idx] = new EntryList(key, start + _prefixLength, stop, depth_
+ _prefixLength, value);
if (addLeadingStar)
{
entries_[idx].addLeadingStar();
}
}
else
{
entries_[idx].put(key, start + _prefixLength, stop, depth + _prefixLength, value,
false);
}
}
return null;
}
Object getSingle(char[] key, int start, int stop)
{
EntryList _entryList = lookup(key[start]);
int _position = start;
while (_entryList != null)
{
int _remainingKeyLength = stop - _position;
int _devoured = _entryList.compare(key, start + _entryList.depth_, start
+ _entryList.depth_ + _remainingKeyLength, false);
if (_devoured == _remainingKeyLength)
{
return _entryList.myEntry_.value_;
}
else if (_devoured > 0)
{
char _firstRemainingChar = key[start + _entryList.depth_ + _devoured];
int _oldDepth = _entryList.depth_;
_entryList = _entryList.lookup(_firstRemainingChar);
if (_entryList != null)
{
_position += _entryList.depth_ - _oldDepth;
}
}
}
return null;
}
/**
* check if the Key for this List ends with a star.
*/
private boolean endsWithStar()
{
return key_[end_ - 1] == WILDCARD_CHAR;
}
/**
* lookup a key in this list. thereby perform Wildcard expansion.
*/
Object[] getMultiple(char[] key, int start, int stop)
{
final List _toBeProcessed = new ArrayList();
final List _resultList = new ArrayList();
Cursor _startCursor;
// first try exact match
EntryList _list = lookup(key[start]);
if (_list != null)
{
// add EntryList to nodes to be processed
_toBeProcessed.add(new Cursor(start, _list));
}
// next try '*'
if ((_list = lookup(WILDCARD_CHAR)) != null)
{
// add EntryList to nodes to be processed
_startCursor = new Cursor(start, _list);
_toBeProcessed.add(_startCursor);
}
// process all found nodes
while (!_toBeProcessed.isEmpty())
{
Cursor _currentCursor = (Cursor) _toBeProcessed.get(0);
int _remainingKeyLength = stop - _currentCursor.cursor_;
// try to match the search key to the part of key which is
// associated with the current node
int _devoured = _currentCursor.list_.compare(key, start + _currentCursor.list_.depth_,
start + _currentCursor.list_.depth_ + _remainingKeyLength, true);
if (_devoured >= _remainingKeyLength)
{
// the whole key could be matched
if (_currentCursor.list_.hasEntry())
{
// if the current node has a result add it to the
// result set.
_resultList.add(_currentCursor.list_.myEntry_.value_);
}
if ((_remainingKeyLength > 0) && _currentCursor.list_.endsWithStar())
{
// current key ends with '*'
// this means the last compare matched everything
// nontheless there still might be outgoing edges
// which must be checked if we have some more chars in
// the key left.
for (int x = 0; x < _currentCursor.list_.entries_.length; ++x)
{
if (_currentCursor.list_.entries_[x] != null)
{
_toBeProcessed.add(new Cursor(_currentCursor.list_.depth_ + 1,
_currentCursor.list_.entries_[x]));
}
}
}
if (_currentCursor.list_.lookup(WILDCARD_CHAR) != null)
{
// if there is a outgoing '*' visit it
// because it might match the end of a key
_currentCursor.list_ = _currentCursor.list_.lookup(WILDCARD_CHAR);
_currentCursor.cursor_ += _devoured;
}
else
{
_toBeProcessed.remove(0);
}
}
else if (_devoured > 0)
{
// a part could be matched
char _firstRemainingChar = key[start + _currentCursor.list_.depth_ + _devoured];
int _oldDepth = _currentCursor.list_.depth_;
// '*' always matches
if (_currentCursor.list_.lookup(WILDCARD_CHAR) != null)
{
EntryList _entryList = _currentCursor.list_.lookup(WILDCARD_CHAR);
_toBeProcessed.add(new Cursor(_currentCursor.cursor_ + _entryList.depth_
- _oldDepth, _entryList));
}
if ((_currentCursor.list_ = _currentCursor.list_.lookup(_firstRemainingChar)) != null)
{
// instead of removing the old and adding a new
// cursor we reuse the old cursor
_currentCursor.cursor_ += _currentCursor.list_.depth_ - _oldDepth;
}
else
{
_toBeProcessed.remove(0);
}
}
else
{
// no part of the search key could be matched
_toBeProcessed.remove(0);
}
}
return _resultList.toArray();
}
Object remove(char[] key, int start, int stop)
{
return remove(this, key, start, stop);
}
private static Object remove(EntryList l, char[] key, int start, int stop)
{
int _cursor = start;
EntryList _current = l;
while (true)
{
int _devoured = findCommonPrefix(key, _cursor, stop, _current.key_, _current.start_,
_current.end_);
_cursor += _devoured;
if (_cursor == stop)
{
Object _old = null;
if (_current.myEntry_ != null)
{
_old = _current.myEntry_.value_;
_current.myEntry_ = null;
}
return _old;
}
char _firstNext = key[start + _devoured];
_current = _current.lookup(_firstNext);
if (_current == null)
{
return null;
}
}
}
////////////////////////////////////////
// private methods
private static class Cursor
{
int cursor_;
EntryList list_;
Cursor(int cursor, EntryList list)
{
cursor_ = cursor;
list_ = list;
}
public String toString()
{
String _rest = new String(list_.key_, cursor_, list_.end_ - cursor_);
return "Cursor: " + _rest;
}
}
private void addLeadingStar()
{
int _newLength = end_ - start_ + 1;
char[] _newKey = new char[_newLength];
System.arraycopy(key_, start_, _newKey, 1, end_ - start_);
_newKey[0] = WILDCARD_CHAR;
initPattern(_newKey, 0, _newLength);
}
private void initPattern()
{
initPattern(key_, start_, end_);
}
private void initPattern(char[] key, int start, int stop)
{
myPattern_ = null;
int _starCount = countStarsInKey(key, start, stop);
if (_starCount > 0)
{
char[] _pattern = new char[stop - start + _starCount + 1];
_pattern[0] = '^'; // regexp to match begin of line
int x = 0;
int _offset = 1;
while (x < (stop - start))
{
char _x = key[start + x];
_pattern[x + _offset] = _x;
// replace '*' with '.*'
if (_pattern[x + _offset] == WILDCARD_CHAR)
{
_pattern[x + _offset] = '.';
_pattern[x + _offset + 1] = WILDCARD_CHAR;
++_offset;
}
++x;
}
String _patternString = new String(_pattern, 0, stop - start + _starCount + 1);
myPattern_ = java.util.regex.Pattern.compile (_patternString);
}
}
private char key()
{
return key_[start_];
}
private EntryList lookup(char key)
{
int idx = computeHashIndex(key);
if (entries_[idx] != null && entries_[idx].key() == key)
{
return entries_[idx];
}
return null;
}
/**
* ensure that the index returned by computeHashIndex for a specified key is available. That
* means
* <ol>
* <li>The Index is empty
* <li>The Index contains an EntryList with the same Key as the specified one
* </ol>
*/
private void ensureIndexIsAvailable(char key)
{
int idx = computeHashIndex(key);
while (true)
{
// assert (idx < entries_.length);
if (entries_[idx] == null || entries_[idx].key() == key)
{
return;
}
doubleCapacity();
idx = computeHashIndex(key);
}
}
/**
* double the capacity for our entries. copy entries from old list into the new one.
*/
private void doubleCapacity()
{
int _newSize = entries_.length * 2;
EntryList[] _newList = new EntryList[_newSize];
for (int x = 0; x < entries_.length; ++x)
{
if (entries_[x] != null)
{
int _arrayPos = computeHashIndex(entries_[x].key(), _newSize);
_newList[_arrayPos] = entries_[x];
}
}
entries_ = _newList;
}
private int compare(char[] a, int start, int stop, boolean wildcard)
{
if (wildcard && myPattern_ != null)
{
return compareKeyToPattern(a, start, stop, myPattern_);
}
return compareKeyToKey(a, start, stop, key_, start_, end_);
}
private int findCommonPrefix(char[] key, int start, int stop)
{
return findCommonPrefix(key, start, stop, key_, start_, end_);
}
private void printToStringBuffer(StringBuffer sb, String offset)
{
if (key_ != null)
{
sb.append(" --");
sb.append(key());
sb.append("-->\n");
sb.append(offset);
sb.append("depth: ");
sb.append(depth_);
sb.append("\n");
sb.append(offset);
sb.append("key: ");
sb.append(new String(key_, start_, end_ - start_));
sb.append("\n");
}
if (myEntry_ != null)
{
sb.append(offset + myEntry_);
sb.append("\n");
}
for (int x = 0; x < entries_.length; x++)
{
sb.append(offset + x);
sb.append(":");
if (entries_[x] == null)
{
sb.append("empty");
}
else
{
entries_[x].printToStringBuffer(sb, offset + " ");
}
sb.append("\n");
}
}
public String toString()
{
StringBuffer _b = new StringBuffer();
printToStringBuffer(_b, "");
return _b.toString();
}
////////////////////////////////////////
// static methods
private static void splitEntryList(EntryList list, int offset)
{
EntryList _ret = new EntryList(list.key_, list.start_ + offset, list.end_, list.depth_
+ offset, list.myEntry_, list.entries_.length);
System.arraycopy(list.entries_, 0, _ret.entries_, 0, list.entries_.length);
list.entries_ = new EntryList[DEFAULT_INITIAL_SIZE];
char _key = list.key_[list.start_ + offset];
int _idx = computeHashIndex(_key, list.entries_.length);
list.entries_[_idx] = _ret;
list.myEntry_ = null;
list.splitted++;
list.end_ = list.start_ + offset;
if (list.endsWithStar())
{
_ret.addLeadingStar();
}
list.initPattern();
}
private static int computeHashIndex(char c, int size)
{
return c % size;
}
private int computeHashIndex(char c)
{
return computeHashIndex(c, entries_.length);
}
static int compareKeyToKey(char[] firstKeyArray, int start1, int stop1, char[] secondKeyArray,
int start2, int stop2)
{
int length1 = stop1 - start1;
int length2 = stop2 - start2;
int _guard = (length1 > length2) ? length2 : length1;
int _ret = 0;
while (_ret < _guard)
{
if (firstKeyArray[start1 + _ret] != secondKeyArray[start2 + _ret])
{
return _ret;
}
++_ret;
}
return _ret;
}
private static int compareKeyToPattern(char[] string1, int start1, int stop1, Pattern p)
{
String _other = new String(string1, start1, stop1 - start1);
Matcher m = p.matcher(_other);
if (m.find())
{
return m.end();
}
return 0;
}
private static int findCommonPrefix(char[] key1, int start1, int stop1, char[] key2,
int start2, int stop2)
{
int _x = 0;
int _length1 = stop1 - start1;
int _length2 = stop2 - start2;
int _guard = (_length1 >= _length2) ? _length2 : _length1;
while ((_x < _guard) && (key1[start1] == key2[start2]))
{
++start1;
++start2;
++_x;
}
return _x;
}
static int countStarsInKey(char[] key, int start, int end)
{
int _starCount = 0;
int x = start;
while (x < end)
{
if (key[x] == WILDCARD_CHAR)
{
++_starCount;
}
++x;
}
return _starCount;
}
/**
* This Class represents a Entry within a WildcardMap. Each Entry is identified by a key and has
* a value associated.
*/
private static class MapEntry
{
/**
* start index of key within key_ array
*/
private final int start_;
/**
* stop index of key within key_ array
*/
private final int stop_;
/**
* this array contains the key. start and stop index of the key are denoted by start_ and
* stop_
*/
final char[] key_;
/**
* value associated to this Entry
*/
final Object value_;
////////////////////////////////////////
/**
* Creates a new <code>WCEntry</code> instance.
*
* @param key
* a <code>char[]</code> value
* @param start
* an <code>int</code> value
* @param stop
* an <code>int</code> value
* @param value
* an <code>Object</code> value
*/
MapEntry(char[] key, int start, int stop, Object value)
{
key_ = key;
start_ = start;
stop_ = stop;
value_ = value;
}
////////////////////////////////////////
public int hashCode()
{
return key_[start_];
}
public boolean equals(Object o)
{
try
{
MapEntry _other = (MapEntry) o;
return (EntryList.compareKeyToKey(key_, start_, stop_, _other.key_, _other.start_,
_other.stop_) > 0);
} catch (ClassCastException e)
{
return super.equals(o);
} catch (NullPointerException e)
{
return false;
}
}
public String toString()
{
StringBuffer _b = new StringBuffer();
_b.append("['");
_b.append(new String(key_, start_, stop_ - start_));
_b.append("' => ");
_b.append(value_);
_b.append("]");
return _b.toString();
}
}
}