6911258: Project Coin: Add essential API support for Automatic Resource Management (ARM) blocks
authordarcy
Wed, 23 Jun 2010 17:03:40 -0700
changeset 5972 e3f47656e9d9
parent 5971 7f83fb438899
child 5973 b48e1702532e
6911258: Project Coin: Add essential API support for Automatic Resource Management (ARM) blocks 6911261: Project Coin: Retrofit Automatic Resource Management (ARM) support onto platform APIs 6962571: Infinite loop in printing out Throwable stack traces with circular references Reviewed-by: darcy, alanb Contributed-by: jjb@google.com
jdk/make/java/java/FILES_java.gmk
jdk/src/share/classes/java/io/Closeable.java
jdk/src/share/classes/java/lang/AutoCloseable.java
jdk/src/share/classes/java/lang/Throwable.java
jdk/src/share/classes/java/nio/channels/FileLock.java
jdk/src/share/classes/javax/imageio/stream/ImageInputStream.java
jdk/test/java/lang/Throwable/SuppressedExceptions.java
--- a/jdk/make/java/java/FILES_java.gmk	Wed Jun 23 21:22:27 2010 +0100
+++ b/jdk/make/java/java/FILES_java.gmk	Wed Jun 23 17:03:40 2010 -0700
@@ -30,6 +30,7 @@
 #
 JAVA_JAVA_java = \
     java/lang/Object.java \
+    java/lang/AutoCloseable.java \
     java/lang/Class.java \
     java/lang/Thread.java \
     java/lang/Character.java \
--- a/jdk/src/share/classes/java/io/Closeable.java	Wed Jun 23 21:22:27 2010 +0100
+++ b/jdk/src/share/classes/java/io/Closeable.java	Wed Jun 23 17:03:40 2010 -0700
@@ -28,14 +28,14 @@
 import java.io.IOException;
 
 /**
- * A <tt>Closeable</tt> is a source or destination of data that can be closed.
+ * A {@code Closeable} is a source or destination of data that can be closed.
  * The close method is invoked to release resources that the object is
  * holding (such as open files).
  *
  * @since 1.5
  */
 
