Java Code Generierung mit Eclipse Papyrus

Nachdem es im letzten Artikel um die Erstellung von UML-Modellen mit Eclipse Papyrus ging, soll es in diesem Artikel um die Generierung von Java-Quellcode aus einem derartigen UML-Modell mit Eclipse Papyrus gehen.

Dazu wird vom im letzten Artikel erstellten Modell ausgegangen. Zur Übersicht hier ein Screenshot der Workbench in Papyrus zu Beginn:

Workbench zu Beginn

Abbildung 1: Workbench zu Beginn


Zu sehen ist das im letzten Artikel erstellte UML-Diagramm mit dem EmployeeManager, welcher die drei Methoden addEmployee(…), removeEmployee(…) und getEmployeeByName(…) besitzt. Diese sollen Objekte vom Typ Employee in eine lokale Liste hinzufügen. Des Weiteren gibt es die Klassen Employee und ExternalConsultant, welche beide von der Oberklasse Person erben. Außerdem besitzt die Klasse Employee die drei Attribute salary, hiringDate, sowie position. Die Position ist dabei ein Enum-Wert. Von Person geerbt werden die Attribute firstName und lastName.

Nun gibt es mehrere Möglichkeiten, in Papyrus aus einem UML Modell Java-Quellcode zu generieren.

Alternative 1: Codegenerierung via Designer

Die erste triviale Möglichkeit generiert über den Designer aus jeder der Klassen im UML-Diagramm eine Java-Klasse. Dazu wird mit der rechten Maustaste auf das Projekt im Model Explorer geklickt. Im sich öffnenden Kontextmenü wird dann ganz unten Designer -> Generate Java code ausgewählt (siehe Abbildung 2).

Quellcodegenerierung via Designer

Abbildung 2: Quellcodegenerierung via Designer

Anschließend wird über den New Java Project Wizard ein neues leeres Java-Projekt erstellt, in welches dann der generierte Code eingefügt wird.

Ohne die Einbindung der nötigen Programmbibliotheken sieht das Resultat dann in etwa aus wie in Abbildung 3:

Generierter Quellcode via Designer

Abbildung 3: Generierter Quellcode via Designer

Es fällt auf, dass der generierte Code zunächst einmal nicht kompiliert. Die gewählte Datumsklasse EcorePrimitiveTypes.EDate wurde nicht standardmäßig auf java.util.Date abgebildet. Stattdessen hätte bei der Modellerstellung direkt die korrekte Datumsklasse in Java ausgewählt werden müssen, was jedoch die Generierung von Quellcode in einer anderen Sprache logischerweise unmöglich gemacht hätte.

Außerdem besitzt die Methode getEmployeesByName(…) statt einer Liste oder eines Arrays nur ein einzelnes Employee-Objekt als Rückgabetyp. Da nichts zurückgegeben wird, kommt es hier zu einem weiteren Compilerfehler.

Auch Getter und Setter wurden nicht automatisch generiert.

Alles in allem also ein begrenzt hilfreiches Resultat.

Alternative 2: Codegenerierung via EMF Generator Model

Für die zweite Alternative wird zunächst ein EMF Generator Model erstellt. Dazu wird im New Wizard der EMF Generator Model Wizard ausgewählt (New -> Other -> Eclipse Modeling Framework -> EMF Generator Model).

EMF Generator Model Wizard

Abbildung 4: EMF Generator Model Wizard

Hier wird zunächst das korrekte Projekt oben ausgewählt. Anschließend wird als File name ein Dateiname für das EMF Generator Model festgelegt. Es ist darauf zu achten, dass die Datei auf “.genmodel” endet (vgl. Abbildung 4).

Nach einem Klick auf Next muss nun der Model Importer ausgewählt werden. Da es sich bei dem im vorigen Artikel erstellten Modell um ein UML-Modell handelt, wird hier UML model ausgewählt.

Es folgt ein weiterer Klick auf Next.

EMF Generator Model UML Import

Abbildung 5: EMF Generator Model UML Import

Auf dieser Wizard-Seite (Abbildung 5) wird das UML-Modell ausgewählt. Dazu wird zunächst auf die Schaltfläche Browse Workspace… geklickt. Dort wird in das Projekt aus dem vorherigen Artikel navigiert und die Datei example-model.uml ausgewählt. Nach einem Klick auf OK schließt sich das vordere Dialogfenster und das hintere Dialogfenster wird aktualisiert. Nach Abschluss der Aktualisierung kann auf Next geklickt werden.

