net.refractions.udig.ui.ImageCache.java Source code

Java tutorial

Introduction

Here is the source code for net.refractions.udig.ui.ImageCache.java

Source

/*******************************************************************************
 * 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 net.refractions.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;
    }

}