-public interface Closeable {
+public interface Closeable extends AutoCloseable {
 
     /**
      * Closes this stream and releases any system resources associated
@@ -45,5 +45,4 @@
      * @throws IOException if an I/O error occurs
      */
     public void close() throws IOException;
-
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/java/lang/AutoCloseable.java	Wed Jun 23 17:03:40 2010 -0700
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code 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
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.lang;
+
+/**
+ * A resource that must be closed when it is no longer needed.
+ *
+ * @author Josh Bloch
+ * @since 1.7
+ */
+public interface AutoCloseable {
+    /**
+     * Close this resource, relinquishing any underlying resources.
+     * This method is invoked automatically by the automatic resource
+     * management block construct.
+     *
+     * <p>Classes implementing this method are strongly encouraged to
+     * be declared to throw more specific exceptions (or no exception
+     * at all, if the close cannot fail).
+     *
+     * @throws Exception if this resource cannot be closed
+     */
+    void close() throws Exception;
+}
--- a/jdk/src/share/classes/java/lang/Throwable.java	Wed Jun 23 21:22:27 2010 +0100
+++ b/jdk/src/share/classes/java/lang/Throwable.java	Wed Jun 23 17:03:40 2010 -0700
@@ -25,6 +25,7 @@
 
 package java.lang;
 import  java.io.*;
+import  java.util.*;
 
 /**
  * The <code>Throwable</code> class is the superclass of all errors and
@@ -102,7 +103,7 @@
  *         lowLevelOp();
  *     } catch (LowLevelException le) {
  *         throw (HighLevelException)
-                 new HighLevelException().initCause(le);  // Legacy constructor
+ *               new HighLevelException().initCause(le);  // Legacy constructor
  *     }
  * </pre>
  *
@@ -193,6 +194,24 @@
      */
 
     /**
+     * The list of suppressed exceptions, as returned by
+     * {@link #getSuppressedExceptions()}.
+     *
+     * @serial
+     * @since 1.7
+     */
+    private List<Throwable> suppressedExceptions = Collections.emptyList();
+
+    /** Message for trying to suppress a null exception. */
+    private static final String NULL_CAUSE_MESSAGE = "Cannot suppress a null exception.";
+
+    /** Caption  for labeling causative exception stack traces */
+    private static final String CAUSE_CAPTION = "Caused by: ";
+
+    /** Caption for labeling suppressed exception stack traces */
+    private static final String SUPPRESSED_CAPTION = "Suppressed: ";
+
+    /**
      * Constructs a new throwable with <code>null</code> as its detail message.
      * The cause is not initialized, and may subsequently be initialized by a
      * call to {@link #initCause}.
@@ -469,6 +488,52 @@
      * class LowLevelException extends Exception {
      * }
      * </pre>
+     * As of release 7, the platform supports the notion of
+     * <i>suppressed exceptions</i> (in conjunction with automatic
+     * resource management blocks). Any exceptions that were
+     * suppressed in order to deliver an exception are printed out
+     * beneath the stack trace.  The format of this information
+     * depends on the implementation, but the following example may be
+     * regarded as typical:
+     *
+     * <pre>
+     * Exception in thread "main" java.lang.Exception: Something happened
+     *  at Foo.bar(Foo.java:10)
+     *  at Foo.main(Foo.java:5)
+     *  Suppressed: Resource$CloseFailException: Resource ID = 0
+     *          at Resource.close(Resource.java:26)
+     *          at Foo.bar(Foo.java:9)
+     *          ... 1 more
+     * </pre>
+     * Note that the "... n more" notation is used on suppressed exceptions
+     * just at it is used on causes. Unlike causes, suppressed exceptions are
+     * indented beyond their "containing exceptions."
+     *
+     * <p>An exception can have both a cause and one or more suppressed
+     * exceptions:
+     * <pre>
+     * Exception in thread "main" java.lang.Exception: Main block
+     *  at Foo3.main(Foo3.java:7)
+     *  Suppressed: Resource$CloseFailException: Resource ID = 2
+     *          at Resource.close(Resource.java:26)
+     *          at Foo3.main(Foo3.java:5)
+     *  Suppressed: Resource$CloseFailException: Resource ID = 1
+     *          at Resource.close(Resource.java:26)
+     *          at Foo3.main(Foo3.java:5)
+     * Caused by: java.lang.Exception: I did it
+     *  at Foo3.main(Foo3.java:8)
+     * </pre>
+     * Likewise, a suppressed exception can have a cause:
+     * <pre>
+     * Exception in thread "main" java.lang.Exception: Main block
+     *  at Foo4.main(Foo4.java:6)
+     *  Suppressed: Resource2$CloseFailException: Resource ID = 1
+     *          at Resource2.close(Resource2.java:20)
+     *          at Foo4.main(Foo4.java:5)
+     *  Caused by: java.lang.Exception: Rats, you caught me
+     *          at Resource2$CloseFailException.<init>(Resource2.java:45)
+     *          ... 2 more
+     * </pre>
      */
     public void printStackTrace() {
         printStackTrace(System.err);
@@ -480,44 +545,71 @@
      * @param s <code>PrintStream</code> to use for output
      */
     public void printStackTrace(PrintStream s) {
-        synchronized (s) {
+        printStackTrace(new WrappedPrintStream(s));
+    }
+
+    private void printStackTrace(PrintStreamOrWriter s) {
+        Set<Throwable> dejaVu = new HashSet<Throwable>();
+        dejaVu.add(this);
+
+        synchronized (s.lock()) {
+            // Print our stack trace
             s.println(this);
             StackTraceElement[] trace = getOurStackTrace();
-            for (int i=0; i < trace.length; i++)
-                s.println("\tat " + trace[i]);
+            for (StackTraceElement traceElement : trace)
+                s.println("\tat " + traceElement);
 
+            // Print suppressed exceptions, if any
+            for (Throwable se : suppressedExceptions)
+                se.printEnclosedStackTrace(s, trace, SUPPRESSED_CAPTION, "\t", dejaVu);
+
+            // Print cause, if any
             Throwable ourCause = getCause();
             if (ourCause != null)
-                ourCause.printStackTraceAsCause(s, trace);
+                ourCause.printEnclosedStackTrace(s, trace, CAUSE_CAPTION, "", dejaVu);
         }
     }
 
     /**
-     * Print our stack trace as a cause for the specified stack trace.
+     * Print our stack trace as an enclosed exception for the specified
+     * stack trace.
      */
-    private void printStackTraceAsCause(PrintStream s,
-                                        StackTraceElement[] causedTrace)
-    {
-        // assert Thread.holdsLock(s);
+    private void printEnclosedStackTrace(PrintStreamOrWriter s,
+                                         StackTraceElement[] enclosingTrace,
+                                         String caption,
+                                         String prefix,
+                                         Set<Throwable> dejaVu) {
+        assert Thread.holdsLock(s.lock());
+        if (dejaVu.contains(this)) {
+            s.println("\t[CIRCULAR REFERENCE:" + this + "]");
+        } else {
+            dejaVu.add(this);
+            // Compute number of frames in common between this and enclosing trace
+            StackTraceElement[] trace = getOurStackTrace();
+            int m = trace.length - 1;
+            int n = enclosingTrace.length - 1;
+            while (m >= 0 && n >=0 && trace[m].equals(enclosingTrace[n])) {
+                m--; n--;
+            }
+            int framesInCommon = trace.length - 1 - m;
 
-        // Compute number of frames in common between this and caused
-        StackTraceElement[] trace = getOurStackTrace();
-        int m = trace.length-1, n = causedTrace.length-1;
-        while (m >= 0 && n >=0 && trace[m].equals(causedTrace[n])) {
-            m--; n--;
+            // Print our stack trace
+            s.println(prefix + caption + this);
+            for (int i = 0; i <= m; i++)
+                s.println(prefix + "\tat " + trace[i]);
+            if (framesInCommon != 0)
+                s.println(prefix + "\t... " + framesInCommon + " more");
+
+            // Print suppressed exceptions, if any
+            for (Throwable se : suppressedExceptions)
+                se.printEnclosedStackTrace(s, trace, SUPPRESSED_CAPTION,
+                                           prefix +"\t", dejaVu);
+
+            // Print cause, if any
+            Throwable ourCause = getCause();
+            if (ourCause != null)
+                ourCause.printEnclosedStackTrace(s, trace, CAUSE_CAPTION, prefix, dejaVu);
         }
-        int framesInCommon = trace.length - 1 - m;
-
-        s.println("Caused by: " + this);
-        for (int i=0; i <= m; i++)
-            s.println("\tat " + trace[i]);
-        if (framesInCommon != 0)
-            s.println("\t... " + framesInCommon + " more");
-
-        // Recurse if we have a cause
-        Throwable ourCause = getCause();
-        if (ourCause != null)
-            ourCause.printStackTraceAsCause(s, trace);
     }
 
     /**
@@ -528,44 +620,51 @@
      * @since   JDK1.1
      */
     public void printStackTrace(PrintWriter s) {
-        synchronized (s) {
-            s.println(this);
-            StackTraceElement[] trace = getOurStackTrace();
-            for (int i=0; i < trace.length; i++)
-                s.println("\tat " + trace[i]);
-
-            Throwable ourCause = getCause();
-            if (ourCause != null)
-                ourCause.printStackTraceAsCause(s, trace);
-        }
+        printStackTrace(new WrappedPrintWriter(s));
     }
 
     /**
-     * Print our stack trace as a cause for the specified stack trace.
+     * Wrapper class for PrintStream and PrintWriter to enable a single
+     * implementation of printStackTrace.
      */
-    private void printStackTraceAsCause(PrintWriter s,
-                                        StackTraceElement[] causedTrace)
-    {
-        // assert Thread.holdsLock(s);
+    private abstract static class PrintStreamOrWriter {
+        /** Returns the object to be locked when using this StreamOrWriter */
+        abstract Object lock();
+
+        /** Prints the specified string as a line on this StreamOrWriter */
+        abstract void println(Object o);
+    }
 
-        // Compute number of frames in common between this and caused
-        StackTraceElement[] trace = getOurStackTrace();
-        int m = trace.length-1, n = causedTrace.length-1;
-        while (m >= 0 && n >=0 && trace[m].equals(causedTrace[n])) {
-            m--; n--;
+    private static class WrappedPrintStream extends PrintStreamOrWriter {
+        private final PrintStream printStream;
+
+        WrappedPrintStream(PrintStream printStream) {
+            this.printStream = printStream;
+        }
+
+        Object lock() {
+            return printStream;
         }
-        int framesInCommon = trace.length - 1 - m;
+
+        void println(Object o) {
+            printStream.println(o);
+        }
+    }
+
+    private static class WrappedPrintWriter extends PrintStreamOrWriter {
+        private final PrintWriter printWriter;
 
-        s.println("Caused by: " + this);
-        for (int i=0; i <= m; i++)
-            s.println("\tat " + trace[i]);
-        if (framesInCommon != 0)
-            s.println("\t... " + framesInCommon + " more");
+        WrappedPrintWriter(PrintWriter printWriter) {
+            this.printWriter = printWriter;
+        }
 
-        // Recurse if we have a cause
-        Throwable ourCause = getCause();
-        if (ourCause != null)
-            ourCause.printStackTraceAsCause(s, trace);
+        Object lock() {
+            return printWriter;
+        }
+
+        void println(Object o) {
+            printWriter.println(o);
+        }
     }
 
     /**
@@ -667,10 +766,60 @@
      */
     native StackTraceElement getStackTraceElement(int index);
 
-    private synchronized void writeObject(java.io.ObjectOutputStream s)
+    private void readObject(ObjectInputStream s)
+        throws IOException, ClassNotFoundException {
+        s.defaultReadObject();     // read in all fields
+        List<Throwable> suppressed = Collections.emptyList();
+        if (suppressedExceptions != null &&
+            !suppressedExceptions.isEmpty()) { // Copy Throwables to new list
+            suppressed = new ArrayList<Throwable>();
+            for(Throwable t : suppressedExceptions) {
+                if (t == null)
+                    throw new NullPointerException(NULL_CAUSE_MESSAGE);
+                suppressed.add(t);
+            }
+        }
+        suppressedExceptions = suppressed;
+    }
+
+    private synchronized void writeObject(ObjectOutputStream s)
         throws IOException
     {
         getOurStackTrace();  // Ensure that stackTrace field is initialized.
         s.defaultWriteObject();
     }
+
+    /**
+     * Adds the specified exception to the list of exceptions that
+     * were suppressed, typically by the automatic resource management
+     * statement, in order to deliver this exception.
+     *
+     * @param exception the exception to be added to the list of
+     *        suppressed exceptions
+     * @throws NullPointerException if {@code exception} is null
+     * @since 1.7
+     */
+    public synchronized void addSuppressedException(Throwable exception) {
+        if (exception == null)
+            throw new NullPointerException(NULL_CAUSE_MESSAGE);
+
+        if (suppressedExceptions.size() == 0)
+            suppressedExceptions = new ArrayList<Throwable>();
+        suppressedExceptions.add(exception);
+    }
+
+    private static final Throwable[] EMPTY_THROWABLE_ARRAY = new Throwable[0];
+
+    /**
+     * Returns an array containing all of the exceptions that were
+     * suppressed, typically by the automatic resource management
+     * statement, in order to deliver this exception.
+     *
+     * @return an array containing all of the exceptions that were
+     *         suppressed to deliver this exception.
+     * @since 1.7
+     */
+    public Throwable[] getSuppressedExceptions() {
+        return suppressedExceptions.toArray(EMPTY_THROWABLE_ARRAY);
+    }
 }
--- a/jdk/src/share/classes/java/nio/channels/FileLock.java	Wed Jun 23 21:22:27 2010 +0100
+++ b/jdk/src/share/classes/java/nio/channels/FileLock.java	Wed Jun 23 17:03:40 2010 -0700
@@ -116,7 +116,7 @@
  * @since 1.4
  */
 
-public abstract class FileLock {
+public abstract class FileLock implements AutoCloseable {
 
     private final Channel channel;
     private final long position;
@@ -299,6 +299,17 @@
     public abstract void release() throws IOException;
 
     /**
+     * This method invokes the {@link #release} method. It was added
+     * to the class so that it could be used in conjunction with the
+     * automatic resource management block construct.
+     *
+     * @since 1.7
+     */
+    public final void close() throws IOException {
+        release();
+    }
+
+    /**
      * Returns a string describing the range, type, and validity of this lock.
      *
      * @return  A descriptive string
--- a/jdk/src/share/classes/javax/imageio/stream/ImageInputStream.java	Wed Jun 23 21:22:27 2010 +0100
+++ b/jdk/src/share/classes/javax/imageio/stream/ImageInputStream.java	Wed Jun 23 17:03:40 2010 -0700
@@ -25,6 +25,7 @@
 
 package javax.imageio.stream;
 
+import java.io.Closeable;
 import java.io.DataInput;
 import java.io.IOException;
 import java.nio.ByteOrder;
@@ -42,7 +43,7 @@
  * @see MemoryCacheImageInputStream
  *
  */
-public interface ImageInputStream extends DataInput {
+public interface ImageInputStream extends DataInput, Closeable {
 
     /**
      * Sets the desired byte order for future reads of data values
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/Throwable/SuppressedExceptions.java	Wed Jun 23 17:03:40 2010 -0700
@@ -0,0 +1,165 @@
+/*
+ * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code 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
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.*;
+import java.util.*;
+
+/*
+ * @test
+ * @bug     6911258 6962571
+ * @summary Basic tests of suppressed exceptions
+ * @author  Joseph D. Darcy
+ */
+
+public class SuppressedExceptions {
+    private static String message = "Bad suppressed exception information";
+
+    public static void main(String... args) throws Exception {
+        basicSupressionTest();
+        serializationTest();
+        selfReference();
+    }
+
+    private static void basicSupressionTest() {
+        Throwable throwable = new Throwable();
+        RuntimeException suppressed = new RuntimeException("A suppressed exception.");
+        AssertionError repressed  = new AssertionError("A repressed error.");
+
+        Throwable[] t0 = throwable.getSuppressedExceptions();
+        if (t0.length != 0) {
+            throw new RuntimeException(message);
+        }
+        throwable.printStackTrace();
+
+        throwable.addSuppressedException(suppressed);
+        Throwable[] t1 = throwable.getSuppressedExceptions();
+        if (t1.length != 1 ||
+            t1[0] != suppressed) {throw new RuntimeException(message);
+        }
+        throwable.printStackTrace();
+
+        throwable.addSuppressedException(repressed);
+        Throwable[] t2 = throwable.getSuppressedExceptions();
+        if (t2.length != 2 ||
+            t2[0] != suppressed ||
+            t2[1] != repressed) {
+            throw new RuntimeException(message);
+        }
+        throwable.printStackTrace();
+    }
+
+    private static void serializationTest() throws Exception {
+        /*
+         * Bytes of the serial form of
+         *
+         * (new Throwable())setStackTrace(new StackTraceElement[0])
+         *
+         * from JDK 6; suppressedException field will be missing and
+         * thus default to null upon deserialization.
+         */
+        byte[] bytes = {
+            (byte)0xac, (byte)0xed, (byte)0x00, (byte)0x05, (byte)0x73, (byte)0x72, (byte)0x00, (byte)0x13,
+            (byte)0x6a, (byte)0x61, (byte)0x76, (byte)0x61, (byte)0x2e, (byte)0x6c, (byte)0x61, (byte)0x6e,
+            (byte)0x67, (byte)0x2e, (byte)0x54, (byte)0x68, (byte)0x72, (byte)0x6f, (byte)0x77, (byte)0x61,
+            (byte)0x62, (byte)0x6c, (byte)0x65, (byte)0xd5, (byte)0xc6, (byte)0x35, (byte)0x27, (byte)0x39,
+            (byte)0x77, (byte)0xb8, (byte)0xcb, (byte)0x03, (byte)0x00, (byte)0x03, (byte)0x4c, (byte)0x00,
+            (byte)0x05, (byte)0x63, (byte)0x61, (byte)0x75, (byte)0x73, (byte)0x65, (byte)0x74, (byte)0x00,
+            (byte)0x15, (byte)0x4c, (byte)0x6a, (byte)0x61, (byte)0x76, (byte)0x61, (byte)0x2f, (byte)0x6c,
+            (byte)0x61, (byte)0x6e, (byte)0x67, (byte)0x2f, (byte)0x54, (byte)0x68, (byte)0x72, (byte)0x6f,
+            (byte)0x77, (byte)0x61, (byte)0x62, (byte)0x6c, (byte)0x65, (byte)0x3b, (byte)0x4c, (byte)0x00,
+            (byte)0x0d, (byte)0x64, (byte)0x65, (byte)0x74, (byte)0x61, (byte)0x69, (byte)0x6c, (byte)0x4d,
+            (byte)0x65, (byte)0x73, (byte)0x73, (byte)0x61, (byte)0x67, (byte)0x65, (byte)0x74, (byte)0x00,
+            (byte)0x12, (byte)0x4c, (byte)0x6a, (byte)0x61, (byte)0x76, (byte)0x61, (byte)0x2f, (byte)0x6c,
+            (byte)0x61, (byte)0x6e, (byte)0x67, (byte)0x2f, (byte)0x53, (byte)0x74, (byte)0x72, (byte)0x69,
+            (byte)0x6e, (byte)0x67, (byte)0x3b, (byte)0x5b, (byte)0x00, (byte)0x0a, (byte)0x73, (byte)0x74,
+            (byte)0x61, (byte)0x63, (byte)0x6b, (byte)0x54, (byte)0x72, (byte)0x61, (byte)0x63, (byte)0x65,
+            (byte)0x74, (byte)0x00, (byte)0x1e, (byte)0x5b, (byte)0x4c, (byte)0x6a, (byte)0x61, (byte)0x76,
+            (byte)0x61, (byte)0x2f, (byte)0x6c, (byte)0x61, (byte)0x6e, (byte)0x67, (byte)0x2f, (byte)0x53,
+            (byte)0x74, (byte)0x61, (byte)0x63, (byte)0x6b, (byte)0x54, (byte)0x72, (byte)0x61, (byte)0x63,
+            (byte)0x65, (byte)0x45, (byte)0x6c, (byte)0x65, (byte)0x6d, (byte)0x65, (byte)0x6e, (byte)0x74,
+            (byte)0x3b, (byte)0x78, (byte)0x70, (byte)0x71, (byte)0x00, (byte)0x7e, (byte)0x00, (byte)0x04,
+            (byte)0x70, (byte)0x75, (byte)0x72, (byte)0x00, (byte)0x1e, (byte)0x5b, (byte)0x4c, (byte)0x6a,
+            (byte)0x61, (byte)0x76, (byte)0x61, (byte)0x2e, (byte)0x6c, (byte)0x61, (byte)0x6e, (byte)0x67,
+            (byte)0x2e, (byte)0x53, (byte)0x74, (byte)0x61, (byte)0x63, (byte)0x6b, (byte)0x54, (byte)0x72,
+            (byte)0x61, (byte)0x63, (byte)0x65, (byte)0x45, (byte)0x6c, (byte)0x65, (byte)0x6d, (byte)0x65,
+            (byte)0x6e, (byte)0x74, (byte)0x3b, (byte)0x02, (byte)0x46, (byte)0x2a, (byte)0x3c, (byte)0x3c,
+            (byte)0xfd, (byte)0x22, (byte)0x39, (byte)0x02, (byte)0x00, (byte)0x00, (byte)0x78, (byte)0x70,
+            (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x78, (byte)0xac, (byte)0xed, (byte)0x00,
+            (byte)0x05, (byte)0x73, (byte)0x72, (byte)0x00, (byte)0x13, (byte)0x6a, (byte)0x61, (byte)0x76,
+            (byte)0x61, (byte)0x2e, (byte)0x6c, (byte)0x61, (byte)0x6e, (byte)0x67, (byte)0x2e, (byte)0x54,
+            (byte)0x68, (byte)0x72, (byte)0x6f, (byte)0x77, (byte)0x61, (byte)0x62, (byte)0x6c, (byte)0x65,
+            (byte)0xd5, (byte)0xc6, (byte)0x35, (byte)0x27, (byte)0x39, (byte)0x77, (byte)0xb8, (byte)0xcb,
+            (byte)0x03, (byte)0x00, (byte)0x03, (byte)0x4c, (byte)0x00, (byte)0x05, (byte)0x63, (byte)0x61,
+            (byte)0x75, (byte)0x73, (byte)0x65, (byte)0x74, (byte)0x00, (byte)0x15, (byte)0x4c, (byte)0x6a,
+            (byte)0x61, (byte)0x76, (byte)0x61, (byte)0x2f, (byte)0x6c, (byte)0x61, (byte)0x6e, (byte)0x67,
+            (byte)0x2f, (byte)0x54, (byte)0x68, (byte)0x72, (byte)0x6f, (byte)0x77, (byte)0x61, (byte)0x62,
+            (byte)0x6c, (byte)0x65, (byte)0x3b, (byte)0x4c, (byte)0x00, (byte)0x0d, (byte)0x64, (byte)0x65,
+            (byte)0x74, (byte)0x61, (byte)0x69, (byte)0x6c, (byte)0x4d, (byte)0x65, (byte)0x73, (byte)0x73,
+            (byte)0x61, (byte)0x67, (byte)0x65, (byte)0x74, (byte)0x00, (byte)0x12, (byte)0x4c, (byte)0x6a,
+            (byte)0x61, (byte)0x76, (byte)0x61, (byte)0x2f, (byte)0x6c, (byte)0x6e, (byte)0x67, (byte)0x3b,
+            (byte)0x61, (byte)0x6e, (byte)0x67, (byte)0x2f, (byte)0x53, (byte)0x74, (byte)0x72, (byte)0x69,
+            (byte)0x5b, (byte)0x00, (byte)0x0a, (byte)0x73, (byte)0x74, (byte)0x61, (byte)0x63, (byte)0x6b,
+            (byte)0x54, (byte)0x72, (byte)0x61, (byte)0x63, (byte)0x65, (byte)0x74, (byte)0x00, (byte)0x1e,
+            (byte)0x5b, (byte)0x4c, (byte)0x6a, (byte)0x61, (byte)0x76, (byte)0x61, (byte)0x2f, (byte)0x6c,
+            (byte)0x61, (byte)0x6e, (byte)0x67, (byte)0x2f, (byte)0x53, (byte)0x74, (byte)0x61, (byte)0x63,
+            (byte)0x6b, (byte)0x54, (byte)0x72, (byte)0x61, (byte)0x63, (byte)0x65, (byte)0x45, (byte)0x6c,
+            (byte)0x65, (byte)0x6d, (byte)0x65, (byte)0x6e, (byte)0x74, (byte)0x3b, (byte)0x78, (byte)0x70,
+            (byte)0x71, (byte)0x00, (byte)0x7e, (byte)0x00, (byte)0x04, (byte)0x70, (byte)0x75, (byte)0x72,
+            (byte)0x00, (byte)0x1e, (byte)0x5b, (byte)0x4c, (byte)0x6a, (byte)0x61, (byte)0x76, (byte)0x61,
+            (byte)0x2e, (byte)0x6c, (byte)0x61, (byte)0x6e, (byte)0x67, (byte)0x2e, (byte)0x53, (byte)0x74,
+            (byte)0x61, (byte)0x63, (byte)0x6b, (byte)0x54, (byte)0x72, (byte)0x61, (byte)0x63, (byte)0x65,
+            (byte)0x45, (byte)0x6c, (byte)0x65, (byte)0x6d, (byte)0x65, (byte)0x6e, (byte)0x74, (byte)0x3b,
+            (byte)0x02, (byte)0x46, (byte)0x2a, (byte)0x3c, (byte)0x3c, (byte)0xfd, (byte)0x22, (byte)0x39,
+            (byte)0x02, (byte)0x00, (byte)0x00, (byte)0x78, (byte)0x70,
+        };
+
+        ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
+        ObjectInputStream ois = new ObjectInputStream(bais);
+
+        Object o = ois.readObject();
+        Throwable throwable = (Throwable) o;
+
+        System.err.println("TESTING SERIALIZED EXCEPTION");
+
+        Throwable[] t0 = throwable.getSuppressedExceptions();
+        if (t0.length != 0) { // Will fail if t0 is null.
+            throw new RuntimeException(message);
+        }
+        throwable.printStackTrace();
+    }
+
+    private static void selfReference() {
+        Throwable throwable1 = new RuntimeException();
+        Throwable throwable2 = new AssertionError();
+        throwable1.initCause(throwable2);
+        throwable2.initCause(throwable1);
+
+        throwable1.printStackTrace();
+
+
+        throwable1.addSuppressedException(throwable1);
+        throwable1.addSuppressedException(throwable2);
+
+        throwable1.printStackTrace();
+    }
+}