blob: 14df30fd7203c7e8ac499ff7a63d5eb80f58ef84 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004, 2006 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jface.resource;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.util.Policy;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Device;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.RGB;
/**
* This class manages SWT resources. It manages reference-counted instances of resources
* such as Fonts, Images, and Colors, and allows them to be accessed using descriptors.
* Everything allocated through the registry should also be disposed through the registry.
* Since the resources are shared and reference counted, they should never be disposed
* directly.
* <p>
* ResourceManager handles correct allocation and disposal of resources. It differs from
* the various JFace *Registry classes, which also map symbolic IDs onto resources. In
* general, you should use a *Registry class to map IDs onto descriptors, and use a
* ResourceManager to convert the descriptors into real Images/Fonts/etc.
* </p>
*
* @since 3.1
*/
public abstract class ResourceManager {
/**
* List of Runnables scheduled to run when the ResourceManager is disposed.
* null if empty.
*/
private List disposeExecs = null;
/**
* Returns the Device for which this ResourceManager will create resources
*
* @since 3.1
*
* @return the Device associated with this ResourceManager
*/
public abstract Device getDevice();
/**
* Returns the resource described by the given descriptor. If the resource already
* exists, the reference count is incremented and the exiting resource is returned.
* Otherwise, a new resource is allocated. Every call to this method should have
* a corresponding call to {@link #destroy(DeviceResourceDescriptor)}.
*
* <p>If the resource is intended to live for entire lifetime of the resource manager,
* a subsequent call to {@link #destroy(DeviceResourceDescriptor)} may be omitted and the
* resource will be cleaned up when the resource manager is disposed. This pattern
* is useful for short-lived {@link LocalResourceManager}s, but should never be used
* with the global resource manager since doing so effectively leaks the resource.</p>
*
* <p>The resources returned from this method are reference counted and may be shared
* internally with other resource managers. They should never be disposed outside of the
* ResourceManager framework, or it will cause exceptions in other code that shares
* them. For example, never call {@link org.eclipse.swt.graphics.Resource#dispose()}
* on anything returned from this method.</p>
*
* <p>Callers may safely downcast the result to the resource type associated with
* the descriptor. For example, when given an ImageDescriptor, the return
* value of this method will always be an Image.</p>
*
* @since 3.1
*
* @param descriptor descriptor for the resource to allocate
* @return the newly allocated resource (not null)
* @throws DeviceResourceException if unable to allocate the resource
*/
public abstract Object create(DeviceResourceDescriptor descriptor);
/**
* Deallocates a resource previously allocated by {@link #create(DeviceResourceDescriptor)}.
* Descriptors are compared by equality, not identity. If the same resource was
* created multiple times, this may decrement a reference count rather than
* disposing the actual resource.
*
* @since 3.1
*
* @param descriptor identifier for the resource
*/
public abstract void destroy(DeviceResourceDescriptor descriptor);
/**
* <p>Returns a previously-allocated resource or allocates a new one if none
* exists yet. The resource will remain allocated for at least the lifetime
* of this resource manager. If necessary, the resource will be deallocated
* automatically when the resource manager is disposed.</p>
*
* <p>The resources returned from this method are reference counted and may be shared
* internally with other resource managers. They should never be disposed outside of the
* ResourceManager framework, or it will cause exceptions in other code that shares
* them. For example, never call {@link org.eclipse.swt.graphics.Resource#dispose()}
* on anything returned from this method.</p>
*
* <p>
* Callers may safely downcast the result to the resource type associated with
* the descriptor. For example, when given an ImageDescriptor, the return
* value of this method may be downcast to Image.
* </p>
*
* <p>
* This method should only be used for resources that should remain
* allocated for the lifetime of the resource manager. To allocate shorter-lived
* resources, manage them with <code>create</code>, and <code>destroy</code>
* rather than this method.
* </p>
*
* <p>
* This method should never be called on the global resource manager,
* since all resources will remain allocated for the lifetime of the app and
* will be effectively leaked.
* </p>
*
* @param descriptor identifier for the requested resource
* @return the requested resource. Never null.
* @throws DeviceResourceException if the resource does not exist yet and cannot
* be created for any reason.
*
* @since 3.3
*/
public final Object get(DeviceResourceDescriptor descriptor) {
Object result = find(descriptor);
if (result == null) {
result = create(descriptor);
}
return result;
}
/**
* <p>Creates an image, given an image descriptor. Images allocated in this manner must
* be disposed by {@link #destroyImage(ImageDescriptor)}, and never by calling
* {@link Image#dispose()}.</p>
*
* <p>
* If the image is intended to remain allocated for the lifetime of the ResourceManager,
* the call to destroyImage may be omitted and the image will be cleaned up automatically
* when the ResourceManager is disposed. This should only be done with short-lived ResourceManagers,
* as doing so with the global manager effectively leaks the resource.
* </p>
*
* @since 3.1
*
* @param descriptor descriptor for the image to create
* @return the Image described by this descriptor (possibly shared by other equivalent
* ImageDescriptors)
* @throws DeviceResourceException if unable to allocate the Image
*/
public final Image createImage(ImageDescriptor descriptor) {
// Assertion added to help diagnose client bugs. See bug #83711 and bug #90454.
Assert.isNotNull(descriptor);
return (Image)create(descriptor);
}
/**
* Creates an image, given an image descriptor. Images allocated in this manner must
* be disposed by {@link #destroyImage(ImageDescriptor)}, and never by calling
* {@link Image#dispose()}.
*
* @since 3.1
*
* @param descriptor descriptor for the image to create
* @return the Image described by this descriptor (possibly shared by other equivalent
* ImageDescriptors)
*/
public final Image createImageWithDefault(ImageDescriptor descriptor) {
if (descriptor == null) {
return getDefaultImage();
}
try {
return (Image) create(descriptor);
} catch (DeviceResourceException e) {
Policy.getLog().log(
new Status(IStatus.WARNING, "org.eclipse.jface", 0, //$NON-NLS-1$
"The image could not be loaded: " + descriptor, //$NON-NLS-1$
e));
return getDefaultImage();
} catch (SWTException e) {
Policy.getLog().log(
new Status(IStatus.WARNING, "org.eclipse.jface", 0, //$NON-NLS-1$
"The image could not be loaded: " + descriptor, //$NON-NLS-1$
e));
return getDefaultImage();
}
}
/**
* Returns the default image that will be returned in the event that the intended
* image is missing.
*
* @since 3.1
*
* @return a default image that will be returned in the event that the intended
* image is missing.
*/
protected abstract Image getDefaultImage();
/**
* Undoes everything that was done by {@link #createImage(ImageDescriptor)}.
*
* @since 3.1
*
* @param descriptor identifier for the image to dispose
*/
public final void destroyImage(ImageDescriptor descriptor) {
destroy(descriptor);
}
/**
* Allocates a color, given a color descriptor. Any color allocated in this
* manner must be disposed by calling {@link #destroyColor(ColorDescriptor)},
* or by an eventual call to {@link #dispose()}. {@link Color#dispose()} must
* never been called directly on the returned color.
*
* @since 3.1
*
* @param descriptor descriptor for the color to create
* @return the Color described by the given ColorDescriptor (not null)
* @throws DeviceResourceException if unable to create the color
*/
public final Color createColor(ColorDescriptor descriptor) {
return (Color)create(descriptor);
}
/**
* Allocates a color, given its RGB value. Any color allocated in this
* manner must be disposed by calling {@link #destroyColor(RGB)},
* or by an eventual call to {@link #dispose()}. {@link Color#dispose()} must
* never been called directly on the returned color.
*
* @since 3.1
*
* @param descriptor descriptor for the color to create
* @return the Color described by the given ColorDescriptor (not null)
* @throws DeviceResourceException if unable to create the color
*/
public final Color createColor(RGB descriptor) {
return createColor(new RGBColorDescriptor(descriptor));
}
/**
* Undoes everything that was done by a call to {@link #createColor(RGB)}.
*
* @since 3.1
*
* @param descriptor RGB value of the color to dispose
*/
public final void destroyColor(RGB descriptor) {
destroyColor(new RGBColorDescriptor(descriptor));
}
/**
* Undoes everything that was done by a call to {@link #createColor(ColorDescriptor)}.
*
*
* @since 3.1
*
* @param descriptor identifier for the color to dispose
*/
public final void destroyColor(ColorDescriptor descriptor) {
destroy(descriptor);
}
/**
* Returns the Font described by the given FontDescriptor. Any Font
* allocated in this manner must be deallocated by calling disposeFont(...),
* or by an eventual call to {@link #dispose()}. The method {@link Font#dispose()}
* must never be called directly on the returned font.
*
* @since 3.1
*
* @param descriptor description of the font to create
* @return the Font described by the given descriptor
* @throws DeviceResourceException if unable to create the font
*/
public final Font createFont(FontDescriptor descriptor) {
return (Font)create(descriptor);
}
/**
* Undoes everything that was done by a previous call to {@link #createFont(FontDescriptor)}.
*
* @since 3.1
*
* @param descriptor description of the font to destroy
*/
public final void destroyFont(FontDescriptor descriptor) {
destroy(descriptor);
}
/**
* Disposes any remaining resources allocated by this manager.
*/
public void dispose() {
if (disposeExecs == null) {
return;
}
// If one of the runnables throws an exception, we need to propagate it.
// However, this should not prevent the remaining runnables from being
// notified. If any runnables throw an exception, we remember one of them
// here and throw it at the end of the method.
RuntimeException foundException = null;
Runnable[] execs = (Runnable[]) disposeExecs.toArray(new Runnable[disposeExecs.size()]);
for (int i = 0; i < execs.length; i++) {
Runnable exec = execs[i];
try {
exec.run();
} catch (RuntimeException e) {
// Ensure that we propagate an exception, but don't stop notifying
// the remaining runnables.
foundException = e;
}
}
if (foundException != null) {
// If any runnables threw an exception, propagate one of them.
throw foundException;
}
}
/**
* Returns a previously allocated resource associated with the given descriptor, or
* null if none exists yet.
*
* @since 3.1
*
* @param descriptor descriptor to find
* @return a previously allocated resource for the given descriptor or null if none.
*/
public abstract Object find(DeviceResourceDescriptor descriptor);
/**
* Causes the <code>run()</code> method of the runnable to
* be invoked just before the receiver is disposed. The runnable
* can be subsequently canceled by a call to <code>cancelDisposeExec</code>.
*
* @param r runnable to execute.
*/
public void disposeExec(Runnable r) {
Assert.isNotNull(r);
if (disposeExecs == null) {
disposeExecs = new ArrayList();
}
disposeExecs.add(r);
}
/**
* Cancels a runnable that was previously scheduled with <code>disposeExec</code>.
* Has no effect if the given runnable was not previously registered with
* disposeExec.
*
* @param r runnable to cancel
*/
public void cancelDisposeExec(Runnable r) {
Assert.isNotNull(r);
if (disposeExecs == null) {
return;
}
disposeExecs.remove(r);
if (disposeExecs.isEmpty()) {
disposeExecs = null;
}
}
}