maybe the files will attach as text files...
----- Original Message -----
From: "Stephen Colebourne" <[EMAIL PROTECTED]>
To: "Jakarta Commons Developers List" <[EMAIL PROTECTED]>
Sent: Sunday, March 09, 2003 12:12 AM
Subject: Re: [collections] Faster than HashMap?
> Trying to attach the files again...
>
> ----- Original Message -----
> From: "Juozas Baliuka" <[EMAIL PROTECTED]>
> To: "Jakarta Commons Developers List" <[EMAIL PROTECTED]>
> Sent: Saturday, March 08, 2003 7:09 PM
> Subject: Re: [collections] Faster than HashMap?
>
>
> >
> > No file is attached.
> > It will be faster on "small" maps, but method code size is limited,
65535
> > bytes or something like this and it needs some workaround.
> >
> > ----- Original Message -----
> > From: "Stephen Colebourne" <[EMAIL PROTECTED]>
> > To: "Jakarta Commons Developers List" <[EMAIL PROTECTED]>
> > Sent: Saturday, March 08, 2003 7:43 PM
> > Subject: [collections] Faster than HashMap?
> >
> >
> > > I'd had an idea for a way to create a map thats faster to read than a
> > > HashMap...
> > >
> > > The idea was to bytecode generate the mapping from the hashcode to the
> > > index:
> > > public int lookupIndex(Object key) {
> > > switch (key.hashCode()) {
> > > case -1682: // hashcode
> > > return 2; // index of Map.Entry
> > > case 86929 // hashcode
> > > return 0; // index of Map.Entry
> > > case 1596969: // hashcode
> > > return 1; // index of Map.Entry
> > > }
> > > return -1;
> > > }
> > >
> > > Simple? No hashcode collisions. Should go faster....
> > >
> > > Attached are the java files. My initial testing was with a size 3 map,
> and
> > > that goes about 40% faster. Unfortunately, the bytecode map's
> performance
> > > degenerates with size. Depending on exact data size 50 entries or more
> may
> > > be slower, gradually getting slower.
> > >
> > > Of course now I realise that the switch statement in bytecode isn't
> > magical
> > > and is just doing a binary search, so was always going to suffer this
> > > problem. What an idiot I am.
> > >
> > > If anyone else wants to take a look and see if I've missed something,
or
> > has
> > > another bright idea triggered by this then feel free to take a look at
> the
> > > code. Otherwise, its not worth adding to [collections]. :-((((
> > >
> > > Stephen
> > >
> > >
> >
> >
>
> --------------------------------------------------------------------------
> --
> > ----
> >
> >
> > > ---------------------------------------------------------------------
> > > To unsubscribe, e-mail: [EMAIL PROTECTED]
> > > For additional commands, e-mail: [EMAIL PROTECTED]
> >
> >
> > ---------------------------------------------------------------------
> > To unsubscribe, e-mail: [EMAIL PROTECTED]
> > For additional commands, e-mail: [EMAIL PROTECTED]
> >
>
>
----------------------------------------------------------------------------
----
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: [EMAIL PROTECTED]
> For additional commands, e-mail: [EMAIL PROTECTED]
/*
* $Header:
/home/cvs/jakarta-commons/collections/src/java/org/apache/commons/collections/iterators/ResetableIterator.java,v
1.1 2003/01/15 21:49:59 scolebourne Exp $
* ====================================================================
*
* The Apache Software License, Version 1.1
*
* Copyright (c) 2003 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 "The Jakarta Project", "Commons", and "Apache Software
* Foundation" must not be used to endorse or promote products derived
* from this software without prior written permission. For written
* permission, please contact [EMAIL PROTECTED]
*
* 5. Products derived from this software may not be called "Apache"
* nor may "Apache" appear in their names 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.
* ====================================================================
*
* 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
* <http://www.apache.org/>.
*
*/
package org.apache.commons.collections;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.apache.bcel.Constants;
import org.apache.bcel.generic.BIPUSH;
import org.apache.bcel.generic.ClassGen;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.InstructionConstants;
import org.apache.bcel.generic.InstructionFactory;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.InstructionList;
import org.apache.bcel.generic.LOOKUPSWITCH;
import org.apache.bcel.generic.MethodGen;
import org.apache.bcel.generic.SIPUSH;
import org.apache.bcel.generic.Type;
/**
* <code>CodeGenMapGenerator</code> code generates the class for the map.
*
* @since Commons Collections 2.2
* @version $Revision: 1.1 $ $Date: 2003/01/15 21:49:59 $
*
* @author Stephen Colebourne
*/
public class CodeGenMapGenerator extends ClassLoader {
private static final Type[] NO_ARGS = new Type[0];
private static final Type[] ARG_TYPES = new Type[] {Type.OBJECT};
private static final String[] ARG_NAMES = new String[] {"key"};
/** Counter used to distinguish separate instances of the lookup */
private static long counter = 0;
/** Table of loaded classes (thread-safe) */
private Map classes = Collections.synchronizedMap(new HashMap());
/**
* Constructor.
* @param parent the parent class loader
*/
CodeGenMapGenerator(ClassLoader parent) {
super(parent);
}
// protected synchronized Class loadClass(String name, boolean resolve) throws
ClassNotFoundException {
// return super.loadClass(name, resolve);
// }
public static void main(String[] args) {
Map map = new CodeGenMap(null, true);
map.put("one", "onevalue");
map.put("day", "dayvalue");
map.put("this", "thisvalue");
map.put("will", "willvalue");
map.put("work", "workvalue");
map.put("well", null);
}
/**
* Create the lookup object.
*/
CodeGenMap.IndexLookup createLookup(CodeGenMap map) {
// long currentCounter = 0;
// synchronized (CodeGenHashMapGenerator.class) {
// currentCounter = counter++;
// }
String className = "org.apache.commons.collections.CodeGenMap$Lookup";// +
Long.toString(currentCounter);
ClassGen cg = new ClassGen(
className,
"org.apache.commons.collections.CodeGenMap$IndexLookup", // superclass
"<generated>", // filename
Constants.ACC_SUPER | Constants.ACC_PUBLIC | Constants.ACC_FINAL,
null
);
ConstantPoolGen cp = cg.getConstantPool();
InstructionList il = new InstructionList();
// constructor
cg.addEmptyConstructor(Constants.ACC_PUBLIC);
// lookupIndex()
MethodGen mg = new MethodGen(
Constants.ACC_PUBLIC | Constants.ACC_FINAL,
Type.INT, ARG_TYPES, ARG_NAMES,
"lookupIndex", className, il, cp
);
InstructionFactory factory = new InstructionFactory(cg);
// call key.hashCode()
il.append(InstructionConstants.ALOAD_1);
il.append(factory.createInvoke("java.lang.Object", "hashCode", Type.INT,
NO_ARGS, Constants.INVOKEVIRTUAL));
InstructionHandle switchPos = il.getEnd();
// sort entries by hashcode
int size = size = map.size();
int[] hashCodes = new int[size];
int[] indices = new int[size];
hashCodes[0] = map.mappings[0].getKey().hashCode();
indices[0] = 0;
for (int i = 1; i < size; i++) {
int loopHashCode = map.mappings[i].getKey().hashCode();
int insert = search(hashCodes, i, loopHashCode);
if (insert < (size - 1) && loopHashCode == hashCodes[insert]) {
throw new IllegalArgumentException("Duplicate hashCode");
}
if (insert < i) {
System.arraycopy(hashCodes, insert, hashCodes, insert + 1, i - insert);
System.arraycopy(indices, insert, indices, insert + 1, i - insert);
}
hashCodes[insert] = loopHashCode;
indices[insert] = i;
}
// build up bytecode
InstructionHandle[] targets = new InstructionHandle[size];
for (int i = 0; i < size; i++) {
int index = indices[i];
if (index == 0) {
targets[i] = il.append(InstructionConstants.ICONST_0);
} else if (index == 1) {
targets[i] = il.append(InstructionConstants.ICONST_1);
} else if (index == 2) {
targets[i] = il.append(InstructionConstants.ICONST_2);
} else if (index == 3) {
targets[i] = il.append(InstructionConstants.ICONST_3);
} else if (index == 4) {
targets[i] = il.append(InstructionConstants.ICONST_4);
} else if (index == 5) {
targets[i] = il.append(InstructionConstants.ICONST_5);
} else if (index < 128) {
targets[i] = il.append(new BIPUSH((byte) index));
} else if (index < 32768) {
targets[i] = il.append(new SIPUSH((short) index));
} else {
throw new IllegalArgumentException("CodeGenMap max size exceeded");
}
il.append(InstructionConstants.IRETURN);
}
InstructionHandle defaultTarget = il.append(InstructionConstants.ICONST_M1);
il.append(InstructionConstants.IRETURN);
il.append(switchPos, new LOOKUPSWITCH(hashCodes, targets, defaultTarget));
mg.setMaxStack();
cg.addMethod(mg.getMethod());
il.dispose();
// try {
// ByteArrayOutputStream out = new ByteArrayOutputStream();
// cg.getJavaClass().dump(out);
// byte[] bytes = out.toByteArray();
// for (int i = 0; i < bytes.length; i++) {
// System.out.println(Integer.toHexString(bytes[i]) + " " + bytes[i] +
" " + ((char) bytes[i]));
// }
// cg.getJavaClass().dump("D:/dev/AA.class");
// } catch (IOException ex) {
// }
Class cl = null;
try {
byte[] bytes = cg.getJavaClass().getBytes();
cl = defineClass(className, bytes, 0, bytes.length);
return (CodeGenMap.IndexLookup) cl.newInstance();
} catch (InstantiationException ex) {
throw new IllegalStateException("Instantiation error during code
generation - " + ex.getMessage());
} catch (IllegalAccessException ex) {
throw new IllegalStateException("Illegal access error during code
generation - " + ex.getMessage());
}
}
/**
* Binary search of an array with specified bounds.
* <p>
* The array must be presorted.
*
* @param array the array
* @param size the size to limit by
* @param search the search value
* @return index of the search or insertion point
*/
private int search(int[] array, int size, int search) {
int start = 0;
int end = size - 1;
while (start <= end) {
int midIndex = (start + end) >> 1;
int midVal = array[midIndex];
if (midVal < search) {
start = midIndex + 1;
} else if (midVal > search) {
end = midIndex - 1;
} else {
// exact match found
return midIndex;
}
}
// no exact match found
return start;
}
}
/*
* $Header:
/home/cvs/jakarta-commons/collections/src/java/org/apache/commons/collections/iterators/ResetableIterator.java,v
1.1 2003/01/15 21:49:59 scolebourne Exp $
* ====================================================================
*
* The Apache Software License, Version 1.1
*
* Copyright (c) 2003 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 "The Jakarta Project", "Commons", and "Apache Software
* Foundation" must not be used to endorse or promote products derived
* from this software without prior written permission. For written
* permission, please contact [EMAIL PROTECTED]
*
* 5. Products derived from this software may not be called "Apache"
* nor may "Apache" appear in their names 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.
* ====================================================================
*
* 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
* <http://www.apache.org/>.
*
*/
package org.apache.commons.collections;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.AbstractCollection;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.Map.Entry;
import org.apache.commons.collections.iterators.ResetableIterator;
/**
* <code>CodeGenMap</code> is a Map implementation that uses bytecode generation
* at runtime to optimise the hashcode lookup.
* <p>
* It is most efficient to use another map type (such as a HashMap) to initially
* setup the map. Those values can then be copied into the CodeGenMap in one go,
* requiring only one bytecode generation.
* <p>
* The map can be made unmodifiable directly, using one of the constructors. This
* will be more efficient than using <code>Collections.unmodifiableMap()</code>.
*
* @since Commons Collections 2.2
* @version $Revision: 1.1 $ $Date: 2003/01/15 21:49:59 $
*
* @author Stephen Colebourne
*/
public class CodeGenMap implements Map, Cloneable {
/** Singleton instance of the empty lookup */
protected static final IndexLookup EMPTY_LOOKUP = new EmptyIndexLookup();
/** Serial version id */
private static final long serialVersionUID = 96239629072091L;
/** Maximum size of map is bytecode limited (a short divided by two) */
private static final int MAX_SIZE = 32768;
/**
* The array of key-values.
*/
protected MapEntryImpl[] mappings = new MapEntryImpl[8];
/**
* The size of the mappings.
*/
protected int size = 0;
/**
* Whether the map is modifiable.
*/
protected boolean modifiable = true;
/**
* The lookup hash to value object.
*/
protected transient IndexLookup lookup = EMPTY_LOOKUP;
/**
* The modifiaction counter used to implement the fail-fast behaviour.
*/
protected transient volatile int modCount;
/**
* The lazily created entry set.
*/
protected transient Set entrySet;
/**
* The lazily created key set.
*/
protected transient Set keySet;
/**
* The lazily created values collection.
*/
protected transient Collection valuesCollection;
// Constructors
// ----------------------------------------------------------------------
/**
* Constructs a new empty modifiable CodeGenMap instance.
*/
public CodeGenMap() {
this(null, true);
}
/**
* Constructs a new modifiable CodeGenMap instance, copying an existing map.
*
* @param map the map to copy, may be null (empty map created)
*/
public CodeGenMap(Map map) {
this(map, true);
}
/**
* Constructs a new CodeGenMap instance, copying an existing map.
* <p>
* The map may optionally be set to be unmodifiable.
*
* @param map the map to copy, may be null (empty map created)
* @param modifiable the modifiability of the map
*/
public CodeGenMap(Map map, boolean modifiable) {
super();
if (map != null) {
if (map.containsKey(null)) {
throw new NullPointerException("Map must not contain a null Key");
}
putAll(map);
}
this.modifiable = modifiable;
}
// Map access
// ----------------------------------------------------------------------
/**
* Gets the value to which the specified key is mapped.
* <p>
* Returns <code>null</code> if the map contains no mapping for this key,
* or if there is a mapping with a value of <code>null</code>. Use the
* <code>containsKey()</code> method to separate these cases.
*
* @param key the key whose value is to be returned
* @return the value mapped to that key, or null
*/
public Object get(Object key) {
// if (key != null) {
// MapIterator it = mapIterator();
// while (it.hasNext()) {
// Object loopKey = (Object) it.next();
// if (key == loopKey || key.equals(loopKey)) {
// return it.getValue();
// }
// }
// }
// return null;
try {
if (key != null) {
MapEntryImpl e = mappings[lookup.lookupIndex(key)];
if (key == e.key || key.equals(e.key)) {
return e.value;
}
}
return null;
} catch (ArrayIndexOutOfBoundsException ex) {
// from key not found
return null;
}
}
/**
* Gets the number of key-value mappings in this map.
*
* @return the current size of the map
*/
public int size() {
return size;
}
/**
* Returns <code>true</code> if this map contains no mappings.
*
* @return is the map currently empty
*/
public boolean isEmpty() {
return (size == 0);
}
/**
* Returns <code>true</code> if this map contains a mapping for the
* specified key. <code>null</code> may be passed into this method
* and will always return false.
*
* @param key the key to be searched for
* @return true if the map contains the key
*/
public boolean containsKey(Object key) {
if (key != null) {
return (lookupEntry(key) != null);
}
return false;
}
/**
* Returns <code>true</code> if this map contains one or more keys
* mapping to the specified value.
*
* @param value the value to be searched for
* @return true if the map contains the value
*/
public boolean containsValue(Object value) {
if (value == null) {
for (int i = 0; i < size; i++) {
if (mappings[i].value == null) {
return true;
}
}
} else {
for (int i = 0; i < size; i++) {
if (value.equals(mappings[i].value)) {
return true;
}
}
}
return false;
}
// Map modification
// ----------------------------------------------------------------------
/**
* Associates the specified value with the specified key in this map.
* <p>
* If the map previously contained a mapping for this key, the old
* value is replaced <i>in position</i> and returned. In all other cases,
* the new key and value are appended to the <i>end</i> of the map.
*
* @param key the key with which the value is to be associated
* @param value the value to be associated with this key
* @return the value previously mapped to the key, or null
* @throws NullPointerException if the key is <code>null</code>
* @throws UnsupportedOperationException if the map is not modifiable
*/
public Object put(Object key, Object value) {
if (modifiable == false) {
throw new UnsupportedOperationException("The map cannot be modified");
}
if (key == null) {
throw new NullPointerException("Key must not be null");
}
// do the put
Object previous = null;
MapEntryImpl entry = lookupEntry(key);
if (entry == null) {
// key not previously there, so append
ensureCapacity(size + 1);
mappings[size++] = new MapEntryImpl(this, key, value);
modCount++;
rehash();
} else {
// overwrite previous value
previous = entry.value;
entry.key = key;
entry.value = value;
}
return previous;
}
/**
* Copies all of the mappings from the specified map to this one.
* <p>
* If the map previously contained a mapping for a key, the old
* value is replaced <i>in position</i>. In all other cases, the
* new key and value are appended to the <i>end</i> of the map.
*
* @param in the map whose mappings are to be copied
* @throws NullPointerException if the map is <code>null</code>
* @throws NullPointerException if the map contains a <code>null</code> key
* @throws UnsupportedOperationException if the map is not modifiable
*/
public void putAll(Map map) {
if (modifiable == false) {
throw new UnsupportedOperationException("The map cannot be modified");
}
if (map == null) {
throw new NullPointerException("Map must not be null");
}
if (map.size() == 0) {
return;
}
if (map.containsKey(null)) {
throw new NullPointerException("Map must not contain a null Key");
}
ensureCapacity(size + map.size());
// loop to copy data
int index = 0;
for (Iterator it = map.entrySet().iterator(); it.hasNext();) {
Map.Entry entry = (Map.Entry) it.next();
MapEntryImpl ourEntry = lookupEntry(entry.getKey());
if (ourEntry == null) {
// key not previously there, so append
mappings[size++] = new MapEntryImpl(this, entry.getKey(),
entry.getValue());
modCount++;
} else {
// overwrite previous value
ourEntry.key = entry.getKey();
ourEntry.value = entry.getValue();
}
}
rehash();
}
/**
* Removes any mapping for this key, and return any previously mapped value.
* An attempt to remove a <code>null</code> key will have no effect.
*
* @param key the key whose mapping is to be removed
* @return the value removed, or null
* @throws UnsupportedOperationException if the map is not modifiable
*/
public Object remove(Object key) {
if (modifiable == false) {
throw new UnsupportedOperationException("The map cannot be modified");
}
if (key == null) {
return null;
}
Object previous = null;
MapEntryImpl entry = lookupEntry(key);
if (entry == null) {
return null; // nothing to remove
}
previous = entry.value;
int index = lookup.lookupIndex(key);
if (index < (size - 1)) {
System.arraycopy(mappings, index + 1, mappings, index, size - index - 1);
}
size--;
modCount++;
mappings[size] = null;
rehash();
return previous;
}
/**
* Removes all mappings from this map.
*
* @throws UnsupportedOperationException if the map is not modifiable
*/
public void clear() {
if (modifiable == false) {
throw new UnsupportedOperationException("The map cannot be modified");
}
modCount++;
mappings = new MapEntryImpl[0];
size = 0;
rehash();
}
/**
* Is this map modifiable?
*
* @return true if modifiable
*/
public boolean isModifiable() {
return modifiable;
}
// // Map iterator view
// // ----------------------------------------------------------------------
// /**
// * Gets a MapIterator used for fast access to the keys and values
// * in a Map.
// * <p>
// * This iterator will be more efficient than using
// * <code>entrySet().iterator()</code>.
// *
// * @return a MapIterator instance
// */
// public MapIterator mapIterator() {
// return new MapIteratorImpl(this);
// }
//
// /**
// * Map iterator implementation.
// */
// protected static class MapIteratorImpl extends IteratorImpl implements
MapIterator {
// /**
// * Constructor
// */
// protected MapIteratorImpl(CodeGenMap map) {
// super(map);
// }
// /**
// * Gets the next entry
// */
// protected Object nextEntry() {
// return map.lookupEntry(index).getKey();
// }
// /**
// * @see org.apache.commons.collections.MapIterator#getKey()
// */
// public Object getKey() {
// if (index == -1) {
// throw new IllegalStateException("next() must be called before
getKey()");
// }
// return map.lookupEntry(index).getKey();
// }
// /**
// * @see org.apache.commons.collections.MapIterator#getValue()
// */
// public Object getValue() {
// if (index == -1) {
// throw new IllegalStateException("next() must be called before
getValue()");
// }
// return map.lookupEntry(index).getValue();
// }
// /**
// * @see org.apache.commons.collections.MapIterator#setValue()
// */
// public void setValue(Object value) {
// if (map.isModifiable() == false) {
// throw new UnsupportedOperationException("The map cannot be
modified");
// }
// if (index == -1) {
// throw new IllegalStateException("next() must be called before
setValue()");
// }
// map.lookupEntry(index).setValue(value);
// }
// /**
// * @see org.apache.commons.collections.MapIterator#getMapEntry()
// */
// public Entry getMapEntry() {
// if (index == -1) {
// throw new IllegalStateException("next() must be called before
getMapEntry()");
// }
// return map.lookupEntry(index);
// }
// }
// Entry set view
// ----------------------------------------------------------------------
/**
* Gets a set of the entries in the Map. Each element in the returned
* collection is a <code>Map.Entry</code>.
* <p>
* Where possible use [EMAIL PROTECTED] #mapIterator()} as this is
* more efficient than using <code>entrySet().iterator()</code>.
* <p>
* The iterator does not support <code>add</code> or <code>addAll</code>.
*
* @return Set of the map keys
*/
public Set entrySet() {
if (entrySet == null) {
entrySet = new EntrySetImpl(this);
}
return entrySet;
}
/**
* Entry set implementation.
*/
protected static class EntrySetImpl extends AbstractSet {
protected final CodeGenMap map;
/**
* Constructor.
* @param map the owning map
*/
protected EntrySetImpl(CodeGenMap map) {
super();
this.map = map;
}
/**
* @see java.util.Collection#size()
*/
public int size() {
return map.size();
}
/**
* @see java.util.Collection#iterator()
*/
public Iterator iterator() {
return new EntrySetIteratorImpl(map);
}
/**
* @see java.util.Collection#remove(java.lang.Object)
*/
public boolean remove(Object entry) {
if (entry instanceof Map.Entry == false) {
return false;
}
Object key = ((Map.Entry) entry).getKey();
if (map.containsKey(key) == false) {
return false;
}
map.remove(key);
return true;
}
/**
* @see java.util.Collection#clear()
*/
public void clear() {
map.clear();
}
/**
* @see java.lang.Object#toString()
*/
public String toString() {
return "EntrySetImpl[size=" + size() + "]";
}
}
/**
* EntrySet iterator implementation.
*/
protected static class EntrySetIteratorImpl extends IteratorImpl {
/**
* Constructor.
*/
protected EntrySetIteratorImpl(CodeGenMap map) {
super(map);
}
/**
* Gets the next entry
*/
protected Object nextEntry() {
return map.lookupEntry(index);
}
}
// Key set view
// ----------------------------------------------------------------------
/**
* Gets a set of the keys in the Map.
* <p>
* Where possible use [EMAIL PROTECTED] #mapIterator()} as this is
* more efficient than using a key set.
* <p>
* The iterator does not support <code>add</code> or <code>addAll</code>.
*
* @return Set of the map keys
*/
public Set keySet() {
if (keySet == null) {
keySet = new KeySetImpl(this);
}
return keySet;
}
/**
* Key set implementation.
*/
protected static class KeySetImpl extends AbstractSet {
protected final CodeGenMap map;
/**
* Constructor.
* @param map the owning map
*/
protected KeySetImpl(CodeGenMap map) {
super();
this.map = map;
}
/**
* @see java.util.Collection#size()
*/
public int size() {
return map.size();
}
/**
* @see java.util.Collection#iterator()
*/
public Iterator iterator() {
return new KeySetIteratorImpl(map);
}
/**
* @see java.util.Collection#remove(java.lang.Object)
*/
public boolean remove(Object key) {
if (map.containsKey(key) == false) {
return false;
}
map.remove(key);
return true;
}
/**
* @see java.util.Collection#clear()
*/
public void clear() {
map.clear();
}
/**
* @see java.lang.Object#toString()
*/
public String toString() {
return "KeySetImpl[size=" + size() + "]";
}
}
/**
* KeySet iterator implementation.
*/
protected static class KeySetIteratorImpl extends IteratorImpl {
/**
* Constructor.
*/
protected KeySetIteratorImpl(CodeGenMap map) {
super(map);
}
/**
* Gets the next entry
*/
protected Object nextEntry() {
return map.lookupEntry(index).getKey();
}
}
// Value collection view
// ----------------------------------------------------------------------
/**
* Gets a collection of the values in the Map.
* <p>
* The iterator does not support <code>add</code> or <code>addAll</code>.
*
* @return Collection of the map values
*/
public Collection values() {
if (valuesCollection == null) {
valuesCollection = new ValuesImpl(this);
}
return valuesCollection;
}
/**
* Values collection implementation.
*/
protected static class ValuesImpl extends AbstractCollection {
protected final CodeGenMap map;
/**
* Constructor.
* @param map the owning map
*/
protected ValuesImpl(CodeGenMap map) {
super();
this.map = map;
}
/**
* @see java.util.Collection#size()
*/
public int size() {
return map.size();
}
/**
* @see java.util.Collection#iterator()
*/
public Iterator iterator() {
return new ValuesIteratorImpl(map);
}
// delegate remove to AbstractCollection, which will iterate correctly
/**
* @see java.util.Collection#clear()
*/
public void clear() {
map.clear();
}
/**
* @see java.lang.Object#toString()
*/
public String toString() {
return "ValuesImpl[size=" + size() + "]";
}
}
/**
* Values iterator implementation.
*/
protected static class ValuesIteratorImpl extends IteratorImpl {
/**
* Constructor.
*/
protected ValuesIteratorImpl(CodeGenMap map) {
super(map);
}
/**
* Gets the next entry
*/
protected Object nextEntry() {
return map.lookupEntry(index).getValue();
}
}
// Implementation
// ----------------------------------------------------------------------
/**
* Lookup a key to an entry.
*
* @param key the key of the object to lookup
* @return the entry
*/
protected MapEntryImpl lookupEntry(Object key) {
// if (key != null) {
// MapIterator it = mapIterator();
// while (it.hasNext()) {
// Object loopKey = (Object) it.next();
// if (key == loopKey || key.equals(loopKey)) {
// return (MapEntryImpl) it.getMapEntry();
// }
// }
// }
// return null;
if (key != null) {
int index = lookup.lookupIndex(key);
if (index != -1) {
MapEntryImpl e = mappings[index];
if (key == e.key || key.equals(e.key)) {
return e;
}
}
}
return null;
}
/**
* Gets the entry for a specified index.
*
* @param index the index
* @return the entry
*/
protected MapEntryImpl lookupEntry(int index) {
return mappings[index];
}
/**
* Create the code generated hash object.
*/
protected void rehash() {
if (size == 0) {
lookup = EMPTY_LOOKUP;
} else if (size == 1) {
lookup = new SingletonIndexLookup(mappings[0].key);
} else {
lookup = new
CodeGenMapGenerator(getClass().getClassLoader()).createLookup(this);
}
}
/**
* Increase the size of the backing storage array as required.
*
* @param requiredSize the required size to increase to
*/
protected void ensureCapacity(int requiredSize) {
if (mappings == null) {
mappings = new MapEntryImpl[requiredSize];
} else {
if (requiredSize < 0) {
throw new IllegalStateException("Unable to increase capacity to
required size");
}
if (requiredSize >= MAX_SIZE) {
throw new IllegalStateException("Unable to increase capacity to
required size");
}
if (mappings.length < requiredSize) {
Object[] oldMappings = mappings;
int newMappingsSize = ((mappings.length * 3) / 2) + 1;
if (newMappingsSize >= MAX_SIZE) {
newMappingsSize = MAX_SIZE;
} else if (newMappingsSize < 8) {
newMappingsSize = 8;
}
if (newMappingsSize < requiredSize) {
newMappingsSize = requiredSize;
}
mappings = new MapEntryImpl[newMappingsSize];
System.arraycopy(oldMappings, 0, mappings, 0, size);
}
}
}
// Basic object methods
// ----------------------------------------------------------------------
/**
* Compare the specified object with this list for equality. This
* implementation uses exactly the code that is used to define the
* map equals function in the documentation for the
* <code>Map.equals</code> method.
* <p>
* Note in particular that the order of the entries is unimportant
* for this comparison.
*
* @param other the object to be compared to this map
* @return true if the two maps are equal
*/
public boolean equals(Object other) {
if (other == this) {
return true;
}
if (other instanceof Map == false) {
return false;
}
Map otherMap = (Map) other;
if (otherMap.size() != size()) {
return false;
}
// check keys and values
for (int i = 0; i < size; i++) {
MapEntryImpl entry = (MapEntryImpl) mappings[i];
if (entry.value == null) {
if ((otherMap.get(entry.key) == null &&
otherMap.containsKey(entry.key)) == false) {
return false;
}
} else {
if (entry.value.equals(otherMap.get(entry.key)) == false) {
return false;
}
}
}
return true;
}
/**
* Return the hash code value for this map. This implementation uses
* exactly the code that is used to define the list hash function in the
* documentation for the <code>Map.hashCode</code> method.
*
* @return suitable integer hashcode
*/
public int hashCode() {
int total = 0;
for (int i = 0; i < size; i++) {
MapEntryImpl entry = (MapEntryImpl) mappings[i];
total += ((entry.key == null ? 0 : entry.key.hashCode()) ^
(entry.value == null ? 0 : entry.value.hashCode()));
}
return total;
}
/**
* Return a shallow copy of this map.
* The keys and values themselves are not copied.
*
* @return a clone of this map
*/
public Object clone() {
try {
CodeGenMap cloned = (CodeGenMap) super.clone();
cloned.modCount = 0;
cloned.mappings = new MapEntryImpl[size];
for (int i = 0; i < size; i++) {
cloned.mappings[i] = new MapEntryImpl(cloned, mappings[i].key,
mappings[i].value);
}
cloned.rehash();
return cloned;
} catch (CloneNotSupportedException ex) {
throw new InternalError();
}
}
/**
* Gets a String version of the map, using the same format as
* [EMAIL PROTECTED] java.util.AbstractMap#toString()}.
*
* @return string version of the map
*/
public String toString() {
if (size == 0) {
return "{}";
}
StringBuffer buf = new StringBuffer(32 * size);
buf.append('{');
for (int i = 0; i < size; i++) {
buf.append(mappings[i].key == this ? "(this Map)" : mappings[i].key);
buf.append('=');
buf.append(mappings[i].value == this ? "(this Map)" : mappings[i].value);
buf.append(", ");
}
buf.setLength(buf.length() - 2);
buf.append('}');
return buf.toString();
}
/**
* Writes this object during serialization.
*
* @serialData the modifiable flag, the map size, the map data as an Object array
*/
protected void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
out.writeBoolean(modifiable);
out.writeInt(size);
for (int i = 0, isize = size; i < isize; i++) {
out.writeObject(mappings[i].key);
out.writeObject(mappings[i].value);
}
}
/**
* Reads the object from a stream during deserialization.
*/
protected void readObject(ObjectInputStream in) throws IOException,
ClassNotFoundException {
in.defaultReadObject();
modifiable = in.readBoolean();
size = in.readInt();
mappings = new MapEntryImpl[size];
for (int i = 0, isize = size; i < isize; i++) {
mappings[i] = new MapEntryImpl(this, in.readObject(), in.readObject());
}
rehash();
}
// Map entry
//-----------------------------------------------------------------------
/**
* Map entry implementation.
*/
protected static class MapEntryImpl implements Map.Entry {
private final CodeGenMap map;
Object key;
Object value;
/**
* Constructor.
* @param map the owning map
* @param key the key
* @param value the value
*/
protected MapEntryImpl(CodeGenMap map, Object key, Object value) {
super();
this.map = map;
this.key = key;
this.value = value;
}
/**
* Gets the key part of the map entry.
* @return the key
*/
public Object getKey() {
return key;
}
/**
* Gets the value part of the map entry.
* @return the value
*/
public Object getValue() {
return value;
}
/**
* Sets the value of the map entry
* @param value the value to set the entry to
* @return the old value of the map entry
* @throws UnsupportedOperationException if the map is unmodifiable
*/
public Object setValue(Object value) {
if (map.isModifiable() == false) {
throw new UnsupportedOperationException("The map cannot be modified");
}
Object previous = value;
this.value = value;
return previous;
}
/**
* Compares this map entry to another.
* @param other the other object
* @return true if the entries are equal (key and value)
*/
public boolean equals(Object other) {
if(other == this) {
return true;
}
if (other instanceof Map.Entry == false) {
return false;
}
Map.Entry otherEntry = (Map.Entry) other;
return (
(getKey() == null ? otherEntry.getKey() == null :
getKey().equals(otherEntry.getKey())) &&
(getValue() == null ? otherEntry.getValue() == null :
getValue().equals(otherEntry.getValue()))
);
}
/**
* Returns a suitable hash code.
* @return a hash code
*/
public int hashCode() {
return ((getKey() == null ? 0 : getKey().hashCode()) ^
(getValue() == null ? 0 : getValue().hashCode())
);
}
/**
* Returns a debugging string.
* @return debugging string
*/
public String toString() {
return "MapEntryImpl[key=" + key + ",value=" + value + "]";
}
}
// Superclass for iterators
//-----------------------------------------------------------------------
/**
* Iterator implementation.
*/
protected static abstract class IteratorImpl implements ResetableIterator {
protected final CodeGenMap map;
protected int endIndex;
protected int index;
protected boolean removeAllowed = false;
protected int expectedModCount;
/**
* Constructor.
* @param map the owning map
*/
protected IteratorImpl(CodeGenMap map) {
super();
this.map = map;
this.endIndex = map.size() - 1;
this.index = -1;
this.expectedModCount = map.modCount;
}
/**
* Does the iterator have another entry.
* @return true if there is another entry
*/
public boolean hasNext() {
return (index < endIndex);
}
/**
* @see java.util.Iterator#next()
*/
public final Object next() {
if (hasNext() == false) {
throw new NoSuchElementException("No more elements in iteration");
}
if (map.modCount != expectedModCount) {
throw new ConcurrentModificationException("A change has been made to
the map during iteration");
}
index++;
removeAllowed = true;
return nextEntry();
}
/**
* Gets the next entry.
*
* @return the next iterator entry
*/
protected abstract Object nextEntry();
/**
* Removes the last returned entry.
* @throws UnsupportedOperationException if the map is unmodifiable
*/
public void remove() {
if (map.isModifiable() == false) {
throw new UnsupportedOperationException("The map cannot be modified");
}
if (removeAllowed == false) {
throw new IllegalStateException("next() must be called before each
call to remove()");
}
if (map.modCount != expectedModCount) {
throw new ConcurrentModificationException("A change has been made to
the map during iteration");
}
map.remove(map.lookupEntry(index).key);
removeAllowed = false;
index--;
endIndex--;
expectedModCount = map.modCount;
}
/**
* Reset the iterator back to the start.
*/
public void reset() {
index = -1;
}
/**
* Returns a debugging string.
* @return debugging string
*/
public String toString() {
return "IteratorImpl[index=" + index + "]";
}
}
// Abstract class defining the lookup
//-----------------------------------------------------------------------
/**
* The lookup abstract class.
* <p>
* An abstract class is used as a virtual method call is faster than an
* interface method call at the bytecode level.
*/
public static abstract class IndexLookup {
/**
* Lookup a hashCode to an index.
*
* @param key the key of the object to lookup
* @return the index into the mappings array for the <b>value</b>
*/
public abstract int lookupIndex(Object key);
}
/**
* Index lookup used when the map is empty.
*/
static final class EmptyIndexLookup extends IndexLookup {
public final int lookupIndex(Object key) {
return -1;
}
}
/**
* Index lookup used when the map is size one.
*/
static final class SingletonIndexLookup extends IndexLookup {
private final Object key;
private final int keyHashCode;
SingletonIndexLookup(Object singletonKey) {
super();
key = singletonKey;
keyHashCode = key.hashCode();
}
public final int lookupIndex(Object key) {
if (key.hashCode() == keyHashCode) {
return 0;
}
return -1;
}
}
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]