gwt and jsr 269's pluggable annotation processing api
TRANSCRIPT
ARNAUD TOURNIERPassionnate developper, trainer and architect at LTEConsulting.
Speaker at Devoxx, GWT.create, Paris/Toulouse JUG, etc...
Email : [email protected]
Twitter : @ltearno
Website : www.lteconsulting.fr
Full stack (x86_64 to JavaScript)
PRESENTATION AVAILABLE ONlteconsulting.fr/annotation-processing
And the demo project is available atgithub.com/ltearno/gwtcon-jsr269
PLUGGABLE ANNOTATION PROCESSING APICode generation in Java (source, byte-code and resource).
Integrated with the Java compiler.
Based on annotations. The developer's annotation processorreceives most of the program's AST.
USED FOR ?
USED FOR ?RPC stubs,Reflection stubs,UI generation,Configuration file generation,Code checkers, Build breakers,Dependency injection,Glue code generation,Your own needs !
IN THE GWT CONTEXT
IN THE GWT CONTEXTGWT 3 will abandon generators because the functionalityexists in standard Java : JSR 269.
Even with GWT 2.8 it makes sense to use it.
Causes migration problems, with most of the time quickresolution.
GOOD POINTS
GOOD POINTSAPI is easy to use.
Generated code is visible and debuggable,
Generated code is known before compilation so you canreference it directly (no GWT.create).
No overhead at runtime.
Does not depend on byte code : GWT compatible
BAD POINTS
BAD POINTSOnly annotated elements trigger processing. API makes itdifficult to coordinate processing of multiple classes overmultiple rounds (bad for incremental compilation).
Dependency to external resource is not managed either.
JAVADOC COMMENTSXDoclet (2002)
/***** Account entity bean** @ejb.bean* name="bank/Account"* jndi-name="ejb/bank/Account"* primkey-field="id"* schema = "Customers"* ...*/public class MonBean { ... }
APT
APTIntroduced in JDK 5, wasremoved with Java 7 because it support new languageelements.
Annotation Processing Toolcan't
Runs outside of javac.
API includes com.sun.mirror packages.
PLUGGABLE ANNOTATION PROCESSING API
PLUGGABLE ANNOTATION PROCESSING APIFixes the sins of the past.
has been included since Java 6 (2006).JSR-269
Runs inside of javac.
API is able to welcome new language features.
HOW IT WORKS
HOW IT WORKSAnnotation processors must be registered.
Java source files are compiled during rounds.
Each round, processors are activated and receive theprogram's AST.
They can then generate files which will be part of the nextround.
When no file is generated during a round, real compilationhappens.
WE WILL HAVE TOWrite an annotation,
Write an annotation processor,
Register our processor through SPI,
Package and use our library.
ANNOTATION CREATION
ANNOTATION CREATIONThis is the annotation we use to trigger the customannotation processing :
import java.lang.annotation.*;
@Target( { ElementType.METHOD } )@Retention( RetentionPolicy.SOURCE )public @interface AutoUi{}
ANNOTATION PROCESSOR IMPLEMENTATION
ANNOTATION PROCESSOR IMPLEMENTATION@SupportedAnnotationTypes({"fr.lteconsulting.AutoUi"})@SupportedSourceVersion(SourceVersion.RELEASE_8)public class AutoUiProcessor extends AbstractProcessor { @Override public boolean process( Set<TypeElement> annotations, RoundEnvironment round) { for(TypeElement element : round.getElementsAnnotatedWith(AutoUi.class)) { ... JavaFileObject javaFile = filer.createSourceFile(classFqn); Writer writer = javaFile.openWriter(); ... } return true; }}
REGISTERING THROUGH SPI
REGISTERING THROUGH SPIJava compiler searches annotation processors through SPI.
Add a file named META-INF/services/javax.annotation.processing.Processor containingthe annotation processors' fqn list :
fr.lteconsulting.AutoUiAnnotationProcessor
Other ways to register : javac has special flags. TheCompilationTask also has methods to set the processors to beused.
PACKAGING
PACKAGINGThe simplest way is to have the annotation and its processorin the same jar package.
Maven tip: dont forget to use the<compilerArgument>-proc:none</compilerArgument> options
USING THE PROCESSOR
USING THE PROCESSORIn a project with the processor's jar in the classpath, we canuse the annotation...
Eclipse tips :
Eclipse uses its own java compiler, JDT. Use m2e-apt toconfigure your project if you work with maven.Don't forget to close the processor project to have it activated.
THE POJO CLASS
THE POJO CLASS@AutoUipublic class Person { @Label("Name") private String firstName; private String lastName;
private int age;
// getters and setters}
THE GENERATED CLASS
THE GENERATED CLASSpublic class PersonAutoUi extends Composite { private final TextBox firstNameTextBox = new TextBox(); ...
public void setPerson(Person pojo) { ... }
public void updatePerson(Person pojo) { ... }
...}
USING THE GENERATED CLASS
USING THE GENERATED CLASSpublic class Application implements EntryPoint { public void onModuleLoad() { Person person = new Person(...);
// Use of the generated UI code PersonAutoUi editor = new PersonAutoUi();
// Feed the ui editor.setPerson(person);
// Update the pojo with the ui values updateButton.addClickHandler((e)->{ editor.updatePerson(person); });
RootPanel.get().add(editor); }}
HOW IS IT POSSIBLE ?
HOW IS IT POSSIBLE ?Using the not yet generated file is possible because the javacompiler deffers processing of theNotFoundSymbolException.
The error is raised at the end of the parsing and annotationprocessing process if the symbol has not been generated.
THE API : A BRIEF
API OVERVIEWFiler class : generate files (source, byte-code, resource)
Language Model classes : browse the program's structure,
Messager class : to communicate with the user,
Other tools : element and type tools
see javadoc of the javax.annotation.processing andjavax.lang.model packages.
API : JAVA SOURCE REPRESENTATION
API : JAVA SOURCE REPRESENTATIONElement : representation of a language construct (classdeclarations, methods, ...). Ex: getSimpleName(), ...
Supports all the language structures through theaccept(visitor) and getKind() methods.Hierarchical structure : getEnclosedElements(),getEnclosingElements().
TypeMirror : Type representation, almost like Class<?>
API : ACCESSING ELEMENTS
API : ACCESSING ELEMENTSElements are given as parameters in the process(...)method of the generator. Annotated elements are retrievedlike this :
round.getElementsAnnotatedWith(AutoUi.class).
All the classes parsed during the current round can beobtained with :
round.getRootElements().
Elements can also be retrieved with the utility methods :
elementsUtils.getTypeElement(fqn),and elementsUtils.getPackageElement(fqn).
API : FILER
API : FILERjavax.annotation.processing.Filer
Creating a new Java source
// obtains Filer from the abstract super classFiler filer = processingEnv.getFiler();
// create only create new filesJavaFileObject jfo = filer.createSourceFile(classFqn);
// compose your java fiPrintWriter pw = new PrintWriter( jfo );
API : MESSAGER
API : MESSAGEROutputs messages to the user.
Can also generate errors and break the build. Very handy toassert things on the code.
messager.printMessage(Kind.ERROR, "Cannot find an ID field !");
IDE integration : hints on the API for the user.
API : TOOLS
API : TOOLSMany tools can be retrieved from theprocessingEnvironment field of the AbstractProcessor
Filer getFiler();Messager getMessager();
Elements getElementUtils();Types getTypeUtils();
Map<String, String> getOptions();SourceVersion getSourceVersion();Locale getLocale();
Other static methods and classes are helpful :
ElementFilter, AbstractVisitors...
TESTING A COMPILATIONJavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
// Oracle JDK: task can be cast into// com.sun.source.util.JavacTaskCompilationTask task = compiler.getTask(...);
// forces the processorstask.setProcessors(processors);
boolean successful = task.call();diagnosticCollector.getDiagnostics(); // structured logsfileManager.getOutputFiles();
COMPILE-TESTINGgithub.com/google/compile-testing
Annotation Processor testing library developped by Googleto help developping of the and projects.Auto Dagger
POSITIVE TESTSassert_().about(javaSource()) .that(forResource("PojoTest.java")) .processedWith(new AutoUiProcessor()) .compilesWithoutError() .and() .generatesSources(forResource("PojoTestAutoUi.java"));
AND NEGATIVE ONES
AND NEGATIVE ONESJavaFileObject fileObject = forResource("PojoErrorTest.java");
assert_().about(javaSource()) .that(fileObject) .processedWith(new AutoUiProcessor()) .failsToCompile() .withErrorContaining("No getter found") .in(fileObject) .onLine(23) .atColumn(5);
LIMITATIONSNot a full access to the code's AST (instructions).
Processors cannot depend one on the other.
Incremental compilation is difficult when havingdependencies to more than one element or to external files.
on Eclipse : Alt+F5, on maven : have to disable incrementalcompilation.
Most of the time those limitations are not embarassing.
HACKING : based on JSR 269 and hacking both javac and jdt
in order to acces to internal implementations and mutatethe class AST.Technical explanations in
Lombok
The Hacker's guide to JavaC
NOTE ON USING TEMPLATES
NOTE ON USING TEMPLATESTry to generate the minimal amount of code, and base it ongeneric implementations. This will ease debugging.
Tools :
Velocity, ...Java Poet, ...String.replaceAll()
LIBRARIES KNOWN USING JSR-269
LIBRARIES KNOWN USING JSR-269JPA meta-model generation (JSR-317),Dagger,Google Auto,Immutables,Lombok,GWT (RequestFactory),Hexa Binding...
LINKS
LINKS,
, , , ,, , ,
, , ,
javadoc for annotation processing javadoc for Java LanguageModel Hibernate Validation Lombok How Lombok works ?Lombok again... Hacking JavaC Coders Breakfast AngelikaLanger presentation Dr. Macphail's trance Annotationprocessing history Save method parameter names
SEE YOU !Slides : lteconsulting.fr/annotation-processing
Demo project : github.com/ltearno/gwtcon-jsr269
Twitter : @ltearno
LTE Consulting : lteconsulting.fr
LinkedIn : fr.linkedin.com/in/lteconsulting