This project is archived. Its data is
read-only
.
Changes
Page history
Add_a_new_constraint_on_a_metamodel, version 73
authored
Dec 20, 2017
by
Saad bin Abid
Show whitespace changes
Inline
Side-by-side
Add_a_new_constraint_on_a_metamodel.md
0 → 100644
View page @
2f5df2f5
Add a new constraint on a metamodel
===================================
Overall concepts
----------------
-
Constraint = a function taking a model element and returning
`true`
or
`false`
(or
`null`
or an error)
-
Constraint instance = the application of a constraint to a given
model element
Contrarily to usual constraint systems, constraint instances are a
first-class citizen for this service
because it allows us to store the status of the constraint on the given
element as well as the checksum of the constrained element.
Example:
-
Constraint: “requirement has a non-empty author”
->essentially a function taking as input a requirement and
returning true if the author field is non-empty
<!-- -->
-
Constraint instance 1: “Requirement REQ
\_
01 has a non-empty
author”
->a persisted model artifact storing the value of the previous
function applied on REQ
\_
01, as well as the checksum of REQ
\_
01 when
the check was done.
<!-- -->
-
Constraint instance 2: “Requirement REQ
\_
02 has a non-empty
author”
->same thing for REQ
\_
02
Basic recipe
------------
**Big picture:**
you need to:
-
implement a “verifier” for your constraint by extending the base
class
`AF3ProjectConstraintCheckerBase`
-
implement a “verifier UI” by extending the base class
`ConstraintVerifierUIBaseAutocheck`
**In details:**
**1.**
identify the plugin which is relevant for you constraint -
>
`plugin`
**2.**
ensure that the model elements which you want to constrain
inherit from the
`IConstrained`
interface
(might already be the case - if not just make the corresponding
modification in the ecore model)
**3.**
add a class in
`plugin`
extending the class
`org.fortiss.af3.project.utils.ConstraintsProjectUtils.AF3ProjectConstraintCheckerBase
Give it a name of the form `
XxxxxConstraintGroup and override the method
getGroup(). you should return the name of the Group that you want your
constraints to belong to.
Consider the following example implementation of the
RequirementsConstraintsGroup
public abstract class RequirementConstraintGroup extends @org.fortiss.af3.project.utils.ConstraintsProjectUtils.AF3ProjectConstraintCheckerBase@. {
/**
* @return this function will return the group Name
*/
@Override
public String getGroup() {
return "Requirement Constraints Group";
}
}
Note: the name of the group is picked up from the getGroup() method and
will be displayed in the constraints configuration tree.
**4.**
add a class in
`plugin`
extending the class
`XxxxxConstraintGroup`
.
Give it a name of the form
`XxxxConstraint`
public class XxxxConstraint extends XxxxxConstraintGroup {
/** {@inheritDoc} */
@Override
public IConstraintInstanceStatus verify(IConstrained constrained) {
// TODO Auto-generated method stub
return null;
}
/** {@inheritDoc} */
@Override
public boolean isApplicable(IConstrained constrained) {
// TODO Auto-generated method stub
return false;
}
}
**5.**
implement in
`verify`
the verification of your constraint.
Different values shall be returned depending on the cases:
-
if the verification is successful
return org.fortiss.tooling.kernel.utils.ConstraintsUtils.createSuccessStatus();
-
if the verification is not successful:
return org.fortiss.tooling.kernel.utils.ConstraintsUtils.createFailStatus();
-
if the verification yielded an exception:
return org.fortiss.tooling.kernel.utils.ConstraintsUtils.createErrorStatus();
-
if it is actually not relevant to verify the constraint:
return null;
**6.**
add a method
`isApplicable`
which should return
`true`
if the
constraint is applicable to the given
`constrained`
element :
/** {@inheritDoc} */
@Override
public boolean isApplicable(IConstrained constrained) {
return constrained instanceof Component; // For instance
}
**7.**
in the method
`PluginActivator.start`
(“PluginActivator” denotes
the activator of your plugin), register your constraint:
/** {@inheritDoc} */
@Override
public void start(BundleContext context) throws Exception {
super.start(context);
plugin = this;
(...)
IConstraintService.getInstance().registerConstraint(XxxxConstraint.class);
(...)
}
**8.**
add a class in
`plugin.ui`
extending the class
`org.fortiss.tooling.kernel.ui.extension.base.ConstraintUIBases.ConstraintUIBaseAutocheck`
.
Call it
`XxxxxConstraintUI`
.
public class XxxxxConstraintUI extends ConstraintUIBaseAutocheck {
/** {@inheritDoc} */
@Override
public String getDescription() {
// TODO Auto-generated method stub
return null;
}
/** {@inheritDoc} */
@Override
public boolean shouldBeManuallyActivated() {
return true;
}
}
**9.**
implement the
`getDescription`
method. This is a short text
describing what the constraint
**ensures**
:
public String getDescription() {
return "Every component is so and so xxxx";
}
**10.**
implement the
`shouldBeManuallyActivated`
method. This is a
boolean deciding whether the user shall be offered the posibility to
control if the constraint shall be activated:
public boolean shouldBeManuallyActivated() {
return true;
}
**11.**
in the method
`PluginUIActivator.start`
(“PluginActivator”
denotes the activator of your plugin), register your constraint UI :
/** {@inheritDoc} */
@Override
public void start(BundleContext context) throws Exception {
super.start(context);
plugin = this;
(...)
IConstraintUIService.getInstance().registerConstraintUI(XxxxConstraintUI.class, XxxxConstraint.class);
(...)
}
Note that
`registerConstraintUI`
requires both the UI part of your
constraint and its not UI part: this is the point where the connection
between both implementations is made.
->You should now have a standard behavior for your constraint. See
below for further customization.
Customization
-------------
### Customize the icon of your constraint
When your constraint is not satisfied by an element, it shows in the
context menu of the element which is not satisfying the constraint.
By default, there is no icon. However you can specify one by overriding
the following method in your UI constraint:
/** {@inheritDoc} */
@Override
public ImageDescriptor getIconImageDescriptor() {
return AF3Activator.getImageDescriptor("icons/myicon.png");
}
### Display a warning instead of an error
By default, all unsatisfied constraints display with an error marker.
If you want instead to have a warning marker, override the following
method in your UI constraint so that it returns
`true`
:
/** {@inheritDoc} */
@Override
public boolean displayAsWarning() {
return true;
}
### Customize when a constraint is automatically outdated
By default, your constraint will be-checked as soon as the constrained
element changes (even without the user saving!).
This might happen very often, even though in many cases, some changes
(e.g., layout changes) are of no importance for the constraint.
Concrete example: changing the layout of a state automaton does not
change anything to the fact that the initial state shall be unique;
therefore you do not want the constraint “initial state should be
unique” to be re-run each time the layout changes.
To fix this you can override the following method in your (non-UI)
constraint:
/** {@inheritDoc} */
@Override
public void preprocessBeforeChecksum(EObject obj) {
LayoutDataUtils.filterAllLayoutData(obj);
}
Inside the method just remove the information from
`obj`
which is
irrelevant for the constraint.
In the snippet above, we make use of the utility method
`filterAllLayoutData`
to remove all layout information.
### Customize the verification status
Often, you might want to store in the status some information collected
during the verification.
This might be useful to provide quick fixes or better error messages.
However, the verification only provides you with a status and no
additional information, so you cannot make use of it.
An easy way to circumvent this is to extend the verification status with
your own class containing the relevant information.
For instance, if a constraint fails because some elements do not contain
a specific property, do not return ErrorVerificationStatus.
Instead, define an Ecore class “MyErrorStatus -
>
ErrorVerificationStatus” containing a reference to the elements which do
not have the property.
Your fixes will then have directly access to these elements and won’t
need to recompute them.
### Cancel a constraint while it is being checked
Some constraints can be really long to check (e.g., formal verification
constraints).
In particular it might happen that the user modifies the model while the
previous constraint check is still not finished.
In such a case, the service will attempt to cancel the previous check
before starting the new one.
You need however to implement the cancellation of your check manually,
which you can do by overriding the following method in your (non-UI)
constraint:
/** {@inheritDoc} */
@Override
public void cancel(ConstraintInstance ci) {
// By default, we do nothing. Not all constraints are so heavy that they deserve to have
// a cancellation procedure.
}
### Provide quick fixes
If relevant, you can provide “quick fixes” to your constraint: these
will appear in the context menu when your constraint shows as
unsatisfied.
To do so you need to override the method
`fixes`
in your UI constraint:
/** {@inheritDoc} */
@Override
public List<IConstraintVerificationUIService.IFix> fixes(ConstraintInstance ci, IConstraintInstanceStatus status) {
// TODO
}
**
“What if I would like to implement a fix, but to do it I have to
implement something partly similar to checking the constraint
itself?”
**
:
To implement the fix, you might need some information which you get only
during the
**check**
of the constraint.
However, the check only provides you with a status and no additional
information, so you cannot make use of it.
See the section above on “Customize the verification status” to
circumvent this.
### Time-consuming constraints: how to improve the user experience?
Some constraints are intrinsically time consuming (e.g., those involving
formal verification).
If you do not want these constraints to be automatically checked there
is no real problem.
Otherwise, we can try to improve the user experience, i.e. (at the
moment of writing these lines):
-
introduce a delay before the automatic check of the constraint
-
warn the user that activating such a constraint might take some
resources
To do so just overload the method
`isTimeConsuming`
of your constraint
UI to make it return
`true`
:
/** {@inheritDoc} */
@Override
public boolean isTimeConsuming() {
return true;
}
### Customize the error message
When your constraint is not satisfied, a default message box with a
standard message will be displayed.
If you want to customize it, for instance to provide more details about
the error, you can override the method
`getMessage`
of your constraint
UI:
/** {@inheritDoc} */
@Override
public String getMessage(Constraint c, IConstraintVerificationStatus status) {
if(status instanceof ErrorVerificationStatus) {
return "My error message here possibly giving more details about the problem";
}
return super.getMessage(c, status);
}
Like explained in the section about quick fixes above, you might define
your own statuses to store relevant information for the user during the
verification.
See the section above on “Customize the verification status”.
If you would like to change more than just the error message but even
change the pop up or trigger some other behaviour, see the section below
on “Customize what happens when a status is opened”.
### Customize what happens when a status is opened
When your constraint is not satisfied, a default message box with a
message will be displayed.
If you want to customize this behaviour, for instance open automatically
the elements which are guilty or highlighting some elements in the
editor, you can override the following method in your constraint UI:
/** {@inheritDoc} */
@Override
public boolean openStatus(Constraint cstr, IConstraintVerificationStatus status) {
if(status instanceof SuccessVerificationStatus) {
// My action here
}
return super.openStatus(c, status);
}
### Prevent the constraint from being automatically activated
You might want to offer the possibility for the user to manually
activate or deactivate a constraint, instead of just letting the
constraint being checked permanently.
To do so, just overload the method
`isTimeConsuming`
of your constraint
UI to make it return
`true`
:
/** {@inheritDoc} */
@Override
public boolean shouldBeManuallyActivated() {
return true;
}
The user will then be able to activate/deactivate the constraint in the
“Development process” editor, by
**
double-clicking on the root of the
AF3 project
**
.
### Manual check instead of automatic
Sometimes, you do not want your constraint to be checked automatically
but rather let the user trigger themselves the check.
For instance if the constraint is too heavy to check, or if the check
can only be done by a human (e.g., review).
To do so, just make your constraint UI extend
`org.fortiss.tooling.kernel.ui.extension.base.ConstraintUIBases.ConstraintUIBase`
instead of
`org.fortiss.tooling.kernel.ui.extension.base.ConstraintUIBases.ConstraintUIBaseAutocheck`
.
### Customize the information contained in a constraint
Every object of type
`Constraint`
always contains a verification
status.
However, you might want it to contain some information, e.g., some
timeout
indicating for every element the maximal time allowed for the
verification of the constraint.
The best way to customize this is to define an Ecore class extending the
type
`Constraint`
.
In such a case your will need however to make your constraint (non-UI)
extend
`org.fortiss.af3.project.utils.ConstraintsProjectUtils.AF3ProjectConstraintBase`
instead of
`org.fortiss.af3.project.utils.ConstraintsProjectUtils.AF3ProjectConstraintCheckerBase`
.
### Constrain more than one element at a time
Some constraints focus on more than one element only. For instance, a
constraint checking whether a test is up to date relates both a
component and a test case.
To tackle this situation, make your (non-UI) constraint extend
`org.fortiss.af3.project.utils.ConstraintsProjectUtils.AF3ProjectConstraintBase`
instead of
`org.fortiss.af3.project.utils.ConstraintsProjectUtils.AF3ProjectConstraintCheckerBase`
.
The main differences are as follows:
-
the
`verify`
method then takes the constraint as input, and not the
constrained element
-
in this method, use utility functions like
`org.fortiss.tooling.kernel.utils.ConstraintsUtils.getFirstConstrained(Constraint)`
to access the constrained elements
-
you need to implement the method
`createConstraintIfNeeded`
which
creates the constraint (or returns
`null`
if not relevant)
-
if you constrain two elements only, it should be enough to call
`org.fortiss.af3.project.utils.ConstraintsProjectUtils.createConstraint(String, IConstrained, IConstrained)`
Add constraint support for your own non-AF3 but kernel-based project
--------------------------------------------------------------------
The method described above is AF3-dependent, as the dependency on
af3.project shows.
However the service has been designed so that you can easily adapt your
project to support constraints.
Step-by-step:
1.
in your model, have some class implement
`IConstraintInstanceContainer`
(either pick an existing one or
create a new one, important thing is that it is contained in the
main project - see
`DevelopmentProcessConfiguration`
in AF3 for an
example).
**
There should be only one constraint instance container
per project.
**
2.
define a compositor for this class, make it inherit
`org.fortiss.tooling.base.compose.ConstraintInstanceContainerCompositor<DevelopmentProcessConfiguration>`
.
You should not have anything to implement in this class.
3.
in your model, have some class extend
`ConstraintBasedProcess`
(either pick an existing one or create a new one, important thing is
that it is contained in the main project - see
`ConstraintBasedDevelopmentProcess`
in AF3 for an example).
**
There should be only one constraint based process element per
project.
**
4.
*in your .ui plugin*
, define a compositor for this class, make it
inherit
`org.fortiss.tooling.base.ui.compose.ConstraintBasedProcessCompositor<ConstraintBasedDevelopmentProcess>`
.
You should not have anything to implement in this class.
5.
define some utility method which allows to retrieve the unique
`IConstraintInstanceContainer`
corresponding to any
`EObject`
. Say
`getConstraintInstanceContainer`
.
6.
define two abstract classes:
/
**
Base class to simplify even more migrating from the old to the new constraint system.
*
/
public static abstract class MyProjectConstraintBase extends ConstraintBases.ConstraintBase {
/** {@inheritDoc} */
Override
public IConstraintInstanceContainer getConstraintInstanceContainer(EObject obj) {
// Here "getConstraintInstanceContainer" is project-specific!
return getConstraintInstanceContainer(obj);
}
}
/** Base class to simplify even more migrating from the old to the new constraint system. */
public static abstract class MyProjectConstraintCheckerBase extends ConstraintBases.ConstraintCheckerBase {
/** {@inheritDoc} */
Override
public IConstraintInstanceContainer getConstraintInstanceContainer(EObject obj) {
// Here "getConstraintInstanceContainer" is project-specific!
return getConstraintInstanceContainer(obj);
}
}
7.
In your UI activator, define a function of signature
`ConstraintBasedProcess getConstraintBasedProcess(ITopLevelElement top)`
which, given a top level element is able to retrieve its unique
constraint based process.
8.
In the
`start`
function of your UI activator, add the following
lines:
ConstraintBasedProcessAwareActivator cstrBasedActivator = new ConstraintBasedProcessAwareActivator(t -> getConstraintBasedProcess(t));
cstrBasedActivator.start();
(see `AF3ProjectUI` for an example)
You should now be good to go.
To add a constraint on your metamodel, follow the step-by-step above
only replacing
`AF3ProjectConstraintBase`
by
`MyProjectConstraintBase`
and
`AF3ProjectConstraintCheckerBase`
by
`MyProjectConstraintCheckerBase`
.
**Optional:**
if you want the user to be able to define their own
development process based on constraints, define some editor extending
`org.fortiss.tooling.base.ui.editor.ConstraintBasedProcessEditor<ConstraintBasedDevelopmentProcess>`
.
You should not have anything to implement in this class.