Package org.locationtech.udig.ui

Source Code of org.locationtech.udig.ui.ImageCache

/*******************************************************************************
* Copyright (c) 2000, 2004 IBM Corporation and others. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Common Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/cpl-v10.html
*
* Contributors: IBM Corporation - initial API and implementation
******************************************************************************/
package org.locationtech.udig.ui;

import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Display;

/**
* A weakly referenced cache of image descriptors to arrays of image instances
* (representing normal, gray and disabled images). This is used to hold images
* in memory while their descriptors are defined. When the image descriptors
* become weakly referred to, the corresponding images in the array of images
* will be disposed.
*
* Weak references of equivalent image descriptors are mapped to the same array
* of images (where equivalent descriptors are <code>equals(Object)</code>).
*
* It is recommended to use this class as a singleton, since it creates a thread
* for cleaning out images.
*
* It is the responsibility of the user to ensure that the image descriptors are
* kept around as long as the images are needed. The users of this cache should
* not explicitly dispose the images.
*
* Upon request of a disabled or gray image, the normal image will be created as
* well (if it was not already in the cache) in order to create the disabled or
* gray version of the image.
*
* This cache makes no guarantees on how long the cleaning process will take, or
* when exactly it will occur.
*
*
* This class may be instantiated; it is not intended to be subclassed.
*
* @since 3.1
*/
public final class ImageCache {

  /**
   * An equivalent set of weak references to equivalent descriptors. The
   * equivalence of image descriptors is determined through
   * <code>equals(Object)</code>.
   *
   * @since 3.1
   */
  private static final class EquivalenceSet {

    /**
     * The equivalence set's hash code is the hash code of the first weak
     * reference added to the set.
     */
    private final int equivalenceHashCode;

    /**
     * A list of weak references to equivalent image descriptors.
     */
    private final ArrayList imageCacheWeakReferences;

    /**
     * Create an equivalence set and add the weak reference to the list.
     *
     * @param referenceToAdd
     *            The weak reference to add to the list of weak references.
     */
    private EquivalenceSet(ImageCacheWeakReference referenceToAdd) {
      imageCacheWeakReferences = new ArrayList();
      imageCacheWeakReferences.add(referenceToAdd);
      // The equivalence hash code will be the hash code of the first
      // inserted weak reference
      equivalenceHashCode = referenceToAdd.getCachedHashCode();
    }

    /**
     * Add a weak refrence to the equivalence set. This method assumes that
     * the reference to add does belong in this set.
     *
     * @param referenceToAdd
     *            The weak reference to add.
     * @return true if the weak reference was added to the set, and false if
     *         the reference already exists in the set.
     */
    public boolean addWeakReference(ImageCacheWeakReference referenceToAdd) {
      // Only add the weak reference if it does not already exist
      ImageCacheWeakReference weakReference = null;
      for (Iterator i = imageCacheWeakReferences.iterator(); i.hasNext();) {
        weakReference = (ImageCacheWeakReference) i.next();
        // "referenceToAdd.get()" will not be null, but
        // "weakReference.get()" could be null, which is ok since we
        // should add the element since its "identity" will be removed
        // shortly.
        if (referenceToAdd.get() == weakReference.get()) {
          return false;
        }
      }
      imageCacheWeakReferences.add(referenceToAdd);
      return true;
    }

    /**
     * Clear the weak references in this equivalence set.
     *
     */
    public void clear() {
      ImageCacheWeakReference currentReference = null;
      for (Iterator i = imageCacheWeakReferences.iterator(); i.hasNext();) {
        currentReference = (ImageCacheWeakReference) i.next();
        // Cleaner thread could've have cleared the reference
        if (currentReference != null) {
          currentReference.clear();
        }
      }
      imageCacheWeakReferences.clear();
    }

    /*
     * (non-Javadoc)
     *
     * @see java.lang.Object#equals(java.lang.Object)
     */
    public boolean equals(Object object) {
      // Two sets are equivalent if their descriptors
      // are "equal"
      ImageDescriptor reachableDescriptor = null;
      if (!(object instanceof EquivalenceSet)) {
        return false;
      }

      // Retrieve an image descriptor in the set of weak references
      // that has not been enqueued
      reachableDescriptor = ((EquivalenceSet) object)
          .getFirstReachableDescriptor();
      if (reachableDescriptor == null) {
        return false;
      }
      // Manipulating descriptors themselves just in case the referent
      // gets cleaned by the time we reach this part.
      return reachableDescriptor.equals(getFirstReachableDescriptor());

    }

