How to define and evaluate constraints#

First Step: Defining a domain Model#

In the first step, we need a domain model. let take an example of simple team-player domain model containing a team and player class as shown in figure below:

Team-Player Domain Model

The corresponding BUML code for defining the classes and association is shown below:

# Primitive DataTypes
t_int: PrimitiveDataType = PrimitiveDataType("int")
t_str: PrimitiveDataType = PrimitiveDataType("str")
t_date: PrimitiveDataType = PrimitiveDataType("date")

# Team attributes definition
team_name: Property = Property(name="name", type=t_str)
team_city: Property = Property(name="city", type=t_str)
team_division: Property = Property(name="division", type=t_str)
# Team class definition
team: Class = Class (name="team", attributes={team_name,team_city,team_division})

# Player attributes definition
name: Property = Property(name="name", type=t_str)
age: Property = Property(name="age", type=t_int)
position: Property = Property(name="position", type=t_str)
jerseyNumber: Property = Property(name="jerseyNumber", type=t_int)

# Player class definition
player: Class = Class (name="player", attributes={name, age, position,jerseyNumber})

# team-player association definition
hasteam: Property = Property(name="many",type=team, multiplicity=Multiplicity(1, 1))
many: Property = Property(name="has", type=player, multiplicity=Multiplicity(0, "*"))
team_player_association: BinaryAssociation = BinaryAssociation(name="team_player_asso", ends={hasteam, many})

Now, lets define some OCL constraints, lets begin with a simple one that the age should be greater than 10.

context player inv inv1: self.age > 10

and the corresponding BUML code is:

constraintPlayerAge: Constraint = Constraint(name = "playerAge",context=player,expression="context player inv inv1: self.age > 10",language="OCL")

Similarly, lets have a constraint where less than three players can play center:

context team inv inv2: self.many -> collect(p:player| p.position = 'center')->size()<3

and the corresponding code BUML is:

constraintTeamCenter: Constraint = Constraint(name = "teamCenter",context=team,expression="context team inv inv2: self.many -> collect(p:player| p.position = 'center')->size()<3",language="OCL")

Lets combine all this into a domain model with the code

team_player_model : DomainModel = DomainModel(name="Team-Player model", types={team,player},
                                      associations={team_player_association },
                                      constraints={
                                      constraintPlayerAge,
                                      constraintTeamCenter
                                      })

Second Step: Defining a object Model#

In the second step, we need to define an object model for these constraints to be validated on. Now, lets create the objects for our team-player domain model as shown in figure below:

Team-Player Object Model

and its corresponding BUML code:

# Team object attributes
teamObjectName: AttributeLink = AttributeLink(attribute=team_name, value=DataValue(classifier=t_str, value="test-3"))
teamcity: AttributeLink = AttributeLink(attribute=team_city, value=DataValue(classifier=t_str, value="not important"))
teamDivision: AttributeLink = AttributeLink(attribute=team_division, value=DataValue(classifier=t_str, value="junior"))
# Team object
teamObject: Object = Object(name="team object", classifier=team, slots=[teamObjectName,teamcity,teamDivision])

# player object attributes
player1_obj_name: AttributeLink = AttributeLink(attribute=name, value=DataValue(classifier=t_str, value="test"))
player1_obj_age: AttributeLink = AttributeLink(attribute=age, value=DataValue(classifier=t_int, value=12))
player1_obj_position: AttributeLink = AttributeLink(attribute=position, value=DataValue(classifier=t_str, value="center"))
player1_obj_JN: AttributeLink = AttributeLink(attribute=jerseyNumber, value=DataValue(classifier=t_int, value=10))

# Player object
player_1_obj: Object = Object(name="playerTest1", classifier=player, slots=[player1_obj_name, player1_obj_age,player1_obj_position,player1_obj_JN])


# player 2 object attributes
player2_obj_name: AttributeLink = AttributeLink(attribute=name, value=DataValue(classifier=t_str, value="test-2"))
player2_obj_age: AttributeLink = AttributeLink(attribute=age, value=DataValue(classifier=t_int, value=15))
player2_obj_position: AttributeLink = AttributeLink(attribute=position, value=DataValue(classifier=t_str, value="center"))
player2_obj_JN: AttributeLink = AttributeLink(attribute=jerseyNumber, value=DataValue(classifier=t_int, value=11))

# Player object
player_2_obj: Object = Object(name="playerTest2", classifier=player, slots=[player2_obj_name, player2_obj_age,player2_obj_position,player2_obj_JN])

# player1 team object link
player_1_link_end: LinkEnd = LinkEnd(name="many", association_end=hasteam, object=player_1_obj)
team_1_link_end: LinkEnd = LinkEnd(name="has", association_end=many, object=teamObject)
team_player_link_1: Link = Link(name="team_player_link_1", association=team_player_association, connections=[player_1_link_end,team_1_link_end])

# player2 team object link
player_2_link_end: LinkEnd = LinkEnd(name="many", association_end=hasteam, object=player_2_obj)
team_2_link_end: LinkEnd = LinkEnd(name="has", association_end=many, object=teamObject)
team_player_link_2: Link = Link(name="team_player_link_2", association=team_player_association, connections=[player_2_link_end,team_2_link_end])

# Object model definition
object_model: ObjectModel = ObjectModel(name="Object model", instances={teamObject, player_1_obj,player_2_obj}, links={team_player_link_1,team_player_link_2})

Third Step: Evaluating the constraint#

For the third step we will define a test case that will iteratively parse the constraints and interpret them based on the values of object model. Here is the python code

from models.team_player_object import team_player_model as domain_model,object_model
from bocl.OCLWrapper import OCLWrapper

if __name__ == "__main__":
    wrapper = OCLWrapper(domain_model, object_model)
    for constraint in domain_model.constraints:
        print("Query: " + str(constraint.expression), end=": ")
        res = None
        try:
            res = wrapper.evaluate(constraint)
            print('\x1b[0;30;35m' + str(res) + '\x1b[0m')

        except Exception as error:
            print('\x1b[0;30;41m' + 'Exception Occured! Info:' + str(error) + '\x1b[0m')
            res = None

and the output for this code will be:

Query: context team inv inv2: self.many -> collect(p:player| p.position = 'center')->size()<3: True
Query: context player inv inv1: self.age > 10: True

The complete code is available on our github repository https://github.com/BESSER-PEARL/BOCL-Interpreter.