Der EMF-generierte Code
10. Dezember 2014
Überblick
Wie sieht der aus einem EMF-Modell generierte
Code aus? Wie ist die Beziehung zwischen Modell
und Code?
Wie kann generierter Code durch
handgeschriebenen erweitert bzw. ersetzt werden?
Wie kann ein Instanzmodell erstellt werden?
Durch einen Instanzeditor
Anwendung der generierten Klassen
Durch Anwendung von reflektiven Methoden auf dem EMFModell
Welcher Test-Code wird erstellt?
Taentzer
Modellgetriebene Entwicklung von mobilen Anwendungen
193
Eclipse Modeling Framework (EMF)
Java
Klassen zur
Manipulation
des Modells
EMF
Klassendiagramm
EMF Generator
benutzt
einfacher
baumbasierter
Editor
basierend auf
JET
Taentzer
Modellgetriebene Entwicklung von mobilen Anwendungen
194
Struktur des generierten Codes
Struktur des Modellcodes:
Schnittstellenklassen
Implementierungsklassen
weitere nützliche Klassen
aus einem EMF-Modell
generierte Plugins:
… .edit
… .editor
… .tests
Taentzer
Modellgetriebene Entwicklung von mobilen Anwendungen
195
Modellierte Klassen
für eine modellierte Klasse wird
erzeugt:
Model.java:
eine Schnittstellenklasse
eine Implementierungsklasse
create-Methode in der FactoryKlasse
public interface Model extends EObject {
…
}
ModelImpl.java:
public class ModelImpl extends MinmalEObjectImpl.Container
implements Model {
…
}
Taentzer
Modellgetriebene Entwicklung von mobilen Anwendungen
196
Abstrakte Klassen
Für eine abstrakte Klasse wird
erzeugt:
eine Schnittstellenklasse
eine abstrakte Impl-Klasse
keine create-Methode in der
Factory-Klasse
Type.java:
public interface Type extends EObject {
…
}
TypeImpl.java:
public abstract class TypeImpl extends
MinimalEObjectImpl.Container implements Type {
…
}
Taentzer
Modellgetriebene Entwicklung von mobilen Anwendungen
197
Abstrakte und Schnittstellenklassen
Abstrakte Klasse:
in EClass:
Attribut abstract = true
im generierten Code:
abstrakte
Implementierungsklasse
keine create-Methode in der
Factory
Taentzer
Schnittstellenklasse:
in EClass:
Attribut interface = true
Attribut instanceClass nicht
gesetzt
im generierten Code:
keine
Implementierungsklasse,
aber Schnittstellenklasse
keine create-Methode in der
Factory
Modellgetriebene Entwicklung von mobilen Anwendungen
198
Vererbung
Vererbung von EObject und
EObjectImpl
Mehrfachvererbung im
Modell - Mehrfachvererbung
der entsprechenden
Schnittenstellen, Impl.Klasse erbt von nur einer
Impl.-Klasse
Entity.java:
public interface Entity extends Type
EntityImpl.java:
public class EntityImpl extends TypeImpl implements Entity
Taentzer
Modellgetriebene Entwicklung von mobilen Anwendungen
199
Structural Features: Zugriffsmethoden
Model.name
Für jedes Attribut und jede
Referenz:
eine getter-Methode
falls changeable = true:
eine setter-Methode
Model.java:
public interface Model extends EObject {
...
String getName();
void setName(String value);
...
}
Taentzer
Modellgetriebene Entwicklung von mobilen Anwendungen
200
Einfache Attribute
ModelImpl.java:
protected static final String
NAME_EDEFAULT = null;
protected String name = NAME_EDEFAULT;
public String getName() {
return name;
}
public void setName(String newName) {
String oldName = name;
name = newName;
if (eNotificationRequired())
eNotify(new ENotificationImpl(this,Notification.SET,
MyCMPackage.MODEL_NAME,
oldName, name));
}
Taentzer
Modellgetriebene Entwicklung von mobilen Anwendungen
201
Subject-Observer-Prinzip
Subject: Objekt aus dem
EMF-Modell
Observer: andere Objekte,
die das EMF-Objekt
benutzen
Subject
Observer
Observer
Observer
Observer
Observer müssen sich als
Listener anmelden.
bei Änderung des Subjects:
Benachrichtungung an alle
Observer
Observer in EMF: Adapter
Taentzer
Modellgetriebene Entwicklung von mobilen Anwendungen
202
Unidirektionale Referenzen
Falls resolveProxies = true:
Zusätzlicher Code zur Behandlung
von Proxy-Objekten
EntityImpl.java:
protected Entity extends_;
public Entity getExtends() {
//…
return extends_;
}
public void setExtends(Entity newExtends){
//…
}
Taentzer
Modellgetriebene Entwicklung von mobilen Anwendungen
203
Bidirektionale Referenzen
TypeImpl.java:
public void setModel(Model newModel) {
if (newModel != eInternalContainer || (eContainerFeatureID() !=
MCMPackage.TYPE__MODEL && newModel != null)) {
//…
NotificationChain msgs = null;
if (eInternalContainer() != null)
msgs = eBasicRemoveFromContainer(msgs);
if (newModel != null)
msgs = ((InternalEObject)newModel).eInverseAdd(this,
MyCMPackage.MODEL__ELEMENTS, Model.class, msgs);
msgs = basicSetModel(newModel, msgs);
if (msgs != null) msgs.dispatch();
}
else if (eNotificationRequired())
eNotify(new ENotificationImpl(this, Notification.SET,
MyCMPackage.TYPE__MODEL, newModel, newModel));
}
Taentzer
Modellgetriebene Entwicklung von mobilen Anwendungen
204
Factories
Eine Factory ist die zentrale Klasse, in der neue Objekte angelegt werden.
MyCMFactory.java:
public interface MyCMFactory extends EFactory {
MyCMFactory eINSTANCE =
org.xtext.example.mydsl.myCM.impl.MyCMFactoryImpl.init();
Model createModel();
ImportedClass createImportedClass();
SimpleType createSimpleType();
Entity createEntity();
Property createProperty();
Method createMethod();
Parameter createParameter();
Enumeration createEnumeration();
Literal createLiteral();
MyCMPackage getMyCMPackage();
Taentzer
Modellgetriebene Entwicklung von mobilen Anwendungen
205
Factories
• Es kann mehrere Implementierungen für eine Factory geben.
• Es gibt eine DefaultFactory.
public class MyCMFactoryImpl extends
EFactoryImpl implements MyCMFactory {
public static MyCMFactory init() {
try {
MyCMFactory theMyCMFactory =
(MyCMFactory)EPackage.Registry.INSTANCE.
getEFactory(MyCMPackage.eNS_URI);
if (theMyCMFactory != null) {
return theMyCMFactory;
}
}
catch (Exception exception) {
EcorePlugin.INSTANCE.log(exception);
}
return new MyCMFactoryImpl();
}
}
Taentzer
Modellgetriebene Entwicklung von mobilen Anwendungen
206
Codeerweiterung
An verschiedensten Stellen
sind Codeerweiterungen
wünschenswert.
Mit EMF kann kein Verhalten
modelliert werden -> Codeerweiterung
Nach der Codeerweiterung
muss eine erneute Codegenerierung möglich sein.
Generierte Methoden haben
ein @generated Tag.
Taentzer
/**
* <!-- begin-user-doc -->
* <!-- end-user-doc -->
* @generated
*/
public boolean checkModel() {
// TODO: implement this method
// Ensure that you remove @generated or
mark it @generated NOT
throw new
UnsupportedOperationException();
}
Nach manueller Codeerweiterung muss das Tag
@generated NOT gesetzt
werden.
Modellgetriebene Entwicklung von mobilen Anwendungen
207
Beispiel: Codeerweiterung
ModelImpl.java:
/**
* <!-- begin-user-doc -->
* <!-- end-user-doc -->
*/
public boolean checkModel() {
EList<Type> modelTypes = this.getElements();
for(Type t1: modelTypes) {
for( Type t2: modelTypes){
if (t1 != t2)
if (t1.getName() == t2.getName())
return false;
}
}
return true;
}
Taentzer
Modellgetriebene Entwicklung von mobilen Anwendungen
208
Erstellung eines Instanzmodells
Beispiel: Erstellung eines Modells
tests.MyCMExample.java:
import
import
import
import
import
import
org.eclipse.emf.common.util.URI;
org.eclipse.emf.ecore.resource.Resource;
org.eclipse.emf.ecore.resource.ResourceSet;
org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl;
org.xtext.example.mydsl.myCM.;
//...
// Create a resource set to hold the resources.
ResourceSet resourceSet = new ResourceSetImpl();
// Register the appropriate resource factory to handle all file extensions.
resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put
(Resource.Factory.Registry.DEFAULT_EXTENSION,
new XMIResourceFactoryImpl());
// Register the package to ensure it is available during loading.
resourceSet.getPackageRegistry().put
(MyCMPackage.eNS_URI,
MyCMPackage.eINSTANCE);
//...
Taentzer
Modellgetriebene Entwicklung von mobilen Anwendungen
209
Erstellung eines Instanzmodells
Beispiel: Erstellung eines Modells
tests.MyCMExample.java:
//...
// If there are no arguments, emit an appropriate usage message.
if (args.length == 0) {
System.out.println("Enter a list of file paths or URIs that have content
like this:");
try {
Resource resource =
resourceSet.createResource(URI.createURI("http://My.cm"));
Model root = MyCMFactory.eINSTANCE.createModel();
resource.getContents().add(root);
resource.save(System.out, null);
//Ab hier eigener Code
}
Taentzer
Modellgetriebene Entwicklung von mobilen Anwendungen
210
Erstellung eines eigenen Modells
Beispiel: Erstellung eines Modells
tests.MyCMExample.java:
//...
Model root = MyCMFactory.eINSTANCE.createModel();
resource.getContents().add(root);
MyCMFactory mFactory = MyCMFactory.eINSTANCE;
Model myModel = mFactory.createModel();
Entity person = mFactory.createEntity();
person.setModel(myModel);
person.setName("Person");
Entity address = mFactory.createEntity();
person.setModel(myModel);
person.setName("Address");
myModel.getElements().add(person);
myModel.getElements().add(address);
//...
Taentzer
Modellgetriebene Entwicklung von mobilen Anwendungen
211
Aufruf einer eignen Methode
Beispiel: Erstellung eines Modell
tests.MyCMExample.java:
//...
if (myModel.checkModel())
System.out.println("Alle Typnamen sind eindeutig.");
else
System.out.println("Es gibt zwei Typen mit dem gleichen Namen.");
//...
Taentzer
Modellgetriebene Entwicklung von mobilen Anwendungen
212
Einlesen eines Instanzmodells
public static void main(String[] args) {
MyCMPackageImpl.init();
Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().
put(„cm", new XMIResourceFactoryImpl());
ResourceSet resourceSet = new ResourceSetImpl();
URI fileURI = URI.createURI("src/Test.cm");
Resource resource = resourceSet.getResource(fileURI, true);
Model myModel = (Model)resource.getContents().get(0);
}//main
Taentzer
Modellgetriebene Entwicklung von mobilen Anwendungen
213
JUnit
Prinzipien:
Zu jeder verfassten Klasse eine Testklasse entwerfen.
Testcode und Anwendungscode sind strikt getrennt.
Zum Schreiben von Tests werden lediglich benötigt:
(TestSuite), TestCase, Assert
Testsuiten dienen dazu, verschiedene Tests in einer
bestimmten Reihenfolge aufzurufen.
Suiten können dazu verwendet werden,
verschiedene Klassen eines Paketes bzw. Projektes
auf einmal zu testen.
Pakettests, Klassentests, Methodentests
public class MyCMTests extends TestSuite { …
Taentzer
Modellgetriebene Entwicklung von mobilen Anwendungen
214
Definition von Testfällen
Testfälle werden in einer normalen Klasse erstellt.
Framework führt die definierten Testfälle aus.
Jeder Test wird gekapselt:
Es kann ein Fixture definiert werden. Damit wird ein
wiederverwendbares Testobjekt festgehalten.
Verschiedene Tests nutzen keine gemeinsamen Daten im
Fixture. Jeder Test hat sein eigenes Fixture.
Assert für den Vergleich von Soll- mit Istwerten
Wir prüfen, ob das Ergebnis eines Tests ein bestimmtes
Ergebnis zurückliefert.
Typische Assert-Methoden: assertTrue, assertEqual,
assertNull
Taentzer
Modellgetriebene Entwicklung von mobilen Anwendungen
215
Beispiel für eine Testklasse
Fixture: Grundlage für ein
Testszenario. Dieses wird vor
einem Test auf und danach wieder
abgebaut.
Taentzer
Modellgetriebene Entwicklung von mobilen Anwendungen
216
Beispiel für einen zweiten Testfall
Taentzer
Modellgetriebene Entwicklung von mobilen Anwendungen
217
Zusammenfassung
Die Beziehung zwischen Modell und Code ist recht
direkt. Die Codegenerierung lässt sich gut durch das
EMF-Modell steuern.
Der generierte Code hat eine gute Qualität:
Java-Konventionen werden eingehalten.
Code Smells werden vermieden, wo möglich.
Passende Design-Patterns werden verwendet.
Generierter Code kann durch handgeschriebenen so
erweitert werden, dass der Generator diesen nicht
überschreibt.
Taentzer
Modellgetriebene Entwicklung von mobilen Anwendungen
218