    /**
     * Get a non-null image descriptor from the list of weak references to
     * image descriptors.
     *
     * @return a non null image descriptor, or null if none could be found.
     */
    public ImageDescriptor getFirstReachableDescriptor() {
      ImageDescriptor referent = null;
      for (Iterator i = imageCacheWeakReferences.iterator(); i.hasNext();) {
        referent = (ImageDescriptor) ((ImageCacheWeakReference) i
            .next()).get();
        if (referent != null) {
          // return descriptor itself. This way, we have a reference
          // to it and it won't be cleared by the time we return from
          // this method
          return referent;
        }
      }
      // no reachable descriptors found
      return null;
    }

    /**
     * Return the number of items in the list of weak references.
     *
     * @return the number of items in the list of weak references.
     */
    public int getSize() {
      return imageCacheWeakReferences.size();
    }

    /*
     * (non-Javadoc)
     *
     * @see java.lang.Object#hashCode()
     */
    public int hashCode() {
      return equivalenceHashCode;
    }

    /**
     * Remove a hashable weak reference from the list. This method makes no
     * assumptions as to whether the reference to remove belongs in this
     * equivalence set or not.
     *
     * @param referenceToRemove
     *            The weak reference to remove.
     * @return true if the reference was removed succesfully.
     */
    public boolean removeReference(ImageCacheWeakReference referenceToRemove) {
      return imageCacheWeakReferences.remove(referenceToRemove);
    }

  }

  /**
   * A wrapper around the weak reference to imae descriptors in order to be
   * able to store the referrent's hash code since it will be null when
   * enqueued.
   *
   * @since 3.1
   */
  private static final class ImageCacheWeakReference extends WeakReference {
    /**
     * Referent's hash code since it will not be available once the
     * reference has been enqueued.
     */
    private final int referentHashCode;

    /**
     * Creates a weak reference for an image descriptor.
     *
     * @param referent
     *            The image descriptor. Will not be <code>null</code>.
     * @param queue
     *            The reference queue.
     */
    public ImageCacheWeakReference(Object referent, ReferenceQueue queue) {
      super(referent, queue);
      referentHashCode = referent.hashCode();
    }

    /**
     * The referent's cached hash code value.
     *
     * @return the referent's cached hash code value.
     */
    public int getCachedHashCode() {
      return referentHashCode;
    }

  }

  /**
   * An entry in the image map, which consists of the array of images (the
   * value), as well as the key. This allows to retrieve BOTH the key (the
   * equivalence set) and the value (the array of images) from the map
   * directly.
   *
   * @since 3.1
   */
  private static final class ImageMapEntry {
    /**
     * The array of images.
     */
    private final Image[] entryImages;

    /**
     * The equivalence set.
     */
    private final EquivalenceSet entrySet;

    /**
     * Create an entry that consists of the equivalence set (key) as well as
     * the array of images.
     *
     * @param equivalenceSet
     *            The equivalence set.
     * @param images
     *            The array of images.
     */
    public ImageMapEntry(EquivalenceSet equivalenceSet, Image[] images) {
      this.entrySet = equivalenceSet;
      this.entryImages = images;
    }

    /**
     * Return the equivalence set in this entry. Should not be
     * <code>null</code>.
     *
     * @return the entry set.
     */
    public EquivalenceSet getEquivalenceSet() {
      return entrySet;
    }

    /**
     * Return the array of images in this entry. Should not be
     * <code>null</code>.
     *
     * @return the array of images.
     */
    public Image[] getImages() {
      return entryImages;
    }

  }

  /**
   * A thread for cleaning up the reference queues as the garbage collector
   * fills them. It takes an image map and a reference queue. When an item
   * appears in the reference queue, it uses it as a key to remove values from
   * the map. If the value is an array of images, then the defined images in
   * that array are is disposed. To shutdown the thread, call
   * <code>stopCleaning()</code>.
   *
   * @since 3.1
   */
  private static class ReferenceCleanerThread extends Thread {

    /**
     * The number of reference cleaner threads created.
     */
    private static int threads = 0;

