Supporting Exclusion and Required Constraints in Attribute Resources

I’m in the process of implementing the ability to model exclusion and required constraints in the Attribute Resource. This will allow the following use cases to be modeled:

  1. If a model component has an associated attribute of type A then it can not have an attribute of type B associated to it and vice versa.

  2. In order to a model component to have an attribute of type A associated with it, it must already have an attribute of type B associated with it.

Note that the exclusion constraint is a symmetric relationship - if an attribute of type A excludes an attribute of type B then an attribute of type B must also exclude an attribute of type A.

The required constraint is asymmetric since if A requires B and B requires A you can never associate either.

To support this I’ve added the following Definition methods:

  • addExclusionDefinition(def)
  • removeExclusionDefinition(def)
  • exclusionDefinitions()
  • addRequiredDefinition(def)
  • removeRequiredDefinition(def)
  • requiredDefinitions()

Since the exclusion constraint is symmetric I’m planning on having adef->addExclusionDefinition(bdef) create both adef excludes bdef and bdef excludes adef. Also when an exclusionDefinition is removed it affects both definitions.

Cool!

Out of context, it may be difficult to understand what a “required definition” is. I’m not sure what’s better, though. Perhaps “conditional definition”?

How about “dependent definition” since you must associate type B first then A? Conditional feels like A can live without B…?

I’ll change the concept to dependent from required.

In order to help simplify determining if an attribute derived from a definition can be associated with an object I’ve added the following methods to Definition:

AssociationResultType canBeAssociated(smtk::resource::ConstPersistentObjectPtr& object,
AttributePtr &conflictAtt, DefinitionPtr &requiredDef) const

Tests to see if attributes based on this definition can be associated with a persistent object - returns:

  • AssociationResultType::Valid if asscoiation is permitted
  • AssociationResultType::Illegal if the object invalidates the definition’s association rule
  • AssociationResultType::Conflict if the object already has an attribute that would conflict with the definition. In this case conflictAtt is set to the conflicting attibute
  • AssociationResultType::Dependent if the object is missing an attribute that the definition depends on. In this case dependentDef is set to the missing dependence.

NOTE - testing is completed once a problem has been detected. There maybe be other issues preventing association so this method may need be called multiple times.

AttributePtr checkForConflicts(smtk::resource::ConstPersistentObjectPtr& object, bool testUniqueness=true) const - returns an attribute associated with the object that would be in conflict with the definition. This will always check the exception information. If testUniqueness is true then the Definition’s isUnique condition is also tested. If there are no conflicts nullptr is returned.

DefinitionPtr checkForDependent(smtk::resource::ConstPersistentObjectPtr& object) const - returns a dependent definition that doesn’t have an attribute associated with the object. If all dependencies are satisfied the nullptr is returned.

So based on John’s feedback I’m changing Dependent to Prerequisite - I am also thinking of shorting the method names:

  • addExclusionDefinition(def)
  • removeExclusionDefinition(def)
  • exclusionDefinitions()
  • addRequiredDefinition(def)
  • removeRequiredDefinition(def)
  • requiredDefinitions()

Will become:

  • addExclusion(def)
  • removeExclusion(def)
  • exclusions()
  • add Prerequisite(def)
  • remove Prerequisite(def)
  • prerequisites()

So exclusion is fully implemented and in fact I’ve changed the isUnique mechanism to use exclusion instead of having special code in place to force the constraint that a persistent object can only have one attribute of a specific type associated with it. When a definition is marked to be unique (setIsUnique(true)) it inserts itself into its exclusion list and the unique condition then process like any other exclusion conflict. Note that the isUnique condition is still written out instead of adding an exclusion rule to the XML/JSON. This change also removes the issue of forcing designers to manually set the isUnique condition for derived definitions.

Now in terms of prerequisites - the code is in place for defining them and preventing attributes from being associated to persistent objects if they don’t have the proper prerequisite attributes associated with it. However, there is an issue with attribute deletion and attribute disassociation.

  • Should a program/application not be allowed to disassociate an attribute that would violate the prerequisite condition of the another attribute that is also associated to the persistent object?
  • The same issue exists for deleting an attribute - should the delete fail if it would violate the prerequisite condition?

Assuming the answers to the above are yes - then both delete and disassociate will need to do some checking. In order to avoid unneeded checks I propose added information to both the attribute resource and attribute definitions to indicate if we are dealing with prerequisites:

  • Add a method to attribute::Resource used by Definitions to increment/decrement the number of definitions in the attribute resource that have prerequisites
  • Add a property to Definition to indicate that either itself or definitions it is derived from have prerequisites - this would be updated appropriately when prerequisites are added or removed.

When deleting or disassociating attributes - the resource is first check to see if we have to be concerned about prerequisites and if we do then we use the definition information in determining if the action is allowed.
Comments?

I’m curious about the reason for this. When we discussed, it sounded like you were leaning toward a separate XML/JSON section for exclusions/pre-requisites.

I am strongly in favor of allowing users to do whatever they want and just showing them the consequences since it can often make interactions easier both in terms of cognitive load and number of clicks required.

In terms of adding a separate section for exclusions and prerequisites in xml/json - that is exactly how things are implemented. In the case of self-exclusion (as in the case of is unique), though it is implemented using exclusion, I’m keeping the property for now.

With respects to allowing association relationships to be come invalid - this was discussed and in the future there will be a mechanism to indicate the resource allows invalid associations but there should also be the ability to prevent invalid associations from being created.

… and just to follow up a bit: the rationale for supporting both was

  • user interfaces should be as accepting as possible of any input from the user and just inform them of the consequences of their decisions (i.e., the attribute/item is no longer in a valid state) while
  • scripts and other automated tools should be notified as soon as possible when things are disallowable, so that the script fails if an unhandled condition is encountered rather than apparent success followed by failure after resources are wasted.