1 /*
  2  * Copyright (c) 1995, 2021, 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      */
 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     @SuppressWarnings("removal")
280     private static void createFetchers(final FetcherInfo info) {
281        // We need to instantiate a new ImageFetcher thread.
282        // First, figure out which ThreadGroup we'll put the
283        // new ImageFetcher into
284        final AppContext appContext = AppContext.getAppContext();
285        ThreadGroup threadGroup = appContext.getThreadGroup();
286        ThreadGroup fetcherThreadGroup;
287        try {
288           if (threadGroup.getParent() != null) {
289              // threadGroup is not the root, so we proceed
290              fetcherThreadGroup = threadGroup;
291           } else {
292              // threadGroup is the root ("system") ThreadGroup.
293              // We instead want to use its child: the "main"
294              // ThreadGroup.  Thus, we start with the current
295              // ThreadGroup, and go up the tree until
296              // threadGroup.getParent().getParent() == null.
297              threadGroup = Thread.currentThread().getThreadGroup();
298              ThreadGroup parent = threadGroup.getParent();
299              while ((parent != null)
300                   && (parent.getParent() != null)) {
301                   threadGroup = parent;
302                   parent = threadGroup.getParent();
303              }
304              fetcherThreadGroup = threadGroup;
305          }
306        } catch (SecurityException e) {
307          // Not allowed access to parent ThreadGroup -- just use
308          // the AppContext's ThreadGroup
309          fetcherThreadGroup = appContext.getThreadGroup();
310        }
311        final ThreadGroup fetcherGroup = fetcherThreadGroup;
312 
313        java.security.AccessController.doPrivileged(
314            new java.security.PrivilegedAction<Object>() {
315                public Object run() {
316                    for (int i = 0; i < info.fetchers.length; i++) {
317                        if (info.fetchers[i] == null) {
318                            ImageFetcher f = new ImageFetcher(fetcherGroup, i);
319                        try {
320                            f.start();
321                            info.fetchers[i] = f;
322                            info.numFetchers++;
323                            break;
324                        } catch (Error e) {
325                        }
326                    }
327                  }
328                  return null;
329                }
330            });
331        return;
332    }
333 
334 }
335 
336 /**
337   * The FetcherInfo class encapsulates the per-AppContext ImageFetcher
338   * information.  This includes the array of ImageFetchers, as well as
339   * the queue of ImageFetchable objects.
340   */
341 class FetcherInfo {
342     static final int MAX_NUM_FETCHERS_PER_APPCONTEXT = 4;
343 
344     Thread[] fetchers;
345     int numFetchers;
346     int numWaiting;
347     Vector<ImageFetchable> waitList;
348 
349     private FetcherInfo() {
350         fetchers = new Thread[MAX_NUM_FETCHERS_PER_APPCONTEXT];
351         numFetchers = 0;
352         numWaiting = 0;
353         waitList = new Vector<>();
354     }
355 
356     /* The key to put()/get() the FetcherInfo into/from the AppContext. */
357     private static final Object FETCHER_INFO_KEY =
358                                         new StringBuffer("FetcherInfo");
359 
360     static FetcherInfo getFetcherInfo() {
361         AppContext appContext = AppContext.getAppContext();
362         synchronized(appContext) {
363             FetcherInfo info = (FetcherInfo)appContext.get(FETCHER_INFO_KEY);
364             if (info == null) {
365                 info = new FetcherInfo();
366                 appContext.put(FETCHER_INFO_KEY, info);
367             }
368             return info;
369         }
370     }
371 }