package org.xmind.ui.resources;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.graphics.Device;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Display;
/**
* Image references are used to cache SWT Image objects to share across the
* entire application to reduce memory/handle usage, which is useful when a
* large image is shown in two or more places within one application, e.g. in a
* duplicated editor.
* <p>
* The first call to any image reference's <code>getImage()</code> method will
* create the underlying Image resource. Any subsequent calls to any image
* reference's <code>getImage()</code> will re-use the created image to avoid
* creating duplicated operating system resource.
* </p>
* <p>
* <b>IMPORTANT: It's client's responsibility to dispose an image reference when
* it's not in service any more, but remember NOT to dispose a referenced image
* object on your own!</b> An image's resource will be automatically released
* when all references registered with it are disposed.
* </p>
* <p>
* Note that an image reference relies on an ImageDescriptor object to describe
* an image to registered with, so make sure two ImageDescriptor objects
* <i>equals</i> each other if they describe the same image.
* </p>
*
* @author Frank Shaka <frank@xmind.net>
*/
public class ImageReference {
/**
* An image cache holds the cached image and all its references.
*/
private static class ImageCache {
private ImageDescriptor descriptor;
private Device device;
private boolean returnMissingImageOnError;
private Image image = null;
private List<ImageReference> refs = new ArrayList<ImageReference>();
public ImageCache(ImageDescriptor descriptor,
boolean returnMissingImageOnError, Device device) {
this.descriptor = descriptor;
this.device = device;
this.returnMissingImageOnError = returnMissingImageOnError;
}
public void register(ImageReference ref) {
synchronized (this) {
refs.add(ref);
}
}
public void unregister(ImageReference ref) {
synchronized (this) {
refs.remove(ref);
if (refs.isEmpty()) {
disposeImage();
}
}
}
private void disposeImage() {
Image oldImage = this.image;
this.image = null;
if (oldImage != null) {
oldImage.dispose();
}
}
public synchronized Image getImage() {
if (image == null || image.isDisposed()) {
image = descriptor.createImage(returnMissingImageOnError,
device);
}
return image;
}
}
/**
* The global cache registry.
*/
private static Map<ImageDescriptor, ImageCache> caches = new HashMap<ImageDescriptor, ImageCache>();
/**
* The corresponding cache object.
*/
private ImageCache cache;
/**
* The dispose state of this reference.
*/
private boolean disposed = false;
/**
* Create a new image reference and register it with an image described by
* an ImageDescriptor. This constructor must be called when there's at least
* one SWT display instance available.
*
* @param descriptor
* describes the image to register with
* @param returnMissingImageOnError
* a flag that determines if a default image is returned on error
* @exception IllegalStateException
* if there's no SWT display available
*/
public ImageReference(ImageDescriptor descriptor,
boolean returnMissingImageOnError) {
this(descriptor, returnMissingImageOnError, findAvailableDevice());
}
/**
* Create a new image reference and register it with an image described by
* an ImageDescriptor.
*
* @param descriptor
* describes the image to register with
* @param returnMissingImageOnError
* a flag that determines if a default image is returned on error
* @param device
* the device on which the image will be created
*/
public ImageReference(ImageDescriptor descriptor,
boolean returnMissingImageOnError, Device device) {
this.cache = getImageCache(descriptor, returnMissingImageOnError,
device);
this.cache.register(this);
}
/**
* Returns the image, which will be created on the first call.
*
* @return the cached image
* @exception SWTException
* <ul>
* <li>ERROR_GRAPHIC_DISPOSED - if the reference has been
* disposed</li>
* </ul>
*/
public Image getImage() {
if (disposed)
throw new SWTException(SWT.ERROR_GRAPHIC_DISPOSED);
return cache.getImage();
}
/**
* Returns the object describing the registered image.
*
* @return the image descriptor
*/
public ImageDescriptor getImageDescriptor() {
return cache.descriptor;
}
/**
* Determines whether a default image will be returned on error
*
* @return <code>true</code> if a default image will be returned on error,
* <code>false</code> otherwise
*/
public boolean returnsMissingImageOnError() {
return cache.returnMissingImageOnError;
}
/**
* Returns the device on which the image will be created.
*
* @return the device on which the image will be created
*/
public Device getDevice() {
return cache.device;
}
/**
* Returns <code>true</code> if the image reference has been disposed, and
* <code>false</code> otherwise.
* <p>
* This method gets the dispose state for the image reference. When an image
* reference has been disposed, it is an error to invoke any other method
* (except {@link #dispose()}) using the image reference.
*
* @return <code>true</code> when the image reference is disposed and
* <code>false</code> otherwise
*/
public boolean isDisposed() {
return disposed;
}
/**
* Unregisters this reference and, if there's no references then registered
* with the image, the image's related operating system resource will be
* released. Applications must dispose of all references which they
* allocate.
* <p>
* This method does nothing if the reference is already disposed.
*/
public void dispose() {
synchronized (this) {
if (disposed)
return;
disposed = true;
cache.unregister(this);
}
}
/**
* Finds or create an image cache for an image reference to register.
*
* @param descriptor
* describes the image to register with
* @param returnMissingImageOnError
* a flag that determines if a default image is returned on error
* @param device
* the device on which the image will be created
* @return an image cache corresponding to the image descriptor
*/
private synchronized static ImageCache getImageCache(
ImageDescriptor descriptor, boolean returnMissingImageOnError,
Device device) {
ImageCache cache = caches.get(descriptor);
if (cache == null) {
cache = new ImageCache(descriptor, returnMissingImageOnError,
device);
caches.put(descriptor, cache);
}
return cache;
}
/**
* Find an available device to create image with.
*
* @return a device found to create image with
* @exception IllegalStateException
* if there's no SWT display available
*/
private static Device findAvailableDevice() {
Device device = Display.getCurrent();
if (device == null) {
device = Display.getDefault();
}
if (device == null)
throw new IllegalStateException("No display available"); //$NON-NLS-1$
return device;
}
}