How to Create Project-Specific Code Generators for Enterprise Architect Easily
Learn how you can create a project-specific code generator for enterprise architect with the Eclipse IDE integration of the YAKINDU EA-Bridge.
Join the DZone community and get the full member experience.
Join For Free
Have you ever wanted to generate code from your enterprise architect UML or SysML models? Have you tried to customize an enterprise architect’s code template framework? Do not give up the dream of project-specific code generators and read how easily they can be implemented.
The Need for a Code Generator
A good software or system architecture is on a higher abstraction level compared to the implementation. It should be a consistent model that documents decisions and neglects unnecessary, often technical, details. Consider, for instance, the class diagram in Figure 1. It shows a domain model that defines the data structure needed for a shop to allow customers to order articles. The properties of each class are modeled in detail, but other unnecessary aspects like operations to access properties are left out.
If the software architecture/design is made upfront before starting with the implementation, a lot of cumbersome and error-prone work can be avoided by code generation. Commercial out-of-the-box code generators often do not change the degree of abstraction. That’s why they often do not match the needs of the project.
The code template framework of Enterprise Architect can be tailored according to the project-specific needs. But this requires some initial training. And often the expected outcome is hard to achieve as described in Eclipse-based Code Generation for Enterprise Architect Models.
A Simple Project-Specific Code Generator
I prefer a general-purpose programming language such as Java or Xtend to implement code generators. In particular, Xtend is well-suited to implement templates because of its template expressions. They allow one to embed executable code inside the text to be generated. It feels like programming PHP, JSP, or JSX. The code in Listing 1 shows a code generation template written in Xtend. It generates Java classes for the classes declared in the class diagram of Figure 1.
package com.yakindu.ea.examples.orderingsoftware.template
import com.yakindu.bridges.ea.examples.runtime.codegen.EACodegen
import org.eclipse.uml2.uml.Class
import org.eclipse.uml2.uml.NamedElement
class ClassTemplate {
"Java") (
def String generate(Class element) '''
package «element.package.javaQualifiedName»;
«val superType = element.generals.findFirst[true]?.name»
«val extends = '''«IF !superType.isNullOrEmpty» extends «superType»«ENDIF»'''»
public«IF element.isAbstract» abstract«ENDIF» class «element.name»«extends» {
«FOR attribute : element.ownedAttributes SEPARATOR System.lineSeparator»
«val type = attribute.type?.javaQualifiedName»
«IF 1 != attribute.upper»
«val defaultValue = '''new java.util.LinkedList<«type»>()'''»
private final java.util.List<«type»> «attribute.name» = «defaultValue»;
«ELSE»
private «type» «attribute.name»;
«ENDIF»
«ENDFOR»
«FOR attribute : element.ownedAttributes SEPARATOR System.lineSeparator»
«val type = attribute.type?.javaQualifiedName»
«IF 1 != attribute.upper»
public List<«type»> get«attribute.name.toFirstUpper»() {
return «attribute.name»;
}
«ELSE»
public «type» get«attribute.name.toFirstUpper»() {
return «attribute.name»;
}
«ENDIF»
«IF 1 == attribute.upper»
«val params = '''«type» «attribute.name»'''»
public void set«attribute.name.toFirstUpper»(«params») {
this.«attribute.name» = «attribute.name»;
}
«ENDIF»
«ENDFOR»
}
'''
protected def String getJavaQualifiedName(NamedElement element) {
element.qualifiedName.replace("::", ".")
}
}
// Listing 1: Example code generation template written in Xtend
The generated Java code shown in Listings 2, 3 and 4 does not look like handwritten, because qualified names are used instead of imports. This will be improved later in Figure 4 by methods collectImports
and printImports
.
xxxxxxxxxx
package com.example.orderingsoftware;
public abstract class AbstractIDObject {
private java.util.UUID id;
public java.util.UUID getId() {
return id;
}
public void setId(java.util.UUID id) {
this.id = id;
}
}
// Listing 2: Java code of class AbstractIDObject generated by the code generation template in Listing 1
xxxxxxxxxx
package com.example.orderingsoftware;
public class OrderItem extends AbstractIDObject {
private java.math.BigInteger amount;
private com.example.orderingsoftware.Article article;
public java.math.BigInteger getAmount() {
return amount;
}
public void setAmount(java.math.BigInteger amount) {
this.amount = amount;
}
public com.example.orderingsoftware.Article getArticle() {
return article;
}
public void setArticle(com.example.orderingsoftware.Article article) {
this.article = article;
}
}
// Listing 3: Java code of class OrderItem generated by the code generation template in Listing 1
xxxxxxxxxx
package com.example.orderingsoftware;
public class Order extends AbstractIDObject {
private java.util.Date date;
private com.example.orderingsoftware.Customer customer;
private final java.util.List<OrderItem> items = new java.util.LinkedList<OrderItem>();
public java.util.Date getDate() {
return date;
}
public void setDate(java.util.Date date) {
this.date = date;
}
public com.example.orderingsoftware.Customer getCustomer() {
return customer;
}
public void setCustomer(com.example.orderingsoftware.Customer customer) {
this.customer = customer;
}
public java.util.List<com.example.orderingsoftware.OrderItem> getItems() {
return items;
}
}
// Listing 4: Java code of class Order generated by the code generation template in Listing 1
If you look carefully at the template in Listing 1, you will realize that it does not know anything about Enterprise Architect. Instead, it handles instances of the UML metamodel which is available in Eclipse thanks to the Eclipse UML 2 project. The missing connection between Enterprise Architect and UML is the YAKINDU EA-Bridge. It is an API that offers UML-compliant read and write access to Enterprise Architect UML and SysML models. The database behind an Enterprise Architect project is automatically transformed into instances of the UML metamodel. This has three major advantages for you as a developer:
- Your code is compatible with other tools that are based on the UML 2 project such as Papyrus.
- Highly performant read and write access to Enterprise Architect models without the need to reverse engineer the database schema of Enterprise Architect.
- You do not have to learn anything about the API of the YAKINDU EA-Bridge. It is completely hidden for you as a developer, because the YAKINDU EA-Bridge integrates itself into the ecosystem of the Eclipse Modeling Framework (EMF).
The YAKINDU EA-Bridge comes with an optional Eclipse IDE integration which allows one to implement project-specific code generators. Those code generators are often prototypically developed and are executed only within a certain context. Thus, it is crucial that the development effort is less compared to manual coding. To implement a project-specific code generator, all you have to do is to place the EAP file in an Eclipse project and to annotate methods in the code generation template with @EACodegen
. Annotated methods should accept the UML element for which code should be generated as the only parameter and return the generated text. If your Enterprise Architect model is hosted by a remote database such as Microsoft SQL Server, you can use a shortcut file instead of an EAP file.
When the project is built, automatically or manually via the main menu item ‘Project, Clean…‘, the template is launched for all UML classes declared in all EAP files. Of course, only EAP files stored in the template’s project are considered. The generated code is saved in a file specified by the qualified name of the class. The file extension is specified by the argument of the @EACodegen
annotation. The structure of the Eclipse project can be seen in Figure 2.
Please observe that the YAKINDU EA-Bridge is an API. It allows you to process the Enterprise Architect model in any way. Indeed, the original use case were comprehensive code generators such as an Autosar RTE generator based on UML architectures.
Generating More than One Artefact per Model Element
Let’s make the example more exciting by implementing a product line with two different persistence approaches: One that uses JPA to store data in a relational database and one that uses HBase as a big data store.
I suggest implementing a persistence manager which can be used to load and save instances. Only the product based on JPA should allow one to start and complete transactions. Furthermore, I would like to place JPA specific annotations in the Java classes. Figure 3 shows the methods offered by the persistence manager.
The consequence is now, that the implementation of all six classes is slightly different in both products. The Java code in Listings 5, 6, 7 and 8 shows an excerpt of the code to be implemented.
package com.example.orderingsoftware;
public class Article extends AbstractIDObject {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
// Listing 5: Java code of class Article with HBase as persistence approach
xxxxxxxxxx
package com.example.orderingsoftware;
import javax.persistence.Entity;
import javax.persistence.Table;
name = "ArticleTable") (
public class Article extends AbstractIDObject {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
// Listing 6: Java code of class Article with JPA as persistence approach
xxxxxxxxxx
public class PersistenceManager implements AutoCloseable {
private final Table articleTable;
public static final byte[] MODEL_FAMILY = "Model".getBytes();
public static final TableName ARTICLE_TABLE_NAME = TableName.valueOf("ArticleTable");
public Article getArticle(UUID id) {
try {
if (id != null) {
Get get = new Get(id.toString().getBytes());
if (articleTable.exists(get)) {
Result r = articleTable.get(get);
Article result = new Article();
result.setId(id);
result.setName(Bytes.toString(r.getValue(MODEL_FAMILY, ARTICLE_NAME_QUALIFIER)));
return result;
}
}
return null;
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
}
// ...
}
// Listing 7: Excerpt of the Java code of class PersistenceManager with HBase as persistence approach
xxxxxxxxxx
public class PersistenceManager implements AutoCloseable {
private EntityManager entityManager;
public Article getArticle(UUID id) {
if (id != null) {
return entityManager.find(Article.class, id);
} else {
return null;
}
}
// ...
}
// Listing 8: Excerpt of the Java code of class PersistenceManager with JPA as persistence approach
Feasible solutions to realize the product line are:
- To use inheritance. That would require an interface definition with the public API for each class and to implement it for JPA and for HBase. The consequence would be that the rest of the application must be adjusted to operate only on the interfaces and never on the concrete classes.
- To copy, paste, and modify the implementation for both products would avoid the need to modify the rest of the application. Maintaining two variants might sound reasonable. But is this still the case with an increasing amount of variants? You should think carefully about the pros and cons of copy and paste.
- To use a code generator which generates the product specific code. The classes realizing code generation templates could be based on a common implementation and each subclass could adjust the product-specific parts.
I prefer the last solution. The outline in Figure 4 shows the refactored class template of Listing 1. Each introduced method generates a specific member of a Java class. This allows me to override these methods in the product-specific templates. In Listing 9, for instance, it can be seen that JPA specific annotations are placed before the class definition.
class JPAClassTemplate extends ClassTemplate {
def IFile path(Class element, IFile ^default) {
val path = "com/example/orderingsoftware/" + ^default.name
return getTargetFilePath("jpa", path)
}
"java") (
override generate(Class element) {
return super.generate(element)
}
override printClassDeclaration(Class element) '''
«IF element.isAbstract»
«ELSE»
name = "«element.name»Table") (
«ENDIF»
«super.printClassDeclaration(element)»'''
// ...
}
// Listing 9: Excerpt of the code generation template for JPA written in Xtend
The method path(Class, IFile)
in the template subclasses annotated with @EACodegenFile
is used to define the target location at which the generated code should be saved. It has two parameters. The first one is the UML element for which code should be generated. The second is the default location where the generated code should be stored. The return value of the annotated method is the adjusted location at which the generated code should be stored.
The screenshot in Figure 5 shows all templates. The arrows point to the files that are generated by each of them. In addition to the production code, also the test code is generated.
Conclusion
Modern general-purpose programming languages such as Xtend are well suited to implement complex code generators. The input could be a UML model, possibly modeled in Enterprise Architect. The YAKINDU EA-Bridge transforms the relational database behind an Enterprise Architect model on the fly, and completely hidden, into instances of the UML metamodel. There is no need to learn the proprietary code generation language provided by Enterprise Architect or to reverse engineer the database schema of Enterprise Architect.
The Eclipse IDE integration of the YAKINDU EA-Bridge allows one to implement project-specific code generators at low costs in a short time. In this way, you can save a lot of cumbersome, error-prone, and mindless implementation work.
If you want to see and run the full example for yourself, try out the YAKINDU EA-Bridge. The presented example is one of the examples shipped with the YAKINDU EA-Bridge.
Published at DZone with permission of Martin Hentschel. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments