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
26package com.sun.imageio.stream;
27
28import sun.awt.util.ThreadGroupUtils;
29
30import java.io.IOException;
31import java.security.AccessController;
32import java.security.PrivilegedAction;
33import java.util.Set;
34import java.util.WeakHashMap;
35import 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 */
48public 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}
133