| Author: | Raphael Marvie (Raphael.Marvie@lifl.fr) |
|---|---|
| Date: | 2007/10/25 |
Abstract
PyEMOF is an implementation of the E-MOF (Essential MOF) specification. It provides a mapping from a EMOF 2.0 meta-model definition to a Python model repository as well as utilities to load/save a model to/from a repository from/to an XMI file. This implementation mainly follows ptc/03-10-09 without support for reflection. The Python mapping tries to follow as much as possible the "Python way".
PyEMOF is provided as a python package using setuptools. The easiest way to install it on your system is to install the provided Egg if you are a Python 2.5 user.
tmp $ sudo easy_install PyEMOF-x.y.z-py2.5.egg
Otherwise, you can install PyEMOF from the source building your own Egg or using the more classic installation.
tmp $ tar xvzf PyEMOF-x.y.z.tar.gz tmp $ cd PyEMOF-x.y.z PyEMOF-x.y.z $ python setup.py build PyEMOF-x.y.z $ sudo python setup.py install
Then PyEMOF is widely available on your system.
There are two ways for defining a meta-model:
As an example, we are going to define a simple meta-model for modeling Java Bean applications. First, we define a package that represents our meta-model. Then, for each concept we define a class specifying attributes of the concept. By default, n primitive data type exist, so we have to define the one we needs: string for example.
A Bean, has a name, a set of properties and it can emit and listen to events. A Property has a name and a type. An event has a name and a set of fields. Finally, a Field has a name and a type.
package javabean {
primitive string ;
class Bean {
attribute name : string ;
attribute properties : Property [0..*] isComposite ;
attribute emit : Event [0..*] ;
attribute listen : Event [0..*] ;
}
class Property {
attribute name : string ;
attribute type : string ;
}
class Event {
attribute name : string ;
attribute fields : Field [0..*] isComposite ;
}
class Field {
attribute name : string ;
attribute type : string ;
}
}
Once the meta-model is defined, we can convert the textual representation to XMI and then generate a repository to handle models conforming to this meta-model.
$ emof2xmi javabean.emof $ pyemof javabean.xmi $ ls javabean __init__.py core.py dumper.py loader.py
The generation produces a package named like the meta-model containing three modules:
As any Python package, PyEMOF can also be used programmatically and interactively. We are now going to see what pyemof tool perform under the hood.
In order to use PyEMOF, you have to load the core module of PyEMOF. More precisely, you need both a repository (to store the meta-model definition) and a factory (to create elements in the repository).
>>> from emof.core import Repository, Factory >>> repository = Repository() >>> factory = Factory(repository)
We can now create a meta-model using the concepts of EMOF. As an example, we are going to define a simple meta-model for modeling Java Bean applications. First, we define a package that represents our meta-model.
>>> package = factory.create_Package() >>> package.name = 'javabean'
First, we have to define the primitive type String.
>>> string = factory.create_PrimitiveType() >>> string.name = 'String' >>> package.ownedType.append(string)
Then, we define the class Bean. A Bean, has a name.
>>> bean = factory.create_Class() >>> bean.name = 'Bean' >>> package.ownedType.append(bean) >>> bname = factory.create_Property() >>> bname.name = 'name' >>> bname.type = string >>> bean.ownedAttribute.append(bname)
A Property has a name and a type. Names and types are defined as properties of the classe.
>>> prop = factory.create_Class() >>> prop.name = 'Property' >>> package.ownedType.append(prop) >>> pname = factory.create_Property() >>> pname.name = 'name' >>> pname.type = string >>> prop.ownedAttribute.append(pname) >>> ptype = factory.create_Property() >>> ptype.name = 'type' >>> ptype.type = string >>> prop.ownedAttribute.append(ptype)
Finally, a Bean has a set of properties.
>>> bprops = factory.create_Property() >>> bprops.name = 'name' >>> bprops.type = prop >>> bprops.lower = '0' >>> bprops.upper = '*' >>> bprops.isComposite = True >>> bean.ownedAttribute.append(bprops)
We are not going to define the event part of the meta-model, as it does not provide more information here. Once, the meta-model defined we can serialize it as an XMI file using the provided dumper module.
>>> from emof.dumper import Dumper >>> Dumper().dump(repository, 'jbean.xmi')
Such meta-model definition can be used to generate the corresponding repository using pyemof tool.
$ pyemof jbean.xmi $ ls jbean __init__.py core.py dumper.py loader.py
PyEMOF was intended to stay as lightweight as possible. To create a model, you have to use the Python interpreter, load the appropriate meta-model repository and factory definition, and interactively (or in a script) build your model [1].
To illustrate (again) the use of PyEMOF repositories, we are going to build a model conforming to our simple javabean meta-model.
>>> from javabean.core import Repository, Factory >>> repository = Repository() >>> factory = Factory(repository)
First, we create two beans: Observer and Subject.
>>> observer = factory.create_Bean() >>> observer.name = 'Observer' >>> subject = factory.create_Bean() >>> subject.name = 'Subject'
Second, we define an Event containing a field containing information.
>>> event = factory.create_Event() >>> event.name = 'Notification' >>> info = factory.create_Field() >>> info.name = 'information' >>> info.type = 'String' >>> event.fields.append(info)
Then, we can state that the Subject emits Notification events, and that the Observer listen to these events.
>>> subject.emit.append(event) >>> observer.listen.append(event)
Finally, we serialize this model into an XML file.
>>> from javabean.dumper import Dumper >>> Dumper().dump(repository, 'observer.xmi')
The resulting XMI model should look as follows. If you prefer write XMI than Python code, help yourself!
<?xml version="1.0" ?>
<xmi:XMI xmi:version="2.0" xmlns:javabean="http://nowhere/javabean.xmi" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<javabean:Bean name="Observer" xmi:id="Bean:1">
<listen>
Event:1
</listen>
</javabean:Bean>
<javabean:Bean name="Subject" xmi:id="Bean:2">
<emit>
Event:1
</emit>
</javabean:Bean>
<javabean:Event name="Notification" xmi:id="Event:1">
<fields name="information" type="String" xmi:id="Field:1" xsi:type="javabean:Field"/>
</javabean:Event>
</xmi:XMI>
PyEMOF is a non-intrusive tool, that permits the handling of models from a Python program: Having Python repository allows you to easily walk and process models using Python scripts or the interpreter. You do not have to know more than the language. To load a model into memory from an XMI file, we use the loader module.
>>> from javabean.loader import Loader
>>> repository = Loader().load('observer.xmi')
Now, we can easily visit the repository in order to generate the Java source code of our Java Bean application. First, let write a snippet that generate the source code of all the events definitions of a model.
>>> for event in repository.Event:
... print 'class %s implements java.util.EventObject {' % event.name
... for field in event.fields:
... print ' public %s %s ;' % (field.type, field.name)
... print '}'
class Notification implements java.util.EventObject {
public String information ;
}
When producing textual data from a model, you will definitely benefit from a template system such as Cheetah or Mako.
As a dynamic language, Python permits to easily define model transformations. PyEMOF simply provides the repositories, and then you write the transformation that visit repositories and produce new models in repositories.
In order to illustrate a simple model transformation, we are going to use a simple meta-model of the Java language and define part of a transformation that translate a javabean compliant model to a java compliant one.
package java {
primitive String ;
class Package {
attribute name : String ;
attribute itfs : Interface [0..*] isComposite ;
attribute clss : Class [0..*] isComposite ;
}
class Interface {
attribute name : String ;
attribute meth : Method [0..*] ;
}
class Class {
attribute name : String ;
attribute attr : Attribute [0..*] ;
attribute meth : Method [0..*] ;
attribute impl : Interface [0..*] ;
}
class Attribute {
attribute name : String ;
attribute type : String ;
}
class Method {
attribute name : String ;
attribute type : String ;
attribute args : Parameter [0..*] ;
}
class Parameter {
attribute name : String ;
attribute type : String ;
}
}
Then, we can write a snippet that transform a Bean to a Java class, including the requirements for listening events.
First, we load the observer model into memory.
>>> from javabean.loader import Loader
>>> source = Loader().load('observer.xmi')
Second, we define a repository and factory for producing the Java model.
>>> from java.core import Repository, Factory >>> target = Repository() >>> factory = Factory(target)
Then, we write the transformation code. To begin, we write the code that translate an event to the Java interface for listeners.
>>> event_listeners = dict() >>> for event in source.Event: ... itf = factory.create_Interface() ... itf.name = event.name + 'Listener' ... event_listeners[event.name] = itf
Beans are then transformed to Java classes and the associated definition to listen to events, i.e. the Java class implements the listener interface for the corresponding event.
>>> for bean in source.Bean: ... clss = factory.create_Class() ... clss.name = bean.name ... for event in bean.listen: ... clss.impl.append(event_listeners[event.name])
Finally, we save the Java model to an XMI file.
>>> from java.dumper import Dumper >>> Dumper().dump(target, 'jobserver.xmi')
The resulting Java model is as follows.
<?xml version="1.0" ?>
<xmi:XMI xmi:version="2.0" xmlns:java="http://nowhere/java.xmi" xmlns:xmi="http:
//www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<java:Interface name="NotificationListener" xmi:id="Interface:1"/>
<java:Class name="Observer" xmi:id="Class:1">
<impl>
Interface:1
</impl>
</java:Class>
<java:Class name="Subject" xmi:id="Class:2"/>
</xmi:XMI>
One motivation in the implementation of PyEMOF was to stay out of the way. I personally do not like when tools impose me a way of working. Thus, PyEMOF is very minimalist (moreover it started as a simple work bench for research prototyping). So, you may find it too much "Roots", as every thing has to be hand-written.
If you intend to use PyEMOF, I recommend to craft a tool box that provides high-level functions for you specific needs. Considering the Java Bean meta-model used in the previous sections. If you need to use it, then you can define a set of function for rapid modeling. BeanModelFactory is a class that encapsulate the user of the PyEMOF repository and factory, a provides such functions.
>>> from javabean.core import Repository, Factory >>> from javabean.dumper import Dumper >>> class BeanModelFactory(object): ... def __init__(self, repository): ... self.factory = Factory(repository) ... def create_bean(self, name): ... b = self.factory.create_Bean() ... b.name = name ... return b ... def create_property(self, bean, name, type): ... p = self.factory.create_Property() ... p.name = name ... p.type = type ... bean.properties.append(p) ... return p ... def create_event(self, name): ... e = self.factory.create_Event() ... e.name = name ... return e ... def create_field(self, event, name, type): ... f = self.factory.create_Field() ... f.name = name ... f.type = type ... event.fields.append(f) ... return f
Then, it is much simpler to define a model conforming to the Java Bean meta-model using this model factory. The following snippet is equivalent to the code we wrote in the model definition section.
>>> repository = Repository()
>>> factory = BeanModelFactory(repository)
>>> observer = factory.create_bean('Observer')
>>> notifier = factory.create_bean('Notifier')
>>> notification = factory.create_event('Notification')
>>> factory.create_field(notification, 'Information', 'String')
<javabean.core.Field object at ...
>>> notifier.emit.append(notification)
>>> observer.listen.append(notification)
>>> Dumper().dump(repository, 'jobserver.xmi')
And you can certainly do much better!
This is a first introduction to PyEMOF. For the moment, PyEMOF does not support E-MOF enumerated types. When writing transformations, you should write helper operation in order to simplify your code and to ease its writing (for example to define attributes). These helpers have not been generated as their implementation differs from one user to another.
Note
If you wonder why all the above Python code is written interactively, it is because this tutorial has been written using docutils (the source is reStructured Text). It is include in a test suite that execute all these statements. Evolution of the tutorial is easy and safer as it can be automatically run again and check that there are not mistakes.
Do not hesitate to ask for more support or information!
| [1] | You have just done this using EMOF as meta-model in order to define a model (which was the jbean meta-model). The emof package has been generated using pyemof itself: E-MOF is meta-circular (it can be used to define itself). |