Return-Path:
A {@link java.util.HashMap} whose keys are sequenced. The
- * sequencing of the keys allow easy access to the values in the order
- * which they were added in. This class is thread safe. Implementing the List interface is not possible due to a instance
- * method name clash between the Collection and the List interface:
+ * This class inherits from {@link java.util.HashMap} purely for =
backwards
+ * compatibility. It should really be inheriting from {@link
+ * java.util.AbstractMap}, or with a tiny extra bit of work, implement =
the
+ * full map interface on its own. APIs should not rely on this class =
being an
+ * actual {@link java.util.HashMap}, and instead should recognize it =
only as a
+ * generic {@link java.util.Map} (unless, of course, you need the =
sequencing
+ * functionality, but even in that case, this class should not be =
referred to
+ * as a HashMap).
*
- *
- *
- *
- * Collections boolean remove(Object o)
- * Lists Object remove(Object o)
Although this map is sequenced, it cannot implement {@link + * java.util.List} because of incompatible interface definitions. The = remove + * methods in List and Map have different return values (see: {@link + * java.util.List#remove(Object)} and {@link = java.util.Map#remove(Object)}). * - *
So one cannot implement both interfaces at the same, which is - * unfortunate because the List interface would be very nice in - * conjuction with Velocity.
+ *This class is not thread safe. When a thread safe = implementation is + * required, wrap this class using {@link = Collections#synchronizedMap(Map)}, + * or use explicit synchronization controls. * - *
A slightly more complex implementation and interface could =
involve
- * the use of a list of Map.Entry
objects.
< 0
or >
the size of the map.
+ **/
+ private Map.Entry getEntry(int index) {
+ Entry pos =3D sentinel;
+
+ if(index < 0) {
+ throw new ArrayIndexOutOfBoundsException(index + " < 0");
+ }
+
+ // loop to one before the position
+ int i =3D -1;
+ while(i < index && pos.next !=3D sentinel) {
+ i++;
+ pos =3D pos.next;
+ }
+ // pos.next is the requested position
+ =20
+ // if sentinel is next, past end of list
+ if(pos.next =3D=3D sentinel) {
+ throw new ArrayIndexOutOfBoundsException(index + " >=3D " + =
(i + 1));
+ }
+
+ return pos.next;
+ }
+
+ /**
* Returns the key at the specified index.
+ *
+ * @exception ArrayIndexOutOfBoundsException if the =
index
is
+ * < 0
or >
the size of the map.
*/
public Object get (int index)
{
- return keySequence.get(index);
+ return getEntry(index).getKey();
}
=20
/**
* Returns the value at the specified index.
+ *
+ * @exception ArrayIndexOutOfBoundsException if the =
index
is
+ * < 0
or >
the size of the map.
*/
public Object getValue (int index)
{
- return get(get(index));
+ return getEntry(index).getValue();
}
=20
/**
@@ -168,7 +491,13 @@
*/
public int indexOf (Object key)
{
- return keySequence.indexOf(key);
+ Entry e =3D (Entry)entries.get(key);
+ int pos =3D 0;
+ while(e.prev !=3D sentinel) {
+ pos++;
+ e =3D e.prev;
+ }
+ return pos;
}
=20
/**
@@ -176,7 +505,7 @@
*/
public Iterator iterator ()
{
- return keySequence.iterator();
+ return keySet().iterator();
}
=20
/**
@@ -184,128 +513,48 @@
*/
public int lastIndexOf (Object key)
{
- return keySequence.lastIndexOf(key);
+ // keys in a map are guarunteed to be unique
+ return indexOf(key);
}
=20
/**
- * Returns the ordered sequence of keys.
+ * Returns a List view of the keys rather than a set view. The =
returned
+ * list is unmodifiable. This is required because changes to the =
values of
+ * the list (using {@link java.util.ListIterator#set(Object)}) will
+ * effectively remove the value from the list and reinsert that =
value at
+ * the end of the list, which is an unexpected side effect of =
changing the
+ * value of a list. This occurs because changing the key, changes =
when the
+ * mapping is added to the map and thus where it appears in the =
list.
*
- * This method is meant to be used for retrieval of Key / Value =
pairs
- * in e.g. Velocity:
- * - * ## $table contains a sequenced hashtable - * #foreach ($key in $table.sequence()) - * <TR> - * <TD>Key: $key</TD> - * </TD>Value: $table.get($key)</TD> - * </TR> - * #end - *+ *
An alternative to this method is to use {@link #keySet()}
*
- * @return The ordered list of keys.
+ * @see #keySet()
+ * @return The ordered list of keys. =20
*/
public List sequence()
- {
- return keySequence;
- }
-
- /**
- * Stores the provided key/value pair. Freshens the sequence of =
existing
- * elements.
- *
- * @param key The key to the provided value.
- * @param value The value to store.
- * @return The previous value for the specified key, or
- * null
if none.
- */
- public Object put (Object key, Object value)
- {
- Object prevValue =3D super.put(key, value);
- freshenSequence(key, prevValue);
- return prevValue;
- }
-
- /**
- * Freshens the sequence of the element value
if
- * value
is not null
.
- *
- * @param key The key whose sequence to freshen.
- * @param value The value whose existance to check before removing =
the old
- * key sequence.
- */
- protected void freshenSequence(Object key, Object value)
{
- if (value !=3D null)
- {
- // Freshening existing element's sequence.
- keySequence.remove(key);
+ List l =3D new ArrayList(size());
+ Iterator iter =3D keySet().iterator();
+ while(iter.hasNext()) {
+ l.add(iter.next());
}
- keySequence.add(key);
+ =20
+ return Collections.unmodifiableList(l);
}
=20
/**
- * Stores the provided key/value pairs.
- *
- * @param t The key/value pairs to store.
- */
- public void putAll (Map t)
- {
- Set set =3D t.entrySet();
- for (Iterator iter =3D set.iterator(); iter.hasNext(); )
- {
- Map.Entry e =3D (Map.Entry)iter.next();
- put(e.getKey(), e.getValue());
- }
- }
-
- /**
* Removes the element at the specified index.
*
* @param index The index of the object to remove.
* @return The previous value coressponding the =
key
, or
* null
if none existed.
- */
- public Object remove (int index)
- {
- return remove(index, null);
- }
-
- /**
- * Removes the element with the specified key.
*
- * @param key The Map
key of the object to remove.
- * @return The previous value coressponding the =
key
, or
- * null
if none existed.
+ * @exception ArrayIndexOutOfBoundsException if the =
index
is
+ * < 0
or >
the size of the map.
*/
- public Object remove (Object key)
+ public Object remove (int index)
{
- return remove(UNKNOWN_INDEX, key);
+ return remove(get(index));
}
=20
- /**
- * Removes the element with the specified key or index.
- *
- * @param index The index of the object to remove, or
- * UNKNOWN_INDEX
if not known.
- * @param key The Map
key of the object to remove.
- * @return The previous value coressponding the =
key
, or
- * null
if none existed.
- */
- private final Object remove (int index, Object key)
- {
- if (index =3D=3D UNKNOWN_INDEX)
- {
- index =3D indexOf(key);
- }
- if (key =3D=3D null)
- {
- key =3D get(index);
- }
- if (index !=3D UNKNOWN_INDEX)
- {
- keySequence.remove(index);
- }
- return super.remove(key);
- }
}
-
------=_NextPart_000_0016_01C16BEB.4D5FDFE0
Content-Type: application/octet-stream;
name="SequencedHashMap.java"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: attachment;
filename="SequencedHashMap.java"
package org.apache.commons.collections;
/* =
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
* The Apache Software License, Version 1.1
*
* Copyright (c) 2001 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowledgment may appear in the software =
itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Apache" and "Apache Software Foundation" and
* "Apache Turbine" must not be used to endorse or promote products
* derived from this software without prior written permission. For
* written permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache",
* "Apache Turbine", nor may "Apache" appear in their name, without
* prior written permission of the Apache Software Foundation.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* =
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
*
Although this map is sequenced, it cannot implement {@link * java.util.List} because of incompatible interface definitions. The = remove * methods in List and Map have different return values (see: {@link * java.util.List#remove(Object)} and {@link = java.util.Map#remove(Object)}). * *
This class is not thread safe. When a thread safe implementation =
is
* required, wrap this class using {@link =
Collections#synchronizedMap(Map)},
* or use explicit synchronization controls.
*
* @author Michael A. =
Smith
* @author Daniel Rall
* @author Henning P. =
Schmiedehausen=20
*/
public class SequencedHashMap extends HashMap
{
/**
* {@link java.util.Map.Entry} that doubles as a node in the linked =
list
* of sequenced mappings.
**/
private static class Entry implements Map.Entry {
private final Object key_;
private Object value_;
=20
Entry next =3D null;
Entry prev =3D null;
public Entry(Object key, Object value) {
this.key_ =3D key;
this.value_ =3D value;
}
public Object getKey() {=20
return this.key_;=20
}
public Object getValue() {=20
return this.value_;=20
}
public Object setValue(Object value) {=20
Object oldValue =3D this.value_;
this.value_ =3D value;=20
return oldValue;
}
public int hashCode() {=20
// per Map.Entry.hashCode() api docs
return ((getKey() =3D=3D null ? 0 : getKey().hashCode()) ^
(getValue()=3D=3Dnull ? 0 : getValue().hashCode())); =
}
public boolean equals(Object obj) {
if(obj =3D=3D null) return false;
if(obj =3D=3D this) return true;
Map.Entry other =3D (Map.Entry)obj;
// per Map.Entry.equals(Object api docs)
return((getKey() =3D=3D null ?
other.getKey() =3D=3D null :=20
getKey().equals(other.getKey())) &&
(getValue() =3D=3D null ?
other.getValue() =3D=3D null :=20
getValue().equals(other.getValue())));
}
public String toString() {
return "[" + getKey() + "=3D" + getValue() + "]";
}
}
private static final Entry createSentinel() {
Entry s =3D new Entry(null, null);
s.prev =3D s;
s.next =3D s;
return s;
}
private Entry sentinel;
private HashMap entries;
public SequencedHashMap() {
sentinel =3D createSentinel();
entries =3D new HashMap();
}
public SequencedHashMap(int initialSize) {
sentinel =3D createSentinel();
entries =3D new HashMap(initialSize);
}
public SequencedHashMap(int initialSize, float loadFactor) {
sentinel =3D createSentinel();
entries =3D new HashMap(initialSize, loadFactor);
}
public SequencedHashMap(Map m) {
this();
putAll(m);
}
/**
* Removes an internal entry from the linked list. This does not =
remove
* it from the underlying map.
**/
private void removeEntry(Entry entry) {
entry.next.prev =3D entry.prev;
entry.prev.next =3D entry.next; =20
}
/**
* Inserts a new internal entry to the linked list. This does not =
add the
* entry to the underlying map.
**/
private void insertEntry(Entry entry) {
entry.next =3D sentinel;
entry.prev =3D sentinel.prev;
sentinel.prev.next =3D entry;
sentinel.prev =3D entry;
}
public int size() {
return entries.size();
}
public boolean isEmpty() {
return entries.isEmpty();
}
public boolean containsKey(Object key) {
return entries.containsKey(key);
}
public boolean containsValue(Object value) {
return entries.containsValue(value);
}
public Object get(Object o) {
// find entry for the specified key
Entry entry =3D (Entry)entries.get(o);
if(entry =3D=3D null) return null;
=20
return entry.getValue();
}
public Object put(Object key, Object value) {
Object oldValue =3D null;
Entry e =3D (Entry)entries.get(key);
if(e !=3D null) {
// remove from list so the entry gets "moved" to the front =
of list
removeEntry(e);
// update value in map
oldValue =3D e.setValue(value);
} else {
// add new entry
e =3D new Entry(key, value);
entries.put(key, e);
}
// entry in map, but not list
// add to list
insertEntry(e);
return oldValue;
}
public Object remove(Object key) {
Entry e =3D (Entry)entries.remove(key);
if(e =3D=3D null) return null;
removeEntry(e);
return e.getValue();
}
// use abstract map impl
//void putAll(Map t)
public void clear() {
entries.clear();
sentinel.next =3D sentinel;
sentinel.prev =3D sentinel;
}
public Set keySet() {
return new AbstractSet() {
// required impl
public Iterator iterator() { return new =
OrderedIterator(KEY); }
public boolean remove(Object o) {
return SequencedHashMap.this.remove(o) !=3D null;
}
// more efficient impls than abstract set
public void clear() {=20
SequencedHashMap.this.clear();=20
}
public int size() {=20
return SequencedHashMap.this.size();=20
}
public boolean isEmpty() {=20
return SequencedHashMap.this.isEmpty();=20
}
public boolean contains(Object o) {=20
return SequencedHashMap.this.containsKey(o);
}
};
}
public Collection values() {
return new AbstractCollection() {
// required impl
public Iterator iterator() { return new =
OrderedIterator(VALUE); }
public boolean remove(Object o) {
for(Entry pos =3D sentinel.next; pos !=3D sentinel; pos =
=3D pos.next) {
if((pos.getValue() =3D=3D null && o =3D=3D null) ||
(pos.getValue() !=3D null && =
pos.getValue().equals(o))) {
SequencedHashMap.this.remove(pos.getKey());
return true;
}
}
return false;
}
// more efficient impls than abstract collection
public void clear() {=20
SequencedHashMap.this.clear();=20
}
public int size() {=20
return SequencedHashMap.this.size();=20
}
public boolean isEmpty() {=20
return SequencedHashMap.this.isEmpty();=20
}
public boolean contains(Object o) {
return SequencedHashMap.this.containsValue(o);
}
};
}
public Set entrySet() {
return new AbstractSet() {
// helper
private Entry findEntry(Object o) {
if(o =3D=3D null) return null;
if(!(o instanceof Map.Entry)) return null;
=20
Map.Entry e =3D (Map.Entry)o;
Entry entry =3D (Entry)entries.get(e.getKey());
if(entry.equals(e)) return entry;
else return null;
}
// required impl
public Iterator iterator() {=20
return new OrderedIterator(ENTRY);=20
}
public boolean remove(Object o) {
Entry e =3D findEntry(o);
if(e =3D=3D null) return false;
return SequencedHashMap.this.remove(e.getKey()) !=3D =
null;
} =20
// more efficient impls than abstract collection
public void clear() {=20
SequencedHashMap.this.clear();=20
}
public int size() {=20
return SequencedHashMap.this.size();=20
}
public boolean isEmpty() {=20
return SequencedHashMap.this.isEmpty();=20
}
public boolean contains(Object o) {
return findEntry(o) !=3D null;
}
};
}
private static final int KEY =3D 0;
private static final int VALUE =3D 1;
private static final int ENTRY =3D 2;
private class OrderedIterator implements Iterator {
private int returnType;
private Entry pos =3D sentinel;
boolean removable =3D false;
=20
public OrderedIterator(int returnType) {
this.returnType =3D returnType;
}
public boolean hasNext() {
return pos.next !=3D sentinel;
}
public Object next() {
if(pos.next =3D=3D sentinel) {
throw new NoSuchElementException();
}
pos =3D pos.next;
removable =3D true;
switch(returnType) {
case KEY:
return pos.getKey();
case VALUE:
return pos.getValue();
case ENTRY:
return pos;
default:
throw new Error("bad iterator type");
}
}
=20
public void remove() {
if(!removable) {
throw new IllegalStateException("remove() must follow =
next()");
}
=20
SequencedHashMap.this.remove(pos.getKey());
}
}
// APIs maintained from previous version of SequencedHashMap for =
backwards
// compatibility
/**
* Creates a shallow copy of this object, preserving the internal
* structure by copying only references. The keys, values, and
* sequence are not clone()
'd.
*
* @return A clone of this instance.
*/
public Object clone ()
{
// yes, calling super.clone() silly since we're just blowing =
away all
// the stuff that super might be doing anyway, but for =
motivations on
// this, see:
// =
http://www.javaworld.com/javaworld/jw-01-1999/jw-01-object.html
SequencedHashMap map =3D (SequencedHashMap)super.clone();
map.sentinel =3D createSentinel();
// note: this does not preserve the initial capacity and load =
factor.
map.entries =3D new HashMap();=20
=20
map.putAll(this);
return map;
}
/**
* Returns the Map.Entry at the specified index
*
* @exception ArrayIndexOutOfBoundsException if the specified index =
is
* < 0
or >
the size of the map.
**/
private Map.Entry getEntry(int index) {
Entry pos =3D sentinel;
if(index < 0) {
throw new ArrayIndexOutOfBoundsException(index + " < 0");
}
// loop to one before the position
int i =3D -1;
while(i < index && pos.next !=3D sentinel) {
i++;
pos =3D pos.next;
}
// pos.next is the requested position
=20
// if sentinel is next, past end of list
if(pos.next =3D=3D sentinel) {
throw new ArrayIndexOutOfBoundsException(index + " >=3D " + =
(i + 1));
}
return pos.next;
}
/**
* Returns the key at the specified index.
*
* @exception ArrayIndexOutOfBoundsException if the =
index
is
* < 0
or >
the size of the map.
*/
public Object get (int index)
{
return getEntry(index).getKey();
}
/**
* Returns the value at the specified index.
*
* @exception ArrayIndexOutOfBoundsException if the =
index
is
* < 0
or >
the size of the map.
*/
public Object getValue (int index)
{
return getEntry(index).getValue();
}
/**
* Returns the index of the specified key.
*/
public int indexOf (Object key)
{
Entry e =3D (Entry)entries.get(key);
int pos =3D 0;
while(e.prev !=3D sentinel) {
pos++;
e =3D e.prev;
}
return pos;
}
/**
* Returns a key iterator.
*/
public Iterator iterator ()
{
return keySet().iterator();
}
/**
* Returns the last index of the specified key.
*/
public int lastIndexOf (Object key)
{
// keys in a map are guarunteed to be unique
return indexOf(key);
}
/**
* Returns a List view of the keys rather than a set view. The =
returned
* list is unmodifiable. This is required because changes to the =
values of
* the list (using {@link java.util.ListIterator#set(Object)}) will
* effectively remove the value from the list and reinsert that =
value at
* the end of the list, which is an unexpected side effect of =
changing the
* value of a list. This occurs because changing the key, changes =
when the
* mapping is added to the map and thus where it appears in the =
list.
*
*
An alternative to this method is to use {@link #keySet()}
*
* @see #keySet()
* @return The ordered list of keys. =20
*/
public List sequence()
{
List l =3D new ArrayList(size());
Iterator iter =3D keySet().iterator();
while(iter.hasNext()) {
l.add(iter.next());
}
=20
return Collections.unmodifiableList(l);
}
/**
* Removes the element at the specified index.
*
* @param index The index of the object to remove.
* @return The previous value coressponding the =
key
, or
* null
if none existed.
*
* @exception ArrayIndexOutOfBoundsException if the =
index
is
* < 0
or >
the size of the map.
*/
public Object remove (int index)
{
return remove(get(index));
}
}
------=_NextPart_000_0016_01C16BEB.4D5FDFE0
Content-Type: application/octet-stream;
name="test-results.post"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: attachment;
filename="test-results.post"
Buildfile: build.xml
init:
build-java:
build-test:
test:
[java] .........................................
[java] .........................................
[java] .........................................
[java] .........................................
[java] .........................................
[java] .........................................
[java] .........................................
[java] ........F........F.........................
[java] .........................................
[java] .............
[java] Time: 1.432
[java] There were 2 failures:
[java] 1) =
testIterator(org.apache.commons.collections.TestHashBag)junit.framework.A=
ssertionFailedError: First should be 'A' expected: but was:
[java] at =
org.apache.commons.collections.TestBag.testIterator(Unknown Source)
[java] at sun.reflect.NativeMethodAccessorImpl.invoke0(Native =
Method)
[java] at =
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java=
:42)
[java] at =
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorI=
mpl.java:28)
[java] 2) =
testIteratorFail(org.apache.commons.collections.TestHashBag)junit.framewo=
rk.AssertionFailedError: First should be 'A' expected: but was:
[java] at =
org.apache.commons.collections.TestBag.testIteratorFail(Unknown Source)
[java] at sun.reflect.NativeMethodAccessorImpl.invoke0(Native =
Method)
[java] at =
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java=
:42)
[java] at =
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorI=
mpl.java:28)
[java]=20
[java] FAILURES!!!
[java] Tests run: 382, Failures: 2, Errors: 0
[java]=20
Total time: 4 seconds
------=_NextPart_000_0016_01C16BEB.4D5FDFE0
Content-Type: text/plain; charset=us-ascii
--
To unsubscribe, e-mail: