| /* ObjectOutputStream.java -- Class used to write serialized objects | |
| Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2008 | |
| 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.ObjectIdentityMap2Int; | |
| 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.security.PrivilegedAction; | |
| /** | |
| * 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 | |
| * @author Tom Tromey (tromey@redhat.com) | |
| * @author Jeroen Frijters (jeroen@frijters.net) | |
| * @author Guilhem Lavaux (guilhem@kaffe.org) | |
| * @author Michael Koch (konqueror@gmx.de) | |
| * @author Andrew John Hughes (gnu_andrew@member.fsf.org) | |
| */ | |
| 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 | |
| { | |
| SecurityManager secMan = System.getSecurityManager(); | |
| if (secMan != null && overridesMethods(getClass())) | |
| secMan.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION); | |
| 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 ObjectIdentityMap2Int(); | |
| 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. | |
| * | |
| * @param obj the object to serialize. | |
| * @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>. | |
| * @see #writeUnshared(Object) | |
| */ | |
| public final void writeObject(Object obj) throws IOException | |
| { | |
| writeObject(obj, true); | |
| } | |
| /** | |
| * Writes an object to the stream in the same manner as | |
| * {@link #writeObject(Object)}, but without the use of | |
| * references. As a result, the object is always written | |
| * to the stream in full. Likewise, if an object is written | |
| * by this method and is then later written again by | |
| * {@link #writeObject(Object)}, both calls will write out | |
| * the object in full, as the later call to | |
| * {@link #writeObject(Object)} will know nothing of the | |
| * earlier use of {@link #writeUnshared(Object)}. | |
| * | |
| * @param obj the object to serialize. | |
| * @throws NotSerializableException if the object being | |
| * serialized does not implement | |
| * {@link Serializable}. | |
| * @throws InvalidClassException if a problem occurs with | |
| * the class of the object being | |
| * serialized. | |
| * @throws IOException if an I/O error occurs on the underlying | |
| * <code>OutputStream</code>. | |
| * @since 1.4 | |
| * @see #writeObject(Object) | |
| */ | |
| public void writeUnshared(Object obj) | |
| throws IOException | |
| { | |
| writeObject(obj, false); | |
| } | |
| /** | |
| * 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. | |
| * | |
| * @param obj the object to serialize. | |
| * @param shared true if the serialized object should be | |
| * shared with later calls. | |
| * @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>. | |
| * @see #writeUnshared(Object) | |
| */ | |
| private final void writeObject(Object obj, boolean shared) | |
| 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; | |
| } | |
| int handle = findHandle(obj); | |
| if (handle >= 0 && shared) | |
| { | |
| realOutput.writeByte(TC_REFERENCE); | |
| realOutput.writeInt(handle); | |
| break; | |
| } | |
| if (obj instanceof Class) | |
| { | |
| Class cl = (Class)obj; | |
| ObjectStreamClass osc = ObjectStreamClass.lookupForClassObject(cl); | |
| realOutput.writeByte(TC_CLASS); | |
| if (!osc.isProxyClass) | |
| { | |
| writeObject (osc); | |
| } | |
| else | |
| {System.err.println("1"); | |
| 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()); | |
| } | |
| if (shared) | |
| 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); | |
| if (shared) | |
| 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) | |
| { | |
| String s = (String)obj; | |
| long l = realOutput.getUTFlength(s, 0, 0); | |
| if (l <= 65535) | |
| { | |
| realOutput.writeByte(TC_STRING); | |
| if (shared) | |
| assignNewHandle(obj); | |
| realOutput.writeUTFShort(s, (int)l); | |
| } | |
| else | |
| { | |
| realOutput.writeByte(TC_LONGSTRING); | |
| if (shared) | |
| assignNewHandle(obj); | |
| realOutput.writeUTFLong(s, l); | |
| } | |
| break; | |
| } | |
| if (clazz.isArray ()) | |
| { | |
| realOutput.writeByte(TC_ARRAY); | |
| writeObject(osc); | |
| if (shared) | |
| assignNewHandle(obj); | |
| writeArraySizeAndElements(obj, clazz.getComponentType()); | |
| break; | |
| } | |
| realOutput.writeByte(TC_OBJECT); | |
| writeObject(osc); | |
| if (shared) | |
| 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 = osc.hierarchy(); | |
| 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 int findHandle(Object obj) | |
| { | |
| return OIDLookupTable.get(obj); | |
| } | |
| // assigns the next availible handle to OBJ | |
| private int assignNewHandle(Object obj) | |
| { | |
| OIDLookupTable.put(obj, 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]); | |
| } | |
| } | |
| /* GCJ LOCAL */ | |
| // writes out FIELDS of OBJECT for the specified ObjectStreamClass. | |
| // FIELDS are already supposed already to be in canonical order, but | |
| // under some circumstances (to do with Proxies) this isn't the | |
| // case, so we call ensureFieldsSet(). | |
| private void writeFields(Object obj, ObjectStreamClass osc) | |
| throws IOException | |
| { | |
| osc.ensureFieldsSet(osc.forClass()); | |
| /* END GCJ LOCAL */ | |
| ObjectStreamField[] fields = osc.fields; | |
| boolean oldmode = setBlockDataMode(false); | |
| try | |
| { | |
| writeFields(obj,fields); | |
| } | |
| catch (IllegalArgumentException _) | |
| { | |
| InvalidClassException e = new InvalidClassException | |
| ("writing fields of class " + osc.forClass().getName()); | |
| e.initCause(_); | |
| throw e; | |
| } | |
| catch (IOException e) | |
| { | |
| throw e; | |
| } | |
| catch (Exception _) | |
| { | |
| IOException e = new IOException("Unexpected exception " + _); | |
| e.initCause(_); | |
| throw(e); | |
| } | |
| setBlockDataMode(oldmode); | |
| } | |
| /** | |
| * Helper function for writeFields(Object,ObjectStreamClass): write | |
| * fields from given fields array. Pass exception on. | |
| * | |
| * @param obj the object to be written | |
| * | |
| * @param fields the fields of obj to be written. | |
| */ | |
| private void writeFields(Object obj, ObjectStreamField[] fields) | |
| throws | |
| IllegalArgumentException, IllegalAccessException, IOException | |
| { | |
| for (int i = 0; i < fields.length; i++) | |
| { | |
| ObjectStreamField osf = fields[i]; | |
| Field field = osf.field; | |
| if (DEBUG && dump) | |
| dumpElementln ("WRITE FIELD: " + osf.getName() + " type=" + osf.getType()); | |
| switch (osf.getTypeCode()) | |
| { | |
| case 'Z': realOutput.writeBoolean(field.getBoolean(obj)); break; | |
| case 'B': realOutput.writeByte (field.getByte (obj)); break; | |
| case 'S': realOutput.writeShort (field.getShort (obj)); break; | |
| case 'C': realOutput.writeChar (field.getChar (obj)); break; | |
| case 'I': realOutput.writeInt (field.getInt (obj)); break; | |
| case 'F': realOutput.writeFloat (field.getFloat (obj)); break; | |
| case 'J': realOutput.writeLong (field.getLong (obj)); break; | |
| case 'D': realOutput.writeDouble (field.getDouble (obj)); break; | |
| case 'L': | |
| case '[': writeObject (field.get (obj)); break; | |
| default: | |
| throw new IOException("Unexpected type code " + osf.getTypeCode()); | |
| } | |
| } | |
| } | |
| // 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 void dumpElementln (String msg, Object obj) | |
| { | |
| try | |
| { | |
| for (int i = 0; i < depth; i++) | |
| System.out.print (" "); | |
| System.out.print (Thread.currentThread() + ": "); | |
| System.out.print (msg); | |
| if (java.lang.reflect.Proxy.isProxyClass(obj.getClass())) | |
| System.out.print (obj.getClass()); | |
| else | |
| System.out.print (obj); | |
| } | |
| catch (Exception _) | |
| { | |
| } | |
| finally | |
| { | |
| System.out.println (); | |
| } | |
| } | |
| 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 ObjectIdentityMap2Int 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; | |
| /** | |
| * Returns true if the given class overrides either of the | |
| * methods <code>putFields</code> or <code>writeUnshared</code>. | |
| * | |
| * @param clazz the class to check. | |
| * @return true if the class overrides one of the methods. | |
| */ | |
| private static boolean overridesMethods(final Class<?> clazz) | |
| { | |
| if (clazz == ObjectOutputStream.class) | |
| return false; | |
| return AccessController.doPrivileged(new PrivilegedAction<Boolean>() { | |
| public Boolean run() | |
| { | |
| Method[] methods = clazz.getDeclaredMethods(); | |
| for (int a = 0; a < methods.length; ++a) | |
| { | |
| String name = methods[a].getName(); | |
| if (name.equals("writeUnshared")) | |
| { | |
| Class<?>[] paramTypes = methods[a].getParameterTypes(); | |
| if (paramTypes.length == 1 && | |
| paramTypes[0] == Object.class && | |
| methods[a].getReturnType() == Void.class) | |
| return true; | |
| } | |
| else if (name.equals("putFields")) | |
| { | |
| if (methods[a].getParameterTypes().length == 0 && | |
| methods[a].getReturnType() == PutField.class) | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| }); | |
| } | |
| } |