The Unified or Pivot Programmers Guide describes the ways in which the Pivot binding Eclipse OCL can be used from Java programs.
The Pivot binding was first available as an examples quality prototype in 3.1.0 (Indigo). The Pivot binding became the preferred binding in 6.0.0 (Mars). The older Ecore and UML bindings are described in a separate Ecore/UML Programmers Guide.
The OCL Parser/Interpreter provides an implementation of the Object Constraint Language 2.4 specification for EMF-based metamodels and models. It offers OCL constraint and query parsing and evaluation, model-based validation, and provides an infrastructure for content assist in textual editors.
The following features are supported in the current version:
Classifier invariant constraints
Operation precondition and postcondition constraints and body conditions
Property constraints (initial-value and derivation)
Attribute and operation definitions (def: expressions)
Package context declaration
Basic values and types
Collection types
Navigation of attributes and association ends
Operation invocation
Iteration expressions (all standard iterators)
Let expressions
If expressions
Tuples
Message expressions, including unspecified values
Operations predefined by OCL: allInstances(), oclIsKindOf(), oclIsTypeOf(), oclAsType(), oclIsNew()
Escape syntax for illegal names: type, operation, attribute, etc. names that correspond to OCL reserved words can be escaped in the standard fashion using a leading underscore (‘_’). In addition, names that contain spaces or tabs can be escaped by enclosing them in double-quotes (‘"’; this is non-standard). e.g.,
self.ownedRule->forAll(c : Constraint | c._context = self)
The above constructs are supported by the parser for parsing and for evaluation, with the exception of the oclIsNew() operation and message expressions. All of the above are supported for both Ecore and UML models. The following are supported by default for UML (both in parsing and evaluation):
Navigation of non-navigable association ends (including those that are owned by the association)
Qualified association end navigation
Navigation to association classes, including source qualifiers
Operations predefined by OCL: oclIsInState()
The following features are provided in addition to the OCL specification:
String case conversion operations: toUpper(), toLower()
Support for comparison (<, <=, etc.) and sorting of any java
Comparable
s of conformant types
Transitive closure of associations: closure(expr : OCLExpression) iterator
Navigation of “hidden” opposites of references specified in Ecore models using a
Property.oppositeRoleName
annotation with source
http://schema.omg.org/spec/MOF/2.0/emof.xml
on the forward reference, producing an
OppositePropertyCallExp
expression
The OCL implementation is defined in plug-ins for convenient deployment in Eclipse, but as is the case for EMF, it can also be used stand-alone. The plug-ins are partitioned thus:
org.eclipse.ocl.pivot
: the neutral Pivot model, interfaces, Standard Library and evaluator.
org.eclipse.ocl.examples.codegen
: the OCL to Java code generator.
org.eclipse.ocl.examples.debug...
: the extensible debugger.
org.eclipse.ocl.examples.validity
: the Validation View.
org.eclipse.ocl.xtext...
: Xtext editors.
The Pivot-based Eclips OCL maintains signifcant amount of working state to cache aspects of the user metamodels and models.
The OCL working meta-state used for OCL parsing and analysis comprises at least a CompleteEnvironment
, CompleteModel
, EnvironmentFactory
, MetamodelManager
, Orphanage
and StandardLibrary
.
The additional OCL working state for OCL execution comprises an Executor
and a ModelManager
.
Eclipse OCL exploits the Eclipse Modeling Framework (EMF) and so has to comply with EMF APIs. Unfortunately the most basic EMF API getXXX()
to access the feature xxx
provides no ability to pass any OCL working state to an OCL implementation of getXXX()
. There is also no ability to create or destroy the OCL working state before and after a sequence of activities for which the OCL requirement of model invariance is satisfied.
Prior to the 2021-03 (6.14.0) release, the OCL working meta-state was lazily created on demand and eventually garbage collected. The working meta-state was persisted by use of an EnvironmentFactoryAdapter
to attach the state to the ResourceSet
containing the user model / metamodel. The state could be recovered by a hiearchical search of the user model’s containment hierarchy. The OCL
facade supports multiple usage of the state via a share count so that GC only kicks in once all usage is done. A WeakOCLRef
provides another mechanism for sharing.
The OCL working state is similarly maintained via ResourceSet
adapters and also by an entry in the validation context of the EMF validate()
API.
The above approaches work for straightforward usage. But...
When OCL invariants or getters are executed from an installed model, there is no ResourceSet
and so the user’s OCL context is not available. Rather a special global OCL working meta-state is used; it ignores any Complete OCL contributions from the user’s application.
Models shared between applications are isolated by careful use of ValidationAdapter
s to identify which OCL is involved.
Multi-threaded applications may successfully share the working state provided the user has resolved all other threading hazards.
Applications using multiple OCLs such as one reference OCL for an old-way, and another experimental OCL for a new way, may successfully use distinct adapters.
However from a developer’s perspective it’s all very fragile and complicated and metamodel schizophrenia is always waiting for an opportunity to bite.
Avoiding leakage with EMF applications is quite hard since a single refernce from one of the global registries can easily lock everything into memory,
Identifying when the user model analysis in the working state can be re-used and when it must be recomputed is hard since there is no mechanism for a user to delimit the duration of model invariance.
Investigation of a bug in the implicit opposites underpinning a stereotype in a static UMLprofioe revealed that the analyses supporting allInstances and implicitOpposites was very very pessimitic and consequently very inefficient.
The major use cases for OCL applications are model validation and model transformation. For the latter, the transformation tool can easily initialize the OCL state prior to transformation and clean up afterwards. For the former case, a massive performance improvement is possible if the one working state can be re-used throughout the model validation.
For the 2021-03 (6.14.0) release a new approach is taken. The OCL working state is referenced by a thread local variable making discovery easy and cleanup inherent in the demise of the thread. Lazy creation is fairly easy too.
There is no need for a special global OCL context with defective capabilities; the GlobalEnvironmentFactory
and its INSTANCE
is obsolete.
But, by default multi-threaded applications have a distinct OCL working state per-thread, which may be beneficial or inefficient. if the programmer is able to manage thread safety, then the ThreadLocalExecutor.set
API may be used to share the same OCL state on more than one theead. NB the current OCL code has some synchronizations that may help with thread safety but overall there is no guarantee of thread safety.
But, multi-OCL applications cannot have more than one active OCL per-thread.
One variation of the multi-OCL use case was prevalent in the OCL JUnit tests where one OCL processed a reference model while another OCL processed an application model. The solution for interleaved usages is to invoke OCL.deactivate
or ThreadLocalExecutor.detachEnvironmentFactory
to suspend one OCL before OCL.activate
or ThreadLocalExecutor.attachEnvironmentFactory
resumes another OCL. The multiple OCLs can therefore co=exist till eventually OCL.dispose()
cleans up.
Another variation of the multi-OCL use case was prevalent in the QVTd JUnit tests where one main extended OCL processed an application model while a nested OCL validated/serialized intermediate results. The same deactivate
/activate
solution again ensures that only one OCL is actve.
The use of concurrent OCLs on the same thread is diagnosed and results in a log message such as
Concurrent EnvironmentFactory instances inhibit local thread Executor passing forcing th code to fall back on the old adapter mased approach. The solution, as described above, is to use deactivate
/activate
to eliminate the concuurent activity.
But, whereas the old approach was very pessimistic creating a new OCL working state far too often, the thread-based OCL state must be invalidated by invoking ThreadLocalExecutor.reset()
whenever the user models are changed in any way that might undermine the OCL assumption that models do not change. (This change could be automated using EMF’s TrackingModificatinAdapters but the cost is considered too high compared to a manual reset).
But, there is currently no support for sharing the UI thread across many IWorkbenchPart
. i.e if two views both use OCL on the UI thread, the state of one view may confuse the other. Fortunately OCL activity such as validation is generally delegated to a worker thread avoiding the confusion and guaranteeing a new OCL per usage.
The adapters associated with the old approach are now obsolete but remain for now as legacy/compatibility clutter. They will be removed in a future release.