Auf der nächsten Seite werden zunächst die Referenced generator models unten ausgewählt. Dort sollte nun das Types-Package verfügbar sein. Anschließend wird oben das EmployeeManagement Package ausgewählt. Nach einem Klick auf Finish wird nun die EMF Generator Model Datei im Project Explorer eingefügt. Je nach Einstellung wird diese Datei als Unterpunkt von example-model angezeigt. Sie kann dann mit einem Rechtsklick -> Open in der EMF Generator View geöffnet werden.

EMF Generator View

Abbildung 6: EMF Generator View

Ein weiterer Rechtsklick auf das Example-model in der EMF Generator View (siehe Abbildung 6) und dann Generate Model Code genügt, um das Model zu erstellen. Der Quellcode wird in einen src-Ordner innerhalb desselben Projektes abgelegt.

Es ist zu bedenken, dass für eine Neugenerierung des Quellcodes nach einer Änderung am UML-Modell der EMF Generator Model manuell aktualisiert werden muss. Dazu wird mit der rechten Maustaste auf das Example-model in der EMF Generator View geklickt und Reload ausgewählt. Daraufhin wird die UML-Datei erneut eingeladen.

Nun zum generierten Quellcode (Abbildung 7).

Generierter Quellcode via EMF Generator Model

Abbildung 7: Generierter Quellcode via EMF Generator Model

Es fällt zunächst auf, dass deutlich mehr Klassen generiert wurden, als in Alternative 1. Außerdem sind die Klassen Employee, EmployeeManager, ExternalConsultant und Person jetzt Interfaces. Die erstellten Attribute sind in Form von Getter- und Setter-Methoden im Interface vorgegeben.

Der EmployeeManager besitzt außerdem noch die Methoden addEmployee(…), removeEmployee..(), getEmployeesByName(…) und getEmployees.(…). Die Methode getEmployeesByName(…) gibt dabei ein Objekt vom Typ org.emf.ecore.EList zurück. EList ist ein Interface, welches von java.util.List erbt. Damit wird die 1:n Multiplizität hier korrekt widergegeben, was bei der Codegenerierung via Designer in Alternative 1 nicht funktioniert hatte.

Es wurde automatisch ein Interface für eine EmployeeFactory angelegt, welches Methoden, wie etwa createEmployee() oder createExternalConsultant() beinhaltet. Außerdem fällt auf, dass die Klasse Person jetzt nicht mehr direkt von Object, sondern von org.eclipse.emf.ecore.EObject erbt.

Des Weiteren gibt es noch ein impl-Package. Darin befindet sich beispielsweise eine EmployeeImpl-Klasse, die von PersonImpl erbt und das Employee-Interface implementiert. In der EmployeeImpl-Klasse sind die im UML-Diagramm definierten Variablen zu finden, welche direkt über die Getter und Setter aus dem Interface erreichbar sind. Außerdem sind die Default-Werte als statische Variablen festgelegt. Um zu zeigen, wie eine derartige generierte Klasse aussieht, wurde der gesamte Code der EmployeeImpl-Klasse unten eingefügt. Die generierten JavaDoc-Kommentare wurden aus Platzgründen entfernt.

public class EmployeeImpl extends PersonImpl implements Employee {

	protected static final double SALARY_EDEFAULT = 0.0;
	protected double salary = SALARY_EDEFAULT;
	protected static final Date HIRING_DATE_EDEFAULT = null;
	protected Date hiringDate = HIRING_DATE_EDEFAULT;
	protected static final Position POSITION_EDEFAULT = Position.CEO;
	protected Position position = POSITION_EDEFAULT;

	protected EmployeeImpl() {
		super();
	}

	@Override
	protected EClass eStaticClass() {
		return EmployeeManagementPackage.Literals.EMPLOYEE;
	}

	public double getSalary() {
		return salary;
	}

	public void setSalary(double newSalary) {
		double oldSalary = salary;
		salary = newSalary;
		if (eNotificationRequired())
			eNotify(new ENotificationImpl(this, Notification.SET, EmployeeManagementPackage.EMPLOYEE__SALARY, oldSalary, salary));
	}

	public Date getHiringDate() {
		return hiringDate;
	}

