Skip to content

Instantly share code, notes, and snippets.

@edefazio
Created July 2, 2018 22:08
Show Gist options
  • Save edefazio/c660bfdfb400775f9bc5a19ff4437da8 to your computer and use it in GitHub Desktop.
Save edefazio/c660bfdfb400775f9bc5a19ff4437da8 to your computer and use it in GitHub Desktop.
Demonstrate _draft .java code generation, dynamic compilation, loading and using on the fly code
package tutorial;
import draft.java.*;
import draft.java.file.*;
import draft.java.runtime._javac;
import draft.java.runtime._proxy;
import junit.framework.TestCase;
import java.util.List;
/**
* Explain the main features of _draft API
*
* (1) Build the (_class, _enum,...) model, using one of the (3) approaches:
* <UL>
* <LI>From a String {@link #testOfString()}
* <LI>Via a builder {@link #testClassViaBuilder()}
* <LI>Loading the .java source from a runtime Class {@link #testSourceOfRuntimeClass()}
* </UL>
*
* (2) analyze / query (_class, _enum, _method, _field...) {@link #analyze(_class)}
* (3) modify the (_class, _enum, _method, _field...) {@link #modify(_class)}
* (4) print the .java source code to a String toString()
* (5) to export to a .java file {@link _export#toRelativeDir(_file, String)}
* (6) compile / load a new Class and create a new instance at runtime {@link #dynamicClassInstanceProxy(_class)}
* (7) compile and export Class files (w/o class loading) {@link #javacAndExportClass(_class)}
*/
public class _draft_1 extends TestCase {
/** We can build _draft models (_class, _enum...) from Strings*/
public void testOfString(){
// (NOTE: NOT THE PREFERRED METHOD of building models)
// ...Using an escaped string as the API has MANY disadvantages:
// its the worse possible API (no checking, hard to debug, "anything goes")
// it doesn't scale at all (when code gets big; it's a nightmare)
// it is hard to read and modify (especially when nesting lambdas or inside loops or strings)
// escaping strings and match curly braces, parenthesis / etc./ is tedious
_class _c = _draft._class("package example.tutorial.draft1;",
"public class C{",
" public static final int ST = 103;",
" public static void main(String[] args){",
" System.out.println(\"Hello\");",
" }",
"}");
/** with the _c model loaded, we can analyze it (_class, _methods, _fields, _params etc.) */
assertEquals( "example.tutorial.draft1", _c.getPackageName() );
assertEquals( "C", _c.getName() );
_field _f = _c.getField("ST");
assertTrue( _f.getModifiers().is("public static final"));
assertEquals("ST", _f.getName());
assertEquals( AST.expr( "103"), _f.getInit() );
_method _m = _c.getMethod("main");
assertTrue( _m.getParam(0).getType().is(String[].class));
_param _p = _m.getParam(0);
assertEquals( _param.of("String[] args"), _p);
assertEquals( _params.of("String[] args"), _c.getMethod("main").getParams());
assertEquals( AST.stmt("System.out.println(\"Hello\");"), _m.getStmt(0));
/** we can mutate the _class model to add, change or remove parts */
_c.getMethod("main").getParam(0).setFinal(); //change a method parameter on main to final
_c.getMethod("main").addCode("System.out.println(\"Bye\");"); //modify a method
_c.remove("ST"); //remove the ST field
_c.field("public int f=100;"); //add a new field
_c.method("public int getF(){ return f;}"); //add a method
//Print the source of the _class or any component
System.out.println(_c); //print the .java source of a model
System.out.println( _c.getMethod("main") ); // .java source for JUST the main method
/** compile/load and use a dynamically created model*/
_draft._proxy( _c) //dynamically compile, load and create a new instance
.main(); //call the main() method on the instance
/** export the .java code to a relative directory*/
String fileName = _export.toRelativeDir(_c, "generated" );
System.out.println( fileName );
}
/** A Streamlined method of creating a Class (in the correct package) using convenience methods */
public void testClassViaBuilder() {
_class _c = _draft._class("example.tutorial.draft1.C2")
.field("public static final int ST = 103")
.main("System.out.println(\"Hello\");"); // the body of the main method
/** building dynamic instances with _proxy */
dynamicClassInstanceProxy( _c );
_export.toRelativeDir(_c, "generated" );
}
/**
* We can build models via ANY EXISTING CODE by it's Class including:
* <UL>
* <LI>Inner Classes (like below) (_class, _enum, _interface, _annotation)</LI>
* <LI>Any Top-level Classes (_class, _enum, _interface, _annotation)</LI>
* <LI>Local (to the method) Classes (_class)</LI>
* </UL>
*/
public void testSourceOfRuntimeClass() {
// _draft can RE-PURPOSE ANY Java CODE
// it is more intuitive to write code normally in your editor of choice
// with code completion / syntax highlighting & real time compilation
// then let _draft read in the models
_class _c = _draft._class(Inner.class) // build _class model from Inner class's .java source
.packageName("example.tutorial.draft1")
.setPublic()
.removeImports()
.setStatic(false);
_draft._proxy(modify(_c)) //create an instance
.main(); //call the main method
_export.toRelativeDir(_c, "generated");
}
/** javadocs are retained*/
private static class Inner{
public static void main(String[] args){
System.out.println("Hello");
}
}
/**
* When reading in existing Classes, annotations can change the code when _draft reads the code in
* Also draft @annotations like:
* <UL>
* <LI>{@link __package}</LI> the the package of the Class
* <LI>{@link __import}</LI> specify the imports
* <LI>{@link _static}</LI> make a class / method / field static
* <LI>{@link _non_static}</LI> make a class /method /field non static
* <LI>{@link _public}</LI> make the accessibility public for class/method/field
* <LI>{@link _final}</LI>
* <LI>{@link _replace}</LI> replace some target String with a replacement
* </UL>
* signify to the _draft API that sm
*/
public void testLocalClassWithAnnotations(){
// Here we define a "Local Class"
// the annotations set the package and imports
@__package("example.tutorial.draft1")
@__import({})
@_public
class LocalAnno {
@_static
public void main(String[] args){
System.out.println("Hello");
}
}
// we can directly LOAD the contents of a Local Class into a model
// the _draft @annotations (like @__package, @__import)
// signify changes that are made to the _draft, when loaded
// the annotations are removed after the _class model is processed
_class _c = _draft._class( LocalAnno.class );
// if we don't need the Class at runtime; we can call the javac compiler
// to verify the code works, and just export Class files
// (without creating a new classLoader & loading the classes)
javacAndExportClass(_c );
//export the .java source files
_export.toRelativeDir(_c, "generated" );
}
/**
* regardless of HOW we load the _class model
* it is logically the same
* the _class is hierarchial for analysis
* also sub-components like _method, _param, _params
*/
public static _class analyze(_class _c){
assertEquals( "example.tutorial.draft1", _c.getPackageName() );
_method _m = _c.getMethod("main");
assertTrue( _m.getParam(0).getType().is(String[].class));
_param _p = _m.getParam(0);
assertEquals( _param.of("String[] args"), _p);
assertEquals( _params.of("String[] args"), _c.getMethod("main").getParams());
assertEquals( AST.stmt("System.out.println(\"Hello\");"), _m.getStmt(0));
return _c;
}
/** _class, _method, _field, _ctor, etc. models are mutable/compose-able */
public static _class modify(_class _c){
_c.getMethod("main").getParam(0).setFinal(); //modify a method parameter
_c.getMethod("main").addCode("System.out.println(\"Bye\");"); //modify a method
_c.field("public int f=100;"); //add a field
_c.method("public int getF(){ return f;}"); //add a method
return _c;
}
/**
* To compile (and NOT load into a _classLoader) a _class at runtime, use {@link _javac#of(_type...)}
* It will call the javac compiler at runtime and create Class files. returning a {@link _classFiles}
*
* the _classFiles are runtime .class files.
* they can be exported to the file System using {@link _export#toRelativeDir(_type, String)}
*
* @param _c _class to be compiled to a Class then exported to the fil
*/
public void javacAndExportClass( _class _c ){
//a single _type, can be many Classes (i.e. Nested classes)
_classFiles _cfs = _javac.of(_c );
//export the class files
List<String> classFileNames =
_export.toRelativeDir( _cfs, "out/test/generated");
//print the names of the .class files created
System.out.println( classFileNames );
}
/**
* A _proxy can compile the _class into a Class, load the Class into a _classLoader,
* create new instances, and provides an API for accessing fields and methods
*
* the _proxy maintains the dynamically built instance as a field, accessed directly by: {@link _proxy#instance}
*
* each _proxy has a unique {@link draft.java.runtime._classLoader},
*
* and provides an api to accesss fields and properties
* via {@link _proxy#get(String)}
*/
public void dynamicClassInstanceProxy(_class _c ){
_proxy _p1 = _draft._proxy(_c); //compile & load a new Class & create new instance
// Modify the _class "some.pkg.C" & ensure both versions can be loaded and used at the same time
_c.field("public static final int VAL=10101;");
//BECAUSE each _proxy has a separate _classLoader,
// you can have two identically named Classes at the same time
_proxy _p2 = _draft._proxy(_c);
assertEquals(10101, _p2.get("VAL")); //
/** _p2 instance is a different Class in a different classLoader */
assertTrue( !_p1.equals(_p2) );
assertTrue( !_p1.instance.getClass().equals(_p2.instance.getClass()) );
assertNotSame( _p1.instance.getClass().getClassLoader(), _p2.instance.getClass().getClassLoader() );
/** To create another instance from the already compiled/loaded Class/_classLoader,
* call the proxyAnother() method on the proxy */
_proxy _ap1 = _p1.proxyAnother();
assertEquals( _ap1.instance.getClass(), _p1.instance.getClass());
assertEquals( _ap1.instance.getClass().getClassLoader(), _p1.instance.getClass().getClassLoader() );
}
}
@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