| /* 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; | |
| } |