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 }