    /**
     * A marker indicating that the reference cleaner thread should exit.
     * This is enqueued when the thread is told to stop. Any referenced
     * enqueued after the thread is told to stop will not be cleaned up.
     */
    private final ImageCacheWeakReference endMarker;

    /**
     * A map of equivalence sets to ImageMapEntry (Image[3],
     * EquivalenceSet).
     */
    private final Map imageMap;

    /**
     * The reference queue to check.
     */
    private final ReferenceQueue referenceQueue;

    private final StaleImages staleImages;

    /**
     * Constructs a new instance of <code>ReferenceCleanerThread</code>.
     *
     * @param referenceQueue
     *            The reference queue to check for garbage.
     * @param map
     *            Map of equivalence sets to ImageMapEntry (Image[3],
     *            EquivalenceSet).
     */
    private ReferenceCleanerThread(final ImageCache imageCache) {
      super("Reference Cleaner: " + ++threads); //$NON-NLS-1$

      this.referenceQueue = imageCache.imageReferenceQueue;
      this.imageMap = imageCache.imageMap;
      this.endMarker = new ImageCacheWeakReference(referenceQueue,
          referenceQueue);
      this.staleImages = imageCache.staleImages;
    }

    /**
     * Remove the reference enqueued by iterating through the set of keys in
     * the map.
     *
     * @param currentReference
     *            The current reference.
     */
    private void removeReferenceEnqueued(
        final ImageCacheWeakReference currentReference) {
      EquivalenceSet currentSet = null;
      Set keySet = imageMap.keySet();
      Image[] images = null;

      // Ensure that the image map is locked until the removal of the
      // reference has finished
      synchronized (imageMap) {
        // Traverse the set of keys to find corresponding
        // equivalence set
        for (Iterator i = keySet.iterator(); i.hasNext();) {
          currentSet = (EquivalenceSet) i.next();
          boolean removed = currentSet
              .removeReference(currentReference);
          if (removed) {
            // Clean up needed since the set is now empty
            if (currentSet.getSize() == 0) {
              images = ((ImageMapEntry) imageMap
                  .remove(currentSet)).getImages();
              if (images == null) {
                throw new NullPointerException(
                    "The array of images removed from the map on clean up should not be null."); //$NON-NLS-1$
              }
            }
            // break out of for loop since the reference has
            // been removed
            break;
          }
        }
      }

      // Images need disposal
      if (images != null) {
        staleImages.addImagesToDispose(images);
        // Run async to avoid deadlock from dispose
        Display display = Display.getDefault();
        if (display != null) {
          display.asyncExec(new Runnable() {
            public void run() {
              staleImages.disposeStaleImages();
            }
          });
        }
      }
    }

    /**
     * Wait for new garbage. When new garbage arrives, remove it, clear it,
     * and dispose of any corresponding images.
     */
    public final void run() {
      while (true) {
        // Get the next reference to dispose.
        Reference reference = null;
        // Block until a reference becomes available in the queue
        try {
          reference = referenceQueue.remove();
        } catch (final InterruptedException e) {
          // Reference will be null.
        }

        // Check to see if we've been told to stop.
        if (reference == endMarker) {
          // Clean up the image map
          break;
        }

        // Image disposal - need to traverse the set of keys, since the
        // image descriptor has been cleaned. No way to directly
        // retrieve the equivalence set from the map . This could be
        // improved (with better search/sort).
        if (reference instanceof ImageCacheWeakReference) {
          removeReferenceEnqueued((ImageCacheWeakReference) reference);
        }

        // Clear the reference.
        if (reference != null) {
          reference.clear();
        }

      }
    }

    /**
     * Tells this thread to stop trying to clean up. This is usually run
     * when the cache is shutting down.
     */
    private final void stopCleaning() {
      endMarker.enqueue();
    }
  }

  /**
   * A container class to hold a list of array of images that have been
   * identified as requiring disposal. This class was added to ensure that if
   * the image cache's dispose method is called while the cleaner thread is in
   * the process of cleaning images, stopping the thread will not prevent
   * those images from being disposed. They will be disposed by the image
   * cache's dispose method.
   *
   */
  private static class StaleImages {
    /**
     * List of array of images the require disposal.
     */
    private final List staleImages;

