Created
July 2, 2018 22:08
-
-
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
This file contains hidden or 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.*; | |
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() ); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
un-commented version