Der EMF-generierte Code 7. November 2012 Ü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 Softwareentwicklung 132 Eclipse Modeling Framework (EMF) Java Klassen zur Manipulation des Modells EMF Klassendiagramm EMF Generator benutzt einfacher baumbasierter Editor basierend auf JET Taentzer Modellgetriebene Softwareentwicklung 133 Struktur des generierten Codes Struktur des Modellcodes: Schnittstellenklassen Implementierungsklassen weitere nützliche Klassen weitere Dateien, um den Code als Eclipse-Plugin zu benutzen META-INF/MANIFEST.MF: alle wichtigen Einstellung zum Plugin plugin.xml: Identifizierung des Plugins und Beziehungen zu anderen Taentzer Modellgetriebene Softwareentwicklung 134 Modellierte Klassen für eine modellierte Klasse wird erzeugt: Dozent.java: eine Schnittstellenklasse eine Implementierungsklasse create-Methode in der FactoryKlasse public interface Dozent extends EObject { … } DozentImpl.java: public class DozentImpl extends EObjectImpl implements Dozent { … } Taentzer Modellgetriebene Softwareentwicklung 135 Abstrakte Klassen Für eine abstrakte Klasse wird erzeugt: eine Schnittstellenklasse eine abstrakte Impl-Klasse keine create-Methode in der Factory-Klasse Lehrveranstaltung.java: public interface Lehrveranstaltung extends EObject { … } LehrveranstaltungImpl.java: public abstract class LehrveranstaltungImpl extends EObjectImpl implements Lehrveranstaltung { … } Taentzer Modellgetriebene Softwareentwicklung 136 Abstrakte und Schnittstellenklassen Abstrakte Klasse: in EClass: Schnittstellenklasse: in EClass: Attribut abstract = true im generierten Code: abstrakte Implementierungsklasse keine create-Methode in der Factory Taentzer Attribut interface = true Attribut instanceClass nicht gesetzt im generierten Code: keine Implementierungsklasse, aber Schnittstellenklasse keine create-Methode in der Factory Modellgetriebene Softwareentwicklung 137 Vererbung Vererbung von EObject und EObjectImpl Mehrfachvererbung im Modell - Mehrfachvererbung der entsprechenden Schnittenstellen, Impl.Klasse erbt von nur einer Impl.-Klasse Uebung.java: public interface Uebung extends Lehrveranstaltung UebungImpl.java: public class UebungImpl extends LehrveranstaltungImpl implements Uebung Taentzer Modellgetriebene Softwareentwicklung 138 Structural Features: Zugriffsmethoden Dozent.name Für jedes Attribut und jede Referenz: eine getter-Methode falls changeable = true: eine setter-Methode Dozent.java: public interface Dozent extends EObject { ... String getName(); void setName(String value); ... } Taentzer Modellgetriebene Softwareentwicklung 139 Einfache Attribute DozentImpl.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, VorlesungsverzeichnisPackage.DOZENT_NAME, oldName, name)); } Taentzer Modellgetriebene Softwareentwicklung 140 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 Softwareentwicklung 141 Unidirektionale Referenzen Falls resolveProxies = true: Zusätzlicher Code zur Behandlung von Proxy-Objekten VorlesungImpl.java: protected EList<Uebung> uebung = null; public EList<Uebung> getUebung() { if (uebung == null) { uebung = new EObjectEList(Uebung.class, this, VorlesungsverzeichnisPackage.VORLESUNG__UEBUNG); } return uebung; } Taentzer Modellgetriebene Softwareentwicklung 142 Bidirektionale Referenzen LehrveranstaltungImpl.java: public void setDozent(Dozent newDozent) { if (newDozent != dozent) { NotificationChain msgs = null; if (dozent != null) msgs = ((InternalEObject)dozent).eInverseRemove(this, VorlesungsverzeichnisPackage.DOZENT__LV, Dozent.class, msgs); if (newDozent != null) msgs = ((InternalEObject)newDozent).eInverseAdd(this, VorlesungsverzeichnisPackage.DOZENT__LV, Dozent.class, msgs); msgs = basicSetDozent(newDozent, msgs); if (msgs != null) msgs.dispatch(); } else if (eNotificationRequired()) eNotify(new ENotificationImpl(this, Notification.SET, VorlesungsverzeichnisPackage.LEHRVERANSTALTUNG__DOZENT, newDozent, newDozent)); } Taentzer Modellgetriebene Softwareentwicklung 143 Factories Eine Factory ist die zentrale Klasse, in der neue Objekte angelegt werden. VorlesungsverzeichnisFactory.java: public interface VorlesungsverzeichnisFactory extends EFactory { Vorlesungen createVorlesungen(); Dozent createDozent(); Raum createRaum(); Vorlesung createVorlesung(); Seminar createSeminar(); Übung createÜbung(); Raumverzeichnis createRaumverzeichnis(); Hörsaal createHörsaal(); Seminarraum createSeminarraum(); VorlesungsverzeichnisPackage getVorlesungsverzeichnisPackage(); Taentzer Modellgetriebene Softwareentwicklung 144 Factories • Es kann mehrere Implementierungen für eine Factory geben. • Es gibt eine DefaultFactory. public class VorlesungsverzeichnisFactoryImpl extends EFactoryImpl implements VorlesungsverzeichnisFactory { public static VorlesungsverzeichnisFactory init() { try { VorlesungsverzeichnisFactory theVorlesungsverzeichnisFactory = (VorlesungsverzeichnisFactory)EPackage.Registry.INSTANCE. getEFactory("http://vorlesungsverzeichnis"); if (theVorlesungsverzeichnisFactory != null) { return theVorlesungsverzeichnisFactory; } } catch (Exception exception) { EcorePlugin.INSTANCE.log(exception); } return new VorlesungsverzeichnisFactoryImpl(); } } Taentzer Modellgetriebene Softwareentwicklung 145 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 checkKonsistenz() { // 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 Softwareentwicklung 146 Beispiel: Codeerweiterung RaumImpl.java: public boolean checkKonsistenz() { EList<Lehrveranstaltung> lvList = this.getVeranstaltung(); for (Lehrveranstaltung lv1 : lvList) { for (Lehrveranstaltung lv2 : lvList) { if (lv1 != null && lv2 != null) { if (lv1 != lv2) { if ((lv1.getZeit() == lv2.getZeit()) && (lv1.getTag() == lv2.getTag())) return false; }// if }// if }// for }// for return true; }// checkKonsistenz Taentzer Modellgetriebene Softwareentwicklung 147 Erstellung eines Instanzmodells Beispiel: Erstellung eines Vorlesungsverzeichnisses VorlesungsverzeichnisBeispiel.java: import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.resource.ResourceSet; import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; import org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl; import vorlesungsverzeichnis.*; import vorlesungsverzeichnis.impl.*; //... // 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 (VorlesungsverzeichnisPackage.eNS_URI, VorlesungsverzeichnisPackage.eINSTANCE); //... Taentzer Modellgetriebene Softwareentwicklung 148 Erstellung eines Instanzmodells Beispiel: Erstellung eines Vorlesungsverzeichnisses VorlesungsverzeichnisBeispiel.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.vorlesungsverzeichnis")); Verzeichnis root = VorlesungsverzeichnisFactory.eINSTANCE.createVerzeichnis(); resource.getContents().add(root); resource.save(System.out, null); //Ab hier eigener Code } Taentzer Modellgetriebene Softwareentwicklung 149 Erstellung eines eignen Modells Beispiel: Erstellung eines Vorlesungsverzeichnisses VorlesungsverzeichnisBeispiel.java: //... VorlesungsverzeichnisFactory vFactory = VorlesungsverzeichnisFactory.eINSTANCE; Verzeichnis vorlesungen = vFactory.createVerzeichnis(); //erstelle ein Raumverzeichnis Raumverzeichnis verzeichnis = vFactory.createRaumverzeichnis(); vorlesungen.setRaumverzeichnis(verzeichnis); //erstelle einen Raum Raum r = vFactory.createHoersaal(); verzeichnis.getRaum().add(r); //erstelle eine Vorlesung Vorlesung vl1 = vFactory.createVorlesung(); vl1.setTag(Tag.DI); vl1.setZeit(Zeit.ZWEI); vorlesungen.getLv().add(vl1); //... Taentzer Modellgetriebene Softwareentwicklung 150 Aufruf einer eignen Methode Beispiel: Erstellung eines Vorlesungsverzeichnisses VorlesungsverzeichnisBeispiel.java: //... Boolean c = true; List<Raum> raeume = verzeichnis.getRaum(); for (Raum raum : raeume) { c = c && raum.checkKonsistenz(); } if (c) System.out.println("Alle Räume sind konsistent belegt."); else System.out.println("Es gibt min. einen Raum mit inkonsistenter Belegung."); //... Taentzer Modellgetriebene Softwareentwicklung 151 Einlesen eines Instanzmodells public static void main(String[] args) { VorlesungsverzeichnisPackageImpl.init(); Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap(). put("vorlesungsverzeichnis", new XMIResourceFactoryImpl()); ResourceSet resourceSet = new ResourceSetImpl(); URI fileURI = URI.createURI("src/Test.vorlesungsverzeichnis"); Resource resource = resourceSet.getResource(fileURI, true); Vorlesungsverzeichnis vz = (Vorlesungsverzeichnis) resource.getContents().get(0); }//main Taentzer Modellgetriebene Softwareentwicklung 152 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 VorlesungsverzeichnisTests extends TestSuite { … Taentzer Modellgetriebene Softwareentwicklung 153 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 Softwareentwicklung 154 Beispiel für einen Testfall abstract class RaumTest extends TestCase public class SeminarraumTest extends RaumTest { protected VorlesungsverzeichnisFactory vFactory = VorlesungsverzeichnisFactory.eINSTANCE; ... public static void main(String[] args) { TestRunner.run(SeminarraumTest.class); } @Override protected void setUp() throws Exception { setFixture(vFactory.createSeminarraum()); } public void testCheckConsistency() { assertTrue(this.getFixture().checkKonsistenz()); } ... Taentzer Modellgetriebene Softwareentwicklung 155 Beispiel für einen zweiten Testfall HoersaalTest.java: public class HoersaalTest { ... public void setUp() throws Exception{ VorlesungsverzeichnisFactory vFactory = VorlesungsverzeichnisFactory.eINSTANCE; Vorlesungen vorlesungen = vFactory.createVorlesungen(); Raumverzeichnis verzeichnis = vFactory.createRaumverzeichnis(); vorlesungen.setRaumverzeichnis(verzeichnis); Raum raum = vFactory.createHoersaal(); verzeichnis.getRaum().add(raum); Vorlesung vl1 = vFactory.createVorlesung(); vl1.setTag(Tag.DI); vl1.setZeit(Zeit.ZWEI); vl1.setRaum(raum); vorlesungen.getLv().add(vl1); ... Taentzer Modellgetriebene Softwareentwicklung 156 Beispiel für einen zweiten Testfall HoersaalTest.java: ... Vorlesung vl2 = vFactory.createVorlesung(); vl2.setTag(Tag.DI); vl2.setZeit(Zeit.ZWEI); vl2.setRaum(raum); vorlesungen.getLv().add(vl2); setFixture(raum); }//setUp() ... Taentzer Modellgetriebene Softwareentwicklung 157 Beispiel für einen zweiten Testfall RaumTest.java: public void testCheckKonsistenz() { // TODO: implement this operation test method // Ensure that you remove @generated or mark it @generated NOT assertTrue(getFixture().checkKonsistenz()); } Taentzer Modellgetriebene Softwareentwicklung 158 Wie wird generiert? Applikation MDD-Infrastruktur modelliert mit Domänenspezif. Modell Domänenspezifische Modellierungssprache transformiert in Generator Code Generator Templates (Modell-zu-Code Tr. ) Wie wird dieser allgemeine Ansatz durch EMF konkretisiert? Taentzer Modellgetriebene Softwareentwicklung 159 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 Softwareentwicklung 160