/************************************************************************** * 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.Array; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Vector; import wonka.decoders.UTF8Decoder; import wonka.vm.ArrayUtil; /** ** TODO: ** - optimize, optimize and then more optimizations ... (defaultReadObject !!!). ** - sort the ObjectStreamClass mess. ** - handle Exceptions ** - readFields ** - close !!! ** - prioryties in objectValidation. ** - readClassDescripter (order of steps). ** - readLine */ public class ObjectInputStream extends InputStream implements ObjectInput, ObjectStreamConstants { private static final int BUFFERSIZE = 1024; //private int dbCount; /** internal buffer to hold blockData */ private int dataPointer; private byte[] buffer; private int available; private InputStream in; private Vector objectCache; private Vector objectValidaters; private boolean enableReplaceObject; private boolean override; private final Object[] args; //used when invoking readObject. private Object current; private ObjectStreamClass currentOSC; private ClassLoader currentLoader; //if not null we are active ! protected ObjectInputStream() throws IOException { 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; } public ObjectInputStream(InputStream in) throws IOException, StreamCorruptedException { this.in = in;/* this.in = new TeeInputStream(in);//*/ buffer = new byte[BUFFERSIZE]; objectCache = new Vector(); args = new Object[1]; args[0] = this; readStreamHeader(); } public int available() throws IOException { if(dataPointer >= available){ return 0; } return available - dataPointer; } public void close() throws IOException { //TODO ... in.close(); } public void defaultReadObject() throws IOException, NotActiveException, ClassNotFoundException { ObjectStreamClass osc = currentOSC; Object obj = current; ObjectStreamField[] fields = osc.osFields; // these should be set when this osc got deserialized. int length = fields.length; byte[] buf = buffer; for(int i = 0 ; i < length ; i++){ ObjectStreamField field = fields[i]; Object value; switch(field.code){ case 'L': case '[': value = readObject(); break; case 'I': available = 0; getBytes(buf, 4); value = new Integer(ArrayUtil.bArrayToI(buf,0)); break; case 'Z': available = 0; int iv = in.read(); //dbCount++; if(iv == -1){ throw new EOFException(); } value = new Boolean(iv != 0); break; case 'F': available = 0; getBytes(buf, 4); value = new Float(ArrayUtil.bArrayToF(buf,0)); break; case 'B': available = 0; iv = in.read(); //dbCount++; if(iv == -1){ throw new EOFException(); } value = new Byte((byte)iv); break; case 'J': available = 0; getBytes(buf, 8); value = new Long(ArrayUtil.bArrayToL(buf,0)); break; case 'C': available = 0; getBytes(buf,2); iv = buf[0]<<8 | (buf[1] & 0xff); value = new Character((char)iv); break; case 'S': available = 0; getBytes(buf,2); iv = buf[0]<<8 | (buf[1] & 0xff); value = new Short((short)iv); break; case 'D': available = 0; getBytes(buf,8); value = new Double(ArrayUtil.bArrayToD(buf,0)); break; default: throw new IOException("unexpected field code: '"+field.code+"'"); } Field f = field.field; if(f != null){ try { f.set(obj,value); } catch(Exception e){ handleException(e); } } //else discard data ... } } public Object readUnshared() throws ClassNotFoundException, IOException { //TODO: to unshared thingy ... return this.readObject(); } protected boolean enableResolveObject(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 int read() throws IOException { if(available <= dataPointer){ try { fill(); } catch(EOFException eof){ return -1; } } int dp = dataPointer++; if(available <= dp){ return -1; } return (buffer[dp] & 0xff); } public int read(byte[] b, int off, int size) throws IOException { if(available <= dataPointer){ try { fill(); } catch(EOFException eof){ return -1; } } int dp = dataPointer; int total = available - dp; if(size > total){ size = total; } System.arraycopy(buffer, dp, b, off, size); dataPointer += size; return size; } public boolean readBoolean() throws IOException { if(available <= dataPointer){ fill(); } int dp = dataPointer++; if(dp >= available){ throw new EOFException(); } return (buffer[dp] == 0 ? false : true); } public byte readByte() throws IOException { if(available <= dataPointer){ fill(); } int dp = dataPointer++; if(dp >= available){ throw new EOFException(); } return buffer[dp]; } public char readChar() throws IOException { if(available <= dataPointer){ fill(); } int dp = dataPointer; byte[] buf = buffer; int total = available - dp; dataPointer += 2; if(total < 2){ throw new EOFException(); } int ch = buf[dp++]<<8; return (char)(ch | (buf[dp] & 0xff)); } protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException { //System.out.println("READING CLASSDESCRIPTOR"); byte[] buf = buffer; getBytes(buf,2); int len = ((buf[0] & 0xff)<<8) | (buf[1] & 0xff); if(len + 11 > buf.length){ buf = new byte[len+11]; buffer = buf; } getBytes(buf, len + 11); String name = UTF8Decoder.bToString(buf,0,len); long suid = ArrayUtil.bArrayToL(buf,len); len+=8; //System.out.println("read SUID "+suid+" from Stream for "+name); ObjectStreamClass osc = new ObjectStreamClass(name,suid,buf[len++]); objectCache.addElement(osc); int mask = 0xff; int nrFields = (buf[len++] & mask)<<8; nrFields |= (buf[len] & mask); ObjectStreamField[] fields = new ObjectStreamField[nrFields]; for (int i = 0 ; i < nrFields ; i++){ getBytes(buf, 3); int b = buf[0]; len = ((buf[1] & mask)<<8) |(buf[2] & mask); if(len > buf.length){ buf = new byte[len]; buffer = buf; } getBytes(buf, len); String fname = UTF8Decoder.bToString(buf, 0, len); //System.out.println("constructing field named "+fname); String desc; switch(b){ case '[': case 'L': desc = desc2Name((String)readObject()); break; case 'I': desc = "int"; break; case 'J': desc = "long"; break; case 'Z': desc = "boolean"; break; case 'F': desc = "float"; break; case 'B': desc = "byte"; break; case 'C': desc = "char"; break; case 'S': desc = "short"; break; case 'D': desc = "double"; break; default: throw new StreamCorruptedException("bad field type "+Integer.toHexString(b)); } //System.out.println("FIELD ["+i+"] = "+fname+" of type "+desc); fields[i] = new ObjectStreamField(fname,desc,(char)b); } osc.osFields = fields; osc.clazz = resolveClass(osc); int rd = in.read(); //dbCount++; if(rd != TC_ENDBLOCKDATA){ throw new StreamCorruptedException("TC_ENDBLOCKDATA not found got "+Integer.toHexString(rd)); } /** TODO determine the correct order of these steps ...*/ osc.verifyInput(); osc.parent = (ObjectStreamClass)readObject(); /** end TODO */ return osc; } public double readDouble() throws IOException { return Double.longBitsToDouble(readLong()); } public GetField readFields() throws NotActiveException, IOException, ClassNotFoundException { if(this.currentLoader == null) { throw new NotActiveException(); } // big TODO ... return null; } public float readFloat() throws IOException { return Float.intBitsToFloat(readInt()); } public void readFully(byte[] data) throws IOException { readFully(data, 0, data.length); } public void readFully(byte[] data, int offset, int size) throws IOException { if(available <= dataPointer){ fill(); } int dp = dataPointer; if(size > available - dp){ throw new EOFException(); } System.arraycopy(buffer, dp, data, offset, size); dataPointer += size; } public int readInt() throws IOException { //System.out.println("readInt start : "+dataPointer+" of "+available); if(available <= dataPointer){ fill(); } int dp = dataPointer; byte[] buf = buffer; int total = available - dp; dataPointer += 4; if(total < 4){ throw new EOFException(); } return ArrayUtil.bArrayToI(buf,dp); } /** @deprecated: don't use this method it's not implemented */ public String readLine() throws IOException { //TODO ... return null; } public long readLong() throws IOException { if(available <= dataPointer){ fill(); } int dp = dataPointer; byte[] buf = buffer; int total = available - dp; dataPointer += 8; if(total < 8){ throw new EOFException(); } return ArrayUtil.bArrayToL(buf,dp); } public final Object readObject() throws OptionalDataException, ClassNotFoundException, IOException { if(override){ return readObjectOverride(); } ClassLoader cl; int rd = in.read(); //dbCount++; //discard data in buffer available = 0; ObjectStreamClass osc; Method readResolve; Vector cache; int place; Object o; switch(rd){ case TC_NULL: //System.out.println("RETURNING NULL"); return null; case TC_REFERENCE: byte[] buf = buffer; getBytes(buf, 4); int handle = ArrayUtil.bArrayToI(buf,0); return objectCache.get(handle - baseWireHandle); case TC_CLASSDESC: cl = currentLoader; if(cl == null){ currentLoader = getCallingClassLoader(); } osc = readClassDescriptor(); currentLoader = cl; return osc; case TC_ARRAY: cl = currentLoader; if(cl == null){ currentLoader = getCallingClassLoader(); } osc = (ObjectStreamClass)readObject(); available = 0; buf = buffer; getBytes(buf, 4); int length = ArrayUtil.bArrayToI(buf,0); Class clz = osc.clazz.getComponentType(); if(clz == null){ throw new StreamCorruptedException("TC_ARRAY encountered but no Array on the stream"); } //System.out.println("Deserializing array from stream "+clz+" length = "+length+" clz isPrimitive ? "+clz.isPrimitive()); cache = objectCache; place = cache.size(); readResolve = null; if(clz.isPrimitive()){ o = createPrimitiveArray(clz,length); cache.addElement(o); } else { o = Array.newInstance(clz,length); cache.addElement(o); Object[] oa = (Object[])o; for(int i = 0 ; i < length ; i++){ oa[i] = readObject(); } } currentLoader = cl; break; case TC_LONGSTRING: buf = buffer; cl = currentLoader; getBytes(buf, 8); int size = ArrayUtil.bArrayToI(buf,4); byte[] bytes = new byte[size]; getBytes(bytes, size); o = UTF8Decoder.bToString(bytes, 0, size); cache = objectCache; place = cache.size(); cache.addElement(o); readResolve = null; break; case TC_STRING: buf = buffer; cl = currentLoader; getBytes(buf, 2); size = ((buf[0] & 0xff)<<8) | (buf[1] & 0xff); if(size > buf.length){ buf = new byte[size]; buffer = buf; } getBytes(buf, size); o = UTF8Decoder.bToString(buf, 0, size); cache = objectCache; place = cache.size(); cache.addElement(o); readResolve = null; break; case TC_OBJECT: cl = currentLoader; if(cl == null){ currentLoader = getCallingClassLoader(); } osc = (ObjectStreamClass)readObject(); o = allocNewInstance(osc.clazz); //System.out.println("created object of type "+osc); readResolve = osc.readResolve; cache = objectCache; place = cache.size(); cache.addElement(o); Object tmp = current; ObjectStreamClass tmpOSC = currentOSC; currentOSC = osc; current = o; if((osc.flags & SC_EXTERNALIZABLE) > 0){ ((Externalizable)o).readExternal(this); } else { 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]; currentOSC = osc; Method readObject = osc.readObject; currentOSC = osc; if(readObject == null){ defaultReadObject(); } else{ try { readObject.invoke(o,args); }catch(InvocationTargetException ite){ handleException(ite.getTargetException()); }catch(IllegalAccessException iae){ handleException(iae); } } if((osc.flags & SC_WRITE_METHOD) > 0){ if(in.read() != TC_ENDBLOCKDATA){ throw new StreamCorruptedException("TC_ENDBLOCKDATA not found"); } } } while(i > 0); } currentLoader = cl; current = tmp; currentOSC = tmpOSC; break; case TC_CLASS: cl = currentLoader; if(cl == null){ currentLoader = getCallingClassLoader(); } osc = (ObjectStreamClass)readObject(); Class clazz = osc.clazz; objectCache.addElement(clazz); currentLoader = cl; return clazz; case TC_BLOCKDATA: rd = in.read(); //dbCount++; if(rd == -1){ throw new EOFException(); } throw new OptionalDataException(rd); case TC_BLOCKDATALONG: buf = buffer; getBytes(buf, 4); throw new OptionalDataException(ArrayUtil.bArrayToI(buf,0)); case TC_RESET: cl = currentLoader; if(cl == null){ currentLoader = getCallingClassLoader(); } objectCache.clear(); return readObject(); case TC_EXCEPTION: objectCache.clear(); Exception e = (Exception) readObject(); throw new WriteAbortedException("Aborting serialization ",e); case TC_ENDBLOCKDATA: return readObject(); case -1: throw new EOFException("no objects on stream"); default: throw new StreamCorruptedException("unkown case in readObject: byte "+Integer.toHexString(rd)); } Object rplc = o; if(readResolve != null){ try { rplc = readResolve.invoke(o, ObjectOutputStream.NO_ARGS); } catch(Exception ite){ //what TODO with it ... } } if(enableReplaceObject){ rplc = resolveObject(rplc); } if(rplc != o){ cache.setElementAt(rplc,place); } if(cl == null && objectValidaters != null){ Vector list = objectValidaters; int l = list.size(); ObjectInputValidation[] array = new ObjectInputValidation[l]; list.toArray(array); for(int i = 0 ; i < l ; i++){ array[i].validateObject(); } } return rplc; } /** default implementation returns null */ protected Object readObjectOverride() throws OptionalDataException, ClassNotFoundException, IOException { return null; } public short readShort() throws IOException { if(available <= dataPointer){ fill(); } int dp = dataPointer; byte[] buf = buffer; int total = available - dp; dataPointer += 2; if(total < 2){ throw new EOFException(); } int sh = buf[dp++]<<8; return (short)(sh | (buf[dp] & 0xff)); } protected void readStreamHeader() throws IOException, StreamCorruptedException { byte[] buf = buffer; getBytes(buf,4); if((buf[0] != 0xffffffac) || (buf[1]!= 0xffffffed) || buf[2] != 0 || buf[3] != 5){ throw new StreamCorruptedException("bad stream header encountered"); } } public int readUnsignedByte() throws IOException { if(available <= dataPointer){ fill(); } int dp = dataPointer++; byte[] buf = buffer; if(available <= dp){ throw new EOFException(); } return (buf[dp] & 0xff); } public int readUnsignedShort() throws IOException { if(available <= dataPointer){ fill(); } int dp = dataPointer; byte[] buf = buffer; int total = available - dp; dataPointer += 2; if(total < 2){ throw new EOFException(); } int sh = (buf[dp++] & 0xff)<<8; return sh | (buf[dp] & 0xff); } public String readUTF() throws IOException { if(available <= dataPointer){ fill(); } int dp = dataPointer; byte[] buf = buffer; int total = available - dp; if(total < 2){ available = 0; throw new EOFException(); } int length = ((buf[dp++] & 0xff)<<8); length |= (buf[dp++] & 0xff); if(total - 2 < length){ available = 0; throw new EOFException(); } dataPointer = dp + length; return UTF8Decoder.bToString(buf, dp, length); } public void registerValidation(ObjectInputValidation obj, int prio) throws NotActiveException, InvalidObjectException { if(currentLoader == null){ throw new NotActiveException(); } if(obj == null){ throw new InvalidObjectException("null object is not allowed as ObjectInputValidation"); } if(objectValidaters == null){ objectValidaters = new Vector(); } //TODO ... do something with priorities !!! objectValidaters.addElement(obj); } protected Class resolveClass(ObjectStreamClass osc) throws IOException, ClassNotFoundException { //System.out.println("Default resolveClass: using "+currentLoader+" to load "+osc); return Class.forName(osc.name, false, currentLoader); } /** the default behaviour is to return the same object */ protected Object resolveObject(Object obj) throws IOException { return obj; } protected Class resolveProxyClass(String[] interfaces) throws IOException, ClassNotFoundException { int length = interfaces.length; Class[] classes = new Class[length]; ClassLoader cl = currentLoader; for (int i = 0 ; i < length ; i++){ Class.forName(interfaces[i], false, cl); } return Proxy.getProxyClass(cl, classes); } public int skipBytes(int len) throws IOException { if(available <= dataPointer){ try { fill(); } catch(EOFException eof){ return 0; } } int skip = available - dataPointer; if(len < skip){ skip = len; } dataPointer += skip; return skip; } /** read and fill the dataBuffer */ private void fill() throws IOException { dataPointer = 0; int rd = in.read(); //dbCount++; switch(rd){ case TC_BLOCKDATA: rd = in.read(); //dbCount++; if(rd == -1){ throw new EOFException(); } //System.out.println("FILL: TC_BLOCKDATA size "+rd+" @"+dbCount); getBytes(buffer, rd); available = rd; break; case TC_BLOCKDATALONG: byte[] buf = buffer; getBytes(buf, 4); int size = ArrayUtil.bArrayToI(buf,0); if(size > buf.length){ buf = new byte[size]; buffer = buf; } getBytes(buf,size); available = size; break; default: throw new StreamCorruptedException("expected TC_BLOCKDATA or TC_BLOCKDATALONG but got "+rd); } } /** serves as an internal readFully , check if parameter offset can be discarded and replaced by 0 */ private void getBytes(byte[] buf, int len) throws IOException { int off = 0; int rd = in.read(buf, off, len); //System.out.println("getBytes needs to load "+len+" bytes and got "+rd); while(rd < len){ if(rd == -1){ throw new EOFException(); } off += rd; len -= rd; rd = in.read(buf, off, len); } //dbCount+=len; } /** creates and fill the primitive array and fills it with data from the byte array */ private native Object createPrimitiveArray(Class clazz, int length); /** get the calling classloader called to resolve classes by the default resolveClass */ private native ClassLoader getCallingClassLoader(); /** creates an object of Class clazz and runs a no-arg constructor */ private native Object allocNewInstance(Class clazz) throws IOException; private void handleException(Throwable e) throws IOException { //TODO ... throw new StreamCorruptedException("stream got corrupted by "+e); } private String desc2Name(String desc){ if(desc.charAt(0) != '['){ desc = desc.substring(1,desc.length()-1); } String name = desc.replace('/','.'); //System.out.println("changed '"+desc+"' to '"+name+"'"); return name; } public abstract static class GetField { public abstract boolean defaulted(String name) throws IOException, IllegalArgumentException; public abstract boolean get(String name, boolean defvalue) throws IOException, IllegalArgumentException; public abstract byte get(String name, byte defvalue) throws IOException, IllegalArgumentException; public abstract char get(String name, char defvalue) throws IOException, IllegalArgumentException; public abstract double get(String name, double defvalue) throws IOException, IllegalArgumentException; public abstract float get(String name, float defvalue) throws IOException, IllegalArgumentException; public abstract int get(String name, int defvalue) throws IOException, IllegalArgumentException; public abstract long get(String name, long defvalue) throws IOException, IllegalArgumentException; public abstract Object get(String name, Object defvalue) throws IOException, IllegalArgumentException; public abstract short get(String name, short defvalue) throws IOException, IllegalArgumentException; public abstract ObjectStreamClass getObjectStreamClass(); } /* simple class to test to check bytes read !!! private static class TeeInputStream extends InputStream { static int counter; private InputStream one; private OutputStream two; TeeInputStream(InputStream out) throws IOException{ one = out; two = new FileOutputStream("INSER"+(counter++)); } public int read() throws IOException { int rd = one.read(); if(rd != -1){ two.write(rd); two.flush(); } else { two.close(); } return rd; } public int read(byte[] b,int o, int l) throws IOException { int rd = one.read(b,o,l); if(rd != -1){ two.write(b,o,rd); two.flush(); } else { two.close(); } return rd; } public int read(byte[] b) throws IOException{ int rd = one.read(b,0,b.length); if(rd != -1){ two.write(b,0,rd); two.flush(); } else { two.close(); } return rd; } public int available() throws IOException { two.flush(); return one.available(); } public void close() throws IOException { one.close(); two.close(); } } //*/ }