# HG changeset patch # User Frank Benoit # Date 1206489439 -3600 # Node ID 6518c18a01f7c834a5cd1a9ccfc3040cea2b0d3c # Parent a012107a911c4c22389a2ec98241c75e11beaa83 eclipse.core package without osgi dependencies diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/AbstractHandler.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/AbstractHandler.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,129 @@ +/******************************************************************************* + * 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.commands.AbstractHandler; + +import dwtx.core.commands.common.EventManager; +import dwtx.core.commands.IHandler; +import dwtx.core.commands.IHandlerListener; +import dwtx.core.commands.HandlerEvent; + +import dwt.dwthelper.utils; + +/** + *

+ * This class is a partial implementation of IHandler. This + * abstract implementation provides support for handler listeners. You should + * subclass from this method unless you want to implement your own listener + * support. Subclasses should call + * {@link AbstractHandler#fireHandlerChanged(HandlerEvent)}when the handler + * changes. Subclasses can also override {@link AbstractHandler#isEnabled()} and + * {@link AbstractHandler#isHandled()}. + *

+ * + * @since 3.1 + */ +public abstract class AbstractHandler : EventManager, IHandler { + + /** + * @see IHandler#addHandlerListener(IHandlerListener) + */ + public void addHandlerListener(IHandlerListener handlerListener) { + addListenerObject(cast(Object)handlerListener); + } + + /** + * The default implementation does nothing. Subclasses who attach listeners + * to other objects are encouraged to detach them in this method. + * + * @see dwtx.core.commands.IHandler#dispose() + */ + public void dispose() { + // Do nothing. + } + + /** + * Fires an event to all registered listeners describing changes to this + * instance. + *

+ * Subclasses may extend the definition of this method (i.e., if a different + * type of listener can be attached to a subclass). This is used primarily + * for support of AbstractHandler in + * dwtx.ui.workbench, and clients should be wary of + * overriding this behaviour. If this method is overridden, then the first + * line of the method should be "super.fireHandlerChanged(handlerEvent);". + *

+ * + * @param handlerEvent + * the event describing changes to this instance. Must not be + * null. + */ + protected void fireHandlerChanged(HandlerEvent handlerEvent) { + if (handlerEvent is null) { + throw new NullPointerException(); + } + + Object[] listeners = getListeners(); + for (int i = 0; i < listeners.length; i++) { + IHandlerListener listener = cast(IHandlerListener) listeners[i]; + listener.handlerChanged(handlerEvent); + } + } + + /** + * Whether this handler is capable of executing at this time. Subclasses may + * override this method. + * + * @return true + */ + public bool isEnabled() { + return true; + } + + /** + * Whether this handler is capable of handling delegated responsibilities at + * this time. Subclasses may override this method. + * + * @return true + */ + public bool isHandled() { + return true; + } + + /** + *

+ * Returns true iff there is one or more IHandlerListeners attached to this + * AbstractHandler. + *

+ *

+ * Subclasses may extend the definition of this method (i.e., if a different + * type of listener can be attached to a subclass). This is used primarily + * for support of AbstractHandler in + * dwtx.ui.workbench, and clients should be wary of + * overriding this behaviour. If this method is overridden, then the return + * value should include "super.hasListeners() ||". + *

+ * + * @return true iff there is one or more IHandlerListeners attached to this + * AbstractHandler + */ + protected bool hasListeners() { + return isListenerAttached(); + } + + /** + * @see IHandler#removeHandlerListener(IHandlerListener) + */ + public void removeHandlerListener(IHandlerListener handlerListener) { + removeListenerObject(cast(Object)handlerListener); + } +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/AbstractHandlerWithState.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/AbstractHandlerWithState.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,131 @@ +/******************************************************************************* + * Copyright (c) 2005, 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ + +module dwtx.core.commands.AbstractHandlerWithState; + +import dwtx.core.commands.AbstractHandler; +import dwtx.core.commands.IObjectWithState; +import dwtx.core.commands.IStateListener; +import dwtx.core.commands.State; + +import dwt.dwthelper.utils; + +import tango.util.collection.HashMap; +import tango.util.collection.model.Map; +import tango.util.collection.model.Set; + +/** + *

+ * An abstract implementation of {@link IObjectWithState}. This provides basic + * handling for adding and remove state. When state is added, the handler + * attaches itself as a listener and fire a handleStateChange event to notify + * this handler. When state is removed, the handler removes itself as a + * listener. + *

+ *

+ * Clients may extend this class. + *

+ * + * @since 3.2 + */ +public abstract class AbstractHandlerWithState : AbstractHandler, + IObjectWithState, IStateListener { + + /** + * The map of states currently held by this handler. If this handler has no + * state (generally, when inactive), then this will be null. + */ + private Map!(String,State) states = null; + + /** + *

+ * Adds a state to this handler. This will add this handler as a listener to + * the state, and then fire a handleStateChange so that the handler can + * respond to the incoming state. + *

+ *

+ * Clients may extend this method, but they should call this super method + * first before doing anything else. + *

+ * + * @param stateId + * The identifier indicating the type of state being added; must + * not be null. + * @param state + * The state to add; must not be null. + */ + public void addState(String stateId, State state) { + if (state is null) { + throw new NullPointerException("Cannot add a null state"); //$NON-NLS-1$ + } + + if (states is null) { + states = new HashMap!(String,State)(/+3+/); + } + states.add(stateId, state); + state.addListener(this); + handleStateChange(state, null); + } + + public final State getState(String stateId) { + if ((states is null) || (states.drained())) { + return null; + } + + return cast(State) states.get(stateId); + } + + public final String[] getStateIds() { + if ((states is null) || (states.drained())) { + return null; + } + + String[] res; + foreach( k, v; states.keys() ){ + res ~= k; + } + return res; + } + + /** + *

+ * Removes a state from this handler. This will remove this handler as a + * listener to the state. No event is fired to notify the handler of this + * change. + *

+ *

+ * Clients may extend this method, but they should call this super method + * first before doing anything else. + *

+ * + * @param stateId + * The identifier of the state to remove; must not be + * null. + */ + public void removeState(String stateId) { + if (stateId is null) { + throw new NullPointerException("Cannot remove a null state"); //$NON-NLS-1$ + } + + State state = cast(State) states.get(stateId); + if (state !is null) { + state.removeListener(this); + if (states !is null) { + states.remove(state); + if (states.drained()) { + states = null; + } + } + } + } +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/AbstractParameterValueConverter.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/AbstractParameterValueConverter.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,76 @@ +/******************************************************************************* + * Copyright (c) 2005, 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.commands.AbstractParameterValueConverter; + +import dwt.dwthelper.utils; + +/** + *

+ * Supports conversion between objects and strings for command parameter values. + * Extenders must produce strings that identify objects (of a specific command + * parameter type) as well as consume the strings to locate and return the + * objects they identify. + *

+ *

+ * This class offers multiple handlers of a command a consistent way of + * converting string parameter values into the objects that the handlers would + * prefer to deal with. This class also gives clients a way to serialize + * object parameters as strings so that entire parameterized commands can be + * serialized, stored and later deserialized and executed. + *

+ *

+ * This class will typically be extended so the subclass can be referenced from + * the converter attribute of the + * commandParameterType elemement of the + * dwtx.ui.commands extension-point. Objects implementing + * this interface may also be passed directly to + * {@link ParameterType#define(String, AbstractParameterValueConverter)} by + * clients. + *

+ * + * @see ParameterType#define(String, AbstractParameterValueConverter) + * @see ParameterizedCommand#serialize() + * @since 3.2 + */ +public abstract class AbstractParameterValueConverter { + + /** + * Converts a string encoded command parameter value into the parameter + * value object. + * + * @param parameterValue + * a command parameter value string describing an object; may be + * null + * @return the object described by the command parameter value string; may + * be null + * @throws ParameterValueConversionException + * if an object cannot be produced from the + * parameterValue string + */ + public abstract Object convertToObject(String parameterValue); + + /** + * Converts a command parameter value object into a string that encodes a + * reference to the object or serialization of the object. + * + * @param parameterValue + * an object to convert into an identifying string; may be + * null + * @return a string describing the provided object; may be null + * @throws ParameterValueConversionException + * if a string reference or serialization cannot be provided for + * the parameterValue + */ + public abstract String convertToString(Object parameterValue); + +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/Category.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/Category.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,180 @@ +/******************************************************************************* + * Copyright (c) 2005, 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.commands.Category; + +import tango.util.collection.ArraySeq; + +import dwtx.core.commands.common.NamedHandleObject; +import dwtx.core.internal.commands.util.Util; +import dwtx.core.commands.ICategoryListener; +import dwtx.core.commands.CategoryEvent; + +import dwt.dwthelper.utils; +import tango.text.convert.Format; + +/** + *

+ * A logical group for a set of commands. A command belongs to exactly one + * category. The category has no functional effect, but may be used in graphical + * tools that want to group the set of commands somehow. + *

+ * + * @since 3.1 + */ +public final class Category : NamedHandleObject { + + /** + * A collection of objects listening to changes to this category. This + * collection is null if there are no listeners. + */ + private ArraySeq!(ICategoryListener) categoryListeners; + + /** + * Constructs a new instance of Category based on the given + * identifier. When a category is first constructed, it is undefined. + * Category should only be constructed by the CommandManager + * to ensure that identifier remain unique. + * + * @param id + * The identifier for the category. This value must not be + * null, and must be unique amongst all + * categories. + */ + this(String id) { + super(id); + } + + /** + * Adds a listener to this category that will be notified when this + * category's state changes. + * + * @param categoryListener + * The listener to be added; must not be null. + */ + public final void addCategoryListener( + ICategoryListener categoryListener) { + if (categoryListener is null) { + throw new NullPointerException(); + } + if (categoryListeners is null) { + categoryListeners = new ArraySeq!(ICategoryListener); + } + if (!categoryListeners.contains(categoryListener)) { + categoryListeners.append(categoryListener); + } + } + + /** + *

+ * Defines this category by giving it a name, and possibly a description as + * well. The defined property automatically becomes true. + *

+ *

+ * Notification is sent to all listeners that something has changed. + *

+ * + * @param name + * The name of this command; must not be null. + * @param description + * The description for this command; may be null. + */ + public final void define(String name, String description) { + if (name is null) { + throw new NullPointerException( + "The name of a command cannot be null"); //$NON-NLS-1$ + } + + bool definedChanged = !this.defined; + this.defined = true; + + bool nameChanged = !Util.equals(this.name, name); + this.name = name; + + bool descriptionChanged = !Util.equals(this.description, + description); + this.description = description; + + fireCategoryChanged(new CategoryEvent(this, definedChanged, + descriptionChanged, nameChanged)); + } + + /** + * Notifies the listeners for this category that it has changed in some way. + * + * @param categoryEvent + * The event to send to all of the listener; must not be + * null. + */ + private final void fireCategoryChanged(CategoryEvent categoryEvent) { + if (categoryEvent is null) { + throw new NullPointerException(); + } + if (categoryListeners !is null) { + foreach( listener; categoryListeners ){ + listener.categoryChanged(categoryEvent); + } + } + } + + /** + * Removes a listener from this category. + * + * @param categoryListener + * The listener to be removed; must not be null. + * + */ + public final void removeCategoryListener( + ICategoryListener categoryListener) { + if (categoryListener is null) { + throw new NullPointerException(); + } + + if (categoryListeners !is null) { + categoryListeners.remove(categoryListener); + } + } + + /* + * (non-Javadoc) + * + * @see dwtx.core.commands.common.HandleObject#toString() + */ + public String toString() { + if (string is null) { + string = Format( "Category({},{},{},{})", id, name, description, defined ); + } + return string; + } + + /* + * (non-Javadoc) + * + * @see dwtx.core.commands.common.HandleObject#undefine() + */ + public void undefine() { + string = null; + + final bool definedChanged = defined; + defined = false; + + final bool nameChanged = name !is null; + name = null; + + final bool descriptionChanged = description !is null; + description = null; + + fireCategoryChanged(new CategoryEvent(this, definedChanged, + descriptionChanged, nameChanged)); + } + +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/CategoryEvent.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/CategoryEvent.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,68 @@ +/******************************************************************************* + * 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.commands.CategoryEvent; + +import dwtx.core.commands.common.AbstractNamedHandleEvent; +import dwtx.core.commands.Category; + +import dwt.dwthelper.utils; + +/** + * An instance of this class describes changes to an instance of + * Category. + *

+ * This class is not intended to be extended by clients. + *

+ * + * @since 3.1 + * @see ICategoryListener#categoryChanged(CategoryEvent) + */ +public final class CategoryEvent : AbstractNamedHandleEvent { + + /** + * The category that has changed; this value is never null. + */ + private final Category category; + + /** + * Creates a new instance of this class. + * + * @param category + * the instance of the interface that changed. + * @param definedChanged + * true, iff the defined property changed. + * @param descriptionChanged + * true, iff the description property changed. + * @param nameChanged + * true, iff the name property changed. + */ + public this(Category category, bool definedChanged, + bool descriptionChanged, bool nameChanged) { + super(definedChanged, descriptionChanged, nameChanged); + + if (category is null) { + throw new NullPointerException(); + } + this.category = category; + } + + /** + * Returns the instance of the interface that changed. + * + * @return the instance of the interface that changed. Guaranteed not to be + * null. + */ + public final Category getCategory() { + return category; + } +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/Command.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/Command.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,1103 @@ +/******************************************************************************* + * Copyright (c) 2004, 2007 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.commands.Command; + +// import java.io.BufferedWriter; +// import java.io.IOException; +// import java.io.StringWriter; + +import dwtx.core.commands.common.NotDefinedException; +import dwtx.core.commands.util.Tracing; +import dwtx.core.internal.commands.util.Util; +import dwtx.core.runtime.ISafeRunnable; +import dwtx.core.runtime.ListenerList; +import dwtx.core.runtime.SafeRunner; + +import dwtx.core.commands.IParameter; +import dwtx.core.commands.IHandlerListener; +import dwtx.core.commands.NotEnabledException; +import dwtx.core.commands.NotHandledException; +import dwtx.core.commands.ExecutionException; +import dwtx.core.commands.CommandEvent; +import dwtx.core.commands.State; +import dwtx.core.commands.Category; +import dwtx.core.commands.NamedHandleObjectWithState; +import dwtx.core.commands.ExecutionEvent; +import dwtx.core.commands.ParameterType; +import dwtx.core.commands.IExecutionListener; +import dwtx.core.commands.ICommandListener; +import dwtx.core.commands.IHandler; +import dwtx.core.commands.IObjectWithState; +import dwtx.core.commands.IExecutionListenerWithChecks; +import dwtx.core.commands.ITypedParameter; +import dwtx.core.commands.HandlerEvent; + +import dwt.dwthelper.utils; +import tango.text.convert.Format; + +/** + *

+ * A command is an abstract representation for some semantic behaviour. It is + * not the actual implementation of this behaviour, nor is it the visual + * appearance of this behaviour in the user interface. Instead, it is a bridge + * between the two. + *

+ *

+ * The concept of a command is based on the command design pattern. The notable + * difference is how the command delegates responsibility for execution. Rather + * than allowing concrete subclasses, it uses a handler mechanism (see the + * handlers extension point). This provides another level of + * indirection. + *

+ *

+ * A command will exist in two states: defined and undefined. A command is + * defined if it is declared in the XML of a resolved plug-in. If the plug-in is + * unloaded or the command is simply not declared, then it is undefined. Trying + * to reference an undefined command will succeed, but trying to access any of + * its functionality will fail with a NotDefinedException. If + * you need to know when a command changes from defined to undefined (or vice + * versa), then attach a command listener. + *

+ *

+ * Commands are mutable and will change as their definition changes. + *

+ * + * @since 3.1 + */ +public final class Command : NamedHandleObjectWithState, + Comparable { + + /** + * This flag can be set to true if commands should print + * information to System.out when executing. + */ + public static bool DEBUG_COMMAND_EXECUTION = false; + + /** + * This flag can be set to true if commands should print + * information to System.out when changing handlers. + */ + public static bool DEBUG_HANDLERS = false; + + /** + * This flag can be set to a particular command identifier if only that + * command should print information to System.out when + * changing handlers. + */ + public static String DEBUG_HANDLERS_COMMAND_ID = null; + + /** + * The category to which this command belongs. This value should not be + * null unless the command is undefined. + */ + private Category category = null; + + /** + * A collection of objects listening to the execution of this command. This + * collection is null if there are no listeners. + */ + private /+transient+/ ListenerList executionListeners = null; + + /** + * The handler currently associated with this command. This value may be + * null if there is no handler currently. + */ + private /+transient+/ IHandler handler = null; + + /** + * The help context identifier for this command. This can be + * null if there is no help currently associated with the + * command. + * + * @since 3.2 + */ + private String helpContextId; + + /** + * The ordered array of parameters understood by this command. This value + * may be null if there are no parameters, or if the command + * is undefined. It may also be empty. + */ + private IParameter[] parameters = null; + + /** + * The type of the return value of this command. This value may be + * null if the command does not declare a return type. + * + * @since 3.2 + */ + private ParameterType returnType = null; + + /** + * Our command will listen to the active handler for enablement changes so + * that they can be fired from the command itself. + * + * @since 3.3 + */ + private IHandlerListener handlerListener; + + /** + * Constructs a new instance of Command based on the given + * identifier. When a command is first constructed, it is undefined. + * Commands should only be constructed by the CommandManager + * to ensure that the identifier remains unique. + * + * @param id + * The identifier for the command. This value must not be + * null, and must be unique amongst all commands. + */ + this(String id) { + super(id); + } + + /** + * Adds a listener to this command that will be notified when this command's + * state changes. + * + * @param commandListener + * The listener to be added; must not be null. + */ + public final void addCommandListener(ICommandListener commandListener) { + if (commandListener is null) { + throw new NullPointerException("Cannot add a null command listener"); //$NON-NLS-1$ + } + addListenerObject(cast(Object)commandListener); + } + + /** + * Adds a listener to this command that will be notified when this command + * is about to execute. + * + * @param executionListener + * The listener to be added; must not be null. + */ + public final void addExecutionListener( + IExecutionListener executionListener) { + if (executionListener is null) { + throw new NullPointerException( + "Cannot add a null execution listener"); //$NON-NLS-1$ + } + + if (executionListeners is null) { + executionListeners = new ListenerList(ListenerList.IDENTITY); + } + + executionListeners.add(cast(Object)executionListener); + } + + /** + *

+ * Adds a state to this command. This will add this state to the active + * handler, if the active handler is an instance of {@link IObjectWithState}. + *

+ *

+ * A single instance of {@link State} cannot be registered with multiple + * commands. Each command requires its own unique instance. + *

+ * + * @param id + * The identifier of the state to add; must not be + * null. + * @param state + * The state to add; must not be null. + * @since 3.2 + */ + public void addState(String id, State state) { + super.addState(id, state); + state.setId(id); + if ( auto h = cast(IObjectWithState)handler) { + h.addState(id, state); + } + } + + /** + * Compares this command with another command by comparing each of its + * non-transient attributes. + * + * @param object + * The object with which to compare; must be an instance of + * Command. + * @return A negative integer, zero or a postivie integer, if the object is + * greater than, equal to or less than this command. + */ + public final int compareTo(Object object) { + Command castedObject = cast(Command) object; + int compareTo = Util.compare(category, castedObject.category); + if (compareTo is 0) { + compareTo = Util.compare(defined, castedObject.defined); + if (compareTo is 0) { + compareTo = Util.compare(description, castedObject.description); + if (compareTo is 0) { + compareTo = Util.compare(cast(Object)handler, cast(Object)castedObject.handler); + if (compareTo is 0) { + compareTo = Util.compare(id, castedObject.id); + if (compareTo is 0) { + compareTo = Util.compare(name, castedObject.name); + if (compareTo is 0) { + Object[] left, right; + foreach( p; parameters ){ + left ~= cast(Object)p; + } + foreach( p; castedObject.parameters ){ + right ~= cast(Object)p; + } + compareTo = Util.compare(left, + right); + } + } + } + } + } + } + return compareTo; + } + + /** + *

+ * Defines this command by giving it a name, and possibly a description as + * well. The defined property automatically becomes true. + *

+ *

+ * Notification is sent to all listeners that something has changed. + *

+ * + * @param name + * The name of this command; must not be null. + * @param description + * The description for this command; may be null. + * @param category + * The category for this command; must not be null. + * @since 3.2 + */ + public final void define(String name, String description, + Category category) { + define(name, description, category, null); + } + + /** + *

+ * Defines this command by giving it a name, and possibly a description as + * well. The defined property automatically becomes true. + *

+ *

+ * Notification is sent to all listeners that something has changed. + *

+ * + * @param name + * The name of this command; must not be null. + * @param description + * The description for this command; may be null. + * @param category + * The category for this command; must not be null. + * @param parameters + * The parameters understood by this command. This value may be + * either null or empty if the command does not + * accept parameters. + */ + public final void define(String name, String description, + Category category, IParameter[] parameters) { + define(name, description, category, parameters, null); + } + + /** + *

+ * Defines this command by giving it a name, and possibly a description as + * well. The defined property automatically becomes true. + *

+ *

+ * Notification is sent to all listeners that something has changed. + *

+ * + * @param name + * The name of this command; must not be null. + * @param description + * The description for this command; may be null. + * @param category + * The category for this command; must not be null. + * @param parameters + * The parameters understood by this command. This value may be + * either null or empty if the command does not + * accept parameters. + * @param returnType + * The type of value returned by this command. This value may be + * null if the command does not declare a return + * type. + * @since 3.2 + */ + public final void define(String name, String description, + Category category, IParameter[] parameters, + ParameterType returnType) { + define(name, description, category, parameters, returnType, null); + } + + /** + *

+ * Defines this command by giving it a name, and possibly a description as + * well. The defined property automatically becomes true. + *

+ *

+ * Notification is sent to all listeners that something has changed. + *

+ * + * @param name + * The name of this command; must not be null. + * @param description + * The description for this command; may be null. + * @param category + * The category for this command; must not be null. + * @param parameters + * The parameters understood by this command. This value may be + * either null or empty if the command does not + * accept parameters. + * @param returnType + * The type of value returned by this command. This value may be + * null if the command does not declare a return + * type. + * @param helpContextId + * The identifier of the help context to associate with this + * command; may be null if this command does not + * have any help associated with it. + * @since 3.2 + */ + public final void define(String name, String description, + Category category, IParameter[] parameters, + ParameterType returnType, String helpContextId) { + if (name is null) { + throw new NullPointerException( + "The name of a command cannot be null"); //$NON-NLS-1$ + } + + if (category is null) { + throw new NullPointerException( + "The category of a command cannot be null"); //$NON-NLS-1$ + } + + bool definedChanged = !this.defined; + this.defined = true; + + bool nameChanged = !Util.equals(this.name, name); + this.name = name; + + bool descriptionChanged = !Util.equals(this.description, + description); + this.description = description; + + bool categoryChanged = !Util.equals(this.category, category); + this.category = category; + + Object[] pLeft, pRight; + foreach( p; this.parameters ){ + pLeft ~= cast(Object)p; + } + foreach( p; parameters ){ + pRight ~= cast(Object)p; + } + bool parametersChanged = !Util.equals(pLeft, + pRight); + this.parameters = parameters; + + bool returnTypeChanged = !Util.equals(this.returnType, + returnType); + this.returnType = returnType; + + bool helpContextIdChanged = !Util.equals(this.helpContextId, + helpContextId); + this.helpContextId = helpContextId; + + fireCommandChanged(new CommandEvent(this, categoryChanged, + definedChanged, descriptionChanged, false, nameChanged, + parametersChanged, returnTypeChanged, helpContextIdChanged)); + } + + /** + * Executes this command by delegating to the current handler, if any. If + * the debugging flag is set, then this method prints information about + * which handler is selected for performing this command. This method will + * succeed regardless of whether the command is enabled or defined. It is + * generally preferred to call {@link #executeWithChecks(ExecutionEvent)}. + * + * @param event + * An event containing all the information about the current + * state of the application; must not be null. + * @return The result of the execution; may be null. This + * result will be available to the client executing the command, and + * execution listeners. + * @throws ExecutionException + * If the handler has problems executing this command. + * @throws NotHandledException + * If there is no handler. + * @deprecated Please use {@link #executeWithChecks(ExecutionEvent)} + * instead. + */ + public final Object execute(ExecutionEvent event) { + firePreExecute(event); + IHandler handler = this.handler; + + // Perform the execution, if there is a handler. + if ((handler !is null) && (handler.isHandled())) { + try { + Object returnValue = handler.execute(event); + firePostExecuteSuccess(returnValue); + return returnValue; + } catch (ExecutionException e) { + firePostExecuteFailure(e); + throw e; + } + } + + NotHandledException e = new NotHandledException( + "There is no handler to execute. " ~ getId()); //$NON-NLS-1$ + fireNotHandled(e); + throw e; + } + + /** + * Executes this command by delegating to the current handler, if any. If + * the debugging flag is set, then this method prints information about + * which handler is selected for performing this command. This does checks + * to see if the command is enabled and defined. If it is not both enabled + * and defined, then the execution listeners will be notified and an + * exception thrown. + * + * @param event + * An event containing all the information about the current + * state of the application; must not be null. + * @return The result of the execution; may be null. This + * result will be available to the client executing the command, and + * execution listeners. + * @throws ExecutionException + * If the handler has problems executing this command. + * @throws NotDefinedException + * If the command you are trying to execute is not defined. + * @throws NotEnabledException + * If the command you are trying to execute is not enabled. + * @throws NotHandledException + * If there is no handler. + * @since 3.2 + */ + public final Object executeWithChecks(ExecutionEvent event) { + firePreExecute(event); + IHandler handler = this.handler; + + if (!isDefined()) { + NotDefinedException exception = new NotDefinedException( + "Trying to execute a command that is not defined. " //$NON-NLS-1$ + ~ getId()); + fireNotDefined(exception); + throw exception; + } + + // Perform the execution, if there is a handler. + if ((handler !is null) && (handler.isHandled())) { + if (!isEnabled()) { + NotEnabledException exception = new NotEnabledException( + "Trying to execute the disabled command " ~ getId()); //$NON-NLS-1$ + fireNotEnabled(exception); + throw exception; + } + + try { + Object returnValue = handler.execute(event); + firePostExecuteSuccess(returnValue); + return returnValue; + } catch (ExecutionException e) { + firePostExecuteFailure(e); + throw e; + } + } + + NotHandledException e = new NotHandledException( + "There is no handler to execute for command " ~ getId()); //$NON-NLS-1$ + fireNotHandled(e); + throw e; + } + + /** + * Notifies the listeners for this command that it has changed in some way. + * + * @param commandEvent + * The event to send to all of the listener; must not be + * null. + */ + private final void fireCommandChanged(CommandEvent commandEvent) { + if (commandEvent is null) { + throw new NullPointerException("Cannot fire a null event"); //$NON-NLS-1$ + } + + Object[] listeners = getListeners(); + for (int i = 0; i < listeners.length; i++) { + ICommandListener listener = cast(ICommandListener) listeners[i]; + SafeRunner.run(new class() ISafeRunnable { + ICommandListener listener_; + this(){ this.listener_ = listener; } + public void handleException(Exception exception) { + } + + public void run() { + listener_.commandChanged(commandEvent); + } + }); + } + } + + /** + * Notifies the execution listeners for this command that an attempt to + * execute has failed because the command is not defined. + * + * @param e + * The exception that is about to be thrown; never + * null. + * @since 3.2 + */ + private final void fireNotDefined(NotDefinedException e) { + // Debugging output + if (DEBUG_COMMAND_EXECUTION) { + Tracing.printTrace("COMMANDS", "execute" ~ Tracing.SEPARATOR //$NON-NLS-1$ //$NON-NLS-2$ + ~ "not defined: id=" ~ getId() ~ "; exception=" ~ e.toString ); //$NON-NLS-1$ //$NON-NLS-2$ + } + + if (executionListeners !is null) { + Object[] listeners = executionListeners.getListeners(); + for (int i = 0; i < listeners.length; i++) { + Object object = listeners[i]; + if ( auto listener = cast(IExecutionListenerWithChecks)object ) { + listener.notDefined(getId(), e); + } + } + } + } + + /** + * Notifies the execution listeners for this command that an attempt to + * execute has failed because there is no handler. + * + * @param e + * The exception that is about to be thrown; never + * null. + * @since 3.2 + */ + private final void fireNotEnabled(NotEnabledException e) { + // Debugging output + if (DEBUG_COMMAND_EXECUTION) { + Tracing.printTrace("COMMANDS", "execute" ~ Tracing.SEPARATOR //$NON-NLS-1$ //$NON-NLS-2$ + ~ "not enabled: id=" ~ getId() ~ "; exception=" ~ e.toString ); //$NON-NLS-1$ //$NON-NLS-2$ + } + + if (executionListeners !is null) { + Object[] listeners = executionListeners.getListeners(); + for (int i = 0; i < listeners.length; i++) { + Object object = listeners[i]; + if ( auto listener = cast(IExecutionListenerWithChecks)object ) { + listener.notEnabled(getId(), e); + } + } + } + } + + /** + * Notifies the execution listeners for this command that an attempt to + * execute has failed because there is no handler. + * + * @param e + * The exception that is about to be thrown; never + * null. + */ + private final void fireNotHandled(NotHandledException e) { + // Debugging output + if (DEBUG_COMMAND_EXECUTION) { + Tracing.printTrace("COMMANDS", "execute" ~ Tracing.SEPARATOR //$NON-NLS-1$ //$NON-NLS-2$ + ~ "not handled: id=" ~ getId() ~ "; exception=" ~ e.toString ); //$NON-NLS-1$ //$NON-NLS-2$ + } + + if (executionListeners !is null) { + Object[] listeners = executionListeners.getListeners(); + for (int i = 0; i < listeners.length; i++) { + IExecutionListener listener = cast(IExecutionListener) listeners[i]; + listener.notHandled(getId(), e); + } + } + } + + /** + * Notifies the execution listeners for this command that an attempt to + * execute has failed during the execution. + * + * @param e + * The exception that has been thrown; never null. + * After this method completes, the exception will be thrown + * again. + */ + private final void firePostExecuteFailure(ExecutionException e) { + // Debugging output + if (DEBUG_COMMAND_EXECUTION) { + Tracing.printTrace("COMMANDS", "execute" ~ Tracing.SEPARATOR //$NON-NLS-1$ //$NON-NLS-2$ + ~ "failure: id=" ~ getId() ~ "; exception=" ~ e.toString ); //$NON-NLS-1$ //$NON-NLS-2$ + } + + if (executionListeners !is null) { + Object[] listeners = executionListeners.getListeners(); + for (int i = 0; i < listeners.length; i++) { + IExecutionListener listener = cast(IExecutionListener) listeners[i]; + listener.postExecuteFailure(getId(), e); + } + } + } + + /** + * Notifies the execution listeners for this command that an execution has + * completed successfully. + * + * @param returnValue + * The return value from the command; may be null. + */ + private final void firePostExecuteSuccess(Object returnValue) { + // Debugging output + if (DEBUG_COMMAND_EXECUTION) { + Tracing.printTrace("COMMANDS", "execute" ~ Tracing.SEPARATOR //$NON-NLS-1$ //$NON-NLS-2$ + ~ "success: id=" ~ getId() ~ "; returnValue=" //$NON-NLS-1$ //$NON-NLS-2$ + ~ returnValue.toString ); + } + + if (executionListeners !is null) { + Object[] listeners = executionListeners.getListeners(); + for (int i = 0; i < listeners.length; i++) { + IExecutionListener listener = cast(IExecutionListener) listeners[i]; + listener.postExecuteSuccess(getId(), returnValue); + } + } + } + + /** + * Notifies the execution listeners for this command that an attempt to + * execute is about to start. + * + * @param event + * The execution event that will be used; never null. + */ + private final void firePreExecute(ExecutionEvent event) { + // Debugging output + if (DEBUG_COMMAND_EXECUTION) { + Tracing.printTrace("COMMANDS", "execute" ~ Tracing.SEPARATOR //$NON-NLS-1$ //$NON-NLS-2$ + ~ "starting: id=" ~ getId() ~ "; event=" ~ event.toString ); //$NON-NLS-1$ //$NON-NLS-2$ + } + + if (executionListeners !is null) { + Object[] listeners = executionListeners.getListeners(); + for (int i = 0; i < listeners.length; i++) { + IExecutionListener listener = cast(IExecutionListener) listeners[i]; + listener.preExecute(getId(), event); + } + } + } + + /** + * Returns the category for this command. + * + * @return The category for this command; never null. + * @throws NotDefinedException + * If the handle is not currently defined. + */ + public final Category getCategory() { + if (!isDefined()) { + throw new NotDefinedException( + "Cannot get the category from an undefined command. " //$NON-NLS-1$ + ~ id); + } + + return category; + } + + /** + * Returns the current handler for this command. This is used by the command + * manager for determining the appropriate help context identifiers and by + * the command service to allow handlers to update elements. + *

+ * This value can change at any time and should never be cached. + *

+ * + * @return The current handler for this command; may be null. + * @since 3.3 + */ + public final IHandler getHandler() { + return handler; + } + + /** + * Returns the help context identifier associated with this command. This + * method should not be called by clients. Clients should use + * {@link CommandManager#getHelpContextId(String)} instead. + * + * @return The help context identifier for this command; may be + * null if there is none. + * @since 3.2 + */ + final String getHelpContextId() { + return helpContextId; + } + + /** + * Returns the parameter with the provided id or null if this + * command does not have a parameter with the id. + * + * @param parameterId + * The id of the parameter to retrieve. + * @return The parameter with the provided id or null if this + * command does not have a parameter with the id. + * @throws NotDefinedException + * If the handle is not currently defined. + * @since 3.2 + */ + public final IParameter getParameter(String parameterId) { + if (!isDefined()) { + throw new NotDefinedException( + "Cannot get a parameter from an undefined command. " //$NON-NLS-1$ + ~ id); + } + + if (parameters is null) { + return null; + } + + for (int i = 0; i < parameters.length; i++) { + IParameter parameter = parameters[i]; + if (parameter.getId().equals(parameterId)) { + return parameter; + } + } + + return null; + } + + /** + * Returns the parameters for this command. This call triggers provides a + * copy of the array, so excessive calls to this method should be avoided. + * + * @return The parameters for this command. This value might be + * null, if the command has no parameters. + * @throws NotDefinedException + * If the handle is not currently defined. + */ + public final IParameter[] getParameters() { + if (!isDefined()) { + throw new NotDefinedException( + "Cannot get the parameters from an undefined command. " //$NON-NLS-1$ + ~ id); + } + + if ((parameters is null) || (parameters.length is 0)) { + return null; + } + + IParameter[] returnValue = new IParameter[parameters.length]; + SimpleType!(IParameter).arraycopy(parameters, 0, returnValue, 0, parameters.length); + return returnValue; + } + + /** + * Returns the {@link ParameterType} for the parameter with the provided id + * or null if this command does not have a parameter type + * with the id. + * + * @param parameterId + * The id of the parameter to retrieve the {@link ParameterType} + * of. + * @return The {@link ParameterType} for the parameter with the provided id + * or null if this command does not have a parameter + * type with the provided id. + * @throws NotDefinedException + * If the handle is not currently defined. + * @since 3.2 + */ + public final ParameterType getParameterType(String parameterId) { + IParameter parameter = getParameter(parameterId); + if ( auto parameterWithType = cast(ITypedParameter)parameter ) { + return parameterWithType.getParameterType(); + } + return null; + } + + /** + * Returns the {@link ParameterType} for the return value of this command or + * null if this command does not declare a return value + * parameter type. + * + * @return The {@link ParameterType} for the return value of this command or + * null if this command does not declare a return + * value parameter type. + * @throws NotDefinedException + * If the handle is not currently defined. + * @since 3.2 + */ + public final ParameterType getReturnType() { + if (!isDefined()) { + throw new NotDefinedException( + "Cannot get the return type of an undefined command. " //$NON-NLS-1$ + ~ id); + } + + return returnType; + } + + /** + * Returns whether this command has a handler, and whether this handler is + * also handled and enabled. + * + * @return true if the command is handled; false + * otherwise. + */ + public final bool isEnabled() { + if (handler is null) { + return false; + } + + return handler.isEnabled(); + } + + /** + * Returns whether this command has a handler, and whether this handler is + * also handled. + * + * @return true if the command is handled; false + * otherwise. + */ + public final bool isHandled() { + if (handler is null) { + return false; + } + + return handler.isHandled(); + } + + /** + * Removes a listener from this command. + * + * @param commandListener + * The listener to be removed; must not be null. + * + */ + public final void removeCommandListener( + ICommandListener commandListener) { + if (commandListener is null) { + throw new NullPointerException( + "Cannot remove a null command listener"); //$NON-NLS-1$ + } + + removeListenerObject(cast(Object)commandListener); + } + + /** + * Removes a listener from this command. + * + * @param executionListener + * The listener to be removed; must not be null. + * + */ + public final void removeExecutionListener( + IExecutionListener executionListener) { + if (executionListener is null) { + throw new NullPointerException( + "Cannot remove a null execution listener"); //$NON-NLS-1$ + } + + if (executionListeners !is null) { + executionListeners.remove(cast(Object)executionListener); + if (executionListeners.isEmpty()) { + executionListeners = null; + } + } + } + + /** + *

+ * Removes a state from this command. This will remove the state from the + * active handler, if the active handler is an instance of + * {@link IObjectWithState}. + *

+ * + * @param stateId + * The identifier of the state to remove; must not be + * null. + * @since 3.2 + */ + public void removeState(String stateId) { + if ( auto h = cast(IObjectWithState)handler ) { + h.removeState(stateId); + } + super.removeState(stateId); + } + + /** + * Changes the handler for this command. This will remove all the state from + * the currently active handler (if any), and add it to handler. + * If debugging is turned on, then this will also print information about + * the change to System.out. + * + * @param handler + * The new handler; may be null if none. + * @return true if the handler changed; false + * otherwise. + */ + public final bool setHandler(IHandler handler) { + if (Util.equals(cast(Object)handler, cast(Object)this.handler)) { + return false; + } + + // Swap the state around. + String[] stateIds = getStateIds(); + if (stateIds !is null) { + for (int i = 0; i < stateIds.length; i++) { + String stateId = stateIds[i]; + if ( auto h = cast(IObjectWithState)this.handler ) { + h.removeState(stateId); + } + if ( auto h = cast(IObjectWithState)handler ) { + State stateToAdd = getState(stateId); + h.addState(stateId, stateToAdd); + } + } + } + + bool enabled = isEnabled(); + if (this.handler !is null) { + this.handler.removeHandlerListener(getHandlerListener()); + } + + // Update the handler, and flush the string representation. + this.handler = handler; + if (this.handler !is null) { + this.handler.addHandlerListener(getHandlerListener()); + } + string = null; + + // Debugging output + if ((DEBUG_HANDLERS) + && ((DEBUG_HANDLERS_COMMAND_ID is null) || (DEBUG_HANDLERS_COMMAND_ID + .equals(id)))) { + StringBuffer buffer = new StringBuffer("Command('"); //$NON-NLS-1$ + buffer.append(id); + buffer.append("') has changed to "); //$NON-NLS-1$ + if (handler is null) { + buffer.append("no handler"); //$NON-NLS-1$ + } else { + buffer.append('\''); + buffer.append(( cast(Object)handler).toString); + buffer.append("' as its handler"); //$NON-NLS-1$ + } + Tracing.printTrace("HANDLERS", buffer.toString()); //$NON-NLS-1$ + } + + // Send notification + fireCommandChanged(new CommandEvent(this, false, false, false, true, + false, false, false, false, enabled !is isEnabled())); + + return true; + } + + /** + * @return + */ + private IHandlerListener getHandlerListener() { + if (handlerListener is null) { + handlerListener = new class IHandlerListener { + public void handlerChanged(HandlerEvent handlerEvent) { + bool enabledChanged = handlerEvent.isEnabledChanged(); + bool handledChanged = handlerEvent.isHandledChanged(); + fireCommandChanged(new CommandEvent(this.outer, false, + false, false, handledChanged, false, false, false, + false, enabledChanged)); + } + }; + } + return handlerListener; + } + + /** + * The string representation of this command -- for debugging purposes only. + * This string should not be shown to an end user. + * + * @return The string representation; never null. + */ + public override final String toString() { + if (string is null) { + String parms; + foreach( p; parameters ){ + parms ~= "{"~(cast(Object)p).toString~"}"; + } + string = Format("Command({},{},\n\t\t{},\n\t\t{},\n\t\t{},\n\t\t{},{},{})", + id, + name is null ? "":name, + description is null?"":description, + category is null?"":category.toString(), + handler is null?"": (cast(Object)handler).toString(), + parms, + returnType is null?"":returnType.toString(), + defined + ); + } + return string; + } + + /** + * Makes this command become undefined. This has the side effect of changing + * the name and description to null. This also removes all + * state and disposes of it. Notification is sent to all listeners. + */ + public final void undefine() { + bool enabledChanged = isEnabled(); + + string = null; + + bool definedChanged = defined; + defined = false; + + bool nameChanged = name !is null; + name = null; + + bool descriptionChanged = description !is null; + description = null; + + bool categoryChanged = category !is null; + category = null; + + bool parametersChanged = parameters !is null; + parameters = null; + + bool returnTypeChanged = returnType !is null; + returnType = null; + + String[] stateIds = getStateIds(); + if (stateIds !is null) { + if ( auto handlerWithState = cast(IObjectWithState)handler ) { + for (int i = 0; i < stateIds.length; i++) { + String stateId = stateIds[i]; + handlerWithState.removeState(stateId); + + State state = getState(stateId); + removeState(stateId); + state.dispose(); + } + } else { + for (int i = 0; i < stateIds.length; i++) { + String stateId = stateIds[i]; + State state = getState(stateId); + removeState(stateId); + state.dispose(); + } + } + } + + fireCommandChanged(new CommandEvent(this, categoryChanged, + definedChanged, descriptionChanged, false, nameChanged, + parametersChanged, returnTypeChanged, false, enabledChanged)); + } +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/CommandEvent.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/CommandEvent.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,299 @@ +/******************************************************************************* + * Copyright (c) 2004, 2007 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.commands.CommandEvent; + +import dwtx.core.commands.common.AbstractNamedHandleEvent; +import dwtx.core.commands.Command; + +import dwt.dwthelper.utils; + +/** + * An instance of this class describes changes to an instance of + * Command. + *

+ * This class is not intended to be extended by clients. + *

+ * + * @since 3.1 + * @see ICommandListener#commandChanged(CommandEvent) + */ +public final class CommandEvent : AbstractNamedHandleEvent { + + /** + * The bit used to represent whether the command has changed its category. + */ + private static const int CHANGED_CATEGORY = LAST_USED_BIT << 1; + + /** + * The bit used to represent whether the command has changed its handler. + */ + private static const int CHANGED_HANDLED = LAST_USED_BIT << 2; + + /** + * The bit used to represent whether the command has changed its parameters. + */ + private static const int CHANGED_PARAMETERS = LAST_USED_BIT << 3; + + /** + * The bit used to represent whether the command has changed its return + * type. + * + * @since 3.2 + */ + private static const int CHANGED_RETURN_TYPE = LAST_USED_BIT << 4; + + /** + * The bit used to represent whether the command has changed its help + * context identifier. + * + * @since 3.2 + */ + private static const int CHANGED_HELP_CONTEXT_ID = LAST_USED_BIT << 5; + + /** + * The bit used to represent whether this commands active handler has + * changed its enablement state. + * + * @since 3.3 + */ + private static const int CHANGED_ENABLED = LAST_USED_BIT << 6; + + /** + * The command that has changed; this value is never null. + */ + private const Command command; + + /** + * Creates a new instance of this class. + * + * @param command + * the instance of the interface that changed. + * @param categoryChanged + * true, iff the category property changed. + * @param definedChanged + * true, iff the defined property changed. + * @param descriptionChanged + * true, iff the description property changed. + * @param handledChanged + * true, iff the handled property changed. + * @param nameChanged + * true, iff the name property changed. + * @param parametersChanged + * true if the parameters have changed; + * false otherwise. + */ + public this(Command command, bool categoryChanged, + bool definedChanged, bool descriptionChanged, + bool handledChanged, bool nameChanged, + bool parametersChanged) { + this(command, categoryChanged, definedChanged, descriptionChanged, + handledChanged, nameChanged, parametersChanged, false); + } + + /** + * Creates a new instance of this class. + * + * @param command + * the instance of the interface that changed. + * @param categoryChanged + * true, iff the category property changed. + * @param definedChanged + * true, iff the defined property changed. + * @param descriptionChanged + * true, iff the description property changed. + * @param handledChanged + * true, iff the handled property changed. + * @param nameChanged + * true, iff the name property changed. + * @param parametersChanged + * true if the parameters have changed; + * false otherwise. + * @param returnTypeChanged + * true iff the return type property changed; + * false otherwise. + * @since 3.2 + */ + public this(Command command, bool categoryChanged, + bool definedChanged, bool descriptionChanged, + bool handledChanged, bool nameChanged, + bool parametersChanged, bool returnTypeChanged) { + this(command, categoryChanged, definedChanged, descriptionChanged, + handledChanged, nameChanged, parametersChanged, + returnTypeChanged, false); + } + + /** + * Creates a new instance of this class. + * + * @param command + * the instance of the interface that changed. + * @param categoryChanged + * true, iff the category property changed. + * @param definedChanged + * true, iff the defined property changed. + * @param descriptionChanged + * true, iff the description property changed. + * @param handledChanged + * true, iff the handled property changed. + * @param nameChanged + * true, iff the name property changed. + * @param parametersChanged + * true if the parameters have changed; + * false otherwise. + * @param returnTypeChanged + * true iff the return type property changed; + * false otherwise. + * @param helpContextIdChanged + * true iff the help context identifier changed; + * false otherwise. + * @since 3.2 + */ + public this(Command command, bool categoryChanged, + bool definedChanged, bool descriptionChanged, + bool handledChanged, bool nameChanged, + bool parametersChanged, bool returnTypeChanged, + bool helpContextIdChanged) { + this(command, categoryChanged, definedChanged, descriptionChanged, + handledChanged, nameChanged, parametersChanged, + returnTypeChanged, helpContextIdChanged, false); + } + + /** + * Creates a new instance of this class. + * + * @param command + * the instance of the interface that changed. + * @param categoryChanged + * true, iff the category property changed. + * @param definedChanged + * true, iff the defined property changed. + * @param descriptionChanged + * true, iff the description property changed. + * @param handledChanged + * true, iff the handled property changed. + * @param nameChanged + * true, iff the name property changed. + * @param parametersChanged + * true if the parameters have changed; + * false otherwise. + * @param returnTypeChanged + * true iff the return type property changed; + * false otherwise. + * @param helpContextIdChanged + * true iff the help context identifier changed; + * false otherwise. + * @param enabledChanged + * true iff the comand enablement changed; + * false otherwise. + * @since 3.3 + */ + public this(Command command, bool categoryChanged, + bool definedChanged, bool descriptionChanged, + bool handledChanged, bool nameChanged, + bool parametersChanged, bool returnTypeChanged, + bool helpContextIdChanged, bool enabledChanged) { + super(definedChanged, descriptionChanged, nameChanged); + + if (command is null) { + throw new NullPointerException(); + } + this.command = command; + + if (categoryChanged) { + changedValues |= CHANGED_CATEGORY; + } + if (handledChanged) { + changedValues |= CHANGED_HANDLED; + } + if (parametersChanged) { + changedValues |= CHANGED_PARAMETERS; + } + if (returnTypeChanged) { + changedValues |= CHANGED_RETURN_TYPE; + } + if (helpContextIdChanged) { + changedValues |= CHANGED_HELP_CONTEXT_ID; + } + if (enabledChanged) { + changedValues |= CHANGED_ENABLED; + } + } + + /** + * Returns the instance of the interface that changed. + * + * @return the instance of the interface that changed. Guaranteed not to be + * null. + */ + public final Command getCommand() { + return command; + } + + /** + * Returns whether or not the category property changed. + * + * @return true, iff the category property changed. + */ + public final bool isCategoryChanged() { + return ((changedValues & CHANGED_CATEGORY) !is 0); + } + + /** + * Returns whether or not the handled property changed. + * + * @return true, iff the handled property changed. + */ + public final bool isHandledChanged() { + return ((changedValues & CHANGED_HANDLED) !is 0); + } + + /** + * Returns whether or not the help context identifier changed. + * + * @return true, iff the help context identifier changed. + * @since 3.2 + */ + public final bool isHelpContextIdChanged() { + return ((changedValues & CHANGED_HELP_CONTEXT_ID) !is 0); + } + + /** + * Returns whether or not the parameters have changed. + * + * @return true, iff the parameters property changed. + */ + public final bool isParametersChanged() { + return ((changedValues & CHANGED_PARAMETERS) !is 0); + } + + /** + * Returns whether or not the return type property changed. + * + * @return true, iff the return type property changed. + * @since 3.2 + */ + public final bool isReturnTypeChanged() { + return ((changedValues & CHANGED_RETURN_TYPE) !is 0); + } + + /** + * Return whether the enable property changed. + * + * @return true iff the comand enablement changed + * @since 3.3 + */ + public final bool isEnabledChanged() { + return ((changedValues & CHANGED_ENABLED) !is 0); + } + +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/CommandManager.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/CommandManager.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,989 @@ +/******************************************************************************* + * 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.commands.CommandManager; + +// import java.util.ArrayList; +// import java.util.Collections; +// import java.util.HashMap; +// import java.util.HashSet; +// import java.util.Iterator; +// import java.util.Map; +// import java.util.Set; +// import java.util.WeakHashMap; +import tango.util.collection.HashMap; +import tango.util.collection.HashSet; +import tango.util.collection.ArraySeq; +import tango.util.collection.model.Map; +import tango.util.collection.model.Set; +import tango.util.collection.model.SetView; + +import dwtx.core.commands.common.HandleObjectManager; +import dwtx.core.commands.common.NotDefinedException; +import dwtx.core.runtime.ListenerList; + +import dwtx.core.commands.ICategoryListener; +import dwtx.core.commands.ICommandListener; +import dwtx.core.commands.IExecutionListener; +import dwtx.core.commands.IExecutionListenerWithChecks; +import dwtx.core.commands.IParameterTypeListener; +import dwtx.core.commands.ICommandManagerListener; +import dwtx.core.commands.CommandEvent; +import dwtx.core.commands.CommandManagerEvent; +import dwtx.core.commands.Command; +import dwtx.core.commands.Category; +import dwtx.core.commands.CategoryEvent; +import dwtx.core.commands.IHandler; +import dwtx.core.commands.ParameterType; +import dwtx.core.commands.IParameter; +import dwtx.core.commands.Parameterization; +import dwtx.core.commands.ParameterizedCommand; +import dwtx.core.commands.ParameterTypeEvent; +import dwtx.core.commands.SerializationException; + +import dwtx.core.commands.NotEnabledException; +import dwtx.core.commands.NotHandledException; +import dwtx.core.commands.ExecutionException; +import dwtx.core.commands.ExecutionEvent; + +import dwt.dwthelper.utils; +static import tango.text.Text; +alias tango.text.Text.Text!(char) StringBuffer; + +/** + *

+ * A central repository for commands -- both in the defined and undefined + * states. Commands can be created and retrieved using this manager. It is + * possible to listen to changes in the collection of commands by attaching a + * listener to the manager. + *

+ * + * @see CommandManager#getCommand(String) + * @since 3.1 + */ +public final class CommandManager : HandleObjectManager, + ICategoryListener, ICommandListener, IParameterTypeListener { + + /** + * A listener that forwards incoming execution events to execution listeners + * on this manager. The execution events will come from any command on this + * manager. + * + * @since 3.1 + */ + private final class ExecutionListener : + IExecutionListenerWithChecks { + + public void notDefined(String commandId, NotDefinedException exception) { + if (executionListeners !is null) { + Object[] listeners = executionListeners.getListeners(); + for (int i = 0; i < listeners.length; i++) { + Object object = listeners[i]; + if ( auto listener = cast(IExecutionListenerWithChecks)object ) { + listener.notDefined(commandId, exception); + } + } + } + } + + public void notEnabled(String commandId, NotEnabledException exception) { + if (executionListeners !is null) { + Object[] listeners = executionListeners.getListeners(); + for (int i = 0; i < listeners.length; i++) { + Object object = listeners[i]; + if ( auto listener = cast(IExecutionListenerWithChecks)object ) { + listener.notEnabled(commandId, exception); + } + } + } + } + + public final void notHandled(String commandId, + NotHandledException exception) { + if (executionListeners !is null) { + Object[] listeners = executionListeners.getListeners(); + for (int i = 0; i < listeners.length; i++) { + Object object = listeners[i]; + if ( auto listener = cast(IExecutionListener)object ) { + listener.notHandled(commandId, exception); + } + } + } + } + + public final void postExecuteFailure(String commandId, + ExecutionException exception) { + if (executionListeners !is null) { + Object[] listeners = executionListeners.getListeners(); + for (int i = 0; i < listeners.length; i++) { + Object object = listeners[i]; + if ( auto listener = cast(IExecutionListener)object ) { + listener.postExecuteFailure(commandId, exception); + } + } + } + } + + public final void postExecuteSuccess(String commandId, + Object returnValue) { + if (executionListeners !is null) { + Object[] listeners = executionListeners.getListeners(); + for (int i = 0; i < listeners.length; i++) { + Object object = listeners[i]; + if ( auto listener = cast(IExecutionListener)object ) { + listener.postExecuteSuccess(commandId, returnValue); + } + } + } + } + + public final void preExecute(String commandId, + ExecutionEvent event) { + if (executionListeners !is null) { + Object[] listeners = executionListeners.getListeners(); + for (int i = 0; i < listeners.length; i++) { + Object object = listeners[i]; + if ( auto listener = cast(IExecutionListener)object ) { + listener.preExecute(commandId, event); + } + } + } + } + } + + /** + * The identifier of the category in which all auto-generated commands will + * appear. This value must never be null. + * + * @since 3.2 + */ + public static const String AUTOGENERATED_CATEGORY_ID = "dwtx.core.commands.categories.autogenerated"; //$NON-NLS-1$ + + /** + * The escape character to use for serialization and deserialization of + * parameterized commands. + */ + static const char ESCAPE_CHAR = '%'; + + /** + * The character that separates a parameter id from its value. + */ + static const char ID_VALUE_CHAR = '='; + + /** + * The character that indicates the end of a list of parameters. + */ + static const char PARAMETER_END_CHAR = ')'; + + /** + * The character that separators parameters from each other. + */ + static const char PARAMETER_SEPARATOR_CHAR = ','; + + /** + * The character that indicates the start of a list of parameters. + */ + static const char PARAMETER_START_CHAR = '('; + + this(){ + categoriesById = new HashMap!(String,Category); + definedCategoryIds = new HashSet!(String); + definedParameterTypeIds = new HashSet!(String); + parameterTypesById = new HashMap!(String,ParameterType); + helpContextIdsByHandler = new WeakHashMap!(IHandler,String); + } + + /** + * Unescapes special characters in the command id, parameter ids and + * parameter values for {@link #deserialize()}. The special characters + * {@link #PARAMETER_START_CHAR}, {@link #PARAMETER_END_CHAR}, + * {@link #ID_VALUE_CHAR}, {@link #PARAMETER_SEPARATOR_CHAR} and + * {@link #ESCAPE_CHAR} are escaped by prepending an {@link #ESCAPE_CHAR} + * character. + * + * @param escapedText + * a String that may contain escaped special + * characters for command serialization. + * @return a String representing escapedText + * with any escaped characters replaced by their literal values + * @throws SerializationException + * if escapedText contains an invalid escape + * sequence + * @see ParameterizedCommand#escape(String) + * @since 3.2 + */ + private static final String unescape(String escapedText) { + + // defer initialization of a StringBuffer until we know we need one + StringBuffer buffer; + + for (int i = 0; i < escapedText.length; i++) { + + char c = escapedText.charAt(i); + if (c !is ESCAPE_CHAR) { + // normal unescaped character + if (buffer !is null) { + buffer.append(c); + } + } else { + if (buffer is null) { + buffer = new StringBuffer(escapedText.substring(0, i)); + } + + if (++i < escapedText.length) { + c = escapedText.charAt(i); + switch (c) { + case PARAMETER_START_CHAR: + case PARAMETER_END_CHAR: + case ID_VALUE_CHAR: + case PARAMETER_SEPARATOR_CHAR: + case ESCAPE_CHAR: + buffer.append(c); + break; + default: + throw new SerializationException( + "Invalid character '" ~ c ~ "' in escape sequence"); //$NON-NLS-1$ //$NON-NLS-2$ + } + } else { + throw new SerializationException( + "Unexpected termination of escape sequence"); //$NON-NLS-1$ + } + } + + } + + if (buffer is null) { + return escapedText; + } + + return buffer.toString(); + } + + /** + * The map of category identifiers (String) to categories ( + * Category). This collection may be empty, but it is never + * null. + */ + private const Map!(String,Category) categoriesById; + + /** + * The set of identifiers for those categories that are defined. This value + * may be empty, but it is never null. + */ + private const Set!(String) definedCategoryIds; + + /** + * The set of identifiers for those command parameter types that are + * defined. This value may be empty, but it is never null. + * + * @since 3.2 + */ + private const Set!(String) definedParameterTypeIds; + + /** + * The execution listener for this command manager. This just forwards + * events from commands controlled by this manager to listeners on this + * manager. + */ + private IExecutionListener executionListener = null; + + /** + * The collection of execution listeners. This collection is + * null if there are no listeners. + */ + private ListenerList executionListeners = null; + + /** + * The help context identifiers ({@link String}) for a handler ({@link IHandler}). + * This map may be empty, but it is never null. Entries are + * removed if all strong references to the handler are removed. + * + * @since 3.2 + */ + private const WeakHashMap!(IHandler,String) helpContextIdsByHandler; + + /** + * The map of parameter type identifiers (String) to + * parameter types ( ParameterType). This collection may be + * empty, but it is never null. + * + * @since 3.2 + */ + private const Map!(String,ParameterType) parameterTypesById; + + /** + * Adds a listener to this command manager. The listener will be notified + * when the set of defined commands changes. This can be used to track the + * global appearance and disappearance of commands. + * + * @param listener + * The listener to attach; must not be null. + */ + public final void addCommandManagerListener( + ICommandManagerListener listener) { + addListenerObject(cast(Object)listener); + } + + /** + * Adds an execution listener to this manager. This listener will be + * notified if any of the commands controlled by this manager execute. This + * can be used to support macros and instrumentation of commands. + * + * @param listener + * The listener to attach; must not be null. + */ + public final void addExecutionListener(IExecutionListener listener) { + if (listener is null) { + throw new NullPointerException( + "Cannot add a null execution listener"); //$NON-NLS-1$ + } + + if (executionListeners is null) { + executionListeners = new ListenerList(ListenerList.IDENTITY); + + // Add an execution listener to every command. + executionListener = new ExecutionListener(); + foreach( k, v; handleObjectsById ){ + Command command = cast(Command) v; + command.addExecutionListener(executionListener); + } + + } + + executionListeners.add(cast(Object)listener); + } + + /* + * (non-Javadoc) + * + * @see dwtx.core.commands.ICategoryListener#categoryChanged(dwtx.core.commands.CategoryEvent) + */ + public final void categoryChanged(CategoryEvent categoryEvent) { + if (categoryEvent.isDefinedChanged()) { + Category category = categoryEvent.getCategory(); + String categoryId = category.getId(); + bool categoryIdAdded = category.isDefined(); + if (categoryIdAdded) { + definedCategoryIds.add(categoryId); + } else { + definedCategoryIds.remove(categoryId); + } + if (isListenerAttached()) { + fireCommandManagerChanged(new CommandManagerEvent(this, null, + false, false, categoryId, categoryIdAdded, true)); + } + } + } + + /* + * (non-Javadoc) + * + * @see dwtx.commands.ICommandListener#commandChanged(dwtx.commands.CommandEvent) + */ + public final void commandChanged(CommandEvent commandEvent) { + if (commandEvent.isDefinedChanged()) { + Command command = commandEvent.getCommand(); + String commandId = command.getId(); + bool commandIdAdded = command.isDefined(); + if (commandIdAdded) { + definedHandleObjects.add(command); + } else { + definedHandleObjects.remove(command); + } + if (isListenerAttached()) { + fireCommandManagerChanged(new CommandManagerEvent(this, + commandId, commandIdAdded, true, null, false, false)); + } + } + } + + /** + * Sets the name and description of the category for uncategorized commands. + * This is the category that will be returned if + * {@link #getCategory(String)} is called with null. + * + * @param name + * The name of the category for uncategorized commands; must not + * be null. + * @param description + * The description of the category for uncategorized commands; + * may be null. + * @since 3.2 + */ + public final void defineUncategorizedCategory(String name, + String description) { + Category category = getCategory(AUTOGENERATED_CATEGORY_ID); + category.define(name, description); + } + + /** + *

+ * Returns a {@link ParameterizedCommand} with a command and + * parameterizations as specified in the provided + * serializedParameterizedCommand string. The + * serializedParameterizedCommand must use the format + * returned by {@link ParameterizedCommand#serialize()} and described in the + * Javadoc for that method. + *

+ *

+ * If a parameter id encoded in the + * serializedParameterizedCommand does not exist in the + * encoded command, that parameter id and value are ignored. A given + * parameter id should not be used more than once in + * serializedParameterizedCommand. This will not result in + * an exception, but in this case the value of the parameter when the + * command is executed is unspecified. + *

+ *

+ * This method will never return null, however it may throw + * an exception if there is a problem processing the serialization string or + * the encoded command is undefined. + *

+ * + * @param serializedParameterizedCommand + * a string representing a command id and parameter ids and + * values; must not be null + * @return a {@link ParameterizedCommand} with the command and + * parameterizations encoded in the + * serializedParameterizedCommand; never + * null. + * @throws NotDefinedException + * if the command indicated in + * serializedParameterizedCommand is not defined + * @throws SerializationException + * if there is an error deserializing + * serializedParameterizedCommand + * @see ParameterizedCommand#serialize() + * @since 3.2 + */ + public final ParameterizedCommand deserialize( + String serializedParameterizedCommand) { + + int lparenPosition = unescapedIndexOf( + serializedParameterizedCommand, PARAMETER_START_CHAR); + + String commandIdEscaped; + String serializedParameters; + if (lparenPosition is -1) { + commandIdEscaped = serializedParameterizedCommand; + serializedParameters = null; + } else { + commandIdEscaped = serializedParameterizedCommand.substring(0, + lparenPosition); + + if (serializedParameterizedCommand + .charAt(serializedParameterizedCommand.length - 1) !is PARAMETER_END_CHAR) { + throw new SerializationException( + "Parentheses must be balanced in serialized ParameterizedCommand"); //$NON-NLS-1$ + } + + serializedParameters = serializedParameterizedCommand.substring( + lparenPosition + 1, // skip PARAMETER_START_CHAR + serializedParameterizedCommand.length - 1); // skip + // PARAMETER_END_CHAR + } + + String commandId = unescape(commandIdEscaped); + Command command = getCommand(commandId); + IParameter[] parameters = command.getParameters(); + Parameterization[] parameterizations = getParameterizations( + serializedParameters, parameters); + + return new ParameterizedCommand(command, parameterizations); + } + + /** + * Notifies all of the listeners to this manager that the set of defined + * command identifiers has changed. + * + * @param event + * The event to send to all of the listeners; must not be + * null. + */ + private final void fireCommandManagerChanged(CommandManagerEvent event) { + if (event is null) { + throw new NullPointerException(); + } + + Object[] listeners = getListeners(); + for (int i = 0; i < listeners.length; i++) { + ICommandManagerListener listener = cast(ICommandManagerListener) listeners[i]; + listener.commandManagerChanged(event); + } + } + + /** + * Returns all of the commands known by this manager -- defined and + * undefined. + * + * @return All of the commands; may be empty, but never null. + * @since 3.2 + */ + public final Command[] getAllCommands() { + Command[] res; + res.length = handleObjectsById.size(); + int idx; + foreach( cmd; handleObjectsById.elements() ){ + res[idx] = cast(Command)cmd; + idx++; + } + return res; + } + + /** + * Gets the category with the given identifier. If no such category + * currently exists, then the category will be created (but be undefined). + * + * @param categoryId + * The identifier to find; must not be null. If + * the category is null, then a category suitable + * for uncategorized items is defined and returned. + * @return The category with the given identifier; this value will never be + * null, but it might be undefined. + * @see Category + */ + public final Category getCategory(String categoryId) { + if (categoryId is null) { + return getCategory(AUTOGENERATED_CATEGORY_ID); + } + + checkId(categoryId); + + Category category = cast(Category) categoriesById.get(categoryId); + if (category is null) { + category = new Category(categoryId); + categoriesById.add(categoryId, category); + category.addCategoryListener(this); + } + + return category; + } + + /** + * Gets the command with the given identifier. If no such command currently + * exists, then the command will be created (but will be undefined). + * + * @param commandId + * The identifier to find; must not be null and + * must not be zero-length. + * @return The command with the given identifier; this value will never be + * null, but it might be undefined. + * @see Command + */ + public final Command getCommand(String commandId) { + checkId(commandId); + + Command command = cast(Command) handleObjectsById.get(commandId); + if (command is null) { + command = new Command(commandId); + handleObjectsById.add(commandId, command); + command.addCommandListener(this); + + if (executionListener !is null) { + command.addExecutionListener(executionListener); + } + } + + return command; + } + + /** + * Returns the categories that are defined. + * + * @return The defined categories; this value may be empty, but it is never + * null. + * @since 3.2 + */ + public final Category[] getDefinedCategories() { + Category[] categories = new Category[definedCategoryIds.size()]; +// Iterator categoryIdItr = definedCategoryIds.iterator(); + int i = 0; + foreach( categoryId; definedCategoryIds ){ +// while (categoryIdItr.hasNext()) { +// String categoryId = cast(String) categoryIdItr.next(); + categories[i++] = getCategory(categoryId); + } + return categories; + } + + /** + * Returns the set of identifiers for those category that are defined. + * + * @return The set of defined category identifiers; this value may be empty, + * but it is never null. + */ + public final SetView!(String) getDefinedCategoryIds() { + return definedCategoryIds; + } + + /** + * Returns the set of identifiers for those commands that are defined. + * + * @return The set of defined command identifiers; this value may be empty, + * but it is never null. + */ + public final SetView!(String) getDefinedCommandIds() { + return getDefinedHandleObjectIds(); + } + + /** + * Returns the commands that are defined. + * + * @return The defined commands; this value may be empty, but it is never + * null. + * @since 3.2 + */ + public final Command[] getDefinedCommands() { + return cast(Command[]) definedHandleObjects + .toArray(); + } + + /** + * Returns the set of identifiers for those parameter types that are + * defined. + * + * @return The set of defined command parameter type identifiers; this value + * may be empty, but it is never null. + * @since 3.2 + */ + public final SetView!(String) getDefinedParameterTypeIds() { + return definedParameterTypeIds; + } + + /** + * Returns the command parameter types that are defined. + * + * @return The defined command parameter types; this value may be empty, but + * it is never null. + * @since 3.2 + */ + public final ParameterType[] getDefinedParameterTypes() { + ParameterType[] parameterTypes = new ParameterType[definedParameterTypeIds + .size()]; +// Iterator iterator = definedParameterTypeIds.iterator(); + int i = 0; + foreach( parameterTypeId; definedParameterTypeIds ){ +// while (iterator.hasNext()) { +// String parameterTypeId = cast(String) iterator.next(); + parameterTypes[i++] = getParameterType(parameterTypeId); + } + return parameterTypes; + } + + /** + * Gets the help context identifier for a particular command. The command's + * handler is first checked for a help context identifier. If the handler + * does not have a help context identifier, then the help context identifier + * for the command is returned. If neither has a help context identifier, + * then null is returned. + * + * @param command + * The command for which the help context should be retrieved; + * must not be null. + * @return The help context identifier to use for the given command; may be + * null. + * @throws NotDefinedException + * If the given command is not defined. + * @since 3.2 + */ + public final String getHelpContextId(Command command) { + // Check if the command is defined. + if (!command.isDefined()) { + throw new NotDefinedException("The command is not defined. " //$NON-NLS-1$ + ~ command.getId()); + } + + // Check the handler. + IHandler handler = command.getHandler(); + if (handler !is null) { + String helpContextId = cast(String) helpContextIdsByHandler + .get(handler); + if (helpContextId !is null) { + return helpContextId; + } + } + + // Simply return whatever the command has as a help context identifier. + return command.getHelpContextId(); + } + + /** + * Returns an array of parameterizations for the provided command by + * deriving the parameter ids and values from the provided + * serializedParameters string. + * + * @param serializedParameters + * a String encoding parameter ids and values; must not be + * null. + * @param parameters + * array of parameters of the command being deserialized; may be + * null. + * @return an array of parameterizations; may be null. + * @throws NotDefinedException + * if the command is not defined + * @throws SerializationException + * if there is an error deserializing the parameters + * @since 3.2 + */ + private final Parameterization[] getParameterizations( + String serializedParameters, IParameter[] parameters) { + + if (serializedParameters is null + || (serializedParameters.length is 0)) { + return null; + } + + if ((parameters is null) || (parameters.length is 0)) { + return null; + } + + auto paramList = new ArraySeq!(Parameterization); + + int commaPosition; // split off each param by looking for ',' + do { + commaPosition = unescapedIndexOf(serializedParameters, ','); + + String idEqualsValue; + if (commaPosition is -1) { + // no more parameters after this + idEqualsValue = serializedParameters; + } else { + // take the first parameter... + idEqualsValue = serializedParameters + .substring(0, commaPosition); + + // ... and put the rest back into serializedParameters + serializedParameters = serializedParameters + .substring(commaPosition + 1); + } + + int equalsPosition = unescapedIndexOf(idEqualsValue, '='); + + String parameterId; + String parameterValue; + if (equalsPosition is -1) { + // missing values are null + parameterId = unescape(idEqualsValue); + parameterValue = null; + } else { + parameterId = unescape(idEqualsValue.substring(0, + equalsPosition)); + parameterValue = unescape(idEqualsValue + .substring(equalsPosition + 1)); + } + + for (int i = 0; i < parameters.length; i++) { + IParameter parameter = parameters[i]; + if (parameter.getId().equals(parameterId)) { + paramList.append(new Parameterization(parameter, + parameterValue)); + break; + } + } + + } while (commaPosition !is -1); + + return cast(Parameterization[]) paramList + .toArray(); + } + + /** + * Gets the command {@link ParameterType} with the given identifier. If no + * such command parameter type currently exists, then the command parameter + * type will be created (but will be undefined). + * + * @param parameterTypeId + * The identifier to find; must not be null and + * must not be zero-length. + * @return The {@link ParameterType} with the given identifier; this value + * will never be null, but it might be undefined. + * @since 3.2 + */ + public final ParameterType getParameterType(String parameterTypeId) { + checkId(parameterTypeId); + + ParameterType parameterType = cast(ParameterType) parameterTypesById + .get(parameterTypeId); + if (parameterType is null) { + parameterType = new ParameterType(parameterTypeId); + parameterTypesById.add(parameterTypeId, parameterType); + parameterType.addListener(this); + } + + return parameterType; + } + + /** + * {@inheritDoc} + * + * @since 3.2 + */ + public final void parameterTypeChanged( + ParameterTypeEvent parameterTypeEvent) { + if (parameterTypeEvent.isDefinedChanged()) { + ParameterType parameterType = parameterTypeEvent + .getParameterType(); + String parameterTypeId = parameterType.getId(); + bool parameterTypeIdAdded = parameterType.isDefined(); + if (parameterTypeIdAdded) { + definedParameterTypeIds.add(parameterTypeId); + } else { + definedParameterTypeIds.remove(parameterTypeId); + } + + fireCommandManagerChanged(new CommandManagerEvent(this, + parameterTypeId, parameterTypeIdAdded, true)); + } + } + + /** + * Removes a listener from this command manager. + * + * @param listener + * The listener to be removed; must not be null. + */ + public final void removeCommandManagerListener( + ICommandManagerListener listener) { + removeListenerObject(cast(Object)listener); + } + + /** + * Removes an execution listener from this command manager. + * + * @param listener + * The listener to be removed; must not be null. + */ + public final void removeExecutionListener(IExecutionListener listener) { + if (listener is null) { + throw new NullPointerException("Cannot remove a null listener"); //$NON-NLS-1$ + } + + if (executionListeners is null) { + return; + } + + executionListeners.remove(cast(Object)listener); + + if (executionListeners.isEmpty()) { + executionListeners = null; + + // Remove the execution listener to every command. + foreach( k, v; handleObjectsById ){ +// Iterator commandItr = handleObjectsById.values().iterator(); +// while (commandItr.hasNext()) { + Command command = cast(Command) v; + command.removeExecutionListener(executionListener); + } + executionListener = null; + + } + } + + /** + * Block updates all of the handlers for all of the commands. If the handler + * is null or the command id does not exist in the map, then + * the command becomes unhandled. Otherwise, the handler is set to the + * corresponding value in the map. + * + * @param handlersByCommandId + * A map of command identifiers (String) to + * handlers (IHandler). This map may be + * null if all handlers should be cleared. + * Similarly, if the map is empty, then all commands will become + * unhandled. + */ + public final void setHandlersByCommandId(Map!(String,Object) handlersByCommandId) { + // Make that all the reference commands are created. + foreach( k, v; handlersByCommandId ){ + //Iterator commandIdItr = handlersByCommandId.keySet().iterator(); + //while (commandIdItr.hasNext()) { + getCommand(k); + } + + // Now, set-up the handlers on all of the existing commands. +// Iterator commandItr = handleObjectsById.values().iterator(); + foreach( k, v; handlersByCommandId ){ +// while (commandItr.hasNext()) { + Command command = cast(Command) v; + String commandId = command.getId(); + Object value = handlersByCommandId.get(commandId); + if ( auto handler = cast(IHandler) value ) { + command.setHandler(handler); + } else { + command.setHandler(null); + } + } + } + + /** + * Sets the help context identifier to associate with a particular handler. + * + * @param handler + * The handler with which to register a help context identifier; + * must not be null. + * @param helpContextId + * The help context identifier to register; may be + * null if the help context identifier should be + * removed. + * @since 3.2 + */ + public final void setHelpContextId(IHandler handler, + String helpContextId) { + if (handler is null) { + throw new NullPointerException("The handler cannot be null"); //$NON-NLS-1$ + } + if (helpContextId is null) { + helpContextIdsByHandler.removeKey(handler); + } else { + helpContextIdsByHandler.add(handler, helpContextId); + } + } + + /** + * Searches for the index of a char in a String + * but disregards characters prefixed with the {@link #ESCAPE_CHAR} escape + * character. This is used by {@link #deserialize(String)} and + * {@link #getParameterizations(String, IParameter[])} to parse the + * serialized parameterized command string. + * + * @param escapedText + * the string to search for the index of ch in + * @param ch + * a character to search for in escapedText + * @return the index of the first unescaped occurrence of the character in + * escapedText, or -1 if the + * character does not occur unescaped. + * @see String#indexOf(int) + */ + private final int unescapedIndexOf(String escapedText, char ch) { + + int pos = escapedText.indexOf(ch); + + // first char can't be escaped + if (pos is 0) { + return pos; + } + + while (pos !is -1) { + // look back for the escape character + if (escapedText.charAt(pos - 1) !is ESCAPE_CHAR) { + return pos; + } + + // scan for the next instance of ch + pos = escapedText.indexOf(ch, pos + 1); + } + + return pos; + + } +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/CommandManagerEvent.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/CommandManagerEvent.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,310 @@ +/******************************************************************************* + * 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.commands.CommandManagerEvent; + +import dwtx.core.commands.CommandManager; + +import dwt.dwthelper.utils; + +/** + *

+ * An event indicating that the set of defined command identifiers has changed. + *

+ * + * @since 3.1 + * @see ICommandManagerListener#commandManagerChanged(CommandManagerEvent) + */ +public final class CommandManagerEvent { + + /** + * The bit used to represent whether the given category has become defined. + * If this bit is not set and there is no category id, then no category has + * become defined nor undefined. If this bit is not set and there is a + * category id, then the category has become undefined. + */ + private static const int CHANGED_CATEGORY_DEFINED = 1; + + /** + * The bit used to represent whether the given command has become defined. + * If this bit is not set and there is no command id, then no command has + * become defined nor undefined. If this bit is not set and there is a + * command id, then the command has become undefined. + */ + private static const int CHANGED_COMMAND_DEFINED = 1 << 1; + + /** + * The bit used to represent whether the given command parameter type has + * become defined. If this bit is not set and there is no parameter type id, + * then no parameter type has become defined nor undefined. If this bit is + * not set and there is a parameter type id, then the parameter type has + * become undefined. + * + * @since 3.2 + */ + private static const int CHANGED_PARAMETER_TYPE_DEFINED = 1 << 2; + + /** + * The category identifier that was added or removed from the list of + * defined category identifiers. This value is null if the + * list of defined category identifiers did not change. + */ + private const String categoryId; + + /** + * A collection of bits representing whether certain values have changed. A + * bit is set (i.e., 1) if the corresponding property has + * changed. + */ + private const int changedValues; + + /** + * The command identifier that was added or removed from the list of defined + * command identifiers. This value is null if the list of + * defined command identifiers did not change. + */ + private const String commandId; + + /** + * The command parameter type identifier that was added or removed from the + * list of defined parameter type identifiers. This value is + * null if the list of defined parameter type identifiers did + * not change. + * + * @since 3.2 + */ + private const String parameterTypeId; + + /** + * The command manager that has changed. + */ + private const CommandManager commandManager; + + /** + * Creates a new CommandManagerEvent instance to describe + * changes to commands and/or categories. + * + * @param commandManager + * the instance of the interface that changed; must not be + * null. + * @param commandId + * The command identifier that was added or removed; must not be + * null if commandIdChanged is true. + * @param commandIdAdded + * Whether the command identifier became defined (otherwise, it + * became undefined). + * @param commandIdChanged + * Whether the list of defined command identifiers has changed. + * @param categoryId + * The category identifier that was added or removed; must not be + * null if categoryIdChanged is true. + * @param categoryIdAdded + * Whether the category identifier became defined (otherwise, it + * became undefined). + * @param categoryIdChanged + * Whether the list of defined category identifiers has changed. + */ + public this(CommandManager commandManager, + String commandId, bool commandIdAdded, + bool commandIdChanged, String categoryId, + bool categoryIdAdded, bool categoryIdChanged) { + if (commandManager is null) { + throw new NullPointerException( + "An event must refer to its command manager"); //$NON-NLS-1$ + } + + if (commandIdChanged && (commandId is null)) { + throw new NullPointerException( + "If the list of defined commands changed, then the added/removed command must be mentioned"); //$NON-NLS-1$ + } + + if (categoryIdChanged && (categoryId is null)) { + throw new NullPointerException( + "If the list of defined categories changed, then the added/removed category must be mentioned"); //$NON-NLS-1$ + } + + this.commandManager = commandManager; + this.commandId = commandId; + this.categoryId = categoryId; + + // this constructor only works for changes to commands and categories + this.parameterTypeId = null; + + int changedValues = 0; + if (categoryIdChanged && categoryIdAdded) { + changedValues |= CHANGED_CATEGORY_DEFINED; + } + if (commandIdChanged && commandIdAdded) { + changedValues |= CHANGED_COMMAND_DEFINED; + } + this.changedValues = changedValues; + } + + /** + * Creates a new CommandManagerEvent instance to describe + * changes to command parameter types. + * + * @param commandManager + * the instance of the interface that changed; must not be + * null. + * @param parameterTypeId + * The command parameter type identifier that was added or + * removed; must not be null if + * parameterTypeIdChanged is true. + * @param parameterTypeIdAdded + * Whether the parameter type identifier became defined + * (otherwise, it became undefined). + * @param parameterTypeIdChanged + * Whether the list of defined parameter type identifiers has + * changed. + * + * @since 3.2 + */ + public this(CommandManager commandManager, + String parameterTypeId, bool parameterTypeIdAdded, + bool parameterTypeIdChanged) { + + if (commandManager is null) { + throw new NullPointerException( + "An event must refer to its command manager"); //$NON-NLS-1$ + } + + if (parameterTypeIdChanged && (parameterTypeId is null)) { + throw new NullPointerException( + "If the list of defined command parameter types changed, then the added/removed parameter type must be mentioned"); //$NON-NLS-1$ + } + + this.commandManager = commandManager; + this.commandId = null; + this.categoryId = null; + + this.parameterTypeId = parameterTypeId; + + int changedValues = 0; + if (parameterTypeIdChanged && parameterTypeIdAdded) { + changedValues |= CHANGED_PARAMETER_TYPE_DEFINED; + } + + this.changedValues = changedValues; + } + + /** + * Returns the category identifier that was added or removed. + * + * @return The category identifier that was added or removed; may be + * null. + */ + public final String getCategoryId() { + return categoryId; + } + + /** + * Returns the command identifier that was added or removed. + * + * @return The command identifier that was added or removed; may be + * null. + */ + public final String getCommandId() { + return commandId; + } + + /** + * Returns the instance of the interface that changed. + * + * @return the instance of the interface that changed. Guaranteed not to be + * null. + */ + public final CommandManager getCommandManager() { + return commandManager; + } + + /** + * Returns the command parameter type identifier that was added or removed. + * + * @return The command parameter type identifier that was added or removed; + * may be null. + * + * @since 3.2 + */ + public final String getParameterTypeId() { + return parameterTypeId; + } + + /** + * Returns whether the list of defined category identifiers has changed. + * + * @return true if the list of category identifiers has + * changed; false otherwise. + */ + public final bool isCategoryChanged() { + return (categoryId !is null); + } + + /** + * Returns whether the category identifier became defined. Otherwise, the + * category identifier became undefined. + * + * @return true if the category identifier became defined; + * false if the category identifier became undefined. + */ + public final bool isCategoryDefined() { + return (((changedValues & CHANGED_CATEGORY_DEFINED) !is 0) && (categoryId !is null)); + } + + /** + * Returns whether the list of defined command identifiers has changed. + * + * @return true if the list of command identifiers has + * changed; false otherwise. + */ + public final bool isCommandChanged() { + return (commandId !is null); + } + + /** + * Returns whether the command identifier became defined. Otherwise, the + * command identifier became undefined. + * + * @return true if the command identifier became defined; + * false if the command identifier became undefined. + */ + public final bool isCommandDefined() { + return (((changedValues & CHANGED_COMMAND_DEFINED) !is 0) && (commandId !is null)); + } + + /** + * Returns whether the list of defined command parameter type identifiers + * has changed. + * + * @return true if the list of command parameter type + * identifiers has changed; false otherwise. + * + * @since 3.2 + */ + public final bool isParameterTypeChanged() { + return (parameterTypeId !is null); + } + + /** + * Returns whether the command parameter type identifier became defined. + * Otherwise, the command parameter type identifier became undefined. + * + * @return true if the command parameter type identifier + * became defined; false if the command parameter + * type identifier became undefined. + * + * @since 3.2 + */ + public final bool isParameterTypeDefined() { + return (((changedValues & CHANGED_PARAMETER_TYPE_DEFINED) !is 0) && (parameterTypeId !is null)); + } +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/ExecutionEvent.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/ExecutionEvent.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,250 @@ +/******************************************************************************* + * Copyright (c) 2005, 2007 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.commands.ExecutionEvent; + +// import java.util.Collections; +import tango.util.collection.model.Map; +import tango.util.collection.HashMap; + +import dwtx.core.commands.common.NotDefinedException; +import dwtx.core.commands.ExecutionException; +import dwtx.core.commands.Command; +import dwtx.core.commands.ParameterType; +import dwtx.core.commands.AbstractParameterValueConverter; +import dwtx.core.commands.ParameterValueConversionException; + +import dwt.dwthelper.utils; + +import tango.text.convert.Format; + +/** + *

+ * The data object to pass to the command (and its handler) as it executes. This + * carries information about the current state of the application, and the + * application context in which the command was executed. + *

+ *

+ * An execution event carries three blocks of data: the parameters, the trigger, + * and the application context. How these blocks are used is application + * dependent. In the Eclipse workbench, the trigger is an DWT event, and the + * application context contains information about the selection and active part. + *

+ * + * @since 3.1 + */ +public final class ExecutionEvent { + private static const Map!(String,String) EMPTY_MAP; + static this(){ + EMPTY_MAP = new HashMap!(String,String); + } + /** + * The state of the application at the time the execution was triggered. In + * the Eclipse workbench, this might contain information about the active + * part of the active selection (for example). This value may be + * null. + */ + private const Object applicationContext; + + /** + * The command being executed. This value may be null. + */ + private const Command command; + + /** + * The parameters to qualify the execution. For handlers that normally + * prompt for additional information, these can be used to avoid prompting. + * This value may be empty, but it is never null. + */ + private const Map!(String,String) parameters; + + /** + * The object that triggered the execution. In an event-driven architecture, + * this is typically just another event. In the Eclipse workbench, this is + * typically an DWT event. This value may be null. + */ + private const Object trigger; + + /** + * Constructs a new instance of ExecutionEvent with no + * parameters, no trigger and no application context. This is just a + * convenience method. + * + * @since 3.2 + */ + public this() { + this(null, EMPTY_MAP, null, null); + } + + /** + * Constructs a new instance of ExecutionEvent. + * + * @param parameters + * The parameters to qualify the execution; must not be + * null. This must be a map of parameter ids (String) + * to parameter values (String). + * @param trigger + * The object that triggered the execution; may be + * null. + * @param applicationContext + * The state of the application at the time the execution was + * triggered; may be null. + * @deprecated use + * {@link ExecutionEvent#ExecutionEvent(Command, Map, Object, Object)} + */ + public this(Map!(String,String) parameters, Object trigger, + Object applicationContext) { + this(null, parameters, trigger, applicationContext); + } + + /** + * Constructs a new instance of ExecutionEvent. + * + * @param command + * The command being executed; may be null. + * @param parameters + * The parameters to qualify the execution; must not be + * null. This must be a map of parameter ids (String) + * to parameter values (String). + * @param trigger + * The object that triggered the execution; may be + * null. + * @param applicationContext + * The state of the application at the time the execution was + * triggered; may be null. + * @since 3.2 + */ + public this(Command command, Map!(String,String) parameters, + Object trigger, Object applicationContext) { + if (parameters is null) { + throw new NullPointerException( + "An execution event must have a non-null map of parameters"); //$NON-NLS-1$ + } + + this.command = command; + this.parameters = parameters; + this.trigger = trigger; + this.applicationContext = applicationContext; + } + + /** + * Returns the state of the application at the time the execution was + * triggered. + * + * @return The application context; may be null. + */ + public final Object getApplicationContext() { + return applicationContext; + } + + /** + * Returns the command being executed. + * + * @return The command being executed. + * @since 3.2 + */ + public final Command getCommand() { + return command; + } + + /** + * Returns the object represented by the string value of the parameter with + * the provided id. + *

+ * This is intended to be used in the scope of an + * {@link IHandler#execute(ExecutionEvent)} method, so any problem getting + * the object value causes ExecutionException to be thrown. + *

+ * + * @param parameterId + * The id of a parameter to retrieve the object value of. + * @return The object value of the parameter with the provided id. + * @throws ExecutionException + * if the parameter object value could not be obtained for any + * reason + * @since 3.2 + */ + public final Object getObjectParameterForExecution(String parameterId) { + if (command is null) { + throw new ExecutionException( + "No command is associated with this execution event"); //$NON-NLS-1$ + } + + try { + ParameterType parameterType = command + .getParameterType(parameterId); + if (parameterType is null) { + throw new ExecutionException( + "Command does not have a parameter type for the given parameter"); //$NON-NLS-1$ + } + AbstractParameterValueConverter valueConverter = parameterType + .getValueConverter(); + if (valueConverter is null) { + throw new ExecutionException( + "Command does not have a value converter"); //$NON-NLS-1$ + } + String stringValue = getParameter(parameterId); + Object objectValue = valueConverter + .convertToObject(stringValue); + return objectValue; + } catch (NotDefinedException e) { + throw new ExecutionException("Command is not defined", e); //$NON-NLS-1$ + } catch (ParameterValueConversionException e) { + throw new ExecutionException( + "The parameter string could not be converted to an object", e); //$NON-NLS-1$ + } + } + + /** + * Returns the value of the parameter with the given id. + * + * @param parameterId + * The id of the parameter to retrieve; may be null. + * @return The parameter value; null if the parameter cannot + * be found. + */ + public final String getParameter(String parameterId) { + return parameters.get(parameterId); + } + + /** + * Returns all of the parameters. + * + * @return The parameters; never null, but may be empty. + */ + public final Map!(String,String) getParameters() { + return parameters; + } + + /** + * Returns the object that triggered the execution + * + * @return The trigger; null if there was no trigger. + */ + public final Object getTrigger() { + return trigger; + } + + /** + * The string representation of this execution event -- for debugging + * purposes only. This string should not be shown to an end user. + * + * @return The string representation; never null. + */ + public final String toString() { + String parm; + foreach( k, v; parameters ){ + parm ~= "{"~k~","~v~"}"; + } + return Format( "ExecutionEvent({},{},{})", command, parm, trigger, applicationContext ); + } +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/ExecutionException.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/ExecutionException.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,58 @@ +/******************************************************************************* + * 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.commands.ExecutionException; + +import dwtx.core.commands.common.CommandException; +import dwt.dwthelper.utils; + +/** + * Signals that an exception occured during the execution of a command. + *

+ * This class is not intended to be extended by clients. + *

+ * + * @since 3.1 + */ +public final class ExecutionException : CommandException { + + /** + * Generated serial version UID for this class. + * + * @since 3.1 + */ + private static final long serialVersionUID = 3258130262767448120L; + + /** + * Creates a new instance of this class with the specified detail message. + * + * @param message + * the detail message; may be null. + * @since 3.2 + */ + public this(String message) { + super(message); + } + + /** + * Creates a new instance of this class with the specified detail message + * and cause. + * + * @param message + * the detail message; may be null. + * @param cause + * the cause; may be null. + */ + public this(String message, Exception cause) { + super(message, cause); + } +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/HandlerEvent.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/HandlerEvent.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,101 @@ +/******************************************************************************* + * 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.commands.HandlerEvent; + +import dwtx.core.commands.common.AbstractBitSetEvent; +import dwtx.core.commands.IHandler; +import dwt.dwthelper.utils; + +/** + * An instance of this class describes changes to an instance of + * IHandler. + *

+ * This class is not intended to be extended by clients. + *

+ * + * @since 3.1 + * @see IHandlerListener#handlerChanged(HandlerEvent) + */ +public final class HandlerEvent : AbstractBitSetEvent { + + /** + * The bit used to represent whether the handler has changed its enabled + * state. + */ + private static final int CHANGED_ENABLED = 1; + + /** + * The bit used to represent whether the handler has changed its handled + * state. + */ + private static final int CHANGED_HANDLED = 1 << 1; + + /** + * The handler that changed; this value is never null. + */ + private final IHandler handler; + + /** + * Creates a new instance of this class. + * + * @param handler + * the instance of the interface that changed; must not be + * null. + * @param enabledChanged + * Whether the enabled state of the handler has changed. + * @param handledChanged + * Whether the handled state of the handler has changed. + */ + public this(IHandler handler, bool enabledChanged, + bool handledChanged) { + if (handler is null) { + throw new NullPointerException(); + } + this.handler = handler; + + if (enabledChanged) { + changedValues |= CHANGED_ENABLED; + } + if (handledChanged) { + changedValues |= CHANGED_HANDLED; + } + } + + /** + * Returns the instance of the interface that changed. + * + * @return the instance of the interface that changed. Guaranteed not to be + * null. + */ + public IHandler getHandler() { + return handler; + } + + /** + * Returns whether or not the enabled property changed. + * + * @return true, iff the enabled property changed. + */ + public bool isEnabledChanged() { + return ((changedValues & CHANGED_ENABLED) !is 0); + } + + /** + * Returns whether or not the handled property changed. + * + * @return true, iff the handled property changed. + */ + public bool isHandledChanged() { + return ((changedValues & CHANGED_HANDLED) !is 0); + } +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/ICategoryListener.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/ICategoryListener.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright (c) 2004, 2005 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.commands.ICategoryListener; + +import dwtx.core.commands.CategoryEvent; + +/** + * An instance of this interface can be used by clients to receive notification + * of changes to one or more instances of Category. + *

+ * This interface may be implemented by clients. + *

+ * + * @since 3.1 + * @see Category#addCategoryListener(ICategoryListener) + * @see Category#removeCategoryListener(ICategoryListener) + */ +public interface ICategoryListener { + + /** + * Notifies that one or more properties of an instance of + * Category have changed. Specific details are described in + * the CategoryEvent. + * + * @param categoryEvent + * the category event. Guaranteed not to be null. + */ + void categoryChanged(CategoryEvent categoryEvent); +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/ICommandListener.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/ICommandListener.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright (c) 2004, 2005 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.commands.ICommandListener; + +import dwtx.core.commands.CommandEvent; + +/** + * An instance of this interface can be used by clients to receive notification + * of changes to one or more instances of Command. + *

+ * This interface may be implemented by clients. + *

+ * + * @since 3.1 + * @see Command#addCommandListener(ICommandListener) + * @see Command#removeCommandListener(ICommandListener) + */ +public interface ICommandListener { + + /** + * Notifies that one or more properties of an instance of + * Command have changed. Specific details are described in + * the CommandEvent. + * + * @param commandEvent + * the command event. Guaranteed not to be null. + */ + void commandChanged(CommandEvent commandEvent); +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/ICommandManagerListener.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/ICommandManagerListener.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright (c) 2004, 2005 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.commands.ICommandManagerListener; + +import dwtx.core.commands.CommandManagerEvent; +/** + * An instance of this interface can be used by clients to receive notification + * of changes to one or more instances of ICommandManager. + *

+ * This interface may be implemented by clients. + *

+ * + * @since 3.1 + * @see CommandManager#addCommandManagerListener(ICommandManagerListener) + * @see CommandManager#removeCommandManagerListener(ICommandManagerListener) + */ +public interface ICommandManagerListener { + + /** + * Notifies that one or more properties of an instance of + * ICommandManager have changed. Specific details are + * described in the CommandManagerEvent. + * + * @param commandManagerEvent + * the commandManager event. Guaranteed not to be + * null. + */ + void commandManagerChanged(CommandManagerEvent commandManagerEvent); +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/IExecutionListener.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/IExecutionListener.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,81 @@ +/******************************************************************************* + * Copyright (c) 2005 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 + * Port to the D programming language: + * Frank Benoit + ******************************************************************************/ + +module dwtx.core.commands.IExecutionListener; + +import dwtx.core.commands.NotHandledException; +import dwtx.core.commands.ExecutionException; +import dwtx.core.commands.ExecutionEvent; + +import dwt.dwthelper.utils; + +/** + *

+ * A listener to the execution of commands. This listener will be notified if a + * command is about to execute, and when that execution completes. It is not + * possible for the listener to prevent the execution, only to respond to it in + * some way. + *

+ * + * @since 3.1 + */ +public interface IExecutionListener { + + /** + * Notifies the listener that an attempt was made to execute a command with + * no handler. + * + * @param commandId + * The identifier of command that is not handled; never + * null + * @param exception + * The exception that occurred; never null. + */ + public void notHandled(String commandId, NotHandledException exception); + + /** + * Notifies the listener that a command has failed to complete execution. + * + * @param commandId + * The identifier of the command that has executed; never + * null. + * @param exception + * The exception that occurred; never null. + */ + public void postExecuteFailure(String commandId, + ExecutionException exception); + + /** + * Notifies the listener that a command has completed execution + * successfully. + * + * @param commandId + * The identifier of the command that has executed; never + * null. + * @param returnValue + * The return value from the command; may be null. + */ + public void postExecuteSuccess(String commandId, Object returnValue); + + /** + * Notifies the listener that a command is about to execute. + * + * @param commandId + * The identifier of the command that is about to execute, never + * null. + * @param event + * The event that will be passed to the execute + * method; never null. + */ + public void preExecute(String commandId, ExecutionEvent event); +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/IExecutionListenerWithChecks.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/IExecutionListenerWithChecks.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,61 @@ +/******************************************************************************* + * Copyright (c) 2005, 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ + +module dwtx.core.commands.IExecutionListenerWithChecks; + +import dwtx.core.commands.common.NotDefinedException; +import dwtx.core.commands.IExecutionListener; +import dwtx.core.commands.NotEnabledException; + +import dwt.dwthelper.utils; + +/** + *

+ * A listener to the execution of commands. This listener will be notified if + * someone tries to execute a command and it is not enabled or not defined. The + * listener also be notified if a command is about to execute, and when that + * execution completes. It is not possible for the listener to prevent the + * execution, only to respond to it in some way. + *

+ *

+ * Clients may implement, but must not extend. + *

+ * + * @since 3.2 + */ +public interface IExecutionListenerWithChecks : IExecutionListener { + + /** + * Notifies the listener that an attempt was made to execute a command that + * is not defined. + * + * @param commandId + * The identifier of command that is not defined; never + * null + * @param exception + * The exception that occurred; never null. + */ + public void notDefined(String commandId, NotDefinedException exception); + + /** + * Notifies the listener that an attempt was made to execute a command that + * is disabled. + * + * @param commandId + * The identifier of command that is not enabled; never + * null + * @param exception + * The exception that occurred; never null. + */ + public void notEnabled(String commandId, NotEnabledException exception); +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/IHandler.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/IHandler.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,93 @@ +/******************************************************************************* + * Copyright (c) 2004, 2005 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.commands.IHandler; + +import dwtx.core.commands.IHandlerListener; +import dwtx.core.commands.ExecutionEvent; + + +/** + * A handler is the pluggable piece of a command that handles execution. Each + * command can have zero or more handlers associated with it (in general), of + * which only one will be active at any given moment in time. When the command + * is asked to execute, it will simply pass that request on to its active + * handler, if any. + * + * @see AbstractHandler + * @since 3.1 + */ +public interface IHandler { + + /** + * Registers an instance of IHandlerListener to listen for + * changes to properties of this instance. + * + * @param handlerListener + * the instance to register. Must not be null. If + * an attempt is made to register an instance which is already + * registered with this instance, no operation is performed. + */ + void addHandlerListener(IHandlerListener handlerListener); + + /** + * Disposes of this handler. This method is run once when the object is no + * longer referenced. This can be used as an opportunity to unhook listeners + * from other objects. + */ + public void dispose(); + + /** + * Executes with the map of parameter values by name. + * + * @param event + * An event containing all the information about the current + * state of the application; must not be null. + * @return the result of the execution. Reserved for future use, must be + * null. + * @throws ExecutionException + * if an exception occurred during execution. + */ + Object execute(ExecutionEvent event); + + /** + * Returns whether this handler is capable of executing at this moment in + * time. + * + * @return true if the command is enabled; false + * otherwise. + */ + public bool isEnabled(); + + /** + * Returns whether this handler is really capable of handling delegation. In + * the case of a handler that is a composition of other handlers, this reply + * is intended to indicate whether the handler is truly capable of receiving + * delegated responsibilities at this time. + * + * @return true if the handler is handled; false + * otherwise. + */ + public bool isHandled(); + + /** + * Unregisters an instance of IHandlerListener listening for + * changes to properties of this instance. + * + * @param handlerListener + * the instance to unregister. Must not be null. + * If an attempt is made to unregister an instance which is not + * already registered with this instance, no operation is + * performed. + */ + void removeHandlerListener(IHandlerListener handlerListener); +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/IHandlerAttributes.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/IHandlerAttributes.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright (c) 2005 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.commands.IHandlerAttributes; + +import dwt.dwthelper.utils; + +/** + *

+ * Attribute constants that have special meanings within this package. These + * attributes can be used to communicate extra information from the handler to + * either the command or the command manager. + *

+ * + * @since 3.1 + */ +public interface IHandlerAttributes { + + /** + *

+ * The name of the attribute indicating whether the handler is handled. + * This is intended largely for backward compatibility with the workbench + * RetargetAction class. It is used to indicate that while + * the handler is handling a command, it should not be treated as such. + * The command should act and behave as if it has no handler. + *

+ */ + public static final String ATTRIBUTE_HANDLED = "handled"; //$NON-NLS-1$ + +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/IHandlerListener.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/IHandlerListener.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright (c) 2004, 2005 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.commands.IHandlerListener; + +import dwtx.core.commands.HandlerEvent; + +/** + * An instance of this interface can be used by clients to receive notification + * of changes to one or more instances of IHandler. + *

+ * This interface may be implemented by clients. + *

+ * + * @since 3.1 + * @see IHandler#addHandlerListener(IHandlerListener) + * @see IHandler#removeHandlerListener(IHandlerListener) + */ +public interface IHandlerListener { + + /** + * Notifies that one or more properties of an instance of + * IHandler have changed. Specific details are described in + * the HandlerEvent. + * + * @param handlerEvent + * the handler event. Guaranteed not to be null. + */ + void handlerChanged(HandlerEvent handlerEvent); +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/INamedHandleStateIds.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/INamedHandleStateIds.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,42 @@ +/******************************************************************************* + * Copyright (c) 2005, 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ + +module dwtx.core.commands.INamedHandleStateIds; + +import dwt.dwthelper.utils; + +/** + *

+ * State identifiers that are understood by named handle objects that implement + * {@link IObjectWithState}. + *

+ *

+ * Clients may implement or extend this class. + *

+ * + * @since 3.2 + */ +public interface INamedHandleStateIds { + + /** + * The state id used for overriding the description of a named handle + * object. This state's value must return a {@link String}. + */ + public static String DESCRIPTION = "DESCRIPTION"; //$NON-NLS-1$ + + /** + * The state id used for overriding the name of a named handle object. This + * state's value must return a {@link String}. + */ + public static String NAME = "NAME"; //$NON-NLS-1$ +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/IObjectWithState.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/IObjectWithState.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,73 @@ +/******************************************************************************* + * Copyright (c) 2005, 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ + +module dwtx.core.commands.IObjectWithState; + +import dwtx.core.commands.State; + +import dwt.dwthelper.utils; + +/** + *

+ * An object that holds zero or more state objects. This state information can + * be shared between different instances of IObjectWithState. + *

+ *

+ * Clients may implement, but must not extend this interface. + *

+ * + * @see AbstractHandlerWithState + * @since 3.2 + */ +public interface IObjectWithState { + + /** + * Adds state to this object. + * + * @param id + * The identifier indicating the type of state being added; must + * not be null. + * @param state + * The new state to add to this object; must not be + * null. + */ + public void addState(String id, State state); + + /** + * Gets the state with the given id. + * + * @param stateId + * The identifier of the state to retrieve; must not be + * null. + * @return The state; may be null if there is no state with + * the given id. + */ + public State getState(String stateId); + + /** + * Gets the identifiers for all of the state associated with this object. + * + * @return All of the state identifiers; may be empty, but never + * null. + */ + public String[] getStateIds(); + + /** + * Removes state from this object. + * + * @param stateId + * The id of the state to remove from this object; must not be + * null. + */ + public void removeState(String stateId); +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/IParameter.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/IParameter.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,63 @@ +/******************************************************************************* + * Copyright (c) 2005 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 + * Port to the D programming language: + * Frank Benoit + ******************************************************************************/ + +module dwtx.core.commands.IParameter; + +import dwtx.core.commands.IParameterValues; + +import dwt.dwthelper.utils; + +/** + *

+ * A parameter for a command. A parameter identifies a type of information that + * the command might accept. For example, a "Show View" command might accept the + * id of a view for display. This parameter also identifies possible values, for + * display in the user interface. + *

+ * + * @since 3.1 + */ +public interface IParameter { + + /** + * Returns the identifier for this parameter. + * + * @return The identifier; never null. + */ + public String getId(); + + /** + * Returns the human-readable name for this parameter. + * + * @return The parameter name; never null. + */ + public String getName(); + + /** + * Returns the values associated with this parameter. + * + * @return The values associated with this parameter. This must not be + * null. + * @throws ParameterValuesException + * If the values can't be retrieved for some reason. + */ + public IParameterValues getValues(); + + /** + * Returns whether parameter is optional. Otherwise, it is required. + * + * @return true if the parameter is optional; + * false if it is required. + */ + public bool isOptional(); +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/IParameterTypeListener.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/IParameterTypeListener.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,42 @@ +/******************************************************************************* + * Copyright (c) 2005, 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.commands.IParameterTypeListener; + +import dwtx.core.commands.ParameterTypeEvent; + +import dwt.dwthelper.utils; + +/** + * An instance of this interface can be used by clients to receive notification + * of changes to one or more instances of {@link ParameterType}. + *

+ * This interface may be implemented by clients. + *

+ * + * @since 3.2 + * @see ParameterType#addListener(IParameterTypeListener) + * @see ParameterType#removeListener(IParameterTypeListener) + */ +public interface IParameterTypeListener { + + /** + * Notifies that one or more properties of an instance of + * {@link ParameterType} have changed. Specific details are described in the + * {@link ParameterTypeEvent}. + * + * @param parameterTypeEvent + * the event. Guaranteed not to be null. + */ + void parameterTypeChanged(ParameterTypeEvent parameterTypeEvent); + +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/IParameterValues.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/IParameterValues.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright (c) 2005 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 + * Port to the D programming language: + * Frank Benoit + ******************************************************************************/ + +module dwtx.core.commands.IParameterValues; + +import tango.util.collection.model.Map; + +import dwt.dwthelper.utils; + +/** + *

+ * The parameters for a command. This interface will only be consulted if the + * parameters need to be displayed to the user. Otherwise, they will be ignored. + *

+ * + * @since 3.1 + */ +public interface IParameterValues { + + /** + * Returns a map keyed by externalized names for parameter values. These + * names should be human-readable, and are generally for display to the user + * in a user interface of some sort. The values should be actual values that + * will be interpreted by the handler for the command. + * + * @return A map of the externalizable name of the parameter value (String) + * to the actual value of the parameter (String). + */ + public Map!(String,String) getParameterValues(); +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/IStateListener.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/IStateListener.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright (c) 2005, 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ + +module dwtx.core.commands.IStateListener; + +import dwtx.core.commands.State; + +/** + *

+ * A listener to changes in some state. + *

+ *

+ * Clients may implement, but must not extend this interface. + *

+ * + * @since 3.2 + */ +public interface IStateListener { + + /** + * Handles a change to the value in some state. + * + * @param state + * The state that has changed; never null. The + * value for this state has been updated to the new value. + * @param oldValue + * The old value; may be anything. + */ + public void handleStateChange(State state, Object oldValue); +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/ITypedParameter.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/ITypedParameter.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (c) 2005, 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.commands.ITypedParameter; + +import dwtx.core.commands.ParameterType; + +/** + * A command parameter that has a declared type. This interface is intended to + * be implemented by implementors of {@link IParameter} that will support + * parameter types. + * + * @since 3.2 + */ +public interface ITypedParameter { + + /** + * Returns the {@link ParameterType} associated with a command parameter or + * null if the parameter does not declare a type. + *

+ * Note that the parameter type returned may be undefined. + *

+ * + * @return the parameter type associated with a command parameter or + * null if the parameter does not declare a type + */ + public ParameterType getParameterType(); +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/NamedHandleObjectWithState.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/NamedHandleObjectWithState.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,136 @@ +/******************************************************************************* + * Copyright (c) 2005, 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ + +module dwtx.core.commands.NamedHandleObjectWithState; + +import tango.util.collection.HashMap; +import tango.util.collection.model.Map; +// import tango.util.collection.model.Set; + +import dwtx.core.commands.common.NamedHandleObject; +import dwtx.core.commands.common.NotDefinedException; +import dwtx.core.commands.IObjectWithState; +import dwtx.core.commands.State; +import dwtx.core.commands.INamedHandleStateIds; + +import dwt.dwthelper.utils; + +/** + *

+ * A named handle object that can carry state with it. This state can be used to + * override the name or description. + *

+ *

+ * Clients may neither instantiate nor extend this class. + *

+ * + * @since 3.2 + */ +abstract class NamedHandleObjectWithState : NamedHandleObject, + IObjectWithState { + + /** + * An empty string array, which can be returned from {@link #getStateIds()} + * if there is no state. + */ + private static const String[] NO_STATE = null; + + /** + * The map of states currently held by this command. If this command has no + * state, then this will be null. + */ + private Map!(String,State) states = null; + + /** + * Constructs a new instance of NamedHandleObject. + * + * @param id + * The identifier for this handle; must not be null. + */ + protected this(String id) { + super(id); + } + + public void addState(String stateId, State state) { + if (state is null) { + throw new NullPointerException("Cannot add a null state"); //$NON-NLS-1$ + } + + if (states is null) { + states = new HashMap!(String,State)/+(3)+/; + } + states.add(stateId, state); + } + + public final String getDescription() { + String description = super.getDescription(); // Trigger a NDE. + + State descriptionState = getState(INamedHandleStateIds.DESCRIPTION); + if (descriptionState !is null) { + Object value = descriptionState.getValue(); + if (value !is null) { + return value.toString(); + } + } + + return description; + } + + public final String getName() { + String name = super.getName(); // Trigger a NDE, if necessary. + + State nameState = getState(INamedHandleStateIds.NAME); + if (nameState !is null) { + final Object value = nameState.getValue(); + if (value !is null) { + return value.toString(); + } + } + + return name; + } + + public final State getState(String stateId) { + if ((states is null) || (states.drained())) { + return null; + } + + return states.get(stateId); + } + + public final String[] getStateIds() { + if ((states is null) || (states.drained())) { + return NO_STATE; + } + + String[] res; + foreach( id, state; states){ + res ~= id; + } + return res; + } + + public void removeState(String id) { + if (id is null) { + throw new NullPointerException("Cannot remove a null id"); //$NON-NLS-1$ + } + + if (states !is null) { + states.removeKey(id); + if (states.drained()) { + states = null; + } + } + } + +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/NotEnabledException.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/NotEnabledException.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,45 @@ +/******************************************************************************* + * Copyright (c) 2004, 2005 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.commands.NotEnabledException; + +import dwtx.core.commands.common.CommandException; + +import dwt.dwthelper.utils; + +/** + * Signals that an attempt was made to execute a command that is not enabled. + *

+ * This class is not intended to be extended by clients. + *

+ * + * @since 3.2 + */ +public final class NotEnabledException : CommandException { + + /** + * Generated serial version UID for this class. + * + * @since 3.1 + */ + private static final long serialVersionUID = 3257572788998124596L; + + /** + * Creates a new instance of this class with the specified detail message. + * + * @param s + * the detail message; may be null. + */ + public this(String s) { + super(s); + } +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/NotHandledException.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/NotHandledException.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright (c) 2004, 2005 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.commands.NotHandledException; + +import dwtx.core.commands.common.CommandException; + +import dwt.dwthelper.utils; + +/** + * Signals that an attempt was made to access the properties of an unhandled + * object. + *

+ * This class is not intended to be extended by clients. + *

+ * + * @since 3.1 + */ +public final class NotHandledException : CommandException { + + /** + * Generated serial version UID for this class. + * + * @since 3.1 + */ + private static final long serialVersionUID = 3256446914827726904L; + + /** + * Creates a new instance of this class with the specified detail message. + * + * @param s + * the detail message. + */ + public this(String s) { + super(s); + } +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/ParameterType.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/ParameterType.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,299 @@ +/******************************************************************************* + * Copyright (c) 2005, 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.commands.ParameterType; + +import dwtx.core.commands.common.HandleObject; +import dwtx.core.commands.common.NotDefinedException; +import dwtx.core.internal.commands.util.Util; +import dwtx.core.commands.AbstractParameterValueConverter; +import dwtx.core.commands.IParameterTypeListener; +import dwtx.core.commands.ParameterTypeEvent; + +import dwt.dwthelper.utils; +import tango.text.convert.Format; + +/** + *

+ * Provides information about the type of a command parameter. Clients can use a + * parameter type to check if an object matches the type of the parameter with + * {@link #isCompatible(Object)} and can get an + * {@link AbstractParameterValueConverter} to convert between objects matching + * the parameter type and strings that encode the object's identity. + *

+ *

+ * A command parameter is not required to declare a type. To determine if a + * given parameter has a type, check if an {@link IParameter} implements + * {@link ITypedParameter} and if so, use + * {@link ITypedParameter#getParameterType()} like this: + *

+ * + *
+ *                   IParameter parameter = // ... get IParameter from Command
+ *                   if (parameter instanceof ITypedParameter) {
+ *                     ParameterType type = ((ITypedParameter)parameter).getParameterType();
+ *                     if (type !is null) {
+ *                       // this parameter has a ParameterType
+ *                     }
+ *                   }
+ * 
+ * + * @see IParameter + * @see ITypedParameter#getParameterType() + * @since 3.2 + */ +public final class ParameterType : HandleObject, Comparable { + + /** + * TODO: this was copied from + * dwtx.core.internal.expressions.Expressions is there a better place + * to reference this? + * + * @param element + * The element to test; may be null. + * @param type + * The type against which we are testing;may be null. + * @return true if the element is an instance + * of type; false otherwise. + */ + private static final bool isInstanceOf(Object element, + String type) { + // null isn't an instanceof of anything. + if (element is null) { + return false; + } + return isSubtype(element.classinfo, type); + } + + /** + * TODO: this was copied from + * dwtx.core.internal.expressions.Expressions is there a better place + * to reference this? + * + * @param clazz + * The class to match; may be null. + * @param type + * The type against which we are testing;may be null. + * @return true if the element is an instance + * of type; false otherwise. + */ + private static final bool isSubtype(ClassInfo clazz, String type) { + if (clazz.name.equals(type)) { + return true; + } + ClassInfo superClass = clazz.base; + if (superClass !is null && isSubtype(superClass, type)) { + return true; + } + Interface[] interfaces = clazz.interfaces; + for (int i = 0; i < interfaces.length; i++) { + if (isSubtype(interfaces[i].classinfo, type)) { + return true; + } + } + return false; + } + + /** + * An {@link AbstractParameterValueConverter} for converting parameter + * values between objects and strings. This may be null. + */ + private /+transient+/ AbstractParameterValueConverter parameterTypeConverter; + + /** + * A string specifying the object type of this parameter type. This will be + * null when the parameter type is undefined but never null + * when it is defined. + */ + private /+transient+/ String type = null; + + /** + * Constructs a new instance based on the given identifier. When a parameter + * type is first constructed, it is undefined. Parameter types should only + * be constructed by the {@link CommandManager} to ensure that the + * identifier remains unique. + * + * @param id + * The identifier for this type. This value must not be + * null, and must be unique amongst all parameter + * types. + */ + this(String id) { + super(id); + } + + /** + * Adds a listener to this parameter type that will be notified when its + * state changes. + * + * @param listener + * The listener to be added; must not be null. + */ + public final void addListener(IParameterTypeListener listener) { + addListenerObject(cast(Object)listener); + } + + /** + * Compares this parameter type with another object by comparing each of the + * non-transient attributes. + * + * @param object + * The object with which to compare; must be an instance of + * {@link ParameterType}. + * @return A negative integer, zero or a positive integer, if the object is + * greater than, equal to or less than this parameter type. + */ + public final int compareTo(Object object) { + ParameterType castedObject = cast(ParameterType) object; + int compareTo = Util.compare(defined, castedObject.defined); + if (compareTo is 0) { + compareTo = Util.compare(id, castedObject.id); + } + return compareTo; + } + + /** + *

+ * Defines this parameter type, setting the defined property to + * true. + *

+ *

+ * Notification is sent to all listeners that something has changed. + *

+ * + * @param type + * a string identifying the Java object type for this parameter + * type; null is interpreted as + * "java.lang.Object" + * @param parameterTypeConverter + * an {@link AbstractParameterValueConverter} to perform + * string/object conversions for parameter values; may be + * null + */ + public final void define(String type, + AbstractParameterValueConverter parameterTypeConverter) { + + bool definedChanged = !this.defined; + this.defined = true; + + this.type = (type is null) ? Object.classinfo.name : type; + this.parameterTypeConverter = parameterTypeConverter; + + fireParameterTypeChanged(new ParameterTypeEvent(this, definedChanged)); + } + + /** + * Notifies all listeners that this parameter type has changed. This sends + * the given event to all of the listeners, if any. + * + * @param event + * The event to send to the listeners; must not be + * null. + */ + private final void fireParameterTypeChanged(ParameterTypeEvent event) { + if (event is null) { + throw new NullPointerException( + "Cannot send a null event to listeners."); //$NON-NLS-1$ + } + + if (!isListenerAttached()) { + return; + } + + Object[] listeners = getListeners(); + for (int i = 0; i < listeners.length; i++) { + IParameterTypeListener listener = cast(IParameterTypeListener) listeners[i]; + listener.parameterTypeChanged(event); + } + } + + /** + * Returns the value converter associated with this parameter, if any. + * + * @return The parameter value converter, or null if there is + * no value converter for this parameter. + * @throws NotDefinedException + * if the parameter type is not currently defined + */ + public final AbstractParameterValueConverter getValueConverter() { + if (!isDefined()) { + throw new NotDefinedException( + "Cannot use getValueConverter() with an undefined ParameterType"); //$NON-NLS-1$ + } + + return parameterTypeConverter; + } + + /** + * Returns whether the provided value is compatible with this parameter + * type. An object is compatible with a parameter type if the object is an + * instance of the class defined as the parameter's type class. + * + * @param value + * an object to check for compatibility with this parameter type; + * may be null. + * @return true if the value is compatible with this type, + * false otherwise + * @throws NotDefinedException + * if the parameter type is not currently defined + */ + public bool isCompatible(Object value) { + if (!isDefined()) { + throw new NotDefinedException( + "Cannot use isCompatible() with an undefined ParameterType"); //$NON-NLS-1$ + } + return isInstanceOf(value, type); + } + + /** + * Unregisters listener for changes to properties of this parameter type. + * + * @param listener + * the instance to unregister. Must not be null. + * If an attempt is made to unregister an instance which is not + * already registered with this instance, no operation is + * performed. + */ + public final void removeListener(IParameterTypeListener listener) { + removeListenerObject(cast(Object)listener); + } + + /** + * The string representation of this parameter type. For debugging purposes + * only. This string should not be shown to an end user. + * + * @return The string representation; never null. + */ + public override final String toString() { + if (string is null) { + string = Format( "ParameterType({},{})", id, defined ); + } + return string; + } + + /** + * Makes this parameter type become undefined. Notification is sent to all + * listeners. + */ + public final void undefine() { + string = null; + + final bool definedChanged = defined; + defined = false; + + type = null; + parameterTypeConverter = null; + + fireParameterTypeChanged(new ParameterTypeEvent(this, definedChanged)); + } + +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/ParameterTypeEvent.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/ParameterTypeEvent.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,68 @@ +/******************************************************************************* + * Copyright (c) 2005, 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.commands.ParameterTypeEvent; + +import dwtx.core.commands.common.AbstractHandleObjectEvent; +import dwtx.core.commands.ParameterType; + +import dwt.dwthelper.utils; + +/** + * An instance of this class describes changes to an instance of + * {@link ParameterType}. + *

+ * This class is not intended to be extended by clients. + *

+ * + * @see IParameterTypeListener#parameterTypeChanged(ParameterTypeEvent) + * @since 3.2 + */ +public final class ParameterTypeEvent : AbstractHandleObjectEvent { + + /** + * The parameter type that has changed. This value is never + * null. + */ + private final ParameterType parameterType; + + /** + * Constructs a new instance. + * + * @param parameterType + * The parameter type that changed; must not be null. + * @param definedChanged + * true, iff the defined property changed. + */ + this(ParameterType parameterType, + bool definedChanged) { + + super(definedChanged); + + if (parameterType is null) { + throw new NullPointerException(); + } + + this.parameterType = parameterType; + } + + /** + * Returns the instance of the parameter type that changed. + * + * @return the instance of the parameter type that changed. Guaranteed not + * to be null. + */ + public final ParameterType getParameterType() { + return parameterType; + } + +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/ParameterValueConversionException.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/ParameterValueConversionException.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright (c) 2005, 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.commands.ParameterValueConversionException; + +import dwtx.core.commands.common.CommandException; + +import dwt.dwthelper.utils; + +/** + * Signals that a problem occurred while converting a command parameter value + * from string to object, or object to string. + * + * @see AbstractParameterValueConverter + * @since 3.2 + */ +public class ParameterValueConversionException : CommandException { + + /** + * Generated serial version UID for this class. + */ + private static final long serialVersionUID = 4703077729505066104L; + + /** + * Creates a new instance of this class with the specified detail message. + * + * @param message + * the detail message; may be null. + */ + public this(String message) { + super(message); + } + + /** + * Creates a new instance of this class with the specified detail message + * and cause. + * + * @param message + * the detail message; may be null. + * @param cause + * the cause; may be null. + */ + public this(String message, + Exception cause) { + super(message, cause); + } +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/ParameterValuesException.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/ParameterValuesException.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright (c) 2005 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 + * Port to the D programming language: + * Frank Benoit + ******************************************************************************/ + +module dwtx.core.commands.ParameterValuesException; + +import dwtx.core.commands.common.CommandException; + +import dwt.dwthelper.utils; + +/** + *

+ * Signals that a problem has occurred while trying to create an instance of + * IParameterValues. In applications based on the registry + * provided by core, this usually indicates a problem creating an + * IExecutableExtension. For other applications, this exception + * could be used to signify any general problem during initialization. + *

+ * + * @since 3.1 + * + */ +public final class ParameterValuesException : CommandException { + + /** + * Generated serial version UID for this class. + */ + private static final long serialVersionUID = 3618976793520845623L; + + /** + * Creates a new instance of this class with the specified detail message + * and cause. + * + * @param message + * the detail message; may be null. + * @param cause + * the cause; may be null. + */ + public this(String message, Exception cause) { + super(message, cause); + } +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/Parameterization.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/Parameterization.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,167 @@ +/******************************************************************************* + * Copyright (c) 2005 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 + * Port to the D programming language: + * Frank Benoit + ******************************************************************************/ + +module dwtx.core.commands.Parameterization; + +import tango.util.collection.model.Map; + +import dwtx.core.commands.IParameter; +import dwtx.core.internal.commands.util.Util; + +import dwt.dwthelper.utils; + +/** + *

+ * A parameter with a specific value. This is usually a part of a + * ParameterizedCommand, which is used to refer to a command + * with a collection of parameterizations. + *

+ * + * @since 3.1 + */ +public final class Parameterization { + + /** + * The constant integer hash code value meaning the hash code has not yet + * been computed. + */ + private static const int HASH_CODE_NOT_COMPUTED = -1; + + /** + * A factor for computing the hash code for all parameterized commands. + */ + private static const int HASH_FACTOR = 89; + + /** + * The seed for the hash code for all parameterized commands. + */ + private static const int HASH_INITIAL; + + static this(){ + HASH_INITIAL = dwt.dwthelper.utils.toHash( Parameterization.classinfo.name ); + } + /** + * The hash code for this object. This value is computed lazily, and marked + * as invalid when one of the values on which it is based changes. + */ + private /+transient+/ hash_t hashCode = HASH_CODE_NOT_COMPUTED; + + /** + * The parameter that is being parameterized. This value is never + * null. + */ + private const IParameter parameter; + + /** + * The value that defines the parameterization. This value may be + * null. + */ + private const String value; + + /** + * Constructs a new instance of Parameterization. + * + * @param parameter + * The parameter that is being parameterized; must not be + * null. + * @param value + * The value for the parameter; may be null. + */ + public this(IParameter parameter, String value) { + if (parameter is null) { + throw new NullPointerException( + "You cannot parameterize a null parameter"); //$NON-NLS-1$ + } + + this.parameter = parameter; + this.value = value; + } + + /* (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + public final bool equals(Object object) { + if (this is object) { + return true; + } + + if (!(cast(Parameterization)object)) { + return false; + } + + Parameterization parameterization = cast(Parameterization) object; + if (!(Util.equals(this.parameter.getId(), parameterization.parameter + .getId()))) { + return false; + } + + return Util.equals(this.value, parameterization.value); + } + + /** + * Returns the parameter that is being parameterized. + * + * @return The parameter; never null. + */ + public final IParameter getParameter() { + return parameter; + } + + /** + * Returns the value for the parameter in this parameterization. + * + * @return The value; may be null. + */ + public final String getValue() { + return value; + } + + /** + * Returns the human-readable name for the current value, if any. If the + * name cannot be found, then it simply returns the value. It also ensures + * that any null values are converted into an empty string. + * + * @return The human-readable name of the value; never null. + * @throws ParameterValuesException + * If the parameter needed to be initialized, but couldn't be. + */ + public final String getValueName() { + auto parameterValues = parameter.getValues().getParameterValues(); + String returnValue = null; + foreach( k, v; parameterValues ){ + if (Util.equals(value, v)) { + returnValue = k; + break; + } + } + if (returnValue is null) { + return Util.ZERO_LENGTH_STRING; + } + + return returnValue; + } + + /* (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + public override final hash_t toHash() { + if (hashCode is HASH_CODE_NOT_COMPUTED) { + hashCode = HASH_INITIAL * HASH_FACTOR + Util.toHash(cast(Object)parameter); + hashCode = hashCode * HASH_FACTOR + Util.toHash(value); + if (hashCode is HASH_CODE_NOT_COMPUTED) { + hashCode++; + } + } + return hashCode; + } +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/ParameterizedCommand.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/ParameterizedCommand.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,666 @@ +/******************************************************************************* + * Copyright (c) 2005, 2007 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ + +module dwtx.core.commands.ParameterizedCommand; + +// import java.util.ArrayList; +// import java.util.Collection; +// import java.util.Collections; +// import java.util.HashMap; +// import java.util.Iterator; +// import java.util.List; +// import java.util.Map; + +import tango.util.collection.model.Seq; +import tango.util.collection.model.Map; +import tango.util.collection.model.Set; +import tango.util.collection.ArraySeq; +import tango.util.collection.HashSet; +import tango.util.collection.HashMap; + +import dwtx.core.commands.Command; +import dwtx.core.commands.CommandManager; +import dwtx.core.commands.IParameter; +import dwtx.core.commands.IParameterValues; +import dwtx.core.commands.Parameterization; +import dwtx.core.commands.ParameterValuesException; +import dwtx.core.commands.ExecutionEvent; +import dwtx.core.commands.common.NotDefinedException; +import dwtx.core.internal.commands.util.Util; + +import dwt.dwthelper.utils; +import tango.text.convert.Format; + +static import tango.text.Text; +alias tango.text.Text.Text!(char) StringBuffer; + +/** + *

+ * A command that has had one or more of its parameters specified. This class + * serves as a utility class for developers that need to manipulate commands + * with parameters. It handles the behaviour of generating a parameter map and a + * human-readable name. + *

+ * + * @since 3.1 + */ +public final class ParameterizedCommand : Comparable { + private static const Map!(String,String) EMPTY_MAP; + /** + * The constant integer hash code value meaning the hash code has not yet + * been computed. + */ + private static const int HASH_CODE_NOT_COMPUTED = -1; + + /** + * A factor for computing the hash code for all parameterized commands. + */ + private static const int HASH_FACTOR = 89; + + /** + * The seed for the hash code for all parameterized commands. + */ + private static const int HASH_INITIAL; + + static this(){ + HASH_INITIAL = dwt.dwthelper.utils.toHash(ParameterizedCommand.classinfo.name ); + EMPTY_MAP = new HashMap!(String,String); + } + /** + * The index of the parameter id in the parameter values. + * + * @deprecated no longer used + */ + public static const int INDEX_PARAMETER_ID = 0; + + /** + * The index of the human-readable name of the parameter itself, in the + * parameter values. + * + * @deprecated no longer used + */ + public static const int INDEX_PARAMETER_NAME = 1; + + /** + * The index of the human-readable name of the value of the parameter for + * this command. + * + * @deprecated no longer used + */ + public static const int INDEX_PARAMETER_VALUE_NAME = 2; + + /** + * The index of the value of the parameter that the command can understand. + * + * @deprecated no longer used + */ + public static const int INDEX_PARAMETER_VALUE_VALUE = 3; + + /** + * Escapes special characters in the command id, parameter ids and parameter + * values for {@link #serialize()}. The special characters + * {@link CommandManager#PARAMETER_START_CHAR}, + * {@link CommandManager#PARAMETER_END_CHAR}, + * {@link CommandManager#ID_VALUE_CHAR}, + * {@link CommandManager#PARAMETER_SEPARATOR_CHAR} and + * {@link CommandManager#ESCAPE_CHAR} are escaped by prepending a + * {@link CommandManager#ESCAPE_CHAR} character. + * + * @param rawText + * a String to escape special characters in for + * serialization. + * @return a String representing rawText with + * special serialization characters escaped + * @see CommandManager#unescape(String) + * @since 3.2 + */ + private static final String escape(String rawText) { + + // defer initialization of a StringBuffer until we know we need one + StringBuffer buffer; + + for (int i = 0; i < rawText.length; i++) { + + char c = rawText.charAt(i); + switch (c) { + case CommandManager.PARAMETER_START_CHAR: + case CommandManager.PARAMETER_END_CHAR: + case CommandManager.ID_VALUE_CHAR: + case CommandManager.PARAMETER_SEPARATOR_CHAR: + case CommandManager.ESCAPE_CHAR: + if (buffer is null) { + buffer = new StringBuffer(rawText.substring(0, i)); + } + buffer.append(CommandManager.ESCAPE_CHAR); + buffer.append(c); + break; + default: + if (buffer !is null) { + buffer.append(c); + } + break; + } + + } + + if (buffer is null) { + return rawText; + } + return buffer.toString(); + } + + /** + * Generates every possible combination of parameter values for the given + * parameters. Parameters values that cannot be initialized are just + * ignored. Optional parameters are considered. + * + * @param startIndex + * The index in the parameters that we should + * process. This must be a valid index. + * @param parameters + * The parameters in to process; must not be null. + * @return A collection (Collection) of combinations (List + * of Parameterization). + */ + private static final Seq!(Object) expandParameters(int startIndex, + IParameter[] parameters) { + int nextIndex = startIndex + 1; + bool noMoreParameters = (nextIndex >= parameters.length); + + IParameter parameter = parameters[startIndex]; + auto parameterizations = new ArraySeq!(Object); + if (parameter.isOptional()) { + parameterizations.append( cast(Object) null); + } + + IParameterValues values = null; + try { + values = parameter.getValues(); + } catch (ParameterValuesException e) { + if (noMoreParameters) { + return parameterizations; + } + + // Make recursive call + return expandParameters(nextIndex, parameters); + } + +// Iterator parameterValueItr = parameterValues.entrySet() +// .iterator(); + auto parameterValues = values.getParameterValues(); + auto set = new HashSet!(String); + foreach( k,v; parameterValues ){ + set.add( v ); + } + + + //while (parameterValueItr.hasNext()) { + foreach( v; set ){ + //Map.Entry entry = (Map.Entry) parameterValueItr.next(); + Parameterization parameterization = new Parameterization( + parameter, v); + parameterizations.append(parameterization); + } + + // Check if another iteration will produce any more names. + int parameterizationCount = parameterizations.size(); + if (noMoreParameters) { + // This is it, so just return the current parameterizations. + for (int i = 0; i < parameterizationCount; i++) { + Parameterization parameterization = cast(Parameterization) parameterizations + .get(i); + auto combination = new ArraySeq!(Object); + combination.append(parameterization); + parameterizations.replaceAt(i, combination); + } + return parameterizations; + } + + // Make recursive call + auto suffixes = expandParameters(nextIndex, parameters); + suffixes.removeAll(cast(Object)null); + if (suffixes.drained()) { + // This is it, so just return the current parameterizations. + for (int i = 0; i < parameterizationCount; i++) { + Parameterization parameterization = cast(Parameterization) parameterizations + .get(i); + auto combination = new ArraySeq!(Object); + combination.append(parameterization); + parameterizations.replaceAt(i, combination); + } + return parameterizations; + } + auto returnValue = new ArraySeq!(Object); +// Iterator suffixItr = suffixes.iterator(); +// while (suffixItr.hasNext()) { + foreach( v; suffixes ){ +// final List combination = (List) suffixItr.next(); + auto combination = cast(Seq!(Object)) v; + int combinationSize = combination.size(); + for (int i = 0; i < parameterizationCount; i++) { + Parameterization parameterization = cast(Parameterization) parameterizations + .get(i); + auto newCombination = new ArraySeq!(Object); + newCombination.capacity(combinationSize + 1); + newCombination.append(parameterization); + foreach( c; combination ){ + newCombination.append(c); + } + returnValue.append(newCombination); + } + } + + return returnValue; + } + + /** + *

+ * Generates all the possible combinations of command parameterizations for + * the given command. If the command has no parameters, then this is simply + * a parameterized version of that command. If a parameter is optional, both + * the included and not included cases are considered. + *

+ *

+ * If one of the parameters cannot be loaded due to a + * ParameterValuesException, then it is simply ignored. + *

+ * + * @param command + * The command for which the parameter combinations should be + * generated; must not be null. + * @return A collection of ParameterizedCommand instances + * representing all of the possible combinations. This value is + * never empty and it is never null. + * @throws NotDefinedException + * If the command is not defined. + */ + public static final Seq!(Object) generateCombinations(Command command) { + IParameter[] parameters = command.getParameters(); + if (parameters is null) { + auto res = new ArraySeq!(Object); + res.append( new ParameterizedCommand(command, null) ); + return res; + } + + auto expansion = expandParameters(0, parameters); + auto combinations = new ArraySeq!(Object); + combinations.capacity(expansion.size()); + foreach( v; expansion ){ +// Iterator expansionItr = expansion.iterator(); +// while (expansionItr.hasNext()) { + auto combination = cast(Seq!(Object)) v; + if (combination is null) { + combinations.append(new ParameterizedCommand(command, null)); + } else { + combination.removeAll(cast(Object)null); + if (combination.drained()) { + combinations.append(new ParameterizedCommand(command, null)); + } else { + Parameterization[] parameterizations = cast(Parameterization[]) combination + .toArray(); + combinations.append(new ParameterizedCommand(command, + parameterizations)); + } + } + } + + return combinations; + } + + /** + * The base command which is being parameterized. This value is never + * null. + */ + private const Command command; + + /** + * The hash code for this object. This value is computed lazily, and marked + * as invalid when one of the values on which it is based changes. + */ + private /+transient+/ int hashCode = HASH_CODE_NOT_COMPUTED; + + /** + * This is an array of parameterization defined for this command. This value + * may be null if the command has no parameters. + */ + private const Parameterization[] parameterizations; + + private String name; + + /** + * Constructs a new instance of ParameterizedCommand with + * specific values for zero or more of its parameters. + * + * @param command + * The command that is parameterized; must not be + * null. + * @param parameterizations + * An array of parameterizations binding parameters to values for + * the command. This value may be null. This + * argument is not copied; if you need to make changes to it + * after constructing this parameterized command, then make a + * copy yourself. + */ + public this(Command command, + Parameterization[] parameterizations) { + if (command is null) { + throw new NullPointerException( + "A parameterized command cannot have a null command"); //$NON-NLS-1$ + } + + this.command = command; + this.parameterizations = (parameterizations is null || parameterizations.length is 0) ? null + : parameterizations; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Comparable#compareTo(java.lang.Object) + */ + public final int compareTo(Object object) { + ParameterizedCommand command = cast(ParameterizedCommand) object; + bool thisDefined = this.command.isDefined(); + bool otherDefined = command.command.isDefined(); + if (!thisDefined || !otherDefined) { + return Util.compare(thisDefined, otherDefined); + } + + try { + int compareTo = getName() < command.getName(); + if (compareTo is 0) { + return getId() < command.getId(); + } + return compareTo; + } catch (NotDefinedException e) { + throw new Exception ( + "Concurrent modification of a command's defined state"); //$NON-NLS-1$ + } + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#equals(java.lang.Object) + */ + public final bool equals(Object object) { + if (this is object) { + return true; + } + + if (!(cast(ParameterizedCommand)object)) { + return false; + } + + ParameterizedCommand command = cast(ParameterizedCommand) object; + if (!Util.equals(this.command, command.command)) { + return false; + } + + return Util.equals(this.parameterizations, command.parameterizations); + } + + /** + * Executes this command with its parameters. This method will succeed + * regardless of whether the command is enabled or defined. It is + * preferrable to use {@link #executeWithChecks(Object, Object)}. + * + * @param trigger + * The object that triggered the execution; may be + * null. + * @param applicationContext + * The state of the application at the time the execution was + * triggered; may be null. + * @return The result of the execution; may be null. + * @throws ExecutionException + * If the handler has problems executing this command. + * @throws NotHandledException + * If there is no handler. + * @deprecated Please use {@link #executeWithChecks(Object, Object)} + * instead. + */ + public final Object execute(Object trigger, + Object applicationContext) { + return command.execute(new ExecutionEvent(command, getParameterMap(), + trigger, applicationContext)); + } + + /** + * Executes this command with its parameters. This does extra checking to + * see if the command is enabled and defined. If it is not both enabled and + * defined, then the execution listeners will be notified and an exception + * thrown. + * + * @param trigger + * The object that triggered the execution; may be + * null. + * @param applicationContext + * The state of the application at the time the execution was + * triggered; may be null. + * @return The result of the execution; may be null. + * @throws ExecutionException + * If the handler has problems executing this command. + * @throws NotDefinedException + * If the command you are trying to execute is not defined. + * @throws NotEnabledException + * If the command you are trying to execute is not enabled. + * @throws NotHandledException + * If there is no handler. + * @since 3.2 + */ + public final Object executeWithChecks(Object trigger, + Object applicationContext) { + return command.executeWithChecks(new ExecutionEvent(command, + getParameterMap(), trigger, applicationContext)); + } + + /** + * Returns the base command. It is possible for more than one parameterized + * command to have the same identifier. + * + * @return The command; never null, but may be undefined. + */ + public final Command getCommand() { + return command; + } + + /** + * Returns the command's base identifier. It is possible for more than one + * parameterized command to have the same identifier. + * + * @return The command id; never null. + */ + public final String getId() { + return command.getId(); + } + + /** + * Returns a human-readable representation of this command with all of its + * parameterizations. + * + * @return The human-readable representation of this parameterized command; + * never null. + * @throws NotDefinedException + * If the underlying command is not defined. + */ + public final String getName() { + if (name is null) { + StringBuffer nameBuffer = new StringBuffer(); + nameBuffer.append(command.getName()); + if (parameterizations !is null) { + nameBuffer.append(" ("); //$NON-NLS-1$ + int parameterizationCount = parameterizations.length; + for (int i = 0; i < parameterizationCount; i++) { + Parameterization parameterization = parameterizations[i]; + nameBuffer + .append(parameterization.getParameter().getName()); + nameBuffer.append(": "); //$NON-NLS-1$ + try { + nameBuffer.append(parameterization.getValueName()); + } catch (ParameterValuesException e) { + /* + * Just let it go for now. If someone complains we can + * add more info later. + */ + } + + // If there is another item, append a separator. + if (i + 1 < parameterizationCount) { + nameBuffer.append(", "); //$NON-NLS-1$ + } + } + nameBuffer.append(')'); + } + name = nameBuffer.toString(); + } + return name; + } + + /** + * Returns the parameter map, as can be used to construct an + * ExecutionEvent. + * + * @return The map of parameter ids (String) to parameter + * values (String). This map is never + * null, but may be empty. + */ + public final Map!(String,String) getParameterMap() { + if ((parameterizations is null) || (parameterizations.length is 0)) { + return EMPTY_MAP; + } + + auto parameterMap = new HashMap!(String,String); + for (int i = 0; i < parameterizations.length; i++) { + Parameterization parameterization = parameterizations[i]; + parameterMap.add(parameterization.getParameter().getId(), + parameterization.getValue()); + } + return parameterMap; + } + + /* (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + public override final hash_t toHash() { + if (hashCode is HASH_CODE_NOT_COMPUTED) { + hashCode = HASH_INITIAL * HASH_FACTOR + Util.toHash(command); + hashCode = hashCode * HASH_FACTOR; + if (parameterizations !is null) { + for (int i = 0; i < parameterizations.length; i++) { + hashCode += Util.toHash(parameterizations[i]); + } + } + if (hashCode is HASH_CODE_NOT_COMPUTED) { + hashCode++; + } + } + return hashCode; + } + + /** + * Returns a {@link String} containing the command id, parameter ids and + * parameter values for this {@link ParameterizedCommand}. The returned + * {@link String} can be stored by a client and later used to reconstruct an + * equivalent {@link ParameterizedCommand} using the + * {@link CommandManager#deserialize(String)} method. + *

+ * The syntax of the returned {@link String} is as follows: + *

+ * + *
+ * serialization = commandId [ '(' parameters ')' ]
+ * parameters = parameter [ ',' parameters ]
+ * parameter = parameterId [ '=' parameterValue ] + *
+ * + *

+ * In the syntax above, sections inside square-brackets are optional. The + * characters in single quotes ((, ), + * , and =) indicate literal characters. + *

+ *

+ * commandId represents the command id encoded with + * separator characters escaped. parameterId and + * parameterValue represent the parameter ids and + * values encoded with separator characters escaped. The separator + * characters (, ), , and + * = are escaped by prepending a %. This + * requires % to be escaped, which is also done by prepending + * a %. + *

+ *

+ * The order of the parameters is not defined (and not important). A missing + * parameterValue indicates that the value of the + * parameter is null. + *

+ *

+ * For example, the string shown below represents a serialized parameterized + * command that can be used to show the Resource perspective: + *

+ *

+ * dwtx.ui.perspectives.showPerspective(dwtx.ui.perspectives.showPerspective.perspectiveId=dwtx.ui.resourcePerspective) + *

+ *

+ * This example shows the more general form with multiple parameters, + * null value parameters, and escaped = in the + * third parameter value. + *

+ *

+ * command.id(param1.id=value1,param2.id,param3.id=esc%=val3) + *

+ * + * @return A string containing the escaped command id, parameter ids and + * parameter values; never null. + * @see CommandManager#deserialize(String) + * @since 3.2 + */ + public final String serialize() { + String escapedId = escape(getId()); + + if ((parameterizations is null) || (parameterizations.length is 0)) { + return escapedId; + } + + StringBuffer buffer = new StringBuffer(escapedId); + buffer.append(CommandManager.PARAMETER_START_CHAR); + + for (int i = 0; i < parameterizations.length; i++) { + + if (i > 0) { + // insert separator between parameters + buffer.append(CommandManager.PARAMETER_SEPARATOR_CHAR); + } + + Parameterization parameterization = parameterizations[i]; + String parameterId = parameterization.getParameter().getId(); + String escapedParameterId = escape(parameterId); + + buffer.append(escapedParameterId); + + String parameterValue = parameterization.getValue(); + if (parameterValue !is null) { + String escapedParameterValue = escape(parameterValue); + buffer.append(CommandManager.ID_VALUE_CHAR); + buffer.append(escapedParameterValue); + } + } + + buffer.append(CommandManager.PARAMETER_END_CHAR); + + return buffer.toString(); + } + + public override final String toString() { + return Format( "ParameterizedCommand({},{})", command, parameterizations); + } +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/SerializationException.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/SerializationException.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,58 @@ +/******************************************************************************* + * 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.commands.SerializationException; + +import dwtx.core.commands.common.CommandException; + +import dwt.dwthelper.utils; + +/** + * Signals that an exception occured while serializing a + * {@link ParameterizedCommand} to a string or deserializing a String to a + * {@link ParameterizedCommand}. + *

+ * This class is not intended to be extended by clients. + *

+ * + * @since 3.2 + */ +public final class SerializationException : CommandException { + + /** + * Generated serial version UID for this class. + */ + private static final long serialVersionUID = 2691599674561684949L; + + /** + * Creates a new instance of this class with the specified detail message. + * + * @param message + * the detail message; may be null. + */ + public this(String message) { + super(message); + } + + /** + * Creates a new instance of this class with the specified detail message + * and cause. + * + * @param message + * the detail message; may be null. + * @param cause + * the cause; may be null. + */ + public this(String message, Exception cause) { + super(message, cause); + } +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/State.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/State.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,141 @@ +/******************************************************************************* + * Copyright (c) 2005, 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ + +module dwtx.core.commands.State; + +import dwtx.core.commands.common.EventManager; +import dwtx.core.internal.commands.util.Util; +import dwtx.core.commands.IStateListener; + +import dwt.dwthelper.utils; + +/** + *

+ * A piece of state information that can be shared between objects, and might be + * persisted between sessions. This can be used for commands that toggle between + * two states and wish to pass this state information between different + * handlers. + *

+ *

+ * This state object can either be used as a single state object shared between + * several commands, or one state object per command -- depending on the needs + * of the application. + *

+ *

+ * Clients may instantiate or extend this class. + *

+ * + * @since 3.2 + */ +public class State : EventManager { + + /** + * The identifier of the state; may be null if it has not + * been initialized. + */ + private String id; + + /** + * The value held by this state; may be anything at all. + */ + private Object value; + + /** + * Adds a listener to changes for this state. + * + * @param listener + * The listener to add; must not be null. + */ + public void addListener(IStateListener listener) { + addListenerObject(cast(Object)listener); + } + + /** + * Disposes of this state. This allows the state to unregister itself with + * any managers or as a listener. + */ + public void dispose() { + // The default implementation does nothing. + } + + /** + * Notifies listeners to this state that it has changed in some way. + * + * @param oldValue + * The old value; may be anything. + */ + protected final void fireStateChanged(Object oldValue) { + final Object[] listeners = getListeners(); + for (int i = 0; i < listeners.length; i++) { + IStateListener listener = cast(IStateListener) listeners[i]; + listener.handleStateChange(this, oldValue); + } + } + + /** + * Returns the identifier for this state. + * + * @return The id; may be null. + */ + public final String getId() { + return id; + } + + /** + * The current value associated with this state. This can be any type of + * object, but implementations will usually restrict this value to a + * particular type. + * + * @return The current value; may be anything. + */ + + public Object getValue() { + return value; + } + + /** + * Removes a listener to changes from this state. + * + * @param listener + * The listener to remove; must not be null. + */ + + public void removeListener(IStateListener listener) { + removeListenerObject(cast(Object)listener); + } + + /** + * Sets the identifier for this object. This method should only be called + * by the command framework. Clients should not call this method. + * + * @param id + * The id; must not be null. + */ + public void setId(String id) { + this.id = id; + } + + /** + * Sets the value for this state object. + * + * @param value + * The value to set; may be anything. + */ + public void setValue(Object value) { + if (!Util.equals(this.value, value)) { + final Object oldValue = this.value; + this.value = value; + fireStateChanged(oldValue); + } + } +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/common/AbstractBitSetEvent.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/common/AbstractBitSetEvent.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,32 @@ +/******************************************************************************* + * Copyright (c) 2005 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 + * Port to the D programming language: + * Frank Benoit + ******************************************************************************/ +module dwtx.core.commands.common.AbstractBitSetEvent; + +/** + *

+ * An event that carries with it two or more boolean values. This provides a + * single integer value which can then be used as a bit set. + *

+ * + * @since 3.1 + */ +public abstract class AbstractBitSetEvent { + + /** + * A collection of bits representing whether certain values have changed. A + * bit is set (i.e., 1) if the corresponding property has + * changed. It can be assumed that this value will be correctly initialized + * by the superconstructor. + */ + protected int changedValues = 0; +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/common/AbstractHandleObjectEvent.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/common/AbstractHandleObjectEvent.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,59 @@ +/******************************************************************************* + * Copyright (c) 2005 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 + * Port to the D programming language: + * Frank Benoit + ******************************************************************************/ + +module dwtx.core.commands.common.AbstractHandleObjectEvent; + +import dwtx.core.commands.common.AbstractBitSetEvent; + +/** + *

+ * An event fired from a NamedHandleObject. This provides + * notification of changes to the defined state, the name and the description. + *

+ * + * @since 3.2 + */ +public abstract class AbstractHandleObjectEvent : AbstractBitSetEvent { + + /** + * The bit used to represent whether the category has changed its defined + * state. + */ + protected static const int CHANGED_DEFINED = 1; + + /** + * The last used bit so that subclasses can add more properties. + */ + protected static const int LAST_BIT_USED_ABSTRACT_HANDLE = CHANGED_DEFINED; + + /** + * Constructs a new instance of AbstractHandleObjectEvent. + * + * @param definedChanged + * true, iff the defined property changed. + */ + protected this( bool definedChanged) { + if (definedChanged) { + changedValues |= CHANGED_DEFINED; + } + } + + /** + * Returns whether or not the defined property changed. + * + * @return true, iff the defined property changed. + */ + public final bool isDefinedChanged() { + return ((changedValues & CHANGED_DEFINED) !is 0); + } +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/common/AbstractNamedHandleEvent.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/common/AbstractNamedHandleEvent.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,83 @@ +/******************************************************************************* + * Copyright (c) 2005 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 + ******************************************************************************/ + +module dwtx.core.commands.common.AbstractNamedHandleEvent; + +import dwtx.core.commands.common.AbstractHandleObjectEvent; + +/** + *

+ * An event fired from a NamedHandleObject. This provides + * notification of changes to the defined state, the name and the description. + *

+ * + * @since 3.1 + */ +public abstract class AbstractNamedHandleEvent : + AbstractHandleObjectEvent { + + /** + * The bit used to represent whether the category has changed its + * description. + */ + protected static const int CHANGED_DESCRIPTION = 1 << LAST_BIT_USED_ABSTRACT_HANDLE; + + /** + * The bit used to represent whether the category has changed its name. + */ + protected static const int CHANGED_NAME = 1 << LAST_BIT_USED_ABSTRACT_HANDLE; + + /** + * The last used bit so that subclasses can add more properties. + */ + protected static const int LAST_USED_BIT = CHANGED_NAME; + + /** + * Constructs a new instance of AbstractHandleObjectEvent. + * + * @param definedChanged + * true, iff the defined property changed. + * @param descriptionChanged + * true, iff the description property changed. + * @param nameChanged + * true, iff the name property changed. + */ + protected this(bool definedChanged, + bool descriptionChanged, bool nameChanged) { + super(definedChanged); + + if (descriptionChanged) { + changedValues |= CHANGED_DESCRIPTION; + } + if (nameChanged) { + changedValues |= CHANGED_NAME; + } + } + + /** + * Returns whether or not the description property changed. + * + * @return true, iff the description property changed. + */ + public final bool isDescriptionChanged() { + return ((changedValues & CHANGED_DESCRIPTION) !is 0); + } + + /** + * Returns whether or not the name property changed. + * + * @return true, iff the name property changed. + */ + public final bool isNameChanged() { + return ((changedValues & CHANGED_NAME) !is 0); + } + +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/common/CommandException.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/common/CommandException.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,68 @@ +/******************************************************************************* + * Copyright (c) 2004, 2005 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.commands.common.CommandException; + +import dwt.dwthelper.utils; + +/** + * Signals that an exception occured within the command architecture. + *

+ * This class is not intended to be extended by clients. + *

+ * + * @since 3.1 + */ +public abstract class CommandException : Exception { + + /** + * This member variable is required here to allow us to compile against JCL + * foundation libraries. The value may be null. + */ + private Exception cause; + + /** + * Creates a new instance of this class with the specified detail message. + * + * @param message + * the detail message; may be null. + */ + public this(String message) { + super(message); + } + + /** + * Creates a new instance of this class with the specified detail message + * and cause. + * + * @param message + * the detail message; may be null. + * @param cause + * the cause; may be null. + */ + public this(String message, Exception cause) { + super(message); + // don't pass the cause to super, to allow compilation against JCL Foundation + this.cause = cause; + } + + /** + * Returns the cause of this throwable or null if the + * cause is nonexistent or unknown. + * + * @return the cause or null + */ + public Exception getCause() { + return cause; + } + +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/common/EventManager.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/common/EventManager.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,108 @@ +/******************************************************************************* + * Copyright (c) 2005, 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ + +module dwtx.core.commands.common.EventManager; + +import dwtx.core.runtime.ListenerList; + +/** + *

+ * A manager to which listeners can be attached. This handles the management of + * a list of listeners -- optimizing memory and performance. All the methods on + * this class are guaranteed to be thread-safe. + *

+ *

+ * Clients may extend. + *

+ * + * @since 3.2 + */ +public abstract class EventManager { + + /** + * An empty array that can be returned from a call to + * {@link #getListeners()} when {@link #listenerList} is null. + */ + private static const Object[] EMPTY_ARRAY = null; + + /** + * A collection of objects listening to changes to this manager. This + * collection is null if there are no listeners. + */ + private /+transient+/ ListenerList listenerList = null; + + /** + * Adds a listener to this manager that will be notified when this manager's + * state changes. + * + * @param listener + * The listener to be added; must not be null. + */ + protected synchronized final void addListenerObject(Object listener) { + if (listenerList is null) { + listenerList = new ListenerList(ListenerList.IDENTITY); + } + + listenerList.add(listener); + } + + /** + * Clears all of the listeners from the listener list. + */ + protected synchronized final void clearListeners() { + if (listenerList !is null) { + listenerList.clear(); + } + } + + /** + * Returns the listeners attached to this event manager. + * + * @return The listeners currently attached; may be empty, but never + * null + */ + protected final Object[] getListeners() { + ListenerList list = listenerList; + if (list is null) { + return EMPTY_ARRAY; + } + + return list.getListeners(); + } + + /** + * Whether one or more listeners are attached to the manager. + * + * @return true if listeners are attached to the manager; + * false otherwise. + */ + protected final bool isListenerAttached() { + return listenerList !is null; + } + + /** + * Removes a listener from this manager. + * + * @param listener + * The listener to be removed; must not be null. + */ + protected synchronized final void removeListenerObject(Object listener) { + if (listenerList !is null) { + listenerList.remove(listener); + + if (listenerList.isEmpty()) { + listenerList = null; + } + } + } +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/common/HandleObject.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/common/HandleObject.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,181 @@ +/******************************************************************************* + * 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.commands.common.HandleObject; + +import dwtx.core.internal.commands.util.Util; +import dwtx.core.commands.common.IIdentifiable; +import dwtx.core.commands.common.EventManager; +import dwt.dwthelper.utils; + +/** + *

+ * An object that can exist in one of two states: defined and undefined. This is + * used by APIs that want to give a handle to an object, even though the object + * does not fully exist yet. This way, users can attach listeners to objects + * before they come into existence. It also protects the API from users that do + * not release references when they should. + *

+ *

+ * To enforce good coding practice, all handle objects must implement + * equals and toString. Please use + * string to cache the result for toString once + * calculated. + *

+ *

+ * All handle objects are referred to using a single identifier. This identifier + * is a instance of String. It is important that this identifier + * remain unique within whatever context that handle object is being used. For + * example, there should only ever be one instance of Command + * with a given identifier. + *

+ * + * @since 3.1 + */ +public abstract class HandleObject : EventManager, + IIdentifiable { + + /** + * The constant integer hash code value meaning the hash code has not yet + * been computed. + */ + private static const int HASH_CODE_NOT_COMPUTED = -1; + + /** + * A factor for computing the hash code for all schemes. + */ + private static const int HASH_FACTOR = 89; + + /** + * The seed for the hash code for all schemes. + */ + private static const int HASH_INITIAL; + + static this(){ + HASH_INITIAL = + //typeid(char[]).getHash(& HandleObject.classinfo.name ); + dwt.dwthelper.utils.toHash(HandleObject.classinfo.name); + } + /** + * Whether this object is defined. A defined object is one that has been + * fully initialized. By default, all objects start as undefined. + */ + protected /+transient+/ bool defined = false; + + /** + * The hash code for this object. This value is computed lazily, and marked + * as invalid when one of the values on which it is based changes. + */ + private /+transient+/ int hashCode = HASH_CODE_NOT_COMPUTED; + + /** + * The identifier for this object. This identifier should be unique across + * all objects of the same type and should never change. This value will + * never be null. + */ + protected /+final+/ String id; + + /** + * The string representation of this object. This string is for debugging + * purposes only, and is not meant to be displayed to the user. This value + * is computed lazily, and is cleared if one of its dependent values + * changes. + */ + protected /+transient+/ String string = null; + + /** + * Constructs a new instance of HandleObject. + * + * @param id + * The id of this handle; must not be null. + */ + protected this(String id) { + if (id is null) { + throw new NullPointerException( + "Cannot create a handle with a null id"); //$NON-NLS-1$ + } + + this.id = id; + } + + /** + * Tests whether this object is equal to another object. A handle object is + * only equal to another handle object with the same id and the same class. + * + * @param object + * The object with which to compare; may be null. + * @return true if the objects are equal; false + * otherwise. + */ + public override int opEquals(Object object) { + // Check if they're the same. + if (object is this) { + return true; + } + + // Check if they're the same type. + if (!(cast(HandleObject)object)) { + return false; + } + + // Check each property in turn. + final HandleObject handle= cast(HandleObject) object; + return Util.equals(id, handle.id) + && (this.classinfo is handle.classinfo); + } + + public override final String getId() { + return id; + } + + /** + * Computes the hash code for this object based on the id. + * + * @return The hash code for this object. + */ + public final override hash_t toHash() { + if (hashCode is HASH_CODE_NOT_COMPUTED) { + hashCode = HASH_INITIAL * HASH_FACTOR + Util.toHash(id); + if (hashCode is HASH_CODE_NOT_COMPUTED) { + hashCode++; + } + } + return hashCode; + } + + /** + * Whether this instance is defined. A defined instance is one that has been + * fully initialized. This allows objects to effectively disappear even + * though other objects may still have references to them. + * + * @return true if this object is defined; false + * otherwise. + */ + public final bool isDefined() { + return defined; + } + + /** + * The string representation of this object -- for debugging purposes only. + * This string should not be shown to an end user. + * + * @return The string representation; never null. + */ + public override abstract String toString(); + + /** + * Makes this object becomes undefined. This method should make any defined + * properties null. It should also send notification to any + * listeners that these properties have changed. + */ + public abstract void undefine(); +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/common/HandleObjectManager.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/common/HandleObjectManager.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,97 @@ +/******************************************************************************* + * Copyright (c) 2005, 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ + +module dwtx.core.commands.common.HandleObjectManager; + +import dwtx.core.commands.common.HandleObject; +import dwtx.core.commands.common.EventManager; +import dwt.dwthelper.utils; + +import tango.util.collection.HashSet; +import tango.util.collection.HashMap; +import tango.util.collection.model.Set; +import tango.util.collection.model.Map; + +// import java.util.HashMap; +// import java.util.HashSet; +// import java.util.Iterator; +// import java.util.Map; +// import java.util.Set; + +/** + *

+ * A manager of {@link HandleObject} instances. This has some common behaviour + * which is shared between all such managers. + *

+ *

+ * Clients may extend. + *

+ * + * @since 3.2 + */ +public abstract class HandleObjectManager : EventManager { + + /** + * The set of handle objects that are defined. This value may be empty, but + * it is never null. + */ + protected const Set!(Object) definedHandleObjects; + + /** + * The map of identifiers (String) to handle objects ( + * HandleObject). This collection may be empty, but it is + * never null. + */ + protected const Map!( String, Object ) handleObjectsById; + + public this(){ + definedHandleObjects = new HashSet!(Object)(); + handleObjectsById = new HashMap!( String, Object )(); + } + + /** + * Verifies that the identifier is valid. Exceptions will be thrown if the + * identifier is invalid in some way. + * + * @param id + * The identifier to validate; may be anything. + */ + protected final void checkId(String id) { + if (id is null) { + throw new NullPointerException( + "A handle object may not have a null identifier"); //$NON-NLS-1$ + } + + if (id.length < 1) { + throw new IllegalArgumentException( + "The handle object must not have a zero-length identifier"); //$NON-NLS-1$ + } + } + + /** + * Returns the set of identifiers for those handle objects that are defined. + * + * @return The set of defined handle object identifiers; this value may be + * empty, but it is never null. + */ + protected final HashSet!(String) getDefinedHandleObjectIds() { + HashSet!(String) definedHandleObjectIds = new HashSet!(String)(/+definedHandleObjects + .size()+/); + foreach ( v; definedHandleObjects.elements() ) { + auto handleObject = cast(HandleObject)v; + String id = handleObject.getId(); + definedHandleObjectIds.add(id); + } + return definedHandleObjectIds; + } +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/common/IIdentifiable.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/common/IIdentifiable.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,1 @@ +/******************************************************************************* * Copyright (c) 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 * Port to the D programming language: * Frank Benoit ******************************************************************************/ module dwtx.core.commands.common.IIdentifiable; import dwt.dwthelper.utils; /** *

* An object that is unique identifiable based on the combination of its class * and its identifier. *

* * @see HandleObject * @since 3.2 */ public interface IIdentifiable { /** * Returns the identifier for this object. * * @return The identifier; never null. */ String getId(); } \ No newline at end of file diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/common/NamedHandleObject.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/common/NamedHandleObject.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,84 @@ +/******************************************************************************* + * 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.commands.common.NamedHandleObject; + +import dwtx.core.commands.common.HandleObject; +import dwtx.core.commands.common.NotDefinedException; +import dwt.dwthelper.utils; + +/** + * A handle object that carries with it a name and a description. This type of + * handle object is quite common across the commands code base. For example, + * Command, Context and Scheme. + * + * @since 3.1 + */ +public abstract class NamedHandleObject : HandleObject { + + /** + * The description for this handle. This value may be null if + * the handle is undefined or has no description. + */ + protected String description = null; + + /** + * The name of this handle. This valud should not be null + * unless the handle is undefined. + */ + protected String name = null; + + /** + * Constructs a new instance of NamedHandleObject. + * + * @param id + * The identifier for this handle; must not be null. + */ + protected this(String id) { + super(id); + } + + /** + * Returns the description for this handle. + * + * @return The description; may be null if there is no + * description. + * @throws NotDefinedException + * If the handle is not currently defined. + */ + public String getDescription() { + if (!isDefined()) { + throw new NotDefinedException( + "Cannot get a description from an undefined object. " //$NON-NLS-1$ + ~ id); + } + + return description; + } + + /** + * Returns the name for this handle. + * + * @return The name for this handle; never null. + * @throws NotDefinedException + * If the handle is not currently defined. + */ + public String getName() { + if (!isDefined()) { + throw new NotDefinedException( + "Cannot get the name from an undefined object. " //$NON-NLS-1$ + ~ id); + } + + return name; + } +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/common/NamedHandleObjectComparator.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/common/NamedHandleObjectComparator.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,1 @@ +/******************************************************************************* * Copyright (c) 2005 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 * Port to the D programming language: * Frank Benoit ******************************************************************************/ module dwtx.core.commands.common.NamedHandleObjectComparator; // import java.util.Comparator; import dwtx.core.commands.common.NotDefinedException; import dwtx.core.commands.common.NamedHandleObject; import dwtx.core.internal.commands.util.Util; import dwt.dwthelper.utils; /** * Comparator for instances of NamedHandleObject for display to * an end user. The comparison is based on the name of the instances. * * @since 3.2 */ public class NamedHandleObjectComparator /+: Comparator+/ { /** * Compares to instances of NamedHandleObject based on their names. This is * useful is they are display to an end user. * * @param left * The first obect to compare; may be null. * @param right * The second object to compare; may be null. * @return -1 if left is null * and right is not null; * 0 if they are both null; * 1 if left is not null * and right is null. Otherwise, the * result of left.compareTo(right). */ public final int compare(Object left, Object right) { NamedHandleObject a = cast(NamedHandleObject) left; NamedHandleObject b = cast(NamedHandleObject) right; String aName = null; try { aName = a.getName(); } catch (NotDefinedException e) { // Leave aName as null. } String bName = null; try { bName = b.getName(); } catch (NotDefinedException e) { // Leave bName as null. } return Util.compare(aName, bName); } } \ No newline at end of file diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/common/NotDefinedException.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/common/NotDefinedException.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,45 @@ +/******************************************************************************* + * Copyright (c) 2004, 2005 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.commands.common.NotDefinedException; + +import dwtx.core.commands.common.CommandException; +import dwt.dwthelper.utils; + +/** + * Signals that an attempt was made to access the properties of an undefined + * object. + *

+ * This class is not intended to be extended by clients. + *

+ * + * @since 3.1 + */ +public final class NotDefinedException : CommandException { + + /** + * Generated serial version UID for this class. + * + * @since 3.1 + */ + private static final long serialVersionUID = 3257572788998124596L; + + /** + * Creates a new instance of this class with the specified detail message. + * + * @param s + * the detail message; may be null. + */ + public this(String s) { + super(s); + } +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/contexts/Context.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/contexts/Context.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,289 @@ +/******************************************************************************* + * 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ + +module dwtx.core.commands.contexts.Context; +import tango.util.collection.HashSet; +// import tango.util.Iterator; +import tango.util.collection.model.Set; + +import dwtx.core.commands.contexts.ContextEvent; +import dwtx.core.commands.contexts.IContextListener; +import dwtx.core.commands.common.NamedHandleObject; +import dwtx.core.commands.common.NotDefinedException; +import dwtx.core.internal.commands.util.Util; + +import dwt.dwthelper.utils; + +static import tango.text.Text; +alias tango.text.Text.Text!(char) StringBuffer; + +/** + *

+ * A context is an answer to the question "when". Other services can listen for + * the activation and deactivation of contexts, and change their own state in + * response to these changes. For example, Eclipse's key binding service listens + * to context activation and deactivation to determine which key bindings should + * be active. + *

+ *

+ * An instance of this interface can be obtained from an instance of + * ContextManager for any identifier, whether or not an context + * with that identifier is defined in the extension registry. + *

+ *

+ * The handle-based nature of this API allows it to work well with runtime + * plugin activation and deactivation. If a context is defined, that means that + * its corresponding plug-in is active. If the plug-in is then deactivated, the + * context will still exist but it will be undefined. An attempts to use an + * undefined context will result in a NotDefinedException being + * thrown. + *

+ *

+ * This class is not intended to be extended by clients. + *

+ * + * @since 3.1 + * @see ContextManager + */ +public final class Context : NamedHandleObject, Comparable { + + /** + * The collection of all objects listening to changes on this context. This + * value is null if there are no listeners. + */ + private Set!(IContextListener) listeners = null; + + /** + * The parent identifier for this context. The meaning of a parent is + * dependent on the system using contexts. This value can be + * null if the context has no parent. + */ + private String parentId = null; + + /** + * Constructs a new instance of Context. + * + * @param id + * The id for this context; must not be null. + */ + this(String id) { + super(id); + } + + /** + * Registers an instance of IContextListener to listen for + * changes to properties of this instance. + * + * @param listener + * the instance to register. Must not be null. If + * an attempt is made to register an instance which is already + * registered with this instance, no operation is performed. + */ + public final void addContextListener(IContextListener listener) { + if (listener is null) { + throw new NullPointerException(); + } + + if (listeners is null) { + listeners = new HashSet!(IContextListener); + } + + listeners.add(listener); + } + + /* (non-Javadoc) + * @see java.lang.Comparable#compareTo(java.lang.Object) + */ + public final int compareTo(Object object) { + Context scheme = cast(Context) object; + int compareTo = Util.compare(this.id, scheme.id); + if (compareTo is 0) { + compareTo = Util.compare(this.name, scheme.name); + if (compareTo is 0) { + compareTo = Util.compare(this.parentId, scheme.parentId); + if (compareTo is 0) { + compareTo = Util.compare(this.description, + scheme.description); + if (compareTo is 0) { + compareTo = Util.compare(this.defined, scheme.defined); + } + } + } + } + + return compareTo; + } + + /** + *

+ * Defines this context by giving it a name, and possibly a description and + * a parent identifier as well. The defined property automatically becomes + * true. + *

+ *

+ * Notification is sent to all listeners that something has changed. + *

+ * + * @param name + * The name of this context; must not be null. + * @param description + * The description for this context; may be null. + * @param parentId + * The parent identifier for this context; may be + * null. + */ + public final void define(String name, String description, + String parentId) { + if (name is null) { + throw new NullPointerException( + "The name of a scheme cannot be null"); //$NON-NLS-1$ + } + + bool definedChanged = !this.defined; + this.defined = true; + + bool nameChanged = !Util.equals(this.name, name); + this.name = name; + + bool descriptionChanged = !Util.equals(this.description, + description); + this.description = description; + + bool parentIdChanged = !Util.equals(this.parentId, parentId); + this.parentId = parentId; + + fireContextChanged(new ContextEvent(this, definedChanged, nameChanged, + descriptionChanged, parentIdChanged)); + } + + /** + * Notifies all listeners that this context has changed. This sends the + * given event to all of the listeners, if any. + * + * @param event + * The event to send to the listeners; must not be + * null. + */ + private final void fireContextChanged(ContextEvent event) { + if (event is null) { + throw new NullPointerException( + "Cannot send a null event to listeners."); //$NON-NLS-1$ + } + + if (listeners is null) { + return; + } + + foreach( listener; listeners){ + listener.contextChanged(event); + } + } + + /** + * Returns the identifier of the parent of this instance. + *

+ * Notification is sent to all registered listeners if this property + * changes. + *

+ * + * @return the identifier of the parent of this instance. May be + * null. + * @throws NotDefinedException + * if this instance is not defined. + */ + public final String getParentId() { + if (!defined) { + throw new NotDefinedException( + "Cannot get the parent identifier from an undefined context. " //$NON-NLS-1$ + ~ id); + } + + return parentId; + } + + /** + * Unregisters an instance of IContextListener listening for + * changes to properties of this instance. + * + * @param contextListener + * the instance to unregister. Must not be null. + * If an attempt is made to unregister an instance which is not + * already registered with this instance, no operation is + * performed. + */ + public final void removeContextListener( + IContextListener contextListener) { + if (contextListener is null) { + throw new NullPointerException("Cannot remove a null listener."); //$NON-NLS-1$ + } + + if (listeners is null) { + return; + } + + listeners.remove(contextListener); + + if (listeners.drained()) { + listeners = null; + } + } + + /** + * The string representation of this context -- for debugging purposes only. + * This string should not be shown to an end user. + * + * @return The string representation; never null. + */ + public override final String toString() { + if (string is null) { + final StringBuffer stringBuffer = new StringBuffer(); + stringBuffer.append("Context("); //$NON-NLS-1$ + stringBuffer.append(id); + stringBuffer.append(','); + stringBuffer.append(name); + stringBuffer.append(','); + stringBuffer.append(description); + stringBuffer.append(','); + stringBuffer.append(parentId); + stringBuffer.append(','); + stringBuffer.append(defined ? "true" : "false"); + stringBuffer.append(')'); + string = stringBuffer.toString(); + } + return string; + } + + /** + * Makes this context become undefined. This has the side effect of changing + * the name, description and parent identifier to null. + * Notification is sent to all listeners. + */ + public final void undefine() { + string = null; + + bool definedChanged = defined; + defined = false; + + bool nameChanged = name !is null; + name = null; + + bool descriptionChanged = description !is null; + description = null; + + bool parentIdChanged = parentId !is null; + parentId = null; + + fireContextChanged(new ContextEvent(this, definedChanged, nameChanged, + descriptionChanged, parentIdChanged)); + } +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/contexts/ContextEvent.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/contexts/ContextEvent.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,90 @@ +/******************************************************************************* + * 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ + +module dwtx.core.commands.contexts.ContextEvent; + +import dwtx.core.commands.contexts.Context; +import dwtx.core.commands.common.AbstractNamedHandleEvent; +import dwt.dwthelper.utils; + +/** + * An instance of this class describes changes to an instance of + * IContext. + *

+ * This class is not intended to be extended by clients. + *

+ * + * @since 3.1 + * @see IContextListener#contextChanged(ContextEvent) + */ +public final class ContextEvent : AbstractNamedHandleEvent { + + /** + * The bit used to represent whether the context has changed its parent. + */ + private static const int CHANGED_PARENT_ID = LAST_USED_BIT << 1; + + /** + * The context that has changed. This value is never null. + */ + private Context context; + + /** + * Creates a new instance of this class. + * + * @param context + * the instance of the interface that changed; must not be + * null. + * @param definedChanged + * true, iff the defined property changed. + * @param nameChanged + * true, iff the name property changed. + * @param descriptionChanged + * true, iff the description property changed. + * @param parentIdChanged + * true, iff the parentId property changed. + */ + public this(Context context, bool definedChanged, + bool nameChanged, bool descriptionChanged, + bool parentIdChanged) { + super(definedChanged, descriptionChanged, nameChanged); + + if (context is null) { + throw new NullPointerException(); + } + this.context = context; + + if (parentIdChanged) { + changedValues |= CHANGED_PARENT_ID; + } + } + + /** + * Returns the instance of the interface that changed. + * + * @return the instance of the interface that changed. Guaranteed not to be + * null. + */ + public final Context getContext() { + return context; + } + + /** + * Returns whether or not the parentId property changed. + * + * @return true, iff the parentId property changed. + */ + public final bool isParentIdChanged() { + return ((changedValues & CHANGED_PARENT_ID) !is 0); + } +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/contexts/ContextManager.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/contexts/ContextManager.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,339 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ + +module dwtx.core.commands.contexts.ContextManager; + +import tango.util.collection.HashSet; +import tango.util.collection.model.Set; +import tango.util.collection.model.SetView; + +import dwtx.core.commands.contexts.IContextListener; +import dwtx.core.commands.contexts.IContextManagerListener; +import dwtx.core.commands.contexts.ContextEvent; +import dwtx.core.commands.contexts.ContextManagerEvent; +import dwtx.core.commands.contexts.Context; +import dwtx.core.commands.common.HandleObjectManager; +import dwtx.core.commands.util.Tracing; +import dwtx.core.internal.commands.util.Util; + +import dwt.dwthelper.utils; + +/** + *

+ * A context manager tracks the sets of defined and enabled contexts within the + * application. The manager sends notification events to listeners when these + * sets change. It is also possible to retrieve any given context with its + * identifier. + *

+ *

+ * This class is not intended to be extended by clients. + *

+ * + * @since 3.1 + */ +public final class ContextManager : HandleObjectManager, + IContextListener { + + private static const String DEFER_EVENTS = "dwtx.ui.internal.contexts.deferEvents"; //$NON-NLS-1$ + private static const String SEND_EVENTS = "dwtx.ui.internal.contexts.sendEvents"; //$NON-NLS-1$ + + /** + * This flag can be set to true if the context manager should + * print information to System.out when certain boundary + * conditions occur. + */ + public static bool DEBUG = false; + + /** + * The set of active context identifiers. This value may be empty, but it is + * never null. + */ + private Set!(String) activeContextIds; + private static Set!(String) EMPTY_SET; + + // allow the ContextManager to send one event for a larger delta + private bool caching = false; + + private int cachingRef = 0; + + private bool activeContextsChange = false; + + private Set!(String) oldIds = null; + + public this(){ + activeContextIds = new HashSet!(String); + if( EMPTY_SET is null ){ + EMPTY_SET = new HashSet!(String); + } + } + + /** + * Activates a context in this context manager. + * + * @param contextId + * The identifier of the context to activate; must not be + * null. + */ + public final void addActiveContext(String contextId) { + if (DEFER_EVENTS.equals(contextId)) { + cachingRef++; + if (cachingRef is 1 ) { + setEventCaching(true); + } + return; + } else if (SEND_EVENTS.equals(contextId)) { + cachingRef--; + if (cachingRef is 0) { + setEventCaching(false); + } + return; + } + + if (activeContextIds.contains(contextId)) { + return; + } + activeContextsChange = true; + + if (caching) { + activeContextIds.add(contextId); + } else { + Set!(String) previouslyActiveContextIds = activeContextIds.duplicate(); + activeContextIds.add(contextId); + + fireContextManagerChanged(new ContextManagerEvent(this, null, + false, true, previouslyActiveContextIds)); + } + + if (DEBUG) { + Tracing.printTrace("CONTEXTS", SetToString(activeContextIds)); //$NON-NLS-1$ + } + + } + + /** + * Adds a listener to this context manager. The listener will be notified + * when the set of defined contexts changes. This can be used to track the + * global appearance and disappearance of contexts. + * + * @param listener + * The listener to attach; must not be null. + */ + public final void addContextManagerListener( + IContextManagerListener listener) { + addListenerObject(cast(Object)listener); + } + + public final void contextChanged(ContextEvent contextEvent) { + if (contextEvent.isDefinedChanged()) { + Context context = contextEvent.getContext(); + String contextId = context.getId(); + bool contextIdAdded = context.isDefined(); + if (contextIdAdded) { + definedHandleObjects.add(context); + } else { + definedHandleObjects.remove(context); + } + if (isListenerAttached()) { + fireContextManagerChanged(new ContextManagerEvent(this, + contextId, contextIdAdded, false, null)); + } + } + } + + /** + * Notifies all of the listeners to this manager that the set of defined + * context identifiers has changed. + * + * @param event + * The event to send to all of the listeners; must not be + * null. + */ + private final void fireContextManagerChanged(ContextManagerEvent event) { + if (event is null) { + throw new NullPointerException(); + } + + Object[] listeners = getListeners(); + for (int i = 0; i < listeners.length; i++) { + IContextManagerListener listener = cast(IContextManagerListener) listeners[i]; + listener.contextManagerChanged(event); + } + } + + /** + * Returns the set of active context identifiers. + * + * @return The set of active context identifiers; this value may be + * null if no active contexts have been set yet. If + * the set is not null, then it contains only + * instances of String. + */ + public final SetView!(String) getActiveContextIds() { + return /+Collections.unmodifiableSet(+/activeContextIds/+)+/; + } + + /** + * Gets the context with the given identifier. If no such context currently + * exists, then the context will be created (but be undefined). + * + * @param contextId + * The identifier to find; must not be null. + * @return The context with the given identifier; this value will never be + * null, but it might be undefined. + * @see Context + */ + public final Context getContext(String contextId) { + checkId(contextId); + + Context context = cast(Context) handleObjectsById.get(contextId); + if (context is null) { + context = new Context(contextId); + handleObjectsById.add(contextId, context); + context.addContextListener(this); + } + + return context; + } + + /** + * Returns the set of identifiers for those contexts that are defined. + * + * @return The set of defined context identifiers; this value may be empty, + * but it is never null. + */ + public final Set!(String) getDefinedContextIds() { + return getDefinedHandleObjectIds(); + } + + /** + * Returns the those contexts that are defined. + * + * @return The defined contexts; this value may be empty, but it is never + * null. + * @since 3.2 + */ + public final Context[] getDefinedContexts() { + return cast(Context[]) definedHandleObjects + .toArray(/+new Context[definedHandleObjects.size()]+/); + } + + /** + * Deactivates a context in this context manager. + * + * @param contextId + * The identifier of the context to deactivate; must not be + * null. + */ + public final void removeActiveContext(String contextId) { + if (!activeContextIds.contains(contextId)) { + return; + } + + activeContextsChange = true; + if (caching) { + activeContextIds.remove(contextId); + } else { + auto previouslyActiveContextIds = activeContextIds.dup; + activeContextIds.remove(contextId); + + fireContextManagerChanged(new ContextManagerEvent(this, null, + false, true, previouslyActiveContextIds)); + } + + if (DEBUG) { + Tracing.printTrace("CONTEXTS", SetToString(activeContextIds)); //$NON-NLS-1$ + } + } + private String SetToString( Set!(String) set ){ + String s = "["; + foreach( id; set ){ + if( s.length > 1 ) s ~= ", "; + s ~= id; + } + s ~= "]"; + return s; + } + /** + * Removes a listener from this context manager. + * + * @param listener + * The listener to be removed; must not be null. + */ + public final void removeContextManagerListener( + IContextManagerListener listener) { + removeListenerObject(cast(Object)listener); + } + + /** + * Changes the set of active contexts for this context manager. The whole + * set is required so that internal consistency can be maintained and so + * that excessive recomputations do nothing occur. + * + * @param activeContextIds + * The new set of active context identifiers; may be + * null. + */ + public final void setActiveContextIds(Set!(String) activeContextIds) { + if (Util.equals(this.activeContextIds.toArray, activeContextIds.toArray)) { + return; + } + + activeContextsChange = true; + + Set!(String) previouslyActiveContextIds = this.activeContextIds; + if (activeContextIds !is null) { + this.activeContextIds = activeContextIds.duplicate; + } else { + this.activeContextIds = null; + } + + if (DEBUG) { + Tracing.printTrace("CONTEXTS", (activeContextIds is null) ? "none" //$NON-NLS-1$ //$NON-NLS-2$ + : SetToString(activeContextIds)); + } + + if (!caching) { + fireContextManagerChanged(new ContextManagerEvent(this, null, + false, true, previouslyActiveContextIds)); + } + } + + /** + * Set the manager to cache context id changes. + * + * @param cache + * true to turn caching on, false + * to turn caching off and send an event if necessary. + * @since 3.3 + */ + private void setEventCaching(bool cache) { + if (caching is cache) { + return; + } + caching = cache; + bool fireChange = activeContextsChange; + Set!(String) holdOldIds = (oldIds is null? EMPTY_SET : oldIds ); + + if (caching) { + oldIds = activeContextIds.duplicate; + } else { + oldIds = null; + } + activeContextsChange = false; + + if (!caching && fireChange) { + fireContextManagerChanged(new ContextManagerEvent(this, null, + false, true, holdOldIds)); + } + } +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/contexts/ContextManagerEvent.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/contexts/ContextManagerEvent.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,168 @@ +/******************************************************************************* + * Copyright (c) 2003, 2005 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.commands.contexts.ContextManagerEvent; +import tango.util.collection.model.Set; + +import dwtx.core.commands.contexts.ContextManager; +import dwtx.core.commands.common.AbstractBitSetEvent; + +import dwt.dwthelper.utils; + +/** + *

+ * An event indicating that the set of defined context identifiers has changed. + *

+ * + * @since 3.1 + * @see IContextManagerListener#contextManagerChanged(ContextManagerEvent) + */ +public final class ContextManagerEvent : AbstractBitSetEvent { + + /** + * The bit used to represent whether the set of defined contexts has + * changed. + */ + private static const int CHANGED_CONTEXT_DEFINED = 1 << 1; + + /** + * The bit used to represent whether the set of active contexts has changed. + */ + private static const int CHANGED_CONTEXTS_ACTIVE = 1; + + /** + * The context identifier that was added or removed from the list of defined + * context identifiers. + */ + private const String contextId; + + /** + * The context manager that has changed. + */ + private const ContextManager contextManager; + + /** + * The set of context identifiers (strings) that were active before the + * change occurred. If the active contexts did not changed, then this value + * is null. + */ + private const Set!(String) previouslyActiveContextIds; + + /** + * Creates a new instance of this class. + * + * @param contextManager + * the instance of the interface that changed; must not be + * null. + * @param contextId + * The context identifier that was added or removed; may be + * null if the active contexts are changing. + * @param contextIdAdded + * Whether the context identifier became defined (otherwise, it + * became undefined). + * @param activeContextsChanged + * Whether the list of active contexts has changed. + * @param previouslyActiveContextIds + * the set of identifiers of previously active contexts. This set + * may be empty. If this set is not empty, it must only contain + * instances of String. This set must be + * null if activeContextChanged is + * false and must not be null if + * activeContextChanged is true. + */ + public this(ContextManager contextManager, + String contextId, bool contextIdAdded, + bool activeContextsChanged, + Set!(String) previouslyActiveContextIds) { + if (contextManager is null) { + throw new NullPointerException(); + } + + this.contextManager = contextManager; + this.contextId = contextId; + this.previouslyActiveContextIds = previouslyActiveContextIds; + + if (contextIdAdded) { + changedValues |= CHANGED_CONTEXT_DEFINED; + } + if (activeContextsChanged) { + changedValues |= CHANGED_CONTEXTS_ACTIVE; + } + } + + /** + * Returns the context identifier that was added or removed. + * + * @return The context identifier that was added or removed. This value may + * be null if no context identifier was added or + * removed. + */ + public final String getContextId() { + return contextId; + } + + /** + * Returns the instance of the interface that changed. + * + * @return the instance of the interface that changed. Guaranteed not to be + * null. + */ + public final ContextManager getContextManager() { + return contextManager; + } + + /** + * Returns the set of identifiers to previously active contexts. + * + * @return the set of identifiers to previously active contexts. This set + * may be empty. If this set is not empty, it is guaranteed to only + * contain instances of String. This set is + * guaranteed to be null if + * haveActiveContextChanged() is false and is + * guaranteed to not be null if + * haveActiveContextsChanged() is true. + */ + public final Set!(String) getPreviouslyActiveContextIds() { + return previouslyActiveContextIds; + } + + /** + * Returns whether the active context identifiers have changed. + * + * @return true if the collection of active contexts changed; + * false otherwise. + */ + public final bool isActiveContextsChanged() { + return ((changedValues & CHANGED_CONTEXTS_ACTIVE) !is 0); + } + + /** + * Returns whether the list of defined context identifiers has changed. + * + * @return true if the list of context identifiers has + * changed; false otherwise. + */ + public final bool isContextChanged() { + return (contextId !is null); + } + + /** + * Returns whether the context identifier became defined. Otherwise, the + * context identifier became undefined. + * + * @return true if the context identifier became defined; + * false if the context identifier became undefined. + */ + public final bool isContextDefined() { + return (((changedValues & CHANGED_CONTEXT_DEFINED) !is 0) && (contextId !is null)); + } +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/contexts/IContextListener.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/contexts/IContextListener.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ + +module dwtx.core.commands.contexts.IContextListener; + +import dwtx.core.commands.contexts.ContextEvent; + +/** + * An instance of this interface can be used by clients to receive notification + * of changes to one or more instances of IContext. + *

+ * This interface may be implemented by clients. + *

+ * + * @since 3.1 + * @see Context#addContextListener(IContextListener) + * @see Context#removeContextListener(IContextListener) + */ +public interface IContextListener { + + /** + * Notifies that one or more properties of an instance of + * IContext have changed. Specific details are described in + * the ContextEvent. + * + * @param contextEvent + * the context event. Guaranteed not to be null. + */ + void contextChanged(ContextEvent contextEvent); +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/contexts/IContextManagerListener.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/contexts/IContextManagerListener.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,41 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ + +module dwtx.core.commands.contexts.IContextManagerListener; + +import dwtx.core.commands.contexts.ContextManagerEvent; + +/** + * An instance of this interface can be used by clients to receive notification + * of changes to one or more instances of IContextManager. + *

+ * This interface may be implemented by clients. + *

+ * + * @since 3.1 + * @see ContextManager#addContextManagerListener(IContextManagerListener) + * @see ContextManager#removeContextManagerListener(IContextManagerListener) + */ +public interface IContextManagerListener { + + /** + * Notifies that one or more properties of an instance of + * IContextManager have changed. Specific details are + * described in the ContextManagerEvent. + * + * @param contextManagerEvent + * the context manager event. Guaranteed not to be + * null. + */ + void contextManagerChanged(ContextManagerEvent contextManagerEvent); +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/operations/AbstractOperation.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/operations/AbstractOperation.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,219 @@ +/******************************************************************************* + * Copyright (c) 2005, 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.commands.operations.AbstractOperation; + +import tango.util.collection.ArraySeq; +import tango.util.collection.model.Seq; + +import dwtx.core.commands.ExecutionException; +import dwtx.core.runtime.Assert; +import dwtx.core.runtime.IAdaptable; +import dwtx.core.runtime.IProgressMonitor; +import dwtx.core.runtime.IStatus; + +import dwtx.core.commands.operations.IUndoableOperation; +import dwtx.core.commands.operations.IUndoContext; + +import dwt.dwthelper.utils; +static import tango.text.Text; +alias tango.text.Text.Text!(char) StringBuffer; + +/** + *

+ * Abstract implementation for an undoable operation. At a minimum, subclasses + * should implement behavior for + * {@link IUndoableOperation#execute(dwtx.core.runtime.IProgressMonitor, dwtx.core.runtime.IAdaptable)}, + * {@link IUndoableOperation#redo(dwtx.core.runtime.IProgressMonitor, dwtx.core.runtime.IAdaptable)}, + * and + * {@link IUndoableOperation#undo(dwtx.core.runtime.IProgressMonitor, dwtx.core.runtime.IAdaptable)}. + *

+ * + * @see dwtx.core.commands.operations.IUndoableOperation + * + * @since 3.1 + */ +public abstract class AbstractOperation : IUndoableOperation { + Seq!(IUndoContext) contexts; + + private String label = ""; //$NON-NLS-1$ + + /** + * Construct an operation that has the specified label. + * + * @param label + * the label to be used for the operation. + */ + public this(String label) { + this.label = label; + contexts = new ArraySeq!(IUndoContext); + } + + /* + * (non-Javadoc) + * + * @see dwtx.core.commands.operations.IUndoableOperation#addContext(dwtx.core.commands.operations.IUndoContext) + * + *

Subclasses may override this method.

+ */ + public void addContext(IUndoContext context) { + if (!contexts.contains(context)) { + contexts.append(context); + } + } + + /* + * (non-Javadoc) + * + * @see dwtx.core.commands.operations.IUndoableOperation#canExecute() + *

Default implementation. Subclasses may override this method. + *

+ * + */ + public bool canExecute() { + return true; + } + + /* + * (non-Javadoc) + * + * @see dwtx.core.commands.operations.IUndoableOperation#canRedo() + *

Default implementation. Subclasses may override this method. + *

+ */ + public bool canRedo() { + return true; + } + + /* + * (non-Javadoc) + * + * @see dwtx.core.commands.operations.IUndoableOperation#canUndo() + *

Default implementation. Subclasses may override this method. + *

+ */ + public bool canUndo() { + return true; + } + + /* + * (non-Javadoc) + * + * @see dwtx.core.commands.operations.IUndoableOperation#dispose() + *

Default implementation. Subclasses may override this method. + *

+ */ + public void dispose() { + // nothing to dispose. + } + + /* + * (non-Javadoc) + * + * @see dwtx.core.commands.operations.IUndoableOperation#execute(dwtx.core.runtime.IProgressMonitor, + * dwtx.core.runtime.IAdaptable) + */ + public abstract IStatus execute(IProgressMonitor monitor, IAdaptable info); + + public final IUndoContext[] getContexts() { + return contexts.toArray(); + } + + /* + * (non-Javadoc) + * + * @see dwtx.core.commands.operations.IUndoableOperation#getLabel() + *

Default implementation. Subclasses may override this method. + *

+ */ + public String getLabel() { + return label; + } + + /** + * Set the label of the operation to the specified name. + * + * @param name + * the string to be used for the label. + */ + public void setLabel(String name) { + label = name; + } + + /* + * (non-Javadoc) + * + * @see dwtx.core.commands.operations.IUndoableOperation#hasContext(dwtx.core.commands.operations.IUndoContext) + */ + public final bool hasContext(IUndoContext context) { + Assert.isNotNull(cast(Object)context); + for (int i = 0; i < contexts.size(); i++) { + IUndoContext otherContext = contexts.get(i); + // have to check both ways because one context may be more general + // in + // its matching rules than another. + if (context.matches(otherContext) || otherContext.matches(context)) { + return true; + } + } + return false; + } + + /* + * (non-Javadoc) + * + * @see dwtx.core.commands.operations.IUndoableOperation#redo(dwtx.core.runtime.IProgressMonitor, + * dwtx.core.runtime.IAdaptable) + */ + public abstract IStatus redo(IProgressMonitor monitor, IAdaptable info); + + /* + * (non-Javadoc) + * + * @see dwtx.core.commands.operations.IUndoableOperation#removeContext(dwtx.core.commands.operations.IUndoContext) + *

Default implementation. Subclasses may override this method. + *

+ */ + + public void removeContext(IUndoContext context) { + contexts.remove(context); + } + + /* + * (non-Javadoc) + * + * @see dwtx.core.commands.operations.IUndoableOperation#undo(dwtx.core.runtime.IProgressMonitor, + * dwtx.core.runtime.IAdaptable) + */ + public abstract IStatus undo(IProgressMonitor monitor, IAdaptable info); + + /** + * The string representation of this operation. Used for debugging purposes + * only. This string should not be shown to an end user. + * + * @return The string representation. + */ + public String toString() { + StringBuffer stringBuffer = new StringBuffer(); + stringBuffer.append(getLabel()); + stringBuffer.append("("); //$NON-NLS-1$ + IUndoContext[] contexts = getContexts(); + for (int i = 0; i < contexts.length; i++) { + stringBuffer.append((cast(Object)contexts[i]).toString()); + if (i !is contexts.length - 1) { + stringBuffer.append(','); + } + } + stringBuffer.append(')'); + return stringBuffer.toString(); + } +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/operations/DefaultOperationHistory.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/operations/DefaultOperationHistory.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,1426 @@ +/******************************************************************************* + * Copyright (c) 2005, 2007 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.commands.operations.DefaultOperationHistory; + +import tango.util.collection.ArraySeq; +import tango.util.collection.model.Seq; +import tango.util.collection.HashMap; +import tango.util.collection.model.Map; + +import dwtx.core.commands.ExecutionException; +import dwtx.core.commands.util.Tracing; +import dwtx.core.runtime.Assert; +import dwtx.core.runtime.IAdaptable; +import dwtx.core.runtime.IProgressMonitor; +import dwtx.core.runtime.ISafeRunnable; +import dwtx.core.runtime.IStatus; +import dwtx.core.runtime.ListenerList; +import dwtx.core.runtime.OperationCanceledException; +import dwtx.core.runtime.SafeRunner; +import dwtx.core.runtime.Status; + +import dwtx.core.commands.operations.IOperationHistory; +import dwtx.core.commands.operations.IUndoContext; +import dwtx.core.commands.operations.IUndoableOperation; +import dwtx.core.commands.operations.ICompositeOperation; +import dwtx.core.commands.operations.IOperationApprover; +import dwtx.core.commands.operations.IOperationApprover2; +import dwtx.core.commands.operations.IOperationHistoryListener; +import dwtx.core.commands.operations.OperationHistoryEvent; +import dwtx.core.commands.operations.IAdvancedUndoableOperation; + +import dwt.dwthelper.utils; +import dwt.dwthelper.Integer; + +/** + *

+ * A base implementation of IOperationHistory that implements a linear undo and + * redo model . The most recently added operation is available for undo, and the + * most recently undone operation is available for redo. + *

+ *

+ * If the operation eligible for undo is not in a state where it can be undone, + * then no undo is available. No other operations are considered. Likewise, if + * the operation available for redo cannot be redone, then no redo is available. + *

+ *

+ * Implementations for the direct undo and redo of a specified operation are + * available. If a strict linear undo is to be enforced, than an + * IOperationApprover should be installed that prevents undo and redo of any + * operation that is not the most recently undone or redone operation in all of + * its undo contexts. + *

+ *

+ * The data structures used by the DefaultOperationHistory are synchronized, and + * entry points that modify the undo and redo history concurrently are also + * synchronized. This means that the DefaultOperationHistory is relatively + * "thread-friendly" in its implementation. Outbound notifications or operation + * approval requests will occur on the thread that initiated the request. + * Clients may use DefaultOperationHistory API from any thread; however, + * listeners or operation approvers that receive notifications from the + * DefaultOperationHistory must be prepared to receive these notifications from + * a background thread. Any UI access occurring inside these notifications must + * be properly synchronized using the techniques specified by the client's + * widget library. + *

+ * + *

+ * This implementation is not intended to be subclassed. + *

+ * + * @see dwtx.core.commands.operations.IOperationHistory + * @see dwtx.core.commands.operations.IOperationApprover + * + * @since 3.1 + */ +public final class DefaultOperationHistory : IOperationHistory { + /** + * This flag can be set to true if the history should print + * information to System.out whenever notifications about + * changes to the history occur. This flag should be used for debug purposes + * only. + */ + public static bool DEBUG_OPERATION_HISTORY_NOTIFICATION = false; + + /** + * This flag can be set to true if the history should print + * information to System.out whenever an unexpected condition + * arises. This flag should be used for debug purposes only. + */ + public static bool DEBUG_OPERATION_HISTORY_UNEXPECTED = false; + + /** + * This flag can be set to true if the history should print + * information to System.out whenever an undo context is + * disposed. This flag should be used for debug purposes only. + */ + public static bool DEBUG_OPERATION_HISTORY_DISPOSE = false; + + /** + * This flag can be set to true if the history should print + * information to System.out during the open/close sequence. + * This flag should be used for debug purposes only. + */ + public static bool DEBUG_OPERATION_HISTORY_OPENOPERATION = false; + + /** + * This flag can be set to true if the history should print + * information to System.out whenever an operation is not + * approved. This flag should be used for debug purposes only. + */ + public static bool DEBUG_OPERATION_HISTORY_APPROVAL = false; + + static const int DEFAULT_LIMIT = 20; + + /** + * the list of {@link IOperationApprover}s + */ + ListenerList approvers; + + /** + * a map of undo limits per context + */ + private Map!(IUndoContext,Integer) limits; + + /** + * the list of {@link IOperationHistoryListener}s + */ + ListenerList listeners; + + /** + * the list of operations available for redo, LIFO + */ + private ArraySeq!(IUndoableOperation) redoList; + + /** + * the list of operations available for undo, LIFO + */ + private ArraySeq!(IUndoableOperation) undoList; + + /** + * a lock that is used to synchronize access between the undo and redo + * history + */ + final Object undoRedoHistoryLock; + + /** + * An operation that is "absorbing" all other operations while it is open. + * When this is not null, other operations added or executed are added to + * this composite. + * + */ + private ICompositeOperation openComposite = null; + + /** + * a lock that is used to synchronize access to the open composite. + */ + const Object openCompositeLock; + + /** + * Create an instance of DefaultOperationHistory. + */ + public this() { + openCompositeLock = new Object(); + undoRedoHistoryLock = new Object(); + approvers = new ListenerList(ListenerList.IDENTITY); + limits = new HashMap!(IUndoContext,Integer); + listeners = new ListenerList(ListenerList.IDENTITY); + redoList = new ArraySeq!(IUndoableOperation); + undoList = new ArraySeq!(IUndoableOperation); + } + + /* + * (non-Javadoc) + * + * @see dwtx.core.commands.operations.IOperationHistory#add(dwtx.core.commands.operations.IUndoableOperation) + */ + public void add(IUndoableOperation operation) { + Assert.isNotNull( cast(Object)operation); + + /* + * If we are in the middle of executing an open batching operation, and + * this is not that operation, then we need only add the context of the + * new operation to the batch. The operation itself is disposed since we + * will never undo or redo it. We consider it to be triggered by the + * batching operation and assume that its undo will be triggered by the + * batching operation undo. + */ + synchronized (openCompositeLock) { + if (openComposite !is null && openComposite !is operation) { + openComposite.add(operation); + return; + } + } + + if (checkUndoLimit(operation)) { + synchronized (undoRedoHistoryLock) { + undoList.append(operation); + } + notifyAdd(operation); + + // flush redo stack for related contexts + IUndoContext[] contexts = operation.getContexts(); + for (int i = 0; i < contexts.length; i++) { + flushRedo(contexts[i]); + } + } else { + // Dispose the operation since we will not have a reference to it. + operation.dispose(); + } + } + + /** + *

+ * Add the specified approver to the list of operation approvers consulted + * by the operation history before an undo or redo is allowed to proceed. + * This method has no effect if the instance being added is already in the + * list. + *

+ *

+ * Operation approvers must be prepared to receive these the operation + * approval messages from a background thread. Any UI access occurring + * inside the implementation must be properly synchronized using the + * techniques specified by the client's widget library. + *

+ * + * @param approver + * the IOperationApprover to be added as an approver. + * + */ + + public void addOperationApprover(IOperationApprover approver) { + approvers.add(cast(Object)approver); + } + + /** + *

+ * Add the specified listener to the list of operation history listeners + * that are notified about changes in the history or operations that are + * executed, undone, or redone. This method has no effect if the instance + * being added is already in the list. + *

+ *

+ * Operation history listeners must be prepared to receive notifications + * from a background thread. Any UI access occurring inside the + * implementation must be properly synchronized using the techniques + * specified by the client's widget library. + *

+ * + * @param listener + * the IOperationHistoryListener to be added as a listener. + * + * @see dwtx.core.commands.operations.IOperationHistoryListener + * @see dwtx.core.commands.operations.OperationHistoryEvent + */ + public void addOperationHistoryListener(IOperationHistoryListener listener) { + listeners.add(cast(Object)listener); + } + + /* + * (non-Javadoc) + * + * @see dwtx.core.commands.operations.IOperationHistory#canRedo(dwtx.core.commands.operations.IUndoContext) + */ + public bool canRedo(IUndoContext context) { + // null context is allowed and passed through + IUndoableOperation operation = getRedoOperation(context); + return (operation !is null && operation.canRedo()); + } + + /* + * (non-Javadoc) + * + * @see dwtx.core.commands.operations.IOperationHistory#canUndo(dwtx.core.commands.operations.IUndoContext) + */ + public bool canUndo(IUndoContext context) { + // null context is allowed and passed through + IUndoableOperation operation = getUndoOperation(context); + return (operation !is null && operation.canUndo()); + } + + /** + * Check the redo limit before adding an operation. In theory the redo limit + * should never be reached, because the redo items are transferred from the + * undo history, which has the same limit. The redo history is cleared + * whenever a new operation is added. We check for completeness since + * implementations may change over time. + * + * Return a bool indicating whether the redo should proceed. + */ + private bool checkRedoLimit(IUndoableOperation operation) { + IUndoContext[] contexts = operation.getContexts(); + for (int i = 0; i < contexts.length; i++) { + int limit = getLimit(contexts[i]); + if (limit > 0) { + forceRedoLimit(contexts[i], limit - 1); + } else { + // this context has a 0 limit + operation.removeContext(contexts[i]); + } + } + return operation.getContexts().length > 0; + } + + /** + * Check the undo limit before adding an operation. Return a bool + * indicating whether the undo should proceed. + */ + private bool checkUndoLimit(IUndoableOperation operation) { + IUndoContext[] contexts = operation.getContexts(); + for (int i = 0; i < contexts.length; i++) { + int limit = getLimit(contexts[i]); + if (limit > 0) { + forceUndoLimit(contexts[i], limit - 1); + } else { + // this context has a 0 limit + operation.removeContext(contexts[i]); + } + } + return operation.getContexts().length > 0; + } + + /* + * (non-Javadoc) + * + * @see dwtx.core.commands.operations.IOperationHistory#dispose(dwtx.core.commands.operations.IUndoContext, + * bool, bool, bool) + */ + public void dispose(IUndoContext context, bool flushUndo_, + bool flushRedo_, bool flushContext_) { + // dispose of any limit that was set for the context if it is not to be + // used again. + if (flushContext_) { + if (DEBUG_OPERATION_HISTORY_DISPOSE) { + Tracing.printTrace("OPERATIONHISTORY", "Flushing context " //$NON-NLS-1$//$NON-NLS-2$ + ~ (cast(Object)context).toString ); + } + flushUndo(context); + flushRedo(context); + synchronized(limits) limits.removeKey(context); + return; + } + if (flushUndo_) { + flushUndo(context); + } + if (flushRedo_) { + flushRedo(context); + } + + } + + /** + * Perform the redo. All validity checks have already occurred. + * + * @param monitor + * @param operation + */ + private IStatus doRedo(IProgressMonitor monitor, IAdaptable info, + IUndoableOperation operation) { + + IStatus status = getRedoApproval(operation, info); + if (status.isOK()) { + notifyAboutToRedo(operation); + try { + status = operation.redo(monitor, info); + } catch (OperationCanceledException e) { + status = Status.CANCEL_STATUS; + } catch (ExecutionException e) { + notifyNotOK(operation); + if (DEBUG_OPERATION_HISTORY_UNEXPECTED) { + Tracing.printTrace("OPERATIONHISTORY", //$NON-NLS-1$ + "ExecutionException while redoing " ~ (cast(Object)operation).toString ); //$NON-NLS-1$ + } + throw e; + } catch (Exception e) { + notifyNotOK(operation); + if (DEBUG_OPERATION_HISTORY_UNEXPECTED) { + Tracing.printTrace("OPERATIONHISTORY", //$NON-NLS-1$ + "Exception while redoing " ~ (cast(Object)operation).toString); //$NON-NLS-1$ + } + throw new ExecutionException( + "While redoing the operation, an exception occurred", e); //$NON-NLS-1$ + } + } + + // if successful, the operation is removed from the redo history and + // placed back in the undo history. + if (status.isOK()) { + bool addedToUndo = true; + synchronized (undoRedoHistoryLock) { + synchronized(redoList) redoList.remove(operation); + if (checkUndoLimit(operation)) { + undoList.append(operation); + } else { + addedToUndo = false; + } + } + // dispose the operation since we could not add it to the + // stack and will no longer have a reference to it. + if (!addedToUndo) { + operation.dispose(); + } + + // notify listeners must happen after history is updated + notifyRedone(operation); + } else { + notifyNotOK(operation, status); + } + + return status; + } + + /** + * Perform the undo. All validity checks have already occurred. + * + * @param monitor + * @param operation + */ + private IStatus doUndo(IProgressMonitor monitor, IAdaptable info, + IUndoableOperation operation) { + IStatus status = getUndoApproval(operation, info); + if (status.isOK()) { + notifyAboutToUndo(operation); + try { + status = operation.undo(monitor, info); + } catch (OperationCanceledException e) { + status = Status.CANCEL_STATUS; + } catch (ExecutionException e) { + notifyNotOK(operation); + if (DEBUG_OPERATION_HISTORY_UNEXPECTED) { + Tracing.printTrace("OPERATIONHISTORY", //$NON-NLS-1$ + "ExecutionException while undoing " ~ (cast(Object)operation).toString); //$NON-NLS-1$ + } + throw e; + } catch (Exception e) { + notifyNotOK(operation); + if (DEBUG_OPERATION_HISTORY_UNEXPECTED) { + Tracing.printTrace("OPERATIONHISTORY", //$NON-NLS-1$ + "Exception while undoing " ~ (cast(Object)operation).toString); //$NON-NLS-1$ + } + throw new ExecutionException( + "While undoing the operation, an exception occurred", e); //$NON-NLS-1$ + } + } + // if successful, the operation is removed from the undo history and + // placed in the redo history. + if (status.isOK()) { + bool addedToRedo = true; + synchronized (undoRedoHistoryLock) { + undoList.remove(operation); + if (checkRedoLimit(operation)) { + synchronized(redoList) redoList.append(operation); + } else { + addedToRedo = false; + } + } + // dispose the operation since we could not add it to the + // stack and will no longer have a reference to it. + if (!addedToRedo) { + operation.dispose(); + } + // notification occurs after the undo and redo histories are + // adjusted + notifyUndone(operation); + } else { + notifyNotOK(operation, status); + } + return status; + } + + /* + * (non-Javadoc) + * + * @see dwtx.core.commands.operations.IOperationHistory#execute(dwtx.core.commands.operations.IUndoableOperation, + * dwtx.core.runtime.IProgressMonitor, + * dwtx.core.runtime.IAdaptable) + */ + public IStatus execute(IUndoableOperation operation, + IProgressMonitor monitor, IAdaptable info) { + Assert.isNotNull(cast(Object)operation); + + // error if operation is invalid + if (!operation.canExecute()) { + return IOperationHistory.OPERATION_INVALID_STATUS; + } + + // check with the operation approvers + IStatus status = getExecuteApproval(operation, info); + if (!status.isOK()) { + // not approved. No notifications are sent, just return the status. + return status; + } + + /* + * If we are in the middle of an open composite, then we will add this + * operation to the open operation rather than add the operation to the + * history. We will still execute it. + */ + bool merging = false; + synchronized (openCompositeLock) { + if (openComposite !is null) { + // the composite shouldn't be executed explicitly while it is + // still + // open + if (openComposite is operation) { + return IOperationHistory.OPERATION_INVALID_STATUS; + } + openComposite.add(operation); + merging = true; + } + } + + /* + * Execute the operation + */ + if (!merging) { + notifyAboutToExecute(operation); + } + try { + status = operation.execute(monitor, info); + } catch (OperationCanceledException e) { + status = Status.CANCEL_STATUS; + } catch (ExecutionException e) { + notifyNotOK(operation); + throw e; + } catch (Exception e) { + notifyNotOK(operation); + throw new ExecutionException( + "While executing the operation, an exception occurred", e); //$NON-NLS-1$ + } + + // if successful, the notify listeners are notified and the operation is + // added to the history + if (!merging) { + if (status.isOK()) { + notifyDone(operation); + add(operation); + } else { + notifyNotOK(operation, status); + // dispose the operation since we did not add it to the stack + // and will no longer have a reference to it. + operation.dispose(); + } + } + // all other severities are not interpreted. Simply return the status. + return status; + } + + /* + * Filter the specified list to include only the specified undo context. + */ + private IUndoableOperation[] filter(Seq!(IUndoableOperation) list, IUndoContext context) { + /* + * This method is used whenever there is a need to filter the undo or + * redo history on a particular context. Currently there are no caches + * kept to optimize repeated requests for the same filter. If benchmarks + * show this to be a common pattern that causes performances problems, + * we could implement a filtered cache here that is nullified whenever + * the global history changes. + */ + + auto filtered = new ArraySeq!(IUndoableOperation); + filtered.capacity(list.size()); +// Iterator iterator = list.iterator(); + synchronized (undoRedoHistoryLock) { + foreach( operation; list ){ +// while (iterator.hasNext()) { +// IUndoableOperation operation = (IUndoableOperation) iterator +// .next(); + if (operation.hasContext(context)) { + filtered.append(operation); + } + } + } + return filtered.toArray(); + } + + /* + * Flush the redo stack of all operations that have the given context. + */ + private void flushRedo(IUndoContext context) { + if (DEBUG_OPERATION_HISTORY_DISPOSE) { + Tracing.printTrace("OPERATIONHISTORY", "Flushing redo history for " //$NON-NLS-1$ //$NON-NLS-2$ + ~ (cast(Object)context).toString ); + } + + IUndoableOperation[] filtered = filter(redoList, context); + for (int i = 0; i < filtered.length; i++) { + IUndoableOperation operation = filtered[i]; + if (context is GLOBAL_UNDO_CONTEXT + || operation.getContexts().length is 1) { + // remove the operation if it only has the context or we are + // flushing all + synchronized(redoList) redoList.remove(operation); + internalRemove(operation); + } else { + // remove the reference to the context. + // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=161786 + // It is not enough to simply remove the context. There could + // be one or more contexts that match the one we are trying to + // dispose. + IUndoContext[] contexts = operation.getContexts(); + for (int j = 0; j < contexts.length; j++) { + if (contexts[j].matches(context)) { + operation.removeContext(contexts[j]); + } + } + if (operation.getContexts().length is 0) { + synchronized(redoList) redoList.remove(operation); + internalRemove(operation); + } + } + } + } + + /* + * Flush the undo stack of all operations that have the given context. + */ + private void flushUndo(IUndoContext context) { + if (DEBUG_OPERATION_HISTORY_DISPOSE) { + Tracing.printTrace("OPERATIONHISTORY", "Flushing undo history for " //$NON-NLS-1$ //$NON-NLS-2$ + ~ (cast(Object)context).toString ); + } + + // Get all operations that have the context (or one that matches) + IUndoableOperation[] filtered = filter(undoList, context); + for (int i = 0; i < filtered.length; i++) { + IUndoableOperation operation = filtered[i]; + if (context is GLOBAL_UNDO_CONTEXT + || operation.getContexts().length is 1) { + // remove the operation if it only has the context or we are + // flushing all + synchronized(undoList) undoList.remove(operation); + internalRemove(operation); + } else { + // remove the reference to the context. + // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=161786 + // It is not enough to simply remove the context. There could + // be one or more contexts that match the one we are trying to + // dispose. + IUndoContext[] contexts = operation.getContexts(); + for (int j = 0; j < contexts.length; j++) { + if (contexts[j].matches(context)) { + operation.removeContext(contexts[j]); + } + } + if (operation.getContexts().length is 0) { + synchronized(undoList) undoList.remove(operation); + internalRemove(operation); + } + } + } + /* + * There may be an open composite. If it has this context, then the + * context must be removed. If it has only this context or we are + * flushing all operations, then null it out and notify that we are + * ending it. We don't remove it since it was never added. + */ + ICompositeOperation endedComposite = null; + synchronized (openCompositeLock) { + if (openComposite !is null) { + if (openComposite.hasContext(context)) { + if (context is GLOBAL_UNDO_CONTEXT + || openComposite.getContexts().length is 1) { + endedComposite = openComposite; + openComposite = null; + } else { + openComposite.removeContext(context); + } + } + } + } + // notify outside of the synchronized block. + if (endedComposite !is null) { + notifyNotOK(endedComposite); + } + } + + /* + * Force the redo history for the given context to contain max or less + * items. + */ + private void forceRedoLimit(IUndoContext context, int max) { + IUndoableOperation[] filtered = filter(redoList, context); + int size = filtered.length; + if (size > 0) { + int index = 0; + while (size > max) { + IUndoableOperation removed = filtered[index]; + if (context is GLOBAL_UNDO_CONTEXT + || removed.getContexts().length is 1) { + /* + * remove the operation if we are enforcing a global limit + * or if the operation only has the specified context + */ + synchronized(redoList) redoList.remove(removed); + internalRemove(removed); + } else { + /* + * if the operation has multiple contexts and we've reached + * the limit for only one of them, then just remove the + * context, not the operation. + */ + removed.removeContext(context); + } + size--; + index++; + } + } + } + + /* + * Force the undo history for the given context to contain max or less + * items. + */ + private void forceUndoLimit(IUndoContext context, int max) { + IUndoableOperation[] filtered = filter(undoList, context); + int size = filtered.length; + if (size > 0) { + int index = 0; + while (size > max) { + IUndoableOperation removed = filtered[index]; + if (context is GLOBAL_UNDO_CONTEXT + || removed.getContexts().length is 1) { + /* + * remove the operation if we are enforcing a global limit + * or if the operation only has the specified context + */ + synchronized(undoList) undoList.remove(removed); + internalRemove(removed); + } else { + /* + * if the operation has multiple contexts and we've reached + * the limit for only one of them, then just remove the + * context, not the operation. + */ + removed.removeContext(context); + } + size--; + index++; + } + } + } + + /* + * (non-Javadoc) + * + * @see dwtx.core.commands.operations.IOperationHistory#getLimit() + */ + public int getLimit(IUndoContext context) { + synchronized(limits) { + if (!limits.containsKey(context)) { + return DEFAULT_LIMIT; + } + return (cast(Integer) (limits.get(context))).intValue(); + } + } + + /* + * Consult the IOperationApprovers to see if the proposed redo should be + * allowed. + */ + private IStatus getRedoApproval(IUndoableOperation operation, + IAdaptable info) { + + Object[] approverArray = approvers.getListeners(); + + for (int i = 0; i < approverArray.length; i++) { + IOperationApprover approver = cast(IOperationApprover) approverArray[i]; + IStatus approval = approver.proceedRedoing(operation, this, info); + if (!approval.isOK()) { + if (DEBUG_OPERATION_HISTORY_APPROVAL) { + Tracing.printTrace("OPERATIONHISTORY", //$NON-NLS-1$ + "Redo not approved by " ~ (cast(Object)approver).toString //$NON-NLS-1$ + ~ "for operation " ~ (cast(Object)operation).toString //$NON-NLS-1$ + ~ " approved by " ~ (cast(Object)approval).toString); //$NON-NLS-1$ + } + return approval; + } + } + return Status.OK_STATUS; + } + + /* + * (non-Javadoc) + * + * @see dwtx.core.commands.operations.IOperationHistory#getRedoHistory(dwtx.core.commands.operations.IUndoContext) + */ + public IUndoableOperation[] getRedoHistory(IUndoContext context) { + Assert.isNotNull(cast(Object)context); + return filter(redoList, context); + } + + /* + * (non-Javadoc) + * + * @see dwtx.core.commands.operations.IOperationHistory#getOperation(dwtx.core.commands.operations.IUndoContext) + */ + public IUndoableOperation getRedoOperation(IUndoContext context) { + Assert.isNotNull(cast(Object)context); + synchronized (undoRedoHistoryLock) { + for (int i = redoList.size() - 1; i >= 0; i--) { + IUndoableOperation operation; + synchronized(redoList) operation = cast(IUndoableOperation) redoList.get(i); + if (operation.hasContext(context)) { + return operation; + } + } + } + return null; + } + + /* + * Consult the IOperationApprovers to see if the proposed undo should be + * allowed. + */ + private IStatus getUndoApproval(IUndoableOperation operation, + IAdaptable info) { + + final Object[] approverArray = approvers.getListeners(); + + for (int i = 0; i < approverArray.length; i++) { + IOperationApprover approver = cast(IOperationApprover) approverArray[i]; + IStatus approval = approver.proceedUndoing(operation, this, info); + if (!approval.isOK()) { + if (DEBUG_OPERATION_HISTORY_APPROVAL) { + Tracing.printTrace("OPERATIONHISTORY", //$NON-NLS-1$ + "Undo not approved by " ~ (cast(Object)approver).toString //$NON-NLS-1$ + ~ "for operation " ~ (cast(Object)operation ).toString//$NON-NLS-1$ + ~ " with status " ~ (cast(Object)approval).toString); //$NON-NLS-1$ + } + return approval; + } + } + return Status.OK_STATUS; + } + + /* + * (non-Javadoc) + * + * @see dwtx.core.commands.operations.IOperationHistory#getUndoHistory(dwtx.core.commands.operations.IUndoContext) + */ + public IUndoableOperation[] getUndoHistory(IUndoContext context) { + Assert.isNotNull(cast(Object)context); + return filter(undoList, context); + } + + /* + * (non-Javadoc) + * + * @see dwtx.core.commands.operations.IOperationHistory#getUndoOperation(dwtx.core.commands.operations.IUndoContext) + */ + public IUndoableOperation getUndoOperation(IUndoContext context) { + Assert.isNotNull(cast(Object)context); + synchronized (undoRedoHistoryLock) { + for (int i = undoList.size() - 1; i >= 0; i--) { + IUndoableOperation operation; + synchronized(undoList) operation = cast(IUndoableOperation) undoList.get(i); + if (operation.hasContext(context)) { + return operation; + } + } + } + return null; + } + + /* + * Consult the IOperationApprovers to see if the proposed execution should + * be allowed. + * + * @since 3.2 + */ + private IStatus getExecuteApproval(IUndoableOperation operation, + IAdaptable info) { + + final Object[] approverArray = approvers.getListeners(); + + for (int i = 0; i < approverArray.length; i++) { + if ( auto apro = cast(IOperationApprover2)approverArray[i] ) { + IOperationApprover2 approver = apro; + IStatus approval = approver.proceedExecuting(operation, this, + info); + if (!approval.isOK()) { + if (DEBUG_OPERATION_HISTORY_APPROVAL) { + Tracing.printTrace("OPERATIONHISTORY", //$NON-NLS-1$ + "Execute not approved by " ~ (cast(Object)approver).toString //$NON-NLS-1$ + ~ "for operation " ~ (cast(Object)operation).toString //$NON-NLS-1$ + ~ " with status " ~ (cast(Object)approval).toString); //$NON-NLS-1$ + } + return approval; + } + } + } + return Status.OK_STATUS; + } + + /* + * Remove the operation by disposing it and notifying listeners. + */ + private void internalRemove(IUndoableOperation operation) { + operation.dispose(); + notifyRemoved(operation); + } + + /* + * Notify listeners of an operation event. + */ + private void notifyListeners(OperationHistoryEvent event) { + if ( auto e = cast(IAdvancedUndoableOperation)event.getOperation() ) { + IAdvancedUndoableOperation advancedOp = e; + SafeRunner.run(new class() ISafeRunnable { + IAdvancedUndoableOperation advancedOp_; + this(){ advancedOp_=advancedOp;} + public void handleException(Exception exception) { + if (DEBUG_OPERATION_HISTORY_UNEXPECTED) { + Tracing.printTrace( + "OPERATIONHISTORY", //$NON-NLS-1$ + "Exception during notification callback " ~ exception.toString); //$NON-NLS-1$ + } + } + + public void run() { + advancedOp_.aboutToNotify(event); + } + }); + } + Object[] listenerArray = listeners.getListeners(); + for (int i = 0; i < listenerArray.length; i++) { + IOperationHistoryListener listener = cast(IOperationHistoryListener) listenerArray[i]; + SafeRunner.run(new class() ISafeRunnable { + IOperationHistoryListener listener_; + this(){ listener_=listener; } + public void handleException(Exception exception) { + if (DEBUG_OPERATION_HISTORY_UNEXPECTED) { + Tracing.printTrace( + "OPERATIONHISTORY", //$NON-NLS-1$ + "Exception during notification callback " ~ exception.toString); //$NON-NLS-1$ + } + } + + public void run() { + listener_.historyNotification(event); + } + }); + } + } + + private void notifyAboutToExecute(IUndoableOperation operation) { + if (DEBUG_OPERATION_HISTORY_NOTIFICATION) { + Tracing.printTrace("OPERATIONHISTORY", "ABOUT_TO_EXECUTE " //$NON-NLS-1$ //$NON-NLS-2$ + ~ (cast(Object)operation).toString); + } + + notifyListeners(new OperationHistoryEvent( + OperationHistoryEvent.ABOUT_TO_EXECUTE, this, operation)); + } + + /* + * Notify listeners that an operation is about to redo. + */ + private void notifyAboutToRedo(IUndoableOperation operation) { + if (DEBUG_OPERATION_HISTORY_NOTIFICATION) { + Tracing.printTrace("OPERATIONHISTORY", "ABOUT_TO_REDO " //$NON-NLS-1$ //$NON-NLS-2$ + ~ (cast(Object)operation).toString); + } + + notifyListeners(new OperationHistoryEvent( + OperationHistoryEvent.ABOUT_TO_REDO, this, operation)); + } + + /* + * Notify listeners that an operation is about to undo. + */ + private void notifyAboutToUndo(IUndoableOperation operation) { + if (DEBUG_OPERATION_HISTORY_NOTIFICATION) { + Tracing.printTrace("OPERATIONHISTORY", "ABOUT_TO_UNDO " //$NON-NLS-1$ //$NON-NLS-2$ + ~ (cast(Object)operation).toString); + } + + notifyListeners(new OperationHistoryEvent( + OperationHistoryEvent.ABOUT_TO_UNDO, this, operation)); + } + + /* + * Notify listeners that an operation has been added. + */ + private void notifyAdd(IUndoableOperation operation) { + if (DEBUG_OPERATION_HISTORY_NOTIFICATION) { + Tracing.printTrace("OPERATIONHISTORY", "OPERATION_ADDED " //$NON-NLS-1$ //$NON-NLS-2$ + ~ (cast(Object)operation).toString); + } + + notifyListeners(new OperationHistoryEvent( + OperationHistoryEvent.OPERATION_ADDED, this, operation)); + } + + /* + * Notify listeners that an operation is done executing. + */ + private void notifyDone(IUndoableOperation operation) { + if (DEBUG_OPERATION_HISTORY_NOTIFICATION) { + Tracing.printTrace("OPERATIONHISTORY", "DONE " ~ (cast(Object)operation).toString); //$NON-NLS-1$ //$NON-NLS-2$ + } + + notifyListeners(new OperationHistoryEvent(OperationHistoryEvent.DONE, + this, operation)); + } + + /* + * Notify listeners that an operation did not succeed after an attempt to + * execute, undo, or redo was made. + */ + private void notifyNotOK(IUndoableOperation operation) { + notifyNotOK(operation, null); + } + + /* + * Notify listeners that an operation did not succeed after an attempt to + * execute, undo, or redo was made. Include the status associated with the + * attempt. + * + * @since 3.2 + */ + private void notifyNotOK(IUndoableOperation operation, IStatus status) { + if (DEBUG_OPERATION_HISTORY_NOTIFICATION) { + Tracing.printTrace("OPERATIONHISTORY", "OPERATION_NOT_OK " //$NON-NLS-1$ //$NON-NLS-2$ + ~ (cast(Object)operation).toString); + } + + notifyListeners(new OperationHistoryEvent( + OperationHistoryEvent.OPERATION_NOT_OK, this, operation, status)); + } + + /* + * Notify listeners that an operation was redone. + */ + private void notifyRedone(IUndoableOperation operation) { + if (DEBUG_OPERATION_HISTORY_NOTIFICATION) { + Tracing.printTrace("OPERATIONHISTORY", "REDONE " ~ (cast(Object)operation).toString); //$NON-NLS-1$ //$NON-NLS-2$ + } + + notifyListeners(new OperationHistoryEvent(OperationHistoryEvent.REDONE, + this, operation)); + } + + /* + * Notify listeners that an operation has been removed from the history. + */ + private void notifyRemoved(IUndoableOperation operation) { + if (DEBUG_OPERATION_HISTORY_NOTIFICATION) { + Tracing.printTrace("OPERATIONHISTORY", "OPERATION_REMOVED " //$NON-NLS-1$ //$NON-NLS-2$ + ~ (cast(Object)operation).toString); + } + + notifyListeners(new OperationHistoryEvent( + OperationHistoryEvent.OPERATION_REMOVED, this, operation)); + } + + /* + * Notify listeners that an operation has been undone. + */ + private void notifyUndone(IUndoableOperation operation) { + if (DEBUG_OPERATION_HISTORY_NOTIFICATION) { + Tracing.printTrace("OPERATIONHISTORY", "UNDONE " ~ (cast(Object)operation).toString); //$NON-NLS-1$ //$NON-NLS-2$ + } + + notifyListeners(new OperationHistoryEvent(OperationHistoryEvent.UNDONE, + this, operation)); + } + + /* + * Notify listeners that an operation has been undone. + */ + private void notifyChanged(IUndoableOperation operation) { + if (DEBUG_OPERATION_HISTORY_NOTIFICATION) { + Tracing.printTrace("OPERATIONHISTORY", "OPERATION_CHANGED " //$NON-NLS-1$//$NON-NLS-2$ + ~ (cast(Object)operation).toString); + } + + notifyListeners(new OperationHistoryEvent( + OperationHistoryEvent.OPERATION_CHANGED, this, operation)); + } + + /* + * (non-Javadoc) + * + * @see dwtx.core.commands.operations.IOperationHistory#redo(dwtx.core.commands.operations.IUndoContext, + * dwtx.core.runtime.IProgressMonitor, + * dwtx.core.runtime.IAdaptable) + */ + public IStatus redo(IUndoContext context, IProgressMonitor monitor, + IAdaptable info) { + Assert.isNotNull(cast(Object)context); + IUndoableOperation operation = getRedoOperation(context); + + // info if there is no operation + if (operation is null) { + return IOperationHistory.NOTHING_TO_REDO_STATUS; + } + + // error if operation is invalid + if (!operation.canRedo()) { + if (DEBUG_OPERATION_HISTORY_UNEXPECTED) { + Tracing.printTrace("OPERATIONHISTORY", //$NON-NLS-1$ + "Redo operation not valid - " ~ (cast(Object)operation).toString); //$NON-NLS-1$ + } + + return IOperationHistory.OPERATION_INVALID_STATUS; + } + + return doRedo(monitor, info, operation); + } + + /* + * (non-Javadoc) + * + * @see dwtx.core.commands.operations.IOperationHistory#redoOperation(dwtx.core.commands.operations.IUndoableOperation, + * dwtx.core.runtime.IProgressMonitor, + * dwtx.core.runtime.IAdaptable) + */ + + public IStatus redoOperation(IUndoableOperation operation, + IProgressMonitor monitor, IAdaptable info) { + Assert.isNotNull(cast(Object)operation); + IStatus status; + if (operation.canRedo()) { + status = doRedo(monitor, info, operation); + } else { + if (DEBUG_OPERATION_HISTORY_UNEXPECTED) { + Tracing.printTrace("OPERATIONHISTORY", //$NON-NLS-1$ + "Redo operation not valid - " ~ (cast(Object)operation).toString); //$NON-NLS-1$ + } + status = IOperationHistory.OPERATION_INVALID_STATUS; + } + return status; + } + + /* + * (non-Javadoc) + * + * @see dwtx.core.commands.operations.IOperationHistory#removeOperationApprover(dwtx.core.commands.operations.IOperationApprover) + */ + public void removeOperationApprover(IOperationApprover approver) { + approvers.remove(cast(Object)approver); + } + + /* + * (non-Javadoc) + * + * @see dwtx.core.commands.operations.IOperationHistory#removeOperationHistoryListener(dwtx.core.commands.operations.IOperationHistoryListener) + */ + public void removeOperationHistoryListener( + IOperationHistoryListener listener) { + listeners.remove(cast(Object)listener); + } + + /* + * (non-Javadoc) + * + * @see dwtx.core.commands.operations.IOperationHistory#replaceOperation(dwtx.core.commands.operations.IUndoableOperation, + * dwtx.core.commands.operations.IUndoableOperation []) + */ + public void replaceOperation(IUndoableOperation operation, + IUndoableOperation[] replacements) { + // check the undo history first. + bool inUndo = false; + synchronized (undoRedoHistoryLock) { + int index = 0; + synchronized(undoList) { + foreach( o; undoList ){ + if( o == operation ){ + break; + } + index++; + } + if( index is undoList.size() ){ + index = -1; + } + } + if (index > -1) { + inUndo = true; + synchronized(undoList) undoList.remove(operation); + // notify listeners after the lock on undoList is released + auto allContexts = new ArraySeq!(IUndoContext); + allContexts.capacity(replacements.length); + for (int i = 0; i < replacements.length; i++) { + IUndoContext[] opContexts = replacements[i].getContexts(); + for (int j = 0; j < opContexts.length; j++) { + allContexts.append(opContexts[j]); + } + synchronized(undoList) undoList.addAt(index, replacements[i]); + // notify listeners after the lock on the history is + // released + } + // recheck all the limits. We do this at the end so the index + // doesn't change during replacement + for (int i = 0; i < allContexts.size(); i++) { + IUndoContext context = allContexts.get(i); + forceUndoLimit(context, getLimit(context)); + } + } + } + if (inUndo) { + // notify listeners of operations added and removed + internalRemove(operation); + for (int i = 0; i < replacements.length; i++) { + notifyAdd(replacements[i]); + } + return; + } + + // operation was not in the undo history. Check the redo history. + + synchronized (undoRedoHistoryLock) { + int index = 0; + synchronized(redoList) { + foreach( o; redoList ){ + if( o == operation ){ + break; + } + index++; + } + if( index is redoList.size() ){ + index = -1; + } + } + if (index is -1) { + return; + } + auto allContexts = new ArraySeq!(IUndoContext); + allContexts.capacity(replacements.length); + synchronized(redoList) redoList.remove(operation); + // notify listeners after we release the lock on redoList + for (int i = 0; i < replacements.length; i++) { + IUndoContext[] opContexts = replacements[i].getContexts(); + for (int j = 0; j < opContexts.length; j++) { + allContexts.append(opContexts[j]); + } + synchronized(redoList) redoList.addAt(index, replacements[i]); + // notify listeners after we release the lock on redoList + } + // recheck all the limits. We do this at the end so the index + // doesn't change during replacement + for (int i = 0; i < allContexts.size(); i++) { + IUndoContext context = cast(IUndoContext) allContexts.get(i); + forceRedoLimit(context, getLimit(context)); + } + } + // send listener notifications after we release the lock on the history + internalRemove(operation); + for (int i = 0; i < replacements.length; i++) { + notifyAdd(replacements[i]); + } + } + + /* + * (non-Javadoc) + * + * @see dwtx.core.commands.operations.IOperationHistory#setLimit(dwtx.core.commands.operations.IUndoContext, + * int) + */ + public void setLimit(IUndoContext context, int limit) { + Assert.isTrue(limit >= 0); + /* + * The limit checking methods interpret a null context as a global limit + * to be enforced. We do not wish to support a global limit in this + * implementation, so we throw an exception for a null context. The rest + * of the implementation can handle a null context, so subclasses can + * override this if a global limit is desired. + */ + Assert.isNotNull(cast(Object)context); + synchronized(limits) limits.add(context, new Integer(limit)); + forceUndoLimit(context, limit); + forceRedoLimit(context, limit); + + } + + /* + * (non-Javadoc) + * + * @see dwtx.core.commands.operations.IOperationHistory#undo(dwtx.core.commands.operations.IUndoContext, + * dwtx.core.runtime.IProgressMonitor, + * dwtx.core.runtime.IAdaptable) + */ + public IStatus undo(IUndoContext context, IProgressMonitor monitor, + IAdaptable info) { + Assert.isNotNull(cast(Object)context); + IUndoableOperation operation = getUndoOperation(context); + + // info if there is no operation + if (operation is null) { + return IOperationHistory.NOTHING_TO_UNDO_STATUS; + } + + // error if operation is invalid + if (!operation.canUndo()) { + if (DEBUG_OPERATION_HISTORY_UNEXPECTED) { + Tracing.printTrace("OPERATIONHISTORY", //$NON-NLS-1$ + "Undo operation not valid - " ~ (cast(Object)operation).toString); //$NON-NLS-1$ + } + return IOperationHistory.OPERATION_INVALID_STATUS; + } + + return doUndo(monitor, info, operation); + } + + /* + * (non-Javadoc) + * + * @see dwtx.core.commands.operations.IOperationHistory#undoOperation(dwtx.core.commands.operations.IUndoableOperation, + * dwtx.core.runtime.IProgressMonitor, + * dwtx.core.runtime.IAdaptable) + */ + public IStatus undoOperation(IUndoableOperation operation, + IProgressMonitor monitor, IAdaptable info) { + Assert.isNotNull(cast(Object)operation); + IStatus status; + if (operation.canUndo()) { + status = doUndo(monitor, info, operation); + } else { + if (DEBUG_OPERATION_HISTORY_UNEXPECTED) { + Tracing.printTrace("OPERATIONHISTORY", //$NON-NLS-1$ + "Undo operation not valid - " ~ (cast(Object)operation).toString); //$NON-NLS-1$ + } + status = IOperationHistory.OPERATION_INVALID_STATUS; + } + return status; + } + + /* + * (non-Javadoc) + * + * @see dwtx.core.commands.operations.IOperationHistory#openOperation(dwtx.core.commands.operations.ICompositeOperation) + */ + public void openOperation(ICompositeOperation operation, int mode) { + synchronized (openCompositeLock) { + if (openComposite !is null && openComposite !is operation) { + // unexpected nesting of operations. + if (DEBUG_OPERATION_HISTORY_UNEXPECTED) { + Tracing.printTrace("OPERATIONHISTORY", //$NON-NLS-1$ + "Open operation called while another operation is open. old: " //$NON-NLS-1$ + ~ (cast(Object)openComposite).toString ~ "; new: " ~ (cast(Object)operation).toString); //$NON-NLS-1$ + } + + throw new IllegalStateException( + "Cannot open an operation while one is already open"); //$NON-NLS-1$ + } + openComposite = operation; + } + if (DEBUG_OPERATION_HISTORY_OPENOPERATION) { + Tracing.printTrace("OPERATIONHISTORY", "Opening operation " //$NON-NLS-1$ //$NON-NLS-2$ + ~ (cast(Object)openComposite).toString); + } + + if (mode is EXECUTE) { + notifyAboutToExecute(openComposite); + } + } + + /* + * (non-Javadoc) + * + * @see dwtx.core.commands.operations.IOperationHistory#closeOperation(bool, + * bool) + */ + public void closeOperation(bool operationOK, bool addToHistory, + int mode) { + ICompositeOperation endedComposite = null; + + synchronized (openCompositeLock) { + if (DEBUG_OPERATION_HISTORY_UNEXPECTED) { + if (openComposite is null) { + Tracing.printTrace("OPERATIONHISTORY", //$NON-NLS-1$ + "Attempted to close operation when none was open"); //$NON-NLS-1$ + return; + } + } + // notifications will occur outside the synchonized block + if (openComposite !is null) { + if (DEBUG_OPERATION_HISTORY_OPENOPERATION) { + Tracing.printTrace("OPERATIONHISTORY", "Closing operation " //$NON-NLS-1$ //$NON-NLS-2$ + ~ (cast(Object)openComposite).toString); + } + endedComposite = openComposite; + openComposite = null; + } + } + // any mode other than EXECUTE was triggered by a request to undo or + // redo something already in the history, so undo and redo + // notification will occur at the end of that sequence. + if (endedComposite !is null) { + if (operationOK) { + if (mode is EXECUTE) { + notifyDone(endedComposite); + } + if (addToHistory) { + add(endedComposite); + } + } else { + if (mode is EXECUTE) { + notifyNotOK(endedComposite); + } + } + } + } + + /* + * (non-Javadoc) + * + * @see dwtx.core.commands.operations.IOperationHistory#operationChanged(dwtx.core.commands.operations.IUndoableOperation) + */ + public void operationChanged(IUndoableOperation operation) { + if (undoList.contains(operation) || redoList.contains(operation)) { + notifyChanged(operation); + } + } +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/operations/IAdvancedUndoableOperation.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/operations/IAdvancedUndoableOperation.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,133 @@ +/******************************************************************************* + * Copyright (c) 2005 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.commands.operations.IAdvancedUndoableOperation; + +import dwtx.core.commands.ExecutionException; +import dwtx.core.runtime.IProgressMonitor; +import dwtx.core.runtime.IStatus; + +import dwtx.core.commands.operations.OperationHistoryEvent; + +import dwt.dwthelper.utils; + +/** + *

+ * IAdvancedUndoableOperation defines an interface for undoable operations that + * modify one or more elements in a model and attempt to keep model listeners up + * to date with changes that occur in the undo and redo history involving particular + * model elements. It also defines methods for computing the validity of an operation + * for undo or redo before attempting to perform the undo or redo. + *

+ *

+ * This interface is intended to be used by legacy frameworks that are adapting + * their original undo and redo support to this framework. The methods in this + * interface allow legacy clients to maintain features not supported in the + * basic operations framework. + *

+ * + * @since 3.1 + * + */ +public interface IAdvancedUndoableOperation { + + /** + *

+ * An operation history notification about this operation is about to be + * sent to operation history listeners. Any preparation needed before + * listeners are notified about this operation should be performed here. + * + *

+ * This method has been added to support legacy undo frameworks that are + * adapting to IUndoableOperation. Operations that previously relied on + * notification from their containing history or stack before any listeners + * are notified about changes to the operation should implement this + * interface. + * + * @param event + * the event that is about to be sent with the pending + * notification + * + */ + void aboutToNotify(OperationHistoryEvent event); + + /** + *

+ * Return an array of objects that are affected by executing, undoing, or + * redoing this operation. If it cannot be determined which objects are + * affected, return null. + *

+ * + * @return the array of Objects modified by this operation, or + * null if the affected objects cannot be determined. + */ + Object[] getAffectedObjects(); + + /** + * Return a status indicating the projected outcome of undoing the receiver. + * + * This method should be used to report the possible outcome of an undo and + * is used when computing the validity of an undo is too expensive to + * perform in {@link IUndoableOperation#canUndo()}. It is not called by the + * operation history, but instead is used by clients (such as implementers + * of {@link IOperationApprover}) who wish to perform advanced validation of + * an operation before attempting to undo it. + * + * If the result of this method is the discovery that an operation can in + * fact not be undone, then the operation is expected to correctly answer + * false on subsequent calls to + * {@link IUndoableOperation#canUndo()}. + * + * @param monitor + * the progress monitor (or null) to use for + * reporting progress to the user while computing the validity. + * + * @return the IStatus indicating the validity of the undo. The status + * severity should be set to OK if the undo can + * successfully be performed, and ERROR if it + * cannnot. Any other status is assumed to represent an ambiguous + * state. + * @throws ExecutionException + * if an exception occurs while computing the validity. + */ + IStatus computeUndoableStatus(IProgressMonitor monitor); + + /** + * Return a status indicating the projected outcome of redoing the receiver. + * + * This method should be used to report the possible outcome of a redo and + * is used when computing the validity of a redo is too expensive to perform + * in {@link IUndoableOperation#canRedo()}. It is not called by the + * operation history, but instead is used by clients (such as implementers + * of {@link IOperationApprover}) who wish to perform advanced validation of + * an operation before attempting to redo it. + * + * If the result of this method is the discovery that an operation can in + * fact not be redone, then the operation is expected to correctly answer + * false on subsequent calls to + * {@link IUndoableOperation#canRedo()}. + * + * @param monitor + * the progress monitor (or null) to use for + * reporting progress to the user while computing the validity. + * + * @return the IStatus indicating the validity of the redo. The status + * severity should be set to OK if the redo can + * successfully be performed, and ERROR if it + * cannnot. Any other status is assumed to represent an ambiguous + * state. + * @throws ExecutionException + * if an exception occurs while computing the validity. + */ + IStatus computeRedoableStatus(IProgressMonitor monitor); + +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/operations/IAdvancedUndoableOperation2.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/operations/IAdvancedUndoableOperation2.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,101 @@ +/******************************************************************************* + * Copyright (c) 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.commands.operations.IAdvancedUndoableOperation2; + +import dwtx.core.commands.ExecutionException; +import dwtx.core.runtime.IProgressMonitor; +import dwtx.core.runtime.IStatus; + +import dwt.dwthelper.utils; + +/** + *

+ * IAdvancedUndoableOperation2 defines a method for computing the validity of + * executing an operation before attempting to execute it. It also defines a way + * for clients to specify that computing status should be done quietly, without + * consulting the user. This interface is useful when implementing + * {@link IOperationApprover2}, or any other object that performs validation of + * the undo history. It also allows operations to specify whether they should be + * run in the UI thread. + *

+ * + * @since 3.3 + * + */ +public interface IAdvancedUndoableOperation2 { + /** + * Return a status indicating the projected outcome of executing the + * receiver. + * + * This method should be used to report the possible outcome of executing an + * operation when computing the validity of an execute is too expensive to + * perform in {@link IUndoableOperation#canExecute()}. It is not called by + * the operation history, but instead is used by clients (such as + * implementers of {@link IOperationApprover2}) who wish to perform + * advanced validation of an operation before attempting to execute it. + * + * If the result of this method is the discovery that an operation can in + * fact not be executed, then the operation is expected to correctly answer + * false on subsequent calls to + * {@link IUndoableOperation#canExecute()}. + * + * @param monitor + * the progress monitor (or null) to use for + * reporting progress to the user while computing the validity. + * + * @return the IStatus indicating the validity of the execute. The status + * severity should be set to OK if the execute can + * successfully be performed, and ERROR if it cannot. + * Any other severity is assumed to represent an ambiguous state. + * @throws ExecutionException + * if an exception occurs while computing the validity. + */ + IStatus computeExecutionStatus(IProgressMonitor monitor); + + /** + * Set a bool that instructs whether the computation of the receiver's + * execution, undo, or redo status should quietly compute status without + * consulting or prompting the user. The default value is false. + * This flag should only be set to true while the execution, + * undo, or redo status computations are being performed in the background, + * and should be restored to false when complete. + *

+ * If the status computation methods typically need to consult the user in + * order to determine the severity of a particular situation, the least + * severe status that could be chosen by the user should be returned when + * this flag is true. This can help to prevent overzealous + * disposal of the operation history when an operation is in an ambiguous + * state. Typically, the status computation methods are invoked with this + * flag set to false just before the actual execution, undo, + * or redo occurs, so the user can be consulted for the final outcome. + * + * @param quiet + * true if it is inappropriate to consult or + * otherwise prompt the user while computing status, and + * false if the user may be prompted. + * + * @see #computeExecutionStatus(IProgressMonitor) + * @see IAdvancedUndoableOperation#computeUndoableStatus(IProgressMonitor) + * @see IAdvancedUndoableOperation#computeRedoableStatus(IProgressMonitor) + */ + public void setQuietCompute(bool quiet); + + /** + * Return a bool that instructs whether the operation should be executed, + * undone, or redone in a background thread. + * + * @return true if the operation should be run in the + * background, false if it should not. + */ + public bool runInBackground(); +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/operations/ICompositeOperation.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/operations/ICompositeOperation.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,57 @@ +/******************************************************************************* + * Copyright (c) 2005 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.commands.operations.ICompositeOperation; + +import dwtx.core.commands.operations.IUndoableOperation; + +import dwt.dwthelper.utils; + +/** + *

+ * ICompositeOperation defines an undoable operation that is composed of child + * operations. Requests to execute, undo, or redo a composite result in the the + * execution, undo, or redo of the composite as a whole. Similarly, a request to + * dispose the composite should result in all child operations being disposed. + *

+ * + * @since 3.1 + */ +public interface ICompositeOperation : IUndoableOperation { + + /** + *

+ * Add the specified operation as a child of this operation. + *

+ * + * @param operation + * the operation to be added. If the operation instance has + * already been added, this method will have no effect. + */ + void add(IUndoableOperation operation); + + /** + *

+ * Remove the specified operation from this operation. + *

+ *

+ * The composite operation should dispose the operation as part of removing + * it. + *

+ * + * @param operation + * the operation to be removed. The operation should be disposed + * by the receiver. This method will have no effect if the + * operation instance is not already a child. + */ + void remove(IUndoableOperation operation); +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/operations/IContextReplacingOperation.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/operations/IContextReplacingOperation.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,43 @@ +/******************************************************************************* + * Copyright (c) 2005, 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.commands.operations.IContextReplacingOperation; + +import dwtx.core.commands.operations.IUndoContext; + +import dwt.dwthelper.utils; + +/** + * IContextReplacingOperation defines an interface for undoable operations that + * can replace one undo context with another undo context. It is used by + * operations, such as composite operations, where removing and adding an undo + * context would not have the same semantic as replacing one undo context with + * another. + * + * @since 3.2 + * + */ +public interface IContextReplacingOperation { + + /** + * Replace the undo context of the receiver with the provided replacement + * undo context. + *

+ * This message has no effect if the original undo context is not present in + * the receiver. + * + * @param original the undo context which is to be replaced + * @param replacement the undo context which is replacing the original + * + */ + void replaceContext(IUndoContext original, IUndoContext replacement); +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/operations/IOperationApprover.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/operations/IOperationApprover.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,110 @@ +/******************************************************************************* + * Copyright (c) 2005 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.commands.operations.IOperationApprover; + +import dwtx.core.runtime.IAdaptable; +import dwtx.core.runtime.IStatus; + +import dwtx.core.commands.operations.IUndoableOperation; +import dwtx.core.commands.operations.IOperationHistory; + +import dwt.dwthelper.utils; + +/** + *

+ * IOperationApprover defines an interface for approving the undo or redo of a + * particular operation within an operation history. Operations that are + * candidates for undo or redo have already been validated against their current + * state and according to the rules of the history. + *

+ *

+ * By the time an IOperationApprover is consulted, the undo has already been + * requested. Approvers should return an IStatus object with + * severity OK if the operation should proceed, and any other + * severity if it should not. When an operation is not approved, it is expected + * that the object not allowing the operation has already consulted the user if + * necessary or otherwise provided any necessary information to the user about + * the fact that the operation is not approved. + *

+ *

+ * Operation approvers must be prepared to receive the approval messages from a + * background thread. Any UI access occurring inside the implementation must be + * properly synchronized using the techniques specified by the client's widget + * library. + *

+ * + * @since 3.1 + */ +public interface IOperationApprover { + + /** + * Return a status indicating whether the specified operation should be + * redone. Any status that does not have severity IStatus.OK + * will not be approved. Implementers should not assume that the redo will + * be performed when the status is OK, since other operation + * approvers may veto the redo. + * + * @param operation + * the operation to be redone + * @param history + * the history redoing the operation + * @param info + * the IAdaptable (or null) provided by the + * caller in order to supply UI information for prompting the + * user if necessary. When this parameter is not + * null, it should minimally contain an adapter + * for the dwt.widgets.Shell.class. Even if UI + * information is provided, the implementation of this method + * must be prepared for being called from a background thread. + * Any UI access must be properly synchronized using the + * techniques specified by the client's widget library. + * @return the IStatus describing whether the operation is approved. The + * redo will not proceed if the status severity is not + * OK, and the caller requesting the redo will be + * returned the status that caused the rejection. Any other status + * severities will not be interpreted by the history. + */ + IStatus proceedRedoing(IUndoableOperation operation, + IOperationHistory history, IAdaptable info); + + /** + * Return a status indicating whether the specified operation should be + * undone. Any status that does not have severity IStatus.OK + * will not be approved. Implementers should not assume that the undo will + * be performed when the status is OK, since other operation + * approvers can veto the undo. + * + * @param operation + * the operation to be undone + * @param history + * the history undoing the operation + * @param info + * the IAdaptable (or null) provided by the + * caller in order to supply UI information for prompting the + * user if necessary. When this parameter is not + * null, it should minimally contain an adapter + * for the dwt.widgets.Shell.class. Even if UI + * information is provided, the implementation of this method + * must be prepared for being called from a background thread. + * Any UI access must be properly synchronized using the + * techniques specified by the client's widget library. + * @return the IStatus describing whether the operation is approved. The + * undo will not proceed if the status severity is not + * OK, and the caller requesting the undo will be + * returned the status that caused the rejection. Any other status + * severities will not be interpreted by the history. + */ + IStatus proceedUndoing(IUndoableOperation operation, + IOperationHistory history, IAdaptable info); + +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/operations/IOperationApprover2.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/operations/IOperationApprover2.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,80 @@ +/******************************************************************************* + * Copyright (c) 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.commands.operations.IOperationApprover2; + +import dwtx.core.runtime.IAdaptable; +import dwtx.core.runtime.IStatus; + +import dwtx.core.commands.operations.IOperationApprover; +import dwtx.core.commands.operations.IUndoableOperation; +import dwtx.core.commands.operations.IOperationHistory; + +import dwt.dwthelper.utils; + +/** + * Extends {@link IOperationApprover} to approve the execution of a particular + * operation within an operation history. Operations that are candidates for + * execution have already been validated against their current state and + * according to the rules of the history. Prior to 3.2, an operation approver + * was only consulted for undo and redo of an operation, not its initial + * execution. + *

+ * By the time an IOperationApprover2 is consulted, the execution has already + * been requested and it has been determined that the operation is valid. + * Approvers should return an IStatus object with severity + * OK if the operation should proceed, and any other severity if + * it should not. When an operation is not approved, it is expected that the + * object not allowing the operation has already consulted the user if necessary + * or otherwise provided any necessary information to the user about the fact + * that the operation is not approved. + *

+ *

+ * Like {@link IOperationApprover}, implementers of this extension must be + * prepared to receive the approval messages from a background thread. Any UI + * access occurring inside the implementation must be properly synchronized + * using the techniques specified by the client's widget library. + *

+ * + * @since 3.2 + */ +public interface IOperationApprover2 : IOperationApprover { + /** + * Return a status indicating whether the specified operation should be + * executed. Any status that does not have severity IStatus.OK + * will not be approved. Implementers should not assume that the execution + * will be performed when the status is OK, since other + * operation approvers may veto the execution. + * + * @param operation + * the operation to be executed + * @param history + * the history performing the execution of the operation + * @param info + * the IAdaptable (or null) provided by the + * caller in order to supply UI information for prompting the + * user if necessary. When this parameter is not + * null, it should minimally contain an adapter + * for the dwt.widgets.Shell.class. Even if UI + * information is provided, the implementation of this method + * must be prepared for being called from a background thread. + * Any UI access must be properly synchronized using the + * techniques specified by the client's widget library. + * @return the IStatus describing whether the operation is approved. The + * execution will not proceed if the status severity is not + * OK, and the caller requesting the execution will + * be returned the status that caused the rejection. Any other + * status severities will not be interpreted by the history. + */ + IStatus proceedExecuting(IUndoableOperation operation, + IOperationHistory history, IAdaptable info); +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/operations/IOperationHistory.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/operations/IOperationHistory.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,712 @@ +/******************************************************************************* + * Copyright (c) 2005, 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.commands.operations.IOperationHistory; + +import dwtx.core.commands.ExecutionException; +import dwtx.core.internal.commands.operations.GlobalUndoContext; +import dwtx.core.runtime.IAdaptable; +import dwtx.core.runtime.IProgressMonitor; +import dwtx.core.runtime.IStatus; + +import dwtx.core.commands.operations.IUndoContext; +import dwtx.core.commands.operations.IUndoableOperation; +import dwtx.core.commands.operations.IOperationApprover; +import dwtx.core.commands.operations.IOperationApprover2; +import dwtx.core.commands.operations.IOperationHistoryListener; +import dwtx.core.commands.operations.ICompositeOperation; +import dwtx.core.commands.operations.OperationStatus; + +import dwt.dwthelper.utils; + +static this(){ + IOperationHistory.GLOBAL_UNDO_CONTEXT = new GlobalUndoContext(); + IOperationHistory.NOTHING_TO_REDO_STATUS = new OperationStatus( + IStatus.INFO, OperationStatus.DEFAULT_PLUGIN_ID, + OperationStatus.NOTHING_TO_REDO, "No operation to redo", null); //$NON-NLS-1$ + IOperationHistory.NOTHING_TO_UNDO_STATUS = new OperationStatus( + IStatus.INFO, OperationStatus.DEFAULT_PLUGIN_ID, + OperationStatus.NOTHING_TO_UNDO, "No operation to undo", null); //$NON-NLS-1$ + IOperationHistory.OPERATION_INVALID_STATUS = new OperationStatus( + IStatus.ERROR, OperationStatus.DEFAULT_PLUGIN_ID, + OperationStatus.OPERATION_INVALID, "Operation is not valid", null); //$NON-NLS-1$ +} + +/** + *

+ * IOperationHistory tracks a history of operations that can be undone or + * redone. Operations are added to the history once they have been initially + * executed. Clients may choose whether to have the operations history perform + * the initial execution or to simply add an already-executed operation to the + * history. + *

+ *

+ * Once operations are added to the history, the methods + * {@link #canRedo(IUndoContext)} and {@link #canUndo(IUndoContext)} are used to + * determine whether there is an operation available for undo and redo in a + * given undo context. The context-based protocol implies that there is only one + * operation that can be undone or redone at a given time in a given context. + * This is typical of a linear undo model, when only the most recently executed + * operation is available for undo. When this protocol is used, a linear model + * is enforced by the history. + *

+ *

+ * It is up to clients to determine how to maintain a history that is invalid or + * stale. For example, when the most recent operation for a context cannot be + * performed, clients may wish to dispose the history for that context. + *

+ *

+ * Additional protocol allows direct undo and redo of a specified operation, + * regardless of its position in the history. When a more flexible undo model is + * supported, these methods can be implemented to undo and redo directly + * specified operations. If an implementer of IOperationHistory does not allow + * direct undo and redo, these methods can return a status indicating that it is + * not allowed. + *

+ *

+ * Listeners ({@link IOperationHistoryListener}) can listen for notifications + * about changes in the history (operations added or removed), and for + * notification before and after any operation is executed, undone or redone. + * Notification of operation execution only occurs when clients direct the + * history to execute the operation. If the operation is added after it is + * executed, there can be no notification of its execution. + *

+ *

+ * {@link IOperationApprover} defines an interface for approving an undo or redo + * before it occurs. This is useful for injecting policy-decisions into the undo + * model - whether direct undo and redo are supported, or warning the user about + * certain kinds of operations. It can also be used when clients maintain state + * related to an operation and need to determine whether an undo or redo will + * cause any conflicts with their local state. + *

+ * + * @since 3.1 + */ +public interface IOperationHistory { + + /** + * An operation is to be opened or closed for execution. (value is 1). + */ + public static const int EXECUTE = 1; + + /** + * An operation is to be opened for undo. (value is 2). + */ + public static const int UNDO = 2; + + /** + * An operation is to be opened for redo. (value is 3). + */ + public static const int REDO = 3; + + /** + * An undo context that can be used to refer to the global undo history. + * This context is not intended to be assigned to operations. Instead, it is + * used for querying the history or performing an undo or redo on the entire + * history, regardless of each operation's undo contexts. + */ + public static /+const+/ IUndoContext GLOBAL_UNDO_CONTEXT; + + /** + * An operation info status describing the condition that there is no + * available operation for redo. + */ + public static /+const+/ IStatus NOTHING_TO_REDO_STATUS; //$NON-NLS-1$ + + /** + * An operation info status describing the condition that there is no + * available operation for undo. + */ + public static /+const+/ IStatus NOTHING_TO_UNDO_STATUS; //$NON-NLS-1$ + + /** + * An operation error status describing the condition that the operation + * available for execution, undo or redo is not in a valid state for the + * action to be performed. + */ + public static /+const+/ IStatus OPERATION_INVALID_STATUS; //$NON-NLS-1$ + + /** + *

+ * Add the specified operation to the history without executing it. The + * operation should have already been executed by the time it is added to + * the history. Listeners will be notified that the operation was added to + * the history (OPERATION_ADDED). + *

+ * + * @param operation + * the operation to be added to the history + */ + void add(IUndoableOperation operation); + + /** + *

+ * Add the specified approver to the list of operation approvers consulted + * by the operation history before an undo or redo is attempted. + *

+ * + * @param approver + * the IOperationApprover to be added as an approver.the instance + * to remove. Must not be null. If an attempt is + * made to register an instance which is already registered with + * this instance, this method has no effect. + * + * @see dwtx.core.commands.operations.IOperationApprover + */ + void addOperationApprover(IOperationApprover approver); + + /** + *

+ * Add the specified listener to the list of operation history listeners + * that are notified about changes in the history or operations that are + * executed, undone, or redone. + *

+ * + * @param listener + * the IOperationHistoryListener to be added as a listener. Must + * not be null. If an attempt is made to register + * an instance which is already registered with this instance, + * this method has no effect. + * + * @see dwtx.core.commands.operations.IOperationHistoryListener + * @see dwtx.core.commands.operations.OperationHistoryEvent + */ + void addOperationHistoryListener(IOperationHistoryListener listener); + + /** + *

+ * Close the current operation. If the operation has successfully completed, + * send listeners a DONE, UNDONE, or + * REDONE notification, depending on the mode. Otherwise send + * an OPERATION_NOT_OK notification. Add the operation to the + * history if specified and send an OPERATION_ADDED + * notification. + *

+ *

+ * Any operations that are executed and added after this operation is closed + * will no longer be considered part of this operation. + *

+ *

+ * This method has no effect if the caller has not previously called + * {@link #openOperation}. + *

+ * + * @param operationOK + * true if the operation successfully completed. + * Listeners should be notified with DONE, + * UNDONE, or REDONE. + * false if the operation did not successfully + * complete. Listeners should be notified with + * OPERATION_NOT_OK. + * @param addToHistory + * true if the operation should be added to the + * history, false if it should not. If the + * operationOK parameter is false, + * the operation will never be added to the history. + * @param mode + * the mode the operation was opened in. Can be one of + * EXECUTE, UNDO, or + * REDO. This determines what notifications are + * sent. + */ + void closeOperation(bool operationOK, bool addToHistory, int mode); + + /** + *

+ * Return whether there is a valid redoable operation available in the given + * context. + *

+ * + * @param context + * the context to be checked + * @return true if there is a redoable operation, + * false otherwise. + */ + + bool canRedo(IUndoContext context); + + /** + *

+ * Return whether there is a valid undoable operation available in the given + * context + *

+ * + * @param context + * the context to be checked + * @return true if there is an undoable operation, + * false otherwise. + */ + bool canUndo(IUndoContext context); + + /** + *

+ * Dispose of the specified context in the history. All operations that have + * only the given context will be disposed. References to the context in + * operations that have more than one context will also be removed. A + * history notification for the removal of each operation being disposed + * will be sent. + *

+ * + * @param context + * the context to be disposed + * @param flushUndo + * true if the context should be flushed from the + * undo history, false if it should not + * @param flushRedo + * true if the context should be flushed from the + * redo history, false if it should not. + * @param flushContext + * true if the context is no longer in use and + * references to it should be flushed. + */ + void dispose(IUndoContext context, bool flushUndo, bool flushRedo, + bool flushContext); + + /** + *

+ * Execute the specified operation and add it to the operations history if + * successful. This method is used by clients who wish operation history + * listeners to receive notifications before and after the execution of the + * operation. Execution of the operation is subject to approval by any + * registered {@link IOperationApprover2}. If execution is approved, + * listeners will be notified before (ABOUT_TO_EXECUTE) and + * after (DONE or OPERATION_NOT_OK). + *

+ *

+ * If the operation successfully executes, an additional notification that + * the operation has been added to the history (OPERATION_ADDED) + * will be sent. + *

+ * + * @param operation + * the operation to be executed and then added to the history + * + * @param monitor + * the progress monitor to be used (or null) + * during the operation. + * + * @param info + * the IAdaptable (or null) provided by the + * caller in order to supply UI information for prompting the + * user if necessary. When this parameter is not + * null, it should minimally contain an adapter + * for the dwt.widgets.Shell.class. + * + * @return the IStatus indicating whether the execution succeeded. + * + *

+ * The severity code in the returned status describes whether the operation + * succeeded and whether it was added to the history. OK + * severity indicates that the execute operation was successful and that the + * operation has been added to the history. Listeners will receive + * notifications about the operation's success (DONE) and + * about the operation being added to the history (OPERATION_ADDED). + *

+ *

+ * CANCEL severity indicates that the user cancelled the + * operation and that the operation was not added to the history. + * ERROR severity indicates that the operation did not + * successfully execute and that it was not added to the history. Any other + * severity code is not specifically interpreted by the history, and the + * operation will not be added to the history. For all severities other than + * OK, listeners will receive the + * OPERATION_NOT_OK notification instead of the + * DONE notification if the execution was approved and + * attempted. + *

+ * + * @throws ExecutionException + * if an exception occurred during execution. + * + */ + IStatus execute(IUndoableOperation operation, IProgressMonitor monitor, + IAdaptable info); + + /** + *

+ * Return the limit on the undo and redo history for a particular context. + *

+ * + * @param context + * the context whose limit is requested + * + * @return the undo and redo history limit for the specified context. + */ + int getLimit(IUndoContext context); + + /** + *

+ * Get the array of operations in the redo history for a the specified undo + * context. The operations are in the order that they were added to the + * history, with the most recently undone operation appearing last in the + * array. This history is used LIFO (last in, first out) when successive + * "Redo" commands are invoked. + * + *

+ * + * @param context + * the context for the redo + * @return the array of operations in the history + */ + IUndoableOperation[] getRedoHistory(IUndoContext context); + + /** + *

+ * Get the operation that will next be redone in the given undo context. + *

+ * + * @param context + * the context for the redo + * @return the operation to be redone or null if there is no + * operation available. There is no guarantee that the returned + * operation is valid for redo. + */ + IUndoableOperation getRedoOperation(IUndoContext context); + + /** + *

+ * Get the array of operations in the undo history for the specified undo + * context. The operations are in the order that they were added to the + * history, with the most recently added operation appearing last in the + * array. This history is used LIFO (last in, first out) when successive + * "Undo" commands are invoked. + *

+ * + * @param context + * the context for the undo + * @return the array of operations in the history + */ + IUndoableOperation[] getUndoHistory(IUndoContext context); + + /** + *

+ * Open this composite operation and consider it an operation that contains + * other related operations. Consider all operations that are subsequently + * executed or added to be part of this operation. When an operation is + * opened, listeners will immediately receive a notification for the opened + * operation. The specific notification depends on the mode in which the + * operation is opened (ABOUT_TO_EXECUTE, + * ABOUT_TO_UNDO, ABOUT_TO_REDO). + * Notifications for any other execute or add while this operation is open + * will not occur. Instead, those operations will be added to the current + * operation. + *

+ *

+ * Note: This method is intended to be used by legacy undo frameworks that + * do not expect related undo operations to appear in the same undo history + * as the triggering undo operation. When an operation is open, any + * subsequent requests to execute, add, undo, or redo another operation will + * result in that operation being added to the open operation. Once the + * operation is closed, the composite will be considered an atomic + * operation. Clients should not modify the composite directly (by adding + * and removing children) while it is open. + *

+ *

+ * When a composite is open, operations that are added to the history will + * be considered part of the open operation instead. Operations that are + * executed while a composite is open will first be executed and then added + * to the composite. + *

+ *

+ * Open operations cannot be nested. If this method is called when a + * different operation is open, it is presumed to be an application coding + * error and this method will throw an IllegalStateException. + *

+ * + * @param operation + * the composite operation to be considered as the parent for all + * subsequent operations. + * @param mode + * the mode the operation is executing in. Can be one of + * EXECUTE, UNDO, or + * REDO. This determines what notifications are + * sent. + */ + void openOperation(ICompositeOperation operation, int mode); + + /** + *

+ * The specified operation has changed in some way since it was added to the + * operation history. Notify listeners with an OPERATION_CHANGED event. + *

+ * + * @param operation + * the operation that has changed. + * + */ + void operationChanged(IUndoableOperation operation); + + /** + *

+ * Get the operation that will next be undone in the given undo context. + *

+ * + * @param context + * the context for the undo + * @return the operation to be undone or null if there is no + * operation available. There is no guarantee that the available + * operation is valid for the undo. + */ + IUndoableOperation getUndoOperation(IUndoContext context); + + /** + *

+ * Redo the most recently undone operation in the given context. The redo of + * the operation is subject to approval by any registered + * {@link IOperationApprover} before it is attempted. + *

+ * + * @param context + * the context to be redone + * @param monitor + * the progress monitor to be used for the redo, or + * null if no progress monitor is provided. + * @param info + * the IAdaptable (or null) provided by the + * caller in order to supply UI information for prompting the + * user if necessary. When this parameter is not + * null, it should minimally contain an adapter + * for the dwt.widgets.Shell.class. + * @return the IStatus indicating whether the redo succeeded. + * + *

+ * The severity code in the returned status describes whether the operation + * succeeded and whether it remains in the history. OK + * severity indicates that the redo operation was successful and (since + * release 3.2), that the operation will be placed in the undo history. + * (Prior to 3.2, a successfully redone operation would not be placed on the + * undo history if it could not be undone. Since 3.2, this is relaxed, and + * all successfully redone operations are placed in the undo history.) + * Listeners will receive the REDONE notification. + *

+ *

+ * Other severity codes (CANCEL, ERROR, + * INFO, etc.) are not specifically interpreted by the + * history. The operation will remain in the history and the returned status + * is simply passed back to the caller. For all severities other than + * OK, listeners will receive the + * OPERATION_NOT_OK notification instead of the + * REDONE notification if the redo was approved and + * attempted. + *

+ * + * @throws ExecutionException + * if an exception occurred during redo. + * + */ + IStatus redo(IUndoContext context, IProgressMonitor monitor, IAdaptable info); + + /** + *

+ * Redo the specified operation. The redo of the operation is subject to + * approval by any registered {@link IOperationApprover} before it is + * attempted. + *

+ * + * @param operation + * the operation to be redone + * @param monitor + * the progress monitor to be used for the redo, or code>null
+ * if no progress monitor is provided + * @param info + * the IAdaptable (or null) provided by the + * caller in order to supply UI information for prompting the + * user if necessary. When this parameter is not null, + * it should minimally contain an adapter for the + * dwt.widgets.Shell.class. + * + * @return the IStatus indicating whether the redo succeeded. + * + *

+ * The severity code in the returned status describes whether the operation + * succeeded and whether it remains in the history. OK + * severity indicates that the redo operation was successful, and (since + * release 3.2), that the operation will be placed in the undo history. + * (Prior to 3.2, a successfully redone operation would not be placed on the + * undo history if it could not be undone. Since 3.2, this is relaxed, and + * all successfully redone operations are placed in the undo history.) + * Listeners will receive the REDONE notification. + *

+ *

+ * Other severity codes (CANCEL, ERROR, + * INFO, etc.) are not specifically interpreted by the + * history. The operation will remain in the history and the returned status + * is simply passed back to the caller. For all severities other than OK, + * listeners will receive the OPERATION_NOT_OK notification + * instead of the REDONE notification if the redo was + * approved and attempted. + *

+ * + * @throws ExecutionException + * if an exception occurred during redo. + */ + IStatus redoOperation(IUndoableOperation operation, + IProgressMonitor monitor, IAdaptable info); + + /** + *

+ * Remove the specified operation approver from the list of operation + * approvers that are consulted before an operation is undone or redone. + *

+ * + * @param approver + * the IOperationApprover to be removed. Must not be + * null. If an attempt is made to remove an + * instance which is not already registered with this instance, + * this method has no effect. + */ + void removeOperationApprover(IOperationApprover approver); + + /** + *

+ * Remove the specified listener from the list of operation history + * listeners. + *

+ * + * @param listener + * The IOperationHistoryListener to be removed. Must not be + * null. If an attempt is made to remove an + * instance which is not already registered with this instance, + * this method has no effect. + */ + void removeOperationHistoryListener(IOperationHistoryListener listener); + + /** + *

+ * Replace the specified operation in the undo or redo history with the + * provided list of replacements. This protocol is typically used when a + * composite is broken up into its atomic parts. The replacements will be + * inserted so that the first replacement will be the first of the + * replacements to be undone or redone. Listeners will be notified about the + * removal of the replaced element and the addition of each replacement. + *

+ * + * @param operation + * The IUndoableOperation to be replaced + * @param replacements + * the array of IUndoableOperation to replace the first operation + */ + void replaceOperation(IUndoableOperation operation, + IUndoableOperation[] replacements); + + /** + *

+ * Set the limit on the undo and redo history for a particular context. + *

+ * + * @param context + * the context whose limit is being set. + * + * @param limit + * the maximum number of operations that should be kept in the + * undo or redo history for the specified context. Must not be + * negative. + */ + void setLimit(IUndoContext context, int limit); + + /** + *

+ * Undo the most recently executed operation in the given context. The undo + * of the operation is subject to approval by any registered + * {@link IOperationApprover} before it is attempted. + *

+ * + * @param context + * the context to be undone + * @param monitor + * the progress monitor to be used for the undo, or + * null if no progress monitor is provided. + * @param info + * the IAdaptable (or null) provided by the + * caller in order to supply UI information for prompting the + * user if necessary. When this parameter is not + * null, it should minimally contain an adapter + * for the dwt.widgets.Shell.class. + * + * @return the IStatus indicating whether the undo succeeded. + * + *

+ * The severity code in the returned status describes whether the operation + * succeeded and whether it remains in the history. OK + * severity indicates that the undo operation was successful, and (since + * release 3.2), that the operation will be placed on the redo history. + * (Prior to 3.2, a successfully undone operation would not be placed on the + * redo history if it could not be redone. Since 3.2, this is relaxed, and + * all successfully undone operations are placed in the redo history.) + * Listeners will receive the UNDONE notification. + *

+ *

+ * Other severity codes (CANCEL, ERROR, + * INFO, etc.) are not specifically interpreted by the + * history. The operation will remain in the history and the returned status + * is simply passed back to the caller. For all severities other than + * OK, listeners will receive the + * OPERATION_NOT_OK notification instead of the + * UNDONE notification if the undo was approved and + * attempted. + *

+ * + * @throws ExecutionException + * if an exception occurred during undo. + */ + + IStatus undo(IUndoContext context, IProgressMonitor monitor, IAdaptable info); + + /** + *

+ * Undo the specified operation. The undo of the operation is subject to + * approval by any registered {@link IOperationApprover} before it is + * attempted. + *

+ * + * @param operation + * the operation to be undone + * @param monitor + * the progress monitor to be used for the undo, or + * null if no progress monitor is provided + * @param info + * the IAdaptable (or null) provided by the + * caller in order to supply UI information for prompting the + * user if necessary. When this parameter is not + * null, it should minimally contain an adapter + * for the dwt.widgets.Shell.class. + * + * @return the IStatus indicating whether the undo succeeded. + * + *

+ * The severity code in the returned status describes whether the operation + * succeeded and whether it remains in the history. OK + * severity indicates that the undo operation was successful, and (since + * release 3.2), that the operation will be placed on the redo history. + * (Prior to 3.2, a successfully undone operation would not be placed on the + * redo history if it could not be redone. Since 3.2, this is relaxed, and + * all successfully undone operations are placed in the redo history.) + * Listeners will receive the UNDONE notification. + *

+ *

+ * Other severity codes (CANCEL, ERROR, + * INFO, etc.) are not specifically interpreted by the + * history. The operation will remain in the history and the returned status + * is simply passed back to the caller. For all severities other than + * OK, listeners will receive the + * OPERATION_NOT_OK notification instead of the + * UNDONE notification if the undo was approved and + * attempted. + *

+ * + * @throws ExecutionException + * if an exception occurred during undo. + */ + IStatus undoOperation(IUndoableOperation operation, + IProgressMonitor monitor, IAdaptable info); + +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/operations/IOperationHistoryListener.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/operations/IOperationHistoryListener.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (c) 2005 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.commands.operations.IOperationHistoryListener; + +import dwtx.core.commands.operations.OperationHistoryEvent; + +import dwt.dwthelper.utils; + +/** + *

+ * This interface is used to listen to notifications from an IOperationHistory. + * The supplied OperationHistoryEvent describes the particular notification. + *

+ *

+ * Operation history listeners must be prepared to receive notifications from a + * background thread. Any UI access occurring inside the implementation must be + * properly synchronized using the techniques specified by the client's widget + * library. + *

+ * + * @since 3.1 + */ +public interface IOperationHistoryListener { + /** + * Something of note has happened in the IOperationHistory. Listeners should + * check the supplied event for details. + * + * @param event + * the OperationHistoryEvent that describes the particular + * notification. + */ + void historyNotification(OperationHistoryEvent event); + +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/operations/IUndoContext.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/operations/IUndoContext.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,61 @@ +/******************************************************************************* + * Copyright (c) 2005 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.commands.operations.IUndoContext; + +import dwt.dwthelper.utils; + +/** + *

+ * An undo context is used to "tag" operations as being applicable to a certain + * context. The undo context is used to filter the history of operations + * available for undo or redo so that only operations appropriate for a given + * undo context are shown when the application is presenting that context. + *

+ *

+ * The scope of an undo context is defined by the application that is making + * undo and redo of operations available. Undo contexts may be related to + * application models, or may be associated with UI objects that are providing + * undo and redo support. + *

+ *

+ * An undo context may be defined as "matching" another context. This allows + * applications to provide specialized implementations of an undo context that + * will appear in the operation history for their matching context. + * + * @since 3.1 + */ + +public interface IUndoContext { + + /** + * Get the label that describes the undo context. + * + * @return the label for the context. + */ + public String getLabel(); + + /** + * Return whether the specified context is considered a match for the + * receiving context. When a context matches another context, operations + * that have the context are considered to also have the matching context. + * + * @param context + * the context to be checked against the receiving context. + * + * @return true if the receiving context can be considered a + * match for the specified context, and false if it + * cannot. + */ + public bool matches(IUndoContext context); + +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/operations/IUndoableOperation.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/operations/IUndoableOperation.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,245 @@ +/******************************************************************************* + * Copyright (c) 2005 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.commands.operations.IUndoableOperation; + +import dwtx.core.commands.ExecutionException; +import dwtx.core.runtime.IAdaptable; +import dwtx.core.runtime.IProgressMonitor; +import dwtx.core.runtime.IStatus; + +import dwtx.core.commands.operations.IUndoContext; + +import dwt.dwthelper.utils; + +/** + *

+ * IUndoableOperation defines an operation that can be executed, undone, and + * redone. Operations typically have fully defined parameters. That is, they are + * usually created after the user has been queried for any input needed to + * define the operation. + *

+ *

+ * Operations determine their ability to execute, undo, or redo according to the + * current state of the application. They do not make decisions about their + * validity based on where they occur in the operation history. That is left to + * the particular operation history. + *

+ * + * @since 3.1 + */ +public interface IUndoableOperation { + + /** + *

+ * Add the specified context to the operation. If a context equal to the + * specified context is already present, do not add it again. Note that + * determining whether a context is already present is based on equality, + * not whether the context matches ({@link IUndoContext#matches(IUndoContext)}) + * another context. + *

+ * + * @param context + * the context to be added + */ + void addContext(IUndoContext context); + + /** + *

+ * Returns whether the operation can be executed in its current state. + *

+ * + *

+ * Note: The computation for this method must be fast, as it is called + * frequently. If necessary, this method can be optimistic in its + * computation (returning true) and later perform more time-consuming + * computations during the actual execution of the operation, returning the + * appropriate status if the operation cannot actually execute at that time. + *

+ * + * @return true if the operation can be executed; + * false otherwise. + */ + bool canExecute(); + + /** + *

+ * Returns whether the operation can be redone in its current state. + *

+ * + *

+ * Note: The computation for this method must be fast, as it is called + * frequently. If necessary, this method can be optimistic in its + * computation (returning true) and later perform more time-consuming + * computations during the actual redo of the operation, returning the + * appropriate status if the operation cannot actually be redone at that + * time. + *

+ * + * @return true if the operation can be redone; + * false otherwise. + */ + bool canRedo(); + + /** + *

+ * Returns whether the operation can be undone in its current state. + *

+ * + *

+ * Note: The computation for this method must be fast, as it is called + * frequently. If necessary, this method can be optimistic in its + * computation (returning true) and later perform more time-consuming + * computations during the actual undo of the operation, returning the + * appropriate status if the operation cannot actually be undone at that + * time. + *

+ * + * @return true if the operation can be undone; + * false otherwise. + */ + bool canUndo(); + + /** + * Dispose of the operation. This method is used when the operation is no + * longer kept in the history. Implementers of this method typically + * unregister any listeners. + * + */ + void dispose(); + + /** + * Execute the operation. This method should only be called the first time + * that an operation is executed. + * + * @param monitor + * the progress monitor (or null) to use for + * reporting progress to the user. + * @param info + * the IAdaptable (or null) provided by the + * caller in order to supply UI information for prompting the + * user if necessary. When this parameter is not + * null, it should minimally contain an adapter + * for the dwt.widgets.Shell.class. + * + * @return the IStatus of the execution. The status severity should be set + * to OK if the operation was successful, and + * ERROR if it was not. Any other status is assumed + * to represent an incompletion of the execution. + * @throws ExecutionException + * if an exception occurred during execution. + */ + IStatus execute(IProgressMonitor monitor, IAdaptable info); + + /** + *

+ * Returns the array of contexts that have been assigned to the operation. + *

+ *

+ * This method may be called by the operation history from inside a + * synchronized block. To avoid deadlock conditions, implementers of this + * method must avoid dispatching and waiting on threads that modify the + * operation history during this method. + *

+ * + * @return the array of contexts + */ + IUndoContext[] getContexts(); + + /** + * Return the label that should be used to show the name of the operation to + * the user. This label is typically combined with the command strings shown + * to the user in "Undo" and "Redo" user interfaces. + * + * @return the label + */ + String getLabel(); + + /** + *

+ * Returns whether the operation has a matching context for the specified + * context. + *

+ *

+ * This method may be called by the operation history from inside a + * synchronized block. To avoid deadlock conditions, implementers of this + * method must avoid dispatching and waiting on threads that modify the + * operation history during this method. + *

+ * + * @see IUndoContext#matches(IUndoContext) + * + * @param context + * the context in question + * @return true if the context is present, false + * if it is not. + */ + bool hasContext(IUndoContext context); + + /** + * Redo the operation. This method should only be called after an operation + * has been undone. + * + * @param monitor + * the progress monitor (or null) to use for + * reporting progress to the user. + * @param info + * the IAdaptable (or null) provided by the + * caller in order to supply UI information for prompting the + * user if necessary. When this parameter is not + * null, it should minimally contain an adapter + * for the dwt.widgets.Shell.class. + * @return the IStatus of the redo. The status severity should be set to + * OK if the redo was successful, and + * ERROR if it was not. Any other status is assumed + * to represent an incompletion of the redo. + * @throws ExecutionException + * if an exception occurred during redo. + */ + + IStatus redo(IProgressMonitor monitor, IAdaptable info); + + /** + * Remove the specified context from the operation. This method has no + * effect if the context is not equal to another context in the context + * list. Note that determining whether a context is present when removing it + * is based on equality, not whether the context matches ({@link + * IUndoContext#matches(IUndoContext)}) another context. + * + * @param context + * the context to be removed + */ + void removeContext(IUndoContext context); + + /** + * Undo the operation. This method should only be called after an operation + * has been executed. + * + * @param monitor + * the progress monitor (or null) to use for + * reporting progress to the user. + * @param info + * the IAdaptable (or null) provided by the + * caller in order to supply UI information for prompting the + * user if necessary. When this parameter is not + * null, it should minimally contain an adapter + * for the dwt.widgets.Shell.class. + * @return the IStatus of the undo. The status severity should be set to + * OK if the redo was successful, and + * ERROR if it was not. Any other status is assumed + * to represent an incompletion of the undo. + * @throws ExecutionException + * if an exception occurred during undo. + */ + IStatus undo(IProgressMonitor monitor, IAdaptable info); + +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/operations/LinearUndoEnforcer.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/operations/LinearUndoEnforcer.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,63 @@ +/******************************************************************************* + * Copyright (c) 2005 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.commands.operations.LinearUndoEnforcer; + +import dwtx.core.runtime.IAdaptable; +import dwtx.core.runtime.IStatus; +import dwtx.core.runtime.Status; + +import dwtx.core.commands.operations.LinearUndoViolationDetector; +import dwtx.core.commands.operations.IUndoableOperation; +import dwtx.core.commands.operations.IUndoContext; +import dwtx.core.commands.operations.IOperationHistory; + +import dwt.dwthelper.utils; + +/** + *

+ * An operation approver that enforces a strict linear undo. It does not allow + * the undo or redo of any operation that is not the latest available operation + * in all of its undo contexts. This class may be instantiated by clients. + *

+ * + * @since 3.1 + */ +public final class LinearUndoEnforcer : LinearUndoViolationDetector { + /** + * Create an instance of LinearUndoEnforcer. + */ + public this() { + super(); + } + + /* + * Return whether a linear redo violation is allowable. A linear redo violation + * is defined as a request to redo a particular operation even if it is not the most + * recently added operation to the redo history. + */ + protected IStatus allowLinearRedoViolation(IUndoableOperation operation, + IUndoContext context, IOperationHistory history, IAdaptable uiInfo) { + return Status.CANCEL_STATUS; + } + + /* + * Return whether a linear undo violation is allowable. A linear undo violation + * is defined as a request to undo a particular operation even if it is not the most + * recently added operation to the undo history. + */ + protected IStatus allowLinearUndoViolation(IUndoableOperation operation, + IUndoContext context, IOperationHistory history, IAdaptable uiInfo) { + return Status.CANCEL_STATUS; + } + +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/operations/LinearUndoViolationDetector.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/operations/LinearUndoViolationDetector.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,150 @@ +/******************************************************************************* + * Copyright (c) 2005, 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.commands.operations.LinearUndoViolationDetector; + +import dwtx.core.runtime.IAdaptable; +import dwtx.core.runtime.IStatus; +import dwtx.core.runtime.Status; + +import dwtx.core.commands.operations.IOperationApprover; +import dwtx.core.commands.operations.IOperationHistory; +import dwtx.core.commands.operations.IUndoableOperation; +import dwtx.core.commands.operations.IUndoContext; + +import dwt.dwthelper.utils; + +/** + *

+ * An abstract class for detecting violations in a strict linear undo/redo + * model. Once a violation is detected, subclasses implement the specific + * behavior for indicating whether or not the undo/redo should proceed. + *

+ * + * @since 3.1 + */ +public abstract class LinearUndoViolationDetector : IOperationApprover { + + /** + * Create an instance of LinearUndoViolationDetector. + */ + public this() { + } + + /** + * Return a status indicating whether a linear redo violation is allowable. + * A linear redo violation is defined as a request to redo a particular + * operation even if it is not the most recently added operation to the redo + * history. + * + * @param operation + * the operation for which a linear redo violation has been + * detected. + * @param context + * the undo context in which the linear redo violation exists + * @param history + * the operation history containing the operation + * @param info + * the IAdaptable (or null) provided by the + * caller in order to supply UI information for prompting the + * user if necessary. When this parameter is not + * null, it should minimally contain an adapter + * for the dwt.widgets.Shell.class. + * + * @return the IStatus describing whether the redo violation is allowed. The + * redo will not proceed if the status severity is not + * OK, and the caller requesting the redo will be + * returned the status that caused the rejection. Specific status + * severities will not be interpreted by the history. + */ + + protected abstract IStatus allowLinearRedoViolation( + IUndoableOperation operation, IUndoContext context, + IOperationHistory history, IAdaptable info); + + /** + * Return a status indicating whether a linear undo violation is allowable. + * A linear undo violation is defined as a request to undo a particular + * operation even if it is not the most recently added operation to the undo + * history. + * + * @param operation + * the operation for which a linear undo violation has been + * detected. + * @param context + * the undo context in which the linear undo violation exists + * @param history + * the operation history containing the operation + * @param info + * the IAdaptable (or null) provided by the + * caller in order to supply UI information for prompting the + * user if necessary. When this parameter is not + * null, it should minimally contain an adapter + * for the dwt.widgets.Shell.class. + * + * @return the IStatus describing whether the undo violation is allowed. The + * undo will not proceed if the status severity is not + * OK, and the caller requesting the undo will be + * returned the status that caused the rejection. Specific status + * severities will not be interpreted by the history. + */ + protected abstract IStatus allowLinearUndoViolation( + IUndoableOperation operation, IUndoContext context, + IOperationHistory history, IAdaptable info); + + /* + * (non-Javadoc) + * + * @see dwtx.core.commands.operations.IOperationApprover#proceedRedoing(dwtx.core.commands.operations.IUndoableOperation, + * dwtx.core.commands.operations.IOperationHistory, + * dwtx.core.runtime.IAdaptable) + */ + public final IStatus proceedRedoing(IUndoableOperation operation, + IOperationHistory history, IAdaptable info) { + IUndoContext[] contexts = operation.getContexts(); + for (int i = 0; i < contexts.length; i++) { + IUndoContext context = contexts[i]; + if (history.getRedoOperation(context) !is operation) { + IStatus status = allowLinearRedoViolation(operation, context, + history, info); + if (!status.isOK()) { + return status; + } + } + } + return Status.OK_STATUS; + } + + /* + * (non-Javadoc) + * + * @see dwtx.core.commands.operations.IOperationApprover#proceedUndoing(dwtx.core.commands.operations.IUndoableOperation, + * dwtx.core.commands.operations.IOperationHistory, + * dwtx.core.runtime.IAdaptable) + */ + + public final IStatus proceedUndoing(IUndoableOperation operation, + IOperationHistory history, IAdaptable info) { + IUndoContext[] contexts = operation.getContexts(); + for (int i = 0; i < contexts.length; i++) { + IUndoContext context = contexts[i]; + if (history.getUndoOperation(context) !is operation) { + IStatus status = allowLinearUndoViolation(operation, context, + history, info); + if (!status.isOK()) { + return status; + } + } + } + return Status.OK_STATUS; + } +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/operations/ObjectUndoContext.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/operations/ObjectUndoContext.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,148 @@ +/******************************************************************************* + * Copyright (c) 2005, 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.commands.operations.ObjectUndoContext; + +import tango.util.collection.ArraySeq; +import tango.util.collection.model.Seq; + +import dwtx.core.commands.operations.UndoContext; +import dwtx.core.commands.operations.IUndoContext; + +import dwt.dwthelper.utils; + +/** + *

+ * An undo context that can be used to represent any given object. Clients + * can add matching contexts to this context. This class may be instantiated + * by clients. + *

+ * + * @since 3.1 + */ +public final class ObjectUndoContext : UndoContext { + + private Object object; + + private String label; + + private Seq!(IUndoContext) children; + + /** + * Construct an operation context that represents the given object. + * + * @param object + * the object to be represented. + */ + public this(Object object) { + this(object, null); + } + + /** + * Construct an operation context that represents the given object and has a + * specialized label. + * + * @param object + * the object to be represented. + * @param label + * the label for the context + */ + public this(Object object, String label) { + this.object = object; + this.label = label; + children = new ArraySeq!(IUndoContext); + } + + /* + * (non-Javadoc) + * + * @see dwtx.core.commands.operations.IUndoContext#getLabel() + */ + public String getLabel() { + if (label !is null) { + return label; + } + if (object !is null) { + return object.toString(); + } + return super.getLabel(); + } + + /** + * Return the object that is represented by this context. + * + * @return the object represented by this context. + */ + public Object getObject() { + return object; + } + + /** + * Add the specified context as a match of this context. Contexts added as + * matches of this context will be interpreted as a match of this context + * when the history is filtered for a particular context. Adding a match + * allows components to create their own contexts for implementing + * specialized behavior, yet have their operations appear in a more + * global context. + * + * @param context + * the context to be added as a match of this context + */ + public void addMatch(IUndoContext context) { + children.append(context); + } + + /** + * Remove the specified context as a match of this context. The context will + * no longer be interpreted as a match of this context when the history is + * filtered for a particular context. This method has no effect if the + * specified context was never previously added as a match. + * + * @param context + * the context to be removed from the list of matches for this + * context + */ + public void removeMatch(IUndoContext context) { + children.remove(context); + } + + /* + * (non-Javadoc) + * + * @see dwtx.core.commands.operations.IUndoContext#matches(IUndoContext + * context) + */ + public bool matches(IUndoContext context) { + // Check first for explicit matches that have been assigned. + if (children.contains(context)) { + return true; + } + // Contexts for equal objects are considered matching + if ( null !is cast(ObjectUndoContext)context && getObject() !is null) { + return getObject().opEquals((cast(ObjectUndoContext)context).getObject()) !is 0; + } + // Use the normal matching implementation + return super.matches(context); + } + + /** + * The string representation of this operation. Used for debugging purposes only. + * This string should not be shown to an end user. + * + * @return The string representation. + */ + public override String toString() { + return getLabel(); + } + + +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/operations/OperationHistoryEvent.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/operations/OperationHistoryEvent.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,242 @@ +/******************************************************************************* + * Copyright (c) 2005, 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.commands.operations.OperationHistoryEvent; + +import dwtx.core.runtime.IStatus; +import dwtx.core.commands.operations.IOperationHistory; +import dwtx.core.commands.operations.IUndoableOperation; + +import dwt.dwthelper.utils; + +/** + *

+ * OperationHistoryEvent is used to communicate changes that occur in a + * DefaultOperationHistory, including the addition or removal of operations, and + * the execution, undo, and redo of operations. + *

+ *

+ * Operation history listeners must be prepared to receive notifications from a + * background thread. Any UI access occurring inside the implementation must be + * properly synchronized using the techniques specified by the client's widget + * library. + *

+ * + * + * @since 3.1 + */ +public final class OperationHistoryEvent { + + /** + * ABOUT_TO_EXECUTE indicates that an operation is about to execute. + * Listeners should prepare for the execution as appropriate. Listeners will + * receive a DONE notification if the operation is successful, or an + * OPERATION_NOT_OK notification if the execution is cancelled or otherwise + * fails. This notification is only received for those operations executed + * by the operation history. Operations that are added to the history after + * execution do not trigger these notifications. + * + * If the operation successfully executes, clients will also receive a + * notification that it has been added to the history. + * + * (value is 1). + */ + + public static const int ABOUT_TO_EXECUTE = 1; + + /** + * ABOUT_TO_REDO indicates that an operation is about to be redone. + * Listeners should prepare for the redo as appropriate. Listeners will + * receive a REDONE notification if the operation is successful, or an + * OPERATION_NOT_OK notification if the redo is cancelled or otherwise + * fails. + * + * (value is 2). + */ + public static const int ABOUT_TO_REDO = 2; + + /** + * ABOUT_TO_UNDO indicates that an operation is about to be undone. + * Listeners should prepare for the undo as appropriate. Listeners will + * receive an UNDONE notification if the operation is successful, or an + * OPERATION_NOT_OK notification if the undo is cancelled or otherwise + * fails. + * + * (value is 3). + */ + public static const int ABOUT_TO_UNDO = 3; + + /** + * DONE indicates that an operation has been executed. Listeners can take + * appropriate action, such as revealing any relevant state in the UI. This + * notification is only received for those operations executed by the + * operation history. Operations that are added to the history after + * execution do not trigger this notification. + * + * Clients will also receive a notification that the operation has been + * added to the history. + * + * (value is 4). + */ + public static const int DONE = 4; + + /** + * OPERATION_ADDED indicates that an operation was added to the history. + * Listeners can use this notification to add their undo context to a new + * operation as appropriate or otherwise record the operation. + * + * (value is 5). + */ + public static const int OPERATION_ADDED = 5; + + /** + * OPERATION_CHANGED indicates that an operation has changed in some way + * since it was added to the operations history. + * + * (value is 6). + */ + public static const int OPERATION_CHANGED = 6; + + /** + * OPERATION_NOT_OK indicates that an operation was attempted and not + * successful. Listeners typically use this when they have prepared for an + * execute, undo, or redo, and need to know that the operation did not + * successfully complete. For example, listeners that turn redraw off before + * an operation is undone would turn redraw on when the operation completes, + * or when this notification is received, since there will be no + * notification of the completion. + * + * (value is 7). + */ + public static const int OPERATION_NOT_OK = 7; + + /** + * OPERATION_REMOVED indicates an operation was removed from the history. + * Listeners typically remove any record of the operation that they may have + * kept in their own state. The operation has been disposed by the time + * listeners receive this notification. + * + * (value is 8). + */ + public static const int OPERATION_REMOVED = 8; + + /** + * REDONE indicates that an operation was redone. Listeners can take + * appropriate action, such as revealing any relevant state in the UI. + * + * (value is 9). + */ + public static const int REDONE = 9; + + /** + * UNDONE indicates that an operation was undone. Listeners can take + * appropriate action, such as revealing any relevant state in the UI. + * + * (value is 10). + */ + public static const int UNDONE = 10; + + private int code = 0; + + private IOperationHistory history; + + private IUndoableOperation operation; + + /* @since 3.2 */ + private IStatus status; + + /** + * Construct an event for the specified operation history. + * + * @param code + * the event code to be used. + * @param history + * the history triggering the event. + * @param operation + * the operation involved in the event. + */ + public this(int code, IOperationHistory history, + IUndoableOperation operation) { + this(code, history, operation, null); + } + + /** + * Construct an event for the specified operation history. + * + * @param code + * the event code to be used. + * @param history + * the history triggering the event. + * @param operation + * the operation involved in the event. + * @param status + * the status associated with the event, or null if no status is + * available. + * + * @since 3.2 + */ + public this(int code, IOperationHistory history, + IUndoableOperation operation, IStatus status) { + if (history is null) { + throw new NullPointerException(); + } + if (operation is null) { + throw new NullPointerException(); + } + this.code = code; + this.history = history; + this.operation = operation; + this.status = status; + } + + /** + * Return the type of event that is occurring. + * + * @return the type code indicating the type of event. + */ + public int getEventType() { + return code; + } + + /** + * Return the operation history that triggered this event. + * + * @return the operation history + */ + + public IOperationHistory getHistory() { + return history; + } + + /** + * Return the operation associated with this event. + * + * @return the operation + */ + + public IUndoableOperation getOperation() { + return operation; + } + + /** + * Return the status associated with this event. + * + * @return the status associated with this event. The status may be null. + * + * @since 3.2 + */ + + public IStatus getStatus() { + return status; + } + +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/operations/OperationHistoryFactory.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/operations/OperationHistoryFactory.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,73 @@ +/******************************************************************************* + * Copyright (c) 2005 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.commands.operations.OperationHistoryFactory; + +import dwtx.core.commands.operations.IOperationHistory; +import dwtx.core.commands.operations.DefaultOperationHistory; + +import dwt.dwthelper.utils; + +/** + *

+ * This class is used to maintain the instance of the operation history that + * should be used by classes that access the undo or redo history and add + * undoable operations to the history. + * + *

+ * It is intended that an application can create an operation history appropriate + * for its needs and set it into this class. Otherwise, a default operation history + * will be created. The operation history may only be set one time. All classes that + * access an operations history use this class to obtain the correct instance. + * + * @since 3.1 + */ +public final class OperationHistoryFactory { + + private static IOperationHistory operationHistory; + + /** + * Return the operation history to be used for managing undoable operations. + * + * @return the operation history to be used for executing, undoing, and + * redoing operations. + */ + public static IOperationHistory getOperationHistory() { + if (operationHistory is null) { + operationHistory = new DefaultOperationHistory(); + } + return operationHistory; + } + + /** + * Set the operation history to be used for managing undoable operations. + * This method may only be called one time, and must be called before any + * request to get the history. Attempts to set the operation history will + * be ignored after it has been already set, or after a default one has + * been created. + * + * @param history + * the operation history to be used for executing, undoing, and + * redoing operations. + */ + public static void setOperationHistory(IOperationHistory history) { + // If one has already been set or created, ignore this request. + if (operationHistory is null) { + operationHistory = history; + } + } + + private this() { + // may not be instantiated + } + +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/operations/OperationStatus.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/operations/OperationStatus.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,75 @@ +/******************************************************************************* + * Copyright (c) 2005 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.commands.operations.OperationStatus; + +import dwtx.core.runtime.Status; + +import dwt.dwthelper.utils; + +/** + *

+ * OperationStatus describes the status of a request to execute, undo, or redo + * an operation. This class may be instantiated by clients. + *

+ * + * @since 3.1 + */ +public final class OperationStatus : Status { + /** + * NOTHING_TO_REDO indicates there was no operation available for redo. + * + * (value is 1). + */ + public static const int NOTHING_TO_REDO = 1; + + /** + * NOTHING_TO_UNDO indicates there was no operation available for undo. + * + * (value is 2). + */ + public static const int NOTHING_TO_UNDO = 2; + + /** + * OPERATION_INVALID indicates that the operation available for undo or redo + * is not in a state to successfully perform the undo or redo. + * + * (value is 3). + */ + public static const int OPERATION_INVALID = 3; + + /** + * DEFAULT_PLUGIN_ID identifies the default plugin reporting the status. + * + * (value is "dwtx.core.commands"). + */ + static String DEFAULT_PLUGIN_ID = "dwtx.core.commands"; //$NON-NLS-1$ + + /** + * Creates a new operation status, specifying all properties. + * + * @param severity + * the severity for the status + * @param pluginId + * the unique identifier of the relevant plug-in + * @param code + * the informational code for the status + * @param message + * a human-readable message, localized to the current locale + * @param exception + * a low-level exception, or null if not + * applicable + */ + public this(int severity, String pluginId, int code, String message, Exception exception) { + super(severity, pluginId, code, message, exception); + } +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/operations/TriggeredOperations.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/operations/TriggeredOperations.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,465 @@ +/******************************************************************************* + * Copyright (c) 2005, 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.commands.operations.TriggeredOperations; + +import tango.util.collection.ArraySeq; +import tango.util.collection.model.Seq; + +import dwtx.core.commands.ExecutionException; +import dwtx.core.runtime.IAdaptable; +import dwtx.core.runtime.IProgressMonitor; +import dwtx.core.runtime.IStatus; +import dwtx.core.runtime.OperationCanceledException; +import dwtx.core.runtime.Status; + +import dwtx.core.commands.operations.AbstractOperation; +import dwtx.core.commands.operations.ICompositeOperation; +import dwtx.core.commands.operations.IAdvancedUndoableOperation; +import dwtx.core.commands.operations.IContextReplacingOperation; +import dwtx.core.commands.operations.IUndoableOperation; +import dwtx.core.commands.operations.IOperationHistory; +import dwtx.core.commands.operations.IUndoContext; +import dwtx.core.commands.operations.OperationHistoryEvent; + +import dwt.dwthelper.utils; + +/** + * Triggered operations are a specialized implementation of a composite + * operation that keeps track of operations triggered by the execution of some + * primary operation. The composite knows which operation was the trigger for + * subsequent operations, and adds all triggered operations as children. When + * execution, undo, or redo is performed, only the triggered operation is + * executed, undone, or redone if it is still present. If the trigger is removed + * from the triggered operations, then the child operations will replace the + * triggered operations in the history. + *

+ * This class may be instantiated by clients. + *

+ * + * @since 3.1 + */ +public final class TriggeredOperations : AbstractOperation, + ICompositeOperation, IAdvancedUndoableOperation, + IContextReplacingOperation { + + private IUndoableOperation triggeringOperation; + + private IOperationHistory history; + + private Seq!(IUndoableOperation) children; + + /** + * Construct a composite triggered operations using the specified undoable + * operation as the trigger. Use the label of this trigger as the label of + * the operation. + * + * @param operation + * the operation that will trigger other operations. + * @param history + * the operation history containing the triggered operations. + */ + public this(IUndoableOperation operation, + IOperationHistory history) { + super(operation.getLabel()); + children = new ArraySeq!(IUndoableOperation); + triggeringOperation = operation; + recomputeContexts(); + this.history = history; + } + + /* + * (non-Javadoc) + * + * @see dwtx.core.commands.operations.IUndoableOperation#add(dwtx.core.commands.operations.IUndoableOperation) + */ + public void add(IUndoableOperation operation) { + children.append(operation); + recomputeContexts(); + } + + /* + * (non-Javadoc) + * + * @see dwtx.core.commands.operations.IUndoableOperation#remove(dwtx.core.commands.operations.IUndoableOperation) + */ + public void remove(IUndoableOperation operation) { + if (operation is triggeringOperation) { + // the triggering operation is being removed, so we must replace + // this composite with its individual triggers. + triggeringOperation = null; + // save the children before replacing the operation, since this + // operation will be disposed as part of replacing it. We don't want + // the children to be disposed since they are to replace this + // operation. + Seq!(IUndoableOperation) childrenToRestore = children.dup; + children = new ArraySeq!(IUndoableOperation); + recomputeContexts(); + operation.dispose(); + // now replace the triggering operation + history.replaceOperation(this, childrenToRestore.toArray()); + } else { + children.remove(operation); + operation.dispose(); + recomputeContexts(); + } + } + + /** + * Remove the specified context from the receiver. This method is typically + * invoked when the history is being flushed for a certain context. In the + * case of triggered operations, if the only context for the triggering + * operation is being removed, then the triggering operation must be + * replaced in the operation history with the atomic operations that it + * triggered. If the context being removed is not the only context for the + * triggering operation, the triggering operation will remain, and the + * children will each be similarly checked. + * + * @param context + * the undo context being removed from the receiver. + */ + public void removeContext(IUndoContext context) { + + bool recompute = false; + // first check to see if we are removing the only context of the + // triggering operation + if (triggeringOperation !is null + && triggeringOperation.hasContext(context)) { + if (triggeringOperation.getContexts().length is 1) { + remove(triggeringOperation); + return; + } + triggeringOperation.removeContext(context); + recompute = true; + } + // the triggering operation remains, check all the children + auto toBeRemoved = new ArraySeq!(IUndoableOperation); + for (int i = 0; i < children.size(); i++) { + IUndoableOperation child = cast(IUndoableOperation) children.get(i); + if (child.hasContext(context)) { + if (child.getContexts().length is 1) { + toBeRemoved.append(child); + } else { + child.removeContext(context); + } + recompute = true; + } + } + for (int i = 0; i < toBeRemoved.size(); i++) { + remove(cast(IUndoableOperation) toBeRemoved.get(i)); + } + if (recompute) { + recomputeContexts(); + } + } + + /* + * (non-Javadoc) + * + * @see dwtx.core.commands.operations.IUndoableOperation#execute(dwtx.core.runtime.IProgressMonitor, + * dwtx.core.runtime.IAdaptable) + */ + public IStatus execute(IProgressMonitor monitor, IAdaptable info) { + if (triggeringOperation !is null) { + history.openOperation(this, IOperationHistory.EXECUTE); + try { + IStatus status = triggeringOperation.execute(monitor, info); + history.closeOperation(status.isOK(), false, + IOperationHistory.EXECUTE); + return status; + } catch (ExecutionException e) { + history.closeOperation(false, false, IOperationHistory.EXECUTE); + throw e; + } catch (RuntimeException e) { + history.closeOperation(false, false, IOperationHistory.EXECUTE); + throw e; + } + + } + return IOperationHistory.OPERATION_INVALID_STATUS; + } + + /* + * (non-Javadoc) + * + * @see dwtx.core.commands.operations.IUndoableOperation#redo(dwtx.core.runtime.IProgressMonitor, + * dwtx.core.runtime.IAdaptable) + */ + public IStatus redo(IProgressMonitor monitor, IAdaptable info) { + if (triggeringOperation !is null) { + history.openOperation(this, IOperationHistory.REDO); + Seq!(IUndoableOperation) childrenToRestore = children.dup; + try { + removeAllChildren(); + IStatus status = triggeringOperation.redo(monitor, info); + if (!status.isOK()) { + children = childrenToRestore; + } + history.closeOperation(status.isOK(), false, + IOperationHistory.REDO); + return status; + } catch (ExecutionException e) { + children = childrenToRestore; + history.closeOperation(false, false, IOperationHistory.REDO); + throw e; + } catch (RuntimeException e) { + children = childrenToRestore; + history.closeOperation(false, false, IOperationHistory.REDO); + throw e; + } + } + return IOperationHistory.OPERATION_INVALID_STATUS; + } + + /* + * (non-Javadoc) + * + * @see dwtx.core.commands.operations.IUndoableOperation#undo(dwtx.core.runtime.IProgressMonitor, + * dwtx.core.runtime.IAdaptable) + */ + public IStatus undo(IProgressMonitor monitor, IAdaptable info) { + if (triggeringOperation !is null) { + history.openOperation(this, IOperationHistory.UNDO); + auto childrenToRestore = children.dup; + try { + removeAllChildren(); + IStatus status = triggeringOperation.undo(monitor, info); + if (!status.isOK()) { + children = childrenToRestore; + } + history.closeOperation(status.isOK(), false, + IOperationHistory.UNDO); + return status; + } catch (ExecutionException e) { + children = childrenToRestore; + history.closeOperation(false, false, IOperationHistory.UNDO); + throw e; + } catch (RuntimeException e) { + children = childrenToRestore; + history.closeOperation(false, false, IOperationHistory.UNDO); + throw e; + } + } + return IOperationHistory.OPERATION_INVALID_STATUS; + } + + /* + * (non-Javadoc) + * + * @see dwtx.core.commands.operations.IUndoableOperation#canUndo() + */ + public bool canUndo() { + if (triggeringOperation !is null) { + return triggeringOperation.canUndo(); + } + return false; + } + + /* + * (non-Javadoc) + * + * @see dwtx.core.commands.operations.IUndoableOperation#canExecute() + */ + public bool canExecute() { + if (triggeringOperation !is null) { + return triggeringOperation.canExecute(); + } + return false; + } + + /* + * (non-Javadoc) + * + * @see dwtx.core.commands.operations.IUndoableOperation#canRedo() + */ + public bool canRedo() { + if (triggeringOperation !is null) { + return triggeringOperation.canRedo(); + } + return false; + } + + /* + * Dispose all operations in the receiver. + */ + public void dispose() { + for (int i = 0; i < children.size(); i++) { + children.get(i).dispose(); + } + if (triggeringOperation !is null) { + triggeringOperation.dispose(); + } + } + + /* + * Recompute contexts in light of some change in the children + */ + private void recomputeContexts() { + auto allContexts = new ArraySeq!(IUndoContext); + if (triggeringOperation !is null) { + IUndoContext[] contexts = triggeringOperation.getContexts(); + for (int i = 0; i < contexts.length; i++) { + allContexts.append(contexts[i]); + } + } + for (int i = 0; i < children.size(); i++) { + IUndoContext[] contexts = children.get(i).getContexts(); + for (int j = 0; j < contexts.length; j++) { + if (!allContexts.contains(contexts[j])) { + allContexts.append(contexts[j]); + } + } + } + contexts = allContexts; + + } + + /* + * Remove all non-triggering children + */ + private void removeAllChildren() { + IUndoableOperation[] nonTriggers = children.toArray(); + for (int i = 0; i < nonTriggers.length; i++) { + children.remove(nonTriggers[i]); + nonTriggers[i].dispose(); + } + } + + /** + * Return the operation that triggered the other operations in this + * composite. + * + * @return the IUndoableOperation that triggered the other children. + */ + public IUndoableOperation getTriggeringOperation() { + return triggeringOperation; + } + + /* + * (non-Javadoc) + * + * @see dwtx.core.commands.operations.IAdvancedModelOperation#getAffectedObjects() + */ + public Object[] getAffectedObjects() { + if ( auto trg = cast(IAdvancedUndoableOperation)triggeringOperation ) { + return trg + .getAffectedObjects(); + } + return null; + } + + /* + * (non-Javadoc) + * + * @see dwtx.core.commands.operations.IAdvancedModelOperation#aboutToNotify(dwtx.core.commands.operations.OperationHistoryEvent) + */ + public void aboutToNotify(OperationHistoryEvent event) { + if ( auto trg = cast(IAdvancedUndoableOperation)triggeringOperation ) { + trg.aboutToNotify(event); + } + } + + /* + * (non-Javadoc) + * + * @see dwtx.core.commands.operations.IAdvancedUndoableOperation#computeUndoableStatus(dwtx.core.runtime.IProgressMonitor) + */ + public IStatus computeUndoableStatus(IProgressMonitor monitor) { + if ( auto trg = cast(IAdvancedUndoableOperation)triggeringOperation ) { + try { + return trg.computeUndoableStatus(monitor); + } catch (OperationCanceledException e) { + return Status.CANCEL_STATUS; + } + } + return Status.OK_STATUS; + + } + + /* + * (non-Javadoc) + * + * @see dwtx.core.commands.operations.IAdvancedUndoableOperation#computeRedoableStatus(dwtx.core.runtime.IProgressMonitor) + */ + public IStatus computeRedoableStatus(IProgressMonitor monitor) { + if ( auto trg = cast(IAdvancedUndoableOperation)triggeringOperation ) { + try { + return trg.computeRedoableStatus(monitor); + } catch (OperationCanceledException e) { + return Status.CANCEL_STATUS; + } + } + return Status.OK_STATUS; + + } + + /** + * Replace the undo context of the receiver with the provided replacement + * undo context. In the case of triggered operations, all contained + * operations are checked and any occurrence of the original context is + * replaced with the new undo context. + *

+ * This message has no effect if the original undo context is not present in + * the receiver. + * + * @param original + * the undo context which is to be replaced + * @param replacement + * the undo context which is replacing the original + * @since 3.2 + */ + public void replaceContext(IUndoContext original, IUndoContext replacement) { + + // first check the triggering operation + if (triggeringOperation !is null + && triggeringOperation.hasContext(original)) { + if ( auto trg = cast(IContextReplacingOperation)triggeringOperation ) { + trg.replaceContext(original, replacement); + } else { + triggeringOperation.removeContext(original); + triggeringOperation.addContext(replacement); + } + } + // Now check all the children + for (int i = 0; i < children.size(); i++) { + IUndoableOperation child = children.get(i); + if (child.hasContext(original)) { + if ( auto c = cast(IContextReplacingOperation)child ) { + c.replaceContext( + original, replacement); + } else { + child.removeContext(original); + child.addContext(replacement); + } + } + } + recomputeContexts(); + } + + /** + * Add the specified context to the operation. Overridden in + * TriggeredOperations to add the specified undo context to the triggering + * operation. + * + * @param context + * the context to be added + * + * @since 3.2 + */ + public void addContext(IUndoContext context) { + if (triggeringOperation !is null) { + triggeringOperation.addContext(context); + recomputeContexts(); + } + } + +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/operations/UndoContext.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/operations/UndoContext.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,61 @@ +/******************************************************************************* + * Copyright (c) 2005 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.commands.operations.UndoContext; + +import dwtx.core.commands.operations.IUndoContext; + +import dwt.dwthelper.utils; + +/** + *

+ * A simple, lightweight undo context that can be used to tag any operation. It + * does not provided a specialized label. This class may be instantiated by + * clients. This class may also be subclassed. + *

+ * + * @since 3.1 + */ +public class UndoContext : IUndoContext { + + /** + *

+ * Get the label that describes the undo context. The default implementation + * returns the empty String. Subclasses may override. + *

+ * + * @return the label for the context. + */ + public String getLabel() { + return ""; //$NON-NLS-1$ + } + + /** + *

+ * Return whether the specified context is considered a match for the + * receiving context. When a context matches another context, operations + * that have the context are considered to also have the matching context. + * The default implementation checks whether the supplied context is + * identical to this context. Subclasses may override. + *

+ * + * @param context + * the context to be checked against the receiving context. + * + * @return true if the receiving context can be considered a + * match for the specified context, and false if it + * cannot. + */ + public bool matches(IUndoContext context) { + return context is this; + } +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/commands/util/Tracing.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/commands/util/Tracing.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,76 @@ +/******************************************************************************* + * Copyright (c) 2005, 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ + +module dwtx.core.commands.util.Tracing; + +import dwt.dwthelper.utils; +import tango.util.log.Trace; + +static import tango.text.Text; +alias tango.text.Text.Text!(char) StringBuffer; + +/** + *

+ * A utility class for printing tracing output to the console. + *

+ *

+ * Clients must not extend or instantiate this class. + *

+ * + * @since 3.2 + */ +public final class Tracing { + + /** + * The separator to place between the component and the message. + */ + public static const String SEPARATOR = " >>> "; //$NON-NLS-1$ + + /** + *

+ * Prints a tracing message to standard out. The message is prefixed by a + * component identifier and some separator. See the example below. + *

+ * + *
+     *        BINDINGS >> There are 4 deletion markers
+     * 
+ * + * @param component + * The component for which this tracing applies; may be + * null + * @param message + * The message to print to standard out; may be null. + */ + public static final void printTrace(String component, + String message) { + StringBuffer buffer = new StringBuffer(); + if (component.length !is 0) { + buffer.append(component); + } + if ((component.length !is 0) && (message.length !is 0)) { + buffer.append(SEPARATOR); + } + if (message.length !is 0) { + buffer.append(message); + } + Trace.formatln( "{}", buffer.toString()); + } + + /** + * This class is not intended to be instantiated. + */ + private this() { + // Do nothing. + } +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/internal/commands/operations/GlobalUndoContext.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/internal/commands/operations/GlobalUndoContext.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,42 @@ +/******************************************************************************* + * Copyright (c) 2005 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.internal.commands.operations.GlobalUndoContext; + +import dwtx.core.commands.operations.IUndoContext; + +import dwt.dwthelper.utils; + +/** + *

+ * An operation context that matches to any context. It can be used to + * get an unfiltered (global) history. + *

+ * + * @since 3.1 + */ +public class GlobalUndoContext : IUndoContext { + + /* (non-Javadoc) + * @see dwtx.core.commands.operations.IUndoContext#getLabel() + */ + public String getLabel() { + return "Global Undo Context"; //$NON-NLS-1$ + } + + /* (non-Javadoc) + * @see dwtx.core.commands.operations.IUndoContext#matches(IUndoContext context) + */ + public bool matches(IUndoContext context) { + return true; + } +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/internal/commands/util/Util.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/internal/commands/util/Util.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,395 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ + +module dwtx.core.internal.commands.util.Util; + +import dwt.dwthelper.utils; + +// import java.util.Collections; +// import java.util.HashMap; +// import java.util.HashSet; +// import java.util.Iterator; +// import java.util.Map; +// import java.util.Set; +// import java.util.SortedMap; +// import java.util.SortedSet; +// import java.util.TreeMap; +// import java.util.TreeSet; + +/** + * A class providing utility functions for the commands plug-in. + * + * @since 3.1 + */ +public final class Util { +/++ + /** + * A shared, unmodifiable, empty, sorted map. This value is guaranteed to + * always be the same. + */ + public const static SortedMap EMPTY_SORTED_MAP = Collections + .unmodifiableSortedMap(new TreeMap()); + + /** + * A shared, unmodifiable, empty, sorted set. This value is guaranteed to + * always be the same. + */ + public final static SortedSet EMPTY_SORTED_SET = Collections + .unmodifiableSortedSet(new TreeSet()); +++/ + /** + * A shared, zero-length string -- for avoiding non-externalized string + * tags. This value is guaranteed to always be the same. + */ + public final static String ZERO_LENGTH_STRING = "\0"[ 0 .. 0 ]; //$NON-NLS-1$ +/++ + + /** + * Asserts the the given object is an instance of the given class -- + * optionally allowing the object to be null. + * + * @param object + * The object for which the type should be checked. + * @param c + * The class that the object must be; fails if the class is + * null. + * @param allowNull + * Whether the object being null will not cause a + * failure. + */ + public static final void assertInstance(final Object object, final Class c, + final bool allowNull) { + if (object is null && allowNull) { + return; + } + + if (object is null || c is null) { + throw new NullPointerException(); + } else if (!c.isInstance(object)) { + throw new IllegalArgumentException(); + } + } +++/ + /** + * Compares two bool values. false is considered to be + * less than true. + * + * @param left + * The left value to compare. + * @param right + * The right value to compare. + * @return -1 if left is false + * and right is true;0 + * if they are equal; 1 if left is + * true and right is + * false + */ + public static final int compare(bool left, bool right) { + return left is false ? (right is true ? -1 : 0) : (right is true ? 0 + : 1); + } + + /** + * Compares two comparable objects, but with protection against + * null. + * + * @param left + * The left value to compare; may be null. + * @param right + * The right value to compare; may be null. + * @return -1 if left is null + * and right is not null; + * 0 if they are both null; + * 1 if left is not null + * and right is null. Otherwise, the + * result of left.compareTo(right). + */ + public static final int compare(Comparable left, + Comparable right) { + if (left is null && right is null) { + return 0; + } else if (left is null) { + return -1; + } else if (right is null) { + return 1; + } else { + return left.compareTo( cast(Object)right); + } + } + + /** + * Compares two integer values. This method fails if the distance between + * left and right is greater than + * Integer.MAX_VALUE. + * + * @param left + * The left value to compare. + * @param right + * The right value to compare. + * @return left - right + */ + public static final int compare(int left, int right) { + return left - right; + } + + /** + * Compares two objects that are not otherwise comparable. If neither object + * is null, then the string representation of each object is + * used. + * + * @param left + * The left value to compare. The string representation of this + * value must not be null. + * @param right + * The right value to compare. The string representation of this + * value must not be null. + * @return -1 if left is null + * and right is not null; + * 0 if they are both null; + * 1 if left is not null + * and right is null. Otherwise, the + * result of + * left.toString().compareTo(right.toString()). + */ + public static final int compare(Object left, Object right) { + if (left is null && right is null) { + return 0; + } else if (left is null) { + return -1; + } else if (right is null) { + return 1; + } else { + return left.toString() < (right.toString()); + } + } + public static final int compare(Object[] left, Object[] right) { + if (left.length !is right.length ) { + return left.length < right.length; + } + for( int i = 0; i < left.length; i++ ){ + int res = left[i].opCmp(right[i]); + if( res !is 0 ){ + return res; + } + } + return 0; + } + public static final int compare(String left, String right) { + return left < right; + } + + /** + * Decides whether two booleans are equal. + * + * @param left + * The first bool to compare; may be null. + * @param right + * The second bool to compare; may be null. + * @return true if the booleans are equal; false + * otherwise. + */ + public static bool equals(bool left, bool right) { + return left is right; + } + + /** + * Decides whether two objects are equal -- defending against + * null. + * + * @param left + * The first object to compare; may be null. + * @param right + * The second object to compare; may be null. + * @return true if the objects are equals; false + * otherwise. + */ + public static bool equals(Object left, Object right) { + return left is null ? right is null : ((right !is null) && (left + .opEquals(right)) !is 0 ); + } + public static bool equals(String left, String right) { + return left == right; + } + public static bool equals(String[] left, String[] right) { + if( left.length !is right.length ){ + return false; + } + for( int i = 0; i < left.length; i++ ){ + if( !equals( left[i], right[i] )){ + return false; + } + } + return true; + } + + /** + * Tests whether two arrays of objects are equal to each other. The arrays + * must not be null, but their elements may be + * null. + * + * @param leftArray + * The left array to compare; may be null, and + * may be empty and may contain null elements. + * @param rightArray + * The right array to compare; may be null, and + * may be empty and may contain null elements. + * @return true if the arrays are equal length and the + * elements at the same position are equal; false + * otherwise. + */ + public static bool equals(Object[] leftArray, + Object[] rightArray) { + if (leftArray is null) { + return (rightArray is null); + } else if (rightArray is null) { + return false; + } + + if (leftArray.length !is rightArray.length) { + return false; + } + + for (int i = 0; i < leftArray.length; i++) { + Object left = leftArray[i]; + Object right = rightArray[i]; + bool equal = (left is null) ? (right is null) : (left + .opEquals(right) !is 0); + if (!equal) { + return false; + } + } + + return true; + } + + /** + * Computes the hash code for an integer. + * + * @param i + * The integer for which a hash code should be computed. + * @return i. + */ + public static final hash_t toHash(int i) { + return i; + } + + /** + * Computes the hash code for an object, but with defense against + * null. + * + * @param object + * The object for which a hash code is needed; may be + * null. + * @return The hash code for object; or 0 if + * object is null. + */ + public static final hash_t toHash( Object object) { + return object !is null ? object.toHash() : 0; + } + public static final hash_t toHash( char[] str) { + return str !is null ? dwt.dwthelper.utils.toHash( str ) : 0; + } +/++ + + /** + * Makes a type-safe copy of the given map. This method should be used when + * a map is crossing an API boundary (i.e., from a hostile plug-in into + * internal code, or vice versa). + * + * @param map + * The map which should be copied; must not be null. + * @param keyClass + * The class that all the keys must be; must not be + * null. + * @param valueClass + * The class that all the values must be; must not be + * null. + * @param allowNullKeys + * Whether null keys should be allowed. + * @param allowNullValues + * Whether null values should be allowed. + * @return A copy of the map; may be empty, but never null. + */ + public static final Map safeCopy(final Map map, final Class keyClass, + final Class valueClass, final bool allowNullKeys, + final bool allowNullValues) { + if (map is null || keyClass is null || valueClass is null) { + throw new NullPointerException(); + } + + final Map copy = Collections.unmodifiableMap(new HashMap(map)); + final Iterator iterator = copy.entrySet().iterator(); + + while (iterator.hasNext()) { + final Map.Entry entry = (Map.Entry) iterator.next(); + assertInstance(entry.getKey(), keyClass, allowNullKeys); + assertInstance(entry.getValue(), valueClass, allowNullValues); + } + + return map; + } + + /** + * Makes a type-safe copy of the given set. This method should be used when + * a set is crossing an API boundary (i.e., from a hostile plug-in into + * internal code, or vice versa). + * + * @param set + * The set which should be copied; must not be null. + * @param c + * The class that all the values must be; must not be + * null. + * @return A copy of the set; may be empty, but never null. + * None of its element will be null. + */ + public static final Set safeCopy(final Set set, final Class c) { + return safeCopy(set, c, false); + } + + /** + * Makes a type-safe copy of the given set. This method should be used when + * a set is crossing an API boundary (i.e., from a hostile plug-in into + * internal code, or vice versa). + * + * @param set + * The set which should be copied; must not be null. + * @param c + * The class that all the values must be; must not be + * null. + * @param allowNullElements + * Whether null values should be allowed. + * @return A copy of the set; may be empty, but never null. + */ + public static final Set safeCopy(final Set set, final Class c, + final bool allowNullElements) { + if (set is null || c is null) { + throw new NullPointerException(); + } + + final Set copy = Collections.unmodifiableSet(new HashSet(set)); + final Iterator iterator = copy.iterator(); + + while (iterator.hasNext()) { + assertInstance(iterator.next(), c, allowNullElements); + } + + return set; + } +++/ + /** + * The utility class is meant to just provide static members. + */ + private this() { + // Should not be called. + } +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/internal/runtime/IRuntimeConstants.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/internal/runtime/IRuntimeConstants.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,41 @@ +/******************************************************************************* + * Copyright (c) 2005, 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.internal.runtime.IRuntimeConstants; + +import dwt.dwthelper.utils; + +public interface IRuntimeConstants { + + /** + * The unique identifier constant (value "dwtx.core.runtime") + * of the Core Runtime (pseudo-) plug-in. + */ + public static const String PI_RUNTIME = "dwtx.core.runtime"; //$NON-NLS-1$ + + /** + * Name of this bundle. + */ + public static const String PI_COMMON = "dwtx.equinox.common"; //$NON-NLS-1$ + + /** + * Status code constant (value 2) indicating an error occurred while running a plug-in. + */ + public static const int PLUGIN_ERROR = 2; + + /** + * Status code constant (value 5) indicating the platform could not write + * some of its metadata. + */ + public static const int FAILED_WRITE_METADATA = 5; + +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/internal/runtime/LocalizationUtils.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/internal/runtime/LocalizationUtils.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,59 @@ +/******************************************************************************* + * Copyright (c) 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.internal.runtime.LocalizationUtils; + +import dwt.dwthelper.utils; + +/** + * Helper methods related to string localization. + * + * @since dwtx.equinox.common 3.3 + */ +public class LocalizationUtils { + /** + * This method can be used in the absence of NLS class. The method tries to + * use the NLS-based translation routine. If it falls, the method returns the original + * non-translated key. + * + * @param key case-sensetive name of the filed in the translation file representing + * the string to be translated + * @return + */ + static public String safeLocalize(String key) { +//TODO: LocalizationUtils tries to load module CommonMessages. How to handle this? +// try { +// Class messageClass = Class.forName("dwtx.core.internal.runtime.CommonMessages"); //$NON-NLS-1$ +// if (messageClass is null) +// return key; +// Field field = messageClass.getDeclaredField(key); +// if (field is null) +// return key; +// Object value = field.get(null); +// if (value instanceof String) +// return (String) value; +// } catch (ClassNotFoundException e) { +// // eat exception and fall through +// } catch (NoClassDefFoundError e) { +// // eat exception and fall through +// } catch (SecurityException e) { +// // eat exception and fall through +// } catch (NoSuchFieldException e) { +// // eat exception and fall through +// } catch (IllegalArgumentException e) { +// // eat exception and fall through +// } catch (IllegalAccessException e) { +// // eat exception and fall through +// } + return key; + } +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/runtime/Assert.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/runtime/Assert.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,118 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.runtime.Assert; + +import dwtx.core.runtime.AssertionFailedException; + +import dwt.dwthelper.utils; + +/** + * Assert is useful for for embedding runtime sanity checks + * in code. The predicate methods all test a condition and throw some + * type of unchecked exception if the condition does not hold. + *

+ * Assertion failure exceptions, like most runtime exceptions, are + * thrown when something is misbehaving. Assertion failures are invariably + * unspecified behavior; consequently, clients should never rely on + * these being thrown (and certainly should not being catching them + * specifically). + *

+ * This class can be used without OSGi running. + *

+ * This class is not intended to be instantiated or sub-classed by clients. + *

+ * @since dwtx.equinox.common 3.2 + */ +public final class Assert { + /* This class is not intended to be instantiated. */ + private this() { + // not allowed + } + + /** Asserts that an argument is legal. If the given bool is + * not true, an IllegalArgumentException + * is thrown. + * + * @param expression the outcode of the check + * @return true if the check passes (does not return + * if the check fails) + * @exception IllegalArgumentException if the legality test failed + */ + public static bool isLegal(bool expression) { + return isLegal(expression, ""); //$NON-NLS-1$ + } + + /** Asserts that an argument is legal. If the given bool is + * not true, an IllegalArgumentException + * is thrown. + * The given message is included in that exception, to aid debugging. + * + * @param expression the outcode of the check + * @param message the message to include in the exception + * @return true if the check passes (does not return + * if the check fails) + * @exception IllegalArgumentException if the legality test failed + */ + public static bool isLegal(bool expression, String message) { + if (!expression) + throw new IllegalArgumentException(message); + return expression; + } + + /** Asserts that the given object is not null. If this + * is not the case, some kind of unchecked exception is thrown. + * + * @param object the value to test + */ + public static void isNotNull(Object object) { + isNotNull(object, ""); //$NON-NLS-1$ + } + + /** Asserts that the given object is not null. If this + * is not the case, some kind of unchecked exception is thrown. + * The given message is included in that exception, to aid debugging. + * + * @param object the value to test + * @param message the message to include in the exception + */ + public static void isNotNull(Object object, String message) { + if (object is null) + throw new AssertionFailedException("null argument:" ~ message); //$NON-NLS-1$ + } + + /** Asserts that the given bool is true. If this + * is not the case, some kind of unchecked exception is thrown. + * + * @param expression the outcode of the check + * @return true if the check passes (does not return + * if the check fails) + */ + public static bool isTrue(bool expression) { + return isTrue(expression, ""); //$NON-NLS-1$ + } + + /** Asserts that the given bool is true. If this + * is not the case, some kind of unchecked exception is thrown. + * The given message is included in that exception, to aid debugging. + * + * @param expression the outcode of the check + * @param message the message to include in the exception + * @return true if the check passes (does not return + * if the check fails) + */ + public static bool isTrue(bool expression, String message) { + if (!expression) + throw new AssertionFailedException("assertion failed: " ~ message); //$NON-NLS-1$ + return expression; + } +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/runtime/AssertionFailedException.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/runtime/AssertionFailedException.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,43 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.runtime.AssertionFailedException; + +import dwt.dwthelper.utils; + +/** + * AssertionFailedException is a runtime exception thrown + * by some of the methods in Assert. + *

+ * This class can be used without OSGi running. + *

+ * This class is not intended to be instantiated or sub-classed by clients. + *

+ * @see Assert + * @since dwtx.equinox.common 3.2 + */ +public class AssertionFailedException : RuntimeException { + + /** + * All serializable objects should have a stable serialVersionUID + */ + private static final long serialVersionUID = 1L; + + /** + * Constructs a new exception with the given message. + * + * @param detail the message + */ + public this(String detail) { + super(detail); + } +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/runtime/CoreException.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/runtime/CoreException.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,120 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.runtime.CoreException; + +import dwtx.core.runtime.IStatus; + +// import java.io.PrintStream; +// import java.io.PrintWriter; + +import dwt.dwthelper.utils; +import tango.io.Stdout; + +/** + * A checked exception representing a failure. + *

+ * Core exceptions contain a status object describing the + * cause of the exception. + *

+ * This class can be used without OSGi running. + *

+ * @see IStatus + */ +public class CoreException : Exception { + + /** + * All serializable objects should have a stable serialVersionUID + */ + private static const long serialVersionUID = 1L; + + /** Status object. */ + private IStatus status; + + /** + * Creates a new exception with the given status object. The message + * of the given status is used as the exception message. + * + * @param status the status object to be associated with this exception + */ + public this(IStatus status) { + super(status.getMessage()); + this.status = status; + } + + /** + * Returns the status object for this exception. + * + * @return a status object + */ + public final IStatus getStatus() { + return status; + } + + /** + * Prints a stack trace out for the exception, and + * any nested exception that it may have embedded in + * its Status object. + */ + public void printStackTrace() { +// printStackTrace(System.err); + Stderr.formatln( "Exception in File {}({}): {}", this.file, this.line, this.msg ); + foreach( msg; this.info ){ + Stderr.formatln( " trc: {}", msg ); + } + if (status.getException() !is null) { + Stderr.formatln( "{}[{}]: ", this.classinfo.name, status.getCode() ); //$NON-NLS-1$ //$NON-NLS-2$ +// status.getException().printStackTrace(); + auto e = status.getException(); + Stderr.formatln( "Exception in File {}({}): {}", e.file, e.line, e.msg ); + foreach( msg; e.info ){ + Stderr.formatln( " trc: {}", msg ); + } + } + } + +//FIXME +// /** +// * Prints a stack trace out for the exception, and +// * any nested exception that it may have embedded in +// * its Status object. +// * +// * @param output the stream to write to +// */ +// public void printStackTrace(PrintStream output) { +// synchronized (output) { +// super.printStackTrace(output); +// if (status.getException() !is null) { +// output.print(getClass().getName() + "[" + status.getCode() + "]: "); //$NON-NLS-1$ //$NON-NLS-2$ +// status.getException().printStackTrace(output); +// } +// } +// } +// +// /** +// * Prints a stack trace out for the exception, and +// * any nested exception that it may have embedded in +// * its Status object. +// * +// * @param output the stream to write to +// */ +// public void printStackTrace(PrintWriter output) { +// synchronized (output) { +// super.printStackTrace(output); +// if (status.getException() !is null) { +// output.print(getClass().getName() + "[" + status.getCode() + "]: "); //$NON-NLS-1$ //$NON-NLS-2$ +// status.getException().printStackTrace(output); +// } +// } +// } + +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/runtime/IAdaptable.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/runtime/IAdaptable.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.runtime.IAdaptable; + +import dwt.dwthelper.utils; + +/** + * An interface for an adaptable object. + *

+ * Adaptable objects can be dynamically extended to provide different + * interfaces (or "adapters"). Adapters are created by adapter + * factories, which are in turn managed by type by adapter managers. + *

+ * For example, + *
+ *     IAdaptable a = [some adaptable];
+ *     IFoo x = (IFoo)a.getAdapter(IFoo.class);
+ *     if (x !is null)
+ *         [do IFoo things with x]
+ * 
+ *

+ * This interface can be used without OSGi running. + *

+ * Clients may implement this interface, or obtain a default implementation + * of this interface by subclassing PlatformObject. + *

+ * @see IAdapterFactory + * @see IAdapterManager + * @see PlatformObject + */ +public interface IAdaptable { + /** + * Returns an object which is an instance of the given class + * associated with this object. Returns null if + * no such object can be found. + * + * @param adapter the adapter class to look up + * @return a object castable to the given class, + * or null if this object does not + * have an adapter for the given class + */ + public Object getAdapter(ClassInfo adapter); +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/runtime/IAdapterFactory.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/runtime/IAdapterFactory.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,58 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.runtime.IAdapterFactory; + +import dwt.dwthelper.utils; + +/** + * An adapter factory defines behavioral extensions for + * one or more classes that implements the IAdaptable + * interface. Adapter factories are registered with an + * adapter manager. + *

+ * This interface can be used without OSGi running. + *

+ * Clients may implement this interface. + *

+ * @see IAdapterManager + * @see IAdaptable + */ +public interface IAdapterFactory { + /** + * Returns an object which is an instance of the given class + * associated with the given object. Returns null if + * no such object can be found. + * + * @param adaptableObject the adaptable object being queried + * (usually an instance of IAdaptable) + * @param adapterType the type of adapter to look up + * @return a object castable to the given adapter type, + * or null if this adapter factory + * does not have an adapter of the given type for the + * given object + */ + public Object getAdapter(Object adaptableObject, ClassInfo adapterType); + + /** + * Returns the collection of adapter types handled by this + * factory. + *

+ * This method is generally used by an adapter manager + * to discover which adapter types are supported, in advance + * of dispatching any actual getAdapter requests. + *

+ * + * @return the collection of adapter types + */ + public ClassInfo[] getAdapterList(); +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/runtime/IAdapterManager.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/runtime/IAdapterManager.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,266 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.runtime.IAdapterManager; + +import dwtx.core.runtime.IAdapterFactory; + +import dwt.dwthelper.utils; + +/** + * An adapter manager maintains a registry of adapter factories. Clients + * directly invoke methods on an adapter manager to register and unregister + * adapters. All adaptable objects (that is, objects that implement the IAdaptable + * interface) funnel IAdaptable.getAdapter invocations to their + * adapter manager's IAdapterManger.getAdapter method. The + * adapter manager then forwards this request unmodified to the IAdapterFactory.getAdapter + * method on one of the registered adapter factories. + *

+ * Adapter factories can be registered programmatically using the registerAdapters + * method. Alternatively, they can be registered declaratively using the + * dwtx.core.runtime.adapters extension point. Factories registered + * with this extension point will not be able to provide adapters until their + * corresponding plugin has been activated. + *

+ * The following code snippet shows how one might register an adapter of type + * com.example.acme.Sticky on resources in the workspace. + *

+ * + *

+ *  IAdapterFactory pr = new IAdapterFactory() {
+ *      public Class[] getAdapterList() {
+ *          return new Class[] { com.example.acme.Sticky.class };
+ *      }
+ *      public Object getAdapter(Object adaptableObject, Class adapterType) {
+ *          IResource res = (IResource) adaptableObject;
+ *          QualifiedName key = new QualifiedName("com.example.acme", "sticky-note");
+ *          try {
+ *              com.example.acme.Sticky v = (com.example.acme.Sticky) res.getSessionProperty(key);
+ *              if (v is null) {
+ *                  v = new com.example.acme.Sticky();
+ *                  res.setSessionProperty(key, v);
+ *              }
+ *          } catch (CoreException e) {
+ *              // unable to access session property - ignore
+ *          }
+ *          return v;
+ *      }
+ *  }
+ *  Platform.getAdapterManager().registerAdapters(pr, IResource.class);
+ *   
+ * + *

+ * This interface can be used without OSGi running. + *

+ * This interface is not intended to be implemented by clients. + *

+ * @see IAdaptable + * @see IAdapterFactory + */ +public interface IAdapterManager { + + /** + * This value can be returned to indicate that no applicable adapter factory + * was found. + * @since dwtx.equinox.common 3.3 + */ + public static const int NONE = 0; + + /** + * This value can be returned to indicate that an adapter factory was found, + * but has not been loaded. + * @since dwtx.equinox.common 3.3 + */ + public static const int NOT_LOADED = 1; + + /** + * This value can be returned to indicate that an adapter factory is loaded. + * @since dwtx.equinox.common 3.3 + */ + public static const int LOADED = 2; + + /** + * Returns the types that can be obtained by converting adaptableClass + * via this manager. Converting means that subsequent calls to getAdapter() + * or loadAdapter() could result in an adapted object. + *

+ * Note that the returned types do not guarantee that + * a subsequent call to getAdapter with the same type as an argument + * will return a non-null result. If the factory's plug-in has not yet been + * loaded, or if the factory itself returns null, then + * getAdapter will still return null. + *

+ * @param adaptableClass the adaptable class being queried + * @return an array of type names that can be obtained by converting + * adaptableClass via this manager. An empty array + * is returned if there are none. + * @since 3.1 + */ + public String[] computeAdapterTypes(ClassInfo adaptableClass); + + /** + * Returns the class search order for a given class. The search order from a + * class with the definition
+ * class X extends Y implements A, B
+ * is as follows: + *
    + *
  • the target's class: X + *
  • X's superclasses in order to Object + *
  • a breadth-first traversal of the target class's interfaces in the + * order returned by getInterfaces (in the example, A and its + * superinterfaces then B and its superinterfaces)
  • + *
+ * + * @param clazz the class for which to return the class order. + * @return the class search order for the given class. The returned + * search order will minimally contain the target class. + * @since 3.1 + */ + public ClassInfo[] computeClassOrder(ClassInfo clazz); + + /** + * Returns an object which is an instance of the given class associated + * with the given object. Returns null if no such object can + * be found. + *

+ * Note that this method will never cause plug-ins to be loaded. If the + * only suitable factory is not yet loaded, this method will return null. + * + * @param adaptable the adaptable object being queried (usually an instance + * of IAdaptable) + * @param adapterType the type of adapter to look up + * @return an object castable to the given adapter type, or null + * if the given adaptable object does not have an available adapter of the + * given type + */ + public Object getAdapter(Object adaptable, ClassInfo adapterType); + + /** + * Returns an object which is an instance of the given class name associated + * with the given object. Returns null if no such object can + * be found. + *

+ * Note that this method will never cause plug-ins to be loaded. If the + * only suitable factory is not yet loaded, this method will return null. + * If activation of the plug-in providing the factory is required, use the + * loadAdapter method instead. + * + * @param adaptable the adaptable object being queried (usually an instance + * of IAdaptable) + * @param adapterTypeName the fully qualified name of the type of adapter to look up + * @return an object castable to the given adapter type, or null + * if the given adaptable object does not have an available adapter of the + * given type + * @since 3.0 + */ + public Object getAdapter(Object adaptable, String adapterTypeName); + + /** + * Returns whether there is an adapter factory registered that may be able + * to convert adaptable to an object of type adapterTypeName. + *

+ * Note that a return value of true does not guarantee that + * a subsequent call to getAdapter with the same arguments + * will return a non-null result. If the factory's plug-in has not yet been + * loaded, or if the factory itself returns null, then + * getAdapter will still return null. + * + * @param adaptable the adaptable object being queried (usually an instance + * of IAdaptable) + * @param adapterTypeName the fully qualified class name of an adapter to + * look up + * @return true if there is an adapter factory that claims + * it can convert adaptable to an object of type adapterType, + * and false otherwise. + * @since 3.0 + */ + public bool hasAdapter(Object adaptable, String adapterTypeName); + + /** + * Returns a status of an adapter factory registered that may be able + * to convert adaptable to an object of type adapterTypeName. + *

+ * One of the following values can be returned:

    + *
  • {@link dwtx.core.runtime.IAdapterManager#NONE} if no applicable adapter factory was found;
  • + *
  • {@link dwtx.core.runtime.IAdapterManager#NOT_LOADED} if an adapter factory was found, but has not been loaded;
  • + *
  • {@link dwtx.core.runtime.IAdapterManager#LOADED} if an adapter factory was found, and it is loaded.
  • + *

+ * @param adaptable the adaptable object being queried (usually an instance + * of IAdaptable) + * @param adapterTypeName the fully qualified class name of an adapter to + * look up + * @return a status of the adapter + * @since dwtx.equinox.common 3.3 + */ + public int queryAdapter(Object adaptable, String adapterTypeName); + + /** + * Returns an object that is an instance of the given class name associated + * with the given object. Returns null if no such object can + * be found. + *

+ * Note that unlike the getAdapter methods, this method + * will cause the plug-in that contributes the adapter factory to be loaded + * if necessary. As such, this method should be used judiciously, in order + * to avoid unnecessary plug-in activations. Most clients should avoid + * activation by using getAdapter instead. + * + * @param adaptable the adaptable object being queried (usually an instance + * of IAdaptable) + * @param adapterTypeName the fully qualified name of the type of adapter to look up + * @return an object castable to the given adapter type, or null + * if the given adaptable object does not have an available adapter of the + * given type + * @since 3.0 + */ + public Object loadAdapter(Object adaptable, String adapterTypeName); + + /** + * Registers the given adapter factory as extending objects of the given + * type. + *

+ * If the type being extended is a class, the given factory's adapters are + * available on instances of that class and any of its subclasses. If it is + * an interface, the adapters are available to all classes that directly or + * indirectly implement that interface. + *

+ * + * @param factory the adapter factory + * @param adaptable the type being extended + * @see #unregisterAdapters(IAdapterFactory) + * @see #unregisterAdapters(IAdapterFactory, Class) + */ + public void registerAdapters(IAdapterFactory factory, ClassInfo adaptable); + + /** + * Removes the given adapter factory completely from the list of registered + * factories. Equivalent to calling unregisterAdapters(IAdapterFactory,Class) + * on all classes against which it had been explicitly registered. Does + * nothing if the given factory is not currently registered. + * + * @param factory the adapter factory to remove + * @see #registerAdapters(IAdapterFactory, Class) + */ + public void unregisterAdapters(IAdapterFactory factory); + + /** + * Removes the given adapter factory from the list of factories registered + * as extending the given class. Does nothing if the given factory and type + * combination is not registered. + * + * @param factory the adapter factory to remove + * @param adaptable one of the types against which the given factory is + * registered + * @see #registerAdapters(IAdapterFactory, Class) + */ + public void unregisterAdapters(IAdapterFactory factory, ClassInfo adaptable); +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/runtime/ILogListener.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/runtime/ILogListener.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.runtime.ILogListener; + +import dwtx.core.runtime.IStatus; + +import dwt.dwthelper.utils; + +/** + * A log listener is notified of entries added to a plug-in's log. + *

+ * This interface can be used without OSGi running. + *

+ * Clients may implement this interface. + *

+ * @see ILog#addLogListener(ILogListener) + * @see Platform#addLogListener(ILogListener) + */ +public interface ILogListener : EventListener { + /** + * Notifies this listener that given status has been logged by + * a plug-in. The listener is free to retain or ignore this status. + * + * @param status the status being logged + * @param plugin the plugin of the log which generated this event + */ + public void logging(IStatus status, String plugin); +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/runtime/IPath.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/runtime/IPath.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,510 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.runtime.IPath; + +import dwt.dwthelper.utils; +import tango.io.FilePath; + +/** + * A path is an ordered collection of string segments, + * separated by a standard separator character, "/". + * A path may also have a leading and/or a trailing separator. + * Paths may also be prefixed by an optional device id, which includes + * the character(s) which separate the device id from the rest + * of the path. For example, "C:" and "Server/Volume:" are typical + * device ids. + * A device independent path has null for a device id. + *

+ * Note that paths are value objects; all operations on paths + * return a new path; the path that is operated on is unscathed. + *

+ *

+ * UNC paths are denoted by leading double-slashes such + * as //Server/Volume/My/Path. When a new path + * is constructed all double-slashes are removed except those + * appearing at the beginning of the path. + *

+ *

+ * This interface can be used without OSGi running. + *

+ * This interface is not intended to be implemented by clients. + *

+ * @see Path + */ +public interface IPath : Cloneable { + + /** + * Path separator character constant "/" used in paths. + */ + public static const char SEPARATOR = '/'; + + /** + * Device separator character constant ":" used in paths. + */ + public static const char DEVICE_SEPARATOR = ':'; + + /** + * Returns a new path which is the same as this path but with + * the given file extension added. If this path is empty, root or has a + * trailing separator, this path is returned. If this path already + * has an extension, the existing extension is left and the given + * extension simply appended. Clients wishing to replace + * the current extension should first remove the extension and + * then add the desired one. + *

+ * The file extension portion is defined as the string + * following the last period (".") character in the last segment. + * The given extension should not include a leading ".". + *

+ * + * @param extension the file extension to append + * @return the new path + */ + public IPath addFileExtension(String extension); + + /** + * Returns a path with the same segments as this path + * but with a trailing separator added. + * This path must have at least one segment. + *

+ * If this path already has a trailing separator, + * this path is returned. + *

+ * + * @return the new path + * @see #hasTrailingSeparator() + * @see #removeTrailingSeparator() + */ + public IPath addTrailingSeparator(); + + /** + * Returns the canonicalized path obtained from the + * concatenation of the given string path to the + * end of this path. The given string path must be a valid + * path. If it has a trailing separator, + * the result will have a trailing separator. + * The device id of this path is preserved (the one + * of the given string is ignored). Duplicate slashes + * are removed from the path except at the beginning + * where the path is considered to be UNC. + * + * @param path the string path to concatenate + * @return the new path + * @see #isValidPath(String) + */ + public IPath append(String path); + + /** + * Returns the canonicalized path obtained from the + * concatenation of the given path's segments to the + * end of this path. If the given path has a trailing + * separator, the result will have a trailing separator. + * The device id of this path is preserved (the one + * of the given path is ignored). Duplicate slashes + * are removed from the path except at the beginning + * where the path is considered to be UNC. + * + * @param path the path to concatenate + * @return the new path + */ + public IPath append(IPath path); + + /** + * Returns a copy of this path. + * + * @return the cloned path + */ + public Object clone(); + + /** + * Returns whether this path equals the given object. + *

+ * Equality for paths is defined to be: same sequence of segments, + * same absolute/relative status, and same device. + * Trailing separators are disregarded. + * Paths are not generally considered equal to objects other than paths. + *

+ * + * @param obj the other object + * @return true if the paths are equivalent, + * and false if they are not + */ + public int opEquals(Object obj); + + /** + * Returns the device id for this path, or null if this + * path has no device id. Note that the result will end in ':'. + * + * @return the device id, or null + * @see #setDevice(String) + */ + public String getDevice(); + + /** + * Returns the file extension portion of this path, + * or null if there is none. + *

+ * The file extension portion is defined as the string + * following the last period (".") character in the last segment. + * If there is no period in the last segment, the path has no + * file extension portion. If the last segment ends in a period, + * the file extension portion is the empty string. + *

+ * + * @return the file extension or null + */ + public String getFileExtension(); + + /** + * Returns whether this path has a trailing separator. + *

+ * Note: In the root path ("/"), the separator is considered to + * be leading rather than trailing. + *

+ * + * @return true if this path has a trailing + * separator, and false otherwise + * @see #addTrailingSeparator() + * @see #removeTrailingSeparator() + */ + public bool hasTrailingSeparator(); + + /** + * Returns whether this path is an absolute path (ignoring + * any device id). + *

+ * Absolute paths start with a path separator. + * A root path, like / or C:/, + * is considered absolute. UNC paths are always absolute. + *

+ * + * @return true if this path is an absolute path, + * and false otherwise + */ + public bool isAbsolute(); + + /** + * Returns whether this path has no segments and is not + * a root path. + * + * @return true if this path is empty, + * and false otherwise + */ + public bool isEmpty(); + + /** + * Returns whether this path is a prefix of the given path. + * To be a prefix, this path's segments must + * appear in the argument path in the same order, + * and their device ids must match. + *

+ * An empty path is a prefix of all paths with the same device; a root path is a prefix of + * all absolute paths with the same device. + *

+ * @param anotherPath the other path + * @return true if this path is a prefix of the given path, + * and false otherwise + */ + public bool isPrefixOf(IPath anotherPath); + + /** + * Returns whether this path is a root path. + *

+ * The root path is the absolute non-UNC path with zero segments; + * e.g., / or C:/. + * The separator is considered a leading separator, not a trailing one. + *

+ * + * @return true if this path is a root path, + * and false otherwise + */ + public bool isRoot(); + + /** + * Returns a bool value indicating whether or not this path + * is considered to be in UNC form. Return false if this path + * has a device set or if the first 2 characters of the path string + * are not Path.SEPARATOR. + * + * @return bool indicating if this path is UNC + */ + public bool isUNC(); + + /** + * Returns whether the given string is syntactically correct as + * a path. The device id is the prefix up to and including the device + * separator for the local file system; the path proper is everything to + * the right of it, or the entire string if there is no device separator. + * When the platform location is a file system with no meaningful device + * separator, the entire string is treated as the path proper. + * The device id is not checked for validity; the path proper is correct + * if each of the segments in its canonicalized form is valid. + * + * @param path the path to check + * @return true if the given string is a valid path, + * and false otherwise + * @see #isValidSegment(String) + */ + public bool isValidPath(String path); + + /** + * Returns whether the given string is valid as a segment in + * a path. The rules for valid segments are as follows: + *
    + *
  • the empty string is not valid + *
  • any string containing the slash character ('/') is not valid + *
  • any string containing segment or device separator characters + * on the local file system, such as the backslash ('\') and colon (':') + * on some file systems. + *
+ * + * @param segment the path segment to check + * @return true if the given path segment is valid, + * and false otherwise + */ + public bool isValidSegment(String segment); + + /** + * Returns the last segment of this path, or + * null if it does not have any segments. + * + * @return the last segment of this path, or null + */ + public String lastSegment(); + + /** + * Returns an absolute path with the segments and device id of this path. + * Absolute paths start with a path separator. If this path is absolute, + * it is simply returned. + * + * @return the new path + */ + public IPath makeAbsolute(); + + /** + * Returns a relative path with the segments and device id of this path. + * Absolute paths start with a path separator and relative paths do not. + * If this path is relative, it is simply returned. + * + * @return the new path + */ + public IPath makeRelative(); + + /** + * Return a new path which is the equivalent of this path converted to UNC + * form (if the given bool is true) or this path not as a UNC path (if the given + * bool is false). If UNC, the returned path will not have a device and the + * first 2 characters of the path string will be Path.SEPARATOR. If not UNC, the + * first 2 characters of the returned path string will not be Path.SEPARATOR. + * + * @param toUNC true if converting to UNC, false otherwise + * @return the new path, either in UNC form or not depending on the bool parameter + */ + public IPath makeUNC(bool toUNC); + + /** + * Returns a count of the number of segments which match in + * this path and the given path (device ids are ignored), + * comparing in increasing segment number order. + * + * @param anotherPath the other path + * @return the number of matching segments + */ + public int matchingFirstSegments(IPath anotherPath); + + /** + * Returns a new path which is the same as this path but with + * the file extension removed. If this path does not have an + * extension, this path is returned. + *

+ * The file extension portion is defined as the string + * following the last period (".") character in the last segment. + * If there is no period in the last segment, the path has no + * file extension portion. If the last segment ends in a period, + * the file extension portion is the empty string. + *

+ * + * @return the new path + */ + public IPath removeFileExtension(); + + /** + * Returns a copy of this path with the given number of segments + * removed from the beginning. The device id is preserved. + * The number must be greater or equal zero. + * If the count is zero, this path is returned. + * The resulting path will always be a relative path with respect + * to this path. If the number equals or exceeds the number + * of segments in this path, an empty relative path is returned. + * + * @param count the number of segments to remove + * @return the new path + */ + public IPath removeFirstSegments(int count); + + /** + * Returns a copy of this path with the given number of segments + * removed from the end. The device id is preserved. + * The number must be greater or equal zero. + * If the count is zero, this path is returned. + *

+ * If this path has a trailing separator, it will still + * have a trailing separator after the last segments are removed + * (assuming there are some segments left). If there is no + * trailing separator, the result will not have a trailing + * separator. + * If the number equals or exceeds the number + * of segments in this path, a path with no segments is returned. + *

+ * + * @param count the number of segments to remove + * @return the new path + */ + public IPath removeLastSegments(int count); + + /** + * Returns a path with the same segments as this path + * but with a trailing separator removed. + * Does nothing if this path does not have at least one segment. + * The device id is preserved. + *

+ * If this path does not have a trailing separator, + * this path is returned. + *

+ * + * @return the new path + * @see #addTrailingSeparator() + * @see #hasTrailingSeparator() + */ + public IPath removeTrailingSeparator(); + + /** + * Returns the specified segment of this path, or + * null if the path does not have such a segment. + * + * @param index the 0-based segment index + * @return the specified segment, or null + */ + public String segment(int index); + + /** + * Returns the number of segments in this path. + *

+ * Note that both root and empty paths have 0 segments. + *

+ * + * @return the number of segments + */ + public int segmentCount(); + + /** + * Returns the segments in this path in order. + * + * @return an array of string segments + */ + public String[] segments(); + + /** + * Returns a new path which is the same as this path but with + * the given device id. The device id must end with a ":". + * A device independent path is obtained by passing null. + *

+ * For example, "C:" and "Server/Volume:" are typical device ids. + *

+ * + * @param device the device id or null + * @return a new path + * @see #getDevice() + */ + public IPath setDevice(String device); + + /** + * Returns a java.io.File corresponding to this path. + * + * @return the file corresponding to this path + */ + public tango.io.FilePath.FilePath toFile(); + + /** + * Returns a string representation of this path which uses the + * platform-dependent path separator defined by java.io.File. + * This method is like toString() except that the + * latter always uses the same separator (/) regardless of platform. + *

+ * This string is suitable for passing to java.io.File(String). + *

+ * + * @return a platform-dependent string representation of this path + */ + public String toOSString(); + + /** + * Returns a platform-neutral string representation of this path. The + * format is not specified, except that the resulting string can be + * passed back to the Path#fromPortableString(String) + * constructor to produce the exact same path on any platform. + *

+ * This string is suitable for passing to Path#fromPortableString(String). + *

+ * + * @return a platform-neutral string representation of this path + * @see Path#fromPortableString(String) + * @since 3.1 + */ + public String toPortableString(); + + /** + * Returns a string representation of this path, including its + * device id. The same separator, "/", is used on all platforms. + *

+ * Example result strings (without and with device id): + *

+     * "/foo/bar.txt"
+     * "bar.txt"
+     * "/foo/"
+     * "foo/"
+     * ""
+     * "/"
+     * "C:/foo/bar.txt"
+     * "C:bar.txt"
+     * "C:/foo/"
+     * "C:foo/"
+     * "C:"
+     * "C:/"
+     * 
+ * This string is suitable for passing to Path(String). + *

+ * + * @return a string representation of this path + * @see Path + */ + public String toString(); + + /** + * Returns a copy of this path truncated after the + * given number of segments. The number must not be negative. + * The device id is preserved. + *

+ * If this path has a trailing separator, the result will too + * (assuming there are some segments left). If there is no + * trailing separator, the result will not have a trailing + * separator. + * Copying up to segment zero simply means making an copy with + * no path segments. + *

+ * + * @param count the segment number at which to truncate the path + * @return the new path + */ + public IPath uptoSegment(int count); +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/runtime/IProgressMonitor.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/runtime/IProgressMonitor.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,133 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.runtime.IProgressMonitor; + +import dwt.dwthelper.utils; + +/** + * The IProgressMonitor interface is implemented + * by objects that monitor the progress of an activity; the methods + * in this interface are invoked by code that performs the activity. + *

+ * All activity is broken down into a linear sequence of tasks against + * which progress is reported. When a task begins, a beginTask(String, int) + * notification is reported, followed by any number and mixture of + * progress reports (worked()) and subtask notifications + * (subTask(String)). When the task is eventually completed, a + * done() notification is reported. After the done() + * notification, the progress monitor cannot be reused; i.e., + * beginTask(String, int) cannot be called again after the call to + * done(). + *

+ *

+ * A request to cancel an operation can be signaled using the + * setCanceled method. Operations taking a progress + * monitor are expected to poll the monitor (using isCanceled) + * periodically and abort at their earliest convenience. Operation can however + * choose to ignore cancelation requests. + *

+ *

+ * Since notification is synchronous with the activity itself, the listener should + * provide a fast and robust implementation. If the handling of notifications would + * involve blocking operations, or operations which might throw uncaught exceptions, + * the notifications should be queued, and the actual processing deferred (or perhaps + * delegated to a separate thread). + *

+ * This interface can be used without OSGi running. + *

+ * Clients may implement this interface. + *

+ */ +public interface IProgressMonitor { + + /** Constant indicating an unknown amount of work. + */ + public final const int UNKNOWN = -1; + + /** + * Notifies that the main task is beginning. This must only be called once + * on a given progress monitor instance. + * + * @param name the name (or description) of the main task + * @param totalWork the total number of work units into which + * the main task is been subdivided. If the value is UNKNOWN + * the implementation is free to indicate progress in a way which + * doesn't require the total number of work units in advance. + */ + public void beginTask(String name, int totalWork); + + /** + * Notifies that the work is done; that is, either the main task is completed + * or the user canceled it. This method may be called more than once + * (implementations should be prepared to handle this case). + */ + public void done(); + + /** + * Internal method to handle scaling correctly. This method + * must not be called by a client. Clients should + * always use the method worked(int). + * + * @param work the amount of work done + */ + public void internalWorked(double work); + + /** + * Returns whether cancelation of current operation has been requested. + * Long-running operations should poll to see if cancelation + * has been requested. + * + * @return true if cancellation has been requested, + * and false otherwise + * @see #setCanceled(bool) + */ + public bool isCanceled(); + + /** + * Sets the cancel state to the given value. + * + * @param value true indicates that cancelation has + * been requested (but not necessarily acknowledged); + * false clears this flag + * @see #isCanceled() + */ + public void setCanceled(bool value); + + /** + * Sets the task name to the given value. This method is used to + * restore the task label after a nested operation was executed. + * Normally there is no need for clients to call this method. + * + * @param name the name (or description) of the main task + * @see #beginTask(java.lang.String, int) + */ + public void setTaskName(String name); + + /** + * Notifies that a subtask of the main task is beginning. + * Subtasks are optional; the main task might not have subtasks. + * + * @param name the name (or description) of the subtask + */ + public void subTask(String name); + + /** + * Notifies that a given number of work unit of the main task + * has been completed. Note that this amount represents an + * installment, as opposed to a cumulative amount of work done + * to date. + * + * @param work a non-negative number of work units just completed + */ + public void worked(int work); +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/runtime/IProgressMonitorWithBlocking.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/runtime/IProgressMonitorWithBlocking.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,69 @@ +/******************************************************************************* + * Copyright (c) 2003, 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 - Initial API and implementation + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.runtime.IProgressMonitorWithBlocking; + +import dwtx.core.runtime.IProgressMonitor; +import dwtx.core.runtime.IStatus; + +import dwt.dwthelper.utils; + +/** + * An extension to the IProgressMonitor interface for monitors that want to + * support feedback when an activity is blocked due to concurrent activity in + * another thread. + *

+ * When a monitor that supports this extension is passed to an operation, the + * operation should call setBlocked whenever it knows that it + * must wait for a lock that is currently held by another thread. The operation + * should continue to check for and respond to cancelation requests while + * blocked. When the operation is no longer blocked, it must call clearBlocked + * to clear the blocked state. + *

+ * This interface can be used without OSGi running. + *

+ * Clients may implement this interface. + *

+ * @see IProgressMonitor + * @since 3.0 + */ +public interface IProgressMonitorWithBlocking : IProgressMonitor { + /** + * Indicates that this operation is blocked by some background activity. If + * a running operation ever calls setBlocked, it must + * eventually call clearBlocked before the operation + * completes. + *

+ * If the caller is blocked by a currently executing job, this method will return + * an IJobStatus indicating the job that is currently blocking + * the caller. If this blocking job is not known, this method will return a plain + * informational IStatus object. + *

+ * + * @param reason an optional status object whose message describes the + * reason why this operation is blocked, or null if this + * information is not available. + * @see #clearBlocked() + * @see dwtx.core.runtime.jobs.IJobStatus + */ + public void setBlocked(IStatus reason); + + /** + * Clears the blocked state of the running operation. If a running + * operation ever calls setBlocked, it must eventually call + * clearBlocked before the operation completes. + * + * @see #setBlocked(IStatus) + */ + public void clearBlocked(); + +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/runtime/ISafeRunnable.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/runtime/ISafeRunnable.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,54 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.runtime.ISafeRunnable; + +// import dwt.internal.Platform; +import dwt.dwthelper.utils; + +/** + * Safe runnables represent blocks of code and associated exception + * handlers. They are typically used when a plug-in needs to call some + * untrusted code (e.g., code contributed by another plug-in via an + * extension). + *

+ * This interface can be used without OSGi running. + *

+ * Clients may implement this interface. + *

+ * @see Platform#run(ISafeRunnable) + */ +public interface ISafeRunnable { + /** + * Handles an exception thrown by this runnable's run + * method. The processing done here should be specific to the + * particular usecase for this runnable. Generalized exception + * processing (e.g., logging in the platform's log) is done by the + * Platform's run mechanism. + * + * @param exception an exception which occurred during processing + * the body of this runnable (i.e., in run()) + * @see Platform#run(ISafeRunnable) + */ + public void handleException(Exception exception); + + /** + * Runs this runnable. Any exceptions thrown from this method will + * be passed to this runnable's handleException + * method. + * + * @exception Exception if a problem occurred while running this method. + * The exception will be processed by handleException + * @see Platform#run(ISafeRunnable) + */ + public void run(); +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/runtime/IStatus.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/runtime/IStatus.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,190 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.runtime.IStatus; + +import dwt.dwthelper.utils; + +/** + * A status object represents the outcome of an operation. + * All CoreExceptions carry a status object to indicate + * what went wrong. Status objects are also returned by methods needing + * to provide details of failures (e.g., validation methods). + *

+ * A status carries the following information: + *

    + *
  • plug-in identifier (required)
  • + *
  • severity (required)
  • + *
  • status code (required)
  • + *
  • message (required) - localized to current locale
  • + *
  • exception (optional) - for problems stemming from a failure at + * a lower level
  • + *
+ * Some status objects, known as multi-statuses, have other status objects + * as children. + *

+ *

+ * The class Status is the standard public implementation + * of status objects; the subclass MultiStatus is the + * implements multi-status objects. + *

+ * This interface can be used without OSGi running. + *

+ * @see MultiStatus + * @see Status + */ +public interface IStatus { + + /** Status severity constant (value 0) indicating this status represents the nominal case. + * This constant is also used as the status code representing the nominal case. + * @see #getSeverity() + * @see #isOK() + */ + public static const int OK = 0; + + /** Status type severity (bit mask, value 1) indicating this status is informational only. + * @see #getSeverity() + * @see #matches(int) + */ + public static const int INFO = 0x01; + + /** Status type severity (bit mask, value 2) indicating this status represents a warning. + * @see #getSeverity() + * @see #matches(int) + */ + public static const int WARNING = 0x02; + + /** Status type severity (bit mask, value 4) indicating this status represents an error. + * @see #getSeverity() + * @see #matches(int) + */ + public static const int ERROR = 0x04; + + /** Status type severity (bit mask, value 8) indicating this status represents a + * cancelation + * @see #getSeverity() + * @see #matches(int) + * @since 3.0 + */ + public static const int CANCEL = 0x08; + + /** + * Returns a list of status object immediately contained in this + * multi-status, or an empty list if this is not a multi-status. + * + * @return an array of status objects + * @see #isMultiStatus() + */ + public IStatus[] getChildren(); + + /** + * Returns the plug-in-specific status code describing the outcome. + * + * @return plug-in-specific status code + */ + public int getCode(); + + /** + * Returns the relevant low-level exception, or null if none. + * For example, when an operation fails because of a network communications + * failure, this might return the java.io.IOException + * describing the exact nature of that failure. + * + * @return the relevant low-level exception, or null if none + */ + public Exception getException(); + + /** + * Returns the message describing the outcome. + * The message is localized to the current locale. + * + * @return a localized message + */ + public String getMessage(); + + /** + * Returns the unique identifier of the plug-in associated with this status + * (this is the plug-in that defines the meaning of the status code). + * + * @return the unique identifier of the relevant plug-in + */ + public String getPlugin(); + + /** + * Returns the severity. The severities are as follows (in + * descending order): + *
    + *
  • CANCEL - cancelation occurred
  • + *
  • ERROR - a serious error (most severe)
  • + *
  • WARNING - a warning (less severe)
  • + *
  • INFO - an informational ("fyi") message (least severe)
  • + *
  • OK - everything is just fine
  • + *
+ *

+ * The severity of a multi-status is defined to be the maximum + * severity of any of its children, or OK if it has + * no children. + *

+ * + * @return the severity: one of OK, ERROR, + * INFO, WARNING, or CANCEL + * @see #matches(int) + */ + public int getSeverity(); + + /** + * Returns whether this status is a multi-status. + * A multi-status describes the outcome of an operation + * involving multiple operands. + *

+ * The severity of a multi-status is derived from the severities + * of its children; a multi-status with no children is + * OK by definition. + * A multi-status carries a plug-in identifier, a status code, + * a message, and an optional exception. Clients may treat + * multi-status objects in a multi-status unaware way. + *

+ * + * @return true for a multi-status, + * false otherwise + * @see #getChildren() + */ + public bool isMultiStatus(); + + /** + * Returns whether this status indicates everything is okay + * (neither info, warning, nor error). + * + * @return true if this status has severity + * OK, and false otherwise + */ + public bool isOK(); + + /** + * Returns whether the severity of this status matches the given + * severity mask. Note that a status with severity OK + * will never match; use isOK instead to detect + * a status with a severity of OK. + * + * @param severityMask a mask formed by bitwise or'ing severity mask + * constants (ERROR, WARNING, + * INFO, CANCEL) + * @return true if there is at least one match, + * false if there are no matches + * @see #getSeverity() + * @see #CANCEL + * @see #ERROR + * @see #WARNING + * @see #INFO + */ + public bool matches(int severityMask); +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/runtime/ListenerList.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/runtime/ListenerList.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,194 @@ +/******************************************************************************* + * 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.runtime.ListenerList; + +import dwt.dwthelper.utils; + +/** + * This class is a thread safe list that is designed for storing lists of listeners. + * The implementation is optimized for minimal memory footprint, frequent reads + * and infrequent writes. Modification of the list is synchronized and relatively + * expensive, while accessing the listeners is very fast. Readers are given access + * to the underlying array data structure for reading, with the trust that they will + * not modify the underlying array. + *

+ * A listener list handles the same listener being added + * multiple times, and tolerates removal of listeners that are the same as other + * listeners in the list. For this purpose, listeners can be compared with each other + * using either equality or identity, as specified in the list constructor. + *

+ *

+ * Use the getListeners method when notifying listeners. The recommended + * code sequence for notifying all registered listeners of say, + * FooListener.eventHappened, is: + * + *

+ * Object[] listeners = myListenerList.getListeners();
+ * for (int i = 0; i < listeners.length; ++i) {
+ *  ((FooListener) listeners[i]).eventHappened(event);
+ * }
+ * 
+ * + *

+ * This class can be used without OSGi running. + *

+ * @since dwtx.equinox.common 3.2 + */ +public class ListenerList { + + /** + * The empty array singleton instance. + */ + private static const Object[] EmptyArray; + + /** + * Mode constant (value 0) indicating that listeners should be considered + * the
same if they are equal. + */ + public static const int EQUALITY = 0; + + /** + * Mode constant (value 1) indicating that listeners should be considered + * the same if they are identical. + */ + public static const int IDENTITY = 1; + + /** + * Indicates the comparison mode used to determine if two + * listeners are equivalent + */ + private final bool identity; + + /** + * The list of listeners. Initially empty but initialized + * to an array of size capacity the first time a listener is added. + * Maintains invariant: listeners !is null + */ + private Object[] listeners; + + /** + * Creates a listener list in which listeners are compared using equality. + */ + public this() { + this(EQUALITY); + } + + /** + * Creates a listener list using the provided comparison mode. + * + * @param mode The mode used to determine if listeners are the same. + */ + public this(int mode) { + if (mode !is EQUALITY && mode !is IDENTITY) + throw new IllegalArgumentException( null ); + this.identity = mode is IDENTITY; + } + + /** + * Adds a listener to this list. This method has no effect if the same + * listener is already registered. + * + * @param listener the non-null listener to add + */ + public synchronized void add(Object listener) { + // This method is synchronized to protect against multiple threads adding + // or removing listeners concurrently. This does not block concurrent readers. + if (listener is null) + throw new IllegalArgumentException( null ); + // check for duplicates + final int oldSize = listeners.length; + for (int i = 0; i < oldSize; ++i) { + Object listener2 = listeners[i]; + if (identity ? listener is listener2 : listener.opEquals(listener2)) + return; + } + // Thread safety: create new array to avoid affecting concurrent readers + Object[] newListeners = new Object[oldSize + 1]; + System.arraycopy(listeners, 0, newListeners, 0, oldSize); + newListeners[oldSize] = listener; + //atomic assignment + this.listeners = newListeners; + } + + /** + * Returns an array containing all the registered listeners. + * The resulting array is unaffected by subsequent adds or removes. + * If there are no listeners registered, the result is an empty array. + * Use this method when notifying listeners, so that any modifications + * to the listener list during the notification will have no effect on + * the notification itself. + *

+ * Note: Callers of this method must not modify the returned array. + * + * @return the list of registered listeners + */ + public Object[] getListeners() { + return listeners; + } + + /** + * Returns whether this listener list is empty. + * + * @return true if there are no registered listeners, and + * false otherwise + */ + public bool isEmpty() { + return listeners.length is 0; + } + + /** + * Removes a listener from this list. Has no effect if the same + * listener was not already registered. + * + * @param listener the non-null listener to remove + */ + public synchronized void remove(Object listener) { + // This method is synchronized to protect against multiple threads adding + // or removing listeners concurrently. This does not block concurrent readers. + if (listener is null) + throw new IllegalArgumentException( null ); + int oldSize = listeners.length; + for (int i = 0; i < oldSize; ++i) { + Object listener2 = listeners[i]; + if (identity ? listener is listener2 : listener.opEquals(listener2)) { + if (oldSize is 1) { + listeners = EmptyArray; + } else { + // Thread safety: create new array to avoid affecting concurrent readers + Object[] newListeners = new Object[oldSize - 1]; + System.arraycopy(listeners, 0, newListeners, 0, i); + System.arraycopy(listeners, i + 1, newListeners, i, oldSize - i - 1); + //atomic assignment to field + this.listeners = newListeners; + } + return; + } + } + } + + /** + * Returns the number of registered listeners. + * + * @return the number of registered listeners + */ + public int size() { + return listeners.length; + } + + /** + * Removes all listeners from this list. + */ + public synchronized void clear() { + listeners = EmptyArray; + } +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/runtime/MultiStatus.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/runtime/MultiStatus.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,159 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.runtime.MultiStatus; + +import dwtx.core.runtime.Assert; +import dwtx.core.runtime.Status; +import dwtx.core.runtime.IStatus; + +import dwt.dwthelper.utils; +static import tango.text.Text; + +/** + * A concrete multi-status implementation, + * suitable either for instantiating or subclassing. + *

+ * This class can be used without OSGi running. + *

+ */ +public class MultiStatus : Status { + + /** List of child statuses. + */ + private IStatus[] children; + + /** + * Creates and returns a new multi-status object with the given children. + * + * @param pluginId the unique identifier of the relevant plug-in + * @param code the plug-in-specific status code + * @param newChildren the list of children status objects + * @param message a human-readable message, localized to the + * current locale + * @param exception a low-level exception, or null if not + * applicable + */ + public this(String pluginId, int code, IStatus[] newChildren, String message, Exception exception) { + this(pluginId, code, message, exception); + Assert.isLegal(newChildren !is null); + int maxSeverity = getSeverity(); + for (int i = 0; i < newChildren.length; i++) { + Assert.isLegal(newChildren[i] !is null); + int severity = newChildren[i].getSeverity(); + if (severity > maxSeverity) + maxSeverity = severity; + } + this.children = new IStatus[newChildren.length]; + setSeverity(maxSeverity); + SimpleType!(IStatus).arraycopy(newChildren, 0, this.children, 0, newChildren.length); + } + + /** + * Creates and returns a new multi-status object with no children. + * + * @param pluginId the unique identifier of the relevant plug-in + * @param code the plug-in-specific status code + * @param message a human-readable message, localized to the + * current locale + * @param exception a low-level exception, or null if not + * applicable + */ + public this(String pluginId, int code, String message, Exception exception) { + super(OK, pluginId, code, message, exception); + children = new IStatus[0]; + } + + /** + * Adds the given status to this multi-status. + * + * @param status the new child status + */ + public void add(IStatus status) { + Assert.isLegal(status !is null); + IStatus[] result = new IStatus[children.length + 1]; + SimpleType!(IStatus).arraycopy(children, 0, result, 0, children.length); + result[result.length - 1] = status; + children = result; + int newSev = status.getSeverity(); + if (newSev > getSeverity()) { + setSeverity(newSev); + } + } + + /** + * Adds all of the children of the given status to this multi-status. + * Does nothing if the given status has no children (which includes + * the case where it is not a multi-status). + * + * @param status the status whose children are to be added to this one + */ + public void addAll(IStatus status) { + Assert.isLegal(status !is null); + IStatus[] statuses = status.getChildren(); + for (int i = 0; i < statuses.length; i++) { + add(statuses[i]); + } + } + + /* (Intentionally not javadoc'd) + * Implements the corresponding method on IStatus. + */ + public IStatus[] getChildren() { + return children; + } + + /* (Intentionally not javadoc'd) + * Implements the corresponding method on IStatus. + */ + public bool isMultiStatus() { + return true; + } + + /** + * Merges the given status into this multi-status. + * Equivalent to add(status) if the + * given status is not a multi-status. + * Equivalent to addAll(status) if the + * given status is a multi-status. + * + * @param status the status to merge into this one + * @see #add(IStatus) + * @see #addAll(IStatus) + */ + public void merge(IStatus status) { + Assert.isLegal(status !is null); + if (!status.isMultiStatus()) { + add(status); + } else { + addAll(status); + } + } + + /** + * Returns a string representation of the status, suitable + * for debugging purposes only. + */ + public String toString() { + tango.text.Text.Text!(char) buf = new tango.text.Text.Text!(char); + buf.append(super.toString()); + buf.append(" children=["); //$NON-NLS-1$ + for (int i = 0; i < children.length; i++) { + if (i !is 0) { + buf.append(" "); //$NON-NLS-1$ + } + buf.append( (cast(Object)children[i]).toString()); + } + buf.append("]"); //$NON-NLS-1$ + return buf.toString(); + } +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/runtime/NullProgressMonitor.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/runtime/NullProgressMonitor.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,131 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.runtime.NullProgressMonitor; + +import dwtx.core.runtime.IProgressMonitor; + +import dwt.dwthelper.utils; + +/** + * A default progress monitor implementation suitable for + * subclassing. + *

+ * This implementation supports cancelation. The default + * implementations of the other methods do nothing. + *

+ * This class can be used without OSGi running. + *

+ */ +public class NullProgressMonitor : IProgressMonitor { + + /** + * Indicates whether cancel has been requested. + */ + private bool cancelled = false; + + /** + * Constructs a new progress monitor. + */ + public this() { + } + + /** + * This implementation does nothing. + * Subclasses may override this method to do interesting + * processing when a task begins. + * + * @see IProgressMonitor#beginTask(String, int) + */ + public void beginTask(String name, int totalWork) { + // do nothing + } + + /** + * This implementation does nothing. + * Subclasses may override this method to do interesting + * processing when a task is done. + * + * @see IProgressMonitor#done() + */ + public void done() { + // do nothing + } + + /** + * This implementation does nothing. + * Subclasses may override this method. + * + * @see IProgressMonitor#internalWorked(double) + */ + public void internalWorked(double work) { + // do nothing + } + + /** + * This implementation returns the value of the internal + * state variable set by setCanceled. + * Subclasses which override this method should + * override setCanceled as well. + * + * @see IProgressMonitor#isCanceled() + * @see IProgressMonitor#setCanceled(bool) + */ + public bool isCanceled() { + return cancelled; + } + + /** + * This implementation sets the value of an internal state variable. + * Subclasses which override this method should override + * isCanceled as well. + * + * @see IProgressMonitor#isCanceled() + * @see IProgressMonitor#setCanceled(bool) + */ + public void setCanceled(bool cancelled) { + this.cancelled = cancelled; + } + + /** + * This implementation does nothing. + * Subclasses may override this method to do something + * with the name of the task. + * + * @see IProgressMonitor#setTaskName(String) + */ + public void setTaskName(String name) { + // do nothing + } + + /** + * This implementation does nothing. + * Subclasses may override this method to do interesting + * processing when a subtask begins. + * + * @see IProgressMonitor#subTask(String) + */ + public void subTask(String name) { + // do nothing + } + + /** + * This implementation does nothing. + * Subclasses may override this method to do interesting + * processing when some work has been completed. + * + * @see IProgressMonitor#worked(int) + */ + public void worked(int work) { + // do nothing + } +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/runtime/OperationCanceledException.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/runtime/OperationCanceledException.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,48 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.runtime.OperationCanceledException; + +import dwt.dwthelper.utils; + +/** + * This exception is thrown to blow out of a long-running method + * when the user cancels it. + *

+ * This class can be used without OSGi running. + *

+ * This class is not intended to be subclassed by clients but + * may be instantiated. + *

+ */ +public final class OperationCanceledException : RuntimeException { + /** + * All serializable objects should have a stable serialVersionUID + */ + private static const long serialVersionUID = 1L; + + /** + * Creates a new exception. + */ + public this() { + super(); + } + + /** + * Creates a new exception with the given message. + * + * @param message the message for the exception + */ + public this(String message) { + super(message); + } +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/runtime/Path.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/runtime/Path.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,1006 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.runtime.Path; + +import tango.io.FilePath; +import dwtx.core.runtime.IPath; +import dwtx.core.runtime.Assert; + +import dwt.dwthelper.utils; + +import tango.io.FileConst; + +static import tango.text.Text; +alias tango.text.Text.Text!(char) StringBuffer; + +/** + * The standard implementation of the IPath interface. + * Paths are always maintained in canonicalized form. That is, parent + * references (i.e., ../../) and duplicate separators are + * resolved. For example, + *
     new Path("/a/b").append("../foo/bar")
+ * will yield the path + *
     /a/foo/bar
+ *

+ * This class can be used without OSGi running. + *

+ * This class is not intended to be subclassed by clients but + * may be instantiated. + *

+ * @see IPath + */ +public class Path : IPath, Cloneable { + /** masks for separator values */ + private static const int HAS_LEADING = 1; + private static const int IS_UNC = 2; + private static const int HAS_TRAILING = 4; + + private static const int ALL_SEPARATORS = HAS_LEADING | IS_UNC | HAS_TRAILING; + + /** Constant empty string value. */ + private static const String EMPTY_STRING = ""; //$NON-NLS-1$ + + /** Constant value indicating no segments */ + private static const String[] NO_SEGMENTS = null; + + /** Constant value containing the empty path with no device. */ + public static const Path EMPTY; + + /** Mask for all bits that are involved in the hash code */ + private static const int HASH_MASK = ~HAS_TRAILING; + + + /** Constant root path string ("/"). */ + private static const String ROOT_STRING = "/"; //$NON-NLS-1$ + + /** Constant value containing the root path with no device. */ + public static const Path ROOT; + + /** Constant value indicating if the current platform is Windows */ + version(Windows){ + private static const bool WINDOWS = true; + } + else { + private static const bool WINDOWS = false; + } + + static this(){ + EMPTY = new Path(EMPTY_STRING); + ROOT = new Path(ROOT_STRING); + } + + /** The device id string. May be null if there is no device. */ + private String device = null; + + //Private implementation note: the segments and separators + //arrays are never modified, so that they can be shared between + //path instances + + /** The path segments */ + private String[] segments_; + + /** flags indicating separators (has leading, is UNC, has trailing) */ + private int separators; + + /** + * Constructs a new path from the given string path. + * The string path must represent a valid file system path + * on the local file system. + * The path is canonicalized and double slashes are removed + * except at the beginning. (to handle UNC paths). All forward + * slashes ('/') are treated as segment delimiters, and any + * segment and device delimiters for the local file system are + * also respected. + * + * @param pathString the portable string path + * @see IPath#toPortableString() + * @since 3.1 + */ + public static IPath fromOSString(String pathString) { + return new Path(pathString); + } + + /** + * Constructs a new path from the given path string. + * The path string must have been produced by a previous + * call to IPath.toPortableString. + * + * @param pathString the portable path string + * @see IPath#toPortableString() + * @since 3.1 + */ + public static IPath fromPortableString(String pathString) { + int firstMatch = pathString.indexOf(DEVICE_SEPARATOR) +1; + //no extra work required if no device characters + if (firstMatch <= 0) + return (new Path()).initialize(null, pathString); + //if we find a single colon, then the path has a device + String devicePart = null; + int pathLength = pathString.length; + if (firstMatch is pathLength || pathString.charAt(firstMatch) !is DEVICE_SEPARATOR) { + devicePart = pathString.substring(0, firstMatch); + pathString = pathString.substring(firstMatch, pathLength); + } + //optimize for no colon literals + if (pathString.indexOf(DEVICE_SEPARATOR) is -1) + return (new Path()).initialize(devicePart, pathString); + //contract colon literals + char[] chars = pathString/+.toCharArray()+/; + int readOffset = 0, writeOffset = 0, length = chars.length; + while (readOffset < length) { + if (chars[readOffset] is DEVICE_SEPARATOR) + if (++readOffset >= length) + break; + chars[writeOffset++] = chars[readOffset++]; + } + return (new Path()).initialize(devicePart, chars[ 0 .. writeOffset] ); + } + + /* (Intentionally not included in javadoc) + * Private constructor. + */ + private this() { + // not allowed + } + + /** + * Constructs a new path from the given string path. + * The string path must represent a valid file system path + * on the local file system. + * The path is canonicalized and double slashes are removed + * except at the beginning. (to handle UNC paths). All forward + * slashes ('/') are treated as segment delimiters, and any + * segment and device delimiters for the local file system are + * also respected (such as colon (':') and backslash ('\') on some file systems). + * + * @param fullPath the string path + * @see #isValidPath(String) + */ + public this(String fullPath) { + String devicePart = null; + if (WINDOWS) { + //convert backslash to forward slash + fullPath = fullPath.indexOf('\\') is -1 ? fullPath : fullPath.replace('\\', SEPARATOR); + //extract device + int i = fullPath.indexOf(DEVICE_SEPARATOR); + if (i !is -1) { + //remove leading slash from device part to handle output of URL.getFile() + int start = fullPath.charAt(0) is SEPARATOR ? 1 : 0; + devicePart = fullPath.substring(start, i + 1); + fullPath = fullPath.substring(i + 1, fullPath.length); + } + } + initialize(devicePart, fullPath); + } + + /** + * Constructs a new path from the given device id and string path. + * The given string path must be valid. + * The path is canonicalized and double slashes are removed except + * at the beginning (to handle UNC paths). All forward + * slashes ('/') are treated as segment delimiters, and any + * segment delimiters for the local file system are + * also respected (such as backslash ('\') on some file systems). + * + * @param device the device id + * @param path the string path + * @see #isValidPath(String) + * @see #setDevice(String) + */ + public this(String device, String path) { + if (WINDOWS) { + //convert backslash to forward slash + path = path.indexOf('\\') is -1 ? path : path.replace('\\', SEPARATOR); + } + initialize(device, path); + } + + /* (Intentionally not included in javadoc) + * Private constructor. + */ + private this(String device, String[] segments_, int _separators) { + // no segment validations are done for performance reasons + this.segments_ = segments_; + this.device = device; + //hash code is cached in all but the bottom three bits of the separators field + this.separators = (computeHashCode() << 3) | (_separators & ALL_SEPARATORS); + } + + /* (Intentionally not included in javadoc) + * @see IPath#addFileExtension + */ + public IPath addFileExtension(String extension) { + if (isRoot() || isEmpty() || hasTrailingSeparator()) + return this; + int len = segments_.length; + String[] newSegments = new String[len]; + System.arraycopy(segments_, 0, newSegments, 0, len - 1); + newSegments[len - 1] = segments_[len - 1] ~ '.' ~ extension; + return new Path(device, newSegments, separators); + } + + /* (Intentionally not included in javadoc) + * @see IPath#addTrailingSeparator + */ + public IPath addTrailingSeparator() { + if (hasTrailingSeparator() || isRoot()) { + return this; + } + //XXX workaround, see 1GIGQ9V + if (isEmpty()) { + return new Path(device, segments_, HAS_LEADING); + } + return new Path(device, segments_, separators | HAS_TRAILING); + } + + /* (Intentionally not included in javadoc) + * @see IPath#append(IPath) + */ + public IPath append(IPath tail) { + //optimize some easy cases + if (tail is null || tail.segmentCount() is 0) + return this; + //these call chains look expensive, but in most cases they are no-ops + if (this.isEmpty()) + return tail.setDevice(device).makeRelative().makeUNC(isUNC()); + if (this.isRoot()) + return tail.setDevice(device).makeAbsolute().makeUNC(isUNC()); + + //concatenate the two segment arrays + int myLen = segments_.length; + int tailLen = tail.segmentCount(); + String[] newSegments = new String[myLen + tailLen]; + System.arraycopy(segments_, 0, newSegments, 0, myLen); + for (int i = 0; i < tailLen; i++) { + newSegments[myLen + i] = tail.segment(i); + } + //use my leading separators and the tail's trailing separator + Path result = new Path(device, newSegments, (separators & (HAS_LEADING | IS_UNC)) | (tail.hasTrailingSeparator() ? HAS_TRAILING : 0)); + String tailFirstSegment = newSegments[myLen]; + if (tailFirstSegment.equals("..") || tailFirstSegment.equals(".")) { //$NON-NLS-1$ //$NON-NLS-2$ + result.canonicalize(); + } + return result; + } + + /* (Intentionally not included in javadoc) + * @see IPath#append(java.lang.String) + */ + public IPath append(String tail) { + //optimize addition of a single segment + if (tail.indexOf(SEPARATOR) is -1 && tail.indexOf("\\") is -1 && tail.indexOf(DEVICE_SEPARATOR) is -1) { //$NON-NLS-1$ + int tailLength = tail.length; + if (tailLength < 3) { + //some special cases + if (tailLength is 0 || ".".equals(tail)) { //$NON-NLS-1$ + return this; + } + if ("..".equals(tail)) //$NON-NLS-1$ + return removeLastSegments(1); + } + //just add the segment + int myLen = segments_.length; + String[] newSegments = new String[myLen + 1]; + System.arraycopy(segments_, 0, newSegments, 0, myLen); + newSegments[myLen] = tail; + return new Path(device, newSegments, separators & ~HAS_TRAILING); + } + //go with easy implementation + return append(new Path(tail)); + } + + /** + * Destructively converts this path to its canonical form. + *

+ * In its canonical form, a path does not have any + * "." segments, and parent references ("..") are collapsed + * where possible. + *

+ * @return true if the path was modified, and false otherwise. + */ + private bool canonicalize() { + //look for segments that need canonicalizing + for (int i = 0, max = segments_.length; i < max; i++) { + String segment = segments_[i]; + if (segment.charAt(0) is '.' && (segment.equals("..") || segment.equals("."))) { //$NON-NLS-1$ //$NON-NLS-2$ + //path needs to be canonicalized + collapseParentReferences(); + //paths of length 0 have no trailing separator + if (segments_.length is 0) + separators &= (HAS_LEADING | IS_UNC); + //recompute hash because canonicalize affects hash + separators = (separators & ALL_SEPARATORS) | (computeHashCode() << 3); + return true; + } + } + return false; + } + + /* (Intentionally not included in javadoc) + * Clones this object. + */ + public Path clone() { + return new Path(device, segments_, separators); + } + + /** + * Destructively removes all occurrences of ".." segments from this path. + */ + private void collapseParentReferences() { + int segmentCount = segments_.length; + String[] stack = new String[segmentCount]; + int stackPointer = 0; + for (int i = 0; i < segmentCount; i++) { + String segment = segments_[i]; + if (segment.equals("..")) { //$NON-NLS-1$ + if (stackPointer is 0) { + // if the stack is empty we are going out of our scope + // so we need to accumulate segments. But only if the original + // path is relative. If it is absolute then we can't go any higher than + // root so simply toss the .. references. + if (!isAbsolute()) + stack[stackPointer++] = segment; //stack push + } else { + // if the top is '..' then we are accumulating segments so don't pop + if ("..".equals(stack[stackPointer - 1])) //$NON-NLS-1$ + stack[stackPointer++] = ".."; //$NON-NLS-1$ + else + stackPointer--; + //stack pop + } + //collapse current references + } else if (!segment.equals(".") || segmentCount is 1) //$NON-NLS-1$ + stack[stackPointer++] = segment; //stack push + } + //if the number of segments hasn't changed, then no modification needed + if (stackPointer is segmentCount) + return; + //build the new segment array backwards by popping the stack + String[] newSegments = new String[stackPointer]; + System.arraycopy(stack, 0, newSegments, 0, stackPointer); + this.segments_ = newSegments; + } + + /** + * Removes duplicate slashes from the given path, with the exception + * of leading double slash which represents a UNC path. + */ + private String collapseSlashes(String path) { + int length = path.length; + // if the path is only 0, 1 or 2 chars long then it could not possibly have illegal + // duplicate slashes. + if (length < 3) + return path; + // check for an occurrence of // in the path. Start at index 1 to ensure we skip leading UNC // + // If there are no // then there is nothing to collapse so just return. + if (path.indexOf("//", 1) is -1) //$NON-NLS-1$ + return path; + // We found an occurrence of // in the path so do the slow collapse. + char[] result = new char[path.length]; + int count = 0; + bool hasPrevious = false; + char[] characters = path/+.toCharArray()+/; + for (int index = 0; index < characters.length; index++) { + char c = characters[index]; + if (c is SEPARATOR) { + if (hasPrevious) { + // skip double slashes, except for beginning of UNC. + // note that a UNC path can't have a device. + if (device is null && index is 1) { + result[count] = c; + count++; + } + } else { + hasPrevious = true; + result[count] = c; + count++; + } + } else { + hasPrevious = false; + result[count] = c; + count++; + } + } + return result[ 0 .. count]; + } + + /* (Intentionally not included in javadoc) + * Computes the hash code for this object. + */ + private int computeHashCode() { + int hash = device.length is 0 ? 17 : dwt.dwthelper.utils.toHash(device); + int segmentCount = segments_.length; + for (int i = 0; i < segmentCount; i++) { + //this function tends to given a fairly even distribution + hash = hash * 37 + dwt.dwthelper.utils.toHash(segments_[i]); + } + return hash; + } + + /* (Intentionally not included in javadoc) + * Returns the size of the string that will be created by toString or toOSString. + */ + private int computeLength() { + int length = 0; + if (device !is null) + length += device.length; + if ((separators & HAS_LEADING) !is 0) + length++; + if ((separators & IS_UNC) !is 0) + length++; + //add the segment lengths + int max = segments_.length; + if (max > 0) { + for (int i = 0; i < max; i++) { + length += segments_[i].length; + } + //add the separator lengths + length += max - 1; + } + if ((separators & HAS_TRAILING) !is 0) + length++; + return length; + } + + /* (Intentionally not included in javadoc) + * Returns the number of segments in the given path + */ + private int computeSegmentCount(String path) { + int len = path.length; + if (len is 0 || (len is 1 && path.charAt(0) is SEPARATOR)) { + return 0; + } + int count = 1; + int prev = -1; + int i; + while ((i = path.indexOf(SEPARATOR, prev + 1)) !is -1) { + if (i !is prev + 1 && i !is len) { + ++count; + } + prev = i; + } + if (path.charAt(len - 1) is SEPARATOR) { + --count; + } + return count; + } + + /** + * Computes the segment array for the given canonicalized path. + */ + private String[] computeSegments(String path) { + // performance sensitive --- avoid creating garbage + int segmentCount = computeSegmentCount(path); + if (segmentCount is 0) + return NO_SEGMENTS; + String[] newSegments = new String[segmentCount]; + int len = path.length; + // check for initial slash + int firstPosition = (path.charAt(0) is SEPARATOR) ? 1 : 0; + // check for UNC + if (firstPosition is 1 && len > 1 && (path.charAt(1) is SEPARATOR)) + firstPosition = 2; + int lastPosition = (path.charAt(len - 1) !is SEPARATOR) ? len - 1 : len - 2; + // for non-empty paths, the number of segments is + // the number of slashes plus 1, ignoring any leading + // and trailing slashes + int next = firstPosition; + for (int i = 0; i < segmentCount; i++) { + int start = next; + int end = path.indexOf(SEPARATOR, next); + if (end is -1) { + newSegments[i] = path.substring(start, lastPosition + 1); + } else { + newSegments[i] = path.substring(start, end); + } + next = end + 1; + } + return newSegments; + } + /** + * Returns the platform-neutral encoding of the given segment onto + * the given string buffer. This escapes literal colon characters with double colons. + */ + private void encodeSegment(String string, StringBuffer buf) { + int len = string.length; + for (int i = 0; i < len; i++) { + char c = string.charAt(i); + buf.append(c); + if (c is DEVICE_SEPARATOR) + buf.append(DEVICE_SEPARATOR); + } + } + + /* (Intentionally not included in javadoc) + * Compares objects for equality. + */ + public override int opEquals(Object obj) { + if (this is obj) + return true; + if (!(cast(Path)obj)) + return false; + Path target = cast(Path) obj; + //check leading separators and hash code + if ((separators & HASH_MASK) !is (target.separators & HASH_MASK)) + return false; + String[] targetSegments = target.segments_; + int i = segments_.length; + //check segment count + if (i !is targetSegments.length) + return false; + //check segments in reverse order - later segments more likely to differ + while (--i >= 0) + if (!segments_[i].equals(targetSegments[i])) + return false; + //check device last (least likely to differ) + return device is target.device || (device !is null && device.equals(target.device)); + } + + /* (Intentionally not included in javadoc) + * @see IPath#getDevice + */ + public String getDevice() { + return device; + } + + /* (Intentionally not included in javadoc) + * @see IPath#getFileExtension + */ + public String getFileExtension() { + if (hasTrailingSeparator()) { + return null; + } + String lastSegment = lastSegment(); + if (lastSegment is null) { + return null; + } + int index = lastSegment.lastIndexOf('.'); + if (index is -1) { + return null; + } + return lastSegment.substring(index + 1); + } + + /* (Intentionally not included in javadoc) + * Computes the hash code for this object. + */ + public int hashCode() { + return separators & HASH_MASK; + } + + /* (Intentionally not included in javadoc) + * @see IPath#hasTrailingSeparator2 + */ + public bool hasTrailingSeparator() { + return (separators & HAS_TRAILING) !is 0; + } + + /* + * Initialize the current path with the given string. + */ + private IPath initialize(String deviceString, String path) { + Assert.isTrue(path.length > 0); + this.device = deviceString; + + path = collapseSlashes(path); + int len = path.length; + + //compute the separators array + if (len < 2) { + if (len is 1 && path.charAt(0) is SEPARATOR) { + this.separators = HAS_LEADING; + } else { + this.separators = 0; + } + } else { + bool hasLeading = path.charAt(0) is SEPARATOR; + bool isUNC = hasLeading && path.charAt(1) is SEPARATOR; + //UNC path of length two has no trailing separator + bool hasTrailing = !(isUNC && len is 2) && path.charAt(len - 1) is SEPARATOR; + separators = hasLeading ? HAS_LEADING : 0; + if (isUNC) + separators |= IS_UNC; + if (hasTrailing) + separators |= HAS_TRAILING; + } + //compute segments and ensure canonical form + segments_ = computeSegments(path); + if (!canonicalize()) { + //compute hash now because canonicalize didn't need to do it + separators = (separators & ALL_SEPARATORS) | (computeHashCode() << 3); + } + return this; + } + + /* (Intentionally not included in javadoc) + * @see IPath#isAbsolute + */ + public bool isAbsolute() { + //it's absolute if it has a leading separator + return (separators & HAS_LEADING) !is 0; + } + + /* (Intentionally not included in javadoc) + * @see IPath#isEmpty + */ + public bool isEmpty() { + //true if no segments and no leading prefix + return segments_.length is 0 && ((separators & ALL_SEPARATORS) !is HAS_LEADING); + + } + + /* (Intentionally not included in javadoc) + * @see IPath#isPrefixOf + */ + public bool isPrefixOf(IPath anotherPath) { + if (device is null) { + if (anotherPath.getDevice() !is null) { + return false; + } + } else { + if (!device.equalsIgnoreCase(anotherPath.getDevice())) { + return false; + } + } + if (isEmpty() || (isRoot() && anotherPath.isAbsolute())) { + return true; + } + int len = segments_.length; + if (len > anotherPath.segmentCount()) { + return false; + } + for (int i = 0; i < len; i++) { + if (!segments_[i].equals(anotherPath.segment(i))) + return false; + } + return true; + } + + /* (Intentionally not included in javadoc) + * @see IPath#isRoot + */ + public bool isRoot() { + //must have no segments, a leading separator, and not be a UNC path. + return this is ROOT || (segments_.length is 0 && ((separators & ALL_SEPARATORS) is HAS_LEADING)); + } + + /* (Intentionally not included in javadoc) + * @see IPath#isUNC + */ + public bool isUNC() { + if (device !is null) + return false; + return (separators & IS_UNC) !is 0; + } + + /* (Intentionally not included in javadoc) + * @see IPath#isValidPath(String) + */ + public bool isValidPath(String path) { + Path test = new Path(path); + for (int i = 0, max = test.segmentCount(); i < max; i++) + if (!isValidSegment(test.segment(i))) + return false; + return true; + } + + /* (Intentionally not included in javadoc) + * @see IPath#isValidSegment(String) + */ + public bool isValidSegment(String segment) { + int size = segment.length; + if (size is 0) + return false; + for (int i = 0; i < size; i++) { + char c = segment.charAt(i); + if (c is '/') + return false; + if (WINDOWS && (c is '\\' || c is ':')) + return false; + } + return true; + } + + /* (Intentionally not included in javadoc) + * @see IPath#lastSegment() + */ + public String lastSegment() { + int len = segments_.length; + return len is 0 ? null : segments_[len - 1]; + } + + /* (Intentionally not included in javadoc) + * @see IPath#makeAbsolute() + */ + public IPath makeAbsolute() { + if (isAbsolute()) { + return this; + } + Path result = new Path(device, segments_, separators | HAS_LEADING); + //may need canonicalizing if it has leading ".." or "." segments + if (result.segmentCount() > 0) { + String first = result.segment(0); + if (first.equals("..") || first.equals(".")) { //$NON-NLS-1$ //$NON-NLS-2$ + result.canonicalize(); + } + } + return result; + } + + /* (Intentionally not included in javadoc) + * @see IPath#makeRelative() + */ + public IPath makeRelative() { + if (!isAbsolute()) { + return this; + } + return new Path(device, segments_, separators & HAS_TRAILING); + } + + /* (Intentionally not included in javadoc) + * @see IPath#makeUNC(bool) + */ + public IPath makeUNC(bool toUNC) { + // if we are already in the right form then just return + if (!(toUNC ^ isUNC())) + return this; + + int newSeparators = this.separators; + if (toUNC) { + newSeparators |= HAS_LEADING | IS_UNC; + } else { + //mask out the UNC bit + newSeparators &= HAS_LEADING | HAS_TRAILING; + } + return new Path(toUNC ? null : device, segments_, newSeparators); + } + + /* (Intentionally not included in javadoc) + * @see IPath#matchingFirstSegments(IPath) + */ + public int matchingFirstSegments(IPath anotherPath) { + Assert.isNotNull( cast(Object) anotherPath); + int anotherPathLen = anotherPath.segmentCount(); + int max = Math.min(segments_.length, anotherPathLen); + int count = 0; + for (int i = 0; i < max; i++) { + if (!segments_[i].equals(anotherPath.segment(i))) { + return count; + } + count++; + } + return count; + } + + /* (Intentionally not included in javadoc) + * @see IPath#removeFileExtension() + */ + public IPath removeFileExtension() { + String extension = getFileExtension(); + if (extension is null || extension.equals("")) { //$NON-NLS-1$ + return this; + } + String lastSegment = lastSegment(); + int index = lastSegment.lastIndexOf(extension) - 1; + return removeLastSegments(1).append(lastSegment.substring(0, index)); + } + + /* (Intentionally not included in javadoc) + * @see IPath#removeFirstSegments(int) + */ + public IPath removeFirstSegments(int count) { + if (count is 0) + return this; + if (count >= segments_.length) { + return new Path(device, NO_SEGMENTS, 0); + } + Assert.isLegal(count > 0); + int newSize = segments_.length - count; + String[] newSegments = new String[newSize]; + System.arraycopy(this.segments_, count, newSegments, 0, newSize); + + //result is always a relative path + return new Path(device, newSegments, separators & HAS_TRAILING); + } + + /* (Intentionally not included in javadoc) + * @see IPath#removeLastSegments(int) + */ + public IPath removeLastSegments(int count) { + if (count is 0) + return this; + if (count >= segments_.length) { + //result will have no trailing separator + return new Path(device, NO_SEGMENTS, separators & (HAS_LEADING | IS_UNC)); + } + Assert.isLegal(count > 0); + int newSize = segments_.length - count; + String[] newSegments = new String[newSize]; + System.arraycopy(this.segments_, 0, newSegments, 0, newSize); + return new Path(device, newSegments, separators); + } + + /* (Intentionally not included in javadoc) + * @see IPath#removeTrailingSeparator() + */ + public IPath removeTrailingSeparator() { + if (!hasTrailingSeparator()) { + return this; + } + return new Path(device, segments_, separators & (HAS_LEADING | IS_UNC)); + } + + /* (Intentionally not included in javadoc) + * @see IPath#segment(int) + */ + public String segment(int index) { + if (index >= segments_.length) + return null; + return segments_[index]; + } + + /* (Intentionally not included in javadoc) + * @see IPath#segmentCount() + */ + public int segmentCount() { + return segments_.length; + } + + /* (Intentionally not included in javadoc) + * @see IPath#segments() + */ + public String[] segments() { + String[] segmentCopy = new String[](segments_.length); + System.arraycopy(segments_, 0, segmentCopy, 0, segments_.length); + return segmentCopy; + } + + /* (Intentionally not included in javadoc) + * @see IPath#setDevice(String) + */ + public IPath setDevice(String value) { + if (value !is null) { + Assert.isTrue(value.indexOf(IPath.DEVICE_SEPARATOR) is (value.length - 1), "Last character should be the device separator"); //$NON-NLS-1$ + } + //return the receiver if the device is the same + if (value is device || (value !is null && value.equals(device))) + return this; + + return new Path(value, segments_, separators); + } + + /* (Intentionally not included in javadoc) + * @see IPath#toFile() + */ + public FilePath toFile() { + return new FilePath(toOSString()); + } + + /* (Intentionally not included in javadoc) + * @see IPath#toOSString() + */ + public String toOSString() { + //Note that this method is identical to toString except + //it uses the OS file separator instead of the path separator + int resultSize = computeLength(); + if (resultSize <= 0) + return EMPTY_STRING; + char FILE_SEPARATOR = FileConst.PathSeparatorChar; + char[] result = new char[resultSize]; + int offset = 0; + if (device !is null) { + int size = device.length; + device.getChars(0, size, result, offset); + offset += size; + } + if ((separators & HAS_LEADING) !is 0) + result[offset++] = FILE_SEPARATOR; + if ((separators & IS_UNC) !is 0) + result[offset++] = FILE_SEPARATOR; + int len = segments_.length - 1; + if (len >= 0) { + //append all but the last segment, with separators + for (int i = 0; i < len; i++) { + int size = segments_[i].length; + segments_[i].getChars(0, size, result, offset); + offset += size; + result[offset++] = FILE_SEPARATOR; + } + //append the last segment + int size = segments_[len].length; + segments_[len].getChars(0, size, result, offset); + offset += size; + } + if ((separators & HAS_TRAILING) !is 0) + result[offset++] = FILE_SEPARATOR; + return result; + } + + /* (Intentionally not included in javadoc) + * @see IPath#toPortableString() + */ + public String toPortableString() { + int resultSize = computeLength(); + if (resultSize <= 0) + return EMPTY_STRING; + StringBuffer result = new StringBuffer(resultSize); + if (device !is null) + result.append(device); + if ((separators & HAS_LEADING) !is 0) + result.append(SEPARATOR); + if ((separators & IS_UNC) !is 0) + result.append(SEPARATOR); + int len = segments_.length; + //append all segments with separators + for (int i = 0; i < len; i++) { + if (segments_[i].indexOf(DEVICE_SEPARATOR) >= 0) + encodeSegment(segments_[i], result); + else + result.append(segments_[i]); + if (i < len-1 || (separators & HAS_TRAILING) !is 0) + result.append(SEPARATOR); + } + return result.toString(); + } + + /* (Intentionally not included in javadoc) + * @see IPath#toString() + */ + public String toString() { + int resultSize = computeLength(); + if (resultSize <= 0) + return EMPTY_STRING; + char[] result = new char[resultSize]; + int offset = 0; + if (device !is null) { + int size = device.length; + device.getChars(0, size, result, offset); + offset += size; + } + if ((separators & HAS_LEADING) !is 0) + result[offset++] = SEPARATOR; + if ((separators & IS_UNC) !is 0) + result[offset++] = SEPARATOR; + int len = segments_.length - 1; + if (len >= 0) { + //append all but the last segment, with separators + for (int i = 0; i < len; i++) { + int size = segments_[i].length; + segments_[i].getChars(0, size, result, offset); + offset += size; + result[offset++] = SEPARATOR; + } + //append the last segment + int size = segments_[len].length; + segments_[len].getChars(0, size, result, offset); + offset += size; + } + if ((separators & HAS_TRAILING) !is 0) + result[offset++] = SEPARATOR; + return result; + } + + /* (Intentionally not included in javadoc) + * @see IPath#uptoSegment(int) + */ + public IPath uptoSegment(int count) { + if (count is 0) + return new Path(device, NO_SEGMENTS, separators & (HAS_LEADING | IS_UNC)); + if (count >= segments_.length) + return this; + Assert.isTrue(count > 0, "Invalid parameter to Path.uptoSegment"); //$NON-NLS-1$ + String[] newSegments = new String[count]; + System.arraycopy(segments_, 0, newSegments, 0, count); + return new Path(device, newSegments, separators); + } +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/runtime/ProgressMonitorWrapper.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/runtime/ProgressMonitorWrapper.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,179 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.runtime.ProgressMonitorWrapper; + +import dwtx.core.runtime.IProgressMonitor; +import dwtx.core.runtime.IProgressMonitorWithBlocking; +import dwtx.core.runtime.IStatus; +import dwtx.core.runtime.Assert; + +import dwt.dwthelper.utils; + +/** + * An abstract wrapper around a progress monitor which, + * unless overridden, forwards IProgressMonitor + * and IProgressMonitorWithBlocking methods to the wrapped progress monitor. + *

+ * This class can be used without OSGi running. + *

+ * Clients may subclass. + *

+ */ +public abstract class ProgressMonitorWrapper : IProgressMonitor, IProgressMonitorWithBlocking { + + /** The wrapped progress monitor. */ + private IProgressMonitor progressMonitor; + + /** + * Creates a new wrapper around the given monitor. + * + * @param monitor the progress monitor to forward to + */ + protected this(IProgressMonitor monitor) { + Assert.isNotNull(cast(Object)monitor); + progressMonitor = monitor; + } + + /** + * This implementation of a IProgressMonitor + * method forwards to the wrapped progress monitor. + * Clients may override this method to do additional + * processing. + * + * @see IProgressMonitor#beginTask(String, int) + */ + public void beginTask(String name, int totalWork) { + progressMonitor.beginTask(name, totalWork); + } + + /** + * This implementation of a IProgressMonitorWithBlocking + * method forwards to the wrapped progress monitor. + * Clients may override this method to do additional + * processing. + * + * @see IProgressMonitorWithBlocking#clearBlocked() + * @since 3.0 + */ + public void clearBlocked() { + if ( auto mon = cast(IProgressMonitorWithBlocking)progressMonitor ) + mon.clearBlocked(); + } + + /** + * This implementation of a IProgressMonitor + * method forwards to the wrapped progress monitor. + * Clients may override this method to do additional + * processing. + * + * @see IProgressMonitor#done() + */ + public void done() { + progressMonitor.done(); + } + + /** + * Returns the wrapped progress monitor. + * + * @return the wrapped progress monitor + */ + public IProgressMonitor getWrappedProgressMonitor() { + return progressMonitor; + } + + /** + * This implementation of a IProgressMonitor + * method forwards to the wrapped progress monitor. + * Clients may override this method to do additional + * processing. + * + * @see IProgressMonitor#internalWorked(double) + */ + public void internalWorked(double work) { + progressMonitor.internalWorked(work); + } + + /** + * This implementation of a IProgressMonitor + * method forwards to the wrapped progress monitor. + * Clients may override this method to do additional + * processing. + * + * @see IProgressMonitor#isCanceled() + */ + public bool isCanceled() { + return progressMonitor.isCanceled(); + } + + /** + * This implementation of a IProgressMonitorWithBlocking + * method forwards to the wrapped progress monitor. + * Clients may override this method to do additional + * processing. + * + * @see IProgressMonitorWithBlocking#setBlocked(IStatus) + * @since 3.0 + */ + public void setBlocked(IStatus reason) { + if ( auto mon = cast(IProgressMonitorWithBlocking)progressMonitor) + mon.setBlocked(reason); + } + + /** + * This implementation of a IProgressMonitor + * method forwards to the wrapped progress monitor. + * Clients may override this method to do additional + * processing. + * + * @see IProgressMonitor#setCanceled(bool) + */ + public void setCanceled(bool b) { + progressMonitor.setCanceled(b); + } + + /** + * This implementation of a IProgressMonitor + * method forwards to the wrapped progress monitor. + * Clients may override this method to do additional + * processing. + * + * @see IProgressMonitor#setTaskName(String) + */ + public void setTaskName(String name) { + progressMonitor.setTaskName(name); + } + + /** + * This implementation of a IProgressMonitor + * method forwards to the wrapped progress monitor. + * Clients may override this method to do additional + * processing. + * + * @see IProgressMonitor#subTask(String) + */ + public void subTask(String name) { + progressMonitor.subTask(name); + } + + /** + * This implementation of a IProgressMonitor + * method forwards to the wrapped progress monitor. + * Clients may override this method to do additional + * processing. + * + * @see IProgressMonitor#worked(int) + */ + public void worked(int work) { + progressMonitor.worked(work); + } +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/runtime/QualifiedName.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/runtime/QualifiedName.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,122 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.runtime.QualifiedName; + +import dwtx.core.runtime.Assert; + +import dwt.dwthelper.utils; + +/** + * Qualified names are two-part names: qualifier and local name. + * The qualifier must be in URI form (see RFC2396). + * Note however that the qualifier may be null if + * the default name space is being used. The empty string is not + * a valid local name. + *

+ * This class can be used without OSGi running. + *

+ * This class is not intended to be subclassed by clients. + *

+ */ +public final class QualifiedName { + + /** Qualifier part (potentially null). */ + /*package*/ + String qualifier = null; + + /** Local name part. */ + /*package*/ + String localName = null; + + /** + * Creates and returns a new qualified name with the given qualifier + * and local name. The local name must not be the empty string. + * The qualifier may be null. + *

+ * Clients may instantiate. + *

+ * @param qualifier the qualifier string, or null + * @param localName the local name string + */ + public this(String qualifier, String localName) { + Assert.isLegal(localName !is null && localName.length !is 0); + this.qualifier = qualifier; + this.localName = localName; + } + + /** + * Returns whether this qualified name is equivalent to the given object. + *

+ * Qualified names are equal if and only if they have the same + * qualified parts and local parts. + * Qualified names are not equal to objects other than qualified names. + *

+ * + * @param obj the object to compare to + * @return true if these are equivalent qualified + * names, and false otherwise + */ + public override int opEquals(Object obj) { + if (obj is this) { + return true; + } + if (!(cast(QualifiedName)obj )) { + return false; + } + QualifiedName qName = cast(QualifiedName) obj; + /* There may or may not be a qualifier */ + if (qualifier is null && qName.getQualifier() !is null) { + return false; + } + if (qualifier !is null && !qualifier.equals(qName.getQualifier())) { + return false; + } + return localName.equals(qName.getLocalName()); + } + + /** + * Returns the local part of this name. + * + * @return the local name string + */ + public String getLocalName() { + return localName; + } + + /** + * Returns the qualifier part for this qualified name, or null + * if none. + * + * @return the qualifier string, or null + */ + public String getQualifier() { + return qualifier; + } + + /* (Intentionally omitted from javadoc) + * Implements the method Object.hashCode. + * + * Returns the hash code for this qualified name. + */ + public override hash_t toHash() { + return (qualifier is null ? 0 : .toHash(qualifier)) + .toHash(localName); + } + + /** + * Converts this qualified name into a string, suitable for + * debug purposes only. + */ + public override String toString() { + return (getQualifier() is null ? "" : getQualifier() ~ ':') ~ getLocalName(); //$NON-NLS-1$ + } +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/runtime/SafeRunner.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/runtime/SafeRunner.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,76 @@ +/******************************************************************************* + * Copyright (c) 2005, 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.runtime.SafeRunner; + +import dwtx.core.runtime.ISafeRunnable; +import dwtx.core.runtime.Assert; + +import dwt.dwthelper.utils; + +/** + * Runs the given ISafeRunnable in a protected mode: exceptions + * thrown in the runnable are logged and passed to the runnable's + * exception handler. Such exceptions are not rethrown by this method. + *

+ * This class can be used without OSGi running. + *

+ * @since dwtx.equinox.common 3.2 + */ +public final class SafeRunner { + + /** + * Runs the given runnable in a protected mode. Exceptions + * thrown in the runnable are logged and passed to the runnable's + * exception handler. Such exceptions are not rethrown by this method. + * + * @param code the runnable to run + */ + public static void run(ISafeRunnable code) { + Assert.isNotNull(cast(Object)code); + try { + code.run(); + } catch (Exception e) { + handleException(code, e); +//FIXME +// } catch (LinkageError e) { +// handleException(code, e); + } + } + + private static void handleException(ISafeRunnable code, Exception e) { +//FIXME +// if (!(e instanceof OperationCanceledException)) { +// // try to obtain the correct plug-in id for the bundle providing the safe runnable +// Activator activator = Activator.getDefault(); +// String pluginId = null; +// if (activator !is null) +// pluginId = activator.getBundleId(code); +// if (pluginId is null) +// pluginId = IRuntimeConstants.PI_COMMON; +// String message = NLS.bind(CommonMessages.meta_pluginProblems, pluginId); +// IStatus status; +// if (e instanceof CoreException) { +// status = new MultiStatus(pluginId, IRuntimeConstants.PLUGIN_ERROR, message, e); +// ((MultiStatus) status).merge(((CoreException) e).getStatus()); +// } else { +// status = new Status(IStatus.ERROR, pluginId, IRuntimeConstants.PLUGIN_ERROR, message, e); +// } +// // Make sure user sees the exception: if the log is empty, log the exceptions on stderr +// if (!RuntimeLog.isEmpty()) +// RuntimeLog.log(status); +// else +// e.printStackTrace(); +// } + code.handleException(e); + } +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/runtime/Status.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/runtime/Status.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,282 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.runtime.Status; + +import dwtx.core.internal.runtime.IRuntimeConstants; +import dwtx.core.internal.runtime.LocalizationUtils; +import dwtx.core.runtime.IStatus; +import dwtx.core.runtime.Assert; + +import dwt.dwthelper.utils; +import tango.text.convert.Format; + +/** + * A concrete status implementation, suitable either for + * instantiating or subclassing. + *

+ * This class can be used without OSGi running. + *

+ */ +public class Status : IStatus { + + /** + * A standard OK status with an "ok" message. + * + * @since 3.0 + */ + public static const IStatus OK_STATUS; + /** + * A standard CANCEL status with no message. + * + * @since 3.0 + */ + public static const IStatus CANCEL_STATUS; + + static this(){ + OK_STATUS = new Status(OK, IRuntimeConstants.PI_RUNTIME, OK, LocalizationUtils.safeLocalize("ok"), null); //$NON-NLS-1$ + CANCEL_STATUS = new Status(CANCEL, IRuntimeConstants.PI_RUNTIME, 1, "", null); //$NON-NLS-1$ + } + + /** + * The severity. One of + *
    + *
  • CANCEL
  • + *
  • ERROR
  • + *
  • WARNING
  • + *
  • INFO
  • + *
  • or OK (0)
  • + *
+ */ + private int severity = OK; + + /** Unique identifier of plug-in. + */ + private String pluginId; + + /** Plug-in-specific status code. + */ + private int code; + + /** Message, localized to the current locale. + */ + private String message; + + /** Wrapped exception, or null if none. + */ + private Exception exception = null; + + /** Constant to avoid generating garbage. + */ + private static const IStatus[] theEmptyStatusArray = null; + + /** + * Creates a new status object. The created status has no children. + * + * @param severity the severity; one of OK, ERROR, + * INFO, WARNING, or CANCEL + * @param pluginId the unique identifier of the relevant plug-in + * @param code the plug-in-specific status code, or OK + * @param message a human-readable message, localized to the + * current locale + * @param exception a low-level exception, or null if not + * applicable + */ + public this(int severity, String pluginId, int code, String message, Exception exception) { + setSeverity(severity); + setPlugin(pluginId); + setCode(code); + setMessage(message); + setException(exception); + } + + /** + * Simplified constructor of a new status object; assumes that code is OK. + * The created status has no children. + * + * @param severity the severity; one of OK, ERROR, + * INFO, WARNING, or CANCEL + * @param pluginId the unique identifier of the relevant plug-in + * @param message a human-readable message, localized to the + * current locale + * @param exception a low-level exception, or null if not + * applicable + * + * @since dwtx.equinox.common 3.3 + */ + public this(int severity, String pluginId, String message, Exception exception) { + setSeverity(severity); + setPlugin(pluginId); + setMessage(message); + setException(exception); + setCode(OK); + } + + /** + * Simplified constructor of a new status object; assumes that code is OK and + * exception is null. The created status has no children. + * + * @param severity the severity; one of OK, ERROR, + * INFO, WARNING, or CANCEL + * @param pluginId the unique identifier of the relevant plug-in + * @param message a human-readable message, localized to the + * current locale + * + * @since dwtx.equinox.common 3.3 + */ + public this(int severity, String pluginId, String message) { + setSeverity(severity); + setPlugin(pluginId); + setMessage(message); + setCode(OK); + setException(null); + } + + /* (Intentionally not javadoc'd) + * Implements the corresponding method on IStatus. + */ + public IStatus[] getChildren() { + return theEmptyStatusArray; + } + + /* (Intentionally not javadoc'd) + * Implements the corresponding method on IStatus. + */ + public int getCode() { + return code; + } + + /* (Intentionally not javadoc'd) + * Implements the corresponding method on IStatus. + */ + public Exception getException() { + return exception; + } + + /* (Intentionally not javadoc'd) + * Implements the corresponding method on IStatus. + */ + public String getMessage() { + return message; + } + + /* (Intentionally not javadoc'd) + * Implements the corresponding method on IStatus. + */ + public String getPlugin() { + return pluginId; + } + + /* (Intentionally not javadoc'd) + * Implements the corresponding method on IStatus. + */ + public int getSeverity() { + return severity; + } + + /* (Intentionally not javadoc'd) + * Implements the corresponding method on IStatus. + */ + public bool isMultiStatus() { + return false; + } + + /* (Intentionally not javadoc'd) + * Implements the corresponding method on IStatus. + */ + public bool isOK() { + return severity is OK; + } + + /* (Intentionally not javadoc'd) + * Implements the corresponding method on IStatus. + */ + public bool matches(int severityMask) { + return (severity & severityMask) !is 0; + } + + /** + * Sets the status code. + * + * @param code the plug-in-specific status code, or OK + */ + protected void setCode(int code) { + this.code = code; + } + + /** + * Sets the exception. + * + * @param exception a low-level exception, or null if not + * applicable + */ + protected void setException(Exception exception) { + this.exception = exception; + } + + /** + * Sets the message. If null is passed, message is set to an empty + * string. + * + * @param message a human-readable message, localized to the + * current locale + */ + protected void setMessage(String message) { + if (message is null) + this.message = ""; //$NON-NLS-1$ + else + this.message = message; + } + + /** + * Sets the plug-in id. + * + * @param pluginId the unique identifier of the relevant plug-in + */ + protected void setPlugin(String pluginId) { + Assert.isLegal(pluginId !is null && pluginId.length > 0); + this.pluginId = pluginId; + } + + /** + * Sets the severity. + * + * @param severity the severity; one of OK, ERROR, + * INFO, WARNING, or CANCEL + */ + protected void setSeverity(int severity) { + Assert.isLegal(severity is OK || severity is ERROR || severity is WARNING || severity is INFO || severity is CANCEL); + this.severity = severity; + } + + /** + * Returns a string representation of the status, suitable + * for debugging purposes only. + */ + public String toString() { + String sev; + if (severity is OK) { + sev="OK"; //$NON-NLS-1$ + } else if (severity is ERROR) { + sev="ERROR"; //$NON-NLS-1$ + } else if (severity is WARNING) { + sev="WARNING"; //$NON-NLS-1$ + } else if (severity is INFO) { + sev="INFO"; //$NON-NLS-1$ + } else if (severity is CANCEL) { + sev="CANCEL"; //$NON-NLS-1$ + } else { + sev=Format( "severity={}", severity); + } + return Format("Status {}: {} code={} {} {}", sev, pluginId, code, message, exception.toString ); + } +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/runtime/SubMonitor.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/runtime/SubMonitor.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,787 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 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: + * Stefan Xenos - initial API and implementation + * Stefan Xenos - bug 174539 - add a 1-argument convert(...) method + * Stefan Xenos - bug 174040 - SubMonitor#convert doesn't always set task name + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.runtime.SubMonitor; + +import dwtx.core.runtime.IProgressMonitorWithBlocking; +import dwtx.core.runtime.IProgressMonitor; +import dwtx.core.runtime.IStatus; +import dwtx.core.runtime.NullProgressMonitor; + +import dwt.dwthelper.utils; + +/** + *

A progress monitor that uses a given amount of work ticks from a parent monitor. This is intended as a + * safer, easier-to-use alternative to SubProgressMonitor. The main benefits of SubMonitor over + * SubProgressMonitor are:

+ *
    + *
  • It is not necessary to call beginTask() or done() on an instance of SubMonitor.
  • + *
  • SubMonitor has a simpler syntax for creating nested monitors.
  • + *
  • SubMonitor is more efficient for deep recursion chains.
  • + *
  • SubMonitor has a setWorkRemining method that allows the remaining space on the monitor to be + * redistributed without reporting any work.
  • + *
  • SubMonitor protects the caller from common progress reporting bugs in a called method. For example, + * if a called method fails to call done() on the given monitor or fails to consume all the ticks on + * the given monitor, the parent will correct the problem after the method returns.
  • + *
+ *

+ *

USAGE:

+ * + *

When implementing a method that accepts an IProgressMonitor:

+ *
    + *
  • At the start of your method, use SubMonitor.convert(...). to convert the IProgressMonitor + * into a SubMonitor.
  • + *
  • Use SubMonitor.newChild(...) whenever you need to call another method that + * accepts an IProgressMonitor.
  • + *
+ *

+ *

DEFAULT BEHAVIOR:

+ * + *

When writing JavaDoc for a method that accepts an IProgressMonitor, you should assume the + * following default behavior unless the method's JavaDoc says otherwise:

+ *
    + *
  • It WILL call beginTask on the IProgressMonitor.
  • + *
  • It WILL NOT accept a null argument.
  • + *
  • It WILL call done on the IProgressMonitor.
  • + *
+ *

+ *

BEST PRACTISES:

+ * + *

We recommend that newly-written methods follow the given contract:

+ *
    + *
  • It WILL call beginTask on the IProgressMonitor.
  • + *
  • It WILL accept a null argument, indicating that no progress should be reported and the operation cannot be cancelled.
  • + *
  • It WILL NOT call done on the IProgressMonitor, leaving this responsibility up to the caller.
  • + *
+ *

If you wish to follow these conventions, you may copy and paste the following text into your method's JavaDoc:

+ * + *
@param monitor the progress monitor to use for reporting progress to the user. It is the caller's responsibility
+ *        to call done() on the given monitor. Accepts null, indicating that no progress should be
+ *        reported and that the operation cannot be cancelled.
+ * + *

+ *

Example: Recommended usage

+ * + *

This example demonstrates how the recommended usage of SubMonitor makes it unnecessary to call + * IProgressMonitor.done() in most situations.

+ * + *

It is never necessary to call done() on a monitor obtained from convert or progress.newChild(). + * In this example, there is no guarantee that monitor is an instance of SubMonitor, making it + * necessary to call monitor.done(). The JavaDoc contract makes this the responsibility of the caller.

+ * + *
+ *      // param monitor the progress monitor to use for reporting progress to the user. It is the caller's responsibility
+ *      //        to call done() on the given monitor. Accepts null, indicating that no progress should be
+ *      //        reported and that the operation cannot be cancelled.
+ *      //
+ *      void doSomething(IProgressMonitor monitor) {
+ *          // Convert the given monitor into a progress instance
+ *          SubMonitor progress = SubMonitor.convert(monitor, 100);
+ *
+ *          // Use 30% of the progress to do some work
+ *          doSomeWork(progress.newChild(30));
+ *
+ *          // Advance the monitor by another 30%
+ *          progress.worked(30);
+ *
+ *          // Use the remaining 40% of the progress to do some more work
+ *          doSomeWork(progress.newChild(40));
+ *      }
+ * 
+ * + * + *

+ *

Example: Default usage

+ * + *

You will often need to implement a method that does not explicitly stipulate that calling done() is the responsibility + * of the caller. In this case, you should use the following pattern:

+ * + *
+ *      // param monitor the progress monitor to use for reporting progress to the user, or null indicating
+ *      //        that no progress should be reported and the operation cannot be cancelled.
+ *      //
+ *      void doSomething(IProgressMonitor monitor) {
+ *          // Convert the given monitor into a progress instance
+ *          SubMonitor progress = SubMonitor.convert(monitor, 100);
+ *          try {
+ *              // Use 30% of the progress to do some work
+ *              doSomeWork(progress.newChild(30));
+ *
+ *              // Advance the monitor by another 30%
+ *              progress.worked(30);
+ *
+ *              // Use the remaining 40% of the progress to do some more work
+ *              doSomeWork(progress.newChild(40));
+ *
+ *          } finally {
+ *              if (monitor !is null) {
+ *                  monitor.done();
+ *              }
+ *          }
+ *      }
+ * 
+ * + *

+ *

Example: Branches

+ * + *

This example demonstrates how to smoothly report progress in situations where some of the work is optional.

+ * + *
+ *      void doSomething(IProgressMonitor monitor) {
+ *          SubMonitor progress = SubMonitor.convert(monitor, 100);
+ *
+ *          if (condition) {
+ *              // Use 50% of the progress to do some work
+ *              doSomeWork(progress.newChild(50));
+ *          }
+ *
+ *          // Don't report any work, but ensure that we have 50 ticks remaining on the progress monitor.
+ *          // If we already consumed 50 ticks in the above branch, this is a no-op. Otherwise, the remaining
+ *          // space in the monitor is redistributed into 50 ticks.
+ *
+ *          progress.setWorkRemaining(50);
+ *
+ *          // Use the remainder of the progress monitor to do the rest of the work
+ *          doSomeWork(progress.newChild(50));
+ *      }
+ * 
+ * + *

Please beware of the following anti-pattern:

+ * + *
+ *          if (condition) {
+ *              // Use 50% of the progress to do some work
+ *              doSomeWork(progress.newChild(50));
+ *          } else {
+ *              // Bad: Causes the progress monitor to appear to start at 50%, wasting half of the
+ *              // space in the monitor.
+ *              progress.worked(50);
+ *          }
+ * 
+ * + * + *

+ *

Example: Loops

+ * + *

This example demonstrates how to report progress in a loop.

+ * + *
+ *      void doSomething(IProgressMonitor monitor, Collection someCollection) {
+ *          SubMonitor progress = SubMonitor.convert(monitor, 100);
+ *
+ *          // Create a new progress monitor that uses 70% of the total progress and will allocate one tick
+ *          // for each element of the given collection.
+ *          SubMonitor loopProgress = progress.newChild(70).setWorkRemaining(someCollection.size());
+ *
+ *          for (Iterator iter = someCollection.iterator(); iter.hasNext();) {
+ *              Object next = iter.next();
+ *
+ *              doWorkOnElement(next, loopProgress.newChild(1));
+ *          }
+ *
+ *          // Use the remaining 30% of the progress monitor to do some work outside the loop
+ *          doSomeWork(progress.newChild(30));
+ *      }
+ * 
+ * + * + *

+ *

Example: Infinite progress

+ * + *

This example demonstrates how to report logarithmic progress in situations where the number of ticks + * cannot be easily computed in advance.

+ * + *
+ *      void doSomething(IProgressMonitor monitor, LinkedListNode node) {
+ *          SubMonitor progress = SubMonitor.convert(monitor, 100);
+ *
+ *          while (node !is null) {
+ *              // Regardless of the amount of progress reported so far,
+ *              // use 5% of the space remaining in the monitor to process the next node.
+ *              progress.setWorkRemaining(100);
+ *
+ *              doWorkOnElement(node, progress.newChild(5));
+ *
+ *              node = node.next;
+ *          }
+ *      }
+ * 
+ * + *

+ * This class can be used without OSGi running. + *

+ * + * @since dwtx.equinox.common 3.3 + */ +public final class SubMonitor : IProgressMonitorWithBlocking { + + /** + * Minimum number of ticks to allocate when calling beginTask on an unknown IProgressMonitor. + * Pick a number that is big enough such that, no matter where progress is being displayed, + * the user would be unlikely to notice if progress were to be reported with higher accuracy. + */ + private static final int MINIMUM_RESOLUTION = 1000; + + /** + * The RootInfo struct holds information about the root progress monitor. A SubMonitor and + * its active descendents share the same RootInfo struct. + */ + private static final class RootInfo { + private final IProgressMonitor root; + + /** + * Remembers the last task name. Prevents us from setting the same task name multiple + * times in a row. + */ + private String taskName = null; + + /** + * Remembers the last subtask name. Prevents the SubMonitor from setting the same + * subtask string more than once in a row. + */ + private String subTask_ = null; + + /** + * Creates a RootInfo struct that delegates to the given progress + * monitor. + * + * @param root progress monitor to delegate to + */ + public this(IProgressMonitor root) { + this.root = root; + } + + public bool isCanceled() { + return root.isCanceled(); + } + + public void setCanceled(bool value) { + root.setCanceled(value); + } + + public void setTaskName(String taskName) { + if (eq(taskName, this.taskName)) { + return; + } + this.taskName = taskName; + root.setTaskName(taskName); + } + + public void subTask(String name) { + if (eq(subTask_, name)) { + return; + } + + this.subTask_ = name; + root.subTask(name); + } + + public void worked(int i) { + root.worked(i); + } + + public void clearBlocked() { + if ( auto mon = cast(IProgressMonitorWithBlocking)root ) + mon.clearBlocked(); + } + + public void setBlocked(IStatus reason) { + if ( auto mon = cast(IProgressMonitorWithBlocking)root ) + mon.setBlocked(reason); + } + + } + + /** + * Total number of ticks that this progress monitor is permitted to consume + * from the root. + */ + private int totalParent; + + /** + * Number of ticks that this progress monitor has already reported in the root. + */ + private int usedForParent = 0; + + /** + * Number of ticks that have been consumed by this instance's children. + */ + private double usedForChildren = 0.0; + + /** + * Number of ticks allocated for this instance's children. This is the total number + * of ticks that may be passed into worked(int) or newChild(int). + */ + private int totalForChildren; + + /** + * Children created by newChild will be completed automatically the next time + * the parent progress monitor is touched. This points to the last incomplete child + * created with newChild. + */ + private IProgressMonitor lastSubMonitor = null; + + /** + * Used to communicate with the root of this progress monitor tree + */ + private const RootInfo root; + + /** + * A bitwise combination of the SUPPRESS_* flags. + */ + private const int flags; + + /** + * May be passed as a flag to newChild. Indicates that the calls + * to subTask on the child should be ignored. Without this flag, + * calling subTask on the child will result in a call to subTask + * on its parent. + */ + public static const int SUPPRESS_SUBTASK = 0x0001; + + /** + * May be passed as a flag to newChild. Indicates that strings + * passed into beginTask should be ignored. If this flag is + * specified, then the progress monitor instance will accept null + * as the first argument to beginTask. Without this flag, any + * string passed to beginTask will result in a call to + * setTaskName on the parent. + */ + public static const int SUPPRESS_BEGINTASK = 0x0002; + + /** + * May be passed as a flag to newChild. Indicates that strings + * passed into setTaskName should be ignored. If this string + * is omitted, then a call to setTaskName on the child will + * result in a call to setTaskName on the parent. + */ + public static const int SUPPRESS_SETTASKNAME = 0x0004; + + /** + * May be passed as a flag to newChild. Indicates that strings + * passed to setTaskName, subTask, and beginTask should all be ignored. + */ + public static const int SUPPRESS_ALL_LABELS = SUPPRESS_SETTASKNAME | SUPPRESS_BEGINTASK | SUPPRESS_SUBTASK; + + /** + * May be passed as a flag to newChild. Indicates that strings + * passed to setTaskName, subTask, and beginTask should all be propogated + * to the parent. + */ + public static const int SUPPRESS_NONE = 0; + + /** + * Creates a new SubMonitor that will report its progress via + * the given RootInfo. + * @param rootInfo the root of this progress monitor tree + * @param totalWork total work to perform on the given progress monitor + * @param availableToChildren number of ticks allocated for this instance's children + * @param flags a bitwise combination of the SUPPRESS_* constants + */ + private this(RootInfo rootInfo, int totalWork, int availableToChildren, int flags) { + root = rootInfo; + totalParent = (totalWork > 0) ? totalWork : 0; + this.totalForChildren = availableToChildren; + this.flags = flags; + } + + /** + *

Converts an unknown (possibly null) IProgressMonitor into a SubMonitor. It is + * not necessary to call done() on the result, but the caller is responsible for calling + * done() on the argument. Calls beginTask on the argument.

+ * + *

This method should generally be called at the beginning of a method that accepts + * an IProgressMonitor in order to convert the IProgressMonitor into a SubMonitor.

+ * + * @param monitor monitor to convert to a SubMonitor instance or null. Treats null + * as a new instance of NullProgressMonitor. + * @return a SubMonitor instance that adapts the argument + */ + public static SubMonitor convert(IProgressMonitor monitor) { + return convert(monitor, "", 0); //$NON-NLS-1$ + } + + /** + *

Converts an unknown (possibly null) IProgressMonitor into a SubMonitor allocated + * with the given number of ticks. It is not necessary to call done() on the result, + * but the caller is responsible for calling done() on the argument. Calls beginTask + * on the argument.

+ * + *

This method should generally be called at the beginning of a method that accepts + * an IProgressMonitor in order to convert the IProgressMonitor into a SubMonitor.

+ * + * @param monitor monitor to convert to a SubMonitor instance or null. Treats null + * as a new instance of NullProgressMonitor. + * @param work number of ticks that will be available in the resulting monitor + * @return a SubMonitor instance that adapts the argument + */ + public static SubMonitor convert(IProgressMonitor monitor, int work) { + return convert(monitor, "", work); //$NON-NLS-1$ + } + + /** + *

Converts an unknown (possibly null) IProgressMonitor into a SubMonitor allocated + * with the given number of ticks. It is not necessary to call done() on the result, + * but the caller is responsible for calling done() on the argument. Calls beginTask + * on the argument.

+ * + *

This method should generally be called at the beginning of a method that accepts + * an IProgressMonitor in order to convert the IProgressMonitor into a SubMonitor.

+ * + * @param monitor to convert into a SubMonitor instance or null. If given a null argument, + * the resulting SubMonitor will not report its progress anywhere. + * @param taskName user readable name to pass to monitor.beginTask. Never null. + * @param work initial number of ticks to allocate for children of the SubMonitor + * @return a new SubMonitor instance that is a child of the given monitor + */ + public static SubMonitor convert(IProgressMonitor monitor, String taskName, int work) { + if (monitor is null) + monitor = new NullProgressMonitor(); + + // Optimization: if the given monitor already a SubMonitor, no conversion is necessary + if ( cast(SubMonitor) monitor ) { + monitor.beginTask(taskName, work); + return cast(SubMonitor) monitor; + } + + monitor.beginTask(taskName, MINIMUM_RESOLUTION); + return new SubMonitor(new RootInfo(monitor), MINIMUM_RESOLUTION, work, SUPPRESS_NONE); + } + + /** + *

Sets the work remaining for this SubMonitor instance. This is the total number + * of ticks that may be reported by all subsequent calls to worked(int), newChild(int), etc. + * This may be called many times for the same SubMonitor instance. When this method + * is called, the remaining space on the progress monitor is redistributed into the given + * number of ticks.

+ * + *

It doesn't matter how much progress has already been reported with this SubMonitor + * instance. If you call setWorkRemaining(100), you will be able to report 100 more ticks of + * work before the progress meter reaches 100%.

+ * + * @param workRemaining total number of remaining ticks + * @return the receiver + */ + public SubMonitor setWorkRemaining(int workRemaining) { + // Ensure we don't try to allocate negative ticks + workRemaining = Math.max(0, workRemaining); + + // Ensure we don't cause division by zero + if (totalForChildren > 0 && totalParent > usedForParent) { + // Note: We want the following value to remain invariant after this method returns + double remainForParent = totalParent * (1.0 - (usedForChildren / totalForChildren)); + usedForChildren = (workRemaining * (1.0 - remainForParent / (totalParent - usedForParent))); + } else + usedForChildren = 0.0; + + totalParent = totalParent - usedForParent; + usedForParent = 0; + totalForChildren = workRemaining; + return this; + } + + /** + * Consumes the given number of child ticks, given as a double. Must only + * be called if the monitor is in floating-point mode. + * + * @param ticks the number of ticks to consume + * @return ticks the number of ticks to be consumed from parent + */ + private int consume(double ticks) { + if (totalParent is 0 || totalForChildren is 0) // this monitor has no available work to report + return 0; + + usedForChildren += ticks; + + if (usedForChildren > totalForChildren) + usedForChildren = totalForChildren; + else if (usedForChildren < 0.0) + usedForChildren = 0.0; + + int parentPosition = cast(int) (totalParent * usedForChildren / totalForChildren); + int delta = parentPosition - usedForParent; + + usedForParent = parentPosition; + return delta; + } + + /* (non-Javadoc) + * @see dwtx.core.runtime.IProgressMonitor#isCanceled() + */ + public bool isCanceled() { + return root.isCanceled(); + } + + /* (non-Javadoc) + * @see dwtx.core.runtime.IProgressMonitor#setTaskName(java.lang.String) + */ + public void setTaskName(String name) { + if ((flags & SUPPRESS_SETTASKNAME) is 0) + root.setTaskName(name); + } + + /** + * Starts a new main task. The string argument is ignored + * if and only if the SUPPRESS_BEGINTASK flag has been set on this SubMonitor + * instance. + * + *

This method is equivalent calling setWorkRemaining(...) on the reciever. Unless + * the SUPPRESS_BEGINTASK flag is set, this will also be equivalent to calling + * setTaskName(...) on the parent.

+ * + * @param name new main task name + * @param totalWork number of ticks to allocate + * + * @see dwtx.core.runtime.IProgressMonitor#beginTask(java.lang.String, int) + */ + public void beginTask(String name, int totalWork) { + if ((flags & SUPPRESS_BEGINTASK) is 0 && name !is null) + root.setTaskName(name); + setWorkRemaining(totalWork); + } + + /* (non-Javadoc) + * @see dwtx.core.runtime.IProgressMonitor#done() + */ + public void done() { + cleanupActiveChild(); + int delta = totalParent - usedForParent; + if (delta > 0) + root.worked(delta); + + totalParent = 0; + usedForParent = 0; + totalForChildren = 0; + usedForChildren = 0.0; + } + + /* (non-Javadoc) + * @see dwtx.core.runtime.IProgressMonitor#internalWorked(double) + */ + public void internalWorked(double work) { + int delta = consume((work > 0.0) ? work : 0.0); + if (delta !is 0) + root.worked(delta); + } + + /* (non-Javadoc) + * @see dwtx.core.runtime.IProgressMonitor#subTask(java.lang.String) + */ + public void subTask(String name) { + if ((flags & SUPPRESS_SUBTASK) is 0) + root.subTask(name); + } + + /* (non-Javadoc) + * @see dwtx.core.runtime.IProgressMonitor#worked(int) + */ + public void worked(int work) { + internalWorked(work); + } + + /* (non-Javadoc) + * @see dwtx.core.runtime.IProgressMonitor#setCanceled(bool) + */ + public void setCanceled(bool b) { + root.setCanceled(b); + } + + /** + *

Creates a sub progress monitor that will consume the given number of ticks from the + * receiver. It is not necessary to call beginTask or done on the + * result. However, the resulting progress monitor will not report any work after the first + * call to done() or before ticks are allocated. Ticks may be allocated by calling beginTask + * or setWorkRemaining.

+ * + *

Each SubMonitor only has one active child at a time. Each time newChild() is called, the + * result becomes the new active child and any unused progress from the previously-active child is + * consumed.

+ * + *

This is property makes it unnecessary to call done() on a SubMonitor instance, since child + * monitors are automatically cleaned up the next time the parent is touched.

+ * + *
+     *      ////////////////////////////////////////////////////////////////////////////
+     *      // Example 1: Typical usage of newChild
+     *      void myMethod(IProgressMonitor parent) {
+     *          SubMonitor progress = SubMonitor.convert(parent, 100);
+     *          doSomething(progress.newChild(50));
+     *          doSomethingElse(progress.newChild(50));
+     *      }
+     *
+     *      ////////////////////////////////////////////////////////////////////////////
+     *      // Example 2: Demonstrates the function of active children. Creating children
+     *      // is sufficient to smoothly report progress, even if worked(...) and done()
+     *      // are never called.
+     *      void myMethod(IProgressMonitor parent) {
+     *          SubMonitor progress = SubMonitor.convert(parent, 100);
+     *
+     *          for (int i = 0; i < 100; i++) {
+     *              // Creating the next child monitor will clean up the previous one,
+     *              // causing progress to be reported smoothly even if we don't do anything
+     *              // with the monitors we create
+     *              progress.newChild(1);
+     *          }
+     *      }
+     *
+     *      ////////////////////////////////////////////////////////////////////////////
+     *      // Example 3: Demonstrates a common anti-pattern
+     *      void wrongMethod(IProgressMonitor parent) {
+     *          SubMonitor progress = SubMonitor.convert(parent, 100);
+     *
+     *          // WRONG WAY: Won't have the intended effect, as only one of these progress
+     *          // monitors may be active at a time and the other will report no progress.
+     *          callMethod(progress.newChild(50), computeValue(progress.newChild(50)));
+     *      }
+     *
+     *      void rightMethod(IProgressMonitor parent) {
+     *          SubMonitor progress = SubMonitor.convert(parent, 100);
+     *
+     *          // RIGHT WAY: Break up method calls so that only one SubMonitor is in use at a time.
+     *          Object someValue = computeValue(progress.newChild(50));
+     *          callMethod(progress.newChild(50), someValue);
+     *      }
+     * 
+ * + * @param totalWork number of ticks to consume from the reciever + * @return new sub progress monitor that may be used in place of a new SubMonitor + */ + public SubMonitor newChild(int totalWork) { + return newChild(totalWork, SUPPRESS_BEGINTASK); + } + + /** + *

Creates a sub progress monitor that will consume the given number of ticks from the + * receiver. It is not necessary to call beginTask or done on the + * result. However, the resulting progress monitor will not report any work after the first + * call to done() or before ticks are allocated. Ticks may be allocated by calling beginTask + * or setWorkRemaining.

+ * + *

Each SubMonitor only has one active child at a time. Each time newChild() is called, the + * result becomes the new active child and any unused progress from the previously-active child is + * consumed.

+ * + *

This is property makes it unnecessary to call done() on a SubMonitor instance, since child + * monitors are automatically cleaned up the next time the parent is touched.

+ * + *
+     *      ////////////////////////////////////////////////////////////////////////////
+     *      // Example 1: Typical usage of newChild
+     *      void myMethod(IProgressMonitor parent) {
+     *          SubMonitor progress = SubMonitor.convert(parent, 100);
+     *          doSomething(progress.newChild(50));
+     *          doSomethingElse(progress.newChild(50));
+     *      }
+     *
+     *      ////////////////////////////////////////////////////////////////////////////
+     *      // Example 2: Demonstrates the function of active children. Creating children
+     *      // is sufficient to smoothly report progress, even if worked(...) and done()
+     *      // are never called.
+     *      void myMethod(IProgressMonitor parent) {
+     *          SubMonitor progress = SubMonitor.convert(parent, 100);
+     *
+     *          for (int i = 0; i < 100; i++) {
+     *              // Creating the next child monitor will clean up the previous one,
+     *              // causing progress to be reported smoothly even if we don't do anything
+     *              // with the monitors we create
+     *              progress.newChild(1);
+     *          }
+     *      }
+     *
+     *      ////////////////////////////////////////////////////////////////////////////
+     *      // Example 3: Demonstrates a common anti-pattern
+     *      void wrongMethod(IProgressMonitor parent) {
+     *          SubMonitor progress = SubMonitor.convert(parent, 100);
+     *
+     *          // WRONG WAY: Won't have the intended effect, as only one of these progress
+     *          // monitors may be active at a time and the other will report no progress.
+     *          callMethod(progress.newChild(50), computeValue(progress.newChild(50)));
+     *      }
+     *
+     *      void rightMethod(IProgressMonitor parent) {
+     *          SubMonitor progress = SubMonitor.convert(parent, 100);
+     *
+     *          // RIGHT WAY: Break up method calls so that only one SubMonitor is in use at a time.
+     *          Object someValue = computeValue(progress.newChild(50));
+     *          callMethod(progress.newChild(50), someValue);
+     *      }
+     * 
+ * + * @param totalWork number of ticks to consume from the reciever + * @return new sub progress monitor that may be used in place of a new SubMonitor + */ + public SubMonitor newChild(int totalWork, int suppressFlags) { + double totalWorkDouble = (totalWork > 0) ? totalWork : 0.0; + totalWorkDouble = Math.min(totalWorkDouble, totalForChildren - usedForChildren); + cleanupActiveChild(); + + // Compute the flags for the child. We want the net effect to be as though the child is + // delegating to its parent, even though it is actually talking directly to the root. + // This means that we need to compute the flags such that - even if a label isn't + // suppressed by the child - if that same label would have been suppressed when the + // child delegated to its parent, the child must explicitly suppress the label. + int childFlags = SUPPRESS_NONE; + + if ((flags & SUPPRESS_SETTASKNAME) !is 0) { + // If the parent was ignoring labels passed to setTaskName, then the child will ignore + // labels passed to either beginTask or setTaskName - since both delegate to setTaskName + // on the parent + childFlags |= SUPPRESS_SETTASKNAME | SUPPRESS_BEGINTASK; + } + + if ((flags & SUPPRESS_SUBTASK) !is 0) { + // If the parent was suppressing labels passed to subTask, so will the child. + childFlags |= SUPPRESS_SUBTASK; + } + + // Note: the SUPPRESS_BEGINTASK flag does not affect the child since there + // is no method on the child that would delegate to beginTask on the parent. + childFlags |= suppressFlags; + + SubMonitor result = new SubMonitor(root, consume(totalWorkDouble), 0, childFlags); + lastSubMonitor = result; + return result; + } + + private void cleanupActiveChild() { + if (lastSubMonitor is null) + return; + + IProgressMonitor child = lastSubMonitor; + lastSubMonitor = null; + child.done(); + } + + /* (non-Javadoc) + * @see dwtx.core.runtime.IProgressMonitorWithBlocking#clearBlocked() + */ + public void clearBlocked() { + root.clearBlocked(); + } + + /* (non-Javadoc) + * @see dwtx.core.runtime.IProgressMonitorWithBlocking#setBlocked(dwtx.core.runtime.IStatus) + */ + public void setBlocked(IStatus reason) { + root.setBlocked(reason); + } + + protected static bool eq(String o1, String o2) { + if (o1.length is 0) + return (o2.length is 0); + if (o2.length is 0) + return false; + return o1.equals(o2); + } +} diff -r a012107a911c -r 6518c18a01f7 dwtx/core/runtime/SubProgressMonitor.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/runtime/SubProgressMonitor.d Wed Mar 26 00:57:19 2008 +0100 @@ -0,0 +1,190 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 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 + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.core.runtime.SubProgressMonitor; + +import dwtx.core.runtime.ProgressMonitorWrapper; +import dwtx.core.runtime.IProgressMonitor; + +import dwt.dwthelper.utils; + +/** + * For new implementations consider using {@link SubMonitor}. + * + * A progress monitor that uses a given amount of work ticks + * from a parent monitor. It can be used as follows: + *
+ *     try {
+ *         pm.beginTask("Main Task", 100);
+ *         doSomeWork(pm, 30);
+ *         SubProgressMonitor subMonitor= new SubProgressMonitor(pm, 40);
+ *         try {
+ *             subMonitor.beginTask("", 300);
+ *             doSomeWork(subMonitor, 300);
+ *         } finally {
+ *             subMonitor.done();
+ *         }
+ *         doSomeWork(pm, 30);
+ *     } finally {
+ *         pm.done();
+ *     }
+ * 
+ *

+ * This class can be used without OSGi running. + *

+ * This class may be instantiated or subclassed by clients. + *

+ * + * @see SubMonitor + */ +public class SubProgressMonitor : ProgressMonitorWrapper { + + /** + * Style constant indicating that calls to subTask + * should not have any effect. + * + * @see #SubProgressMonitor(IProgressMonitor,int,int) + */ + public static const int SUPPRESS_SUBTASK_LABEL = 1 << 1; + /** + * Style constant indicating that the main task label + * should be prepended to the subtask label. + * + * @see #SubProgressMonitor(IProgressMonitor,int,int) + */ + public static const int PREPEND_MAIN_LABEL_TO_SUBTASK = 1 << 2; + + private int parentTicks = 0; + private double sentToParent = 0.0; + private double scale = 0.0; + private int nestedBeginTasks = 0; + private bool usedUp = false; + private bool hasSubTask = false; + private int style; + private String mainTaskLabel; + + /** + * Creates a new sub-progress monitor for the given monitor. The sub + * progress monitor uses the given number of work ticks from its + * parent monitor. + * + * @param monitor the parent progress monitor + * @param ticks the number of work ticks allocated from the + * parent monitor + */ + public this(IProgressMonitor monitor, int ticks) { + this(monitor, ticks, 0); + } + + /** + * Creates a new sub-progress monitor for the given monitor. The sub + * progress monitor uses the given number of work ticks from its + * parent monitor. + * + * @param monitor the parent progress monitor + * @param ticks the number of work ticks allocated from the + * parent monitor + * @param style one of + *
    + *
  • SUPPRESS_SUBTASK_LABEL
  • + *
  • PREPEND_MAIN_LABEL_TO_SUBTASK
  • + *
+ * @see #SUPPRESS_SUBTASK_LABEL + * @see #PREPEND_MAIN_LABEL_TO_SUBTASK + */ + public this(IProgressMonitor monitor, int ticks, int style) { + super(monitor); + this.parentTicks = (ticks > 0) ? ticks : 0; + this.style = style; + } + + /* (Intentionally not javadoc'd) + * Implements the method IProgressMonitor.beginTask. + * + * Starts a new main task. Since this progress monitor is a sub + * progress monitor, the given name will NOT be used to update + * the progress bar's main task label. That means the given + * string will be ignored. If style PREPEND_MAIN_LABEL_TO_SUBTASK + * is specified, then the given string will be prepended to + * every string passed to subTask(String). + */ + public void beginTask(String name, int totalWork) { + nestedBeginTasks++; + // Ignore nested begin task calls. + if (nestedBeginTasks > 1) { + return; + } + // be safe: if the argument would cause math errors (zero or + // negative), just use 0 as the scale. This disables progress for + // this submonitor. + scale = totalWork <= 0 ? 0 : cast(double) parentTicks / cast(double) totalWork; + if ((style & PREPEND_MAIN_LABEL_TO_SUBTASK) !is 0) { + mainTaskLabel = name; + } + } + + /* (Intentionally not javadoc'd) + * Implements the method IProgressMonitor.done. + */ + public void done() { + // Ignore if more done calls than beginTask calls or if we are still + // in some nested beginTasks + if (nestedBeginTasks is 0 || --nestedBeginTasks > 0) + return; + // Send any remaining ticks and clear out the subtask text + double remaining = parentTicks - sentToParent; + if (remaining > 0) + super.internalWorked(remaining); + //clear the sub task if there was one + if (hasSubTask) + subTask(""); //$NON-NLS-1$ + sentToParent = 0; + } + + /* (Intentionally not javadoc'd) + * Implements the internal method IProgressMonitor.internalWorked. + */ + public void internalWorked(double work) { + if (usedUp || nestedBeginTasks !is 1) { + return; + } + + double realWork = (work > 0.0) ? scale * work : 0.0; + super.internalWorked(realWork); + sentToParent += realWork; + if (sentToParent >= parentTicks) { + usedUp = true; + } + } + + /* (Intentionally not javadoc'd) + * Implements the method IProgressMonitor.subTask. + */ + public void subTask(String name) { + if ((style & SUPPRESS_SUBTASK_LABEL) !is 0) { + return; + } + hasSubTask = true; + String label = name; + if ((style & PREPEND_MAIN_LABEL_TO_SUBTASK) !is 0 && mainTaskLabel !is null && mainTaskLabel.length > 0) { + label = mainTaskLabel ~ ' ' ~ label; + } + super.subTask(label); + } + + /* (Intentionally not javadoc'd) + * Implements the method IProgressMonitor.worked. + */ + public void worked(int work) { + internalWorked(work); + } +}