| // Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file | 
 | // for details. All rights reserved. Use of this source code is governed by a | 
 | // BSD-style license that can be found in the LICENSE file. | 
 | package trywithresources; | 
 |  | 
 | import java.io.Closeable; | 
 | import java.io.IOException; | 
 |  | 
 | public abstract class TryWithResources { | 
 |   // --- TEST SUPPORT --- | 
 |  | 
 |   interface Test { | 
 |     void test() throws Throwable; | 
 |   } | 
 |  | 
 |   private void test(Test test) { | 
 |     try { | 
 |       test.test(); | 
 |     } catch (Throwable e) { | 
 |       dumpException(e); | 
 |     } | 
 |   } | 
 |  | 
 |   private void dumpException(Throwable e) { | 
 |     dumpException(e, "Exception: "); | 
 |   } | 
 |  | 
 |   private void dumpException(Throwable e, String indent) { | 
 |     assert e != null; | 
 |     System.out.println(indent + e.getMessage()); | 
 |  | 
 |     indent = indent.replaceAll("[^:]", " "); | 
 |  | 
 |     Throwable cause = e.getCause(); | 
 |     if (cause != null) { | 
 |       dumpException(cause, indent + "  cause: "); | 
 |     } | 
 |  | 
 |     Throwable[] suppressed = e.getSuppressed(); | 
 |     for (int i = 0; i < suppressed.length; i++) { | 
 |       dumpException(suppressed[i], indent + "supp[" + i + "]: "); | 
 |     } | 
 |   } | 
 |  | 
 |   // --- TEST SYMBOLS --- | 
 |  | 
 |   static class Resource implements Closeable { | 
 |     final String tag; | 
 |  | 
 |     Resource(String tag) { | 
 |       this.tag = tag; | 
 |     } | 
 |  | 
 |     @Override | 
 |     public void close() throws IOException { | 
 |       Class<? extends Resource> cls = this.getClass(); | 
 |       System.out.println("Closing " + tag + " (" + | 
 |           cls.getName().substring(TryWithResources.class.getName().length() + 1) + ")"); | 
 |     } | 
 |   } | 
 |  | 
 |   // --- TEST --- | 
 |  | 
 |   class RegularTryWithResources { | 
 |     class RegularResource extends Resource { | 
 |       RegularResource(String tag) { | 
 |         super(tag); | 
 |       } | 
 |     } | 
 |  | 
 |     private void test() throws Throwable { | 
 |       test(2); | 
 |     } | 
 |  | 
 |     private void test(int level) throws Throwable { | 
 |       try (RegularResource a = new RegularResource("a" + level); | 
 |            RegularResource b = new RegularResource("b" + level)) { | 
 |         if (level > 0) { | 
 |           try { | 
 |             test(level - 1); | 
 |           } catch (Throwable e) { | 
 |             throw new RuntimeException("e" + level, e); | 
 |           } | 
 |         } | 
 |         throw new RuntimeException("primary cause"); | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   // --- TEST --- | 
 |  | 
 |   class FailingTryWithResources { | 
 |     class FailingResource extends Resource { | 
 |       FailingResource(String tag) { | 
 |         super(tag); | 
 |       } | 
 |  | 
 |       @Override | 
 |       public void close() throws IOException { | 
 |         super.close(); | 
 |         throw new RuntimeException("failed to close '" + tag + "'"); | 
 |       } | 
 |     } | 
 |  | 
 |     private void test() throws Throwable { | 
 |       test(2); | 
 |     } | 
 |  | 
 |     private void test(int level) throws Throwable { | 
 |       try (FailingResource a = new FailingResource("a" + level); | 
 |            FailingResource b = new FailingResource("b" + level)) { | 
 |         if (level > 0) { | 
 |           try { | 
 |             test(level - 1); | 
 |           } catch (Throwable e) { | 
 |             throw new RuntimeException("e" + level, e); | 
 |           } | 
 |         } | 
 |         throw new RuntimeException("primary cause"); | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   // --- TEST --- | 
 |  | 
 |   class ExplicitAddGetSuppressed { | 
 |     class RegularResource extends Resource { | 
 |       RegularResource(String tag) { | 
 |         super(tag); | 
 |       } | 
 |  | 
 |       @Override | 
 |       public void close() throws IOException { | 
 |         super.close(); | 
 |         throw new RuntimeException("failed to close '" + tag + "'"); | 
 |       } | 
 |     } | 
 |  | 
 |     private void test() throws Throwable { | 
 |       test(2); | 
 |     } | 
 |  | 
 |     private void test(int level) throws RuntimeException { | 
 |       try (RegularResource a = new RegularResource("a" + level); | 
 |            RegularResource b = new RegularResource("b" + level)) { | 
 |         if (level > 0) { | 
 |           try { | 
 |             test(level - 1); | 
 |           } catch (RuntimeException e) { | 
 |             // Just collect suppressed, but throw away the exception. | 
 |             RuntimeException re = new RuntimeException("e" + level); | 
 |             for (Throwable suppressed : e.getSuppressed()) { | 
 |               re.addSuppressed(suppressed); | 
 |             } | 
 |             throw re; | 
 |           } | 
 |         } | 
 |         throw new RuntimeException("primary cause"); | 
 |       } catch (IOException e) { | 
 |         throw new RuntimeException(e); | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   // --- TEST --- | 
 |  | 
 |   interface Consumer { | 
 |     void act(RuntimeException re); | 
 |   } | 
 |  | 
 |   interface Supplier { | 
 |     Throwable[] get(); | 
 |   } | 
 |  | 
 |   class AddGetSuppressedRoundTrip { | 
 |     private void test() throws Throwable { | 
 |       RuntimeException carrier = new RuntimeException("carrier"); | 
 |       Consumer packer = carrier::addSuppressed; | 
 |       Supplier unpacker = carrier::getSuppressed; | 
 |  | 
 |       packer.act(new RuntimeException("original exception A")); | 
 |       packer.act(new RuntimeException("original exception Z")); | 
 |  | 
 |       for (Throwable unpacked : unpacker.get()) { | 
 |         dumpException(unpacked); | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   // --- TEST --- | 
 |  | 
 |   class UnreachableCatchAfterCallsRemoved { | 
 |     private void test() throws Throwable { | 
 |       RuntimeException main = new RuntimeException("main"); | 
 |       RuntimeException origA = new RuntimeException("original exception A"); | 
 |       RuntimeException origB = new RuntimeException("original exception Z"); | 
 |  | 
 |       try { | 
 |         // After both calls below are removed, the whole catch | 
 |         // handler should be removed. | 
 |         main.addSuppressed(origA); | 
 |         main.addSuppressed(origB); | 
 |       } catch (Throwable t) { | 
 |         throw new RuntimeException("UNREACHABLE"); | 
 |       } | 
 |  | 
 |       // Return value not used. | 
 |       main.getSuppressed(); | 
 |     } | 
 |   } | 
 |  | 
 |   // --- MAIN TEST --- | 
 |  | 
 |   void test() throws Exception { | 
 |     System.out.println("----- TEST 1 -----"); | 
 |     test(new RegularTryWithResources()::test); | 
 |     System.out.println("----- TEST 2 -----"); | 
 |     test(new FailingTryWithResources()::test); | 
 |     System.out.println("----- TEST 3 -----"); | 
 |     test(new ExplicitAddGetSuppressed()::test); | 
 |     System.out.println("----- TEST 4 -----"); | 
 |     test(new AddGetSuppressedRoundTrip()::test); | 
 |     System.out.println("----- TEST 5 -----"); | 
 |     test(new UnreachableCatchAfterCallsRemoved()::test); | 
 |     System.out.println("------------------"); | 
 |   } | 
 | } |