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