001/* MediaTracker.java -- Class used for keeping track of images 002 Copyright (C) 1999, 2002, 2004, 2005 Free Software Foundation, Inc. 003 004This file is part of GNU Classpath. 005 006GNU Classpath is free software; you can redistribute it and/or modify 007it under the terms of the GNU General Public License as published by 008the Free Software Foundation; either version 2, or (at your option) 009any later version. 010 011GNU Classpath is distributed in the hope that it will be useful, but 012WITHOUT ANY WARRANTY; without even the implied warranty of 013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014General Public License for more details. 015 016You should have received a copy of the GNU General Public License 017along with GNU Classpath; see the file COPYING. If not, write to the 018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 01902110-1301 USA. 020 021Linking this library statically or dynamically with other modules is 022making a combined work based on this library. Thus, the terms and 023conditions of the GNU General Public License cover the whole 024combination. 025 026As a special exception, the copyright holders of this library give you 027permission to link this library with independent modules to produce an 028executable, regardless of the license terms of these independent 029modules, and to copy and distribute the resulting executable under 030terms of your choice, provided that you also meet, for each linked 031independent module, the terms and conditions of the license of that 032module. An independent module is a module which is not derived from 033or based on this library. If you modify this library, you may extend 034this exception to your version of the library, but you are not 035obligated to do so. If you do not wish to do so, delete this 036exception statement from your version. */ 037 038 039package java.awt; 040 041import java.awt.image.ImageObserver; 042import java.util.ArrayList; 043 044/** 045 * This class is used for keeping track of the status of various media 046 * objects. 047 * 048 * Media objects are tracked by assigning them an ID. It is possible 049 * to assign the same ID to mutliple objects, effectivly grouping them 050 * together. In this case the status flags ({@link #statusID}) and error flag 051 * (@link #isErrorID} and {@link #getErrorsID}) are ORed together. This 052 * means that you cannot say exactly which media object has which status, 053 * at most you can say that there <em>are</em> certain media objects with 054 * some certain status. 055 * 056 * At the moment only images are supported by this class. 057 * 058 * @author Aaron M. Renn (arenn@urbanophile.com) 059 * @author Bryce McKinlay 060 */ 061public class MediaTracker implements java.io.Serializable 062{ 063 /** Indicates that the media is still loading. */ 064 public static final int LOADING = 1 << 0; 065 066 /** Indicates that the loading operation has been aborted. */ 067 public static final int ABORTED = 1 << 1; 068 069 /** Indicates that an error has occured during loading of the media. */ 070 public static final int ERRORED = 1 << 2; 071 072 /** Indicates that the media has been successfully and completely loaded. */ 073 public static final int COMPLETE = 1 << 3; 074 075 /** The component on which the media is eventually been drawn. */ 076 Component target; 077 078 /** The head of the linked list of tracked media objects. */ 079 MediaEntry head; 080 081 /** Our serialVersionUID for serialization. */ 082 static final long serialVersionUID = -483174189758638095L; 083 084 /** 085 * This represents a media object that is tracked by a MediaTracker. 086 * It also implements a simple linked list. 087 */ 088 // FIXME: The serialized form documentation says MediaEntry is a 089 // serializable field, but the serialized form of MediaEntry itself 090 // doesn't appear to be documented. 091 class MediaEntry implements ImageObserver 092 { 093 /** The ID of the media object. */ 094 int id; 095 096 /** The media object. (only images are supported ATM). */ 097 Image image; 098 099 /** The link to the next entry in the list. */ 100 MediaEntry next; 101 102 /** The tracking status. */ 103 int status; 104 105 /** The width of the image. */ 106 int width; 107 108 /** The height of the image. */ 109 int height; 110 111 /** 112 * Receives notification from an {@link java.awt.image.ImageProducer} 113 * that more data of the image is available. 114 * 115 * @param img the image that is updated 116 * @param flags flags from the ImageProducer that indicate the status 117 * of the loading process 118 * @param x the X coordinate of the upper left corner of the image 119 * @param y the Y coordinate of the upper left corner of the image 120 * @param width the width of the image 121 * @param height the height of the image 122 * 123 * @return <code>true</code> if more data is needed, <code>false</code> 124 * otherwise 125 * 126 * @see java.awt.image.ImageObserver 127 */ 128 public boolean imageUpdate(Image img, int flags, int x, int y, 129 int width, int height) 130 { 131 if ((flags & ABORT) != 0) 132 status = ABORTED; 133 else if ((flags & ERROR) != 0) 134 status = ERRORED; 135 else if ((flags & ALLBITS) != 0) 136 status = COMPLETE; 137 else 138 status = 0; 139 140 synchronized (MediaTracker.this) 141 { 142 MediaTracker.this.notifyAll(); 143 } 144 145 // If status is not COMPLETE then we need more updates. 146 return ((status & (COMPLETE | ERRORED | ABORTED)) == 0); 147 } 148 } 149 150 /** 151 * Constructs a new MediaTracker for the component <code>c</code>. The 152 * component should be the component that uses the media (i.e. draws it). 153 * 154 * @param c the Component that wants to use the media 155 */ 156 public MediaTracker(Component c) 157 { 158 target = c; 159 } 160 161 /** 162 * Adds an image to the tracker with the specified <code>ID</code>. 163 * 164 * @param image the image to be added 165 * @param id the ID of the tracker list to which the image is added 166 */ 167 public void addImage(Image image, int id) 168 { 169 MediaEntry e = new MediaEntry(); 170 e.id = id; 171 e.image = image; 172 synchronized(this) 173 { 174 e.next = head; 175 head = e; 176 } 177 } 178 179 /** 180 * Adds an image to the tracker with the specified <code>ID</code>. 181 * The image is expected to be rendered with the specified width and 182 * height. 183 * 184 * @param image the image to be added 185 * @param id the ID of the tracker list to which the image is added 186 * @param width the width of the image 187 * @param height the height of the image 188 */ 189 public void addImage(Image image, int id, int width, int height) 190 { 191 MediaEntry e = new MediaEntry(); 192 e.id = id; 193 e.image = image; 194 e.width = width; 195 e.height = height; 196 synchronized(this) 197 { 198 e.next = head; 199 head = e; 200 } 201 } 202 203 /** 204 * Checks if all media objects have finished loading, i.e. are 205 * {@link #COMPLETE}, {@link #ABORTED} or {@link #ERRORED}. 206 * 207 * If the media objects are not already loading, a call to this 208 * method does <em>not</em> start loading. This is equivalent to 209 * a call to <code>checkAll(false)</code>. 210 * 211 * @return if all media objects have finished loading either by beeing 212 * complete, have been aborted or errored. 213 */ 214 public boolean checkAll() 215 { 216 return checkAll(false); 217 } 218 219 /** 220 * Checks if all media objects have finished loading, i.e. are 221 * {@link #COMPLETE}, {@link #ABORTED} or {@link #ERRORED}. 222 * 223 * If the media objects are not already loading, and <code>load</code> 224 * is <code>true</code> then a call to this 225 * method starts loading the media objects. 226 * 227 * @param load if <code>true</code> this method starts loading objects 228 * that are not already loading 229 * 230 * @return if all media objects have finished loading either by beeing 231 * complete, have been aborted or errored. 232 */ 233 public boolean checkAll(boolean load) 234 { 235 MediaEntry e = head; 236 boolean result = true; 237 238 while (e != null) 239 { 240 if ((e.status & (COMPLETE | ERRORED | ABORTED)) == 0) 241 { 242 if (load && ((e.status & LOADING) == 0)) 243 { 244 if (target.prepareImage(e.image, e)) 245 e.status = COMPLETE; 246 else 247 { 248 e.status = LOADING; 249 int flags = target.checkImage(e.image, e); 250 if ((flags & ImageObserver.ABORT) != 0) 251 e.status = ABORTED; 252 else if ((flags & ImageObserver.ERROR) != 0) 253 e.status = ERRORED; 254 else if ((flags & ImageObserver.ALLBITS) != 0) 255 e.status = COMPLETE; 256 } 257 boolean complete = (e.status 258 & (COMPLETE | ABORTED | ERRORED)) != 0; 259 if (!complete) 260 result = false; 261 } 262 else 263 result = false; 264 } 265 e = e.next; 266 } 267 return result; 268 } 269 270 /** 271 * Checks if any of the registered media objects has encountered an error 272 * during loading. 273 * 274 * @return <code>true</code> if at least one media object has encountered 275 * an error during loading, <code>false</code> otherwise 276 * 277 */ 278 public boolean isErrorAny() 279 { 280 MediaEntry e = head; 281 while (e != null) 282 { 283 if ((e.status & ERRORED) != 0) 284 return true; 285 e = e.next; 286 } 287 return false; 288 } 289 290 /** 291 * Returns all media objects that have encountered errors during loading. 292 * 293 * @return an array of all media objects that have encountered errors 294 * or <code>null</code> if there were no errors at all 295 */ 296 public Object[] getErrorsAny() 297 { 298 MediaEntry e = head; 299 ArrayList result = null; 300 while (e != null) 301 { 302 if ((e.status & ERRORED) != 0) 303 { 304 if (result == null) 305 result = new ArrayList(); 306 result.add(e.image); 307 } 308 e = e.next; 309 } 310 if (result == null) 311 return null; 312 else 313 return result.toArray(); 314 } 315 316 /** 317 * Waits for all media objects to finish loading, either by completing 318 * successfully or by aborting or encountering an error. 319 * 320 * @throws InterruptedException if another thread interrupted the 321 * current thread while waiting 322 */ 323 public void waitForAll() throws InterruptedException 324 { 325 synchronized (this) 326 { 327 while (checkAll(true) == false) 328 wait(); 329 } 330 } 331 332 /** 333 * Waits for all media objects to finish loading, either by completing 334 * successfully or by aborting or encountering an error. 335 * 336 * This method waits at most <code>ms</code> milliseconds. If the 337 * media objects have not completed loading within this timeframe, this 338 * method returns <code>false</code>, otherwise <code>true</code>. 339 * 340 * @param ms timeframe in milliseconds to wait for the media objects to 341 * finish 342 * 343 * @return <code>true</code> if all media objects have successfully loaded 344 * within the timeframe, <code>false</code> otherwise 345 * 346 * @throws InterruptedException if another thread interrupted the 347 * current thread while waiting 348 */ 349 public boolean waitForAll(long ms) throws InterruptedException 350 { 351 long start = System.currentTimeMillis(); 352 boolean result = checkAll(true); 353 synchronized (this) 354 { 355 while (result == false) 356 { 357 wait(ms); 358 result = checkAll(true); 359 if ((System.currentTimeMillis() - start) > ms) 360 break; 361 } 362 } 363 364 return result; 365 } 366 367 /** 368 * Returns the status flags of all registered media objects ORed together. 369 * If <code>load</code> is <code>true</code> then media objects that 370 * are not already loading will be started to load. 371 * 372 * @param load if set to <code>true</code> then media objects that are 373 * not already loading are started 374 * 375 * @return the status flags of all tracked media objects ORed together 376 */ 377 public int statusAll(boolean load) 378 { 379 int result = 0; 380 MediaEntry e = head; 381 while (e != null) 382 { 383 if (load && e.status == 0) 384 { 385 if (target.prepareImage(e.image, e)) 386 e.status = COMPLETE; 387 else 388 { 389 e.status = LOADING; 390 int flags = target.checkImage(e.image, e); 391 if ((flags & ImageObserver.ABORT) != 0) 392 e.status = ABORTED; 393 else if ((flags & ImageObserver.ERROR) != 0) 394 e.status = ERRORED; 395 else if ((flags & ImageObserver.ALLBITS) != 0) 396 e.status = COMPLETE; 397 } 398 } 399 result |= e.status; 400 e = e.next; 401 } 402 return result; 403 } 404 405 /** 406 * Checks if the media objects with <code>ID</code> have completed loading. 407 * 408 * @param id the ID of the media objects to check 409 * 410 * @return <code>true</code> if all media objects with <code>ID</code> 411 * have successfully finished 412 */ 413 public boolean checkID(int id) 414 { 415 return checkID(id, false); 416 } 417 418 /** 419 * Checks if the media objects with <code>ID</code> have completed loading. 420 * If <code>load</code> is <code>true</code> then media objects that 421 * are not already loading will be started to load. 422 * 423 * @param id the ID of the media objects to check 424 * @param load if set to <code>true</code> then media objects that are 425 * not already loading are started 426 * 427 * @return <code>true</code> if all media objects with <code>ID</code> 428 * have successfully finished 429 */ 430 public boolean checkID(int id, boolean load) 431 { 432 MediaEntry e = head; 433 boolean result = true; 434 435 while (e != null) 436 { 437 if (e.id == id && ((e.status & (COMPLETE | ABORTED | ERRORED)) == 0)) 438 { 439 if (load && ((e.status & LOADING) == 0)) 440 { 441 e.status = LOADING; 442 if (target.prepareImage(e.image, e)) 443 e.status = COMPLETE; 444 else 445 { 446 int flags = target.checkImage(e.image, e); 447 if ((flags & ImageObserver.ABORT) != 0) 448 e.status = ABORTED; 449 else if ((flags & ImageObserver.ERROR) != 0) 450 e.status = ERRORED; 451 else if ((flags & ImageObserver.ALLBITS) != 0) 452 e.status = COMPLETE; 453 } 454 boolean complete = (e.status 455 & (COMPLETE | ABORTED | ERRORED)) != 0; 456 if (!complete) 457 result = false; 458 } 459 else 460 result = false; 461 } 462 e = e.next; 463 } 464 return result; 465 } 466 467 /** 468 * Returns <code>true</code> if any of the media objects with <code>ID</code> 469 * have encountered errors during loading, false otherwise. 470 * 471 * @param id the ID of the media objects to check 472 * 473 * @return <code>true</code> if any of the media objects with <code>ID</code> 474 * have encountered errors during loading, false otherwise 475 */ 476 public boolean isErrorID(int id) 477 { 478 MediaEntry e = head; 479 while (e != null) 480 { 481 if (e.id == id && ((e.status & ERRORED) != 0)) 482 return true; 483 e = e.next; 484 } 485 return false; 486 } 487 488 /** 489 * Returns all media objects with the specified ID that have encountered 490 * an error. 491 * 492 * @param id the ID of the media objects to check 493 * 494 * @return an array of all media objects with the specified ID that 495 * have encountered an error 496 */ 497 public Object[] getErrorsID(int id) 498 { 499 MediaEntry e = head; 500 ArrayList result = null; 501 while (e != null) 502 { 503 if (e.id == id && ((e.status & ERRORED) != 0)) 504 { 505 if (result == null) 506 result = new ArrayList(); 507 result.add(e.image); 508 } 509 e = e.next; 510 } 511 if (result == null) 512 return null; 513 else 514 return result.toArray(); 515 } 516 517 /** 518 * Waits for all media objects with the specified ID to finish loading, 519 * either by completing successfully or by aborting or encountering an error. 520 * 521 * @param id the ID of the media objects to wait for 522 * 523 * @throws InterruptedException if another thread interrupted the 524 * current thread while waiting 525 */ 526 public void waitForID(int id) throws InterruptedException 527 { 528 MediaEntry e = head; 529 synchronized (this) 530 { 531 while (checkID (id, true) == false) 532 wait(); 533 } 534 } 535 536 /** 537 * Waits for all media objects with the specified ID to finish loading, 538 * either by completing successfully or by aborting or encountering an error. 539 * 540 * This method waits at most <code>ms</code> milliseconds. If the 541 * media objects have not completed loading within this timeframe, this 542 * method returns <code>false</code>, otherwise <code>true</code>. 543 * 544 * @param id the ID of the media objects to wait for 545 * @param ms timeframe in milliseconds to wait for the media objects to 546 * finish 547 * 548 * @return <code>true</code> if all media objects have successfully loaded 549 * within the timeframe, <code>false</code> otherwise 550 * 551 * @throws InterruptedException if another thread interrupted the 552 * current thread while waiting 553 */ 554 public boolean waitForID(int id, long ms) throws InterruptedException 555 { 556 MediaEntry e = head; 557 long start = System.currentTimeMillis(); 558 boolean result = checkID(id, true); 559 560 synchronized (this) 561 { 562 while (result == false) 563 { 564 wait(ms); 565 result = checkID(id, true); 566 if ((System.currentTimeMillis() - start) > ms) 567 break; 568 } 569 } 570 571 return result; 572 } 573 574 /** 575 * Returns the status flags of the media objects with the specified ID 576 * ORed together. 577 * 578 * If <code>load</code> is <code>true</code> then media objects that 579 * are not already loading will be started to load. 580 * 581 * @param load if set to <code>true</code> then media objects that are 582 * not already loading are started 583 * 584 * @return the status flags of all tracked media objects ORed together 585 */ 586 public int statusID(int id, boolean load) 587 { 588 int result = 0; 589 MediaEntry e = head; 590 while (e != null) 591 { 592 if (e.id == id) 593 { 594 if (load && e.status == 0) 595 { 596 if (target.prepareImage(e.image, e)) 597 e.status = COMPLETE; 598 else 599 { 600 e.status = LOADING; 601 int flags = target.checkImage(e.image, e); 602 if ((flags & ImageObserver.ABORT) != 0) 603 e.status = ABORTED; 604 else if ((flags & ImageObserver.ERROR) != 0) 605 e.status = ERRORED; 606 else if ((flags & ImageObserver.ALLBITS) != 0) 607 e.status = COMPLETE; 608 } 609 } 610 result |= e.status; 611 } 612 e = e.next; 613 } 614 return result; 615 } 616 617 /** 618 * Removes an image from this MediaTracker. 619 * 620 * @param image the image to be removed 621 */ 622 public void removeImage(Image image) 623 { 624 synchronized (this) 625 { 626 MediaEntry e = head; 627 MediaEntry prev = null; 628 while (e != null) 629 { 630 if (e.image == image) 631 { 632 if (prev == null) 633 head = e.next; 634 else 635 prev.next = e.next; 636 } 637 else 638 prev = e; 639 e = e.next; 640 } 641 } 642 } 643 644 /** 645 * Removes an image with the specified ID from this MediaTracker. 646 * 647 * @param image the image to be removed 648 */ 649 public void removeImage(Image image, int id) 650 { 651 synchronized (this) 652 { 653 MediaEntry e = head; 654 MediaEntry prev = null; 655 while (e != null) 656 { 657 if (e.id == id && e.image == image) 658 { 659 if (prev == null) 660 head = e.next; 661 else 662 prev.next = e.next; 663 } 664 else 665 prev = e; 666 e = e.next; 667 } 668 } 669 } 670 671 /** 672 * Removes an image with the specified ID and scale from this MediaTracker. 673 * 674 * @param image the image to be removed 675 */ 676 public void removeImage(Image image, int id, int width, int height) 677 { 678 synchronized (this) 679 { 680 MediaEntry e = head; 681 MediaEntry prev = null; 682 while (e != null) 683 { 684 if (e.id == id && e.image == image 685 && e.width == width && e.height == height) 686 { 687 if (prev == null) 688 head = e.next; 689 else 690 prev.next = e.next; 691 } 692 else 693 prev = e; 694 e = e.next; 695 } 696 } 697 } 698}