1. The submodel Engine Architecture
The submodel engine is structured according to a hierarchy of Java packages as described by the following figure:
The two packages fr.cristal.caramel.genmodel and fr.cristal.caramel.typedgenmodel form the abstract core of the submodel engine (middle part of the figure).
Different concrete versions are then provided. First, the fr.cristal.caramel.submodel.model package is an implementation which allows to program submodels or submetamodels (see section Programming sub(meta)models) and also typed models (see section Programming typed sub(meta)models). A typed model is a model where each model element owns a meta link to its meta element.
Second, the fr.cristal.caramel.submodel.emf package is used when sub(meta)models are derived from Eclipse EMF models. This package provides the same functionalities as the previous one. In addition, this implementation preserves the link between a source EMF EObject and one or many model elements of our formalism (depending on the chosen model mapping). This package is described in section [From EMF Models to submodels]
2. Programming sub(meta)models
To illustrate the submodel engine Java API , we will use different models derived from the following figure:
Here, model Elements of involved models can either be derived from instances of Class, Feature (Attribute or Operation), Association and DataType. Possible dependency constraints appear when :
-
a feature depends on its Class
-
a feature depends on its Type
-
an association depends on their linked classes
-
an operation depends on their parameter types
You need the following plugin:
-
fr.cristal.caramel.submodel.wfm
2.1. Creating models from scratch
First, you need to import the package from the plugin:
import fr.cristal.caramel.submodel.model.Model;
import fr.cristal.caramel.submodel.model.ModelElement;
Next, the creation of models is quite simple:
public Model createAndGetModelHeatingSubCircuit() { Model m = new Model("HeatingSubCircuit"); (1) ModelElement Boiler = new ModelElement("Boiler"); (2) ModelElement Radiator = new ModelElement("Radiator"); ModelElement pipes = new ModelElement("pipes"); ModelElement temperature = new ModelElement("temperature"); ModelElement vfloat = new ModelElement("float"); ModelElement position = new ModelElement("position"); ModelElement vint = new ModelElement("int"); ModelElement on = new ModelElement("on"); ModelElement off = new ModelElement("off"); ModelElement up = new ModelElement("up"); ModelElement down = new ModelElement("down"); ModelElement link = new ModelElement("link"); m.addElement(Boiler); (3) m.addElement(Radiator); m.addElement(pipes); m.addElement(temperature); m.addElement(vfloat); m.addElement(position); m.addElement(vint); m.addElement(on); m.addElement(off); m.addElement(up); m.addElement(down); m.addElement(link); m.addPair(temperature, vfloat); m.addPair(temperature, Boiler); (4) m.addPair(on, Boiler); m.addPair(off, Boiler); m.addPair(position, vint); m.addPair(position, Radiator); m.addPair(up, Radiator); m.addPair(down, Radiator); m.addPair(link, Boiler); m.addPair(link, Radiator); m.addPair(pipes, Radiator); m.addPair(pipes, Boiler); return m ; }
1 | creation of an empty model m |
2 | creation of model elements |
3 | assign model elements to m |
4 | add model dependencies to m |
The following static Java method allows to discover the model structure:
public static void displayInfo(Model m) {
System.out.println("------------------------");
System.out.println("Info on:" + m.getName());
System.out.println("Nb element(s):" + m.nbElements()); (1)
System.out.println("Nb pair(s):" + m.nbPairs()); (2)
// display model elements
System.out.println("List of model elements:");
for (ModelElement e : m.elements()) { (3)
System.out.println(" - Element: "+e.getName());
}
// display dependencies
System.out.println("List of dependencies:");
for (ModelElementPair pair : m.pairs()) { (4)
System.out.println(" - Element: "+pair);
}
}
1 | number of model elements of m |
2 | number of dependency constraints of m |
3 | collection of model elements of m |
4 | collection of dependency constraints of m |
2.2. Submodel comparison
The following static Java method do some different model comparison:
public static void displaySubmodel(Model m1, Model m2) {
String m1Name = m1.getName(); String m2Name = m2.getName();
System.out.println("Results"); System.out.println("=======");
System.out.println("Model1: " + m1Name);
System.out.println("Model2: " + m2Name);
System.out.println("-------");
System.out.println("graph of " + m1Name + " included in graph of "
+ m2Name + ":" + m2.includesPairs(m1.pairs())); (1)
System.out.println(m1Name + " closed submodel of " + m2Name + ":" +
m1.closedSubModelOf(m2)); (2)
System.out.println(m1Name + " covariant submodel of " + m2Name + ":"
+ m1.covariantSubModelOf(m2)); (3)
System.out.println(m1Name + " invariant submodel of " + m2Name + ":"
+ m1.invariantSubModelOf(m2)); (4)
System.out.println();
}
1 | m2.includesPairs(m1.pairs()) returns true if the graph of m2 includes the graph of m1 , false otherwise |
2 | m1.closedSubModeOf(m2) returns true if m1 is a closed submodel of m2, false otherwise |
3 | m1.covariantSubModeOf(m2) returns true if m1 is a covariant submodel of m2, false otherwise |
4 | m1.invariantSubModeOf(m2) returns true if m1 is an invariant submodel of m2, false otherwise |
2.3. Model Engineering with submodels
The submodel Engine Java API allows to manage models. We distinguish three kinds of model management:
-
operations on subsets of model elements
-
operations on subsets of model element pairs
-
operation on submodels
2.3.1. Management of subset of model elements
The Model class provides the following methods (let m the current model):
Set<ModelElement> elements() |
returns the set of elements of m |
addElement(ModelElement me) |
adds me to the set of model elements of m |
addElements(Set<ModelElement> sme) |
adds the set sme to the set of model elements of m |
boolean includesElement(ModelElement me) |
returns true if me is included in the set of model elements of m |
boolean includesElements(Set<ModelElement> sme) |
returns true if the set sme is included in the set of model elements of m |
boolean includesElements(Model m1) |
returns true if the model elements of m1 are included in the set of model elements of m |
Set<ModelElement> intersectElements(Model m1) |
returns the set of model elements that are both present in m and m1 |
Set<ModelElement> unionElements(Model m1) |
returns the union of the set of model elements of m and m1 |
Set<ModelElement> closure(Set<ModelElement> s) |
returns a set of model elements of m whose elements of s transitively depend with respect to dependency constraints asserted by m |
Set<ModelElement> dependent(Set<ModelElement> s) |
returns a set of model elements of m which transitively depends to elements of s with respect to dependency constraints asserted by m |
2.3.2. Management of subset of pairs of model elements
The Model class provides the following methods (let m the current model):
Set<ModelElementPair> pairs() |
returns the set of model element pairs of the model |
addPair(ModelElement me1, ModelElement me2) |
adds the new pair (me1,me2) to the set of model element pairs of the model |
addPairs(Set<ModelElementPair> smp) |
adds the set smp to the set of model element pairs of the model |
boolean includesPair(ModelElementPair mp) |
returns true if mp is included in m.pairs() |
boolean includesPairs(Set<ModelElementPair> smp) |
returns true if smp is included in m.pairs() |
Set<ModelElementPair> intersectPairs(Model m1) |
returns the set of model element pairs that are both present in m and m1 |
Set<ModelElementPair> unionPairs(Model m1) |
returns the union of the set of model element pairs of m and m1 |
Set<ModelElementPair> subgraph(Set<ModelElement> sme) |
returns the subgraph of m.pairs() where involved model elements are included in sme |
2.3.3. Management of submodels
The Model class provides the following methods (let m the current model):
Model model(Set<ModelElement> sme) |
extracts the submodel from m that only includes model elements of sme |
Model difference(Model m1, String nm1) |
returns the submodel called nm1 with model elements of m that are not present in m1 |
2.3.4. Illustration
Now, we want to build a new model "Boiler and Radiator" (see Figure "Boiler And Radiator" Model) .
The java programmer already created models derived from Figure Example of submodels
Thanks to the rich Java API, there are various ways to build this model.
The following method mainly uses the operator model (model extraction).
Set<ModelElement> s=m2.elements() ;
s.removeAll(m0.elements());
Model boilerAndRadiator1=m2.model(s) ;
boilerAndRadiator1.setName("boilerAndRadiator1");
SubmodelUtil.displayInfo(boilerAndRadiator1);
While this second method uses the difference model operator to obtain the same result:
Model boilerAndRadiator2= m1.difference(m0, "boilerAndRadiator2");
SubmodelUtil.displayInfo(boilerAndRadiator2);
3. Programming typed sub(meta)models
To illustrate this section , we will use different models from the following figure:
Figure Example of typed models show model samples using UML graphical convention on the left and their graph representation on the right.
To build these typed models, we use the metamodel MM :
3.1. Creating typed models from scratch
First, you need to import the package from the plugin:
import fr.cristal.caramel.submodel.model.TypedModel;
import fr.cristal.caramel.submodel.model.TypedModelElement;
You need to first create a metamodel in order to build a typed model. The metamodel, which is itself a typed model, also requires a metametamodel. To bootstrap the model creation, we create a simple metametamodel made of a unique meta meta class:
public TypedModel createAndGetMetaMetaModel() { TypedModel metametamodel = new TypedModel("MetametaModel"); TypedModelElement metametaCls = new TypedModelElement("@", null); metametamodel.addElement(metametaCls); return metametamodel ; }
Here is the creation of the metamodel MM:
public TypedModel createAndGetMetaModel(TypedModel metaMM) { TypedModelElement metametaCls = metaMM.findElementByName("@") ; TypedModelElement pkg = metametaCls.create("Package"); (1) TypedModelElement cls = metametaCls.create("Class"); TypedModelElement attribute = new TypedModelElement("Attribute",metametaCls); (2) TypedModelElement operation = new TypedModelElement("Operation",metametaCls); TypedModel MM = new TypedModel("MM"); (3) MM.addElement(pkg); MM.addElement(cls); MM.addElement(attribute); MM.addElement(operation); MM.addPair(cls, pkg); MM.addPair(attribute, cls); MM.addPair(operation, cls); return MM ; }
1 | first method to create a typed model element |
2 | second method to create a typed model element |
3 | then, the creation of a typed model is equivalent to the creation of (no typed) model. |
The creation of typed models does not differ from the creation of metamodels (metamodels are models). Here is an illustration for the model m4
public TypedModel createAndGetM4(TypedModel MM) { TypedModelElement packageMC = MM.findElementByName("Package") ; TypedModelElement attributeMC = MM.findElementByName("Attribute") ; TypedModelElement classMC = MM.findElementByName("Class") ; TypedModelElement hhs = new TypedModelElement("HHS",packageMC); TypedModelElement temperature = new TypedModelElement("temperature",attributeMC); TypedModelElement boiler = new TypedModelElement("Boiler",classMC); TypedModelElement radiator = new TypedModelElement("Radiator",classMC); TypedModel m = new TypedModel("m4"); m.addElement(hhs); m.addElement(boiler); m.addElement(radiator); m.addElement(temperature); m.addPair(temperature, boiler); m.addPair(radiator, hhs); return m ; }
3.2. Submetamodel comparison
The following static Java method do some different model comparison:
public static void displayTest(TypedModel m, TypedModel meta) {
System.out.println("------------------------");
System.out.println("Tests of :" + m.getName() + " wrt " + meta.getName());
System.out.println("member of :" + m.memberOf(meta)); (1)
System.out.println("wellformed of:" + m.wellformedOf(meta)); (2)
}
1 | m.memberOf(meta) returns true if m is a member model of meta, false otherwise |
2 | m.wellformedOf(meta) returns true if m is a well-formed member model of meta, false otherwise |
3.3. Model Engineering with typed submodels
The submodel Engine Java API allows to manage typed models. We distinguish two kinds of model management:
-
operations on subsets of model elements or pairs of model elements
-
operations on typed submodels
3.3.1. Management of subset of model elements or pairs of model elements
The TypedModel class provides the following methods (let m the current model):
Set<TypedModelElement> meta() |
returns the set of model elements that are meta classes of model elements of m |
Set<TypedModelElementPair> graphmeta() |
returns the set of meta constraints that are related to model constraints of m |
Set<TypedModelElement> piOnElement(TypedModel M) |
returns the subset of model elements of m that are instances of metamodel elements of the metamodel M |
Set<TypedModelElementPair> piOnGraph(TypedModel M) |
returns the subset of constraints of m that respect meta_constraints of the submetamodel M |
3.3.2. Management of typed models
The TypedModel class provides the following methods (let m the current model):
TypedModel properMetamodel(String name) |
builds and returns the proper metamodel of m. name is the name of the build metamodel. |
TypedModel project(TypedModel SM, String nm) |
builds and returns the submodel called nm which is the projected model of m relatively to the submetamodel SM |
3.3.3. Illustration
The java programmer already created models derived from Figure Example of typed models
For illustration, we want now to build a new model "projectM5" from the model m5 where model elements of "projectM5" are either instances of Class or Attribute
Below is one way to build this model.
TypedModel properM2 = m2.properMetamodel("properM2") ; (1)
TypedModel projectM5=m5.project(properM2, "projectM5"); (2)
System.out.println("invariant submodel ? :" +
m2.invariantSubModelOf(projectM5)) ;(3)
1 | the model m2 is a model that only contains instances of Class or Attribute, this line of code builds the proper metamodel of m2 |
2 | then the project operator is used with the model m5 and the previous build metamodel "properM2" |
3 | With this example, it appears that m2 is an invariant submodel of the resulting projected model. (See figure Example of typed models to be convinced) |
4. From EMF Models to sub(meta)models
From an EMF model, it is possible to define a mapping in our submodel formalism. The package fr.cristal.caramel.submodel.emf is dedicated to this objective. Models and metamodels are supported and the Java programming is identical to previous sections. The major distinction is the preservation of the link between a model element and its original EMF EObject. This link has multiple benefits. For example, it facilitates the programming of export functions to an EMF model.
In this section, we will use the following simple Ecore "classMM" metamodel as an illustration.
The following figure shows some models that are instances of the metamodel classMM
4.1. Programming sub(meta)models from EMF models
First, you need to import the following classes:
import fr.cristal.caramel.submodel.EMFAdapter.EMFFileUtil; (1)
import fr.cristal.caramel.submodel.emf.EModel; (2)
import fr.cristal.caramel.submodel.emf.EModelElement;
import fr.cristal.caramel.submodel.emf.EModelElementPair;
1 | the EMF class EMFFileUtils provides useful generic static methods to load and store EMF models |
2 | The 3 imported classes allow to manage models while preserving the link with the EObjects which are at the origin |
Next, the creation of models is quite simple. The big difference with the Programming sub(meta)models section is that it is necessary to specify the source object EObject when instantiating a model element.
This model creation is guided by the EMF model structure. In the following example, we simply want to focus on the classes and attributes and the dependencies between these elements. The other elements are simply ignored.
public EModel createAndGetModel(String fileName, String modelName) { URI m_uri = org.eclipse.emf.common.util.URI.createURI(fileName) ; Resource m_res = EMFFileUtil.loadModel(m_uri); (1) EModel m= new EModel(); m.setName(modelName) ; (2) TreeIterator<EObject> allElements = m_res.getAllContents(); while (allElements.hasNext()) { (3) EObject eObj = allElements.next() ; if (eObj instanceof classMM.Class || eObj instanceof classMM.Attribute) { EModelElement element= new EModelElement(eObj) ; (4) m.addElement(element); if (eObj instanceof classMM.Class) { classMM.Class cl= (classMM.Class) eObj ; element.setName(cl.getName()); } else { classMM.Attribute at= (classMM.Attribute) eObj ; element.setName(at.getName()); } } } for ( EModelElement tme : m.elements()) { (5) if (tme.getEObject() instanceof classMM.Attribute) { (6) String nameOwnerClass=((classMM.Attribute) tme.getEObject()).getOwnerClass().getName(); EModelElement tmOwnerClass = m.findElementByName(nameOwnerClass); if (tmOwnerClass!=null) m.addPair(tme, tmOwnerClass) ; } } return m ; }
1 | loading of the EMF model |
2 | creation of an empty model m |
3 | section of creation of model elements restricted to classes and attributes |
4 | creation of a model element, the EObject is given as a parameter |
5 | section of creation of model dependencies to m (attribute and its owning class) |
6 | the getEObject method returns the source EMF EObject |
Once this template is created, all the functions described in the Programming sub(meta)models section are available. In addition, thanks to the traceability to the source EMF object established when the model was created, some new functions can be developed such as an export function to the EMF model.
The following Java program builds 4 models from EMF model given in Figure [img-instancesclassMM]
public static void main(String[] args) { EPackage.Registry.INSTANCE.put(ClassMMPackage.eNS_URI, ClassMMPackage.eINSTANCE); (1) DemoEMFSubmodels demo= new DemoEMFSubmodels() ; // creation of models for (int i=1; i<=4;i++ ) { EModel m=demo.createAndGetModel("./models/m"+i+".classmm","m"+i) ; DemoEMFSubmodels.displayInfo(m); } }
1 | Explicit EPackage registration is needed for standalone applications |
4.2. Programming typed sub(meta)models from EMF models
In this section, build models, which are equivalent to the previous subsection (focus on classes and attributes), are typed models. As a consequence, all model elements refer to its meta model element.
First, you need to import the following classes:
import fr.cristal.caramel.submodel.EMFAdapter.EMFFileUtil; (1)
import fr.cristal.caramel.submodel.emf.ETypedModel; (2)
import fr.cristal.caramel.submodel.emf.ETypedModelElement;
import fr.cristal.caramel.submodel.emf.ETypedModelElementPair;
1 | the EMF class EMFFileUtils provides useful generic static methods to load and store EMF models |
2 | The 3 imported classes allow to manage typed models while preserving the link with the EObjects which are at the origin |
Next, the creation of models is quite simple. The big difference with the Programming typed sub(meta)models section is that it is necessary to specify the source object EObject when instantiating a model element.
With typed models, each model element refers to its meta model element. As a consequence, in our example, there is the creation of a simple metametamodel followed by the creation of the metamodel followed by the creation of typed models.
Here is the Java code, which creates the simple metametamodel (for bootstrap):
public ETypedModel createAndGetMetaMetaModel() { // metametamodel for bootstrapping ETypedModel metametamodelBootstrap = new ETypedModel(null) ; metametamodelBootstrap.setName("@"); ETypedModelElement metametaCls = new ETypedModelElement(null,null) ; metametaCls.setName("@"); metametaCls.setMetaclass(metametaCls); metametamodelBootstrap.addElement(metametaCls); return metametamodelBootstrap ; }
Next, the Java Code for the creation of the metametamodel:
public ETypedModel createAndGetMetaModel(String fileName, String modelName,ETypedModel mm) { URI uri = URI.createURI(fileName); Resource r = EMFFileUtil.loadModel(uri); EPackage root = (EPackage) r.getContents().get(0); ETypedModel m= new ETypedModel(root) ; m.setName(modelName); EClass cls = (EClass) root.getEClassifier("Class"); ETypedModelElement tmCls = new ETypedModelElement((EObject) cls,mm.findElementByName("@")) ; tmCls.setName("Class") ; EClass attr = (EClass) root.getEClassifier("Attribute") ; ETypedModelElement tmAttr = new ETypedModelElement((EObject) attr,mm.findElementByName("@")) ; tmAttr.setName("Attribute") ; m.addElement(tmCls) ; m.addElement(tmAttr) ; m.addPair(tmAttr, tmCls); return m ; }
Then, the Java code for the creation of a typed model :
public ETypedModel createAndGetModel(String fileName, String modelName,ETypedModel mm) { URI m_uri = org.eclipse.emf.common.util.URI.createURI(fileName) ; Resource m_res = EMFFileUtil.loadModel(m_uri); (1) ETypedModel m= new ETypedModel(); m.setName(modelName) ; (2) TreeIterator<EObject> allElements = m_res.getAllContents(); while (allElements.hasNext()) { (3) EObject eObj = allElements.next() ; if (eObj instanceof classMM.Class || eObj instanceof classMM.Attribute) { ETypedModelElement element ; if (eObj instanceof classMM.Class) { element= new ETypedModelElement(eObj,mm.findElementByName("Class")) ; (4) classMM.Class cl= (classMM.Class) eObj ; element.setName(cl.getName()); } else { element= new ETypedModelElement(eObj,mm.findElementByName("Attribute")) ; classMM.Attribute at= (classMM.Attribute) eObj ; element.setName(at.getName()); } m.addElement(element); } } for ( ETypedModelElement tme : m.elements()) { (5) if (tme.getEObject() instanceof classMM.Attribute) { (6) String nameOwnerClass=((classMM.Attribute) tme.getEObject()).getOwnerClass().getName(); ETypedModelElement tmOwnerClass = m.findElementByName(nameOwnerClass); if (tmOwnerClass!=null) m.addPair(tme, tmOwnerClass) ; } } return m ; }
1 | loading of the EMF model |
2 | creation of an empty typed model m |
3 | section of creation of model elements restricted to classes and attributes |
4 | creation of a typed model element, the EObject is given as the first parameter, the meta model element is given as the second parameter |
5 | section of creation of model dependencies to m (attribute and its owning class) |
6 | the getEObject method returns the source EMF EObject |
Once this template is created, all the functions described in the Programming typed sub(meta)models section are available.
The following Java program builds 4 typed models from EMF model given in Figure [img-instancesclassMM]
public static void main(String[] args) { EPackage.Registry.INSTANCE.put(EcorePackage.eNS_URI, EcorePackage.eINSTANCE); (1) EPackage.Registry.INSTANCE.put(ClassMMPackage.eNS_URI, ClassMMPackage.eINSTANCE); DemoEMFTypedSubmodels demo= new DemoEMFTypedSubmodels() ; // metametamodel for bootstrapping ETypedModel metametamodelBootstrap = demo.createAndGetMetaMetaModel(); // metametamodel ETypedModel metamodel=demo.createAndGetMetaModel("./models/classMM.ecore", "entityClassMM", metametamodelBootstrap); // creation of typed models for (int i=1; i<=4;i++ ) { ETypedModel m=demo.createAndGetModel("./models/m"+i+".classmm","m"+i,metamodel) ; DemoEMFTypedSubmodels.displayInfo(m); // wellformed tests System.out.println(m.getName()+" welformed wrt M ?"+m.wellformedOf(metamodel)); (2) } }
1 | Explicit EPackage registration is needed for standalone applications |
2 | Functions suitable for typed models are available, here the wellformed test |
5. Metadata facilities
The core abstract layer of the submodel engine includes API for metadata management. As a consequence, all implementations inherit these facilities.
5.1. Adding metadata to a model
First, you need to import the following Java elements:
import fr.cristal.caramel.genmodel.SubModelMetadata; import fr.cristal.caramel.genmodel.SubModelMetadata.PredefinedMetadataKey;
The following method allows to display all the metadata of a given model
public void displayMetadata(Model m) { SubModelMetadata minfo = m.getMetadata(); (1) if (minfo != null) { System.out.println("metadatas from model "+m.getName()+" :") ; for (String key : minfo.getKeys()) { (2) System.out.println(key+": "+minfo.getMetadataValue(key)); (3) } } else System.out.println("no meta information for "+m.getName()); }
1 | the method to retrieve metadata of a model |
2 | metadata are stored in a map |
3 | method that retrieves the value of a metadata identified by a string. |
The following Java Code illustrates how to add metadata to a model
public void runDemoSubmetatada() { DemoSubmodels demo = new DemoSubmodels() ; Model m2 = demo.createAndGetM2() ; SubModelMetadata metainfo = m2.getMetadata(); (1) metainfo.addMetadata("author", "The CARAMEL team"); (2) metainfo.addMetadata(PredefinedMetadataKey.dateCreated, "24-06-2021"); (3) metainfo.addMetadata(PredefinedMetadataKey.modelURI, "http://fr.cristal.caramel/m2"); this.displayMetadata(m2); (4) Set<ModelElement> subset = new HashSet<ModelElement>(); subset.add(m2.findElementByName("boiler")) ; subset.add(m2.findElementByName("temperature")) ; subset.add(m2.findElementByName("on")) ; Model newModel = m2.extract(subset) ; (5) newModel.setName("subM2") ; newModel.getMetadata().addMetadata(PredefinedMetadataKey.modelURI, "http://fr.cristal.caramel/subM2"); this.displayMetadata(newModel); (6) }
1 | the method to retrieve metadata of a model |
2 | the method to add a metadata, first parameter is the key, the second one is the related value. |
3 | there are some predefined metadata keys, there are available thanks to the PredefinedMetadataKey Java enum |
4 | This method displays the following lines:
metadatas from model m2 : dateCreated: 24-06-2021 author: The CARAMEL team modelURI: http://fr.cristal.caramel/m2 |
5 | all model operators of the submodel engine automatically add metadata to the created/modified models. Here, the extract operator adds the three predefined metadata "dateCreated", "buildByOperator" and "buildFromModelURI". Thanks to this mechanism, tracability of models built by the submodel engined is ensured. |
6 | This method displays the following lines:
metadatas from model subM2 : buildFromModelURI: http://fr.cristal.caramel/m2 dateCreated: 24-06-2021 buildByOperator: extract modelURI: http://fr.cristal.caramel/subM2 |
5.2. Metadata from EMF Model
The EMF framework supports meta information thanks to the EAnnotation class. This tutorial shows how metadata are loaded/restored from EMF models.
The following Java code shows how to load and store metadata from an EMF Model.
public void runDemoSubmetatadaFromEMFModels() { EPackage.Registry.INSTANCE.put(ClassMMPackage.eNS_URI, ClassMMPackage.eINSTANCE); DemoEMFSubmodels demo= new DemoEMFSubmodels() ; String fileName="./models/m2.classmm" ; EModel m2=demo.createAndGetModel(fileName,"m2") ; (1) SubModelMetadata minfo=null; URI m_uri = org.eclipse.emf.common.util.URI.createURI(fileName) ; try { minfo = EMFFileUtil.loadMetadataFromXMI(m_uri); (2) } catch (EMFAdapterMetaInformationException e) { System.err.println("unable to read metadatas"); (3) } m2.setMetadata(minfo); (4) this.displayMetadata(m2); (5) minfo.addMetadata("author","the Caramel Team") ; (6) minfo.addMetadata(SubModelMetadata.PredefinedMetadataKey.dateCreated, "24-06-2021"); this.displayMetadata(m2); (7) String newFileName="./models/m2bis.classmm" ; Resource m_res = EMFFileUtil.loadModel(m_uri); URI mbis_uri = org.eclipse.emf.common.util.URI.createURI(newFileName) ; try { EAnnotation eAnno = m2.getMetadata().getEAnnotation() ; (8) EMFFileUtil.saveModelWithMetaInfo(mbis_uri, (EObject []) m_res.getContents().toArray(), eAnno); (9) } catch (IOException e) { System.err.println("I/O Error when saving model"); } }
1 | A model is created from a EMF Model (explained in Section Programming sub(meta)models from EMF models) |
2 | the static method EMFFileUtil.loadMetadataFromXMI allows to load the metadata from the EMFModel. The metadata are stored in a dedicated EAnnotation. |
3 | An exception may occur if the EMF File is corrupted. |
4 | the loaded metadata are assigned to the model. |
5 | This method displays the following lines:
metadatas from model m2 : modelURI: ./models/m2.classmm |
6 | the code to add meta information |
7 | This method displays the following lines:
metadatas from model m2 : dateCreated: 24-06-2021 author: the Caramel Team modelURI: ./models/m2.classmm |
8 | The method getEAnnotation provides an EMF EAnnotation object, which contains all metatada of the current model. |
9 | the static method _EMFFileUtil.saveModelWithMetaInfo saves a EMF model with metadata. |
6. EMF adapters
The submodel engine is combined with an extensible set of EMF model adapters that allow to import and convert EMF (meta-)models into our model formalism. Thanks to this architecture, the engine offers customization capacities for adapting the representation scheme of EMF models in the unified formalism.
Within Eclipse, as a plugin, the most suitable EMF adapter can be selected using an OSGI service. Outside Eclipse, the desired EMF adapter must be explicitly requested.
Once the EMF adapter has been obtained, its use is identical regardless of the method used to obtain it.
The plugin required to use EMF adapters is "fr.cristal.caramel.submodel.adapter" . This plugin owns a default generic EMF model adapter available with any EMF (meta-)model.
6.1. EMF Adapter API
All implementations of EMF adapters must respect the following Java interface.
package fr.cristal.caramel.submodel.EMFAdapter.extension; import java.io.BufferedWriter; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import org.eclipse.emf.common.util.EList; import org.eclipse.emf.ecore.EObject; import fr.cristal.caramel.submodel.EMFAdapter.ModelExportException; import fr.cristal.caramel.submodel.EMFAdapter.ModelImportException; import fr.cristal.caramel.submodel.EMFAdapter.NotSupportedException; import fr.cristal.caramel.submodel.emf.EModel; import fr.cristal.caramel.submodel.emf.ETypedModel; import fr.cristal.caramel.submodel.emf.ETypedModelElement; import fr.cristal.caramel.submodel.emf.ETypedModelElementPair; public interface ISubModelAdapter { public enum ModelKindSupport { OnlyEModel, OnlyETypedModel, BothModelKinds }; public boolean accept(EObject eObj); (1) public boolean accept(EList<EObject> eObjects); public ModelKindSupport getModelkindSupport() ; (2) public boolean isExportSupport() ; (3) /* begin set of methods if modelKindSupport = OnlyEModel or BothModelKinds */ (4) public EModel getModel(String modelName, EObject eObj) throws NotSupportedException,ModelImportException ; public EModel getModel(String modelName, EList<EObject> eObjects) throws NotSupportedException,ModelImportException ; /* end set of methods if modelKindSupport = or BothModelKinds */ /* begin set of methods if modelKindSupport <> OnlyEModel */ (5) public ETypedModel getTypedModel(String modelName, EObject eObj, ETypedModel metamodel) throws NotSupportedException,ModelImportException ; public ETypedModel getTypedModel(String modelName, EList<EObject> eObjects, ETypedModel metamodel) throws NotSupportedException,ModelImportException ; public ETypedModel getMetamodel() throws NotSupportedException ;(6) public ETypedModel getMetamodelBootstrap() throws NotSupportedException ; public EObject[] exportModel(ETypedModel tm) throws NotSupportedException, ModelExportException ; (7) /* end set of methods if modelKindSupport <> OnlyEModel */ public static void genereModelGraphviz(ETypedModel m) throws IOException { (8) PrintWriter dotfile; dotfile = new PrintWriter(new BufferedWriter(new FileWriter(m.getName()+".dot"))); dotfile.println("digraph "+m.getName()+"{") ; for (ETypedModelElement me : m.elements()) dotfile.println(" \""+me.getName()+"\"") ; for (ETypedModelElementPair pair : m.pairs()) dotfile.println(" \""+pair.getElement1().getName()+"\" -> \""+pair.getElement2().getName()+"\"") ; dotfile.println("}"); dotfile.close(); } }
1 | the accept methods check whether the EObject instances given as parameters are supported by the EMF adapter |
2 | This method specifies the capabilities of the adapter. Specifically, it indicates whether the adapter supports Emodel or ETypedModel or both. |
3 | This method indicates if the adapter supports the export to EMF function. |
4 | This first set of methods translates graphs of EObjects to an EModel. The NotSupportedException is thrown if the adapter does not support this mode. If an error occurs during the translation, the ModelImportException is thrown |
5 | Equivalent set of methods for translation to an ETypedModel. if the adapter supports it. The difference is in the metamodel that must be provided as a parameter |
6 | These methods provides the used metamodel. |
7 | This method translates an ETypedModel to an EMF model. |
8 | This static method produces a graph in the graphviz formalism |
6.2. Accessing an EMF adapter outside of Eclipse
In the case of a standalone Java application (outside Eclipse), the adapter used is explicitly referenced. The example given below allows to translate Ecore models using the generic ecore adapter provided by the plugin "fr.cristal.caramel.submodel.adapter"
First of all, we need to import the following classes:
import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EPackage; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.common.util.EList; import org.eclipse.emf.ecore.resource.Resource; import classMM.ClassMMPackage; import fr.cristal.caramel.submodel.EMFAdapter.EMFFileUtil; import fr.cristal.caramel.submodel.EMFAdapter.ModelImportException; import fr.cristal.caramel.submodel.EMFAdapter.NotSupportedException; import fr.cristal.caramel.submodel.EMFAdapter.extension.ISubModelAdapter; import fr.cristal.caramel.submodel.emf.ETypedModel; import fr.cristal.caramel.submodel.genericEMFAdapter.GenericEMFEcoreAdapter; import fr.cristal.caramel.submodel.genericEMFAdapter.GenericEMFModelAdapter;
Then, here is an example of using the generic ecore file adapter
ETypedModel tm=null,tmClassMM=null ; ISubModelAdapter ecoreAdapter = new GenericEMFEcoreAdapter() ; (1) ETypedModel tmm=null; try { tmm = ecoreAdapter.getMetamodel(); (2) } catch (NotSupportedException e) { System.err.println("error: the adapter does not support getMetamodel()"); return ; } String modelNames[] = { "classMM", "entityClassMM","hierarchyClassMM", "packageMM" } ; for (String name : modelNames) { try { // load a ecore metamodel URI m_uri = org.eclipse.emf.common.util.URI.createURI("./models/"+name+".ecore") ; Resource m_res = EMFFileUtil.loadModel(m_uri); (3) EPackage ecoreMM = (EPackage) m_res.getContents().get(0) ; System.out.println(ecoreMM.getName()+" is loaded") ; if (ecoreAdapter.accept(ecoreMM)) { tm = ecoreAdapter.getTypedModel(name,ecoreMM,tmm) ; (4) if (name.equals("classMM")) tmClassMM=tm ; else { System.out.println (name +" is an invariant of classMM? " (5) +tm.invariantSubModelOf(tmClassMM)); } } else System.out.println("Error, only EPackages are supported") ; } catch(ModelImportException e1){ System.err.println("error import metamodel :"+name); } catch (NotSupportedException e) { System.err.println("error: ecoreAdapter does not support getTypedModel()"); } }
1 | We are using the Generic EMF Ecore adapter provided by the adapter plugin. |
2 | This adapter provides the ETypedModel that represents the Ecore metamodel |
3 | Several ecore models are loaded |
4 | the adapter translates the loaded models into ETypedModel |
5 | An invariance test between models is performed. This Java code displays the following lines:
classMM is loaded entityClassMM is loaded entityClassMM is an invariant of classMM? true hierarchyClassMM is loaded hierarchyClassMM is an invariant of classMM? true packageMM is loaded packageMM is an invariant of classMM? true |
Next, the following Java code shows how different models that are all instances of the classMM metamodel are loaded in the same way that previous code. Once again, the generic EMF model adapter is used.
EPackage.Registry.INSTANCE.put(ClassMMPackage.eNS_URI, ClassMMPackage.eINSTANCE); ISubModelAdapter modelAdapter= new GenericEMFModelAdapter() ; (1) for (int index=1; index <=4; index++) { try { // load model instance of classmm URI m_uri = org.eclipse.emf.common.util.URI.createURI("./models/m"+index+".classmm") ; String name="m"+index ; Resource m_res = EMFFileUtil.loadModel(m_uri); EList<EObject> eObjs = m_res.getContents() ; System.out.println(name+".classmm is loaded") ; if (modelAdapter.accept(eObjs)) { tm = modelAdapter.getTypedModel(name,eObjs,tmClassMM) ; (2) System.out.println (name +" is well-formed w.r.t classMM? " (3) +tm.wellformedOf(tmClassMM)); } else System.err.println("Error, some EObjects of "+name+".classmm are not supported") ; } catch(ModelImportException e1){ System.err.println("error importing model"); } catch (NotSupportedException e) { System.err.println("error: model Adapter does not support getTypedModel()"); } }
1 | We are using the Generic EMF Model adapter provided by the adapter plugin. |
2 | The translation step |
3 | A well-formed test w.r.t. classMM is performed. |
6.3. Accessing an EMF adapter with the adapter OSGI service
The EMF Adapter OSGI service is specified by the following Java interface:
package fr.cristal.caramel.submodel.EMFAdapter; import org.eclipse.emf.common.util.EList; import org.eclipse.emf.ecore.EObject; import fr.cristal.caramel.submodel.EMFAdapter.extension.ISubModelAdapter; public interface ISubModelAdapterService { public static final int METASUPPORT = 1 ; (1) public static final int EXPORTSUPPORT = 2 ; public static final String DefautEcoreAdapterName="fr.cristal.caramel.submodel.genericEMFAdapter.GenericEMFEcoreAdapter"; (2) public static final String DefautModelAdapterName="fr.cristal.caramel.submodel.genericEMFAdapter.GenericEMFModelAdapter"; ISubModelAdapter getSubmodelAdapter(int capacities,EObject eObj) throws NotFoundSubModelAdapter; (3) ISubModelAdapter getSubmodelAdapter(int capacities,EList<EObject> eObjects) throws NotFoundSubModelAdapter; ISubModelAdapter getSpecificSubmodelAdapter(String submodelAdapterName) throws NotFoundSubModelAdapter; (4) }
1 | These constant values make it possible to find an adapter according to its capabilities. |
2 | These constant values make it possible to find an adapter according to these capacities. Here is the identifier strings for the generic ecore adapter and the generic model adapter. These two adapters are embedded in the adapter service plugin. |
3 | the "getSubmodelAdapter" methods are used to find an adapter capable of handling the EObject (second parameter) and having the functionality specified in the first parameter. |
4 | This last method provides a specific adapter thanks to its string identifier. |
Any Eclipse plugin can access to this service thanks to the OSGI service tracker.
The following code shows how this can be done:
somePlugin.Activator.java
... public class Activator extends AbstractUIPlugin { ... private static ServiceTracker<ISubModelAdapterService, ISubModelAdapterService> adapterServiceTracker ; public void start(BundleContext context) throws Exception { (1) super.start(context); ... adapterServiceTracker = new ServiceTracker<ISubModelAdapterService,ISubModelAdapterService>(context,ISubModelAdapterService.class.getName(), null); adapterServiceTracker.open(); } public void stop(BundleContext context) throws Exception { super.stop(context); adapterServiceTracker.close(); adapterServiceTracker=null; } public static ISubModelAdapterService getAdapterService() { return adapterServiceTracker.getService(); (2) } }
1 | The service tracker tracks the availability of the adapter service. |
2 | The getService() method provides the adapter service |
Once the adapter service is obtained, the plugin can find a adapter suitable for its needs.
somePlugin.someCode.java (extract)
ISubModelAdapterService adapterService=Activator.getAdapterService(); (1) String newFileName="./models/m2bis.classmm" ; Resource m_res = EMFFileUtil.loadModel(locationfile); String filepath = "./models/someModel.uml" ; URI m_uri = org.eclipse.emf.common.util.URI.createURI(filepath) ; Resource m_res = EMFFileUtil.loadModel(m_uri); (2) EList<EObject> objs= resourceModel.getContents() ; ISubmodelAdapter adapter = null ; try { adapter= adapterService.getSubmodelAdapter( ISubModelAdapterService.METASUPPORT| (3) ISubModelAdapterService.EXPORTSUPPORT, objs) ; } catch(NotFoundSubModelAdapter e) { System.err.println("No available adapter for this model") ; }
1 | access to the adapter service found by the plugin activator |
2 | an EMF model file is loaded |
3 | The adapter service is looking for an adapter for the loaded model, which supports ETypedModel (Meta support) and provides an export function. |
Once the required adapter is found, its use is identical to that described in section Accessing an EMF adapter outside of Eclipse.