package org.marketcetera.photon.commons.ui;
import java.util.Collection;
import java.util.EnumSet;
import java.util.Map;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import org.eclipse.jface.resource.ColorDescriptor;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.resource.LocalResourceManager;
import org.eclipse.jface.resource.ResourceManager;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Device;
import org.eclipse.swt.widgets.Display;
import org.marketcetera.photon.commons.Validate;
import org.marketcetera.util.misc.ClassVersion;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
/* $License$ */
/**
* Manages colors in a thread safe way. There are only two states for this
* object:
* <ol>
* <li>not initialized - getColor returns null for all ColorDescriptors and
* IColorDescriptorProviders.</li>
* <li>initialized - getColor returns a valid Color for all ColorDescriptors
* that were used to create the manager.</li>
* </ol>
*
* @author <a href="mailto:will@marketcetera.com">Will Horn</a>
* @version $Id: ColorManager.java 16154 2012-07-14 16:34:05Z colin $
* @since 2.0.0
*/
@ThreadSafe
@ClassVersion("$Id: ColorManager.java 16154 2012-07-14 16:34:05Z colin $")
public class ColorManager {
/**
* An object that provides a {@link ColorDescriptor}.
*/
@ClassVersion("$Id: ColorManager.java 16154 2012-07-14 16:34:05Z colin $")
public interface IColorDescriptorProvider {
/**
* Returns a ColorDescriptor that can be used to create a {@link Color}.
*
* @return the color descriptor
*/
ColorDescriptor getDescriptor();
}
/**
* Creates a ColorManager from an enum that implements
* {@link IColorDescriptorProvider}.
*
* @param <E>
* the enum type
* @param clazz
* the enum class
* @return a ColorManager for the ColorDescriptors provided by the enum
* instances
* @throws IllegalStateException
* if any enum instance provides a null ColorDescriptor
*/
public static <E extends Enum<E> & IColorDescriptorProvider> ColorManager createForEnum(Class<E> clazz) {
Collection<IColorDescriptorProvider> providers = Lists.newArrayList();
for (IColorDescriptorProvider provider : EnumSet.allOf(clazz)) {
providers.add(provider);
}
return createForProviders(providers);
}
/**
* Creates a ColorManager from an collection of IColorDescriptorProviders.
*
* @param providers
* the IColorDescriptorProviders that provide the
* ColorDescriptors
* @return a ColorManager for the ColorDescriptors provided by the providers
* @throws IllegalArgumentException
* if any provider is null or provides a null ColorDescriptor
*/
public static ColorManager createForProviders(
Collection<IColorDescriptorProvider> providers) {
Validate.noNullElements(providers, "providers"); //$NON-NLS-1$
Collection<ColorDescriptor> descriptors = Lists.newArrayList();
for (IColorDescriptorProvider provider : providers) {
final ColorDescriptor descriptor = provider.getDescriptor();
if (descriptor == null) {
throw new IllegalArgumentException(
Messages.COLOR_MANAGER_PROVIDER_NULL_DESCRIPTOR
.getText(provider));
}
descriptors.add(descriptor);
}
return createFor(descriptors);
}
/**
* Creates a ColorManager from an collection of ColorDescriptors.
*
* @param descriptors
* the ColorDescriptors to manage
* @return a ColorManager for the provided ColorDescriptors
* @throws IllegalArgumentException
* if descriptors is null or contains any null elements
*/
public static ColorManager createFor(Collection<ColorDescriptor> descriptors) {
Validate.noNullElements(descriptors, "descriptors"); //$NON-NLS-1$
return new ColorManager(descriptors);
}
/**
* Returns the Color for the provider's descriptor. Clients must not dispose
* the returned Color.
*
* @param provider
* the color descriptor provider
* @return the color, or null if the manager is not initialized
* @throws IllegalArgumentException
* if the provider is null, if the provider's descriptor is
* null, or if the provider's descriptor is not managed by this
* class
*/
public Color getColor(IColorDescriptorProvider provider) {
Validate.notNull(provider, "provider"); //$NON-NLS-1$
return getColor(provider.getDescriptor());
}
/**
* Returns the Color for the color descriptor.
*
* @param descriptor
* the color descriptor
* @return the color, or null if the manager is not initialized
* @throws IllegalArgumentException
* if the descriptor is null, or is not managed by this class
*/
public Color getColor(ColorDescriptor descriptor) {
Validate.notNull(descriptor, "descriptor"); //$NON-NLS-1$
if (!mColorDescriptors.contains(descriptor)) {
throw new IllegalArgumentException(
Messages.COLOR_MANAGER_UNKNOWN_DESCRIPTOR
.getText(descriptor));
}
synchronized (this) {
if (mResources == null) {
return null;
} else {
return mResources.get(descriptor);
}
}
}
/**
* Initializes the colors for the current {@link Display}. Calling multiple
* times on the same thread is a no-op unless the current display has
* changed, in which case an IllegalStateException will be thrown. The
* exception can be avoided by first calling {@link #disposeColors()};
* <p>
* If this method completes successfully, {@link #getColor(ColorDescriptor)} will return a
* valid color for each ColorDescriptor that was used to instantiate this
* manager.
*
* @throws IllegalStateException
* if called from a non-UI thread, i.e. one for which
* Display.getCurrent() is null
* @throws IllegalStateException
* on subsequent invocations from the same thread if
* Display.getCurrent() has changed since the previous call
*/
public void initColors() {
SWTUtils.checkThread();
Display display = Display.getCurrent();
synchronized (this) {
if (mResources != null) {
if (!mResources.isInitializedFor(display)) {
throw new IllegalStateException(
Messages.COLOR_MANAGER_ALREADY_INITIALIZED
.getText());
} else {
return;
}
}
mResources = new Resources(JFaceResources.getResources(display));
}
}
/**
* Disposes the colors. After this method completes, {@link #getColor(ColorDescriptor)}
* will return null for all ColorDescriptors and {@link #initColors()} may
* be safely called again on any UI thread.
* <p>
* This will automatically be called during disposal of the initializing
* thread's {@link Display}, but can be called sooner to clean up resource
* handles.
* <p>
* Successive calls are a no-op if {@link #initColors()} is not called.
*/
public synchronized void disposeColors() {
if (mResources != null) {
mResources.dispose();
mResources = null;
}
}
/**
* the color descriptors being managed
*/
private final ImmutableCollection<ColorDescriptor> mColorDescriptors;
/**
* Constructor.
*
* @param descriptors
* the color descriptors to manage
*/
private ColorManager(Collection<ColorDescriptor> descriptors) {
mColorDescriptors = ImmutableSet.copyOf(descriptors);
}
/**
* mutable state
*/
@GuardedBy("this")
private Resources mResources;
/**
* Encapsulates mutable state. This object can be disposed manually via
* {@link #dispose()} or automatically with the root {@link ResourceManager}
* provided in the constructor.
*/
@ClassVersion("$Id: ColorManager.java 16154 2012-07-14 16:34:05Z colin $")
private final class Resources {
private final Map<ColorDescriptor, Color> mColors;
private final ResourceManager mRootResourceManager;
private final ResourceManager mLocalResourceManager;
private final Runnable mDisposeRunnable = new Runnable() {
@Override
public void run() {
// dispose the ColorManager which will safely dispose this
// Resources instance and clean up the thread local state
disposeColors();
}
};
/**
* Constructor. If construction completes successfully, then this object
* will provide valid colors via {@link #get(ColorDescriptor)}. If
* construction fails, there are no side effects.
*
* @param rootResourceManager
* root resource manager to use for creating colors
*/
public Resources(ResourceManager rootResourceManager) {
mRootResourceManager = rootResourceManager;
mLocalResourceManager = new LocalResourceManager(
rootResourceManager);
mColors = Maps.newHashMap();
boolean success = false;
try {
for (ColorDescriptor descriptor : mColorDescriptors) {
mColors.put(descriptor, mLocalResourceManager
.createColor(descriptor));
}
mRootResourceManager.disposeExec(mDisposeRunnable);
success = true;
} finally {
if (!success) {
dispose();
}
}
}
public boolean isInitializedFor(Device device) {
return mLocalResourceManager.getDevice().equals(device);
}
public Color get(ColorDescriptor descriptor) {
return mColors.get(descriptor);
}
public void dispose() {
// this may be called from the dispose runnable, but it is safe to
// call cancel regardless
mRootResourceManager.cancelDisposeExec(mDisposeRunnable);
mLocalResourceManager.dispose();
}
}
}