/************************************************************************** * Parts copyright (c) 2001 by Punch Telematix. All rights reserved. * * Parts copyright (c) 2009 by Chris Gray, /k/ Embedded Java Solutions. * * 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. Neither the name of Punch Telematix or of /k/ Embedded Java Solutions* * nor the names of other contributors may be used to endorse or promote* * products derived from this software without specific prior written * * permission. * * * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS 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 PUNCH TELEMATIX, /K/ EMBEDDED JAVA SOLUTIONS OR OTHER * * 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. * **************************************************************************/ package java.io; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Field; import wonka.vm.ArrayUtil; import wonka.decoders.UTF8Decoder; /** ** TODO ** - check out the story of serialPersitentFields !!! --> work to be done in ObjectStreamClass.c ** - replacing objects: get rid of getDeclaredMethod ! ** - handle Exceptions. test handling. ** - putFields & writeFields. ** - useProtocolVersion. ** - remove unneeded drain() or inline them ** - optimize like hell ** - test native array code and optimize were needed ! ** - handle proxy classes when available ! ** - optimize defaultWriteObject --> 1 native method ? */ public class ObjectOutputStream extends OutputStream implements ObjectOutput, ObjectStreamConstants { private static final int BUFFERSIZE = 1024; private static final Class clazzObjectStreamClass = new ObjectStreamClass("").getClass(); private static final Class clazzClass = clazzObjectStreamClass.getClass(); private static final Class clazzString = "".getClass(); private static final Class[] EMPTY = new Class[0]; static final Object[] NO_ARGS = new Object[0]; private Object[] args; //used when invoking writeObject. private OutputStream out; private final byte[] buffer; private int pointer; private byte[] databuffer; private int datapointer; private int handleCounter; /** internal hashtable for handle cache */ private Object[] objectCache; private int [] handles; private int hthreshold; private int hcapacity; private int hoccupancy; /** end of internal hashtable */ /** internal hashtable for replacement cache */ private Object[] replacementCache; private Object[] replacements; private int rthreshold; private int rcapacity; private int roccupancy; /** end of internal hashtable */ //internal state of the ObjectOutputStream private Object active; /** object currently being serialized */ private ObjectStreamClass currentObjectStreamClass; /** ObjectStreamClass of the object currently being serialized */ private boolean enableReplaceObject; private boolean override; protected ObjectOutputStream() throws IOException, SecurityException { if (wonka.vm.SecurityConfiguration.ENABLE_SECURITY_CHECKS) { SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION); } } args = new Object[1]; args[0] = this; override = true; buffer = null; } public ObjectOutputStream(OutputStream out) throws IOException { this.out = out; /* this.out = new TeeOutputStream(out); //*/ buffer = new byte[BUFFERSIZE]; databuffer = new byte[BUFFERSIZE]; args = new Object[1]; args[0] = this; handleCounter = baseWireHandle; objectCache = new Object[101]; handles = new int[101]; hthreshold = 75;; hcapacity = 101; replacementCache = new Object[101]; replacements = new Object[101]; rthreshold = 75;; rcapacity = 101; writeStreamHeader(); } protected void annotateClass(Class clazz) throws IOException { //default implementation does nothing } protected void annotateProxyClass(Class clazz) throws IOException { //default implementation does nothing } public void close() throws IOException { //TODO try to find out if flush() or drain is needed. for now drain which is faster. drain(); out.close(); } public void defaultWriteObject() throws IOException { Object current = active; ObjectStreamClass osc = currentObjectStreamClass; if(current == null){ throw new NotActiveException("Not serializing an object"); } ObjectStreamField[] osfields = osc.getFields(); int len = osfields.length; byte[] buf = buffer; try { for(int i = 0 ; i < len ; i++){ ObjectStreamField osfield = osfields[i]; Field f = osfield.field; //System.out.println("Writing field "+f+" ("+osfield+") to stream"); if(f == null){ InvalidClassException ice = new InvalidClassException("no field "+osfield.name+" of "+osfield.type+" in "+osc.name); ice.classname = osc.name; handleException(ice); } if(osfield.isPrimitive){ if(datapointer > 0 || pointer + 8 > BUFFERSIZE){ drain(); } int p = pointer; switch(osfield.code){ case 'I': ArrayUtil.iInBArray(f.getInt(current), buf, p); p += 4; break; case 'J': ArrayUtil.lInBArray(f.getLong(current), buf, p); p += 8; break; case 'F': ArrayUtil.fInBArray(f.getFloat(current), buf, p); p += 4; break; case 'D': ArrayUtil.dInBArray(f.getDouble(current), buf , p); p += 8; break; case 'B': buf[p++] = f.getByte(current); break; case 'Z': buf[p++] = (byte)(f.getBoolean(current) ? 1 : 0); break; case 'C': int ch = f.getChar(current); buf[p++] = (byte)(ch >> 8); buf[p++] = (byte) ch; break; case 'S': int s = f.getShort(current); buf[p++] = (byte)(s >> 8); buf[p++] = (byte) s; break; default: throw new StreamCorruptedException("trying to write primitive field with code '"+(osfield.code)+"'"); } pointer = p; } else { writeObject(f.get(current)); } } } catch(IllegalAccessException iae){ handleException(iae); } } public void writeUnshared(Object obj) throws IOException { //TODO: do unshared thingy. writeObject(obj); } protected void drain() throws IOException { int p = pointer; byte[] buf = buffer; if(datapointer > 0){ int dp = datapointer; if(p > BUFFERSIZE - 5){ //lets hope don't get into this branch to much out.write(buf,0,p); p = 0; } if(dp < 256){ buf[p++] = TC_BLOCKDATA; buf[p++] = (byte)dp; } else{ buf[p++] = TC_BLOCKDATALONG; ArrayUtil.iInBArray(dp, buf, p); p += 4; } out.write(buf,0,p); pointer = 0; out.write(databuffer,0,dp); datapointer = 0; } else { out.write(buf,0,p); pointer = 0; } } protected boolean enableReplaceObject(boolean enable) throws SecurityException { if (wonka.vm.SecurityConfiguration.ENABLE_SECURITY_CHECKS) { SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(SUBSTITUTION_PERMISSION); } } boolean b = enableReplaceObject; enableReplaceObject = enable; return b; } public void flush() throws IOException { drain(); out.flush(); } public PutField putFields() throws IOException { //Big TODO ... return null; } protected Object replaceObject(Object obj) throws IOException { //the default behaviour is not to replace object hence we return obj. return obj; } public void reset() throws IOException { //check if we are currently serializing an object --> throw IOException if(active != null){ throw new IOException("cannot reset while serializing an object"); } if(datapointer > 0 || pointer >= BUFFERSIZE){ drain(); } buffer[pointer++] = TC_RESET; //clean object cache!!! objectCache = new Object[101]; handles = new int[101]; hthreshold = 75;; hcapacity = 101; hoccupancy = 0; replacementCache = new Object[101]; replacements = new Object[101]; rthreshold = 75;; rcapacity = 101; roccupancy = 0; handleCounter = baseWireHandle; } public void useProtocolVersion(int version) throws IOException { if(version == PROTOCOL_VERSION_1){ //System.out.println("\n\nPROTOCOL_VERSION_1 is not supported using PROTOCOL_VERSION_2\n\n"); } else if(version != PROTOCOL_VERSION_1){ throw new IllegalArgumentException("protocol version "+version+" not supported"); } //TODO did we start already ??? --> throw IllegalStateException //TODO ??? } public void write(byte[] buf) throws IOException { write(buf, 0, buf.length); } public void write(byte[] buf, int off, int len) throws IOException { if(datapointer + len > databuffer.length){ grow(len); } System.arraycopy(buf,off,databuffer,datapointer,len); datapointer += len; } public void write(int data) throws IOException { if(datapointer >= databuffer.length){ grow(BUFFERSIZE); } databuffer[datapointer++] = (byte)data; } public void writeBoolean(boolean data) throws IOException { if(datapointer >= databuffer.length){ grow(BUFFERSIZE); } databuffer[datapointer++] = (byte)(data ? 1 : 0); } public void writeByte(int data) throws IOException { if(datapointer >= databuffer.length){ grow(BUFFERSIZE); } databuffer[datapointer++] = (byte)data; } public void writeBytes(String string) throws IOException { byte[] buf = string.getBytes("latin1"); int len = buf.length; if(datapointer > databuffer.length - len){ grow(len); } System.arraycopy(buf,0,databuffer,datapointer,len); datapointer += len; } public void writeChar(int data) throws IOException { if(datapointer + 2 > databuffer.length){ grow(BUFFERSIZE); } databuffer[datapointer++] = (byte)(data>>8); databuffer[datapointer++] = (byte)data; } public void writeChars(String string) throws IOException { byte[] buf = string.getBytes("UTF-16BE"); int len = buf.length; if(datapointer > databuffer.length - len){ grow(len); } System.arraycopy(buf,0,databuffer,datapointer,len); datapointer += len; } protected void writeClassDescriptor(ObjectStreamClass osc) throws IOException { // we need space for 2 + len + 8 + 1 + 2 +??? + 1 byte[] bytes = UTF8Decoder.stringToB(osc.name); int len = bytes.length; byte[] buf = buffer; int p; //lets hope we have enough space in our buffer. if(pointer + len >= BUFFERSIZE - 13){ drain(); //TODO remove or replace ??? if(len > BUFFERSIZE - 13){ buf[0] = (byte)(len>>8); buf[1] = (byte) len; out.write(buf,0,2); out.write(bytes,0,len); p = 0; } else { p = pointer; buf[p++] = (byte)(len>>8); buf[p++] = (byte) len; System.arraycopy(bytes, 0, buf, p, len); p += len; } } else { p = pointer; buf[p++] = (byte)(len>>8); buf[p++] = (byte) len; System.arraycopy(bytes, 0, buf, p, len); p += len; } ArrayUtil.lInBArray(osc.suid, buf , p); p += 8; buf[p++] = (byte) osc.flags; ObjectStreamField[] fields = osc.getFields(); len = fields.length; buf[p++] = (byte)(len>>8); buf[p++] = (byte) len; for(int i = 0 ; i < len ; i++){ ObjectStreamField osf = fields[i]; bytes = UTF8Decoder.stringToB(osf.name); int l = bytes.length; if(p + l > BUFFERSIZE - 3){ out.write(buf,0,p); p = 0; if(l > BUFFERSIZE - 3){ buf[0] = (byte) osf.code; buf[1] = (byte)(l>>8); buf[2] = (byte) l; out.write(buf,0,3); out.write(bytes,0,l); } else { buf[p++] = (byte) osf.code; buf[p++] = (byte)(l>>8); buf[p++] = (byte) l; System.arraycopy(bytes, 0, buf, p, l); p += l; } } else { buf[p++] = (byte) osf.code; buf[p++] = (byte)(l>>8); buf[p++] = (byte) l; System.arraycopy(bytes, 0, buf, p, l); p += l; } if(!osf.isPrimitive){ pointer = p; writeObject(osf.typeString); p = pointer; } } pointer = p; annotateClass(osc.clazz); p = pointer; if(p >= BUFFERSIZE){ out.write(buf,0,p); p = 0; } buf[p++] = TC_ENDBLOCKDATA; ObjectStreamClass tmp = osc.parent; if(tmp == null){ if(p >= BUFFERSIZE){ out.write(buf,0,p); p = 0; } buf[p++] = TC_NULL; pointer = p; } else { pointer = p; writeObject(tmp); } } public void writeDouble(double d) throws IOException { int dp = datapointer; if(dp + 8 > databuffer.length){ grow(BUFFERSIZE); } ArrayUtil.dInBArray(d, databuffer, dp); datapointer = dp + 8; } public void writeFields() throws IOException { //big TODO ... } public void writeFloat(float f) throws IOException { int dp = datapointer; if(dp + 4 > databuffer.length){ grow(BUFFERSIZE); } ArrayUtil.fInBArray(f, databuffer, dp); datapointer = dp + 4; } public void writeInt(int data) throws IOException { int dp = datapointer; if(dp + 4 > databuffer.length){ grow(BUFFERSIZE); } ArrayUtil.iInBArray(data, databuffer, dp); datapointer = dp + 4; } public void writeLong(long data) throws IOException { int dp = datapointer; if(dp + 8 > databuffer.length){ grow(BUFFERSIZE); } ArrayUtil.lInBArray(data, databuffer, dp); datapointer = dp + 8; } public final void writeObject(Object obj) throws IOException { //step 1. if(override){ writeObjectOverride(obj); return; } //step 2. clear data buffer. if(datapointer > 0 || pointer > BUFFERSIZE -9){ drain(); } ObjectStreamClass osc; Class objClass; byte[] buf = this.buffer; do { //step 3. if obj == null; write null reference. if(obj == null){ buf[pointer++] = TC_NULL; if(active == null){ drain(); } return; } //step 4 If the object has been previously replaced, as described in Step 8, //write the handle of the replacement to the stream and writeObject returns. int cap = rcapacity; int ihash = System.identityHashCode(obj); int hash = ihash % cap; Object k[] = replacementCache; do { if(hash < 0) hash += cap; Object k2 = k[hash]; if(k2 == null) { break; } else if(obj == k2) { obj = replacements[hash]; if(obj == null){ buf[pointer++] = TC_NULL; if(active == null){ drain(); } return; } ihash = System.identityHashCode(obj); break; } hash--; } while(true); //5.If the object has already been written to the stream, its handle is written to the stream and writeObject returns. int handle = getHandle(obj,ihash); if(handle != -1){ int p = pointer; buf[p++] = TC_REFERENCE; ArrayUtil.iInBArray(handle, buf, p); pointer = p + 4; if(active == null){ drain(); } return; } //6.If the object is a Class, the corresponding ObjectStreamClass is written to the stream, a handle is assigned for the class, //and writeObject returns. objClass = obj.getClass(); if(objClass == clazzClass){ buf[pointer++] = TC_CLASS; Class clazz = (Class)obj; osc = ObjectStreamClass.lookup(clazz); if(osc == null){ put(obj,handleCounter++); byte[] bytes = UTF8Decoder.stringToB(clazz.getName()); int l = bytes.length; int p = pointer; buf[p++] = TC_CLASSDESC; buf[p++] = (byte)(l>>8); buf[p++] = (byte) l; if(l + p > BUFFERSIZE - 13){ out.write(buf,0,p); p = 0; if(l > BUFFERSIZE - 13){ out.write(bytes,0,l); } else { System.arraycopy(bytes,0,buf,p,l); p += l; } } else { System.arraycopy(bytes,0,buf,p,l); p += l; } //suid is 0 (8)and flag are 0 (1) and no fields (2) ArrayUtil.lInBArray(0L, buf, p); p += 8; buf[p++] = TC_ENDBLOCKDATA; buf[p++] = TC_NULL; pointer = p; } else { writeObject(osc); } put(obj, handleCounter++); if(active == null){ drain(); } return; } //7.If the object is an ObjectStreamClass, a descriptor for the class is written to the stream including its name, //serialVersionUID, and the list of fields by name and type. A handle is assigned for the descriptor. //The annotateClass subclass method is called before writeObject returns. if(clazzObjectStreamClass.isInstance(obj)){ put(obj, handleCounter++); osc = (ObjectStreamClass)obj; buf[pointer++] = TC_CLASSDESC; writeClassDescriptor(osc); if(active == null){ drain(); } return; } //8.Process potential substitutions by the class of the object and/or by a subclass of ObjectInputStream. /* a.If the class of an object defines the appropriate writeReplace method, the method is called. Optionally, it can return a substitute object to be serialized. */ boolean same = true; Object replace = obj; try { //TODO do we really need to do this by calling getDeclaredMethod ??? Method writeReplace = objClass.getDeclaredMethod("writeReplace",EMPTY); replace = writeReplace.invoke(obj,NO_ARGS); same = (replace == obj); } catch(NoSuchMethodException nsme){/** ignore */} catch(InvocationTargetException ite){ handleException(ite.getTargetException()); } catch(IllegalAccessException iae){ /** should never occur*/ handleException(iae); } if(enableReplaceObject){ replace = replaceObject(replace); same = (replace == obj); } if(same){ break; } putReplacement(obj,replace); obj = replace; } while(true); if(objClass == clazzString){ //we have at least 9 bytes available. String s = (String)obj; byte[] bytes = UTF8Decoder.stringToB(s); int len = bytes.length; int p = pointer; if(len < 65536) { buf[p++] = TC_STRING; buf[p++] = (byte)(len>>8); buf[p++] = (byte)len; } else { buf[p++] = TC_LONGSTRING; ArrayUtil.lInBArray(len & 0x0ffffffffL, buf, p); p += 8; } if(len > BUFFERSIZE - p){ out.write(buf,0,p); out.write(bytes,0,len); pointer = 0; } else { System.arraycopy(bytes,0,buf,p,len); pointer = p + len; } put(obj, handleCounter++); if(active == null){ drain(); } return; } osc = ObjectStreamClass.lookup(objClass); if(osc == null){ throw new NotSerializableException(obj+" of "+objClass+" is not Serializable"); } Object current = active; active = obj; if((osc.flags & ObjectStreamClass.IS_ARRAY) > 0){ buf[pointer++] = TC_ARRAY; writeObject(osc); put(obj, handleCounter++); Class ctype = objClass.getComponentType(); if(ctype.isPrimitive()){ int p = pointer; byte[] bytes = primitiveArrayToBytes(obj); ArrayUtil.iInBArray(pointer, buf, p); p += 4; int l = bytes.length; if(p + l > BUFFERSIZE){ out.write(buf,0,p); out.write(bytes,0,l); pointer = 0; } else { //System.out.println("copying array "+bytes.length+", 0, "+buf.length+", "+p+", "+l); System.arraycopy(bytes, 0, buf, p, l); pointer = p + l; } } else { Object[] objectArray = (Object[])obj; int p = pointer; int arrayLength = objectArray.length; ArrayUtil.iInBArray(arrayLength, buf, p); pointer = p + 4; for (int i = 0; i < arrayLength; i++) { writeObject(objectArray[i]); } } if(current == null){ drain(); } active = current; return; } buf[pointer++] = TC_OBJECT; writeObject(osc); put(obj, handleCounter++); ObjectStreamClass curOsc = currentObjectStreamClass; if((osc.flags & SC_EXTERNALIZABLE) > 0){ currentObjectStreamClass = osc; ((Externalizable)obj).writeExternal(this); //TODO ... verify if this check makes sense, it might be better to drain immediatly if(datapointer > 0 || pointer >= BUFFERSIZE){ drain(); } buf[pointer++] = TC_ENDBLOCKDATA; } else { //object is only serializable int size = 128; ObjectStreamClass[] array = new ObjectStreamClass[size]; int i=0; do { if(i == size){ size *= 2; ObjectStreamClass[] newArray = new ObjectStreamClass[size]; System.arraycopy(array,0,newArray,0,i); array = newArray; } array[i++] = osc; osc = osc.parent; } while(osc != null); do { osc = array[--i]; currentObjectStreamClass = osc; Method writeObject = osc.writeObject; if(writeObject == null){ defaultWriteObject(); } else{ try { writeObject.invoke(obj,args); //does this check makes sense or do we drain right away ... if(datapointer > 0 || pointer >= BUFFERSIZE){ drain(); } buf[pointer++] = TC_ENDBLOCKDATA; } catch(InvocationTargetException ite){ handleException(ite.getTargetException()); } catch(IllegalAccessException iae){ handleException(iae); } } } while(i > 0); } active = current; if(current == null){ drain(); } currentObjectStreamClass = curOsc; } protected void writeObjectOverride(Object obj) throws IOException { /** this one is easy. The default implementation does nothing. */ } public void writeShort(int data) throws IOException { if(datapointer + 2 > databuffer.length){ grow(BUFFERSIZE); } databuffer[datapointer++] = (byte)(data>>8); databuffer[datapointer++] = (byte)data; } protected void writeStreamHeader() throws IOException { byte[] buf = buffer; buf[0] = (byte)(STREAM_MAGIC>>8); buf[1] = (byte)STREAM_MAGIC; buf[2] = (byte)(STREAM_VERSION>>8); buf[3] = (byte)STREAM_VERSION; out.write(buf, 0, 4); } public void writeUTF(String s) throws IOException { byte[] bytes = UTF8Decoder.stringToB(s); int len = bytes.length; if(len > 65535){ throw new UTFDataFormatException("to much bytes in string"); } if(datapointer + 2 +len > databuffer.length){ grow(len+2); } databuffer[datapointer++] = (byte)(len>>8); databuffer[datapointer++] = (byte)len; System.arraycopy(bytes,0,databuffer,datapointer,len); datapointer += len; } /** ** this method make the internal dataBuffer larger. Should not be called very often though, ** so no need to inline it. */ private void grow(int size) { byte[] buf = databuffer; if(size < BUFFERSIZE){ size = BUFFERSIZE; } databuffer = new byte[size+buf.length]; System.arraycopy(buf,0,databuffer,0,datapointer); } /** ** when this method is called something is going wrong. Again we don't need to inline ** performance in not an issue here. */ private void handleException(Throwable e) throws IOException { //TODO notify peer ... see docs if(e instanceof IOException){ if(datapointer > 0 || pointer >= BUFFERSIZE){ drain(); } buffer[pointer++] = TC_EXCEPTION; objectCache = new Object[101]; handles = new int[101]; hthreshold = 75;; hcapacity = 101; hoccupancy = 0; replacementCache = new Object[101]; replacements = new Object[101]; rthreshold = 75;; rcapacity = 101; roccupancy = 0; handleCounter = baseWireHandle; writeObject(e); drain(); throw((IOException)e); } throw new StreamCorruptedException("stream got corrupted by "+e); } /** ** Take care: This method will change pointer. (pointer will hold the length of the array). ** */ private native byte[] primitiveArrayToBytes(Object pArray); /** ** internal hashtable methods */ private int getHandle(Object key, int hash) { int cap = hcapacity; Object k[] = objectCache; hash = hash % cap; do { if(hash < 0) hash += cap; Object k2 = k[hash]; if(k2 == null) { return -1; } else if(key == k2) { return this.handles[hash]; } hash--; } while(true); } private void put(Object key, int handle){ int cap = hcapacity; int hash = System.identityHashCode(key) % cap; Object k[] = objectCache; do { if(hash < 0) hash += cap; Object k2 = k[hash]; if(k2 == null) { k[hash] = key; this.handles[hash] = handle; if (++hoccupancy >= hthreshold) { hRehash(); } return; } hash--; } while(true); } private void hRehash() { int oldsize = this.hcapacity; int newsize = oldsize * 2 + 1; Object[] oldkeys = this.objectCache; int[] oldvalues = this.handles; objectCache = new Object[newsize]; handles = new int[newsize]; this.hcapacity = newsize; this.hthreshold = (int)(newsize * 0.75f); this.hoccupancy = 0; for (int oldindex = 0; oldindex < oldsize; ++oldindex) { Object key = oldkeys[oldindex]; if (key != null) put(key, oldvalues[oldindex]); } } private void putReplacement(Object key, Object handle){ int cap = rcapacity; int hash = System.identityHashCode(key) % cap; Object k[] = replacementCache; do { if(hash < 0) hash += cap; Object k2 = k[hash]; if(k2 == null) { k[hash] = key; this.replacements[hash] = handle; roccupancy++; if (roccupancy >= rthreshold) { rRehash(); } return; } hash--; } while(true); } private void rRehash() { int oldsize = this.rcapacity; int newsize = oldsize * 2 + 1; Object[] oldkeys = this.replacementCache; Object[] oldvalues = this.replacements; replacementCache = new Object[newsize]; replacements = new Object[newsize]; this.rcapacity = newsize; this.rthreshold = (int)(newsize * 0.75f); this.roccupancy = 0; for (int oldindex = 0; oldindex < oldsize; ++oldindex) { Object key = oldkeys[oldindex]; if (key != null) putReplacement(key, oldvalues[oldindex]); } } /** end of internal hashtable methods */ public abstract static class PutField { public PutField(){} public abstract void put(String name, boolean value); public abstract void put(String name, byte value); public abstract void put(String name, char value); public abstract void put(String name, double value); public abstract void put(String name, float value); public abstract void put(String name, int value); public abstract void put(String name, long value); public abstract void put(String name, Object value); public abstract void put(String name, short value); public abstract void write(ObjectOutput out) throws IOException; } /* just to be able to write Serialized data to a file !!!. private static class TeeOutputStream extends OutputStream { private static int counter; private OutputStream one; private OutputStream two; TeeOutputStream(OutputStream out) throws IOException{ one = out; two = new FileOutputStream("SER"+(counter++)); } public void write(int ch) throws IOException { one.write(ch); two.write(ch); } public void write(byte[] b,int o, int l) throws IOException { one.write(b,o,l); two.write(b,o,l); } public void write(byte[] b) throws IOException{ one.write(b,0,b.length); two.write(b,0,b.length); } public void flush() throws IOException { one.flush(); two.flush(); } public void close() throws IOException { one.close(); two.close(); } } //*/ }