Mercurial > projects > dwt-addons
diff dwtx/core/commands/ParameterizedCommand.d @ 3:6518c18a01f7
eclipse.core package without osgi dependencies
author | Frank Benoit <benoit@tionex.de> |
---|---|
date | Wed, 26 Mar 2008 00:57:19 +0100 |
parents | |
children | ea8ff534f622 |
line wrap: on
line diff
--- /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 <benoit@tionex.de> + *******************************************************************************/ + +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; + +/** + * <p> + * 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. + * </p> + * + * @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 <code>String</code> to escape special characters in for + * serialization. + * @return a <code>String</code> representing <code>rawText</code> 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 <code>parameters</code> that we should + * process. This must be a valid index. + * @param parameters + * The parameters in to process; must not be <code>null</code>. + * @return A collection (<code>Collection</code>) of combinations (<code>List</code> + * of <code>Parameterization</code>). + */ + 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; + } + + /** + * <p> + * 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. + * </p> + * <p> + * If one of the parameters cannot be loaded due to a + * <code>ParameterValuesException</code>, then it is simply ignored. + * </p> + * + * @param command + * The command for which the parameter combinations should be + * generated; must not be <code>null</code>. + * @return A collection of <code>ParameterizedCommand</code> instances + * representing all of the possible combinations. This value is + * never empty and it is never <code>null</code>. + * @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 + * <code>null</code>. + */ + 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 <code>null</code> if the command has no parameters. + */ + private const Parameterization[] parameterizations; + + private String name; + + /** + * Constructs a new instance of <code>ParameterizedCommand</code> with + * specific values for zero or more of its parameters. + * + * @param command + * The command that is parameterized; must not be + * <code>null</code>. + * @param parameterizations + * An array of parameterizations binding parameters to values for + * the command. This value may be <code>null</code>. 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 + * <code>null</code>. + * @param applicationContext + * The state of the application at the time the execution was + * triggered; may be <code>null</code>. + * @return The result of the execution; may be <code>null</code>. + * @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 + * <code>null</code>. + * @param applicationContext + * The state of the application at the time the execution was + * triggered; may be <code>null</code>. + * @return The result of the execution; may be <code>null</code>. + * @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 <code>null</code>, 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 <code>null</code>. + */ + 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 <code>null</code>. + * @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 + * <code>ExecutionEvent</code>. + * + * @return The map of parameter ids (<code>String</code>) to parameter + * values (<code>String</code>). This map is never + * <code>null</code>, 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. + * <p> + * The syntax of the returned {@link String} is as follows: + * </p> + * + * <blockquote> + * <code>serialization = <u>commandId</u> [ '(' parameters ')' ]</code><br> + * <code>parameters = parameter [ ',' parameters ]</code><br> + * <code>parameter = <u>parameterId</u> [ '=' <u>parameterValue</u> ]</code> + * </blockquote> + * + * <p> + * In the syntax above, sections inside square-brackets are optional. The + * characters in single quotes (<code>(</code>, <code>)</code>, + * <code>,</code> and <code>=</code>) indicate literal characters. + * </p> + * <p> + * <code><u>commandId</u></code> represents the command id encoded with + * separator characters escaped. <code><u>parameterId</u></code> and + * <code><u>parameterValue</u></code> represent the parameter ids and + * values encoded with separator characters escaped. The separator + * characters <code>(</code>, <code>)</code>, <code>,</code> and + * <code>=</code> are escaped by prepending a <code>%</code>. This + * requires <code>%</code> to be escaped, which is also done by prepending + * a <code>%</code>. + * </p> + * <p> + * The order of the parameters is not defined (and not important). A missing + * <code><u>parameterValue</u></code> indicates that the value of the + * parameter is <code>null</code>. + * </p> + * <p> + * For example, the string shown below represents a serialized parameterized + * command that can be used to show the Resource perspective: + * </p> + * <p> + * <code>dwtx.ui.perspectives.showPerspective(dwtx.ui.perspectives.showPerspective.perspectiveId=dwtx.ui.resourcePerspective)</code> + * </p> + * <p> + * This example shows the more general form with multiple parameters, + * <code>null</code> value parameters, and escaped <code>=</code> in the + * third parameter value. + * </p> + * <p> + * <code>command.id(param1.id=value1,param2.id,param3.id=esc%=val3)</code> + * </p> + * + * @return A string containing the escaped command id, parameter ids and + * parameter values; never <code>null</code>. + * @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); + } +}