    /**
     * Create the list of stale images.
     *
     */
    public StaleImages() {
      staleImages = Collections.synchronizedList(new ArrayList());
    }

    /**
     * Add the array of images to the list of images to dispose. This is
     * called only from the cleaner thread.
     *
     * @param images
     *            The array of images.
     */
    public void addImagesToDispose(final Image[] images) {
      staleImages.add(images);
    }

    /**
     * Dispose images that require disposal.
     *
     */
    public void disposeStaleImages() {
      Image[] imagesToDispose = null;
      // Ensure only one thread at a time accesses the stale images list
      synchronized (staleImages) {
        for (Iterator i = staleImages.iterator(); i.hasNext();) {
          imagesToDispose = (Image[]) i.next();
          for (int j = 0; j < imagesToDispose.length; j++) {
            final Image image = imagesToDispose[j];
            if ((image != null) && (!image.isDisposed())) {
              image.dispose();
            }
          }
        }
        staleImages.clear();
      }
    }

  }

  /**
   * Types of images supported by the image cache.
   */
  public static final int DISABLE = 0;

  public static final int GRAY = 1;

  public static final int REGULAR = 2;

  private static final int TYPES_OF_IMAGES = 3;

  /**
   * The thread responsible for cleaning out images that are no longer needed.
   * The images in Image[3] will be cleaned if the corresponding equivalence
   * set contains no more weak references to image descriptor.
   */
  private final ReferenceCleanerThread imageCleaner;

  /**
   * A map of equivalence sets to ImageMapEntry (Image[3], EquivalenceSet).
   * The equivalence set represents a list of weakly referenced image
   * descriptors that are equivalent ("equal"). The equivalence set will
   * contain no duplicate image descriptor references (check for identical
   * descriptors on addition using "==").
   */
  private final Map imageMap;

  /**
   * A queue of references (<code>HashableWeakReference</code>) waiting to
   * be garbage collected. This value is never <code>null</code>. This is
   * the queue for <code>imageMap</code>.
   */
  private final ReferenceQueue imageReferenceQueue;

  /**
   * The image to display when no image is available. This value is
   * <code>null</code> until it is first used, and will not get disposed
   * until the image cache itself is disposed.
   */
  private Image missingImage = null;

  /**
   * Stale images that the cleaner thread might not have the opportunity to
   * dispose. The latter images will be disposed by the image cache's dispose.
   */
  private StaleImages staleImages;

  /**
   * Constructs a new instance of <code>ImageCache</code>, and starts a
   * thread to monitor the reference queue for image clean up.
   */
  public ImageCache() {
    imageMap = Collections.synchronizedMap(new HashMap());

    staleImages = new StaleImages();
    imageReferenceQueue = new ReferenceQueue();
    imageCleaner = new ReferenceCleanerThread(this);
    imageCleaner.start();
  }

  /**
   * Constructs a new instance of <code>ImageCache</code>, and starts a
   * thread to monitor the reference queue for image clean up. If the passed
   * initial load capacity is negative, the image map is created with the
   * default <code>HashMap</code> constructor.
   *
   * @param initialLoadCapacity
   *            Initial load capacity for the image hash map.
   */
  public ImageCache(final int initialLoadCapacity) {
    if (initialLoadCapacity < 0) {
      imageMap = Collections.synchronizedMap(new HashMap());
    } else {
      imageMap = Collections.synchronizedMap(new HashMap(
          initialLoadCapacity));
    }

    staleImages = new StaleImages();
    imageReferenceQueue = new ReferenceQueue();
    imageCleaner = new ReferenceCleanerThread(this);
    imageCleaner.start();
  }

  /**
   * Constructs a new instance of <code>ImageCache</code>, and starts a
   * thread to monitor the reference queue for image clean up. If the passed
   * initial load capacity is negative or if the load factor is nonpositive,
   * the image map is created with the default <code>HashMap</code>
   * constructor.
   *
   * @param initialLoadCapacity
   *            Initial load capacity for the image hash map.
   * @param loadFactor
   *            Load factor for the image hash map.
   */
  public ImageCache(final int initialLoadCapacity, final float loadFactor) {
    if (initialLoadCapacity < 0 || loadFactor <= 0) {
      imageMap = Collections.synchronizedMap(new HashMap());
    } else {
      imageMap = Collections.synchronizedMap(new HashMap(
          initialLoadCapacity, loadFactor));
    }

    staleImages = new StaleImages();
    imageReferenceQueue = new ReferenceQueue();
    imageCleaner = new ReferenceCleanerThread(this);
    imageCleaner.start();
  }

