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
Once the Libraries package is loaded, it can ne deployed as an EMF model using:
Libraries.writeEcore("@myProject/model");
where @ references the current workspace and myProject is an EMF project. Once deployed, the model is added to the project as shown below in the Eclipse project browser:

Library Model

The EMF model can be transformed into a diagram using the menu as shown below:

Diagram Menu

which creates and produces an EMF diagram in a file:

Diagram File

The diagram is displayed with a default layout:

Library Diagram No 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:

Library Diagram with Layout

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
The second operation is supplied with an output channel and a path. The path is required in case we need to deploy any other packages into the same folder:
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
The operation deployEcore defined above provides an example of the use of the PrintXML syntax package that defines @XML ... end. The XML syntax construct has the following format:
@XML(out)
// Elements and XOCL code...
end
where out is an expression defining an output channel. The body of the XML construct lists XML elements and XOCL expressions in any order. The XML elements are written to the output channel and the XOCL expressions are evaluated in the order that they are written. XML attributes have values that are either constants, variables or arbitrary expressions contained in parentheses.

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
The parents of an XCore class must be encoded as an EMF string in the output:
context Class
@Operation eCoreParents(path:String):String
parents->asSeq->collect(p | p.eCoreRef(path))->separateWith(" ")
end
Classes are used as types in XCore. In EMF they are referenced by name. Where a type refeence occurs, if the class is in the same EMF package then it is simply referenced by an unqualified name. If the class is referenced via an imported package then the name must be qualified with the file containing the definition of the imported package. The following aspect operation performs a reference of a class via an imported package:
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
To top.

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
An XCore attribute is an ECore attribute when the type is simple:
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
The type of an XCore attribute is encoded in EMF as follows:
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
To top.