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 
26 package sun.awt.image;
27 
28 import java.util.Vector;
29 import 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   */
44 class 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      */
ImageFetcher()57     private ImageFetcher() {
58         throw new UnsupportedOperationException("Must erase locals");
59     }
60     /**
61       * Constructor for ImageFetcher -- only called by add() below.
62       */
ImageFetcher(ThreadGroup threadGroup, int index)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       */
add(ImageFetchable src)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       */
remove(ImageFetchable src)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       */
isFetcher(Thread t)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       */
amFetcher()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       */
nextImage()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       */
run()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       */
fetchloop()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       */
startingAnimation()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       */
stoppingAnimation(Thread me)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       */
createFetchers(final FetcherInfo info)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   */
340 class 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 
FetcherInfo()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 
getFetcherInfo()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