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}