blob: fad6625908bc6aa0f2a89768b9af2e87c5c831c3 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 1997, 2008 by ProSyst Software GmbH
* http://www.prosyst.com
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* ProSyst Software GmbH - initial API and implementation
*******************************************************************************/
package org.eclipse.equinox.internal.util.io;
import java.io.*;
import java.lang.reflect.Array;
import java.util.*;
/**
* This class implements a hashtable, which serves the needs of
* ConfigurationManagement of capsulation of configuration properties with
* String keys, which are case insentive at lookup (at get, remove, put
* operations) but preserve the last case of keys.
*
* The implementaion of the Externalizable interface allows remote transfer of
* those properties.
*
* @author Pavlin Dobrev
* @version 1.0
*/
public class ExternalizableDictionary extends Dictionary implements Externalizable, Cloneable {
private static Class[] CLASSES = new Class[] {ExternalizableDictionary.class};
private HashtableEntry table[];
private int count;
private int threshold;
private float loadFactor;
private static int MIN_CAPACITY = 5;
ClassLoader loader;
/**
* Constructs a new, empty dictionary with the specified initial capacity
* and the specified load factor.
*
* @param initialCapacity
* the initial capacity of the hashtable. when 0 is passed for
* capacity, minimum possible capacity is used; i.e. when the
* object is read as externalizable, then the table is created
* with the written size.
* @param loadFactor
* a number between 0.0 and 1.0.
* @exception IllegalArgumentException
* if the initial capacity is less than zero, or if the load
* factor is less than or equal to zero.
*/
public ExternalizableDictionary(int initialCapacity, float loadFactor) {
if ((initialCapacity < 0) || (loadFactor <= 0.0)) {
throw new IllegalArgumentException();
}
this.loadFactor = loadFactor;
if (initialCapacity > 0)
initTable(initialCapacity);
}
private void initTable(int initialCapacity) {
table = new HashtableEntry[initialCapacity];
threshold = (int) (initialCapacity * loadFactor);
}
/**
* Constructs a new, empty dictionary with the wanted capacity and default
* load factor.
*/
public ExternalizableDictionary(int initialCapacity) {
this(initialCapacity, 0.75f);
}
/**
* Constructs a new, empty dictionary with a default capacity and load
* factor.
*/
public ExternalizableDictionary() {
this(101, 0.75f);
}
public ClassLoader setClassLaoder(ClassLoader loader) {
ClassLoader tmp = this.loader;
this.loader = loader;
return tmp;
}
public ClassLoader setClassLoader(ClassLoader loader) {
ClassLoader tmp = this.loader;
this.loader = loader;
return tmp;
}
public Class[] remoteInterfaces() {
return CLASSES;
}
/**
* Gets the size of the elements in the dictionary.
*
* @return size of hashtable
*/
public int size() {
return count;
}
/**
* Checks if there is any element in the dictionary.
*
* @return true if empty.
*/
public boolean isEmpty() {
return count == 0;
}
/**
* Gets an enumeration with dictionary's keys.
*
* @return an enumeration with dictionary's keys.
*/
public synchronized Enumeration keys() {
return new HashtableEnumerator(table, true);
}
/**
* Gets an enumeration with dictionary's values.
*
* @return an enumeration with dictionary's values.
*/
public synchronized Enumeration elements() {
return new HashtableEnumerator(table, false);
}
private int hashCode(String key) {
return key.toLowerCase().hashCode();
}
/**
* Gets the value corresponding to this key or any of its case
* representations.
*
* @param key
* String key
* @return object value or null if none
*/
public synchronized Object get(Object key) {
if (table != null) {
HashtableEntry tab[] = table;
int hash = hashCode((String) key);
int index = (hash & 0x7FFFFFFF) % tab.length;
for (HashtableEntry e = tab[index]; e != null; e = e.next) {
if ((e.hash == hash) && e.key.equalsIgnoreCase((String) key)) {
return e.value;
}
}
}
return null;
}
protected void rehash() {
int oldCapacity = table.length;
HashtableEntry oldTable[] = table;
int newCapacity = oldCapacity * 2 + 1;
HashtableEntry newTable[] = new HashtableEntry[newCapacity];
threshold = (int) (newCapacity * loadFactor);
table = newTable;
for (int i = oldCapacity; i-- > 0;) {
for (HashtableEntry old = oldTable[i]; old != null;) {
HashtableEntry e = old;
old = old.next;
int index = (e.hash & 0x7FFFFFFF) % newCapacity;
e.next = newTable[index];
newTable[index] = e;
}
}
}
/**
* Puts the key and the value in the table. If there already is a key equal
* ignore case to the one passed the new value exchhanes the old one.
*
* @param key
* String key
* @param value
* object to put
* @return old value if any, or null if none
* @exception IllegalArgumentException
* if key is not a string
*/
public synchronized Object put(Object key, Object value) throws IllegalArgumentException {
if (value == null) {
throw new NullPointerException();
}
if (table == null)
initTable(MIN_CAPACITY);
try {
// Makes sure the key is not already in the hashtable.
int hash = hashCode((String) key);
int index;
HashtableEntry[] tab = null;
do {
tab = table;
index = (hash & 0x7FFFFFFF) % tab.length;
for (HashtableEntry e = tab[index]; e != null; e = e.next) {
if ((e.hash == hash) && e.key.equalsIgnoreCase((String) key)) {
Object old = e.value;
e.value = value;
return old;
}
}
if (count >= threshold) {
// Rehash the table if the threshold is exceeded
rehash();
continue;
}
break;
} while (true);
// Creates the new entry.
HashtableEntry e = new HashtableEntry();
e.hash = hash;
e.key = (String) key;
e.value = value;
e.next = tab[index];
tab[index] = e;
count++;
return null;
} catch (ClassCastException cce) {
throw new IllegalArgumentException("Non string keys are not accepted!");
}
}
/**
* Removes the key and its corresponding value. Key is searched case
* insensitively.
*
* @param key
* string key
* @return object removed or null if none
* @exception IllegalArgumentException
* if key is not s string
*/
public synchronized Object remove(Object key) throws IllegalArgumentException {
if (table == null)
return null;
try {
HashtableEntry tab[] = table;
int hash = hashCode((String) key);
int index = (hash & 0x7FFFFFFF) % tab.length;
for (HashtableEntry e = tab[index], prev = null; e != null; prev = e, e = e.next) {
if ((e.hash == hash) && e.key.equalsIgnoreCase((String) key)) {
if (prev != null) {
prev.next = e.next;
} else {
tab[index] = e.next;
}
count--;
return e.value;
}
}
return null;
} catch (ClassCastException cce) {
throw new IllegalArgumentException("Non string keys are not accepted!");
}
}
/**
* Clears all elements from the distionary.
*/
public synchronized void clear() {
if (table == null)
return;
for (int index = table.length; --index >= 0;) {
table[index] = null;
}
count = 0;
}
/**
* Creates a shallow copy of this hashtable. The keys and values themselves
* are not cloned.
*
* @return a new CMDictioanry with the same fields
*/
public synchronized Object clone() {
try {
ExternalizableDictionary cmDict = (ExternalizableDictionary) super.clone();
if (table != null) {
cmDict.table = new HashtableEntry[table.length];
for (int i = table.length; i-- > 0;) {
cmDict.table[i] = (table[i] != null) ? (HashtableEntry) table[i].clone() : null;
}
}
return cmDict;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError();
}
}
/**
* Compares the specified Object with this Dictionary for equality,
*
* @return true if the specified Object is equal to this Dictionary.
*/
public synchronized boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof Dictionary))
return false;
Dictionary dict = (Dictionary) o;
int max = size();
if (dict.size() != max)
return false;
Enumeration k = keys();
Enumeration e = elements();
for (int i = 0; i < max; i++) {
Object key = k.nextElement();
Object value = e.nextElement();
if (!value.equals(dict.get(key))) {
return false;
}
}
return true;
}
/**
* Makes a string represntation of this object.
*
* @return string represntation of this object
*/
public synchronized String toString() {
int max = size() - 1;
StringBuffer buf = new StringBuffer();
Enumeration k = keys();
Enumeration e = elements();
buf.append('{');
for (int i = 0; i <= max; i++) {
String s1 = (String) k.nextElement();
String s2 = e.nextElement().toString();
buf.append(s1);
buf.append('=');
buf.append(s2);
if (i < max) {
buf.append(',');
buf.append(' ');
}
}
buf.append('}');
return buf.toString();
}
/**
* Writes this object to the stream passed. The object can be loaded again
* via its readObject method.
*
* @param os
* strream to write data to.
* @exception Exception
* if error of any kind occurs
*/
public synchronized void writeObject(OutputStream os) throws Exception {
Enumeration keys = keys();
Enumeration values = elements();
PDataStream.writeInt(size(), os);
// dictionary is never empty:
// it either has elements or is null
while (keys.hasMoreElements()) {
PDataStream.writeUTF((String) keys.nextElement(), os);
writeValue(values.nextElement(), os);
}
}
private static void writeValue(Object value, OutputStream os) throws IOException {
if (value == null) {
os.write(-1);
} else {
Class valueClass = value.getClass();
// write 0 for a single value, 1 for array, and 2 for Vector
if (valueClass.isArray()) {
os.write(1);
int length = Array.getLength(value);
PDataStream.writeInt(length, os);
Class componentType = valueClass.getComponentType();
if (componentType.isPrimitive()) {
// primitive
PDataStream.writeBoolean(true, os);
// 1: int;2: long; 3: byte; 4: boolean; 5: character; 6:
// short; 7: float; 8: double
writePrimitiveArray(componentType, value, length, os);
} else {
PDataStream.writeBoolean(false, os);
PDataStream.writeUTF(componentType.getName(), os);
Object[] oArr = (Object[]) value;
for (int i = 0; i < length; i++) {
writeValue(oArr[i], os);
}
}
} else if (valueClass.equals(Vector.class)) {
os.write(2);
int size = ((Vector) value).size();
PDataStream.writeInt(size, os);
for (int i = 0; i < size; i++) {
writeValue(((Vector) value).elementAt(i), os);
}
} else {
os.write(0);
writeRealObject(value, valueClass, os);
}
}
}
private static Object readValue(InputStream is, ClassLoader loader) throws Exception {
byte type = (byte) is.read();
if (type == -1) {
return null;
}
Class vClass = null;
if (type == 2) {
int length = PDataStream.readInt(is);
Vector v = new Vector(length);
for (int i = 0; i < length; i++) {
v.insertElementAt(readValue(is, loader), i);
}
return v;
} else if (type == 0) {
return readRealObject((byte) is.read(), is, loader);
} else {
int length = PDataStream.readInt(is);
boolean primitive = PDataStream.readBoolean(is);
if (primitive) {
return readPrimitiveArray(length, is);
}
vClass = loader == null ? Class.forName(PDataStream.readUTF(is)) : loader.loadClass(PDataStream.readUTF(is));
Object array = Array.newInstance(vClass, length);
for (int i = 0; i < length; i++) {
Array.set(array, i, readValue(is, loader));
}
return array;
}
}
/**
* Reads the data from the InputStream and loads the data in the table.
*
* @param is
* stream to read dictionary's data from
* @exception Exception
* if an error of any kind occurs while reading
*/
public synchronized void readObject(InputStream is) throws Exception {
try {
int size = PDataStream.readInt(is);
if (table == null) {
if (size > 0) {
initTable(size);
} else
initTable(MIN_CAPACITY);
}
for (int i = 0; i < size; i++) {
put(PDataStream.readUTF(is), readValue(is, loader));
}
} catch (Exception e) {
throw e;
}
}
// 1: int;2: long; 3: byte; 4: boolean; 5: character;
// 6: short; 7: float; 8: double
private static void writePrimitiveArray(Class componentType, Object array, int length, OutputStream os) throws IOException {
if (componentType.equals(Integer.TYPE)) {
int[] ints = (int[]) array;
os.write(1);
for (int i = 0; i < length; i++) {
PDataStream.writeInt(ints[i], os);
}
} else if (componentType.equals(Long.TYPE)) {
os.write(2);
long[] longs = (long[]) array;
for (int i = 0; i < length; i++) {
PDataStream.writeLong(longs[i], os);
}
} else if (componentType.equals(Byte.TYPE)) {
os.write(3);
os.write((byte[]) array);
} else if (componentType.equals(Boolean.TYPE)) {
os.write(4);
boolean[] booleans = (boolean[]) array;
for (int i = 0; i < length; i++) {
PDataStream.writeBoolean(booleans[i], os);
}
} else if (componentType.equals(Character.TYPE)) {
os.write(5);
char[] chars = (char[]) array;
for (int i = 0; i < length; i++) {
PDataStream.writeChar(chars[i], os);
}
} else if (componentType.equals(Short.TYPE)) {
os.write(6);
short[] shorts = (short[]) array;
for (int i = 0; i < length; i++) {
PDataStream.writeShort(shorts[i], os);
}
} else if (componentType.equals(Float.TYPE)) {
os.write(7);
float[] floats = (float[]) array;
for (int i = 0; i < length; i++) {
PDataStream.writeFloat(floats[i], os);
}
} else if (componentType.equals(Double.TYPE)) {
os.write(8);
double[] doubles = (double[]) array;
for (int i = 0; i < length; i++) {
PDataStream.writeDouble(doubles[i], os);
}
} else {
throw new IllegalArgumentException("Unsupported Primitive Type: " + componentType);
}
}
private static Object readPrimitiveArray(int length, InputStream is) throws IOException {
byte type = (byte) is.read();
if (type == 1) {
int[] ints = new int[length];
for (int i = 0; i < length; i++) {
ints[i] = PDataStream.readInt(is);
}
return ints;
} else if (type == 2) {
long[] longs = new long[length];
for (int i = 0; i < length; i++) {
longs[i] = PDataStream.readLong(is);
}
return longs;
} else if (type == 3) {
byte[] bytes = new byte[length];
is.read(bytes);
return bytes;
} else if (type == 4) {
boolean[] booleans = new boolean[length];
for (int i = 0; i < length; i++) {
booleans[i] = PDataStream.readBoolean(is);
}
return booleans;
} else if (type == 5) {
char[] chars = new char[length];
for (int i = 0; i < length; i++) {
chars[i] = PDataStream.readChar(is);
}
return chars;
} else if (type == 6) {
short[] shorts = new short[length];
for (int i = 0; i < length; i++) {
shorts[i] = PDataStream.readShort(is);
}
return shorts;
} else if (type == 7) {
float[] floats = new float[length];
for (int i = 0; i < length; i++) {
floats[i] = PDataStream.readFloat(is);
}
return floats;
} else if (type == 8) {
double[] doubles = new double[length];
for (int i = 0; i < length; i++) {
doubles[i] = PDataStream.readDouble(is);
}
return doubles;
} else {
throw new IllegalArgumentException("Trying to read unsupported primitive type: " + type);
}
}
// only if this is an object (not primitive) and non null!
private static void writeRealObject(Object value, Class vClass, OutputStream os) throws IOException {
try {
if (vClass.equals(String.class)) {
os.write(0);
PDataStream.writeUTF((String) value, os);
} else if (vClass.equals(Integer.class)) {
os.write(1);
PDataStream.writeInt(((Integer) value).intValue(), os);
} else if (vClass.equals(Long.class)) {
os.write(2);
PDataStream.writeLong(((Long) value).longValue(), os);
} else if (vClass.equals(Byte.class)) {
os.write(3);
os.write(((Byte) value).byteValue());
} else if (vClass.equals(Boolean.class)) {
os.write(4);
PDataStream.writeBoolean(((Boolean) value).booleanValue(), os);
} else if (vClass.equals(Character.class)) {
os.write(5);
PDataStream.writeChar(((Character) value).charValue(), os);
} else if (vClass.equals(Short.class)) {
os.write(6);
PDataStream.writeShort(((Short) value).shortValue(), os);
} else if (vClass.equals(Float.class)) {
os.write(7);
PDataStream.writeFloat(((Float) value).floatValue(), os);
} else if (vClass.equals(Double.class)) {
os.write(8);
PDataStream.writeDouble(((Double) value).doubleValue(), os);
} else if (Externalizable.class.isAssignableFrom(vClass)) {
os.write(11);
String name = vClass.getName();
PDataStream.writeUTF(name, os);
Externalizable tmp = (Externalizable) value;
tmp.writeObject(os);
} else {
os.write(12);
ObjectOutputStream out = new ObjectOutputStream(os);
out.writeObject(value);
}
} catch (Exception exc) {
throw new IOException(exc.toString());
}
}
private static Object readRealObject(byte type, InputStream is, ClassLoader loader) throws IOException {
try {
if (type == 0) {
return PDataStream.readUTF(is);
} else if (type == 1) {
return new Integer(PDataStream.readInt(is));
} else if (type == 2) {
return new Long(PDataStream.readLong(is));
} else if (type == 3) {
return new Byte((byte) is.read());
} else if (type == 4) {
return PDataStream.readBoolean(is) ? Boolean.TRUE : Boolean.FALSE;
} else if (type == 5) {
return new Character(PDataStream.readChar(is));
} else if (type == 6) {
return new Short(PDataStream.readShort(is));
} else if (type == 7) {
return new Float(PDataStream.readFloat(is));
} else if (type == 8) {
return new Double(PDataStream.readDouble(is));
} else if (type == 11) {
String name = PDataStream.readUTF(is);
Class c = loader == null ? Class.forName(name) : loader.loadClass(name);
if (Externalizable.class.isAssignableFrom(c)) {
Externalizable obj = (Externalizable) c.newInstance();
obj.readObject(is);
return obj;
}
throw new IOException("Could not read object " + name);
} else if (type == 12) {
ObjectInputStream in = loader == null ? new ObjectInputStream(is) : (ObjectInputStream) new XObjectInputStream(loader, is);
return in.readObject();
}
} catch (ClassNotFoundException cnfe) {
throw new IOException("Could not find class " + cnfe.toString());
} catch (Exception exc) {
throw exc instanceof IOException ? (IOException) exc : new IOException("Could not read object " + exc.toString());
}
throw new IllegalArgumentException("Unsupported Typed Object: " + type);
}
/**
*
*
* @param props
* @exception IllegalArgumentException
*/
public synchronized void copyFrom(Dictionary props) throws IllegalArgumentException {
Enumeration keys = props.keys();
Enumeration values = props.elements();
while (keys.hasMoreElements()) {
put(keys.nextElement(), values.nextElement());
}
}
}
class HashtableEntry {
int hash;
String key;
Object value;
HashtableEntry next;
protected Object clone() {
HashtableEntry entry = new HashtableEntry();
entry.hash = hash;
entry.key = key;
entry.value = value;
entry.next = (next != null) ? (HashtableEntry) next.clone() : null;
return entry;
}
}
class HashtableEnumerator implements Enumeration {
boolean keys;
int index;
HashtableEntry table[];
HashtableEntry entry;
HashtableEnumerator(HashtableEntry table[], boolean keys) {
this.table = table;
this.keys = keys;
this.index = table.length;
}
public boolean hasMoreElements() {
if (table == null)
return false;
if (entry != null) {
return true;
}
while (index-- > 0) {
if ((entry = table[index]) != null) {
return true;
}
}
return false;
}
public Object nextElement() {
if (table != null) {
if (entry == null) {
while ((index-- > 0) && ((entry = table[index]) == null));
}
if (entry != null) {
HashtableEntry e = entry;
entry = e.next;
return keys ? e.key : e.value;
}
}
throw new NoSuchElementException("HashtableEnumerator");
}
}
class XObjectInputStream extends ObjectInputStream {
ClassLoader loader;
public XObjectInputStream(ClassLoader loader, InputStream is) throws IOException {
super(is);
this.loader = loader;
}
protected Class resolveClass(ObjectStreamClass v) throws ClassNotFoundException {
return loader.loadClass(v.getName());
}
}