  /**
   * Add a new equivalence set to the imag map.
   *
   * @param imageDescriptor
   *            The image descriptor.
   * @param temporaryKey
   *            The temporary key.
   * @param typeOfImage
   *            The type of image requested.
   * @return the requested image, or the missing image if an error occurs in
   *         the creation of the image.
   */
  private Image addNewEquivalenceSet(final ImageDescriptor imageDescriptor,
      EquivalenceSet equivalenceKey, int typeOfImage) {

    // Create the array of images, as well as the regular image
    // since it will be need to create gray or disable
    final Image[] images = new Image[TYPES_OF_IMAGES];
    images[REGULAR] = imageDescriptor.createImage(false);

    // If the image creation fails, returns the missing image
    if (images[REGULAR] == null) {
      // clear the key (this will also clear the reference created)
      equivalenceKey.clear();
      return getMissingImage();

    }
    if (typeOfImage == DISABLE) {
      images[typeOfImage] = new Image(null, images[REGULAR],
          SWT.IMAGE_DISABLE);
    } else if (typeOfImage == GRAY) {
      images[typeOfImage] = new Image(null, images[REGULAR],
          SWT.IMAGE_GRAY);
    }
    // Add the entry to the map
    final ImageMapEntry mapEntry = new ImageMapEntry(equivalenceKey, images);
    imageMap.put(equivalenceKey, mapEntry);
    return images[typeOfImage];
  }

  /**
   * Cleans up all images in the cache. This disposes of all of the images,
   * and drops references to them. This should only be called when the images
   * and the image cache are no longer needed (i.e.: shutdown). Note that the
   * image disposal is handled by the cleaner thread.
   */
  public final void dispose() {
    // Clean up the missing image.
    if ((missingImage != null) && (!missingImage.isDisposed())) {
      missingImage.dispose();
      missingImage = null;
    }

    // Stop the image cleaner thread
    imageCleaner.stopCleaning();
    try {
      imageCleaner.join();
    } catch (InterruptedException e) {
      // Interrupted
    }

    // Clear all the references in the equivalence sets and
    // dispose the corresponding images
    for (Iterator imageItr = imageMap.entrySet().iterator(); imageItr
        .hasNext();) {
      final Map.Entry entry = (Map.Entry) imageItr.next();
      final EquivalenceSet key = (EquivalenceSet) entry.getKey();
      // Dispose the images if they have been created and have
      // not been disposed yet
      final Image[] images = ((ImageMapEntry) entry.getValue())
          .getImages();
      for (int i = 0; i < images.length; i++) {
        final Image image = images[i];
        if ((image != null) && (!image.isDisposed())) {
          image.dispose();
        }
      }

      // Clear all the references in the equivalence set
      key.clear();
    }
    // Clear map
    imageMap.clear();

    // Clean up the stale images that the cleaner thread might have missed
    staleImages.disposeStaleImages();
  }

  /**
   * Returns the regular image for the given image descriptor. This caches the
   * result so that future attempts to get the image for an equivalent or
   * identical image descriptor will only access the cache. When all
   * references to equivalent image descriptors are dropped, the images
   * (regular, gray and disabled) will be cleaned up if they have been
   * created. This clean up makes no guarantees about how long or when it will
   * take place.
   *
   * @param descriptor
   *            The image descriptor with which a regular image should be
   *            created; may be <code>null</code>.
   * @return The regular image, either newly created or from the cache. This
   *         value is <code>null</code> if the image descriptor passed in is
   *         <code>null</code>. Note that a missing image will be returned
   *         if a problem occurs in the creation of the image.
   */
  public final Image getImage(final ImageDescriptor imageDescriptor) {
    return getImage(imageDescriptor, REGULAR);
  }

