001    /* MediaTracker.java -- Class used for keeping track of images
002       Copyright (C) 1999, 2002, 2004, 2005  Free Software Foundation, Inc.
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    
039    package java.awt;
040    
041    import java.awt.image.ImageObserver;
042    import 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      */
061    public 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    }