1 /*
   2  * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.imageio.stream;
  27 
  28 import sun.awt.util.ThreadGroupUtils;
  29 
  30 import java.io.IOException;
  31 import java.security.AccessController;
  32 import java.security.PrivilegedAction;
  33 import java.util.Set;
  34 import java.util.WeakHashMap;
  35 import javax.imageio.stream.ImageInputStream;
  36 
  37 /**
  38  * This class provide means to properly close hanging
  39  * image input/output streams on VM shutdown.
  40  * This might be useful for proper cleanup such as removal
  41  * of temporary files.
  42  *
  43  * Addition of stream do not prevent it from being garbage collected
  44  * if no other references to it exists. Stream can be closed
  45  * explicitly without removal from StreamCloser queue.
  46  * Explicit removal from the queue only helps to save some memory.
  47  */
  48 public class StreamCloser {
  49 
  50     private static WeakHashMap<CloseAction, Object> toCloseQueue;
  51     private static Thread streamCloser;
  52 
  53     public static void addToQueue(CloseAction ca) {
  54         synchronized (StreamCloser.class) {
  55             if (toCloseQueue == null) {
  56                 toCloseQueue =
  57                     new WeakHashMap<CloseAction, Object>();
  58             }
  59 
  60             toCloseQueue.put(ca, null);
  61 
  62             if (streamCloser == null) {
  63                 final Runnable streamCloserRunnable = new Runnable() {
  64                     public void run() {
  65                         if (toCloseQueue != null) {
  66                             synchronized (StreamCloser.class) {
  67                                 Set<CloseAction> set =
  68                                     toCloseQueue.keySet();
  69                                 // Make a copy of the set in order to avoid
  70                                 // concurrent modification (the is.close()
  71                                 // will in turn call removeFromQueue())
  72                                 CloseAction[] actions =
  73                                     new CloseAction[set.size()];
  74                                 actions = set.toArray(actions);
  75                                 for (CloseAction ca : actions) {
  76                                     if (ca != null) {
  77                                         try {
  78                                             ca.performAction();
  79                                         } catch (IOException e) {
  80                                         }
  81                                     }
  82                                 }
  83                             }
  84                         }
  85                     }
  86                 };
  87 
  88                 AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
  89                     /* The thread must be a member of a thread group
  90                      * which will not get GCed before VM exit.
  91                      * Make its parent the top-level thread group.
  92                      */
  93                     ThreadGroup tg = ThreadGroupUtils.getRootThreadGroup();
  94                     streamCloser = new Thread(tg, streamCloserRunnable,
  95                                               "StreamCloser", 0, false);
  96                     /* Set context class loader to null in order to avoid
  97                      * keeping a strong reference to an application classloader.
  98                      */
  99                     streamCloser.setContextClassLoader(null);
 100                     Runtime.getRuntime().addShutdownHook(streamCloser);
 101                     return null;
 102                 });
 103             }
 104         }
 105     }
 106 
 107     public static void removeFromQueue(CloseAction ca) {
 108         synchronized (StreamCloser.class) {
 109             if (toCloseQueue != null) {
 110                 toCloseQueue.remove(ca);
 111             }
 112         }
 113     }
 114 
 115     public static CloseAction createCloseAction(ImageInputStream iis) {
 116         return new CloseAction(iis);
 117     }
 118 
 119     public static final class CloseAction {
 120         private ImageInputStream iis;
 121 
 122         private CloseAction(ImageInputStream iis) {
 123             this.iis = iis;
 124         }
 125 
 126         public void performAction() throws IOException {
 127             if (iis != null) {
 128                 iis.close();
 129             }
 130         }
 131     }
 132 }