Skip to content

Instantly share code, notes, and snippets.

@edefazio
Created July 2, 2018 23:34
Show Gist options
  • Save edefazio/144a03a5cda47dc9663c9a46a9a2533f to your computer and use it in GitHub Desktop.
Save edefazio/144a03a5cda47dc9663c9a46a9a2533f to your computer and use it in GitHub Desktop.
demonstrate using $macros to modify _class models and reduce boilerplate code
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;
}
}
}
@edefazio
Copy link
Author

edefazio commented Jul 2, 2018

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment