draft.java is a developer-friendly tool to build, access, change, add, remove, and analyze .java source code; then optionally compile, load and use/run the code from within a program. it's a "code generator", and much MUCH more. (it's like having a DOM for Java source code). To get up to speed quickly with simple code generation, here is JavaPoet examples done in the draft.java API for you to compare and contrast
draft.java runs on JDK 8 or later.*(runtime compilation requires the JDK rather than the JRE) draft.java depends only on the (fantastic) JavaParser core (3.13.2 or later) library for transforming Java source code into ASTs. draft.java also integrates well with the JShell environment for "live" code generation / modification and scripting. draft.java can generate source code for ANY version of Java (Java 1.0 - 12).
<dependency>
<groupId>draft</groupId>
<artifactId>draft.java</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>com.github.javaparser</groupId>
<artifactId>javaparser-core</artifactId>
<version>3.13.2</version>
</dependency>
the most convenient way to build a DOM-like _type is by pass in an existing Class to the _type.of(Class) method (the .java source of the Class is modeled)
public class Point { @Deprecated int x, y; }
_class _c = _class.of(Point.class); //_class _c represents the source of Point.java
public interface Drawable{ public void draw(); }
_interface _i = _interface.of(Drawable.class); //_interface _i represents the source of Drawable.java
public enum State{ STABLE, REDRAW; }
_enum _e = _enum.of(State.class); //_enum _e represents the source of State.java
public @interface Refresh{ int value() default 0; }
_annotation _a = _annotation.of(Refresh.class); //_annotation _a represents the source of Refresh.java
you can "manually" build a _type (_class, _enum, _interface, _annotation) via the simple API
//verify that building _types via Class is equivalent to building via component / Strings
assertEquals(_c, _class.of("Point").fields("@Deprecated int x,y;"));
assertEquals(_i, _interface.of("Drawable").method("public void draw();"));
assertEquals(_e, _enum.of("State").constants("STABLE", "REDRAW"));
assertEquals(_a, _annotation.of("Refresh").element("int value() default 0;"));
accessing members of _types
_field _x = _c.getField("x");
_method _m = _i.getMethod("draw");
_enum._constant _ec = _e.getConstant("STABLE");
_annotation._element _ae = _a.getElement("value");
accessing lists of like members of each _type with .listXXX()
List<_field> _fs = _c.listFields(); //list all fields on _c
List<_method> _ms = _i.listMethods(); //list all methods on _i
List<_enum._constant> _ecs = _e.listConstants(); //list all constants on _e
List<_annotation._element> _aes = _a.listElements(); //list all elements on _a
each _type can selectively list members based on a lambda with .listXXX(Predicate)
_fs = _c.listFields(f -> f.isPrivate()); //list all private fields on _c
_ms = _i.listMethods(m -> m.isDefault()); //list all default methods on _i
_ecs = _e.listConstants(c -> c.hasArguments()); //list all constants with constructor arguments on _e
_aes = _a.listElements(e -> e.hasDefault()); //list all elements with defaults on _a
we can also list all (generic) members with .listMembers(Predicate)
List<_model._member>_mems = _c.listMembers(m -> m.hasAnnos()); //list all annotated members
change _types & members
changes can be applied to the _type or members are reflected in the source of the _class
//apply changes to top level _type
_c.implements(Serializable.class); //add Serializable import & implement Serializable to _c
_i.packageName("graphics.draw"); //set Package Name on _i
_a.setTargetRuntime(); //add the @Target(ElementType.TYPE) annotation
//apply changes to individual members
_c.getField("x").init(0); //initialize value of field x to be 0
_c.getField("y").init(0); //initialize value of field y to be 0
_types allow lambdas to simplify iteration over members with forXXX(Consumer)
_c.forFields(f->f.setPrivate()); //set ALL _fields to be private on _c
_c.forFields(f->f.removeAnnos(Deprecated.class)); //remove Deprecated annotation from ALL fields of _c
_e.forConstants(c->c.addArgument(100)); //add constructor argument 100 for ALL constants of _e
_i.forMembers(m -> m.annotate(Deprecated.class)); //apply @Deprecated to all members (fields, methods) of _i
_types can also selectively change members easily with forXXX(Predicate, Consumer)
_c.forFields(f->f.isStatic() && f.hasInit(), f->f.setFinal()); //set all static initialized fields as final
_i.forMethods(m->m.isStatic(), m->m.setPublic()); //select all static methods & make them public
adding members to _types
each _type provides methods for adding members appropriate for the underlying _type
_c.field("public static final int ID = 1023;");
_c.method("public int getX(){ return this.x; }");
_e.field("final int count;"); //add a field to _enum _e
_i.field("public static final int VERSION = 12;");
_e.constant("INVALID(100);"); //add a new constant to _e
_a.element("String name() default \"\";"); //add a new annotation element to _a
_c.field("/** temp field */ public String temp;");
_c.method("public String getTemp() { return temp; }");
_types provide the ability to "pass code around" via lambda bodies and anonymous objects. (this technique allows source code to be parsed / checked and presented/colorized in real time by the IDE, and not treated like escaped plain text... much easier to read, debug, and modify.)
/* this method body is defined by a lambda body */
_c.method("public int getY()", (Integer y)->{ return y; });
/* the riseRun method is defined on an anonymous object & added to the _class */
_c.method( new Object(){ int x, y; //these exist to avoid compiler errors
public double riseRun(){
return y * 1.0d / x * 1.0d;
}
});
removing members
remove members to the _type with removeXXX(...)
_c.removeField("temp"); //remove field named "temp" from _c
_c.removeMethods(m->m.getName().equals("getTemp")); //remove all methods with name "getTemp" from _c
_e.removeConstant("INVALID"); //remove constant named "INVALID" from _e
_i.removeFields(f-> f.isStatic()); //remove all static fields on _i
macros can automate repetitive manual coding tasks when building _types. use the built in ones -or- easily build your own
_c.apply(_autoSet.$,_autoEquals.$,_autoHashCode.$); //adds set methods & equals, hashCode methods
_e.apply(_autoConstructor.$); //generate the appropriate constructor for the enum based on fields
to uniformly collect or operate on _types, we use the _type interface.
List<_type> _ts = new ArrayList<>();
_ts.add(_c);
_ts.add(_i);
_ts.add(_e);
_ts.add(_a);
//here we can operate on all _types (_class _c, _enum _e, _interface _i, and _annotation _a)
_ts.forEach( t-> t.packageName("graphics.draw") ); //change the package name for ALL _types
for testing and debugging support, you can always use toString() to get the .java source code for a _type
System.out.println(_c);
//PRINTS:
package graphics.draw;
import java.io.Serializable;
public class Point implements Serializable{
private int x = 0, y = 0;
public int getX() {
return this.x;
}
public int getY() {
return y;
}
public double riseRun() {
return y * 1.0d / x * 1.0d;
}
public void setX(int x) {
this.x = x;
}
public void setY(int y) {
this.y = y;
}
public boolean equals(Object o) {
if (o == null) {
return false;
}
if (this == o) {
return true;
}
if (getClass() != o.getClass()) {
return false;
}
Point test = (Point) o;
boolean eq = true;
eq = eq && this.x == test.x;
eq = eq && this.y == test.y;
return eq;
}
public int hashCode() {
int hash = 5;
int prime = 3;
hash = hash * prime + x;
hash = hash * prime + y;
return hash;
}
}
draft.java doesn't stop at just building or modifying source code, it also provides "instant feedback" by compiling & loading the dynamically built code, and allowing the code to be loaded and used. (This gives developers a tight feedback loop, to generate build, compile and use or test code in a single program without a restart)
compiling _types with _javac
to compile the .java code represented by one or more _type (_class, _enum, _interface, _annotation) use _javac.of(...):
//we can verify the generated code is valid Java, call the javac compiler
_classFiles _cfs = _javac.of(_c); //compile & return the _classFiles (bytecode) for _c _class
_classFiles _allCfs = _javac.of(_c, _i, _a, _e); //compile & return _classFiles for all _types
we can pass in options to the javac compiler via a builder _javac.options().XXX
_classFiles _allCfs = _javac.of(
_javac.options()
//https://stackoverflow.com/questions/44067477/drawbacks-of-javac-parameters-flag
.parameterNamesStoredForRuntimeReflection()
.terminateOnWarning(), //strict compile: fail if any warning is found
_c, _i, _a, _e); //compile & return the _classFiles (bytecode) for the (_c, _i, _a, and _e) _types
compile & load in one step via _project
to compile and load (in a new ClassLoader) the .java code represented by one or more _class, _enum, _interface... _types, use _project:
// we can create a _project of a single _type
_project _proj = _project.of(_c);
List<Class> loadedClasses = _proj.listClasses(); //list all loaded classes
// _project constructor optionally accepts _javac options
_proj = _project.of( _javac.options().terminateOnWarning(), _c, _a, _i, _e);
_project gives you rudimentary access to individual Classes
assertEquals(1023, _proj.get(_c, "ID")); //get the value of a static field value from the Point.class
Object aPoint = _proj._new(_c); //create a new instance of the Point.class
using a _proxy to interact with dynamic instances
a _proxy simplifies the using a _class instances.
_proxy _p = _proj._proxy(_c ); //build a proxy from the Point.class created by _class _c
field or property values on an instance can be retrieved with .get(...).
/* _proxy simplifies accessing fields or getters on the proxied instance */
assertEquals(1023,_p.get("ID")); //get static field value
assertEquals(0,_p.get("x")); //call get method (note: x is private field)
field or property values on an instance can be updated with .set(...).
_p.set("x",100).set("y",200); //set(...) will call set methods since x, y are private
static or instance methods can be invoked with .call(...)
assertEquals(200.0d / 100.0d, _p.call("riseRun"));
the object instance wrapped by a _proxy is available via the .instance field
assertEquals("Point", _p.instance.getClass().getCanonicalName());
a new instance of the _class can be created with _new(...) on the proxy, or we can create from the _project._proxy(...)
_proxy _p2 = _p._new().set("x",100).set("y",200);
assertEquals(_p2, _proj._proxy(_c).set("x",100).set("y",200));
we can use these (2) _proxy instances (_p, _p2) to test the macro generated equals() and hashcode() methods
assertEquals(_p.instance, _p2.instance); //instance equality check
assertEquals(_p.instance.hashCode(),_p2.instance.hashCode()); //instance hashCode equality check
/* _proxy instances try to operate as "transparently" as possible */
assertEquals(_p, _p2); // _proxy delegates equals() method to the .instance method
assertEquals(_p.hashCode(), _p2.hashCode()); //_proxy delegates the hashCode() method to instance hashCode() method
when we are satisfied with the .java code and .class that was generated, we can export it
/* export Point.java to a file & verify where it was written to */
assertTrue(_io.out("C:\\temp\\",_c).contains("C:\\temp\\Point.java"));
/* export Point.class to a file & verify where it was written to */
assertTrue(_io.out("C:\\temp\\",_proj.classFiles()).contains("C:\\temp\\Point.class"));
/* export Point.java & Point.class to files & verify where both files were written to */
assertTrue(_io.out("C:\\temp\\",_proj).containsAll("C:\\temp\\Point.class", "C:\\temp\\Point.java"));