-
Notifications
You must be signed in to change notification settings - Fork 0
Home
Welcome to my Java code generator project. There are three steps to generating code:
- Writing the setup file
- Writing the source files
- Running the code generator
- Specify all code generation source files (xml)
- Specify the target directories for the generated code
<Setup>
<Source name="user.xml" type="RESOURCE" />
<Source name="data.xml" type="RESOURCE" />
<Target name="USER" directory="target/test/java/user" />
<Target name="DATA" directory="target/test/java/data" />
</Setup>The source files contain the generated code definitions. They are read as FILE or RESOURCE in the usual way. The target directories are possible destinations for generated code. Each source file specifies the target to use (by its name).
The source files are xml files containing any number of bean, enum, interface, etc... definitions as you wish. The definitions are loaded in such a way that the order they reference each other does not matter, even between separate source files. The root element in each xml file is calledModel, and has the following attributes:
- The
idattribute associates a unique id with each object generated, and should be significantly different in each source file - The
packageattribute is applied to all definitions in the file - The
targetattribute must reference a target named in the setup file, and specifies the directory to write code to
<Model id="2000" package="com.generated.data" target="DATA">
... beans, enums, interfaces, etc ...
</Model>ExecutorService.
The generator can perform a number of operations in parallel, so it is worth providing a degree of concurrency.
ISetup setup = new SimpleResourceReader().read("setup.xml", Setup.class);
ExecutorSerivce service = Executors.newFixedThreadPool(10);
JavaModelGenerator generator = new JavaModelGenerator(setup, service);
generator.generate();- Beans - plain old java objects
- Enums - enum objects, with support for simple fields
- Interfaces - standard interfaces
- Aliases - alias fully qualified class names to simple names for clarity
- Validators - provide validation for arguments and fields
- Comparators - generate comparators to compare one object to another
- Adaptors - generate adaptors to copy one object to another
- Executable Beans - beans that represent remote methods
- Data Stores - database and memory storage and access
There are a few important notes regarding the usage of this API:
- Generics - simple usage of generics is fully supported
- Naming - all types and aliases must be uniquely named
XML Source
<Bean name="Person">
<Field name="name" type="String" />
<Field name="dateOfBirth" type="Date" />
</Bean>Generated Code
public class Person implements IPerson {
private String name = null;
private Date dateOfBirth = null;
public Person() {
}
public Person(String name, Date dateOfBirth) {
setName(name);
setDateOfBirth(dateOfBirth);
}
public Person(IPerson clone) {
setName(clone.getName());
setDateOfBirth(clone.getDateOfBirth());
}
public String getName() {
return name;
}
public Date getDateOfBirth() {
return dateOfBirth;
}
public void setName(String name) {
this.name = name;
}
public void setDateOfBirth(Date dateOfBirth) {
this.dateOfBirth = dateOfBirth
}
}equals(), hashCode() and toString() methods.
Generated Code
@Override
public int hashCode() {
HashCodeBuilder builder = new HashCodeBuilder();
builder.append(getName());
builder.append(getDateOfBirth());
return builder.toHashCode();
}
@Override
public boolean equals(Object object) {
if (object == this) {
return true;
}
if (!this.getClass().equals(object.getClass())) {
return false;
}
IPerson compare = (IPerson) object;
EqualsBuilder builder = new EqualsBuilder();
builder.append(this.getName(), compare.getName());
builder.append(this.getDateOfBirth(), compare.getDateOfBirth());
return builder.isEquals();
}
@Override
public String toString() {
ToStringBuilder builder = new new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE);
builder.append(getName());
builder.append(getDateOfBirth());
return builder.toString();
} compareTo() method.
Generated Code
@Override
public int compareTo(IPerson compare) {
CompareToBuilder builder = new CompareToBuilder();
builder.append(this.getName(), compare.getName());
builder.append(this.getDateOfBirth(), compare.getDateOfBirth());
return builder.toComparison();
}Disable comparable by adding an attribute.
XML Source
<Bean name="Person" comparable="false">
...
</Bean>immutable="true". All fields are final and only set from constructors.
Setters are generated and return a copy of the object with the setter field modified. Just add the immutable attribute to make a bean immutable!
XML Source
<Bean name="Person" comparable="false">
<Field name="name" type="String" />
<Field name="dateOfBirth" type="Date" />
</Bean>Generated Code
public class Person implements IPerson {
private final String name;
private final Date dateOfBirth;
public Person(String name, Date dateOfBirth) {
this.name = name;
this.dateOfBirth = dateOfBirth;
}
public Person(IPerson clone) {
this.name = clone.getName();
this.dateOfBirth = clone.getDateOfBirth();
}
public String getName() {
return name;
}
public Date getDateOfBirth() {
return dateOfBirth;
}
}<Bean name="Person">
<Extends type="INamedPerson" />
<Field name="name" type="String" />
<Field name="dateOfBirth" type="Date" />
</Bean>
<Interface name="INamedPerson">
<Method name="getName" returnType="String" />
</Interface>XML Source
<Enum name="HttpMethod">
<Constant name="GET" />
<Constant name="POST" />
<Constant name="OPTIONS" />
<Constant name="HEAD" />
<Constant name="PUT" />
<Constant name="DELETE" />
<Constant name="TRACE" />
<Constant name="CONNECT" />
<Constant name="PATCH" />
</Enum>Generated Code
public enum HttpMethod {
/** The GET constant. */
GET,
/** The POST constant. */
POST,
/** The OPTIONS constant. */
OPTIONS,
/** The HEAD constant. */
HEAD,
/** The PUT constant. */
PUT,
/** The DELETE constant. */
DELETE,
/** The TRACE constant. */
TRACE,
/** The CONNECT constant. */
CONNECT,
/** The PATCH constant. */
PATCH;
}Enums can extend interfaces, existing or generated.
XML Source
<Enum name="HttpStatusCode">
<Extends type="IHttpStatusCode" />
<Constant name="OK">
<Field name="code" type="int" value="200" />
</Constant>
<Constant name="NOT_FOUND">
<Field name="code" type="int" value="404" />
</Constant>
<Constant name="INTERNAL_SERVER_ERROR">
<Field name="code" type="int" value="500" />
</Constant>
</Enum>
<Interface name="IHttpStatusCode">
<Method name="getName" returnType="int" />
</Interface>Generated Code
public enum HttpStatusCode implements IHttpStatusCode {
/** The OK constant. */
OK(200),
/** The NOT_FOUND constant. */
NOT_FOUND(404),
/** The INTERNAL_SERVER_ERROR constant. */
INTERNAL_SERVER_ERROR(500);
/** The code field. */
private final int code;
private HttpStatusCode(int code) {
this.code = code;
}
/**
* Getter for the code field.
* @return the value of the code field.
*/
public int getCode() {
return code;
}
}
public interface IHttpStatusCode {
int getCode();
}You can even use basic generics in definitions!
XML Source
<Inteface name="ICache{K, V}">
<Method name="size" returnType="int" />
<Method name="isEmpty" returnType="boolean" />
<Method name="put" returnType="int">
<Parameter name="key" type="K" />
<Parameter name="value" type="V" />
</Method>
<Method name="get" returnType="V">
<Parameter name="key" type="K" />
</Method>
</Interface>Generated Code
public interface ICache<K, V> {
int size();
boolean isEmpty();
boolean put(K key, V value);
V get(K key);
}We can avoiding the need for fully qualified names in definitions:
XML Source
<Bean name="Time">
<Field name="unit" type="java.util.concurrent.TimeUnit" />
<Field name="value" type="long" />
</Bean>By adding an alias, we can shorten all references to TimeUnit:
XML Source
<Alias name="TimeUnit" type="java.util.concurrent.TimeUnit" />
<Bean name="Time">
<Field name="unit" type="TimeUnit" />
<Field name="value" type="long" />
</Bean>- not null on any object (defaults to true if not specified - null is evil!)
- size of collection, list, set, map
- size of string
- pattern of string
- range of Byte, Short, Integer, Long, Float, Double
- range of byte, short, int, long, float, double
Some basic validator examples:
XML Source
<Validator name="id" type="int" min="1" />
<Validator name="name" type="String" min="6" max="255/>
<Validator name="description" type="String" pattern="[a-zA-Z]+" />
<Validator name="age" type="short" min="0" max="120" />XML Source
<Comparator name="PersonComparator" type="Person">
<Field name="dateOfBirth" />
<Field name="name" />
</Comparator>Generated Code
@Override
public int compare(Person compare1, Person compare2) {
CompareToBuilder builder = new CompareToBuilder();
if (reverse) {
if (swap) {
builder.append(compare2.getName(), compare1.getName();)
builder.append(compare2.getDateOfBirth(), compare1.getDateOfBirth();)
} else {
builder.append(compare1.getName(), compare2.getName();)
builder.append(compare1.getDateOfBirth(), compare2.getDateOfBirth();)
}
} else {
if (swap) {
builder.append(compare2.getDateOfBirth(), compare1.getDateOfBirth();)
builder.append(compare2.getName(), compare1.getName();)
} else {
builder.append(compare1.getDateOfBirth(), compare2.getDateOfBirth();)
builder.append(compare1.getName(), compare2.getName();)
}
}
return builder.toComparison();
}XML Source
<Adaptor name="PersonAdaptor" from="Person" to="FacebookUser">
<Field from="name" />
<Field from="email" />
</Adaptor>Generated Code
@Override
public FacebookUser adapt(Person from) {
FacebookUser to = new FacebookUser();
to.setName(from.getName());
to.setEmail(from.getEmail());
return to;
}- The bean fields represent the parameters to the method call, and are typically basic types.
- The bean implements the IExecutableBean<R> interface, with R representing the return type of the method call. This type can be any object, ideally another bean.
XML Source
// We add the returnType attribute
// which denotes the return type of the executable method call
<Bean name="LoginUser" returnType="String">
<Field name="email" validator="email" />
<Field name="password" validator="password" />
</Bean>The resulting bean is just like any other, however it implements IExecutableBean which extends IBean.
Generated Code
public class LoginUser implements ILoginUser {
/** The email field. */
private String email = null;
/** The password field. */
private String password = null;
}
public interface ILoginUser extends IExecutableBean<Long>, Comparable<ILoginUser> {
int SERIALIZATION_ID = 1101;
...Currently, three serialization formats are supported:
XML Source
<JsonSerializer type="LoginUser" />
<XmlSerializer type="LoginUser" />
<DataSerializer type="LoginUser" />The resulting serialization code includes read and write methods.
Generated Code
public class LoginUserDataSerializer extends ObjectSerializer<ILoginUser> {
@Override
public ILoginUser readValue(IDataReader reader) throws IOException {
String param1 = reader.readObject(new StringSerializer(false));
String param2 = reader.readObject(new StringSerializer(false));
return new LoginUser(param1, param2);
}
@Override
public void writeValue(IDataWriter writer, ILoginUser object) throws IOException {
writer.writeObject(object.getEmail(), new StringSerializer(false));
writer.writeObject(object.getPassword(), new StringSerializer(false));
}
}| Map | A Map backed DataStore implementation, useful for caching or as an in-memory table. |
| Delegate | A simple delegate DataStore |
| Concurrent | A delegate DataStore that uses a ReadWriteLock on individual method calls. |
| Copy | A delegate DataStore that can be configured to clone values stored or retrieved. |
| Write Behind | A delegate DataStore that uses an ExecutorService to execute write methods asynchronously. |
| Cached Persister | A DataStore that delegates read methods to one DataStore (typically a cache), and write methods to another DataStore (typically a persister). |
- A Row Bean - the fields represent the full set of columns in the table
- A Key Bean - the fields are a subset of the row
It is important to specify a constructor in the key bean over the row bean. It is also desirable to make the key bean immutable, although not mandatory. A data store is defined by its row and key beans
XML Source
<Bean name="UserRow">
<Field name="id" type="int" />
<Field name="email" type="String" />
<Field name="password" type="String" />
</Bean>
<Bean name="UserKey">
<Constructor type="IUserRow" />
<Field name="id" type="int" />
</Bean>
<DataStore name="UserTable" element="UserRow" key="UserKey" />By default all the different data store variants are generated... To selectively disable generation, the following attributes are available.
sql="false"map="false"copy="false"delegate="false"concurrent="false"writeBehind="false"cachedPersister="false"
It is not currently possible to selectively disable generation of individual SQL implementations.
There are two interfaces that form the bases of all the data store implementationsIDataView and IDataStore. The view is
a read only version of the store, which extends it.
The view is read only
public interface IDataView<R> {
Lock getReadLock();
boolean exists();
int size();
boolean isEmpty();
boolean contains(R row);
List<R> getAll();
}The store extends the view and is both read and write
public interface IDataStore<R> extends IDataView<R> {
Lock getWriteLock();
void create();
void destroy();
void clear();
void remove(R row);
void add(R row);
void set(R row);
void addAll(Collection<? extends R> elements);
void setAll(Collection<? extends R> elements);
void removeAll(Collection<? extends R> elements);
}Notice that the data store looks very similar to a collection. The interfaces both expose access to a read and write lock, used for locking in concurrent versions of the data store.
It is not always necessary to store data in the database directly. The map data store provides an alternative which can be used to store data in memory. The map store delegates to an implementation of the Map interface.Generated Code
public class MapUserTable implements IUserTable {
private final Map<IUserId, IUser> map;
private final ReadWriteLock reentrantLock;
private final AtomicInteger autoIncrement;
private final Set<String> emailSet = new HashSet<String>();
public MapUserTable(Map<IUserId, IUser> map) {
if (map == null) {
throw new NullPointerException("map");
}
this.map = map;
this.reentrantLock = new ReentrantReadWriteLock(true);
this.autoIncrement = new AtomicInteger(0);
}
@Override
public void add(IUser element) {
if (emailSet.contains(element.getEmail())) {
throw new IllegalStateException("email already exists: " + element.getEmail());
}
UserId key = getKey(element);
if (map.containsKey(key)) {
throw new IllegalStateException("key already exists: " + key);
}
if (autoIncrement.get() < element.getId()) {
autoIncrement.set(element.getId());
}
map.put(key, element);
}
@Override
public IUser get(IUserId key) {
if (key == null) {
throw new NullPointerException("key");
}
return map.get(key);
}
}Generated Code
public class ConcurrentUserTable implements IUserTable {
private final IUserTable delegate;
public ConcurrentUserTable(IUserTable delegate) {
if (delegate == null) {
throw new NullPointerException("delegate");
}
this.delegate = delegate;
}
@Override
public IUser get(IUserId key) {
Lock lock = getReadLock();
lock.lock();
try {
return delegate.get(key);
} finally {
lock.unlock();
}
}
@Override
public void add(IUser element) {
Lock lock = getWriteLock();
lock.lock();
try {
delegate.add(element);
} finally {
lock.unlock();
}
}
}Generated Code
public class WriteBehindUserTable implements IUserTable {
private final IUserTable delegate;
private final ExecutorService executor;
public WriteBehindUserTable(IUserTable delegate, ExecutorService executor) {
this.delegate = delegate;
this.executor = executor;
}
@Override
public IUser get(final IUserId key) {
// Synchronous
Future<IUser> future = executor.submit(new Callable<IUser>() {
public IUser call() {
return delegate.get(key);
}
});
try {
return future.get();
} catch(Exception e) {
throw Throwables.propagate(e);
}
}
@Override
public void add(final IUser element) {
// Asynchronous (Write Behind)
executor.submit(new Runnable() {
public void run() {
delegate.add(element);
}
});
}
}Generated Code
public class CopyUserTable implements IUserTable {
private final boolean copyOnRead;
private final boolean copyOnWrite;
private final IUserTable delegate;
public CopyUserTable(IUserTable delegate, boolean copyOnRead, boolean copyOnWrite) {
if (delegate == null) {
throw new NullPointerException("delegate");
}
this.delegate = delegate;
this.copyOnRead = copyOnRead;
this.copyOnWrite = copyOnWrite;
}
@Override
public void add(IUser element) {
if (copyOnWrite) {
element = new User(element);
}
delegate.add(element);
}
@Override
public IUser get(IUserId key) {
IUser returnValue = delegate.get(key);
if (copyOnRead) {
returnValue = new User(returnValue);
}
return returnValue;
}
}Generated Code
public class CachedPersisterUserTable implements IUserTable {
private final IUserTable cache;
private final IUserTable persister;
public CachedPersisterUserTable(IUserTable cache, IUserTable persister) {
this.cache = cache;
this.persister = persister;
}
@Override
public void add(IUser element) {
cache.add(element);
persister.add(element);
}
@Override
public IUser get(IUserId key) {
return cache.get(key);
}
}Typically this data store is combined with the write behind data store to provide exceptional read and write access speeds. Reads are made directly in to the cache, and writes are written asynchonously. For thread safety the entire thing can be wrapped in a concurrent data store! To protect the data store against the mutation of added or accessed rows, a copy data store can also be used. This clonse the rows read from and written to the data store, so developer manipulation of the objects outside the store has no effect on its internal state.