	public void setHiringDate(Date newHiringDate) {
		Date oldHiringDate = hiringDate;
		hiringDate = newHiringDate;
		if (eNotificationRequired())
			eNotify(new ENotificationImpl(this, Notification.SET, EmployeeManagementPackage.EMPLOYEE__HIRING_DATE, oldHiringDate, hiringDate));
	}

	public Position getPosition() {
		return position;
	}

	public void setPosition(Position newPosition) {
		Position oldPosition = position;
		position = newPosition == null ? POSITION_EDEFAULT : newPosition;
		if (eNotificationRequired())
			eNotify(new ENotificationImpl(this, Notification.SET, EmployeeManagementPackage.EMPLOYEE__POSITION, oldPosition, position));
	}

	@Override
	public Object eGet(int featureID, boolean resolve, boolean coreType) {
		switch (featureID) {
			case EmployeeManagementPackage.EMPLOYEE__SALARY:
				return getSalary();
			case EmployeeManagementPackage.EMPLOYEE__HIRING_DATE:
				return getHiringDate();
			case EmployeeManagementPackage.EMPLOYEE__POSITION:
				return getPosition();
		}
		return super.eGet(featureID, resolve, coreType);
	}

	
	@Override
	public void eSet(int featureID, Object newValue) {
		switch (featureID) {
			case EmployeeManagementPackage.EMPLOYEE__SALARY:
				setSalary((Double)newValue);
				return;
			case EmployeeManagementPackage.EMPLOYEE__HIRING_DATE:
				setHiringDate((Date)newValue);
				return;
			case EmployeeManagementPackage.EMPLOYEE__POSITION:
				setPosition((Position)newValue);
				return;
		}
		super.eSet(featureID, newValue);
	}

	
	@Override
	public void eUnset(int featureID) {
		switch (featureID) {
			case EmployeeManagementPackage.EMPLOYEE__SALARY:
				setSalary(SALARY_EDEFAULT);
				return;
			case EmployeeManagementPackage.EMPLOYEE__HIRING_DATE:
				setHiringDate(HIRING_DATE_EDEFAULT);
				return;
			case EmployeeManagementPackage.EMPLOYEE__POSITION:
				setPosition(POSITION_EDEFAULT);
				return;
		}
		super.eUnset(featureID);
	}

	@Override
	public boolean eIsSet(int featureID) {
		switch (featureID) {
			case EmployeeManagementPackage.EMPLOYEE__SALARY:
				return salary != SALARY_EDEFAULT;
			case EmployeeManagementPackage.EMPLOYEE__HIRING_DATE:
				return HIRING_DATE_EDEFAULT == null ? hiringDate != null : !HIRING_DATE_EDEFAULT.equals(hiringDate);
			case EmployeeManagementPackage.EMPLOYEE__POSITION:
				return position != POSITION_EDEFAULT;
		}
		return super.eIsSet(featureID);
	}

	@Override
	public String toString() {
		if (eIsProxy()) return super.toString();

		StringBuffer result = new StringBuffer(super.toString());
		result.append(" (salary: ");
		result.append(salary);
		result.append(", hiringDate: ");
		result.append(hiringDate);
		result.append(", position: ");
		result.append(position);
		result.append(')');
		return result.toString();
	}
} 

Die Methoden addEmployee(), getEmployeesByName(), etc. in der EmployeeManagerImpl-Klasse sind nicht vorausgefüllt. Stattdessen hat der Generator einen TODO-Kommentar und eine UnsupportedOperationException hineingeneriert.

/**
 * @generated
 */
public EList<Employee> getEmployeesByName(String employeeName) {
	// TODO: implement this method
	// Ensure that you remove @generated or mark it @generated NOT
	throw new UnsupportedOperationException();
}

Fazit

Während die Java-Quellcodegenerierung mit dem Designer-Generator deutliche Schwächen hatte, funktioniert die Codegenerierung über das EMF Generator Model hervorragend. An einigen Stellen ist der Code in den generierten Klassen zwar nicht ganz einfach zu verstehen. Dafür bietet er aber deutlich mehr Flexibilität durch die Separierung in Interfaces und Implementierungsklassen.

Sofern bereits ein korrektes UML-Diagramm existiert, können mit Hilfe des Codegenerators mit wenig Aufwand daraus allgemein erweiterbare Java-Klassen generiert werden.

Short URL for this post: https://wp.me/p4nxik-2Rd
This entry was posted in Eclipse Universe, MDSD and tagged , , , , , , . Bookmark the permalink.

Leave a Reply