This post introduces many
important concepts in jMDA without any ado. You will understand most
parts of the example without explanation. The remaining parts will be
briefly explained later in this post. The complete code and everything necessary to compile and run the example is available for download here.
The show case (generate Java Bean
style classes from minimal Java types) might not be the most exciting
one but it is fairly reasonable and – other MDD / MDSD approaches
use similar introductory examples. This way you can easily compare
jMDA with popular frameworks and tools like
xText
for example.
We start by introducing the domain.
The key requirement is: For each Person in the domain there has to be
a way to manage arbitrary textual Data that is organised in so called
data items:
Picture 1: Domain Model for jMDA tutorial
So here is the complete model and the Java
Bean generator in one place:
package
de.jmda.sample.tutorial;
import
static
de.jmda.gen.LineIndenter.TAB_SINGLE;
import
static
de.jmda.gen.java.ModifiersUtil.VisibilityModifier.PRIVATE;
import
static
de.jmda.gen.java.ModifiersUtil.VisibilityModifier.PUBLIC;
import
static
de.jmda.gen.java.naming.ImportManagerProvider.getImportManager;
import
static
de.jmda.gen.java.naming.ImportManagerProvider.newImportManager;
import
static
de.jmda.gen.java.naming.ImportManagerProvider.restoreImportManager;
import
static
de.jmda.core.mproc.ProcessingUtilities.getFields;
import
static
de.jmda.core.mproc.ProcessingUtilities.getPackageQualifiedName;
import
static
de.jmda.core.mproc.ProcessingUtilities.getTypeElement;
import
static
de.jmda.core.util.CollectionsUtil.asSet;
import
static
de.jmda.core.util.StringUtil.firstLetterToUpperCase;
import
de.jmda.gen.GeneratorException;
import
de.jmda.gen.java.ClassGenerator;
import
de.jmda.gen.java.CompilationUnitUtil;
import
de.jmda.gen.java.FieldGenerator;
import
de.jmda.gen.java.MethodGenerator;
import
de.jmda.gen.java.impl.DefaultClassGenerator;
import
de.jmda.gen.java.impl.DefaultInstanceFieldGenerator;
import
de.jmda.gen.java.impl.DefaultInstanceMethodGenerator;
import
de.jmda.gen.java.naming.ImportManager;
import
de.jmda.core.mproc.task.AbstractTypeElementsTaskTypes;
import
de.jmda.core.mproc.task.TaskException;
import
de.jmda.core.mproc.task.TaskRunner;
//
… further
import statements omitted
/**
* Demonstrates how to
generate a Java Bean style class from a simple Java model.
*/
public
class
JUTBeanGenerator
{
// domain model
definition - start
class
Person
{
String firstname;
String lastname;
Date birthdate;
List<Data>
dataItems;
}
class
Data
{
String dataItem;
}
//
domain model definition - end
// processor for
domain model information
private
class
Task extends
AbstractTypeElementsTaskTypes
{
//
constructor
private
Task(Set<? extends
Class<?>> types) { super(types);
}
//
process model domain information
@Override
public
boolean
execute() throws
TaskException
{
Map<TypeElement,
String> typeToTargetTypeName = new
HashMap<>();
String
personTargetTypeName =
Person.class.getPackage().getName()
+ "."
+ Person.class.getSimpleName();
String
dataTargetTypeName =
Data.class.getPackage().getName()
+ "."
+ Data.class.getSimpleName();
typeToTargetTypeName.put(getTypeElement(Person.class),
personTargetTypeName);
typeToTargetTypeName.put(getTypeElement(Data.class),
dataTargetTypeName);
//
iterate types of domain model
for
(TypeElement type : getTypeElements())
{
//
extract type's package and name
String
packagename = getPackageQualifiedName(type);
String typename
= type.getSimpleName().toString();
//
make backup of current import manager and
create new import manager
ImportManager
backup =
newImportManager(packagename,
typename, typeToTargetTypeName);
//
create and configure a generator for the Java Bean to be generated
ClassGenerator
cg = new
DefaultClassGenerator();
cg.setPackageName(packagename);
cg.setVisibility(PUBLIC);
cg.setClassName(type.getSimpleName());
//
iterate the fields of the domain model type
for
(VariableElement field : getFields(type))
{
String
fieldname = field.getSimpleName().toString();
//
create and configure a generator for the field declaration
FieldGenerator
fg = new
DefaultInstanceFieldGenerator();
fg.setVisibility(PRIVATE);
fg.setTypeFrom(field);
fg.setName(fieldname);
fg.setLineIndenter(TAB_SINGLE);
//
add field generator to class generator
cg.addFieldGenerator(fg);
//
create and configure a generator for the field getter method
MethodGenerator
mgg = new
DefaultInstanceMethodGenerator();
mgg.setVisibility(PUBLIC);
mgg.setTypeFrom(field);
mgg.setName("get"
+ firstLetterToUpperCase(fieldname));
mgg.setMethodBody("return
"
+ fieldname + ";");
mgg.setLineIndenter(TAB_SINGLE);
//
add getter method generator to class generator
cg.addMethodGenerator(mgg);
//
create and configure a generator for the field setter method
MethodGenerator
mgs = new
DefaultInstanceMethodGenerator();
mgs.setVisibility(PUBLIC);
mgs.setName("set"
+ firstLetterToUpperCase(fieldname));
mgs.addParameterFrom(field,
field.getSimpleName());
mgs.setMethodBody("this."
+ fieldname + "
= "
+ fieldname + ";");
mgs.setLineIndenter(TAB_SINGLE);
//
add setter method generator to class generator
cg.addMethodGenerator(mgs);
}
//
add generator for import statements to class generator
cg.addImportStatementGenerators(
getImportManager().getImportStatementGenerators());
try
{
FileUtils.writeStringToFile(
CompilationUnitUtil.buildFile("src/gen/java",
packagename, typename),
cg.generate().toString());
}
catch
(GeneratorException | IOException e)
{
throw
new
TaskException("failure
generating class for "
+ type, e);
}
finally
{
//
restore formally import manager
restoreImportManager(backup);
}
}
return
false;
}
}
@Test
public
void
test() throws
IOException
{
Task task = new
Task(asSet(Person.class,
Data.class));
TaskRunner.run(task);
}
}
Example 1: NoDSL! - Domain Modelling with Java and Generating Software with jMDA
The
complete example
consists of a single
Java class with three inner classes. It
starts with import statements
for types and
methods of the jmda.core and jmda.gen libraries. Particularly the
static imports allow for quite dense coding in the remaining part of
the code.
The
code continues with the definition of the domain model. The domain
model is made up
from two very simple
Java type definitions, namely Person and Data.
Creating
a domain model could
hardly be easier.
The
model contains only a minimum amount of technical details. You
can type the few
lines
that
build up the model using
any text editor. If
you use a modern Java
IDE
you get the full power of Java source code editing including
syntax highlighting, code assistance, refactoring support and many
more for
your modelling activities.
With
jMDA there is no need for the definition of a new proprietary
language and the generation of a special tool set for that language!
Picture
1 shows a visual representation of the model. The
UML class diagram was reverse engineered from the source code with
the marvellous, free
ObjectAid
eclipse plugin.
Now
back to the example source code. The definition of the domain model
is directly followed by the inner class Task.
In
a sense tasks are the working horses for jMDA. To understand the
example have a closer look at the execute
method.
The
execute
method first iterates
over the business
objects of the domain model: Person and Data. Both
model elements are represented by TypeElement instances. For
each domain model element the
execute
method creates
and configures a ClassGenerator object. It configures the package, the visibility and the class name
for the element.
Then
it examines the declared fields of the domain model element. For each
field it performs three steps: instantiate and configure a
generator for
Each
generator
is instantiated
with a default implementation of the respective interface. The
default implementations provide a reasonable behaviour that can be
configured to meet the particular requirements following the
convention over configuration pattern.
Finally
the declared generator objects are added to the class generator. The
class generator takes care of things
like the proper
order
of the added generators during the ClassGenerator.generate
step.
There
are only a few things that might need further explanation. The first
thing is the import statements management. Simple Java
source code generators
always produce full qualified type names and don't care about
reasonable import statement management. This
way
they repeatedly produce unnecessary qualified names that make the
code very verbose and hard to read and understand.
jMDA
provides an import management facility to overcome this problem.
Usually you create an import manager for each Java type that is to be
generated with newImportManager(...).
Once an import manager has been created it transparently takes care
about the types that are used in the generated code. Look at the
generated import statements in the example.
In
general the generation of Java types may be nested: You start with
the generation of one Java type and before you finish that another
nested Java type generation is started. To avoid side effects between
multiple import manager instances it is strongly recommended to
backup a possibly existing import manager before creating a new one
and to restore the backed up manager when you are finished with the
generation of a Java type (restoreImportManager(...)).
Finally
CompilationUnitUtil helps with properly writing the generated Java
types (that are StringBuffers until then) as .java files into the
file system. If necessary it builds up the package folder hierarchy
transparently.
That's
it! Now
let's have a look at the output of the class generators. First part
is Person.java:
package
de.jmda.sample.tutorial;
import
java.util.Date;
import
java.util.List;
public
class
Person
{
private
String firstname;
private
String lastname;
private
Date birthdate;
private
List<Data> dataItems;
public
String getFirstname()
{
return
firstname;
}
public
void
setFirstname(String firstname)
{
this.firstname
= firstname;
}
public
String getLastname()
{
return
lastname;
}
public
void
setLastname(String lastname)
{
this.lastname
= lastname;
}
public
Date getBirthdate()
{
return
birthdate;
}
public
void
setBirthdate(Date birthdate)
{
this.birthdate
= birthdate;
}
public
List<Data> getDataItems()
{
return
dataItems;
}
public
void
setDataItems(List<Data> dataItems)
{
this.dataItems
= dataItems;
}
}
Example 2: Generated Person Bean
And
here comes Data.java:
package
de.jmda.sample.tutorial;
public
class
Data
{
private
String dataItem;
public
String getDataItem()
{
return
dataItem;
}
public
void
setDataItem(String dataItem)
{
this.dataItem
= dataItem;
}
}
Example
3: Generated Data
Bean
No comments:
Post a Comment