logo

Introduction

The Object-Constraint-Language is used to add additional constraints to a given modelling language. Since there do exist implementations in Java (e.g. Eclipse Modelling Framework (EMF)) but not JavaScript, this is a basic OCL implementation in JavaScript.

The library does not claim to be fully compliant to the given OMG OCL definition.

Usage

Installation

To add OCL.js to your project, just install it via npm.

$ npm install ocl-js --save

You may also downlad a tarball from the GitHub Repository and include the ocl.min.js in your project.

Use as ES6 module

import OclEngine from 'ocl-js';

const myOclExpression = `
    context Person
        inv: self.parents->forAll(p | p <> self)
`;

OclEngine.create()
    .addOclExpression(myOclExpression)

Expressions

Context

Every OCL rule runs in a specific context. The context constraints the execution of a rule to a specific type. Even though the type system of JavaScript is very basic, the context expression tries to guess the type as follows:

function Person() {}    => Person
class Person {}         => Person
{}                      => Object
function() {}           => undefined

When calling an OCL rule like the one below, it will only apply on objects which are of type "Person". "Type of" in context of OCL means, that the object is a direct instance of the given type.

context Person inv:
    [...]

Invariants

An invariant is a constraint that has to be fulfilled. Whenever an invariant is violated, the whole OCL rule is violated. One can define multiple invariants per context which are started using the keyword inv.

inv: self.variable = "value"

Definitions

When one wants to define additional variables before invariants are executed, one can use the keyword def for creating variable definitions.

def: let name: "value"

Mathematical operators

The operators =, <, <=, >= and <> can be used to compare literals.

Furthermore, mathematical operations like +, -, *, /, and mod are supported.

Logic gates

Boolean expressions can be concatenated by using the keywords and, or, xor and implies. The way how logic gates work is the same as in all programming languages. But there are two special kinds: xor and implies

AND

and is used whenever both sides of the condition have to be fulfilled.

context Person
    inv: self.actsAs = "student" and self.actsAs = "employee"
OR

or is used whenever one of both (or both) sides of the condition have to be fulfilled.

context Person
    inv: self.actsAs = "student" or self.actsAs = "employee"
XOR

xor is used whenever one of two expressions have to be true. In case of the example A xor B, either A or B but not both may be true to fulfill the condition.

context Person
    inv: self.actsAs = "student" xor self.actsAs = "employee"
Implies

implies is used whenever one condition leads to another truthy condition: A implies B states, that whenever A is true, B has to be true as well. If A is false, we are not interested in B at all.

context Person
    inv: self.age >= 0 implies self.isAlive = true

Literals

The current implementation supports String, Boolean, Number and Nil literals.

Collection operations

Collection->exists(expr) : Boolean

Operation which checks whether a collection contains an element specified by expr.

self.collection->exists(item | item.name = "random")
Collection->forAll(expr) : Boolean

Runs the expression for all elements in collection and returns true if the expression is true for all, false otherwise.

self.collection->forEach(c | c.attribute < 10)
self.collection->forEach(c1, c2 | c1.attribute <> c2.attribute)
Collection->select(expr) : Collection

Selects all elements from collection which fit the expr.

self.collection->select(item | item.name = "random")
Collection->isEmpty() : Boolean

Returns true when collection is empty, false otherwise.

self.collection->isEmpty()
Collection->isNotEmpty() : Boolean

Returns false when collection is empty, true otherwise.

self.collection->isNotEmpty()
Collection->union(Collection) : Collection

Concatenates the two given collections and returns one single collection.

self.collection->union(self->anotherCollection)
Collection->at(index:Number) : Object

Returns the element of the collection at index index

self.collection->at(2)
Collection->first() : Object

Returns the first element of the collection.

self.collection->first()
Collection->last() : Object

Returns the last element of the collection.

self.collection->last()
Collection->asSet() : Collection

Returns the given collection as set, containing unique entries.

self.collection->asSet()
Collection->size() : Number

Returns the length of the given collection.

self.collection->size()

Examples

The following paragraphs contain some OCL constraints which are evaluated live while this page loads. To create a use case, the following images shows two connected classes which describe the following: A person can own multiple cars. A car belongs to just one person. A car has a color. A person has a name as well as its age.

uml example

A vehicle owner has to be at least 18 years old

The first example is about the common fact, that vehicle owners have to be at least 18 years old. To check this rule, the following OCL constraint can be used.

var person = new Person(29);
var car = new Car('red');
car.owner = person;
context Car
   inv: self.owner.age >= 18

The fleet size of a person must not exceed 3 cars

var person = new Person(29);
var car = new Car('red');

person.fleet.push(car);
person.fleet.push(car);
person.fleet.push(car);
person.fleet.push(car);
context Person
   inv: self.fleet->size() <= 3

All cars a person owns are red

var person = new Person(29);
var redCar = new Car('red');
var greenCar = new Car('green');
person.fleet.push(redCar);
person.fleet.push(redCar);
person.fleet.push(redCar);
person.fleet.push(greenCar);
context Person
   inv: self.cars->forAll(c|c.color = "red")

A person younger than 18 years old owns no cars

var person = new Person(6);
context Person
   inv: self.age < 18 implies self.fleet->isEmpty

A person owns at least one red car

var person = new Person(29);
var car = new Car('red');

person.fleet.push(car);
person.fleet.push(car);
context Person
   inv: self.fleet->select(c|c.color="red")->size > 0

A person owns at least one red car

var person = new Person(29);
var car = new Car('red');

person.fleet.push(car);
person.fleet.push(car);
context Person
   def: let redCars: self.fleet->select(c|c.color="red")
   inv: self.redCars->size > 0