  /**
   * Returns the requested image for the given image descriptor and image
   * type. This caches the result so that future attempts to get the image for
   * an equivalent or identical image descriptor will only access the cache.
   * When all references to equivalent image descriptors are dropped, the
   * images (regular, gray and disabled) will be cleaned up if they have been
   * created. This clean up makes no guarantees about how long or when it will
   * take place.
   *
   * @param descriptor
   *            The image descriptor with which the requested image should be
   *            created; may be <code>null</code>.
   * @param typeOfImage
   *            The type of the desired image:
   *            <code>ImageCache.DISABLED</code>,
   *            <code>ImageCache.GRAY</code> or
   *            <code>ImageCache.NORMAL</code>.
   * @return The image for the requested image type, either newly created or
   *         from the cache. This value is <code>null</code> if the image
   *         descriptor passed in is <code>null</code>, or if the image
   *         type is invalid. Note that a missing image will be returned if a
   *         problem occurs in the creation of the image.
   */
  public final Image getImage(final ImageDescriptor imageDescriptor,
      final int typeOfImage) {
    // Invalid descriptor
    if (imageDescriptor == null) {
      return null;
    }
    // Invalid type of image
    if (typeOfImage < 0 || !(typeOfImage < TYPES_OF_IMAGES)) {
      return null;
    }
    // Created a temporary key to query the image map
    ImageCacheWeakReference referencedToAdd = new ImageCacheWeakReference(
        imageDescriptor, imageReferenceQueue);
    EquivalenceSet temporaryKey = new EquivalenceSet(referencedToAdd);

    Image imageToReturn = null;

    // Ensure that the image map is locked until the retrieving of the image
    // process is finished
    synchronized (imageMap) {
      // Retrieve the corresponding entry in the map
      ImageMapEntry mapEntry = (ImageMapEntry) imageMap.get(temporaryKey);
      if (mapEntry != null) {
        // The entry was found, retrieve the image from cache, or
        // create it if it has not been created yet
        imageToReturn = getImageFromEquivalenceSet(imageDescriptor,
            mapEntry, referencedToAdd, typeOfImage);
      } else {
        // The entry was not found, create it.
        imageToReturn = addNewEquivalenceSet(imageDescriptor,
            temporaryKey, typeOfImage);
      }
    }
    return imageToReturn;

  }

  /**
   * Retrieve the image from the cache, or create it if it has not been
   * created yet.
   *
   * @param imageDescriptor
   *            The image descriptor.
   * @param mapEntry
   *            The mape entry.
   * @param referenceToAdd
   *            The weak reference to add.
   * @param typeOfImage
   *            The type of image to create.
   * @return the requested image, or the missing image if an error occurs in
   *         the creation of the image.
   */
  private Image getImageFromEquivalenceSet(ImageDescriptor imageDescriptor,
      ImageMapEntry mapEntry, ImageCacheWeakReference referenceToAdd,
      int typeOfImage) {

    final Image[] images = mapEntry.getImages();
    final EquivalenceSet equivalenceKey = mapEntry.getEquivalenceSet();

    // Add the weak reference to the equivalence set
    boolean added = equivalenceKey.addWeakReference(referenceToAdd);
    if (!added) {
      // The identical reference already exists in the set, clear it
      referenceToAdd.clear();
    }
    // If the type of image requested is cached
    if (images[typeOfImage] != null) {
      return images[typeOfImage];
    }

    // Regular image shoudl not be null, since it gets created when the set
    // is created
    if (images[REGULAR] == null) {
      throw new NullPointerException(
          "The normal image from the equivalence set should not be null.");//$NON-NLS-1$
    }

    if (typeOfImage == GRAY) {
      images[typeOfImage] = new Image(null, images[REGULAR],
          SWT.IMAGE_GRAY);
    } else if (typeOfImage == DISABLE) {
      images[typeOfImage] = new Image(null, images[REGULAR],
          SWT.IMAGE_DISABLE);
    }
    return images[typeOfImage];
  }

  /**
   * Returns the image to display when no image can be found, or none is
   * specified. This image is only disposed when the cache is disposed.
   *
   * @return The image to display for missing images. This value will never be
   *         <code>null</code>.
   */
  public final Image getMissingImage() {
    // Ensure that the missing image is not being accessed by another thread
    if (missingImage == null) {
      missingImage = ImageDescriptor.getMissingImageDescriptor()
          .createImage();
    }

    return missingImage;
  }

}
TOP

Related Classes of org.locationtech.udig.ui.ImageCache

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.