Created
July 2, 2018 23:34
-
-
Save edefazio/144a03a5cda47dc9663c9a46a9a2533f to your computer and use it in GitHub Desktop.
demonstrate using $macros to modify _class models and reduce boilerplate code
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package tutorial; | |
import draft.java.*; | |
import draft.java.file._export; | |
import draft.java.macro.*; | |
import junit.framework.TestCase; | |
import java.util.*; | |
/** | |
* NOTE: in _draft, we use the "_" prefix to mean "model of a ..." (i.e. _class is "model of a class") | |
* we use the "$" prefix to mean "template of a ..." or "macro for a ...", (i.e. $get is "macro for a get method") | |
* | |
* using these prefixes for code readability, to quickly visually distinguish between | |
* "regular code" and _draft abstractions for building and customizing code | |
*/ | |
public class _draft_2_$macro | |
extends TestCase { | |
/** | |
* $macros are reuseable code to analyze and mutate models (_class, _enum, _methods) | |
* (great for boilerplate code) | |
* | |
* You can apply macros directly to the _class model | |
*/ | |
public void testApply$MacroAfterLoading() { | |
@__package("example.tutorial.draft2") | |
@__import({}) | |
@_public | |
class L { | |
@_final | |
int x; | |
int y; | |
int z; | |
} | |
_class _cmac = _draft._class(L.class); | |
//apply the $autoCtor, $setFluent and $get macros to a _class | |
_cmac = $autoCtor.to( $setFluent.to( $get.to( _cmac ) ) ); | |
_export.toRelativeDir(_cmac, "generated" ); | |
} | |
/** | |
* We can use the @_macro annotation will apply one or more macros to a _class when it is loaded | |
* (and the annotation will be removed) | |
*/ | |
public void testApply$macrosViaAnnotations(){ | |
@__package("example.tutorial.draft2") | |
@_public | |
@__import({}) | |
@_macro({$get.class, $setFluent.class, $autoCtor.class}) | |
class L2{ | |
@_final int x; | |
int y; | |
int z; | |
} | |
_class _cam = _draft._class(L2.class); | |
//make sure the $autoCtor created a constructor including a final (non initialized) field x | |
assertTrue(_cam.getCtor(0).getParam(0).getType().is(int.class)); | |
assertEquals( AST.stmt("this.x = x;"), _cam.getCtor(0).getStmt(0)); | |
//make sure the macro created getters | |
assertTrue( _cam.getMethod("getX").getType().is(int.class)); | |
assertTrue( _cam.getMethod("getY").getType().is(int.class)); | |
assertTrue( _cam.getMethod("getZ").getType().is(int.class)); | |
//make sure the macro created setters | |
//No setter for x (it's a final field) | |
assertTrue( _cam.getMethod("setY").getParam(0).getType().is(int.class)); | |
assertTrue( _cam.getMethod("setZ").getParam(0).getType().is(int.class)); | |
_export.toRelativeDir(_cam, "generated" ); | |
} | |
/** it is easy to create and apply your own reuseable custom $macro to apply it any code (via the @_macro*/ | |
public void testCustom$Macro(){ | |
@__package("example.tutorial.draft2") | |
@__import({}) | |
@_macro( $serialVersionUID.class ) | |
@_public | |
class CM { | |
public int a; | |
public String name; | |
transient Map m; | |
} | |
_class _c = _draft._class(CM.class); //the _draft API will run the @_macro | |
//now verify that the $serialVersionUID macro created a field | |
_field _f = _c.getField("serialVersionUID"); | |
assertTrue( _f.isStatic() ); | |
assertTrue( _f.isFinal() ); | |
assertTrue( _f.hasInit() ); | |
assertTrue( _f.getType().is(long.class) ); | |
_export.toRelativeDir(_c, "generated" ); | |
} | |
/** | |
* HERE IS OUR CUSTOM MACRO | |
* | |
* simple Reuseable macro for generating the serialVersionUID | |
* the top 32 bits of the 64-bit long is the hashcode of the fully qualified class name | |
* the bottom 32 bits is the hash of the types/names of all non-static non transient member fields of a class | |
* | |
* https://stackoverflow.com/questions/285793/what-is-a-serialversionuid-and-why-should-i-use-it | |
*/ | |
public static class $serialVersionUID { | |
public static _type to( _type _t ){ | |
if( _t instanceof _class){ | |
_class _c = (_class)_t; | |
//list non-static, non-transient fields | |
List<_field> _fs = _c.listFields(f -> !f.isStatic() && !f.isTransient() ); | |
//organize the fields in Set/order | |
Set<_field> sfs = new HashSet<_field>(); | |
// what matters if the NAME and TYPE of all candidate fields in some (well defined) order | |
// the modifiers, init values are not important from a serialization perspective | |
// (assuming they are non-static non-transient) | |
_fs.forEach(f -> sfs.add(_field.of(f.getType() + " " + f.getName()))); | |
// the first 32 bits is the hashcode of the (String) fully qualified class name | |
long id = (0L + Objects.hashCode( _t.getFullName() )) << 32; | |
// the 2nd 32 bits is the hashcode of the field name, | |
// for brevity, lets just use a 32-bit hashcode of the ordered set of fields | |
int fieldsHash = Objects.hashCode( sfs ); | |
id = id + fieldsHash; | |
_field _f = _field.of("public static final long serialVersionUID = "+id+"L"); | |
_c.addOrReplace(_f); | |
} | |
return _t; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
uncommented version