| /* ObjectOutputStream.java -- Class used to write serialized objects |
| Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006 |
| Free Software Foundation, Inc. |
| |
| This file is part of GNU Classpath. |
| |
| GNU Classpath is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 2, or (at your option) |
| any later version. |
| |
| GNU Classpath is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with GNU Classpath; see the file COPYING. If not, write to the |
| Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
| 02110-1301 USA. |
| |
| Linking this library statically or dynamically with other modules is |
| making a combined work based on this library. Thus, the terms and |
| conditions of the GNU General Public License cover the whole |
| combination. |
| |
| As a special exception, the copyright holders of this library give you |
| permission to link this library with independent modules to produce an |
| executable, regardless of the license terms of these independent |
| modules, and to copy and distribute the resulting executable under |
| terms of your choice, provided that you also meet, for each linked |
| independent module, the terms and conditions of the license of that |
| module. An independent module is a module which is not derived from |
| or based on this library. If you modify this library, you may extend |
| this exception to your version of the library, but you are not |
| obligated to do so. If you do not wish to do so, delete this |
| exception statement from your version. */ |
| |
| |
| package java.io; |
| |
| import gnu.java.io.ObjectIdentityWrapper; |
| import gnu.java.lang.reflect.TypeSignature; |
| import gnu.java.security.action.SetAccessibleAction; |
| |
| import java.lang.reflect.Array; |
| import java.lang.reflect.Field; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.security.AccessController; |
| import java.util.Hashtable; |
| |
| /** |
| * An <code>ObjectOutputStream</code> can be used to write objects |
| * as well as primitive data in a platform-independent manner to an |
| * <code>OutputStream</code>. |
| * |
| * The data produced by an <code>ObjectOutputStream</code> can be read |
| * and reconstituted by an <code>ObjectInputStream</code>. |
| * |
| * <code>writeObject (Object)</code> is used to write Objects, the |
| * <code>write<type></code> methods are used to write primitive |
| * data (as in <code>DataOutputStream</code>). Strings can be written |
| * as objects or as primitive data. |
| * |
| * Not all objects can be written out using an |
| * <code>ObjectOutputStream</code>. Only those objects that are an |
| * instance of <code>java.io.Serializable</code> can be written. |
| * |
| * Using default serialization, information about the class of an |
| * object is written, all of the non-transient, non-static fields of |
| * the object are written, if any of these fields are objects, they are |
| * written out in the same manner. |
| * |
| * An object is only written out the first time it is encountered. If |
| * the object is encountered later, a reference to it is written to |
| * the underlying stream. Thus writing circular object graphs |
| * does not present a problem, nor are relationships between objects |
| * in a graph lost. |
| * |
| * Example usage: |
| * <pre> |
| * Hashtable map = new Hashtable (); |
| * map.put ("one", new Integer (1)); |
| * map.put ("two", new Integer (2)); |
| * |
| * ObjectOutputStream oos = |
| * new ObjectOutputStream (new FileOutputStream ("numbers")); |
| * oos.writeObject (map); |
| * oos.close (); |
| * |
| * ObjectInputStream ois = |
| * new ObjectInputStream (new FileInputStream ("numbers")); |
| * Hashtable newmap = (Hashtable)ois.readObject (); |
| * |
| * System.out.println (newmap); |
| * </pre> |
| * |
| * The default serialization can be overriden in two ways. |
| * |
| * By defining a method <code>private void |
| * writeObject (ObjectOutputStream)</code>, a class can dictate exactly |
| * how information about itself is written. |
| * <code>defaultWriteObject ()</code> may be called from this method to |
| * carry out default serialization. This method is not |
| * responsible for dealing with fields of super-classes or subclasses. |
| * |
| * By implementing <code>java.io.Externalizable</code>. This gives |
| * the class complete control over the way it is written to the |
| * stream. If this approach is used the burden of writing superclass |
| * and subclass data is transfered to the class implementing |
| * <code>java.io.Externalizable</code>. |
| * |
| * @see java.io.DataOutputStream |
| * @see java.io.Externalizable |
| * @see java.io.ObjectInputStream |
| * @see java.io.Serializable |
| */ |
| public class ObjectOutputStream extends OutputStream |
| implements ObjectOutput, ObjectStreamConstants |
| { |
| /** |
| * Creates a new <code>ObjectOutputStream</code> that will do all of |
| * its writing onto <code>out</code>. This method also initializes |
| * the stream by writing the header information (stream magic number |
| * and stream version). |
| * |
| * @exception IOException Writing stream header to underlying |
| * stream cannot be completed. |
| * |
| * @see #writeStreamHeader() |
| */ |
| public ObjectOutputStream (OutputStream out) throws IOException |
| { |
| realOutput = new DataOutputStream(out); |
| blockData = new byte[ BUFFER_SIZE ]; |
| blockDataCount = 0; |
| blockDataOutput = new DataOutputStream(this); |
| setBlockDataMode(true); |
| replacementEnabled = false; |
| isSerializing = false; |
| nextOID = baseWireHandle; |
| OIDLookupTable = new Hashtable(); |
| protocolVersion = defaultProtocolVersion; |
| useSubclassMethod = false; |
| writeStreamHeader(); |
| |
| if (DEBUG) |
| { |
| String val = System.getProperty("gcj.dumpobjects"); |
| if (val != null && !val.equals("")) |
| dump = true; |
| } |
| } |
| |
| /** |
| * Writes a representation of <code>obj</code> to the underlying |
| * output stream by writing out information about its class, then |
| * writing out each of the objects non-transient, non-static |
| * fields. If any of these fields are other objects, |
| * they are written out in the same manner. |
| * |
| * This method can be overriden by a class by implementing |
| * <code>private void writeObject (ObjectOutputStream)</code>. |
| * |
| * If an exception is thrown from this method, the stream is left in |
| * an undefined state. |
| * |
| * @exception NotSerializableException An attempt was made to |
| * serialize an <code>Object</code> that is not serializable. |
| * |
| * @exception InvalidClassException Somebody tried to serialize |
| * an object which is wrongly formatted. |
| * |
| * @exception IOException Exception from underlying |
| * <code>OutputStream</code>. |
| */ |
| public final void writeObject(Object obj) throws IOException |
| { |
| if (useSubclassMethod) |
| { |
| if (dump) |
| dumpElementln ("WRITE OVERRIDE: " + obj); |
| |
| writeObjectOverride(obj); |
| return; |
| } |
| |
| if (dump) |
| dumpElementln ("WRITE: " + obj); |
| |
| depth += 2; |
| |
| boolean was_serializing = isSerializing; |
| boolean old_mode = setBlockDataMode(false); |
| try |
| { |
| isSerializing = true; |
| boolean replaceDone = false; |
| Object replacedObject = null; |
| |
| while (true) |
| { |
| if (obj == null) |
| { |
| realOutput.writeByte(TC_NULL); |
| break; |
| } |
| |
| Integer handle = findHandle(obj); |
| if (handle != null) |
| { |
| realOutput.writeByte(TC_REFERENCE); |
| realOutput.writeInt(handle.intValue()); |
| break; |
| } |
| |
| if (obj instanceof Class) |
| { |
| Class cl = (Class)obj; |
| ObjectStreamClass osc = ObjectStreamClass.lookupForClassObject(cl); |
| realOutput.writeByte(TC_CLASS); |
| if (!osc.isProxyClass) |
| { |
| writeObject (osc); |
| } |
| else |
| { |
| realOutput.writeByte(TC_PROXYCLASSDESC); |
| Class[] intfs = cl.getInterfaces(); |
| realOutput.writeInt(intfs.length); |
| for (int i = 0; i < intfs.length; i++) |
| realOutput.writeUTF(intfs[i].getName()); |
| |
| boolean oldmode = setBlockDataMode(true); |
| annotateProxyClass(cl); |
| setBlockDataMode(oldmode); |
| realOutput.writeByte(TC_ENDBLOCKDATA); |
| |
| writeObject(osc.getSuper()); |
| } |
| assignNewHandle(obj); |
| break; |
| } |
| |
| if (obj instanceof ObjectStreamClass) |
| { |
| writeClassDescriptor((ObjectStreamClass) obj); |
| break; |
| } |
| |
| Class clazz = obj.getClass(); |
| ObjectStreamClass osc = ObjectStreamClass.lookupForClassObject(clazz); |
| if (osc == null) |
| throw new NotSerializableException(clazz.getName()); |
| |
| if (osc.isEnum()) |
| { |
| /* TC_ENUM classDesc newHandle enumConstantName */ |
| realOutput.writeByte(TC_ENUM); |
| writeObject(osc); |
| assignNewHandle(obj); |
| writeObject(((Enum) obj).name()); |
| break; |
| } |
| |
| if ((replacementEnabled || obj instanceof Serializable) |
| && ! replaceDone) |
| { |
| replacedObject = obj; |
| |
| if (obj instanceof Serializable) |
| { |
| try |
| { |
| Method m = osc.writeReplaceMethod; |
| if (m != null) |
| obj = m.invoke(obj, new Object[0]); |
| } |
| catch (IllegalAccessException ignore) |
| { |
| } |
| catch (InvocationTargetException ignore) |
| { |
| } |
| } |
| |
| if (replacementEnabled) |
| obj = replaceObject(obj); |
| |
| replaceDone = true; |
| continue; |
| } |
| |
| if (obj instanceof String) |
| { |
| realOutput.writeByte(TC_STRING); |
| assignNewHandle(obj); |
| realOutput.writeUTF((String)obj); |
| break; |
| } |
| |
| if (clazz.isArray ()) |
| { |
| realOutput.writeByte(TC_ARRAY); |
| writeObject(osc); |
| assignNewHandle(obj); |
| writeArraySizeAndElements(obj, clazz.getComponentType()); |
| break; |
| } |
| |
| realOutput.writeByte(TC_OBJECT); |
| writeObject(osc); |
| |
| if (replaceDone) |
| assignNewHandle(replacedObject); |
| else |
| assignNewHandle(obj); |
| |
| if (obj instanceof Externalizable) |
| { |
| if (protocolVersion == PROTOCOL_VERSION_2) |
| setBlockDataMode(true); |
| |
| ((Externalizable)obj).writeExternal(this); |
| |
| if (protocolVersion == PROTOCOL_VERSION_2) |
| { |
| setBlockDataMode(false); |
| realOutput.writeByte(TC_ENDBLOCKDATA); |
| } |
| |
| break; |
| } |
| |
| if (obj instanceof Serializable) |
| { |
| Object prevObject = this.currentObject; |
| ObjectStreamClass prevObjectStreamClass = this.currentObjectStreamClass; |
| currentObject = obj; |
| ObjectStreamClass[] hierarchy = |
| ObjectStreamClass.getObjectStreamClasses(clazz); |
| |
| for (int i = 0; i < hierarchy.length; i++) |
| { |
| currentObjectStreamClass = hierarchy[i]; |
| |
| fieldsAlreadyWritten = false; |
| if (currentObjectStreamClass.hasWriteMethod()) |
| { |
| if (dump) |
| dumpElementln ("WRITE METHOD CALLED FOR: " + obj); |
| setBlockDataMode(true); |
| callWriteMethod(obj, currentObjectStreamClass); |
| setBlockDataMode(false); |
| realOutput.writeByte(TC_ENDBLOCKDATA); |
| if (dump) |
| dumpElementln ("WRITE ENDBLOCKDATA FOR: " + obj); |
| } |
| else |
| { |
| if (dump) |
| dumpElementln ("WRITE FIELDS CALLED FOR: " + obj); |
| writeFields(obj, currentObjectStreamClass); |
| } |
| } |
| |
| this.currentObject = prevObject; |
| this.currentObjectStreamClass = prevObjectStreamClass; |
| currentPutField = null; |
| break; |
| } |
| |
| throw new NotSerializableException(clazz.getName() |
| + " in " |
| + obj.getClass()); |
| } // end pseudo-loop |
| } |
| catch (ObjectStreamException ose) |
| { |
| // Rethrow these are fatal. |
| throw ose; |
| } |
| catch (IOException e) |
| { |
| realOutput.writeByte(TC_EXCEPTION); |
| reset(true); |
| |
| setBlockDataMode(false); |
| try |
| { |
| if (DEBUG) |
| { |
| e.printStackTrace(System.out); |
| } |
| writeObject(e); |
| } |
| catch (IOException ioe) |
| { |
| StreamCorruptedException ex = |
| new StreamCorruptedException |
| (ioe + " thrown while exception was being written to stream."); |
| if (DEBUG) |
| { |
| ex.printStackTrace(System.out); |
| } |
| throw ex; |
| } |
| |
| reset (true); |
| |
| } |
| finally |
| { |
| isSerializing = was_serializing; |
| setBlockDataMode(old_mode); |
| depth -= 2; |
| |
| if (dump) |
| dumpElementln ("END: " + obj); |
| } |
| } |
| |
| protected void writeClassDescriptor(ObjectStreamClass osc) throws IOException |
| { |
| if (osc.isProxyClass) |
| { |
| realOutput.writeByte(TC_PROXYCLASSDESC); |
| Class[] intfs = osc.forClass().getInterfaces(); |
| realOutput.writeInt(intfs.length); |
| for (int i = 0; i < intfs.length; i++) |
| realOutput.writeUTF(intfs[i].getName()); |
| |
| assignNewHandle(osc); |
| |
| boolean oldmode = setBlockDataMode(true); |
| annotateProxyClass(osc.forClass()); |
| setBlockDataMode(oldmode); |
| realOutput.writeByte(TC_ENDBLOCKDATA); |
| } |
| else |
| { |
| realOutput.writeByte(TC_CLASSDESC); |
| realOutput.writeUTF(osc.getName()); |
| if (osc.isEnum()) |
| realOutput.writeLong(0L); |
| else |
| realOutput.writeLong(osc.getSerialVersionUID()); |
| assignNewHandle(osc); |
| |
| int flags = osc.getFlags(); |
| |
| if (protocolVersion == PROTOCOL_VERSION_2 |
| && osc.isExternalizable()) |
| flags |= SC_BLOCK_DATA; |
| |
| realOutput.writeByte(flags); |
| |
| ObjectStreamField[] fields = osc.fields; |
| |
| if (fields == ObjectStreamClass.INVALID_FIELDS) |
| throw new InvalidClassException |
| (osc.getName(), "serialPersistentFields is invalid"); |
| |
| realOutput.writeShort(fields.length); |
| |
| ObjectStreamField field; |
| for (int i = 0; i < fields.length; i++) |
| { |
| field = fields[i]; |
| realOutput.writeByte(field.getTypeCode ()); |
| realOutput.writeUTF(field.getName ()); |
| |
| if (! field.isPrimitive()) |
| writeObject(field.getTypeString()); |
| } |
| |
| boolean oldmode = setBlockDataMode(true); |
| annotateClass(osc.forClass()); |
| setBlockDataMode(oldmode); |
| realOutput.writeByte(TC_ENDBLOCKDATA); |
| } |
| |
| if (osc.isSerializable() || osc.isExternalizable()) |
| writeObject(osc.getSuper()); |
| else |
| writeObject(null); |
| } |
| |
| /** |
| * Writes the current objects non-transient, non-static fields from |
| * the current class to the underlying output stream. |
| * |
| * This method is intended to be called from within a object's |
| * <code>private void writeObject (ObjectOutputStream)</code> |
| * method. |
| * |
| * @exception NotActiveException This method was called from a |
| * context other than from the current object's and current class's |
| * <code>private void writeObject (ObjectOutputStream)</code> |
| * method. |
| * |
| * @exception IOException Exception from underlying |
| * <code>OutputStream</code>. |
| */ |
| public void defaultWriteObject() |
| throws IOException, NotActiveException |
| { |
| markFieldsWritten(); |
| writeFields(currentObject, currentObjectStreamClass); |
| } |
| |
| |
| private void markFieldsWritten() throws IOException |
| { |
| if (currentObject == null || currentObjectStreamClass == null) |
| throw new NotActiveException |
| ("defaultWriteObject called by non-active class and/or object"); |
| |
| if (fieldsAlreadyWritten) |
| throw new IOException |
| ("Only one of writeFields and defaultWriteObject may be called, and it may only be called once"); |
| |
| fieldsAlreadyWritten = true; |
| } |
| |
| /** |
| * Resets stream to state equivalent to the state just after it was |
| * constructed. |
| * |
| * Causes all objects previously written to the stream to be |
| * forgotten. A notification of this reset is also written to the |
| * underlying stream. |
| * |
| * @exception IOException Exception from underlying |
| * <code>OutputStream</code> or reset called while serialization is |
| * in progress. |
| */ |
| public void reset() throws IOException |
| { |
| reset(false); |
| } |
| |
| |
| private void reset(boolean internal) throws IOException |
| { |
| if (!internal) |
| { |
| if (isSerializing) |
| throw new IOException("Reset called while serialization in progress"); |
| |
| realOutput.writeByte(TC_RESET); |
| } |
| |
| clearHandles(); |
| } |
| |
| |
| /** |
| * Informs this <code>ObjectOutputStream</code> to write data |
| * according to the specified protocol. There are currently two |
| * different protocols, specified by <code>PROTOCOL_VERSION_1</code> |
| * and <code>PROTOCOL_VERSION_2</code>. This implementation writes |
| * data using <code>PROTOCOL_VERSION_2</code> by default, as is done |
| * since the JDK 1.2. |
| * <p> |
| * For an explanation of the differences between the two protocols |
| * see the Java Object Serialization Specification. |
| * </p> |
| * |
| * @param version the version to use. |
| * |
| * @throws IllegalArgumentException if <code>version</code> is not a valid |
| * protocol. |
| * @throws IllegalStateException if called after the first the first object |
| * was serialized. |
| * @throws IOException if an I/O error occurs. |
| * |
| * @see ObjectStreamConstants#PROTOCOL_VERSION_1 |
| * @see ObjectStreamConstants#PROTOCOL_VERSION_2 |
| * |
| * @since 1.2 |
| */ |
| public void useProtocolVersion(int version) throws IOException |
| { |
| if (version != PROTOCOL_VERSION_1 && version != PROTOCOL_VERSION_2) |
| throw new IllegalArgumentException("Invalid protocol version requested."); |
| |
| if (nextOID != baseWireHandle) |
| throw new IllegalStateException("Protocol version cannot be changed " |
| + "after serialization started."); |
| |
| protocolVersion = version; |
| } |
| |
| /** |
| * An empty hook that allows subclasses to write extra information |
| * about classes to the stream. This method is called the first |
| * time each class is seen, and after all of the standard |
| * information about the class has been written. |
| * |
| * @exception IOException Exception from underlying |
| * <code>OutputStream</code>. |
| * |
| * @see ObjectInputStream#resolveClass(java.io.ObjectStreamClass) |
| */ |
| protected void annotateClass(Class cl) throws IOException |
| { |
| } |
| |
| protected void annotateProxyClass(Class cl) throws IOException |
| { |
| } |
| |
| /** |
| * Allows subclasses to replace objects that are written to the |
| * stream with other objects to be written in their place. This |
| * method is called the first time each object is encountered |
| * (modulo reseting of the stream). |
| * |
| * This method must be enabled before it will be called in the |
| * serialization process. |
| * |
| * @exception IOException Exception from underlying |
| * <code>OutputStream</code>. |
| * |
| * @see #enableReplaceObject(boolean) |
| */ |
| protected Object replaceObject(Object obj) throws IOException |
| { |
| return obj; |
| } |
| |
| |
| /** |
| * If <code>enable</code> is <code>true</code> and this object is |
| * trusted, then <code>replaceObject (Object)</code> will be called |
| * in subsequent calls to <code>writeObject (Object)</code>. |
| * Otherwise, <code>replaceObject (Object)</code> will not be called. |
| * |
| * @exception SecurityException This class is not trusted. |
| */ |
| protected boolean enableReplaceObject(boolean enable) |
| throws SecurityException |
| { |
| if (enable) |
| { |
| SecurityManager sm = System.getSecurityManager(); |
| if (sm != null) |
| sm.checkPermission(new SerializablePermission("enableSubstitution")); |
| } |
| |
| boolean old_val = replacementEnabled; |
| replacementEnabled = enable; |
| return old_val; |
| } |
| |
| |
| /** |
| * Writes stream magic and stream version information to the |
| * underlying stream. |
| * |
| * @exception IOException Exception from underlying |
| * <code>OutputStream</code>. |
| */ |
| protected void writeStreamHeader() throws IOException |
| { |
| realOutput.writeShort(STREAM_MAGIC); |
| realOutput.writeShort(STREAM_VERSION); |
| } |
| |
| /** |
| * Protected constructor that allows subclasses to override |
| * serialization. This constructor should be called by subclasses |
| * that wish to override <code>writeObject (Object)</code>. This |
| * method does a security check <i>NOTE: currently not |
| * implemented</i>, then sets a flag that informs |
| * <code>writeObject (Object)</code> to call the subclasses |
| * <code>writeObjectOverride (Object)</code> method. |
| * |
| * @see #writeObjectOverride(Object) |
| */ |
| protected ObjectOutputStream() throws IOException, SecurityException |
| { |
| SecurityManager sec_man = System.getSecurityManager (); |
| if (sec_man != null) |
| sec_man.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION); |
| useSubclassMethod = true; |
| } |
| |
| |
| /** |
| * This method allows subclasses to override the default |
| * serialization mechanism provided by |
| * <code>ObjectOutputStream</code>. To make this method be used for |
| * writing objects, subclasses must invoke the 0-argument |
| * constructor on this class from there constructor. |
| * |
| * @see #ObjectOutputStream() |
| * |
| * @exception NotActiveException Subclass has arranged for this |
| * method to be called, but did not implement this method. |
| */ |
| protected void writeObjectOverride(Object obj) throws NotActiveException, |
| IOException |
| { |
| throw new NotActiveException |
| ("Subclass of ObjectOutputStream must implement writeObjectOverride"); |
| } |
| |
| |
| /** |
| * @see DataOutputStream#write(int) |
| */ |
| public void write (int data) throws IOException |
| { |
| if (writeDataAsBlocks) |
| { |
| if (blockDataCount == BUFFER_SIZE) |
| drain(); |
| |
| blockData[ blockDataCount++ ] = (byte)data; |
| } |
| else |
| realOutput.write(data); |
| } |
| |
| |
| /** |
| * @see DataOutputStream#write(byte[]) |
| */ |
| public void write(byte[] b) throws IOException |
| { |
| write(b, 0, b.length); |
| } |
| |
| |
| /** |
| * @see DataOutputStream#write(byte[],int,int) |
| */ |
| public void write(byte[] b, int off, int len) throws IOException |
| { |
| if (writeDataAsBlocks) |
| { |
| if (len < 0) |
| throw new IndexOutOfBoundsException(); |
| |
| if (blockDataCount + len < BUFFER_SIZE) |
| { |
| System.arraycopy(b, off, blockData, blockDataCount, len); |
| blockDataCount += len; |
| } |
| else |
| { |
| drain(); |
| writeBlockDataHeader(len); |
| realOutput.write(b, off, len); |
| } |
| } |
| else |
| realOutput.write(b, off, len); |
| } |
| |
| |
| /** |
| * @see DataOutputStream#flush() |
| */ |
| public void flush () throws IOException |
| { |
| drain(); |
| realOutput.flush(); |
| } |
| |
| |
| /** |
| * Causes the block-data buffer to be written to the underlying |
| * stream, but does not flush underlying stream. |
| * |
| * @exception IOException Exception from underlying |
| * <code>OutputStream</code>. |
| */ |
| protected void drain() throws IOException |
| { |
| if (blockDataCount == 0) |
| return; |
| |
| if (writeDataAsBlocks) |
| writeBlockDataHeader(blockDataCount); |
| realOutput.write(blockData, 0, blockDataCount); |
| blockDataCount = 0; |
| } |
| |
| |
| /** |
| * @see java.io.DataOutputStream#close () |
| */ |
| public void close() throws IOException |
| { |
| flush(); |
| realOutput.close(); |
| } |
| |
| |
| /** |
| * @see java.io.DataOutputStream#writeBoolean (boolean) |
| */ |
| public void writeBoolean(boolean data) throws IOException |
| { |
| blockDataOutput.writeBoolean(data); |
| } |
| |
| |
| /** |
| * @see java.io.DataOutputStream#writeByte (int) |
| */ |
| public void writeByte(int data) throws IOException |
| { |
| blockDataOutput.writeByte(data); |
| } |
| |
| |
| /** |
| * @see java.io.DataOutputStream#writeShort (int) |
| */ |
| public void writeShort (int data) throws IOException |
| { |
| blockDataOutput.writeShort(data); |
| } |
| |
| |
| /** |
| * @see java.io.DataOutputStream#writeChar (int) |
| */ |
| public void writeChar(int data) throws IOException |
| { |
| blockDataOutput.writeChar(data); |
| } |
| |
| |
| /** |
| * @see java.io.DataOutputStream#writeInt (int) |
| */ |
| public void writeInt(int data) throws IOException |
| { |
| blockDataOutput.writeInt(data); |
| } |
| |
| |
| /** |
| * @see java.io.DataOutputStream#writeLong (long) |
| */ |
| public void writeLong(long data) throws IOException |
| { |
| blockDataOutput.writeLong(data); |
| } |
| |
| |
| /** |
| * @see java.io.DataOutputStream#writeFloat (float) |
| */ |
| public void writeFloat(float data) throws IOException |
| { |
| blockDataOutput.writeFloat(data); |
| } |
| |
| |
| /** |
| * @see java.io.DataOutputStream#writeDouble (double) |
| */ |
| public void writeDouble(double data) throws IOException |
| { |
| blockDataOutput.writeDouble(data); |
| } |
| |
| |
| /** |
| * @see java.io.DataOutputStream#writeBytes (java.lang.String) |
| */ |
| public void writeBytes(String data) throws IOException |
| { |
| blockDataOutput.writeBytes(data); |
| } |
| |
| |
| /** |
| * @see java.io.DataOutputStream#writeChars (java.lang.String) |
| */ |
| public void writeChars(String data) throws IOException |
| { |
| dataOutput.writeChars(data); |
| } |
| |
| |
| /** |
| * @see java.io.DataOutputStream#writeUTF (java.lang.String) |
| */ |
| public void writeUTF(String data) throws IOException |
| { |
| dataOutput.writeUTF(data); |
| } |
| |
| |
| /** |
| * This class allows a class to specify exactly which fields should |
| * be written, and what values should be written for these fields. |
| * |
| * XXX: finish up comments |
| */ |
| public abstract static class 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, short value); |
| public abstract void put (String name, Object value); |
| |
| /** |
| * @deprecated |
| */ |
| public abstract void write (ObjectOutput out) throws IOException; |
| } |
| |
| public PutField putFields() throws IOException |
| { |
| if (currentPutField != null) |
| return currentPutField; |
| |
| currentPutField = new PutField() |
| { |
| private byte[] prim_field_data |
| = new byte[currentObjectStreamClass.primFieldSize]; |
| private Object[] objs |
| = new Object[currentObjectStreamClass.objectFieldCount]; |
| |
| private ObjectStreamField getField (String name) |
| { |
| ObjectStreamField field |
| = currentObjectStreamClass.getField(name); |
| |
| if (field == null) |
| throw new IllegalArgumentException("no such serializable field " + name); |
| |
| return field; |
| } |
| |
| public void put(String name, boolean value) |
| { |
| ObjectStreamField field = getField(name); |
| |
| checkType(field, 'Z'); |
| prim_field_data[field.getOffset ()] = (byte)(value ? 1 : 0); |
| } |
| |
| public void put(String name, byte value) |
| { |
| ObjectStreamField field = getField(name); |
| |
| checkType(field, 'B'); |
| prim_field_data[field.getOffset()] = value; |
| } |
| |
| public void put(String name, char value) |
| { |
| ObjectStreamField field = getField(name); |
| |
| checkType(field, 'C'); |
| int off = field.getOffset(); |
| prim_field_data[off++] = (byte)(value >>> 8); |
| prim_field_data[off] = (byte)value; |
| } |
| |
| public void put(String name, double value) |
| { |
| ObjectStreamField field = getField (name); |
| |
| checkType(field, 'D'); |
| int off = field.getOffset(); |
| long l_value = Double.doubleToLongBits (value); |
| prim_field_data[off++] = (byte)(l_value >>> 52); |
| prim_field_data[off++] = (byte)(l_value >>> 48); |
| prim_field_data[off++] = (byte)(l_value >>> 40); |
| prim_field_data[off++] = (byte)(l_value >>> 32); |
| prim_field_data[off++] = (byte)(l_value >>> 24); |
| prim_field_data[off++] = (byte)(l_value >>> 16); |
| prim_field_data[off++] = (byte)(l_value >>> 8); |
| prim_field_data[off] = (byte)l_value; |
| } |
| |
| public void put(String name, float value) |
| { |
| ObjectStreamField field = getField(name); |
| |
| checkType(field, 'F'); |
| int off = field.getOffset(); |
| int i_value = Float.floatToIntBits(value); |
| prim_field_data[off++] = (byte)(i_value >>> 24); |
| prim_field_data[off++] = (byte)(i_value >>> 16); |
| prim_field_data[off++] = (byte)(i_value >>> 8); |
| prim_field_data[off] = (byte)i_value; |
| } |
| |
| public void put(String name, int value) |
| { |
| ObjectStreamField field = getField(name); |
| checkType(field, 'I'); |
| int off = field.getOffset(); |
| prim_field_data[off++] = (byte)(value >>> 24); |
| prim_field_data[off++] = (byte)(value >>> 16); |
| prim_field_data[off++] = (byte)(value >>> 8); |
| prim_field_data[off] = (byte)value; |
| } |
| |
| public void put(String name, long value) |
| { |
| ObjectStreamField field = getField(name); |
| checkType(field, 'J'); |
| int off = field.getOffset(); |
| prim_field_data[off++] = (byte)(value >>> 52); |
| prim_field_data[off++] = (byte)(value >>> 48); |
| prim_field_data[off++] = (byte)(value >>> 40); |
| prim_field_data[off++] = (byte)(value >>> 32); |
| prim_field_data[off++] = (byte)(value >>> 24); |
| prim_field_data[off++] = (byte)(value >>> 16); |
| prim_field_data[off++] = (byte)(value >>> 8); |
| prim_field_data[off] = (byte)value; |
| } |
| |
| public void put(String name, short value) |
| { |
| ObjectStreamField field = getField(name); |
| checkType(field, 'S'); |
| int off = field.getOffset(); |
| prim_field_data[off++] = (byte)(value >>> 8); |
| prim_field_data[off] = (byte)value; |
| } |
| |
| public void put(String name, Object value) |
| { |
| ObjectStreamField field = getField(name); |
| |
| if (value != null && |
| ! field.getType().isAssignableFrom(value.getClass ())) |
| throw new IllegalArgumentException("Class " + value.getClass() + |
| " cannot be cast to " + field.getType()); |
| objs[field.getOffset()] = value; |
| } |
| |
| public void write(ObjectOutput out) throws IOException |
| { |
| // Apparently Block data is not used with PutField as per |
| // empirical evidence against JDK 1.2. Also see Mauve test |
| // java.io.ObjectInputOutput.Test.GetPutField. |
| boolean oldmode = setBlockDataMode(false); |
| out.write(prim_field_data); |
| for (int i = 0; i < objs.length; ++ i) |
| out.writeObject(objs[i]); |
| setBlockDataMode(oldmode); |
| } |
| |
| private void checkType(ObjectStreamField field, char type) |
| throws IllegalArgumentException |
| { |
| if (TypeSignature.getEncodingOfClass(field.getType()).charAt(0) |
| != type) |
| throw new IllegalArgumentException(); |
| } |
| }; |
| // end PutFieldImpl |
| |
| return currentPutField; |
| } |
| |
| |
| public void writeFields() throws IOException |
| { |
| if (currentPutField == null) |
| throw new NotActiveException("writeFields can only be called after putFields has been called"); |
| |
| markFieldsWritten(); |
| currentPutField.write(this); |
| } |
| |
| |
| // write out the block-data buffer, picking the correct header |
| // depending on the size of the buffer |
| private void writeBlockDataHeader(int size) throws IOException |
| { |
| if (size < 256) |
| { |
| realOutput.writeByte(TC_BLOCKDATA); |
| realOutput.write(size); |
| } |
| else |
| { |
| realOutput.writeByte(TC_BLOCKDATALONG); |
| realOutput.writeInt(size); |
| } |
| } |
| |
| |
| // lookup the handle for OBJ, return null if OBJ doesn't have a |
| // handle yet |
| private Integer findHandle(Object obj) |
| { |
| return (Integer)OIDLookupTable.get(new ObjectIdentityWrapper(obj)); |
| } |
| |
| |
| // assigns the next availible handle to OBJ |
| private int assignNewHandle(Object obj) |
| { |
| OIDLookupTable.put(new ObjectIdentityWrapper(obj), |
| new Integer(nextOID)); |
| return nextOID++; |
| } |
| |
| |
| // resets mapping from objects to handles |
| private void clearHandles() |
| { |
| nextOID = baseWireHandle; |
| OIDLookupTable.clear(); |
| } |
| |
| |
| // write out array size followed by each element of the array |
| private void writeArraySizeAndElements(Object array, Class clazz) |
| throws IOException |
| { |
| int length = Array.getLength(array); |
| |
| if (clazz.isPrimitive()) |
| { |
| if (clazz == Boolean.TYPE) |
| { |
| boolean[] cast_array = (boolean[])array; |
| realOutput.writeInt (length); |
| for (int i = 0; i < length; i++) |
| realOutput.writeBoolean(cast_array[i]); |
| return; |
| } |
| if (clazz == Byte.TYPE) |
| { |
| byte[] cast_array = (byte[])array; |
| realOutput.writeInt(length); |
| realOutput.write(cast_array, 0, length); |
| return; |
| } |
| if (clazz == Character.TYPE) |
| { |
| char[] cast_array = (char[])array; |
| realOutput.writeInt(length); |
| for (int i = 0; i < length; i++) |
| realOutput.writeChar(cast_array[i]); |
| return; |
| } |
| if (clazz == Double.TYPE) |
| { |
| double[] cast_array = (double[])array; |
| realOutput.writeInt(length); |
| for (int i = 0; i < length; i++) |
| realOutput.writeDouble(cast_array[i]); |
| return; |
| } |
| if (clazz == Float.TYPE) |
| { |
| float[] cast_array = (float[])array; |
| realOutput.writeInt(length); |
| for (int i = 0; i < length; i++) |
| realOutput.writeFloat(cast_array[i]); |
| return; |
| } |
| if (clazz == Integer.TYPE) |
| { |
| int[] cast_array = (int[])array; |
| realOutput.writeInt(length); |
| for (int i = 0; i < length; i++) |
| realOutput.writeInt(cast_array[i]); |
| return; |
| } |
| if (clazz == Long.TYPE) |
| { |
| long[] cast_array = (long[])array; |
| realOutput.writeInt (length); |
| for (int i = 0; i < length; i++) |
| realOutput.writeLong(cast_array[i]); |
| return; |
| } |
| if (clazz == Short.TYPE) |
| { |
| short[] cast_array = (short[])array; |
| realOutput.writeInt (length); |
| for (int i = 0; i < length; i++) |
| realOutput.writeShort(cast_array[i]); |
| return; |
| } |
| } |
| else |
| { |
| Object[] cast_array = (Object[])array; |
| realOutput.writeInt(length); |
| for (int i = 0; i < length; i++) |
| writeObject(cast_array[i]); |
| } |
| } |
| |
| |
| // writes out FIELDS of OBJECT for the specified ObjectStreamClass. |
| // FIELDS are already in canonical order. |
| private void writeFields(Object obj, ObjectStreamClass osc) |
| throws IOException |
| { |
| ObjectStreamField[] fields = osc.fields; |
| boolean oldmode = setBlockDataMode(false); |
| String field_name; |
| Class type; |
| |
| for (int i = 0; i < fields.length; i++) |
| { |
| field_name = fields[i].getName(); |
| type = fields[i].getType(); |
| |
| if (dump) |
| dumpElementln ("WRITE FIELD: " + field_name + " type=" + type); |
| |
| if (type == Boolean.TYPE) |
| realOutput.writeBoolean(getBooleanField(obj, osc.forClass(), field_name)); |
| else if (type == Byte.TYPE) |
| realOutput.writeByte(getByteField(obj, osc.forClass(), field_name)); |
| else if (type == Character.TYPE) |
| realOutput.writeChar(getCharField(obj, osc.forClass(), field_name)); |
| else if (type == Double.TYPE) |
| realOutput.writeDouble(getDoubleField(obj, osc.forClass(), field_name)); |
| else if (type == Float.TYPE) |
| realOutput.writeFloat(getFloatField(obj, osc.forClass(), field_name)); |
| else if (type == Integer.TYPE) |
| realOutput.writeInt(getIntField(obj, osc.forClass(), field_name)); |
| else if (type == Long.TYPE) |
| realOutput.writeLong(getLongField(obj, osc.forClass(), field_name)); |
| else if (type == Short.TYPE) |
| realOutput.writeShort(getShortField(obj, osc.forClass(), field_name)); |
| else |
| writeObject(getObjectField(obj, osc.forClass(), field_name, |
| fields[i].getTypeString ())); |
| } |
| setBlockDataMode(oldmode); |
| } |
| |
| |
| // Toggles writing primitive data to block-data buffer. |
| // Package-private to avoid a trampoline constructor. |
| boolean setBlockDataMode(boolean on) throws IOException |
| { |
| if (on == writeDataAsBlocks) |
| return on; |
| |
| drain(); |
| boolean oldmode = writeDataAsBlocks; |
| writeDataAsBlocks = on; |
| |
| if (on) |
| dataOutput = blockDataOutput; |
| else |
| dataOutput = realOutput; |
| |
| return oldmode; |
| } |
| |
| |
| private void callWriteMethod(Object obj, ObjectStreamClass osc) |
| throws IOException |
| { |
| currentPutField = null; |
| try |
| { |
| Object args[] = {this}; |
| osc.writeObjectMethod.invoke(obj, args); |
| } |
| catch (InvocationTargetException x) |
| { |
| /* Rethrow if possible. */ |
| Throwable exception = x.getTargetException(); |
| if (exception instanceof RuntimeException) |
| throw (RuntimeException) exception; |
| if (exception instanceof IOException) |
| throw (IOException) exception; |
| |
| IOException ioe |
| = new IOException("Exception thrown from writeObject() on " + |
| osc.forClass().getName() + ": " + |
| exception.getClass().getName()); |
| ioe.initCause(exception); |
| throw ioe; |
| } |
| catch (Exception x) |
| { |
| IOException ioe |
| = new IOException("Failure invoking writeObject() on " + |
| osc.forClass().getName() + ": " + |
| x.getClass().getName()); |
| ioe.initCause(x); |
| throw ioe; |
| } |
| } |
| |
| private boolean getBooleanField(Object obj, Class klass, String field_name) |
| throws IOException |
| { |
| try |
| { |
| Field f = getField(klass, field_name); |
| boolean b = f.getBoolean(obj); |
| return b; |
| } |
| catch (IllegalArgumentException _) |
| { |
| throw new InvalidClassException |
| ("invalid requested type for field " + field_name + " in class " + klass.getName()); |
| } |
| catch (IOException e) |
| { |
| throw e; |
| } |
| catch (Exception _) |
| { |
| throw new IOException("Unexpected exception " + _); |
| } |
| } |
| |
| private byte getByteField (Object obj, Class klass, String field_name) |
| throws IOException |
| { |
| try |
| { |
| Field f = getField (klass, field_name); |
| byte b = f.getByte (obj); |
| return b; |
| } |
| catch (IllegalArgumentException _) |
| { |
| throw new InvalidClassException |
| ("invalid requested type for field " + field_name + " in class " + klass.getName()); |
| } |
| catch (IOException e) |
| { |
| throw e; |
| } |
| catch (Exception _) |
| { |
| throw new IOException("Unexpected exception " + _); |
| } |
| } |
| |
| private char getCharField (Object obj, Class klass, String field_name) |
| throws IOException |
| { |
| try |
| { |
| Field f = getField (klass, field_name); |
| char b = f.getChar (obj); |
| return b; |
| } |
| catch (IllegalArgumentException _) |
| { |
| throw new InvalidClassException |
| ("invalid requested type for field " + field_name + " in class " + klass.getName()); |
| } |
| catch (IOException e) |
| { |
| throw e; |
| } |
| catch (Exception _) |
| { |
| throw new IOException("Unexpected exception " + _); |
| } |
| } |
| |
| private double getDoubleField (Object obj, Class klass, String field_name) |
| throws IOException |
| { |
| try |
| { |
| Field f = getField (klass, field_name); |
| double b = f.getDouble (obj); |
| return b; |
| } |
| catch (IllegalArgumentException _) |
| { |
| throw new InvalidClassException |
| ("invalid requested type for field " + field_name + " in class " + klass.getName()); |
| } |
| catch (IOException e) |
| { |
| throw e; |
| } |
| catch (Exception _) |
| { |
| throw new IOException("Unexpected exception " + _); |
| } |
| } |
| |
| private float getFloatField (Object obj, Class klass, String field_name) |
| throws IOException |
| { |
| try |
| { |
| Field f = getField (klass, field_name); |
| float b = f.getFloat (obj); |
| return b; |
| } |
| catch (IllegalArgumentException _) |
| { |
| throw new InvalidClassException |
| ("invalid requested type for field " + field_name + " in class " + klass.getName()); |
| } |
| catch (IOException e) |
| { |
| throw e; |
| } |
| catch (Exception _) |
| { |
| throw new IOException("Unexpected exception " + _); |
| } |
| } |
| |
| private int getIntField (Object obj, Class klass, String field_name) |
| throws IOException |
| { |
| try |
| { |
| Field f = getField (klass, field_name); |
| int b = f.getInt (obj); |
| return b; |
| } |
| catch (IllegalArgumentException _) |
| { |
| throw new InvalidClassException |
| ("invalid requested type for field " + field_name + " in class " + klass.getName()); |
| } |
| catch (IOException e) |
| { |
| throw e; |
| } |
| catch (Exception _) |
| { |
| throw new IOException("Unexpected exception " + _); |
| } |
| } |
| |
| private long getLongField (Object obj, Class klass, String field_name) |
| throws IOException |
| { |
| try |
| { |
| Field f = getField (klass, field_name); |
| long b = f.getLong (obj); |
| return b; |
| } |
| catch (IllegalArgumentException _) |
| { |
| throw new InvalidClassException |
| ("invalid requested type for field " + field_name + " in class " + klass.getName()); |
| } |
| catch (IOException e) |
| { |
| throw e; |
| } |
| catch (Exception _) |
| { |
| throw new IOException("Unexpected exception " + _); |
| } |
| } |
| |
| private short getShortField (Object obj, Class klass, String field_name) |
| throws IOException |
| { |
| try |
| { |
| Field f = getField (klass, field_name); |
| short b = f.getShort (obj); |
| return b; |
| } |
| catch (IllegalArgumentException _) |
| { |
| throw new InvalidClassException |
| ("invalid requested type for field " + field_name + " in class " + klass.getName()); |
| } |
| catch (IOException e) |
| { |
| throw e; |
| } |
| catch (Exception _) |
| { |
| throw new IOException("Unexpected exception " + _); |
| } |
| } |
| |
| private Object getObjectField (Object obj, Class klass, String field_name, |
| String type_code) throws IOException |
| { |
| try |
| { |
| Field f = getField (klass, field_name); |
| ObjectStreamField of = new ObjectStreamField(f.getName(), f.getType()); |
| |
| /* if of is primitive something went wrong |
| * in the check for primitive classes in writeFields. |
| */ |
| if (of.isPrimitive()) |
| throw new InvalidClassException |
| ("invalid type code for " + field_name + " in class " + klass.getName() + " : object stream field is primitive"); |
| |
| if (!of.getTypeString().equals(type_code)) |
| throw new InvalidClassException |
| ("invalid type code for " + field_name + " in class " + klass.getName() + " : object stream field " + of + " has type string " + of.getTypeString() + " instead of " + type_code); |
| |
| Object o = f.get (obj); |
| // FIXME: We should check the type_code here |
| return o; |
| } |
| catch (IOException e) |
| { |
| throw e; |
| } |
| catch (Exception e) |
| { |
| throw new IOException (); |
| } |
| } |
| |
| private Field getField (Class klass, String name) |
| throws java.io.InvalidClassException |
| { |
| try |
| { |
| final Field f = klass.getDeclaredField(name); |
| setAccessible.setMember(f); |
| AccessController.doPrivileged(setAccessible); |
| return f; |
| } |
| catch (java.lang.NoSuchFieldException e) |
| { |
| throw new InvalidClassException |
| ("no field called " + name + " in class " + klass.getName()); |
| } |
| } |
| |
| private void dumpElementln (String msg) |
| { |
| for (int i = 0; i < depth; i++) |
| System.out.print (" "); |
| System.out.print (Thread.currentThread() + ": "); |
| System.out.println(msg); |
| } |
| |
| // this value comes from 1.2 spec, but is used in 1.1 as well |
| private static final int BUFFER_SIZE = 1024; |
| |
| private static int defaultProtocolVersion = PROTOCOL_VERSION_2; |
| |
| private DataOutputStream dataOutput; |
| private boolean writeDataAsBlocks; |
| private DataOutputStream realOutput; |
| private DataOutputStream blockDataOutput; |
| private byte[] blockData; |
| private int blockDataCount; |
| private Object currentObject; |
| // Package-private to avoid a trampoline. |
| ObjectStreamClass currentObjectStreamClass; |
| private PutField currentPutField; |
| private boolean fieldsAlreadyWritten; |
| private boolean replacementEnabled; |
| private boolean isSerializing; |
| private int nextOID; |
| private Hashtable OIDLookupTable; |
| private int protocolVersion; |
| private boolean useSubclassMethod; |
| private SetAccessibleAction setAccessible = new SetAccessibleAction(); |
| |
| // The nesting depth for debugging output |
| private int depth = 0; |
| |
| // Set if we're generating debugging dumps |
| private boolean dump = false; |
| |
| private static final boolean DEBUG = false; |
| } |