view dwtx/core/commands/CommandManager.d @ 90:7ffeace6c47f

Update 3.4M7 to 3.4
author Frank Benoit <benoit@tionex.de>
date Sun, 06 Jul 2008 23:30:07 +0200
parents 46a6e0e6ccd4
children 04b47443bb01
line wrap: on
line source

/*******************************************************************************
 * Copyright (c) 2004, 2008 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 <benoit@tionex.de>
 *******************************************************************************/
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;

import dwt.dwthelper.WeakHashMap;

/**
 * <p>
 * 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.
 * </p>
 *
 * @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 <code>null</code>.
     *
     * @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();
    }

    /**
     * Unescapes special characters in the command id, parameter ids and
     * parameter values for {@link #deserialize(String)}. 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.
     * <p>
     * See also ParameterizedCommand.escape(String)
     * </p>
     *
     * @param escapedText
     *            a <code>String</code> that may contain escaped special
     *            characters for command serialization.
     * @return a <code>String</code> representing <code>escapedText</code>
     *         with any escaped characters replaced by their literal values
     * @throws SerializationException
     *             if <code>escapedText</code> contains an invalid escape
     *             sequence
     * @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 (<code>String</code>) to categories (
     * <code>Category</code>). This collection may be empty, but it is never
     * <code>null</code>.
     */
    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 <code>null</code>.
     */
    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 <code>null</code>.
     *
     * @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 IExecutionListenerWithChecks executionListener = null;

    /**
     * The collection of execution listeners. This collection is
     * <code>null</code> 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 <code>null</code>. Entries are
     * removed if all strong references to the handler are removed.
     *
     * @since 3.2
     */
    private const WeakHashMap helpContextIdsByHandler;

    /**
     * The map of parameter type identifiers (<code>String</code>) to
     * parameter types ( <code>ParameterType</code>). This collection may be
     * empty, but it is never <code>null</code>.
     *
     * @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 <code>null</code>.
     */
    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 <code>null</code>.
     */
    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 <code>null</code>.
     *
     * @param name
     *            The name of the category for uncategorized commands; must not
     *            be <code>null</code>.
     * @param description
     *            The description of the category for uncategorized commands;
     *            may be <code>null</code>.
     * @since 3.2
     */
    public final void defineUncategorizedCategory(String name,
            String description) {
        Category category = getCategory(AUTOGENERATED_CATEGORY_ID);
        category.define(name, description);
    }

    /**
     * <p>
     * Returns a {@link ParameterizedCommand} with a command and
     * parameterizations as specified in the provided
     * <code>serializedParameterizedCommand</code> string. The
     * <code>serializedParameterizedCommand</code> must use the format
     * returned by {@link ParameterizedCommand#serialize()} and described in the
     * Javadoc for that method.
     * </p>
     * <p>
     * If a parameter id encoded in the
     * <code>serializedParameterizedCommand</code> 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
     * <code>serializedParameterizedCommand</code>. This will not result in
     * an exception, but in this case the value of the parameter when the
     * command is executed is unspecified.
     * </p>
     * <p>
     * This method will never return <code>null</code>, however it may throw
     * an exception if there is a problem processing the serialization string or
     * the encoded command is undefined.
     * </p>
     *
     * @param serializedParameterizedCommand
     *            a string representing a command id and parameter ids and
     *            values; must not be <code>null</code>
     * @return a {@link ParameterizedCommand} with the command and
     *         parameterizations encoded in the
     *         <code>serializedParameterizedCommand</code>; never
     *         <code>null</code>.
     * @throws NotDefinedException
     *             if the command indicated in
     *             <code>serializedParameterizedCommand</code> is not defined
     * @throws SerializationException
     *             if there is an error deserializing
     *             <code>serializedParameterizedCommand</code>
     * @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
     *            <code>null</code>.
     */
    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 <code>null</code>.
     * @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 <code>null</code>. If
     *            the category is <code>null</code>, then a category suitable
     *            for uncategorized items is defined and returned.
     * @return The category with the given identifier; this value will never be
     *         <code>null</code>, 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 <code>null</code> and
     *            must not be zero-length.
     * @return The command with the given identifier; this value will never be
     *         <code>null</code>, 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
     *         <code>null</code>.
     * @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 <code>null</code>.
     */
    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 <code>null</code>.
     */
    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
     *         <code>null</code>.
     * @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 <code>null</code>.
     * @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 <code>null</code>.
     * @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 <code>null</code> is returned.
     *
     * @param command
     *            The command for which the help context should be retrieved;
     *            must not be <code>null</code>.
     * @return The help context identifier to use for the given command; may be
     *         <code>null</code>.
     * @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 = stringcast( helpContextIdsByHandler.get( cast(Object) 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
     * <code>serializedParameters</code> string.
     *
     * @param serializedParameters
     *            a String encoding parameter ids and values; must not be
     *            <code>null</code>.
     * @param parameters
     *            array of parameters of the command being deserialized; may be
     *            <code>null</code>.
     * @return an array of parameterizations; may be <code>null</code>.
     * @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 <code>null</code> and
     *            must not be zero-length.
     * @return The {@link ParameterType} with the given identifier; this value
     *         will never be <code>null</code>, 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 <code>null</code>.
     */
    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 <code>null</code>.
     */
    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 <code>null</code> 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 (<code>String</code>) to
     *            handlers (<code>IHandler</code>). This map may be
     *            <code>null</code> 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 <code>null</code>.
     * @param helpContextId
     *            The help context identifier to register; may be
     *            <code>null</code> 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(cast(Object) handler);
        } else {
            helpContextIdsByHandler.add(cast(Object) handler, stringcast(helpContextId));
        }
    }

    /**
     * Searches for the index of a <code>char</code> in a <code>String</code>
     * 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 <code>ch</code> in
     * @param ch
     *            a character to search for in <code>escapedText</code>
     * @return the index of the first unescaped occurrence of the character in
     *         <code>escapedText</code>, or <code>-1</code> 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;

    }
    /**
     * Fires the <code>notEnabled</code> event for
     * <code>executionListeners</code>.
     * <p>
     * <b>Note:</b> This supports bridging actions to the command framework,
     * and should not be used outside the framework.
     * </p>
     * 
     * @param commandId
     *            The command id of the command about to execute, never
     *            <code>null</code>.
     * @param exception
     *            The exception, never <code>null</code>.
     * @since 3.4
     */
    public void fireNotEnabled(String commandId, NotEnabledException exception) {
        if (executionListener !is null) {
            executionListener.notEnabled(commandId, exception);
        }
    }
    
    /**
     * Fires the <code>notDefined</code> event for
     * <code>executionListeners</code>.
     * <p>
     * <b>Note:</b> This supports bridging actions to the command framework,
     * and should not be used outside the framework.
     * </p>
     * 
     * @param commandId
     *            The command id of the command about to execute, never
     *            <code>null</code>.
     * @param exception
     *            The exception, never <code>null</code>.
     * @since 3.4
     */
    public void fireNotDefined(String commandId, NotDefinedException exception) {
        if (executionListener !is null) {
            executionListener.notDefined(commandId, exception);
        }
    }
    
    /**
     * Fires the <code>preExecute</code> event for
     * <code>executionListeners</code>.
     * <p>
     * <b>Note:</b> This supports bridging actions to the command framework,
     * and should not be used outside the framework.
     * </p>
     * 
     * @param commandId
     *            The command id of the command about to execute, never
     *            <code>null</code>.
     * @param event
     *            The event that triggered the command, may be <code>null</code>.
     * @since 3.4
     */
    public void firePreExecute(String commandId, ExecutionEvent event) {
        if (executionListener !is null) {
            executionListener.preExecute(commandId, event);
        }
    }
    
    /**
     * Fires the <code>postExecuteSuccess</code> event for
     * <code>executionListeners</code>.
     * <p>
     * <b>Note:</b> This supports bridging actions to the command framework,
     * and should not be used outside the framework.
     * </p>
     * 
     * @param commandId
     *            The command id of the command executed, never
     *            <code>null</code>.
     * @param returnValue
     *            The value returned from the command, may be <code>null</code>.
     * @since 3.4
     */
    public void firePostExecuteSuccess(String commandId, Object returnValue) {
        if (executionListener !is null) {
            executionListener.postExecuteSuccess(commandId, returnValue);
        }
    }
    
    /**
     * Fires the <code>postExecuteFailure</code> event for
     * <code>executionListeners</code>.
     * <p>
     * <b>Note:</b> This supports bridging actions to the command framework,
     * and should not be used outside the framework.
     * </p>
     * 
     * @param commandId
     *            The command id of the command executed, never
     *            <code>null</code>.
     * @param exception
     *            The exception, never <code>null</code>.
     * @since 3.4
     */
    public void firePostExecuteFailure(String commandId, 
            ExecutionException exception) {
        if (executionListener !is null) {
            executionListener.postExecuteFailure(commandId, exception);
        }
    }
}