1/*
2 * Copyright (c) 1995, 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 sun.awt.image;
27
28import java.util.Vector;
29import sun.awt.AppContext;
30
31/**
32  * An ImageFetcher is a thread used to fetch ImageFetchable objects.
33  * Once an ImageFetchable object has been fetched, the ImageFetcher
34  * thread may also be used to animate it if necessary, via the
35  * startingAnimation() / stoppingAnimation() methods.
36  *
37  * There can be up to FetcherInfo.MAX_NUM_FETCHERS_PER_APPCONTEXT
38  * ImageFetcher threads for each AppContext.  A per-AppContext queue
39  * of ImageFetchables is used to track objects to fetch.
40  *
41  * @author Jim Graham
42  * @author Fred Ecks
43  */
44class ImageFetcher extends Thread {
45    static final int HIGH_PRIORITY = 8;
46    static final int LOW_PRIORITY = 3;
47    static final int ANIM_PRIORITY = 2;
48
49    static final int TIMEOUT = 5000; // Time in milliseconds to wait for an
50                                     // ImageFetchable to be added to the
51                                     // queue before an ImageFetcher dies
52
53    /**
54     * We must only call the 5 args super() constructor passing
55     * in "false" to indicate to not inherit locals.
56     */
57    private ImageFetcher() {
58        throw new UnsupportedOperationException("Must erase locals");
59    }
60    /**
61      * Constructor for ImageFetcher -- only called by add() below.
62      */
63    private ImageFetcher(ThreadGroup threadGroup, int index) {
64        super(threadGroup, null, "Image Fetcher " + index, 0, false);
65        setDaemon(true);
66    }
67
68    /**
69      * Adds an ImageFetchable to the queue of items to fetch.  Instantiates
70      * a new ImageFetcher if it's reasonable to do so.
71      * If there is no available fetcher to process an ImageFetchable, then
72      * reports failure to caller.
73      */
74    public static boolean add(ImageFetchable src) {
75        final FetcherInfo info = FetcherInfo.getFetcherInfo();
76        synchronized(info.waitList) {
77            if (!info.waitList.contains(src)) {
78                info.waitList.addElement(src);
79                if (info.numWaiting == 0 &&
80                            info.numFetchers < info.fetchers.length) {
81                    createFetchers(info);
82                }
83                /* Creation of new fetcher may fail due to high vm load
84                 * or some other reason.
85                 * If there is already exist, but busy, fetcher, we leave
86                 * the src in queue (it will be handled by existing
87                 * fetcher later).
88                 * Otherwise, we report failure: there is no fetcher
89                 * to handle the src.
90                 */
91                if (info.numFetchers > 0) {
92                    info.waitList.notify();
93                } else {
94                    info.waitList.removeElement(src);
95                    return false;
96                }
97            }
98        }
99        return true;
100    }
101
102    /**
103      * Removes an ImageFetchable from the queue of items to fetch.
104      */
105    public static void remove(ImageFetchable src) {
106        final FetcherInfo info = FetcherInfo.getFetcherInfo();
107        synchronized(info.waitList) {
108            if (info.waitList.contains(src)) {
109                info.waitList.removeElement(src);
110            }
111        }
112    }
113
114    /**
115      * Checks to see if the given thread is one of the ImageFetchers.
116      */
117    public static boolean isFetcher(Thread t) {
118        final FetcherInfo info = FetcherInfo.getFetcherInfo();
119        synchronized(info.waitList) {
120            for (int i = 0; i < info.fetchers.length; i++) {
121                if (info.fetchers[i] == t) {
122                    return true;
123                }
124            }
125        }
126        return false;
127    }
128
129    /**
130      * Checks to see if the current thread is one of the ImageFetchers.
131      */
132    public static boolean amFetcher() {
133        return isFetcher(Thread.currentThread());
134    }
135
136    /**
137      * Returns the next ImageFetchable to be processed.  If TIMEOUT
138      * elapses in the mean time, or if the ImageFetcher is interrupted,
139      * null is returned.
140      */
141    private static ImageFetchable nextImage() {
142        final FetcherInfo info = FetcherInfo.getFetcherInfo();
143        synchronized(info.waitList) {
144            ImageFetchable src = null;
145            long end = System.currentTimeMillis() + TIMEOUT;
146            while (src == null) {
147                while (info.waitList.size() == 0) {
148                    long now = System.currentTimeMillis();
149                    if (now >= end) {
150                        return null;
151                    }
152                    try {
153                        info.numWaiting++;
154                        info.waitList.wait(end - now);
155                    } catch (InterruptedException e) {
156                        // A normal occurrence as an AppContext is disposed
157                        return null;
158                    } finally {
159                        info.numWaiting--;
160                    }
161                }
162                src = info.waitList.elementAt(0);
163                info.waitList.removeElement(src);
164            }
165            return src;
166        }
167    }
168
169    /**
170      * The main run() method of an ImageFetcher Thread.  Calls fetchloop()
171      * to do the work, then removes itself from the array of ImageFetchers.
172      */
173    public void run() {
174        final FetcherInfo info = FetcherInfo.getFetcherInfo();
175        try {
176            fetchloop();
177        } catch (Exception e) {
178            e.printStackTrace();
179        } finally {
180            synchronized(info.waitList) {
181                Thread me = Thread.currentThread();
182                for (int i = 0; i < info.fetchers.length; i++) {
183                    if (info.fetchers[i] == me) {
184                        info.fetchers[i] = null;
185                        info.numFetchers--;
186                    }
187                }
188            }
189        }
190    }
191
192    /**
193      * The main ImageFetcher loop.  Repeatedly calls nextImage(), and
194      * fetches the returned ImageFetchable objects until nextImage()
195      * returns null.
196      */
197    private void fetchloop() {
198        Thread me = Thread.currentThread();
199        while (isFetcher(me)) {
200            // we're ignoring the return value and just clearing
201            // the interrupted flag, instead of bailing out if
202            // the fetcher was interrupted, as we used to,
203            // because there may be other images waiting
204            // to be fetched (see 4789067)
205            Thread.interrupted();
206            me.setPriority(HIGH_PRIORITY);
207            ImageFetchable src = nextImage();
208            if (src == null) {
209                return;
210            }
211            try {
212                src.doFetch();
213            } catch (Exception e) {
214                System.err.println("Uncaught error fetching image:");
215                e.printStackTrace();
216            }
217            stoppingAnimation(me);
218        }
219    }
220
221
222    /**
223      * Recycles this ImageFetcher thread as an image animator thread.
224      * Removes this ImageFetcher from the array of ImageFetchers, and
225      * resets the thread name to "ImageAnimator".
226      */
227    static void startingAnimation() {
228        final FetcherInfo info = FetcherInfo.getFetcherInfo();
229        Thread me = Thread.currentThread();
230        synchronized(info.waitList) {
231            for (int i = 0; i < info.fetchers.length; i++) {
232                if (info.fetchers[i] == me) {
233                    info.fetchers[i] = null;
234                    info.numFetchers--;
235                    me.setName("Image Animator " + i);
236                    if(info.waitList.size() > info.numWaiting) {
237                       createFetchers(info);
238                    }
239                    return;
240                }
241            }
242        }
243        me.setPriority(ANIM_PRIORITY);
244        me.setName("Image Animator");
245    }
246
247    /**
248      * Returns this image animator thread back to service as an ImageFetcher
249      * if possible.  Puts it back into the array of ImageFetchers and sets
250      * the thread name back to "Image Fetcher".  If there are already the
251      * maximum number of ImageFetchers, this method simply returns, and
252      * fetchloop() will drop out when it sees that this thread isn't one of
253      * the ImageFetchers, and this thread will die.
254      */
255    private static void stoppingAnimation(Thread me) {
256        final FetcherInfo info = FetcherInfo.getFetcherInfo();
257        synchronized(info.waitList) {
258            int index = -1;
259            for (int i = 0; i < info.fetchers.length; i++) {
260                if (info.fetchers[i] == me) {
261                    return;
262                }
263                if (info.fetchers[i] == null) {
264                    index = i;
265                }
266            }
267            if (index >= 0) {
268                info.fetchers[index] = me;
269                info.numFetchers++;
270                me.setName("Image Fetcher " + index);
271                return;
272            }
273        }
274    }
275
276    /**
277      * Create and start ImageFetcher threads in the appropriate ThreadGroup.
278      */
279    private static void createFetchers(final FetcherInfo info) {
280       // We need to instantiate a new ImageFetcher thread.
281       // First, figure out which ThreadGroup we'll put the
282       // new ImageFetcher into
283       final AppContext appContext = AppContext.getAppContext();
284       ThreadGroup threadGroup = appContext.getThreadGroup();
285       ThreadGroup fetcherThreadGroup;
286       try {
287          if (threadGroup.getParent() != null) {
288             // threadGroup is not the root, so we proceed
289             fetcherThreadGroup = threadGroup;
290          } else {
291             // threadGroup is the root ("system") ThreadGroup.
292             // We instead want to use its child: the "main"
293             // ThreadGroup.  Thus, we start with the current
294             // ThreadGroup, and go up the tree until
295             // threadGroup.getParent().getParent() == null.
296             threadGroup = Thread.currentThread().getThreadGroup();
297             ThreadGroup parent = threadGroup.getParent();
298             while ((parent != null)
299                  && (parent.getParent() != null)) {
300                  threadGroup = parent;
301                  parent = threadGroup.getParent();
302             }
303             fetcherThreadGroup = threadGroup;
304         }
305       } catch (SecurityException e) {
306         // Not allowed access to parent ThreadGroup -- just use
307         // the AppContext's ThreadGroup
308         fetcherThreadGroup = appContext.getThreadGroup();
309       }
310       final ThreadGroup fetcherGroup = fetcherThreadGroup;
311
312       java.security.AccessController.doPrivileged(
313           new java.security.PrivilegedAction<Object>() {
314               public Object run() {
315                   for (int i = 0; i < info.fetchers.length; i++) {
316                       if (info.fetchers[i] == null) {
317                           ImageFetcher f = new ImageFetcher(fetcherGroup, i);
318                       try {
319                           f.start();
320                           info.fetchers[i] = f;
321                           info.numFetchers++;
322                           break;
323                       } catch (Error e) {
324                       }
325                   }
326                 }
327                 return null;
328               }
329           });
330       return;
331   }
332
333}
334
335/**
336  * The FetcherInfo class encapsulates the per-AppContext ImageFetcher
337  * information.  This includes the array of ImageFetchers, as well as
338  * the queue of ImageFetchable objects.
339  */
340class FetcherInfo {
341    static final int MAX_NUM_FETCHERS_PER_APPCONTEXT = 4;
342
343    Thread[] fetchers;
344    int numFetchers;
345    int numWaiting;
346    Vector<ImageFetchable> waitList;
347
348    private FetcherInfo() {
349        fetchers = new Thread[MAX_NUM_FETCHERS_PER_APPCONTEXT];
350        numFetchers = 0;
351        numWaiting = 0;
352        waitList = new Vector<>();
353    }
354
355    /* The key to put()/get() the FetcherInfo into/from the AppContext. */
356    private static final Object FETCHER_INFO_KEY =
357                                        new StringBuffer("FetcherInfo");
358
359    static FetcherInfo getFetcherInfo() {
360        AppContext appContext = AppContext.getAppContext();
361        synchronized(appContext) {
362            FetcherInfo info = (FetcherInfo)appContext.get(FETCHER_INFO_KEY);
363            if (info == null) {
364                info = new FetcherInfo();
365                appContext.put(FETCHER_INFO_KEY, info);
366            }
367            return info;
368        }
369    }
370}
371