| /*************************************************************************************************** |
| * Copyright (c) 2003, 2004 IBM Corporation and others. All rights reserved. This program and the |
| * accompanying materials are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: IBM Corporation - initial API and implementation |
| **************************************************************************************************/ |
| package org.eclipse.wst.common.frameworks.internal.operations; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.Hashtable; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.eclipse.core.resources.IProject; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.MultiStatus; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.wst.common.frameworks.internal.WTPResourceHandler; |
| |
| /** |
| * WTPOperationDataModel is an essential piece of both the WTP Operation and WTP Wizard frameworks. |
| * WTPOPerationDataModels (DataModels) act as smart property containers used to pass various |
| * properties between components. DataModels are smart property containers because they can: |
| * <UL> |
| * <LI>Compute default values for their properties thus saving clients from needing to populate (or |
| * understand) all available properties.</LI> |
| * <LI>Modify the computed default values when necessary (e.g. if the default value of property A |
| * is based on property B, then A should change when B changes).</LI> |
| * <LI>Notify listeners when properties change.</LI> |
| * <LI>Check the validity of the of the proptery values (e.g. if a property is supposed to be an |
| * Integer < 10)</LI> |
| * <LI>Check the validity of the entire property set (e.g if property A is supposed to be an Iteger |
| * which is a multiple of the Integer property B).</LI> |
| * <LI>Supply an operation to execute.</LI> |
| * <LI>Compose and decompose entire DataModels through nesting.</LI> |
| * </UL> |
| * |
| * <B>PropertyNames </B> Clients interact with DataModels by getting and setting properties |
| * (Objects) with PropertyNames. A PropertyName is a String Object uniquely identifing a particular |
| * property. The recommended practice for defining PropertyNames is to define them as static final |
| * Class level Strings and to use the DataModel instance class name appended with the property name |
| * as the value (this should ensure uniqueness and gives a readible value when debugging). |
| * |
| * <A NAME="nestedDataModels"> <!-- --> </A> |
| * <p> |
| * <B>Nested DataModels </B> |
| * <p> |
| * |
| * |
| * The WTP Wizard framework uses DataModels to hold all the properties displayed to the user through |
| * UI controls (e.g. textboxes, comboboxes, etc.). The Wizard framework also relies on DataModels |
| * for validation, default values, and the operation executed on finish. |
| * |
| * The WTO Operation framework uses DataModels to pass all parameters for execution. The DataModel |
| * validation is used to ensure all the properties are valid prior to operation execution. |
| * |
| * This class is EXPERIMENTAL and is subject to substantial changes. |
| * |
| * @link org.eclipse.wst.common.frameworks.ui.WTPWizard for details on the WTP Wizard framework. |
| * @link org.eclipse.wst.common.frameworks.operations.WTPOperation for details on the WTP Operation |
| * framework. |
| */ |
| public abstract class WTPOperationDataModel implements WTPOperationDataModelListener { |
| |
| /** |
| * This property is used by the extended operation framework. Extended operations run within a |
| * context; e.g. they run within the scope of a project, resource, file, etc. The default value |
| * for this property is an empty List. Subclasses should configure themselves to return a list |
| * Objects. Each Object represents a Context which should be adaptable to IProject. |
| */ |
| protected static final String EXTENDED_CONTEXT = "WTPOperationDataModel.EXTENDED_CONTEXT"; //$NON-NLS-1$ |
| |
| /** |
| * An unsettable property used soley to trip validation for nested models. Clients only use this |
| * property for validation purposes and never get or set its value. Subclasses can override |
| * nested model validation by checking for this property in the doValidate method and not |
| * calling super with it. |
| */ |
| public static final String NESTED_MODEL_VALIDATION_HOOK = "WTPOperationDataModel.NESTED_MODEL_VALIDATION_HOOK"; //$NON-NLS-1$ |
| /** |
| * Optional, type boolean This boolean was added for users who wish to delay the operation from |
| * being run on a "finish". The operation will be cached in the CACHED_DELAYED_OPERATION which |
| * then leaves the user responsible for running this operation when they see fit. |
| */ |
| public static final String RUN_OPERATION = "WTPOperationDataModel.RUN_OPERATION"; //$NON-NLS-1$ |
| /** |
| * Internal, type WTPOperation |
| */ |
| public static final String CACHED_DELAYED_OPERATION = "WTPOperationDataModel.CACHED_DELAYED_OPERATION"; //$NON-NLS-1$ |
| /** |
| * Optional Operation handler to allow user to prompt on save defaults to NullOperationHandler() |
| * set to UIOperationHanlder() to add prompt |
| */ |
| public static final String UI_OPERATION_HANLDER = "WTPOperationDataModel.UI_OPERATION_HANLDER"; //$NON-NLS-1$ |
| |
| /** |
| * This is a convenience static status for subclasses to return during validation when the |
| * validation is OK. |
| */ |
| protected static IStatus OK_STATUS = new Status(IStatus.OK, "org.eclipse.wst.common.frameworks.internal", 0, "OK", null); //$NON-NLS-1$ //$NON-NLS-2$ |
| |
| private static final String PROPERTY_NOT_LOCATED_ = WTPResourceHandler.getString("20"); //$NON-NLS-1$ |
| private static final String NESTED_MODEL_NOT_LOCATED = WTPResourceHandler.getString("21"); //$NON-NLS-1$ |
| private static final String NESTED_MODEL_DUPLICATE = WTPResourceHandler.getString("33"); //$NON-NLS-1$ |
| |
| private Set validProperties = new HashSet(); |
| private Set validBaseProperties = new HashSet(); |
| private Map propertyValues = new Hashtable(); |
| private Map nestedModels; |
| private Set nestingModels; |
| private List listeners; |
| private boolean ignorePropertyChanges = false; |
| private boolean notificationEnabled = true; |
| private boolean locked = false; |
| private boolean operationValidationEnabled = false; |
| private boolean hasBeenExecutedAgainst = false; |
| private boolean suspendValidation = false; |
| |
| /** |
| * <p> |
| * The WTPOperationDataModel constructor. This constructor will first add the base |
| * WTPOPerationDataModel properties (RUN_OPERATION, CACHED_DELAYED_OPERATION, and |
| * UI_OPERATION_HANLDER). It then invokes the following: |
| * </p> |
| * <ol> |
| * <li><b><code>initValidBaseProperties()</code> </b> subclasses should override this method |
| * to add their properties using <code>addValidBaseProperty(String)</code>.</li> |
| * <li><b><code>initNestedModels()</code> </b> subclasses should override this method to add |
| * their nested models using <code>addNestedModel(String, WTPOperationDataModel)</code>. |
| * </li> |
| * <li><b><code>init()</code> </b> subclasses should override this method to perform any |
| * final initialization.</li> |
| * </ol> |
| * |
| * @see #initValidBaseProperties() |
| * @see #initNestedModels() |
| * @see #init() |
| */ |
| public WTPOperationDataModel() { |
| init_internal(); |
| } |
| |
| private final void init_internal() { |
| addValidBaseProperty(EXTENDED_CONTEXT); |
| addValidBaseProperty(RUN_OPERATION); |
| addValidBaseProperty(CACHED_DELAYED_OPERATION); |
| addValidBaseProperty(UI_OPERATION_HANLDER); |
| initValidBaseProperties(); |
| initNestedModels(); |
| init(); |
| } |
| |
| /** |
| * Subclasses should use this method within <code>initValidBaseProperties()</code> to add |
| * properties. |
| * |
| * @param propertyName |
| * The property name to be added. |
| * @see #initValidBaseProperties() |
| */ |
| protected final void addValidBaseProperty(String propertyName) { |
| validBaseProperties.add(propertyName); |
| validProperties.add(propertyName); |
| } |
| |
| /** |
| * Subclasses should override this method to add properties using |
| * <code>addValidBaseProperty(String)</code>. |
| * |
| * @see #addValidBaseProperty(String) |
| * @see #WTPOperationDataModel() |
| */ |
| protected void initValidBaseProperties() { |
| } |
| |
| /** |
| * Subclasses should override this method to add nested DataModels using |
| * <code>addNestedModel(String, WTPOperationDataModel)</code>. |
| * |
| * @see #addNestedModel(String, WTPOperationDataModel) |
| * @see #WTPOperationDataModel() |
| */ |
| protected void initNestedModels() { |
| } |
| |
| /** |
| * Subclasses should override this method to perform any final initialization not covered by |
| * either <code>initValidBaseProperties()</code> or <code>initNestedModels()</code>. |
| * |
| * @see #initValidBaseProperties() |
| * @see #initNestedModels() |
| * @see #WTPOperationDataModel() |
| */ |
| protected void init() { |
| } |
| |
| /** |
| * <p> |
| * This method is used to nest the specified WTPOperationDataModel within this |
| * WTPOperationDataModel. The <code>modelName</code> argument should be a unique String to |
| * identify this particular nested DataModel. The same String is required when accessing the |
| * nested DataModel using either <code>getNestedModel(String)</code> or |
| * <code>removeNestedModel(String)</code>. If this is the first nested DataModel being added, |
| * then the <code>NESTED_MODEL_VALIDATION_HOOK</code> will be added to this DataModel . |
| * </p> |
| * <p> |
| * Refer to <A HREF="#nestedDataModels"> <CODE>NestedDataModels</CODE> </A>. |
| * </p> |
| * |
| * @param modelName |
| * the name of the WTPOperationDataModel to be nested |
| * @param dataModel |
| * the WTPOperationDataModel to be nested |
| * |
| * @see #getNestedModel(String) |
| * @see #removeNestedModel(String) |
| */ |
| public final void addNestedModel(String modelName, WTPOperationDataModel dataModel) { |
| if (dataModel == null) |
| return; |
| if (null == nestedModels) { |
| validBaseProperties.add(NESTED_MODEL_VALIDATION_HOOK); |
| validProperties.add(NESTED_MODEL_VALIDATION_HOOK); |
| nestedModels = new Hashtable(); |
| } |
| if (null == dataModel.nestingModels) { |
| dataModel.nestingModels = new HashSet(); |
| } |
| if (dataModel.nestingModels.contains(this)) { |
| throw new RuntimeException(NESTED_MODEL_DUPLICATE); |
| } |
| dataModel.nestingModels.add(this); |
| |
| nestedModels.put(modelName, dataModel); |
| |
| addNestedProperties(dataModel.validProperties); |
| dataModel.addListener(this); |
| } |
| |
| private void addNestedProperties(Set nestedProperties) { |
| boolean propertiesAdded = validProperties.addAll(nestedProperties); |
| // Pass the new properties up the nesting chain |
| if (propertiesAdded && nestingModels != null) { |
| Iterator iterator = nestingModels.iterator(); |
| while (iterator.hasNext()) { |
| ((WTPOperationDataModel) iterator.next()).addNestedProperties(nestedProperties); |
| } |
| } |
| } |
| |
| public Iterator getNestedModels() { |
| return nestedModels.values().iterator(); |
| } |
| |
| public Iterator getNestingModels() { |
| return nestingModels.iterator(); |
| } |
| |
| /** |
| * Subclasses should override to return a WTPOperation to execute using this DataModel instance. |
| * The goal is for clients of to be able to simple create an instance of a particular DataModel, |
| * set a few properties on it, and then execute the operation returned by getDefaultOperation() |
| * |
| * @return an initialized and executable WTPOperation |
| */ |
| public abstract WTPOperation getDefaultOperation(); |
| |
| public WTPOperationDataModel removeNestedModel(String modelName) { |
| if (modelName == null || nestedModels == null) |
| return null; |
| WTPOperationDataModel model = (WTPOperationDataModel) nestedModels.remove(modelName); |
| model.nestingModels.remove(this); |
| removeNestedProperties(model.validProperties); |
| model.removeListener(this); |
| if (nestedModels.isEmpty()) { |
| nestedModels = null; |
| validBaseProperties.remove(NESTED_MODEL_VALIDATION_HOOK); |
| validProperties.remove(NESTED_MODEL_VALIDATION_HOOK); |
| } |
| return model; |
| } |
| |
| private void removeNestedProperties(Set nestedProperties) { |
| Iterator iterator = nestedProperties.iterator(); |
| String property = null; |
| boolean keepProperty = false; |
| Set nestedPropertiesToRemove = null; |
| while (iterator.hasNext()) { |
| keepProperty = false; |
| property = (String) iterator.next(); |
| if (validBaseProperties.contains(property)) { |
| keepProperty = true; |
| } |
| if (!keepProperty && nestedModels != null) { |
| Iterator nestedModelsIterator = nestedModels.values().iterator(); |
| while (!keepProperty && nestedModelsIterator.hasNext()) { |
| WTPOperationDataModel nestedModel = (WTPOperationDataModel) nestedModelsIterator.next(); |
| if (nestedModel.isProperty(property)) { |
| keepProperty = true; |
| } |
| } |
| } |
| if (!keepProperty) { |
| if (null == nestedPropertiesToRemove) { |
| nestedPropertiesToRemove = new HashSet(); |
| } |
| nestedPropertiesToRemove.add(property); |
| } |
| } |
| |
| if (null != nestedPropertiesToRemove) { |
| validProperties.removeAll(nestedPropertiesToRemove); |
| if (nestingModels != null) { |
| Iterator nestingModelsIterator = nestingModels.iterator(); |
| while (nestingModelsIterator.hasNext()) { |
| ((WTPOperationDataModel) nestingModelsIterator.next()).removeNestedProperties(nestedPropertiesToRemove); |
| } |
| } |
| } |
| } |
| |
| |
| public final WTPOperationDataModel getNestedModel(String modelName) { |
| WTPOperationDataModel dataModel = (WTPOperationDataModel) nestedModels.get(modelName); |
| if (null == dataModel) { |
| throw new RuntimeException(NESTED_MODEL_NOT_LOCATED + modelName); |
| } |
| return dataModel; |
| } |
| |
| /** |
| * Subclasses can override this method to determine if a given propertyName should be enabled |
| * for edit. Returning null indicates that there is no precedence on the enablement. Note that |
| * you can override this in an outer model since enablement may be different in the outer model. |
| * |
| * @param propertyName |
| * @return the property's enablment state, null indicates there is no state |
| */ |
| public final Boolean isEnabled(String propertyName) { |
| checkValidPropertyName(propertyName); |
| return basicIsEnabled(propertyName); |
| } |
| |
| /** |
| * Subclasses can override this method to determine if a given propertyName should be enabled |
| * for edit. Returning null indicates that there is no precedence on the enablement. Note that |
| * you can override this in an outer model since enablement may be different in the outer model. |
| * |
| * @param propertyName |
| * @return |
| */ |
| protected Boolean basicIsEnabled(String propertyName) { |
| if (isBaseProperty(propertyName)) { |
| return null; |
| } else if (nestedModels != null) { |
| WTPOperationDataModel dataModel = null; |
| Object[] keys = nestedModels.keySet().toArray(); |
| for (int i = 0; i < keys.length; i++) { |
| dataModel = (WTPOperationDataModel) nestedModels.get(keys[i]); |
| if (dataModel.isProperty(propertyName)) { |
| return dataModel.isEnabled(propertyName); |
| } |
| } |
| } |
| throw new RuntimeException(PROPERTY_NOT_LOCATED_ + propertyName); |
| } |
| |
| /** |
| * <p> |
| * Returns a WTPPropertyDescriptor array consisting of all valid WTPPropertyDescriptors for the |
| * specified property. Each WTPPropertyDescriptor {@see WTPPropertyDescriptor for details} |
| * contains a value and a human readible description for the value. The set of all values in the |
| * returned array are those values which are valid for the DataModel. This value set only makes |
| * sense when valid property values conform to a well defined finite set. If no such value set |
| * exists for the property, the a 0 length array is returned. <code>null</code> is never |
| * returned. |
| * </p> |
| * <p> |
| * As an example, suppose there is a property called <code>SHIRT_SIZE</code> which is an |
| * <code>Integer</code> type. Also suppse that valid shirt sizes are only small, medium, or |
| * large. However, the actual values representing small, medium, and large are 1, 2, and 3 |
| * respectively. A call to <code>getValidPropertyDescriptors(SHIRT_SIZE)</code> would return a |
| * WTPPropertyDescriptor array where the value, description pairs would be {(1, small), (2, |
| * medium), (3, large)}. |
| * </p> |
| * <p> |
| * Subclasses should override {@see #doGetValidPropertyDescriptors(String)}as necessary. |
| * </p> |
| * |
| * @param propertyName |
| * @return the array of valid WTPPropertyDescriptors |
| * @see #getPropertyDescriptor(String) |
| * @see #doGetValidPropertyDescriptors(String) |
| */ |
| public final WTPPropertyDescriptor[] getValidPropertyDescriptors(String propertyName) { |
| checkValidPropertyName(propertyName); |
| if (isBaseProperty(propertyName)) { |
| return doGetValidPropertyDescriptors(propertyName); |
| } else if (nestedModels != null) { |
| WTPOperationDataModel dataModel = null; |
| Object[] keys = nestedModels.keySet().toArray(); |
| for (int i = 0; i < keys.length; i++) { |
| dataModel = (WTPOperationDataModel) nestedModels.get(keys[i]); |
| if (dataModel.isProperty(propertyName)) { |
| return dataModel.getValidPropertyDescriptors(propertyName); |
| } |
| } |
| } |
| throw new RuntimeException(PROPERTY_NOT_LOCATED_ + propertyName); |
| } |
| |
| /** |
| * Subclasses should override this method to return their set of valid WTPPropertyDescriptors. |
| * This default implementation returns: <code>new WTPPropertyDescriptor[0];</code> |
| * |
| * @param propertyName |
| * @return |
| * |
| * @see #getValidPropertyDescriptors(String) |
| */ |
| protected WTPPropertyDescriptor[] doGetValidPropertyDescriptors(String propertyName) { |
| return new WTPPropertyDescriptor[0]; |
| } |
| |
| /** |
| * <p> |
| * Returns a WTPPropertyDescriptor for the specified property. The |
| * <code>getPropertyValue()</code> method on the returned WTPPropertyDescriptor will be the |
| * same value as returned by <code>getPropertyValue(propertyName)</code>. |
| * </p> |
| * <p> |
| * Following the example introduced in {@see #getValidPropertyDescriptors(String)}, suppose the |
| * <code>SHIRT_SIZE</code> property is currently set to 1. A call to this method would return |
| * a WTPPropertyDescriptor whose <code>getPropertyValue()</code> returns <code>1</code> and |
| * whose <code>getPropertyDescription()</code> returns <code>small</code>. |
| * </p> |
| * <p> |
| * Also, note that even if a particular property is not confined to a finite set of values as |
| * defined by {@see #getValidPropertyDescriptors(String)}this method will always return a valid |
| * WTPPropertyDescriptor. |
| * </p> |
| * <p> |
| * Subclasses should should override {@see #doGetPropertyDescriptor(String)}as necessary. |
| * </p> |
| * |
| * @param propertyName |
| * @return the WTPPropertyDescriptor for the specified property |
| * |
| * @see #doGetValidPropertyDescriptors(String) |
| * @see #doGetPropertyDescriptor(String) |
| */ |
| public final WTPPropertyDescriptor getPropertyDescriptor(String propertyName) { |
| checkValidPropertyName(propertyName); |
| if (isBaseProperty(propertyName)) { |
| return doGetPropertyDescriptor(propertyName); |
| } else if (nestedModels != null) { |
| WTPOperationDataModel dataModel = null; |
| Object[] keys = nestedModels.keySet().toArray(); |
| for (int i = 0; i < keys.length; i++) { |
| dataModel = (WTPOperationDataModel) nestedModels.get(keys[i]); |
| if (dataModel.isProperty(propertyName)) { |
| return dataModel.doGetPropertyDescriptor(propertyName); |
| } |
| } |
| } |
| throw new RuntimeException(PROPERTY_NOT_LOCATED_ + propertyName); |
| } |
| |
| /** |
| * Subclasses should override this method as necessary. This default implementation returns |
| * <code>new WTPPropertyDescriptor(getProperty(propertyName));</code>. |
| * |
| * @param propertyName |
| * @return |
| */ |
| protected WTPPropertyDescriptor doGetPropertyDescriptor(String propertyName) { |
| return new WTPPropertyDescriptor(getProperty(propertyName)); |
| } |
| |
| /** |
| * <p> |
| * A convenience method for getting Strings. If the property is set then this method is |
| * equavalent to: |
| * <p> |
| * <code>(String)getProperty(propertyName)</code> |
| * <p> |
| * If the property is unset, the empty String, <code>""</code>, will be returned. |
| * |
| * @param propertyName |
| * @param value |
| * @see #setProperty(String, Object) |
| */ |
| public final String getStringProperty(String propertyName) { |
| Object prop = getProperty(propertyName); |
| if (prop == null) |
| return ""; //$NON-NLS-1$ |
| return (String) prop; |
| } |
| |
| /** |
| * <p> |
| * A convenience method for getting booleans. If the property is set then this method is |
| * equavalent to: |
| * <p> |
| * <code>((Boolean)getProperty(propertyName)).booleanValue();</code> |
| * <p> |
| * If the property is unset, <code>false</code> will be returned. |
| * |
| * @param propertyName |
| * the property name |
| * @return the boolean value of the property |
| * @see #setProperty(String, Object) |
| * @see #setBooleanProperty(String, int) |
| */ |
| public final boolean getBooleanProperty(String propertyName) { |
| Object prop = getProperty(propertyName); |
| if (prop == null) |
| return false; |
| return ((Boolean) prop).booleanValue(); |
| } |
| |
| /** |
| * <p> |
| * A convenience method for getting ints. If the property is set then this method is equavalent |
| * to: |
| * <p> |
| * <code>((Integer)getProperty(propertyName)).intValue();</code> |
| * <p> |
| * If the property is unset, <code>-1</code> will be returned. |
| * |
| * @param propertyName |
| * the property name |
| * @return the int value of the property |
| * @see #setProperty(String, Object) |
| * @see #setIntProperty(String, int) |
| */ |
| public final int getIntProperty(String propertyName) { |
| Object prop = getProperty(propertyName); |
| if (prop == null) |
| return -1; |
| return ((Integer) prop).intValue(); |
| } |
| |
| /** |
| * <p> |
| * A convenience method for setting booleans. This method is equavalent to: |
| * <p> |
| * <code>setProperty(propertyName, (value) ? Boolean.TRUE : Boolean.FALSE);</code> |
| * </p> |
| * |
| * @param propertyName |
| * the name of the property |
| * @param value |
| * the <code>boolean</code> value of the property |
| * @see #setProperty(String, Object) |
| * @see #getBooleanProperty(String) |
| */ |
| public final void setBooleanProperty(String propertyName, boolean value) { |
| setProperty(propertyName, (value) ? Boolean.TRUE : Boolean.FALSE); |
| } |
| |
| /** |
| * <p> |
| * A convenience method for setting ints. This method is equavalent to: |
| * <p> |
| * <code>setProperty(propertyName, new Integer(value));</code> |
| * </p> |
| * |
| * @param propertyName |
| * the name of the property |
| * @param value |
| * the <code>int</code> value of the property |
| * @see #setProperty(String, Object) |
| * @see #getIntProperty(String) |
| */ |
| public void setIntProperty(String propertyName, int value) { |
| setProperty(propertyName, new Integer(value)); |
| } |
| |
| /** |
| * Override this method to compute default property values. |
| * |
| * @param propertyName |
| * @return |
| */ |
| protected Object getDefaultProperty(String propertyName) { |
| if (propertyName.equals(EXTENDED_CONTEXT)) { |
| Object targetProject = getTargetProject(); // TODO delete this block with |
| // getTargetProject() |
| if (null == targetProject) { |
| return Collections.EMPTY_LIST; |
| } else { |
| List list = new ArrayList(); |
| list.add(targetProject); |
| return list; |
| } |
| } |
| if (propertyName.equals(RUN_OPERATION)) { |
| return Boolean.TRUE; |
| } |
| if (propertyName.equals(UI_OPERATION_HANLDER)) { |
| return new NullOperationHandler(); |
| } |
| return null; |
| } |
| |
| /** |
| * Returns <code>true</code> if the specified propertyName is a valid propertyName for this |
| * root DataModel only. Nested DataModels are not checked. |
| * |
| * @param propertyName |
| * @return |
| * @see #isProperty(String) |
| */ |
| public final boolean isBaseProperty(String propertyName) { |
| if (validBaseProperties != null) |
| return validBaseProperties.contains(propertyName); |
| return true; |
| } |
| |
| /** |
| * Returns <code>true</code> if the specified propertyName is a valid propertyName for this |
| * DataModel or any of its (recursively) nested DataModels. |
| * |
| * @param propertyName |
| * @return |
| * @see #isBaseProperty(String) |
| */ |
| public final boolean isProperty(String propertyName) { |
| return validProperties.contains(propertyName); |
| } |
| |
| private void checkValidPropertyName(String propertyName) { |
| if (!validProperties.contains(propertyName)) { |
| throw new RuntimeException(PROPERTY_NOT_LOCATED_ + propertyName); |
| } |
| } |
| |
| /** |
| * <p> |
| * Returns the property value for the specified propertyName. |
| * </p> |
| * <p> |
| * If the specified propertyName is not a property {@see #isProperty(String)}then a |
| * RuntimeException will be thrown. |
| * </p> |
| * <p> |
| * If the specified propertyName is a base property {@see #isBaseProperty(String)}then it will |
| * immediatly be set and nested models will not be affected. If it is not a base property (i.e. |
| * it is a property for a nested DataModel) then a recursive search through nested DataModels |
| * will be conducted. The first nested DataModel having the property will return its value. |
| * </p> |
| * |
| * @param propertyName |
| * @return |
| * |
| * @see #getBooleanProperty(String) |
| * @see #getIntProperty(String) |
| * @see #getStringProperty(String) |
| */ |
| public final Object getProperty(String propertyName) { |
| checkValidPropertyName(propertyName); |
| if (isBaseProperty(propertyName)) { |
| return doGetProperty(propertyName); |
| } else if (nestedModels != null) { |
| WTPOperationDataModel dataModel = null; |
| Object[] keys = nestedModels.keySet().toArray(); |
| for (int i = 0; i < keys.length; i++) { |
| dataModel = (WTPOperationDataModel) nestedModels.get(keys[i]); |
| if (dataModel.isProperty(propertyName)) { |
| return dataModel.getProperty(propertyName); |
| } |
| } |
| } |
| throw new RuntimeException(PROPERTY_NOT_LOCATED_ + propertyName); |
| } |
| |
| /** |
| * <p> |
| * Though generally unecessary, implementors may override this method as they see fit. |
| * <p> |
| * |
| * @param propertyName |
| * @return |
| */ |
| protected Object doGetProperty(String propertyName) { |
| if (propertyValues.containsKey(propertyName)) { |
| return propertyValues.get(propertyName); |
| } |
| return getDefaultProperty(propertyName); |
| } |
| |
| /** |
| * <p> |
| * Sets the specified propertyName to the specified propertyValue. Subsequent calls to |
| * #getProperty(String) will return the same propertyValue. |
| * <p> |
| * When a propertyValue other than <code>null</code> is set, then the property is considered |
| * "set" (see #isSet(String)), conversly, a propertyValue of <code>null</code> is considered |
| * "unset". |
| * <p> |
| * If the specified propertyName is not a property (see#isProperty(String)) then a |
| * RuntimeException will be thrown. |
| * <p> |
| * Attempting to set a propertyName when this DataModel is locked (see #isLocked()) will result |
| * in a thrown IllegalStateException. An IllegalStateException will not be thrown, however, if |
| * the propertyName is a Result Property, (see #isResultProperty(String)). |
| * <p> |
| * |
| * @param propertyName |
| * @param propertyValue |
| * |
| * |
| * @see #getProperty(String) |
| * @see #isSet(String) |
| * @see #isProperty(String) |
| * @see #isResultProperty(String) |
| * @see #isLocked() |
| * |
| * <p> |
| * There are also convenience methods for setting properties representing property types of |
| * boolean and int. |
| * <p> |
| * @see #setBooleanProperty(String, boolean) |
| * @see #setIntProperty(String, int) |
| */ |
| public final void setProperty(String propertyName, Object propertyValue) { |
| if (isLocked() && !isResultProperty(propertyName)) |
| throw new IllegalStateException(WTPResourceHandler.getString("18", new Object[]{getClass().getName()})); //$NON-NLS-1$ |
| if (ignorePropertyChanges) |
| return; // ignoring property changes |
| checkValidPropertyName(propertyName); |
| boolean nestedFound = false; |
| if (isBaseProperty(propertyName)) { |
| internalSetProperty(propertyName, propertyValue); |
| return; |
| } else if (nestedModels != null) { |
| WTPOperationDataModel dataModel = null; |
| Object[] keys = nestedModels.keySet().toArray(); |
| for (int i = 0; i < keys.length; i++) { |
| dataModel = (WTPOperationDataModel) nestedModels.get(keys[i]); |
| if (dataModel.isProperty(propertyName)) { |
| nestedFound = true; |
| dataModel.setProperty(propertyName, propertyValue); |
| } |
| } |
| } |
| if (!nestedFound) { |
| throw new RuntimeException(PROPERTY_NOT_LOCATED_ + propertyName); |
| } |
| } |
| |
| private final void internalSetProperty(String propertyName, Object propertyValue) { |
| Object oldValue = propertyValues.get(propertyName); |
| if (valueChanged(propertyValue, oldValue)) { |
| if (doSetProperty(propertyName, propertyValue)) { |
| notifyListeners(propertyName, WTPOperationDataModelEvent.PROPERTY_CHG); |
| } |
| } |
| } |
| |
| /* |
| * Return true to notify listeners. |
| */ |
| protected boolean doSetProperty(String propertyName, Object propertyValue) { |
| if (null != propertyValue) |
| propertyValues.put(propertyName, propertyValue); |
| else if (propertyValues.containsKey(propertyName)) |
| propertyValues.remove(propertyName); |
| return true; |
| } |
| |
| private boolean valueChanged(Object o1, Object o2) { |
| return o1 != o2 && ((o1 != null && !o1.equals(o2)) || !o2.equals(o1)); |
| } |
| |
| /** |
| * Convenience method to create a WTPOperationDataModelEvent.PROPERTY_CHG event and notify |
| * listeners. |
| * |
| * @param propertyName |
| * @see #notifyListeners(WTPOperationDataModelEvent) |
| */ |
| protected void notifyListeners(String propertyName) { |
| notifyListeners(propertyName, WTPOperationDataModelEvent.PROPERTY_CHG); |
| } |
| |
| /** |
| * Convenience method to create a WTPOperationDataModelEvent event of the specified type and |
| * notify listeners. |
| * |
| * @param propertyName |
| * @see WTPOperationDataModelEvent for the list of valid flag values |
| */ |
| protected void notifyListeners(String propertyName, int flag) { |
| notifyListeners(new WTPOperationDataModelEvent(this, propertyName, flag)); |
| } |
| |
| /** |
| * Notifies all registerd WTPOperationDataModelListeners of the specified |
| * WTPOperationDataModelEvent. |
| * |
| * @param event |
| */ |
| protected void notifyListeners(WTPOperationDataModelEvent event) { |
| if (notificationEnabled && listeners != null && !listeners.isEmpty()) { |
| WTPOperationDataModelListener listener; |
| for (int i = 0; i < listeners.size(); i++) { |
| listener = (WTPOperationDataModelListener) listeners.get(i); |
| if (listener != event.getDataModel()) { |
| listener.propertyChanged(event); |
| } |
| } |
| } |
| } |
| |
| public void propertyChanged(WTPOperationDataModelEvent event) { |
| notifyListeners(event); |
| } |
| |
| public boolean isSet(String propertyName) { |
| checkValidPropertyName(propertyName); |
| if (isBaseProperty(propertyName)) { |
| return propertyValues.containsKey(propertyName); |
| } else if (nestedModels != null) { |
| WTPOperationDataModel dataModel = null; |
| Object[] keys = nestedModels.keySet().toArray(); |
| for (int i = 0; i < keys.length; i++) { |
| dataModel = (WTPOperationDataModel) nestedModels.get(keys[i]); |
| if (dataModel.isProperty(propertyName)) { |
| return dataModel.isSet(propertyName); |
| } |
| } |
| } |
| throw new RuntimeException(PROPERTY_NOT_LOCATED_ + propertyName); |
| } |
| |
| public final IStatus validateDataModel() { |
| return validateDataModel(false); |
| } |
| |
| public final IStatus validateDataModel(boolean stopOnFirstFailure) { |
| if (suspendValidation) |
| return OK_STATUS; |
| IStatus status = null; |
| if (validBaseProperties != null && !validBaseProperties.isEmpty()) { |
| IStatus propStatus; |
| String propName; |
| Iterator it = validBaseProperties.iterator(); |
| while (it.hasNext()) { |
| propName = (String) it.next(); |
| propStatus = validateProperty(propName); |
| if (status == null || status.isOK()) |
| status = propStatus; |
| else { |
| if (status.isMultiStatus()) |
| ((MultiStatus) status).merge(propStatus); |
| else { |
| MultiStatus multi = new MultiStatus("org.eclipse.wst.common.frameworks.internal", 0, "", null); //$NON-NLS-1$ //$NON-NLS-2$ |
| multi.merge(status); |
| multi.merge(propStatus); |
| status = multi; |
| } |
| } |
| if (stopOnFirstFailure && status != null && !status.isOK() && status.getSeverity() == IStatus.ERROR) |
| return status; |
| } |
| } |
| if (status == null) |
| return OK_STATUS; |
| return status; |
| } |
| |
| public void addListener(WTPOperationDataModelListener listener) { |
| if (listener != null) { |
| if (listeners == null) { |
| listeners = new ArrayList(); |
| listeners.add(listener); |
| } else if (!listeners.contains(listener)) |
| listeners.add(listener); |
| } |
| } |
| |
| public void removeListener(WTPOperationDataModelListener listener) { |
| if (listeners != null && listener != null) |
| listeners.remove(listener); |
| } |
| |
| /** |
| * Return true if the model doesn't have any errors. |
| * |
| * @return boolean |
| */ |
| public boolean isValid() { |
| IStatus status = validateDataModel(true); |
| if (status.isOK()) |
| return true; |
| if (status.getSeverity() == IStatus.ERROR) |
| return false; |
| return true; |
| } |
| |
| /** |
| * Use this method when the model should ignore any property set calls. Remember to always reset |
| * the value in a finally block. |
| * |
| * @param aBoolean |
| */ |
| public void setIgnorePropertyChanges(boolean aBoolean) { |
| ignorePropertyChanges = aBoolean; |
| } |
| |
| /** |
| * Return the status for the validation of a particular property. Subclasses should override |
| * when a specific validation is required. |
| * |
| * @param propertyName |
| * @return |
| */ |
| protected IStatus doValidateProperty(String propertyName) { |
| if (NESTED_MODEL_VALIDATION_HOOK.equals(propertyName)) { |
| if (nestedModels != null && !nestedModels.isEmpty()) { |
| IStatus modelStatus; |
| WTPOperationDataModel dataModel; |
| Iterator it = nestedModels.values().iterator(); |
| while (it.hasNext()) { |
| dataModel = (WTPOperationDataModel) it.next(); |
| modelStatus = dataModel.validateDataModel(true); |
| if (!modelStatus.isOK()) { |
| return modelStatus; |
| } |
| } |
| } |
| } |
| return OK_STATUS; |
| } |
| |
| public final IStatus validateProperty(String propertyName) { |
| if (suspendValidation) |
| return OK_STATUS; |
| checkValidPropertyName(propertyName); |
| if (isBaseProperty(propertyName)) { |
| return doValidateProperty(propertyName); |
| } else if (nestedModels != null) { |
| WTPOperationDataModel dataModel = null; |
| Object[] keys = nestedModels.keySet().toArray(); |
| boolean propertyFound = false; |
| IStatus status = null; |
| for (int i = 0; i < keys.length; i++) { |
| dataModel = (WTPOperationDataModel) nestedModels.get(keys[i]); |
| if (dataModel.isProperty(propertyName)) { |
| propertyFound = true; |
| status = dataModel.validateProperty(propertyName); |
| if (!status.isOK()) { |
| return status; |
| } |
| } |
| } |
| if (propertyFound) { |
| return OK_STATUS; |
| } |
| } |
| throw new RuntimeException(PROPERTY_NOT_LOCATED_ + propertyName); |
| } |
| |
| // handles for validation enablement for model and nested children |
| protected void enableValidation() { |
| suspendValidation = false; |
| if (nestedModels != null) { |
| WTPOperationDataModel dataModel = null; |
| Object[] keys = nestedModels.keySet().toArray(); |
| for (int i = 0; i < keys.length; i++) { |
| dataModel = (WTPOperationDataModel) nestedModels.get(keys[i]); |
| dataModel.enableValidation(); |
| } |
| } |
| } |
| |
| // handles for validation disablement for model and nested children |
| protected void disableValidation() { |
| suspendValidation = true; |
| if (nestedModels != null) { |
| WTPOperationDataModel dataModel = null; |
| Object[] keys = nestedModels.keySet().toArray(); |
| for (int i = 0; i < keys.length; i++) { |
| dataModel = (WTPOperationDataModel) nestedModels.get(keys[i]); |
| dataModel.disableValidation(); |
| } |
| } |
| } |
| |
| /** |
| * This method should be called from doSetProperty(String, Object) when a change to a |
| * propertyName will cause default values within the model to change. The passed propertyName is |
| * another property that may need to have its default value recomputed. This allows for UIs to |
| * refresh. |
| * |
| * @param propertyName |
| */ |
| public void notifyDefaultChange(String propertyName) { |
| if (!isSet(propertyName)) |
| notifyListeners(propertyName, WTPOperationDataModelEvent.PROPERTY_CHG); |
| } |
| |
| /** |
| * This method should be called when the valid values for the given propertyName may need to be |
| * recaculated. This allows for UIs to refresh. |
| * |
| * @param propertyName |
| */ |
| public void notifyValidValuesChange(String propertyName) { |
| notifyListeners(propertyName, WTPOperationDataModelEvent.VALID_VALUES_CHG); |
| } |
| |
| protected void notifyEnablementChange(String propertyName) { |
| Boolean enable = isEnabled(propertyName); |
| if (enable != null) { |
| notifyListeners(propertyName, WTPOperationDataModelEvent.ENABLE_CHG); |
| } |
| } |
| |
| public void dispose() { |
| } |
| |
| protected boolean isNotificationEnabled() { |
| return notificationEnabled; |
| } |
| |
| protected void setNotificationEnabled(boolean notificationEnabled) { |
| this.notificationEnabled = notificationEnabled; |
| } |
| |
| /** |
| * @return Returns the locked. |
| */ |
| protected final boolean isLocked() { |
| return locked; |
| } |
| |
| /** |
| * @param locked |
| * The locked to set. |
| */ |
| protected final void setLocked(boolean locked) { |
| this.locked = locked; |
| if (locked) |
| hasBeenExecutedAgainst = true; |
| } |
| |
| /** |
| * By Overwriting this method, you can change the model when its executing. This is only |
| * recommened when you need to set the result of the operation in the model. |
| * |
| * @param propertyName |
| * @return |
| */ |
| protected boolean isResultProperty(String propertyName) { |
| return false; |
| } |
| |
| |
| public final boolean isOperationValidationEnabled() { |
| return operationValidationEnabled; |
| } |
| |
| public final void setOperationValidationEnabled(boolean operationValidationEnabled) { |
| this.operationValidationEnabled = operationValidationEnabled; |
| } |
| |
| /** |
| * This method is EXPERIMENTAL and is subject to substantial changes. |
| * |
| * Gets the specified array of properties from the source DataModel and sets them on the |
| * destination DataModel. |
| * |
| * @param source |
| * @param destination |
| * @param properties |
| */ |
| public static void copyProperties(WTPOperationDataModel source, WTPOperationDataModel destination, String[] properties) { |
| for (int i = 0; i < properties.length; i++) { |
| destination.setProperty(properties[i], source.getProperty(properties[i])); |
| } |
| } |
| |
| |
| /** |
| * Recursively removes all property values of this DataModel and all nested DataModels. |
| */ |
| public void clearAllValues() { |
| if (propertyValues != null) |
| propertyValues.clear(); |
| if (nestedModels != null) { |
| Iterator it = nestedModels.values().iterator(); |
| while (it.hasNext()) |
| ((WTPOperationDataModel) it.next()).clearAllValues(); |
| } |
| } |
| |
| /** |
| * Use this method to determine if an operation has been run using this data model. |
| * |
| * @return |
| */ |
| public boolean hasBeenExecutedAgainst() { |
| return hasBeenExecutedAgainst; |
| } |
| |
| // TODO delete this |
| /** |
| * This will be deleted before WTP M4 If this is used for extended operations, see the property |
| * EXTENDED_CONTEXT. Otherwise, there is no replacement method. |
| * |
| * @deprecated see #EXTENDED_CONTEXT |
| */ |
| public IProject getTargetProject() { |
| return null; |
| } |
| } |