private void runDenyWriteRule()

in source/com.microsoft.tfs.core/src/com/microsoft/tfs/core/clients/workitem/internal/rules/RuleEngine.java [429:906]


    private void runDenyWriteRule(final Rule rule) {
        final IRuleTargetField field = target.getRuleTargetField(rule.getThenFldID());

        /*
         * The unless and thennot flags determine semantics for the rule.
         * Ignoring the reverse flag (which is not used in TFS v1), a denywrite
         * rule can be interpreted as: deny write (unless ? UNLESS : WHEN) the
         * condition is (thennot ? FALSE : TRUE).
         *
         * Note that (unless==true && thennot==true) is the same condition as
         * (unless==false && thennot==false). Likewise, (unless==true &&
         * thennot==false) is the same condition as (unless==false &&
         * thennot==true).
         *
         * Therefore, there are only two states. If (unless == thennot), deny
         * write if the condition is true. If (unless != thennot), deny write if
         * the condition is false.
         */
        final boolean not = rule.isFlagThenNot();
        final boolean unless = rule.isFlagUnless();

        String logRuleEffect = "no effect"; //$NON-NLS-1$

        if (SpecialConstantIDs.isSpecialConstantID(rule.getThenConstID())) {
            switch (rule.getThenConstID()) {
                case SpecialConstantIDs.CONST_EMPTY_VALUE:

                    // triggered by:
                    // * <REQUIRED /> (unless==true, thennot==true)
                    // * <CANNOTLOSEVALUE /> (unless==true, thennot==true)
                    // * <EMPTY /> (unless==true, thennot==false)
                    // * lots of in-the-box rules (unless==false,
                    // thennot==false)

                    /*
                     * ThenImplicitUnchanged will be true if there was an
                     * <ALLOWEXISTINGVALUE /> rule at in scope as of a <REQUIRED
                     * />, <CANNOTLOSEVALUE />, or <EMPTY /> rule.
                     * ThenImplicitUnchanged is ignored by this type of rule in
                     * Visual Studio's implementation.
                     */

                    /*
                     * empty is true if the field has no value and the field is
                     * not set to be server computed
                     */
                    final boolean empty = (field.getValue() == null) && (field.getServerComputedType() == null);

                    if ((not != unless)) {
                        /*
                         * This is a rare case where a data validation rule can
                         * end up behaving like a value providing rule. In this
                         * case, the field holds a value but a data validation
                         * rule says that it must be empty.
                         *
                         * We could simply set the field invalid:
                         * field.setStatus(FieldStatus.INVALID_NOT_EMTPY);
                         * logRuleEffect = "INVALID_NOT_EMTPY";
                         *
                         * However, that's not very user friendly (it's also not
                         * what the Microsoft implementation does). A more
                         * user-friendly approach will be to set the field's
                         * value to empty (if it is not already empty), and then
                         * set the field to read-only so the user can't set a
                         * value which would then be invalid. For most data
                         * validation rules we can't do this, since we only know
                         * "what's wrong", and not "how to fix it". This is a
                         * rare case where we do know "how to fix it".
                         */
                        if (!empty) {
                            field.setValueFromRule(null);
                            field.setReadOnly(true);
                            logRuleEffect = "cleared value and set readonly true"; //$NON-NLS-1$
                        } else {
                            field.setReadOnly(true);
                            logRuleEffect = "set readonly true"; //$NON-NLS-1$
                        }
                    } else if ((not == unless) && empty) {
                        setInvalidStatus(field, FieldStatus.INVALID_EMPTY);
                        logRuleEffect = "INVALID_EMTPY"; //$NON-NLS-1$
                    }
                    break;

                case SpecialConstantIDs.CONST_SAME_AS_OLD_VALUE:

                    // triggered by:
                    // * <READONLY /> (unless==true, thennot==false)
                    // * multiple in-the-box rules (unless==true,
                    // thennot==false)

                    /*
                     * ThenImplicitUnchanged will be true if there was an
                     * <ALLOWEXISTINGVALUE /> rule at in scope as of a <READONLY
                     * /> rule. ThenImplicitUnchanged is ignored by this type of
                     * rule in Visual Studio's implementation.
                     */

                    if (not != unless) {
                        field.setReadOnly(true);
                        field.unsetNewValue();
                        logRuleEffect = "unset new value and set readonly true"; //$NON-NLS-1$
                    } else {
                        /*
                         * should never happen in TFS WIT v1
                         */
                        throw new UnhandledRuleStateException(
                            rule,
                            "unless==thennot for denywrite ConstSameAsOldValue"); //$NON-NLS-1$
                    }
                    break;

                case SpecialConstantIDs.CONST_WAS_EMPTY_OR_SAME_AS_OLD_VALUE:

                    // triggered by:
                    // <FROZEN /> (unless==true, thennot==false)

                    /*
                     * ThenImplicitUnchanged will be true if there was an
                     * <ALLOWEXISTINGVALUE /> rule at in scope as of a <FROZEN
                     * /> rule. ThenImplicitUnchanged is ignored by this type of
                     * rule in Visual Studio's implementation.
                     */

                    final boolean wasEmpty = (field.getOriginalValue() == null);
                    final boolean sameAsOldValue = isSameAsOldValue(field);
                    final boolean wasEmptyOrSameAsOldValue = wasEmpty | sameAsOldValue;

                    if (not != unless) {
                        if (!wasEmptyOrSameAsOldValue) {
                            setInvalidStatus(field, FieldStatus.INVALID_NOT_EMPTY_OR_OLD_VALUE);
                            logRuleEffect = "INVALID_NOT_EMPTY_OR_OLD_VALUE"; //$NON-NLS-1$
                        }
                    } else {
                        /*
                         * Should never happen in TFS WIT v1 Presumably this
                         * case is what FieldStatus.INVALID_EMPTY_OR_OLD_VALUE
                         * is for
                         */
                        throw new UnhandledRuleStateException(
                            rule,
                            "unless==thennot for denywrite ConstWasEmptyOrSameAsOldValue"); //$NON-NLS-1$
                    }
                    break;

                case SpecialConstantIDs.CONST_CURRENT_USER:

                    // only triggered by in-the-box rules:
                    // * [Created By] should default to the person doing the
                    // action (Will cause OM to set it to the right value)
                    // (unless==false, thennot==false)
                    // * If [Created By] became non-empty (-10014) then it
                    // should be equal to Person doing action (unless==true,
                    // thennot==false)

                    /*
                     * seems that we can ignore this as things are working fine
                     * without it
                     */
                    break;

                case SpecialConstantIDs.CONST_OLD_VALUE_PLUS_ONE:

                    // only triggered by an in-the-box rule:
                    // * The Rev of a new item must be increased by one from
                    // the old version (unless==true, thennot==false).

                    /*
                     * So it will only ever show up for ThenFld == System.Rev.
                     * We can safely ignore as this is really more of a
                     * server-side concern than a client-side concern - the rev
                     * field is read-only as far as the client is concerned.
                     */
                    break;

                case SpecialConstantIDs.CONST_SERVER_DATE_TIME:

                    // triggered by:
                    // * <SERVERDEFAULT from="clock" /> (unless==true,
                    // thennot==false)
                    // * multiple in-the-box-rules (unless==true,
                    // thennot==false)

                    /*
                     * ThenImplicitUnchanged will be true if there was an
                     * <ALLOWEXISTINGVALUE /> rule at in scope as of a
                     * <SERVERDEFAULT /> rule. Unknown what Visual Studio's
                     * implementation does if this flag is set - it likely
                     * ignores it.
                     */

                    /*
                     * As far as the WITD triggering, we can ignore as this is
                     * handled automatically by our OM without the need for a
                     * denywrite rule. Not sure if we need to care about the
                     * in-the-box rules - seems unlikely but probably need to
                     * review them to be sure.
                     */

                    /*
                     * If the field is empty, apply "make true" logic since this
                     * field can only have one value.
                     *
                     * TODO: this is a temporary or at least partial fix for the
                     * Dev11 BUILD release. We have a user story to investigate
                     * the .NET rule engine's "make true" logic.
                     */
                    if (field.getValue() == null && field.getServerComputedType() == null) {
                        field.setServerComputed(ServerComputedFieldType.DATE_TIME);
                    }
                    break;

                case SpecialConstantIDs.CONST_VALUE_IN_OTHER_FIELD:

                    // triggered by:
                    // * <NOTSAMEAS field="x" /> (unless==true, thennot==true)

                    /*
                     * ThenImplicitUnchanged will be true if there was an
                     * <ALLOWEXISTINGVALUE /> rule at in scope as of a
                     * <NOTSAMEAS /> rule. ThenImplicitUnchanged is ignored by
                     * this type of rule in Visual Studio's implementation.
                     */

                    if (rule.getIf2ConstID() != SpecialConstantIDs.CONST_VALUE_IN_OTHER_FIELD) {
                        throw new UnhandledRuleStateException(
                            rule,
                            MessageFormat.format(
                                "denywrite ConstValueInOtherField rule with If2ConstID={0}", //$NON-NLS-1$
                                Integer.toString(rule.getIf2ConstID())));
                    }

                    final int otherFieldID = rule.getIf2FldID();
                    if (otherFieldID == 0) {
                        throw new UnhandledRuleStateException(
                            rule,
                            MessageFormat.format(
                                "denywrite ConstValueInOtherField with If2FldID={0}", //$NON-NLS-1$
                                Integer.toString(rule.getIf2FldID())));
                    }

                    final Object otherFieldValue = target.getRuleTargetField(otherFieldID).getValue();
                    final Object targetFieldValue = field.getValue();
                    final boolean fieldValuesEqual = fieldValuesEqual(targetFieldValue, otherFieldValue);

                    if (unless == not) {
                        if (fieldValuesEqual) {
                            setInvalidStatus(field, FieldStatus.INVALID_VALUE_IN_OTHER_FIELD);
                            logRuleEffect = "INVALID_VALUE_IN_OTHER_FIELD (" + otherFieldID + ")"; //$NON-NLS-1$ //$NON-NLS-2$
                        }
                    } else {
                        /*
                         * Should never happen in TFS WIT v1. Presumably this
                         * case is what
                         * FieldStatus.INVALID_VALUE_NOT_IN_OTHER_FIELD is for.
                         */
                        throw new UnhandledRuleStateException(
                            rule,
                            "unless!=thennot for denywrite ConstValueInOtherField"); //$NON-NLS-1$
                    }
                    break;

                case SpecialConstantIDs.CONST_SERVER_CURRENT_USER:

                    // triggered by:
                    // * <SERVERDEFAULT from="currentuser" /> (unless==true,
                    // thennot==false)

                    /*
                     * ThenImplicitUnchanged will be true if there was an
                     * <ALLOWEXISTINGVALUE /> rule at in scope as of a
                     * <SERVERDEFAULT /> rule. Unknown what Visual Studio's
                     * implementation does if this flag is set - it likely
                     * ignores it.
                     */

                    /*
                     * currently do-nothing as this is handled automatically
                     * without the need for a denywrite rule
                     */
                    break;

                case SpecialConstantIDs.CONST_SERVER_RANDOM_GUID:
                    break;

                case SpecialConstantIDs.CONST_GREATER_THAN_OLD_VALUE:

                    // only triggered by an in-the-box rule:
                    // * [Changed Date] is greater than [Changed Date] of the
                    // previous revision (unless==true, thennot==false)

                    /*
                     * can safely ignore this case, as this is a server-side
                     * concern. the client side doesn't modify the changed date
                     */
                    break;

                case SpecialConstantIDs.CONST_DELETED_TREE_LOCATION:

                    // only triggered by in-the-box rules:
                    // * WorkItem cannot be in an invalid location
                    // (unless==false, thennot==false), ThenFld == System.AreaId
                    // * WorkItem cannot have an invalid IterationID
                    // (unless==false, thennot==false), ThenFld =
                    // System.IterationId

                    /*
                     * This rule will only be applied to one of the tree id
                     * fields (System.AreaId or System.IterationId). The rule
                     * must check if the field value is the id of a deleted
                     * node.
                     *
                     * Not sure if this rule will ever get triggered - is it
                     * possible to set area id / iteration id to a deleted node
                     * using TEE?
                     */
                    break;

                case SpecialConstantIDs.CONST_ADMIN_ONLY_TREE_LOCATION:

                    // only triggered by an in-the-box rule:
                    // * WorkItem cannot be written when in an admin only tree
                    // location (unless==false, thennot==false), ThenFld ==
                    // System.AreaId

                    /*
                     * This rule will only be applied to one of the tree id
                     * fields (System.AreaId or System.IterationId). The rule
                     * must check if the field value is the id of a admin node.
                     *
                     * Need to check on the semantics of that and what is
                     * considered an admin node. In fact, I think there is a
                     * good chance that TFS doesn't even support the concept of
                     * "admin-only" tree nodes, and the in-the-box rule that
                     * triggers this case is simply left over from product
                     * studio.
                     */
                    break;

                case SpecialConstantIDs.CONST_NOT_GREATER_THAN_SERVER_TIME:
                    // New rule in Dev11. "Server time" is terminology used to
                    // refer to "Authorized Date". This rule is specifically
                    // created to ensure that "Changed date" is not set to an
                    // invalid value. The VS WIT OM does nothing for this rule,
                    // it is enforced on the server.
                    break;

                default:
                    /*
                     * All of the special constants that are possible in TFS WIT
                     * should be handled by the above cases.
                     */
                    throw new UnhandledSpecialConstantIDException(
                        rule.getThenConstID(),
                        rule,
                        "deny write rule ThenConstID"); //$NON-NLS-1$
            }
        } else {
            // triggered by:
            // * <ALLOWEDVALUES /> (unless==true, thennot==false,
            // ThenImplicitEmpty==true)
            // * <PROHIBITEDVALUES /> (unless==true, thennot==true)
            // * <VALIDUSER /> (unless==true, thennot==false,
            // ThenImplicitEmpty==true)
            // * <MATCH /> (unless==true, thennot==false,
            // ThenImplicitEmpty==true, ThenLike==true)
            // provisioning-generated rules:
            // * WITImporter.ProcessDefaultState (unless==true,
            // thennot==false)
            // * WITImporter.ProcessReasons (unless==true, thennot==false)
            // * WITImporter.ProcessStates (unless==true, thennot==false)
            // * WITImporter.ProcessWorkItemType (unless==true,
            // thennot==false)
            // * WITImporter.RestrictTransition (unless==false,
            // thennot==false)

            /*
             * ThenImplicitUnchanged will be true if there was an
             * <ALLOWEXISTINGVALUE /> rule at in scope as of the <ALLOWEDVALUES
             * />, <VALIDUSER />, or <MATCH /> rule (ThenImplicitUnchanged is
             * disabled by the importer for <PROHIBITEDVALUES /> rules). If this
             * flag is set, existing values should be allowed, even if they
             * would not otherwise pass this rule.
             */

            final IConstantSet constantSet = getConstantSetFromThenFields(rule);
            final boolean allowedValues = (unless != not);

            /*
             * update the picklist if the rule is not a pattern match rule
             */
            if (!rule.isFlagThenLike()) {
                final Set<String> listValues = constantSet.getValues();
                final IFieldPickListSupport pickList = field.getPickListSupport();
                if (allowedValues) {
                    pickList.addAllowedValues(listValues);
                    logRuleEffect = "allowed values (" + listValues.size() + ")"; //$NON-NLS-1$ //$NON-NLS-2$
                } else {
                    field.getPickListSupport().addProhibitedValues(listValues);
                    logRuleEffect = "prohibited values (" + listValues.size() + ")"; //$NON-NLS-1$ //$NON-NLS-2$
                }
            }

            if (rule.isFlagThenImplicitEmpty() && field.getValue() == null) {
                /*
                 * This rule has no effect, since ThenImplicitEmpty is set and
                 * the field is empty.
                 */
                logRuleEffect += " (no denywrite - ThenImplicitEmpty)"; //$NON-NLS-1$
            } else if (rule.isFlagThenImplicitUnchanged() && isSameAsOldValue(field)) {
                /*
                 * This rule has no effect, since ThenImplicitUnchanged is set
                 * and the field has not changed.
                 */
                logRuleEffect += " (no denywrite - ThenImplicitUnchanged)"; //$NON-NLS-1$
            } else {
                if (rule.isFlagThenLike()) {
                    if (!allowedValues) {
                        /*
                         * The unless == thennot state should never occur for
                         * pattern match rules in TFS WIT v1.
                         */
                        throw new UnhandledRuleStateException(
                            rule,
                            "pattern match unless=" //$NON-NLS-1$
                                + unless
                                + " thennot=" //$NON-NLS-1$
                                + not);
                    }

                    final boolean matches = constantSet.patternMatch(
                        field.getValue(),
                        "ruleid:" //$NON-NLS-1$
                            + rule.getRuleID()
                            + ",fldid:" //$NON-NLS-1$
                            + rule.getThenFldID());

                    if (!matches) {
                        setInvalidStatus(field, FieldStatus.INVALID_FORMAT);
                        logRuleEffect = "INVALID_FORMAT"; //$NON-NLS-1$
                    } else {
                        /*
                         * This rule has no effect, since the current value in
                         * the field matches the pattern.
                         */
                        logRuleEffect += " (pattern matches)"; //$NON-NLS-1$
                    }
                } else {
                    final Object fieldValue = field.getValue();
                    final String fieldValueAsString = translateFieldValueIntoString(field.getValue());

                    /*
                     * The value is considered in the list if non-null (constant
                     * sets never contain empty values), the conversion to a
                     * string succeeded, and the constant set contains the
                     * value.
                     */
                    final boolean valueInList =
                        fieldValue != null && fieldValueAsString != null && constantSet.contains(fieldValueAsString);

                    if ((allowedValues && !valueInList) || (!allowedValues && valueInList)) {
                        setInvalidStatus(field, FieldStatus.INVALID_LIST_VALUE);
                        logRuleEffect += ", INVALID_LIST_VALUE"; //$NON-NLS-1$
                    } else {
                        logRuleEffect += " (no denywrite effect - value " //$NON-NLS-1$
                            + (valueInList ? "is" : "is not") //$NON-NLS-1$ //$NON-NLS-2$
                            + " in list)"; //$NON-NLS-1$
                    }
                }
            }
        }

        log.trace(MessageFormat.format(
            "applied DW rule [{0}] in area [{1}] to field [{2}]: {3}", //$NON-NLS-1$
            Integer.toString(rule.getRuleID()),
            Integer.toString(rule.getAreaID()),
            getFieldNameForTrace(rule.getThenFldID()),
            logRuleEffect));
    }