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      */
  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   */
 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 
 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 }