Models managed by the platform, whether UML models or Domain Specific models, are all model instances of some Ecore models. As an example, every UML model in RMP is an instance of the UML.ecore metamodel available in UML2's org.eclipse.uml2.uml plug-in, which is an implementation of the official UML 2.0 specification. UML models are read and modified using the Java interfaces generated from UML.ecore using EMF's code generation capabilities. The objects found in such EMF model instances are called EMF objects.
As a concrete example, the UML 2.0 specification defines the concept of a Package. In UML2, this concept
was modeled in UML.ecore as a class named Package. UML2 also leveraged EMF to generate the interface named
Package
. When
a user creates a UML package using RMP's UML Modeler, internally the tool instantiates an object that implements the
Package
interface.
The interfaces implemented by an EMF object generally include
EObject
. This
interface gives generic access to EMF objects' content, provides a reflection API and
helps determine whether objects are loaded in memory. Because EObject extends
Notifier
,
every EMF object can notify listeners upon state change. In EMF, listeners are called
Adapter
s.
The following diagram presents the interface hierarchy of the UML2
Package
metaclass.
The root of all UML2 interface is
Element
. This interface
specializes EMF's EModelElement
,
an interface which adds the ability to associate annotations to an EMF object.
EMF introduced the concept of an Editing Domain to control the lifecycle of models through commands.
Among the most important features of
Editing Domain
are its command stack and its resource set. The DevOps Modeling Platform uses a specialization of EMF's Editing Domain found
in the EMF Transactions component. This specialization enables access to models in a multithreaded environment. The editing
domain used by RMP's UML Modeler is accessed through the
UMLModeler.getEditingDomain
method.
The CommandStack
contains
commands executed to modify the models controlled by the editing domain. A command is executed by calling
CommandStack.execute()
.
A Command
is responsible for
the modification of a model using the latter's APIs. If it is undoable, the command is also responsible for undoing and redoing the
modification. Commands can be chained to form a composite command.
Models managed by an Editing Domain are contained in Resource
s.
Resources are associated a URI
that determines where to load and save the resource's
contents,
which is simply a list of EObjects. For a resource containing a UML2 model, the list may very well only contain a single EObject,
Model
. This object is the container
for the remaining elements of the model. The resources managed by a given Editing Domain are accessible through the latter's
ResourceSet
,
which is accessible through the
getResourceSet()
method.
An EMF Object owned by a resource can reference other EMF Objects owned by different resources. Such cross-resource
references are not resolved until the reference is actually accessed. Until the reference is resolved, it references
a Proxy EObject, which is a lightweight object containing a URI to the referenced object.
EObject.eIsProxy()
can be used to test whether an EObject is a proxy. Usually, the API generated by EMF to access a reference from an EMF object
will attempt to resolve it. As an example, the UML2
Generalization.getGeneral()
method will attempt to resolve the Classifier it returns.
When resolving a proxy, EMF will first see if the resource containing the referenced element is already loaded in the ResourceSet that contains the resource of the referencing object. If found, it just finds the object in that resource and returns it. If not found, it will attempt to locate the resource using the proxy's URI and attempt to load the resource in the referencer's ResourceSet. If successful, it again finds the object and returns it. If for any reason it cannot locate and/or load the resource, EMF will return back the same proxy. The lesson here is that even though EMF's generated API attempt to resolve proxies, clients should always validate whether the EObject they get back from an API is a proxy.
URI
s
are used in EMF to locate resources as well as objects within a resource. A URI consists of a subset or all of the following components:
<scheme>://<authority><path>?<query>#<fragment>
EMF will resolve any URI supported by Java such as URIs using the file
scheme.
EMF supports Eclipse's platform
scheme to access resources within a workspace. For instance, a URI such as
platform:/resource/project1/folder1/model.emx
will refer to a file named model.emx
in a folder
named folder1
in a project named project1
.
Also supported are URIs making use of the pathmap
scheme. Pathmap variables are defined in the preferences page
accessible from Window > Preferences... > Modeling > Pathmap. For instance, pathmap://UML_LIBRARIES/Ecore.library.uml2
references the file named Ecore.library.uml2
located at the location specified for the symbol UML_LIBRARIES
in the preference page.
Before delving into EMF's Reflection API, we will define a few concepts.
A metaclass is a class describing a set of classes. A class is an instance of its metaclass
A metamodel is a model describing a set of models at a different abstraction level. A model is an instance of its metamodel. A metamodel contains a set of metaclasses and associations between them called metaassociations. A metamodel can be an instance of a metametamodel or an instance of itself if it is self-sufficient.
The following diagrams provides an example of a model instantiated from a metamodel.
Not unlike Java's own reflection API, EMF objects offer an API to inquire about an object's metaclass. EMF's provides richer information and performs faster. The metainformation actually reflects the information in the Ecore model used to instantiate the EMF object in the first place.
An EMF Object, thanks to its implementation of the EObject interface, supports a method called
eClass
that will
return its metaclass. The metaclasses of an Ecore model are objects of type
EClass
that
provide the metaclass name, the list of the metaclasses it extends, the list of its attributes and operations, and more.
Attributes are described in EMF by EStructuralFeature
.
Two EStructuralFeature specializations are worth mentioning:
EReference
which provides
information about attributes referencing other EObjects, and
EAtttribute
which provides
information about attributes not referencing other EObject.
For a given Ecore model, the implementation EMF generates usually includes a Package interface that provides access to all metaclasses of
the model. As an example, the Package generated for UML2 is
UMLPackage
. This class can be used
to retrieve UML2 metaclasses such as
Actor
(through its getActor()
method). It can also be
used to access metaattributes such as a Generalization
's "General" (through its getGeneralization_General()
method)
The following diagram shows the main classes of EMF's own Ecore model, named Ecore, which is the metamodel for all Ecore models:
We often refer to this model as the Ecore metamodel, as opposed to an Ecore model, which is an instance of the former.
Noteworthy is the fact that the Ecore metamodel was bootstrapped using EMF itself; the Ecore metamodel is its own metamodel. The model describing Ecore is an Ecore model defined in a resource named Ecore.ecore available in EMF's org.eclipse.emf.ecore plug-in.