/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package org.apache.harmony.tests.java.io;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InvalidObjectException;
import java.io.NotActiveException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamClass;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.io.StreamCorruptedException;
import java.io.WriteAbortedException;
import java.security.Permission;
import java.security.PermissionCollection;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.IdentityHashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.PropertyPermission;
import java.util.Set;
import java.util.SimpleTimeZone;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TimeZone;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.Vector;
import libcore.io.Streams;

/**
 * Automated Test Suite for class java.io.ObjectOutputStream
 */
@SuppressWarnings("serial")
public class SerializationStressTest extends junit.framework.TestCase implements
        Serializable {

    // protected static final String MODE_XLOAD = "xload";

    // protected static final String MODE_XDUMP = "xdump";

    static final String FOO = "foo";

    static final String MSG_TEST_FAILED = "Failed to write/read/assertion checking: ";

    protected static final boolean DEBUG = false;

    protected static boolean xload = false;

    protected static boolean xdump = false;

    protected static String xFileName = null;

    protected transient int dumpCount = 0;

    protected transient ObjectInputStream ois;

    protected transient ObjectOutputStream oos;

    protected transient ByteArrayOutputStream bao;

    // -----------------------------------------------------------------------------------

    private static class ObjectInputStreamSubclass extends ObjectInputStream {
        private Vector<Class> resolvedClasses = new Vector<Class>();

        public ObjectInputStreamSubclass(InputStream in) throws IOException,
                StreamCorruptedException {
            super(in);
        }

        public Class<?> resolveClass(ObjectStreamClass osClass)
                throws IOException, ClassNotFoundException {
            Class result = super.resolveClass(osClass);
            resolvedClasses.addElement(result);
            return result;
        }

        public Class[] resolvedClasses() {
            return (Class[]) resolvedClasses.toArray(new Class[resolvedClasses
                    .size()]);
        }
    }

    static final Map<String, String> TABLE = new Hashtable<String, String>();

    static final Map<String, String> MAP = new HashMap<String, String>();

    static final SortedMap<String, String> TREE = new TreeMap<String, String>();

    static final LinkedHashMap<String, String> LINKEDMAP = new LinkedHashMap<String, String>();

    static final LinkedHashSet<String> LINKEDSET = new LinkedHashSet<String>();

    static final IdentityHashMap<String, String> IDENTITYMAP = new IdentityHashMap<String, String>();

    static final List<String> ALIST = Arrays.asList(new String[] { "a", "list", "of",
            "strings" });

    static final List<String> LIST = new ArrayList<String>(ALIST);

    static final Set<String> SET = new HashSet<String>(Arrays.asList(new String[] { "one",
            "two", "three" }));

    static final SortedSet<String> SORTSET = new TreeSet<String>(Arrays.asList(new String[] {
            "one", "two", "three" }));

    static final java.text.DateFormat DATEFORM = java.text.DateFormat
            .getInstance();

    static final java.text.ChoiceFormat CHOICE = new java.text.ChoiceFormat(
            "1#one|2#two|3#three");

    static final java.text.NumberFormat NUMBERFORM = java.text.NumberFormat
            .getInstance();

    static final java.text.MessageFormat MESSAGE = new java.text.MessageFormat(
            "the time: {0,time} and date {0,date}");

    static final LinkedList<String> LINKEDLIST = new LinkedList<String>(Arrays
            .asList(new String[] { "a", "linked", "list", "of", "strings" }));

    static final SimpleTimeZone TIME_ZONE = new SimpleTimeZone(3600000,
            "S-TEST");

    static final Calendar CALENDAR = new GregorianCalendar(TIME_ZONE);

    static {
        TABLE.put("one", "1");
        TABLE.put("two", "2");
        TABLE.put("three", "3");
        MAP.put("one", "1");
        MAP.put("two", "2");
        MAP.put("three", "3");
        LINKEDMAP.put("one", "1");
        LINKEDMAP.put("two", "2");
        LINKEDMAP.put("three", "3");
        IDENTITYMAP.put("one", "1");
        IDENTITYMAP.put("two", "2");
        IDENTITYMAP.put("three", "3");
        LINKEDSET.add("one");
        LINKEDSET.add("two");
        LINKEDSET.add("three");
        TREE.put("one", "1");
        TREE.put("two", "2");
        TREE.put("three", "3");
        // To make sure they all use the same Calendar
        CALENDAR.setTimeZone(new SimpleTimeZone(0, "GMT"));
        CALENDAR.set(1999, Calendar.JUNE, 23, 15, 47, 13);
        CALENDAR.set(Calendar.MILLISECOND, 553);
        DATEFORM.setCalendar(CALENDAR);
        java.text.DateFormatSymbols symbols = new java.text.DateFormatSymbols();
        symbols.setZoneStrings(new String[][] { { "a", "b", "c", "d", "e" },
                { "f", "g", "h", "i", "j" } });
        ((java.text.SimpleDateFormat) DATEFORM).setDateFormatSymbols(symbols);
        DATEFORM.setNumberFormat(new java.text.DecimalFormat("#0.#"));
        DATEFORM.setTimeZone(TimeZone.getTimeZone("EST"));
        ((java.text.DecimalFormat) NUMBERFORM).applyPattern("#0.#");
        MESSAGE.setFormat(0, DATEFORM);
        MESSAGE.setFormat(1, DATEFORM);
    }

    public SerializationStressTest() {
    }

    public SerializationStressTest(String name) {
        super(name);
    }

    public String getDumpName() {
        return getName() + dumpCount;
    }

    protected void dump(Object o) throws IOException, ClassNotFoundException {
        if (dumpCount > 0)
            setUp();
        // Dump the object
        try {
            oos.writeObject(o);
        } finally {
            oos.close();
        }
    }

    protected Object dumpAndReload(Object o) throws IOException,
            ClassNotFoundException {
        dump(o);
        return reload();
    }

    protected InputStream loadStream() throws IOException {
        // Choose the load stream
        if (xload || xdump) {
            // Load from pre-existing file
            return new FileInputStream(xFileName + "-" + getDumpName() + ".ser");
        } else {
            // Just load from memory, we dumped to memory
            return new ByteArrayInputStream(bao.toByteArray());
        }
    }

    protected Object reload() throws IOException, ClassNotFoundException {
        ois = new ObjectInputStream(loadStream());
        dumpCount++;
        try {
            return ois.readObject();
        } finally {
            ois.close();
        }
    }

    /**
     * Sets up the fixture, for example, open a network connection. This method
     * is called before a test is executed.
     */
    protected void setUp() {
        try {
            if (xdump) {
                oos = new ObjectOutputStream(new FileOutputStream(xFileName
                        + "-" + getDumpName() + ".ser"));
            } else {
                oos = new ObjectOutputStream(bao = new ByteArrayOutputStream());
            }
        } catch (Exception e) {
            fail("Exception thrown during setup : " + e.getMessage());
        }
    }

    /**
     * Tears down the fixture, for example, close a network connection. This
     * method is called after a test is executed.
     */
    protected void tearDown() {
        if (oos != null) {
            try {
                oos.close();
            } catch (Exception e) {
            }
        }
    }

    public void test_1_Constructor() throws Exception {
        // Test for method java.io.ObjectOutputStream(java.io.OutputStream)
        oos.close();
        oos = new ObjectOutputStream(new ByteArrayOutputStream());
        oos.close();
    }

    public void test_2_close() {
        // Test for method void java.io.ObjectOutputStream.close()
        try {
            oos.close();
            oos = new ObjectOutputStream(bao = new ByteArrayOutputStream());
            oos.close();
            oos.writeChar('T');
            oos.writeObject(FOO);
            // Writing to a closed stream does not cause problems. This is
            // the expected behavior
        } catch (IOException e) {
            fail("Operation on closed stream threw IOException : "
                    + e.getMessage());
        }
    }

    public void test_3_defaultWriteObject() {
        // Test for method void java.io.ObjectOutputStream.defaultWriteObject()

        try {
            oos.defaultWriteObject();
        } catch (NotActiveException e) {
            // Correct
            return;
        } catch (IOException e) {
        }
        fail(
                "Failed to throw NotActiveException when invoked outside readObject");
    }

    public void test_4_flush() {
        // Test for method void java.io.ObjectOutputStream.flush()
        try {
            oos.close();
            oos = new ObjectOutputStream(bao = new ByteArrayOutputStream());
            int size = bao.size();
            oos.writeByte(127);
            assertTrue("Data flushed already", bao.size() == size);
            oos.flush();
            assertTrue("Failed to flush data", bao.size() > size);
            // we don't know how many bytes are actually written for 1 byte,
            // so we test > <before>
            oos.close();
            oos = null;
        } catch (IOException e) {
            fail("IOException serializing data : " + e.getMessage());
        }
    }

    public void test_5_reset() {
        // Test for method void java.io.ObjectOutputStream.reset()
        try {
            String o = "HelloWorld";
            oos.writeObject(o);
            oos.writeObject(o);
            oos.reset();
            oos.writeObject(o);
            ois = new ObjectInputStream(loadStream());
            ois.close();
        } catch (IOException e) {
            fail("IOException serializing data : " + e.getMessage());
        }
    }

    public void test_6_write() {
        // Test for method void java.io.ObjectOutputStream.write(byte [], int,
        // int)
        try {
            byte[] buf = new byte[255];
            byte[] output = new byte[255];
            for (int i = 0; i < output.length; i++)
                output[i] = (byte) i;
            oos.write(output, 0, output.length);
            oos.close();
            ois = new ObjectInputStream(loadStream());
            ois.readFully(buf);
            ois.close();
            for (int i = 0; i < output.length; i++)
                if (buf[i] != output[i])
                    fail("Read incorrect byte: " + i);
        } catch (IOException e) {
            fail("IOException serializing data : " + e.getMessage());
        }
    }

    public void test_6a_write() {
        // Test for method void java.io.ObjectOutputStream.write(byte [], int,
        // int)
        try {
            byte[] buf = new byte[256];
            byte[] output = new byte[256];
            for (int i = 0; i < output.length; i++)
                output[i] = (byte) (i & 0xff);
            oos.write(output, 0, output.length);
            oos.close();
            ois = new ObjectInputStream(loadStream());
            ois.readFully(buf);
            ois.close();
            for (int i = 0; i < output.length; i++)
                if (buf[i] != output[i])
                    fail("Read incorrect byte: " + i);
        } catch (IOException e) {
            fail("IOException serializing data : " + e.getMessage());
        }
    }

    public void test_7_write() {
        // Test for method void java.io.ObjectOutputStream.write(int)
        try {
            oos.write('T');
            oos.close();
            ois = new ObjectInputStream(loadStream());
            assertEquals("Read incorrect byte", 'T', ois.read());
            ois.close();
        } catch (IOException e) {
            fail("IOException serializing data : " + e.getMessage());
        }
    }

    public void test_8_write() {
        // Test for method void java.io.ObjectOutputStream.write(byte [])
        try {
            byte[] buf = new byte[10];
            oos.write("HelloWorld".getBytes());
            oos.close();
            ois = new ObjectInputStream(loadStream());
            ois.read(buf, 0, 10);
            ois.close();
            assertEquals("Read incorrect bytes", "HelloWorld", new String(buf, 0, 10)
            );
        } catch (IOException e) {
            fail("IOException serializing data : " + e.getMessage());
        }
    }

    public void test_9_writeBoolean() {
        // Test for method void java.io.ObjectOutputStream.writeBoolean(boolean)
        try {
            oos.writeBoolean(true);
            oos.close();
            ois = new ObjectInputStream(loadStream());
            assertTrue("Wrote incorrect byte value", ois.readBoolean());
        } catch (IOException e) {
            fail("IOException serializing data : " + e.getMessage());
        }
    }

    public void test_10_writeByte() {
        // Test for method void java.io.ObjectOutputStream.writeByte(int)
        try {
            oos.writeByte(127);
            oos.close();
            ois = new ObjectInputStream(loadStream());
            assertEquals("Wrote incorrect byte value", 127, ois.readByte());
        } catch (IOException e) {
            fail("IOException serializing data : " + e.getMessage());
        }
    }

    public void test_11_writeBytes() {
        // Test for method void
        // java.io.ObjectOutputStream.writeBytes(java.lang.String)
        try {
            byte[] buf = new byte[10];
            oos.writeBytes("HelloWorld");
            oos.close();
            ois = new ObjectInputStream(loadStream());
            ois.readFully(buf);
            ois.close();
            assertEquals("Wrote incorrect bytes value", "HelloWorld", new String(buf, 0, 10, "UTF-8")
            );
        } catch (IOException e) {
            fail("IOException serializing data : " + e.getMessage());
        }
    }

    public void test_12_writeChar() {
        // Test for method void java.io.ObjectOutputStream.writeChar(int)
        try {
            oos.writeChar('T');
            oos.close();
            ois = new ObjectInputStream(loadStream());
            assertEquals("Wrote incorrect char value", 'T', ois.readChar());
        } catch (IOException e) {
            fail("IOException serializing data : " + e.getMessage());
        }
    }

    public void test_13_writeChars() {
        // Test for method void
        // java.io.ObjectOutputStream.writeChars(java.lang.String)
        try {
            int avail = 0;
            char[] buf = new char[10];
            oos.writeChars("HelloWorld");
            oos.close();
            ois = new ObjectInputStream(loadStream());
            // Number of prim data bytes in stream / 2 to give char index
            avail = ois.available() / 2;
            for (int i = 0; i < avail; ++i)
                buf[i] = ois.readChar();
            ois.close();
            assertEquals("Wrote incorrect chars", "HelloWorld", new String(buf, 0, 10)
            );
        } catch (IOException e) {
            fail("IOException serializing data : " + e.getMessage());
        }
    }

    public void test_14_writeDouble() {
        // Test for method void java.io.ObjectOutputStream.writeDouble(double)
        try {
            oos.writeDouble(Double.MAX_VALUE);
            oos.close();
            ois = new ObjectInputStream(loadStream());
            assertTrue("Wrote incorrect double value",
                    ois.readDouble() == Double.MAX_VALUE);
        } catch (IOException e) {
            fail("IOException serializing data : " + e.getMessage());
        }
    }

    public void test_15_writeFloat() {
        // Test for method void java.io.ObjectOutputStream.writeFloat(float)
        try {
            oos.writeFloat(Float.MAX_VALUE);
            oos.close();
            ois = new ObjectInputStream(loadStream());
            assertTrue("Wrote incorrect double value",
                    ois.readFloat() == Float.MAX_VALUE);
            ois.close();
            ois = null;
        } catch (IOException e) {
            fail("IOException serializing data : " + e.getMessage());
        }
    }

    public void test_16_writeInt() {
        // Test for method void java.io.ObjectOutputStream.writeInt(int)
        try {
            oos.writeInt(Integer.MAX_VALUE);
            oos.close();
            ois = new ObjectInputStream(loadStream());
            assertTrue("Wrote incorrect double value",
                    ois.readInt() == Integer.MAX_VALUE);
            ois.close();
        } catch (IOException e) {
            fail("IOException serializing data : " + e.getMessage());
        }
    }

    public void test_17_writeLong() {
        // Test for method void java.io.ObjectOutputStream.writeLong(long)
        try {
            oos.writeLong(Long.MAX_VALUE);
            oos.close();
            ois = new ObjectInputStream(loadStream());
            assertTrue("Wrote incorrect double value",
                    ois.readLong() == Long.MAX_VALUE);
        } catch (IOException e) {
            fail("IOException serializing data : " + e.getMessage());
        }
    }

    public void test_19_writeShort() {
        // Test for method void java.io.ObjectOutputStream.writeShort(int)
        try {
            oos.writeShort(127);
            oos.close();
            ois = new ObjectInputStream(loadStream());
            assertEquals("Wrote incorrect short value", 127, ois.readShort());
        } catch (IOException e) {
            fail("IOException serializing data : " + e.getMessage());
        }
    }

    public void test_20_writeUTF() {
        // Test for method void
        // java.io.ObjectOutputStream.writeUTF(java.lang.String)
        try {
            oos.writeUTF("HelloWorld");
            oos.close();
            ois = new ObjectInputStream(loadStream());
            assertEquals("Wrote incorrect UTF value",
                    "HelloWorld", ois.readUTF());
        } catch (IOException e) {
            fail("IOException serializing data : " + e.getMessage());
        }
    }

    public void test_25_available() {
        try {
            oos.writeObject(FOO);
            oos.writeObject(FOO);
            oos.flush();
            int available1 = 0;
            int available2 = 0;
            Object obj1 = null;
            Object obj2 = null;
            ObjectInputStream ois = new ObjectInputStream(loadStream());
            available1 = ois.available();
            obj1 = ois.readObject();
            available2 = ois.available();
            obj2 = ois.readObject();

            assertEquals("available returned incorrect value", 0, available1);
            assertEquals("available returned incorrect value", 0, available2);

            assertTrue("available caused incorrect reading", FOO.equals(obj1));
            assertTrue("available returned incorrect value", FOO.equals(obj2));

        } catch (IOException e) {
            fail("IOException serializing object : " + e.getMessage());
        } catch (ClassNotFoundException e) {
            fail("Unable to read Object type : " + e.toString());
        } catch (Error err) {
            System.out.println("Error " + err);
            throw err;
        }

    }

    protected void t_MixPrimitivesAndObjects() throws IOException,
            ClassNotFoundException {
        int i = 7;
        String s1 = "string 1";
        String s2 = "string 2";
        byte[] bytes = { 1, 2, 3 };

        oos.writeInt(i);
        oos.writeObject(s1);
        oos.writeUTF(s2);
        oos.writeObject(bytes);
        oos.close();
        try {
            ois = new ObjectInputStream(loadStream());

            int j = ois.readInt();
            assertTrue("Wrong int :" + j, i == j);

            String l1 = (String) ois.readObject();
            assertTrue("Wrong obj String :" + l1, s1.equals(l1));

            String l2 = (String) ois.readUTF();
            assertTrue("Wrong UTF String :" + l2, s2.equals(l2));

            byte[] bytes2 = (byte[]) ois.readObject();
            assertTrue("Wrong byte[]", Arrays.equals(bytes, bytes2));

        } finally {
            ois.close();
        }
    }

    public void test_resolveClass() {
        try {
            oos.writeObject(new Object[] { Integer.class, new Integer(1) });
            oos.close();

            ois = new ObjectInputStreamSubclass(loadStream());
            ois.readObject();
            ois.close();
        } catch (IOException e1) {
            fail("IOException : " + e1.getMessage());
        } catch (ClassNotFoundException e2) {
            fail("ClassNotFoundException : " + e2.getMessage());
        }

        Class[] resolvedClasses = ((ObjectInputStreamSubclass) ois)
                .resolvedClasses();
        assertEquals("missing resolved", 3, resolvedClasses.length);
        assertTrue("resolved class 1", resolvedClasses[0] == Object[].class);
        assertTrue("resolved class 2", resolvedClasses[1] == Integer.class);
        assertTrue("resolved class 3", resolvedClasses[2] == Number.class);
    }

    public void test_reset() throws IOException, ClassNotFoundException {
        oos.reset();
        oos.writeObject("R");
        oos.reset();
        oos.writeByte(24);
        oos.close();

        DataInputStream dis = new DataInputStream(loadStream());
        byte[] input = Streams.readFully(dis);
        byte[] result = new byte[] { (byte) 0xac, (byte) 0xed, (byte) 0,
                (byte) 5, (byte) 0x79, (byte) 0x74, (byte) 0, (byte) 1,
                (byte) 'R', (byte) 0x79, (byte) 0x77, (byte) 1, (byte) 24 };
        assertTrue("incorrect output", Arrays.equals(input, result));

        ois = new ObjectInputStreamSubclass(loadStream());
        assertEquals("Wrong result from readObject()", "R", ois.readObject());
        assertEquals("Wrong result from readByte()", 24, ois.readByte());
        ois.close();
    }

    private static class ResolveObjectTest implements Serializable {
        Object field1, field2;
    }

    private static class ResolveObjectInputStream extends ObjectInputStream {
        ResolveObjectInputStream(InputStream in)
                throws StreamCorruptedException, IOException {
            super(in);
        }

        public void enableResolve() {
            enableResolveObject(true);
        }

        public Object resolveObject(Object obj) {
            if (obj instanceof Vector) // test_1_resolveObject()
                return new Hashtable();
            else if ("abc".equals(obj)) // test_2_resolveObject()
                return "ABC";
            else if (obj instanceof String) // test_3_resolveObject()
                return String.valueOf(((String) obj).length());
            else if (obj instanceof int[]) // test_4_resolveObject()
                return new Object[1];
            else if (obj instanceof Object[] && ((Object[]) obj).length == 2) // test_5_resolveObject()
                return new char[1];
            return obj;
        }
    }

    public void test_1_resolveObject() {
        try {
            ResolveObjectTest obj = new ResolveObjectTest();
            obj.field1 = new Vector();
            obj.field2 = obj.field1;
            oos.writeObject(obj);
            oos.close();
            ois = new ResolveObjectInputStream(loadStream());
            ((ResolveObjectInputStream) ois).enableResolve();
            ResolveObjectTest result = null;
            try {
                result = (ResolveObjectTest) ois.readObject();
            } catch (ClassNotFoundException e) {
                fail(e.toString());
            }
            assertTrue("Object not resolved",
                    result.field1 instanceof Hashtable);
            assertTrue("Second reference not resolved",
                    result.field1 == result.field2);
        } catch (IOException e) {
            fail("IOException serializing data : " + e.getMessage());
        }
    }

    public void test_2_resolveObject() {
        try {
            ResolveObjectTest obj = new ResolveObjectTest();
            obj.field1 = "abc";
            obj.field2 = obj.field1;
            oos.writeObject(obj);
            oos.close();
            ois = new ResolveObjectInputStream(loadStream());
            ((ResolveObjectInputStream) ois).enableResolve();
            ResolveObjectTest result = null;
            try {
                result = (ResolveObjectTest) ois.readObject();
            } catch (ClassNotFoundException e) {
                fail(e.toString());
            }
            assertEquals("String not resolved", "ABC", result.field1);
            assertTrue("Second reference not resolved",
                    result.field1 == result.field2);
        } catch (IOException e) {
            fail("IOException serializing data : " + e.getMessage());
        }
    }

    public void test_3_resolveObject() {
        try {
            ResolveObjectTest obj = new ResolveObjectTest();
            char[] lchars = new char[70000];
            obj.field1 = new String(lchars);
            obj.field2 = obj.field1;
            oos.writeObject(obj);
            oos.close();
            ois = new ResolveObjectInputStream(loadStream());
            ((ResolveObjectInputStream) ois).enableResolve();
            ResolveObjectTest result = null;
            try {
                result = (ResolveObjectTest) ois.readObject();
            } catch (ClassNotFoundException e) {
                fail(e.toString());
            }
            assertTrue("Long String not resolved", "70000"
                    .equals(result.field1));
            assertTrue("Second reference not resolved",
                    result.field1 == result.field2);
        } catch (IOException e) {
            fail("IOException serializing data : " + e.getMessage());
        }
    }

    public void test_4_resolveObject() {
        try {
            ResolveObjectTest obj = new ResolveObjectTest();
            obj.field1 = new int[5];
            obj.field2 = obj.field1;
            oos.writeObject(obj);
            oos.close();
            ois = new ResolveObjectInputStream(loadStream());
            ((ResolveObjectInputStream) ois).enableResolve();
            ResolveObjectTest result = null;
            try {
                result = (ResolveObjectTest) ois.readObject();
            } catch (ClassNotFoundException e) {
                fail(e.toString());
            }
            Class cl = new Object[0].getClass();
            assertTrue("int[] not resolved", result.field1.getClass() == cl);
            assertTrue("Second reference not resolved",
                    result.field1 == result.field2);
        } catch (IOException e) {
            fail("IOException serializing data : " + e.getMessage());
        }
    }

    public void test_5_resolveObject() {
        try {
            ResolveObjectTest obj = new ResolveObjectTest();
            obj.field1 = new Object[2];
            obj.field2 = obj.field1;
            oos.writeObject(obj);
            oos.close();
            ois = new ResolveObjectInputStream(loadStream());
            ((ResolveObjectInputStream) ois).enableResolve();
            ResolveObjectTest result = null;
            try {
                result = (ResolveObjectTest) ois.readObject();
            } catch (ClassNotFoundException e) {
                fail(e.toString());
            }
            Class cl = new char[0].getClass();
            assertTrue("int[] not resolved", result.field1.getClass() == cl);
            assertTrue("Second reference not resolved",
                    result.field1 == result.field2);
        } catch (IOException e) {
            fail("IOException serializing data : " + e.getMessage());
        }
    }

    static class WriteReplaceTestA implements Serializable {
        public Object writeReplace() throws ObjectStreamException {
            return new ReadResolveTestB();
        }
    }

    static class WriteReplaceTestB extends WriteReplaceTestA {
    }

    static class WriteReplaceTestC extends WriteReplaceTestA {
        public Object writeReplace() throws ObjectStreamException {
            return new ReadResolveTestC();
        }
    }

    static class WriteReplaceTestD implements Serializable {
        private Object writeReplace() throws ObjectStreamException {
            return new ReadResolveTestD();
        }
    }

    static class WriteReplaceTestE extends WriteReplaceTestD {
    }

    static class WriteReplaceTestF implements Serializable {
        int type, readType;

        public WriteReplaceTestF(int type, int readType) {
            this.type = type;
            this.readType = readType;
        }

        public Object writeReplace() throws ObjectStreamException {
            switch (type) {
                case 0:
                    throw new InvalidObjectException("invalid");
                case 1:
                    throw new RuntimeException("runtime");
                case 2:
                    throw new Error("error");
                default:
                    return new ReadResolveTestE(readType);
            }
        }
    }

    static class ReadResolveTestA implements Serializable {
        public Object readResolve() throws ObjectStreamException {
            return new ReadResolveTestA();
        }
    }

    static class ReadResolveTestB extends ReadResolveTestA {
    }

    static class ReadResolveTestC implements Serializable {
        private Object readResolve() throws ObjectStreamException {
            return new ReadResolveTestB();
        }
    }

    static class ReadResolveTestD extends ReadResolveTestC {
    }

    static class ReadResolveTestE implements Serializable {
        int type;

        public ReadResolveTestE(int type) {
            this.type = type;
        }

        public Object readResolve() throws ObjectStreamException {
            switch (type) {
                case 0:
                    throw new InvalidObjectException("invalid");
                case 1:
                    throw new RuntimeException("runtime");
                case 2:
                    throw new Error("error");
                case 3:
                    return this;
                default:
                    return new ReadResolveTestF();
            }
        }
    }

    static class ReadResolveTestF implements Serializable {
    }

    public void test_1_writeReplace() {
        try {
            Vector<Object> v = new Vector<Object>();
            v.addElement(new WriteReplaceTestA());
            v.addElement(new WriteReplaceTestB());
            v.addElement(new WriteReplaceTestB());
            v.addElement(new WriteReplaceTestC());
            v.addElement(new WriteReplaceTestD());
            v.addElement(new WriteReplaceTestE());
            oos.writeObject(v);
            oos.close();
            ois = new ObjectInputStream(loadStream());
            Vector result = (Vector) ois.readObject();
            assertTrue("invalid 0 : " + result.elementAt(0), result
                    .elementAt(0).getClass() == ReadResolveTestA.class);
            assertTrue("invalid 1 : " + result.elementAt(1), result
                    .elementAt(1).getClass() == ReadResolveTestA.class);
            assertTrue("invalid 2 : " + result.elementAt(2), result
                    .elementAt(2).getClass() == ReadResolveTestA.class);
            assertTrue("invalid 3 : " + result.elementAt(3), result
                    .elementAt(3).getClass() == ReadResolveTestB.class);
            assertTrue("invalid 4 : " + result.elementAt(4), result
                    .elementAt(4).getClass() == ReadResolveTestD.class);
            assertTrue("invalid 5 : " + result.elementAt(5), result
                    .elementAt(5).getClass() == WriteReplaceTestE.class);
        } catch (IOException e) {
            fail("IOException serializing data : " + e.getMessage());
        } catch (ClassNotFoundException e) {
            fail("ClassNotFoundException serializing data : " + e.getMessage());
        }
    }

    public void test_2_writeReplace() {
        try {
            boolean exception = false;
            try {
                oos.writeObject(new WriteReplaceTestF(0, -1));
            } catch (ObjectStreamException e) {
                exception = true;
            }
            assertTrue("Should throw ObjectStreamException", exception);
            exception = false;
            try {
                oos.writeObject(new WriteReplaceTestF(1, -1));
            } catch (RuntimeException e) {
                exception = true;
            }
            assertTrue("Should throw RuntimeException", exception);
            exception = false;
            try {
                oos.writeObject(new WriteReplaceTestF(2, -1));
            } catch (Error e) {
                exception = true;
            }
            assertTrue("Should throw Error", exception);

            oos.writeObject(new WriteReplaceTestF(3, 0));
            oos.writeObject(new WriteReplaceTestF(3, 1));
            oos.writeObject(new WriteReplaceTestF(3, 2));
            WriteReplaceTestF test = new WriteReplaceTestF(3, 3);
            oos.writeObject(test);
            oos.writeObject(test);
            WriteReplaceTestF test2 = new WriteReplaceTestF(3, 4);
            oos.writeObject(test2);
            oos.writeObject(test2);
            oos.close();
            ois = new ObjectInputStream(loadStream());
            try {
                ois.readObject();
            } catch (WriteAbortedException e) {
            }

            exception = false;
            try {
                ois.readObject();
            } catch (ObjectStreamException e) {
                exception = true;
            }
            assertTrue("Expected ObjectStreamException", exception);
            exception = false;
            try {
                ois.readObject();
            } catch (RuntimeException e) {
                exception = true;
            }
            assertTrue("Expected RuntimeException", exception);
            exception = false;
            try {
                ois.readObject();
            } catch (Error e) {
                exception = true;
            }
            assertTrue("Expected Error", exception);

            Object readE1 = ois.readObject();
            Object readE2 = ois.readObject();
            assertTrue("Replaced objects should be identical", readE1 == readE2);
            Object readF1 = ois.readObject();
            Object readF2 = ois.readObject();
            assertTrue("Replaced resolved objects should be identical: "
                    + readF1 + " " + readF2, readF1 == readF2);
        } catch (IOException e) {
            fail("IOException serializing data : " + e.getMessage());
        } catch (ClassNotFoundException e) {
            fail("ClassNotFoundException serializing data : " + e.getMessage());
        }
    }
}
