Aspects
Contents
Overview
An aspect in XMF is a collection of operations that are added to some existing classes in order to add some new functionality to them. For example, you might want to add functionality to an existing system in order to generate program source code or to generate HTML. XMF makes it easy to define new aspects for existing classes because operations can be dynamically added (and removed) from classes. In addition to your own XMF class definitions, all the system class definitions can be extended in this way too. Aspects are a great way to modularize your system, you can separate out the data definition of your system from the various functional aspects. The source code for the data definition and each aspect can be expressed separately, allowing you to build the system in different ways by including different collections of aspects.This section gives an example of defining an aspect for generating XML output from XCore that represents the XCore packages and classes as ECore packages and classes. This aspect is very useful because it allows you to deploy your XMF packages as EMF packages and then view them using the EMF diagram editor. The aspect adds new operations to the classes Package, Class and Attribute from XCore. The rest of this section describes a simple example use of the aspect involving a package defining a Library model. Then the aspect is defined by adding operations to each of the XCore classes in turn. In addition to providing an example definition of an aspect, this section provides an example of XML output using the syntax package XML::PrintXML.
To top.
Library Example
Consider the following XMF definition for Libraries:context Root
@Package Libraries
@Class Library
@Attribute date : String end
@Attribute books : Set(Book) end
@Attribute copies : Set(Copy) end
@Attribute borrowings : Set(Borrows) end
@Attribute fines : Set(Fine) end
@Attribute readers : Set(Reader) end
end
@Class Book
@Attribute title : String end
end
@Class Copy
@Attribute id : String end
@Attribute book : Book end
end
@Class Borrows
@Attribute date : String end
@Attribute copy : Copy end
@Attribute reader : Reader end
end
@Class Fine
@Attribute borrows : Borrows end
end
@Class Reader
@Attribute name : String end
end
end
Libraries.writeEcore("@myProject/model");
The EMF model can be transformed into a diagram using the menu as shown below:
which creates and produces an EMF diagram in a file:
The diagram is displayed with a default layout:
You can manually layout the diagram or use the default layout mechanism and then save the diagram so that you have a permanent diagram for your XMF Libraries package:
The rest of this section shows how the aspect that achieved the deployment is defined.
To top.
Aspect Definitions
To define an aspect you simply use context definitions to add operations to existing classes. Typically, an aspect will be contained in its own folder in the file system. Each file in the folder contains one or more context definitions. Often there is one file per class that is to be modified by the aspect definition. The folder contains a manifest that builds the aspect.To top.
Packages
A package will offer two operations in the EMF deployment aspect. The first operation is supplied with a path to the EMF model folder. Each package P is written to a file P.ecore. If the file does not already exist on the folder then the operation creates an output channel to a new file and deploys the package:context Package
@Operation writeEcore(path:String)
// Writes a .ecore file for the receiver in the supplied
// directory providing that the .ecore file does not already
// exist...
let file = path + "/" + name + ".ecore"
in if not file.fileExists()
then
@WithOpenFile(fout -> path + "/" + name + ".ecore")
self.deployEcore(fout,path)
end
end
end
end
parserImport XOCL;
parserImport XML::PrintXML;
import IO;
context Package
@Operation deployEcore(out:OutputChannel,path:String)
// Deploy a header for the file and then an EPackage
// containing the appropriate information...
@XML(out)
<?xml version="1.0" encoding="UTF-8" ?>
<ecore:EPackage
xmi:version="2.0"
xmlns:xmi="https://www.omg.org/XMI"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:ecore="https://www.eclipse.org/emf/2002/Ecore"
name=name
nsURI=name
nsPrefix=name>
// Deploy the classes in the package...
@For class in classes do
class.deployEcore(out,self,path)
end
</ecore:EPackage>
end
end
@XML(out)
// Elements and XOCL code...
end
To top.
Classes
A class is deployed as follows:parserImport XOCL;
parserImport XML::PrintXML;
import IO;
context Class
@Operation deployEcore(out:OutputChannel,package:Package,path:String)
// No need to include parents if it is Object...
if parents->forAll(p | p = Object)
then
@XML(out)
<eClassifiers xsi:type="ecore:EClass" name=name>
// Generate the structure features of the class..
@For attribute in attributes do
attribute.deployEcore(out,package,path)
end
</eClassifiers>
end
else
@XML(out)
<eClassifiers
xsi:type="ecore:EClass" name=name eSuperTypes=(self.eCoreParents(path))>
// Generate the structure features of the class..
@For attribute in attributes do
attribute.deployEcore(out,package,path)
end
</eClassifiers>
end
end
end
context Class
@Operation eCoreParents(path:String):String
parents->asSeq->collect(p | p.eCoreRef(path))->separateWith(" ")
end
context Class
@Operation eCoreRef(path:String):String
// Generate a reference to an imported type. The owner
// should be the imported package so deploy that and
// then produce a reference string in an EMF specific
// format...
owner.writeEcore(path);
"ecore:EClass " + owner.name() + ".ecore#//" + name
end
Attributes
XCore classes have structural features that are attributes. EMF distinguishes between attributes (simple typed values) and references (pointers to objects). Attribute deployment must distinguish between these two modes, translate simple values into a special EMF encoding, and translate references types into class references as described above. Attributes are deployed as follows:parserImport XOCL;
parserImport XML::PrintXML;
import IO;
context Attribute
@Operation deployEcore(out:OutputChannel,package:Package,path:String)
// If the attribute has a simple type then an EAttribute is created
// otherwise an EReference is created...
if self.isEAttribute()
then self.deployEAttribute(out,package,path)
else self.deployEReference(out,package,path)
end
end
context Attribute
@Operation deployEReference(out:OutputChannel,package:Package,path:String)
// An EReference may have multiplicity 1 or -1 depending on whether
// it is atomic or not. The type may be local to the package or imported.
// If it is imported then the imported package may need to be
// deployed...
@XML(out)
<eStructuralFeatures
xsi:type="ecore:EReference"
name=name
upperBound=(if self.hasAtomicType() then "1" else "-1" end)
eType=(self.ecoreEType(package,path))
containment="true"/>
end
end
context Attribute
@Operation deployEAttribute(out:OutputChannel,package:Package,path:String)
@XML(out)
<eStructuralFeatures
xsi:type="ecore:EAttribute"
name=name
eType=(self.ecoreEType(package,path))/>
end
end
context Attribute
@Operation isEAttribute():Boolean
// True of the attribute has a simple type and false otherwise...
@Case self.underlyingType() of
[String] do
true
end
[Symbol] do
true
end
[Integer] do
true
end
[Boolean] do
true
end
[Float] do
true
end
else false
end
end
context Attribute
@Operation ecoreEType(package:Package,path:String):String
// Generate a string for the type of the attribute. If the type
// is simple then it is encoded in an EMF specific way. Otherwise
// the type is either local to the package or imported. Local
// types are encoded as #//NAME, imported types are generated
// via eCoreRef (which may cause the imported package to be deployed)...
@Case self.underlyingType() of
[String] do
"ecore:EDataType https://www.eclipse.org/emf/2002/Ecore#//EString"
end
[Symbol] do
"ecore:EDataType https://www.eclipse.org/emf/2002/Ecore#//EString"
end
[Integer] do
"ecore:EDataType https://www.eclipse.org/emf/2002/Ecore#//EInt"
end
[Boolean] do
"ecore:EDataType https://www.eclipse.org/emf/2002/Ecore#//EBoolean"
end
[Float] do
"ecore:EDataType https://www.eclipse.org/emf/2002/Ecore#//EFloat"
end
type do
if package.classes->includes(type)
then "#//" + type.name()
else type.eCoreRef(path)
end
end
end
end