Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Кодогенерация от А до Я

Кодогенерация от А до Я

Доклад про кодогенерацию на MBLTDev 2016.

Grigoriy Dzhanelidze

November 17, 2016
Tweet

More Decks by Grigoriy Dzhanelidze

Other Decks in Programming

Transcript

  1. 6 Немного про AspectJ @Override
 protected void onCreate(Bundle savedInstanceState) {

    Debug.startMethodTracing();
 super.onCreate(savedInstanceState);
 /* doing the do */
 Debug.stopMethodTracing(); }
  2. 7 Немного про AspectJ Сross-cutting concerns @Override
 protected void onCreate(Bundle

    savedInstanceState) { Debug.startMethodTracing();
 super.onCreate(savedInstanceState);
 /* doing the do */
 Debug.stopMethodTracing(); }
  3. 8 Немного про AspectJ Сross-cutting concerns @Override
 protected void onCreate(Bundle

    savedInstanceState) { Debug.startMethodTracing();
 super.onCreate(savedInstanceState);
 /* doing the do */
 Debug.stopMethodTracing(); } • Чтобы удалить или поменять нужно целых два телодвижения • А что если мы хотим применить ко всем Activity#onCreate? • А что если мы хотим добавить этот код ко всем функциям во всех классах com.sample.package начинающимся с set...?
  4. 9 Подключаем AspectJ к проекту buildscript {
 repositories {
 mavenCentral()


    maven { url 'https://github.com/Archinamon/GradleAspectJ-Android/raw/master' }
 }
 dependencies {
 classpath 'com.android.tools.build:gradle:2.1.0'
 classpath 'com.archinamon:android-gradle-aspectj:2.0.2'
 }
 } apply plugin: 'com.archinamon.aspectj' https://github.com/Archinamon/GradleAspectJ-Android
  5. 10 Пример аспекта @Aspect
 public class LogAspect {
 @Around("within(com.mblt16.*) &&

    execution(public onCreate(..))")
 public Object around(ProceedingJoinPoint point) throws Throwable {
 Debug.startMethodTracing();
 Object result = point.proceed();
 Debug.stopMethodTracing();
 return result;
 }
 }
  6. 11 Пример аспекта @Aspect
 public class LogAspect {
 @Around("within(com.mblt16.*) &&

    execution(public onCreate(..))")
 public Object around(ProceedingJoinPoint point) throws Throwable {
 Debug.startMethodTracing();
 Object result = point.proceed();
 Debug.stopMethodTracing();
 return result;
 }
 }
  7. 12 Аспекты превращаются в… public class MainActivity extends AppCompatActivity {


    private static final JoinPoint.StaticPart ajc$tjp_0;
 
 private static void ajc$preClinit() {
 Factory localFactory = new Factory("MainActivity.java", MainActivity.class);
 ajc$tjp_0 = localFactory.makeSJP("method-execution", localFactory.makeMethodSig("4", "onCreate", "me.strlght.mbltdev16.MainActivity", "android.os.Bundle", "savedInstanceState", "", "void"), 9);
 }
 
 private static final void onCreate_aroundBody0(MainActivity paramMainActivity, Bundle paramBundle, JoinPoint paramJoinPoint) {
 paramMainActivity.onCreate(paramBundle);
 }
 
 private static final Object onCreate_aroundBody1$advice(MainActivity paramMainActivity, Bundle paramBundle, JoinPoint paramJoinPoint, SampleAspect paramSampleAspect, ProceedingJoinPoint paramProceedingJoinPoint) {
 
 try {
 onCreate_aroundBody0(paramMainActivity, paramBundle, paramProceedingJoinPoint);
 return null;
 } catch (Throwable paramMainActivity) {
 throw paramMainActivity;
 } finally {
 Debug.stopMethodTracing();
 }
 }
 
 protected void onCreate(Bundle paramBundle) {
 JoinPoint localJoinPoint = Factory.makeJP(ajc$tjp_0, this, this, paramBundle);
 onCreate_aroundBody1$advice(this, paramBundle, localJoinPoint, SampleAspect.aspectOf(), (ProceedingJoinPoint) localJoinPoint);
 }
 }
  8. 13 Что хорошо? • Можно буквально в пару строк вынести

    сквозной код • Работает с .class-файлами • Из коробки генерирует список классов, которые были затронуты
  9. 14 Что хорошо? • Можно буквально в пару строк вынести

    сквозной код • Работает с .class-файлами • Из коробки генерирует список классов, которые были затронуты А что не так? • Удачи с тестированием • Jack&Jill
  10. 15 Что хорошо? • Можно буквально в пару строк вынести

    сквозной код • Работает с .class-файлами • Из коробки генерирует список классов, которые были затронуты А что не так? • Удачи с тестированием • Jack&Jill А кто использует? • https://github.com/JakeWharton/hugo
  11. 17 AutoValue @AutoValue
 public abstract class User {
 public static

    User create(long id, String name, String userPic) {
 return new AutoValue_User(id, name, userPic);
 }
 
 public abstract long id();
 
 public abstract String name();
 
 public abstract String userPic();
 }
  12. 18 AutoValue @AutoValue
 public abstract class User {
 public static

    User create(long id, String name, String userPic) {
 return new AutoValue_User(id, name, userPic);
 }
 
 public abstract long id();
 
 public abstract String name();
 
 public abstract String userPic();
 } final class AutoValue_User extends User {
 
 private final long id;
 private final String name;
 private final String userPic;
 
 AutoValue_User(
 long id,
 String name,
 String userPic) {
 this.id = id;
 if (name == null) {
 throw new NullPointerException("Null name");
 }
 this.name = name;
 if (userPic == null) {
 throw new NullPointerException("Null userPic");
 }
 this.userPic = userPic;
 }
 
 @Override
 public long id() {
 return id;
 }
 
 @Override
 public String name() {
 return name;
 }
 
 @Override
 public String userPic() {
 return userPic;
 }
 
 @Override
 public String toString() {
 return "Data{"
 + "id=" + id + ", "
 + "name=" + name + ", "
 + "userPic=" + userPic
 + "}";
 }
 
 @Override
 public boolean equals(Object o) {
 if (o == this) {
 return true;
 }
 if (o instanceof User) {
 Data that = (User) o;
 return (this.id == that.id())
 && (this.name.equals(that.name()))
 && (this.userPic.equals(that.userPic()));
 }
 return false;
 }
 
 @Override
 public int hashCode() {
 int h = 1;
 h *= 1000003;
 h ^= (this.id >>> 32) ^ this.id;
 h *= 1000003;
 h ^= this.name.hashCode();
 h *= 1000003;
 h ^= this.userPic.hashCode();
 return h;
 }
 
 }
  13. 19 AutoValue Extension public abstract class AutoValueExtension {
 
 public

    boolean applicable(Context context) {
 return false;
 }
 
 public boolean mustBeFinal(Context context) {
 return false;
 }
 
 public Set<String> consumeProperties(Context context) {
 return Collections.emptySet();
 }
 
 public Set<ExecutableElement> consumeMethods(Context context) {
 return Collections.emptySet();
 }
 
 public abstract String generateClass(
 Context context, String className, String classToExtend, boolean isFinal);
 }
  14. 20 AutoValue Extension public boolean applicable(Context context) @AutoValue
 public abstract

    class User {} false @AutoValue @SQLTable
 public abstract class User {} true
  15. 21 AutoValue Extension public boolean applicable(Context context) private static Set<String>

    getAnnotations(Element element) {
 final Set<String> set = new LinkedHashSet<>();
 
 final List<? extends AnnotationMirror> annotations = element.getAnnotationMirrors();
 for (AnnotationMirror annotation : annotations) {
 set.add(annotation.getAnnotationType().asElement() .getSimpleName().toString());
 }
 
 return Collections.unmodifiableSet(set);
 } @Override
 public boolean applicable(Context context) {
 return getAnnotations(context.autoValueClass()).contains("SQLTable");
 }
  16. 22 AutoValue Extension public boolean mustBeFinal(Context context) false true SampleExt

    Data AutoValue SQLTable Data SQLTable Data SampleExt Data AutoValue
  17. 23 AutoValue Extension public Set<String> consumeProperties(Context context) ∅ ["describeContents"] abstract

    class $AutoValue_Data extends Data {
 /* sample autovalue implementation */ private final int describeContents; public int describeContents() { return describeContents; }
 } abstract class $AutoValue_Data extends Data {
 /* sample autovalue implementation */ public abstract int describeContents();
 }
  18. 24 AutoValue Extension @Override
 public String generateClass(Context context, String className,

    String classToExtend,
 boolean isFinal) {
 String packageName = context.packageName();
 String originalName = context.autoValueClass().getSimpleName().toString();
 Map<String, ExecutableElement> properties = context.properties();
 
 TypeSpec subclass = TypeSpec.classBuilder(className)
 .addModifiers(isFinal ? Modifier.FINAL : Modifier.ABSTRACT)
 .superclass(ClassName.get(packageName, classToExtend))
 .addMethod(generateConstructor(properties))
 .addMethod(generateCreateTable(originalName, properties))
 .build();
 
 JavaFile javaFile = JavaFile.builder(packageName, subclass).build();
 return javaFile.toString();
 }
  19. 25 AutoValue Extension @Override
 public String generateClass(Context context, String className,

    String classToExtend,
 boolean isFinal) {
 String packageName = context.packageName();
 String originalName = context.autoValueClass().getSimpleName().toString();
 Map<String, ExecutableElement> properties = context.properties();
 
 TypeSpec subclass = TypeSpec.classBuilder(className)
 .addModifiers(isFinal ? Modifier.FINAL : Modifier.ABSTRACT)
 .superclass(ClassName.get(packageName, classToExtend))
 .addMethod(generateConstructor(properties))
 .addMethod(generateCreateTable(originalName, properties))
 .build();
 
 JavaFile javaFile = JavaFile.builder(packageName, subclass).build();
 return javaFile.toString();
 } https://github.com/square/javapoet
  20. 26 AutoValue Extension private static MethodSpec generateCreateTable(String className,
 Map<String, ExecutableElement>

    properties) {
 final StringBuilder body = new StringBuilder("return \"CREATE TABLE ")
 .append(className).append(" (\" \n");
 int count = 0;
 boolean hasId = false;
 for (Map.Entry<String, ExecutableElement> entry : properties.entrySet()) {
 final ExecutableElement element = entry.getValue();
 String column = element.getSimpleName().toString();
 if ("id".equalsIgnoreCase(column)) {
 hasId = true;
 column = "_id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT";
 }
 body.append("+ \"\\n").append(column);
 if (++count != properties.size()) {
 body.append(",");
 }
 body.append("\"\n");
 }
 body.append(" + \")\"");
 if (!hasId) {
 throw new IllegalStateException("Missing property id");
 }
 return MethodSpec.methodBuilder("createTable")
 .addModifiers(Modifier.PUBLIC)
 .returns(String.class)
 .addStatement(body.toString())
 .build();
 }
  21. 27 AutoValue Extension final class AutoValue_User extends $AutoValue_User {
 AutoValue_User(long

    id, String name, String userPic) {
 super(id, name, userPic);
 }
 
 public String createTable() {
 return "CREATE TABLE Data ("
 + "\n_id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,"
 + "\nname,"
 + "\nuserPic"
 + ")";
 }
 }
  22. 29 AutoValue Extension @AutoService(AutoValueExtension.class) public class SampleExtension extends AutoValueExtension {

    /* … */ } resources/META-INF/services/com.google.auto.value.extension.AutoValueExtension: our.autovalue.SampleExtension
  23. 30 AutoValue Extension @AutoService(AutoValueExtension.class) public class SampleExtension extends AutoValueExtension {

    /* … */ } resources/META-INF/services/com.google.auto.value.extension.AutoValueExtension: our.autovalue.SampleExtension
  24. 32 Что хорошо? • Простота • Легко тестировать А что

    не так? • Может поменяться в любой момент • Только data class’ы • Boilerplate
  25. 34 public class MainActivity extends AppCompatActivity {
 @BindView(R.id.activity_main)
 RelativeLayout rootLayout;


    
 @BindView(R.id.sample_text)
 TextView sampleText;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 Butterknife.bind(this);
 sampleText.setText("Hello butterknife!");
 }
 } Butterknife
  26. 35 Annotation Processor class SampleProcessor implements Processor { public Set<String>

    getSupportedAnnotationTypes() {
 return Collections.singleton("mbltdev2016.BindView");
 } public SourceVersion getSupportedSourceVersion() { return SourceVersion.RELEASE_7;
 } }
  27. 36 Annotation Processor @AutoService(Processor.class)
 @SupportedAnnotationTypes({"mbltdev2016.BindView"})
 @SupportedSourceVersion(SourceVersion.RELEASE_7)
 class SampleProcessor extends AbstractProcessor

    class SampleProcessor implements Processor { public Set<String> getSupportedAnnotationTypes() {
 return Collections.singleton("mbltdev2016.BindView");
 } public SourceVersion getSupportedSourceVersion() { return SourceVersion.RELEASE_7;
 } }
  28. 37 Annotation Processor @AutoService(Processor.class)
 @SupportedAnnotationTypes({"mbltdev2016.BindView"})
 @SupportedSourceVersion(SourceVersion.RELEASE_7)
 class SampleProcessor extends AbstractProcessor

    class SampleProcessor implements Processor { public Set<String> getSupportedAnnotationTypes() {
 return Collections.singleton("mbltdev2016.BindView");
 } public SourceVersion getSupportedSourceVersion() { return SourceVersion.RELEASE_7;
 } }
  29. 38 Annotation Processor @AutoService(Processor.class)
 @SupportedAnnotationTypes({"mbltdev2016.BindView"})
 @SupportedSourceVersion(SourceVersion.RELEASE_7)
 public class BindViewBasicProcessor extends

    AbstractProcessor {
 @Override
 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 if (annotations.isEmpty()) {
 return false;
 }
 final Map<TypeElement, BindViewBasicVisitor> visitors = new HashMap<>();
 final Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(BindView.class);
 for (Element element : elements) {
 final TypeElement object = (TypeElement) element.getEnclosingElement();
 BindViewBasicVisitor visitor = visitors.get(object);
 if (visitor == null) {
 visitor = new BindViewBasicVisitor(processingEnv, object);
 visitors.put(object, visitor);
 }
 element.accept(visitor, null);
 }
 for (BindViewBasicVisitor visitor : visitors.values()) {
 visitor.generate();
 }
 return true;
 }
 }
  30. 39 Annotation Processor @AutoService(Processor.class)
 @SupportedAnnotationTypes({"mbltdev2016.BindView"})
 @SupportedSourceVersion(SourceVersion.RELEASE_7)
 public class BindViewBasicProcessor extends

    AbstractProcessor {
 @Override
 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 if (annotations.isEmpty()) {
 return false;
 }
 final Map<TypeElement, BindViewBasicVisitor> visitors = new HashMap<>();
 final Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(BindView.class);
 for (Element element : elements) {
 final TypeElement object = (TypeElement) element.getEnclosingElement();
 BindViewBasicVisitor visitor = visitors.get(object);
 if (visitor == null) {
 visitor = new BindViewBasicVisitor(processingEnv, object);
 visitors.put(object, visitor);
 }
 element.accept(visitor, null);
 }
 for (BindViewBasicVisitor visitor : visitors.values()) {
 visitor.generate();
 }
 return true;
 }
 }
  31. 40 Annotation Processor @AutoService(Processor.class)
 @SupportedAnnotationTypes({"mbltdev2016.BindView"})
 @SupportedSourceVersion(SourceVersion.RELEASE_7)
 public class BindViewBasicProcessor extends

    AbstractProcessor {
 @Override
 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 if (annotations.isEmpty()) {
 return false;
 }
 final Map<TypeElement, BindViewBasicVisitor> visitors = new HashMap<>();
 final Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(BindView.class);
 for (Element element : elements) {
 final TypeElement object = (TypeElement) element.getEnclosingElement();
 BindViewBasicVisitor visitor = visitors.get(object);
 if (visitor == null) {
 visitor = new BindViewBasicVisitor(processingEnv, object);
 visitors.put(object, visitor);
 }
 element.accept(visitor, null);
 }
 for (BindViewBasicVisitor visitor : visitors.values()) {
 visitor.generate();
 }
 return true;
 }
 }
  32. 41 Annotation Processor public class BindViewBasicVisitor extends ElementScanner7<Void, Void> {


    private final CodeBlock.Builder bindViewBlock = CodeBlock.builder();
 private final Messager logger;
 private final Filer filer;
 private final TypeElement originElement;
 
 public BindViewBasicVisitor(ProcessingEnvironment env, TypeElement element) {
 super();
 logger = env.getMessager();
 filer = env.getFiler();
 originElement = element;
 } }
  33. 42 Annotation Processor public class BindViewBasicVisitor extends ElementScanner7<Void, Void> {


    private final CodeBlock.Builder bindViewBlock = CodeBlock.builder();
 private final Messager logger;
 private final Filer filer;
 private final TypeElement originElement;
 
 public BindViewBasicVisitor(ProcessingEnvironment env, TypeElement element) {
 super();
 logger = env.getMessager();
 filer = env.getFiler();
 originElement = element;
 } }
  34. 43 Annotation Processor public class BindViewBasicVisitor extends ElementScanner7<Void, Void> {


    private final CodeBlock.Builder bindViewBlock = CodeBlock.builder();
 private final Messager logger;
 private final Filer filer;
 private final TypeElement originElement;
 
 public BindViewBasicVisitor(ProcessingEnvironment env, TypeElement element) {
 super();
 logger = env.getMessager();
 filer = env.getFiler();
 originElement = element;
 } }
  35. 44 Annotation Processor public class BindViewBasicVisitor extends ElementScanner7<Void, Void> {


    @Override
 public Void visitVariable(VariableElement e, Void aVoid) {
 if (e.getModifiers().contains(Modifier.PROTECTED)
 || e.getModifiers().contains(Modifier.PRIVATE)) {
 logger.printMessage(Diagnostic.Kind.ERROR, "Field " + e.getSimpleName() + "must be package-local or public");
 }
 if (e.getModifiers().contains(Modifier.FINAL)) {
 logger.printMessage(Diagnostic.Kind.ERROR, "Field " + e.getSimpleName() + "must be non-final");
 }
 final BindView bindView = e.getAnnotation(BindView.class);
 bindViewBlock.addStatement("param.$L = ($T) param.findViewById($L)",
 e.getSimpleName(), ClassName.get(e.asType()), bindView.value());
 return super.visitVariable(e, aVoid);
 } }
  36. 45 Annotation Processor public class BindViewBasicVisitor extends ElementScanner7<Void, Void> {


    @Override
 public Void visitVariable(VariableElement e, Void aVoid) {
 if (e.getModifiers().contains(Modifier.PROTECTED)
 || e.getModifiers().contains(Modifier.PRIVATE)) {
 logger.printMessage(Diagnostic.Kind.ERROR, "Field " + e.getSimpleName() + "must be package-local or public");
 }
 if (e.getModifiers().contains(Modifier.FINAL)) {
 logger.printMessage(Diagnostic.Kind.ERROR, "Field " + e.getSimpleName() + "must be non-final");
 }
 final BindView bindView = e.getAnnotation(BindView.class);
 bindViewBlock.addStatement("param.$L = ($T) param.findViewById($L)",
 e.getSimpleName(), ClassName.get(e.asType()), bindView.value());
 return super.visitVariable(e, aVoid);
 } }
  37. 46 Annotation Processor public void generate() {
 String className =

    originElement.getSimpleName() + "_ViewBinding";
 TypeSpec typeSpec = TypeSpec.classBuilder(className)
 .addModifiers(Modifier.FINAL)
 .addMethod(MethodSpec.methodBuilder("bind")
 .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
 .addParameter(TypeName.get(originElement.asType()), "param")
 .addCode(bindViewBlock.build())
 .build())
 .build();
 JavaFile javaFile = JavaFile.builder(originElement.getEnclosingElement().toString(), typeSpec)
 .addFileComment("Generated, do not modify")
 .build();
 try {
 javaFile.writeTo(filer);
 } catch (IOException exception) {
 logger.printMessage(Diagnostic.Kind.ERROR, "Can't write file: " + exception.getMessage());
 }
 }
  38. 47 Annotation Processor // Generated, do not modify
 package me.strlght.basic_processor_sample;


    
 import android.widget.RelativeLayout;
 import android.widget.TextView;
 
 final class MainActivity_ViewBinding {
 public static void bind(MainActivity param) {
 param.rootLayout = (RelativeLayout) param.findViewById(2131492944);
 param.sampleText = (TextView) param.findViewById(2131492945);
 }
 }
  39. 48 Что хорошо? • Работоспособность зависит от компилятора и/или языка

    • Легко тестировать • Сгенерированный код можно безболезненно дебажить • Никаких ограничений на генерацию нового кода по аннотациям
  40. 49 Что хорошо? • Работоспособность зависит от компилятора и/или языка

    • Легко тестировать • Сгенерированный код можно безболезненно дебажить • Никаких ограничений на генерацию нового кода по аннотациям А кто использует? • Dagger 2 • Butterknife • IcePick • …
  41. 50 Тестируем: Compile Testing @Test public void testProcessor() {
 JavaFileObject

    source = JavaFileObjects .forSourceString("test.Source", "/* source */");
 
 JavaFileObject expected = JavaFileObjects .forSourceString("test.Source_ViewBinding", "/* source */");
 
 assertAbout(javaSource())
 .that(source)
 .processedWith(new BindViewBasicProcessor())
 .compilesWithoutError()
 .and()
 .generatesSources(expected);
 } https://github.com/google/compile-testing
  42. 51 А что не так? public class MainActivity extends AppCompatActivity

    {
 @BindView(R.id.activity_main)
 RelativeLayout rootLayout;
 
 @BindView(R.id.sample_text)
 TextView sampleText;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 Butterknife.bind(this);
 sampleText.setText("Hello annotations!");
 }
 }
  43. 52 А что не так? public class MainActivity extends AppCompatActivity

    {
 @BindView(R.id.activity_main)
 RelativeLayout rootLayout;
 
 @BindView(R.id.sample_text)
 TextView sampleText;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 Butterknife.bind(this);
 sampleText.setText("Hello annotations!");
 }
 }
  44. 53 А что не так? public class MainActivity extends AppCompatActivity

    {
 @BindView(R.id.activity_main)
 RelativeLayout rootLayout;
 
 @BindView(R.id.sample_text)
 TextView sampleText;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 Butterknife.bind(this);
 sampleText.setText("Hello annotations!");
 }
 }
  45. 54 Proguard? А что не так? public class MainActivity extends

    AppCompatActivity {
 @BindView(R.id.activity_main)
 RelativeLayout rootLayout;
 
 @BindView(R.id.sample_text)
 TextView sampleText;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 Butterknife.bind(this);
 sampleText.setText("Hello annotations!");
 }
 }
  46. 55 Proguard? А что не так? public class MainActivity extends

    AppCompatActivity {
 @BindView(R.id.activity_main)
 RelativeLayout rootLayout;
 
 @BindView(R.id.sample_text)
 TextView sampleText;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 Butterknife.bind(this);
 sampleText.setText("Hello annotations!");
 }
 }
  47. 57 Идём глубже public class BindViewAdvancedVisitor extends ElementScanner7<Void, Void> {


    private final CodeBlock.Builder bindViewBlock = CodeBlock.builder();
 private final Messager logger;
 private final Filer filer;
 private final TypeElement originElement; private final Trees trees;
 private final TreeMaker treeMaker;
 private final Names names;
 
 public BindViewAdvancedVisitor(ProcessingEnvironment env, TypeElement element) {
 super();
 logger = env.getMessager();
 filer = env.getFiler();
 originElement = element; trees = Trees.instance(env);
 final JavacProcessingEnvironment javacEnv = (JavacProcessingEnvironment) env;
 treeMaker = TreeMaker.instance(javacEnv.getContext());
 names = Names.instance(javacEnv.getContext());
 } }
  48. 58 Идём глубже public class BindViewAdvancedVisitor extends ElementScanner7<Void, Void> {


    private final CodeBlock.Builder bindViewBlock = CodeBlock.builder();
 private final Messager logger;
 private final Filer filer;
 private final TypeElement originElement; private final Trees trees;
 private final TreeMaker treeMaker;
 private final Names names;
 
 public BindViewAdvancedVisitor(ProcessingEnvironment env, TypeElement element) {
 super();
 logger = env.getMessager();
 filer = env.getFiler();
 originElement = element; trees = Trees.instance(env);
 final JavacProcessingEnvironment javacEnv = (JavacProcessingEnvironment) env;
 treeMaker = TreeMaker.instance(javacEnv.getContext());
 names = Names.instance(javacEnv.getContext());
 } }
  49. 59 Идём глубже @Override
 public Void visitVariable(VariableElement e, Void aVoid)

    {
 ((JCTree) trees.getTree(e)).accept(new TreeTranslator() {
 @Override
 public void visitVarDef(JCTree.JCVariableDecl jcVariableDecl) {
 super.visitVarDef(jcVariableDecl);
 jcVariableDecl.mods.flags &= ~Flags.PRIVATE;
 }
 });
 final BindView bindView = e.getAnnotation(BindView.class);
 bindViewBlock.addStatement("(($T) this).$L = ($T) findViewById($L)",
 ClassName.get(originElement), e.getSimpleName(), ClassName.get(e.asType()), bindView.value());
 return super.visitVariable(e, aVoid);
 }
  50. 60 Идём глубже @Override
 public Void visitVariable(VariableElement e, Void aVoid)

    {
 ((JCTree) trees.getTree(e)).accept(new TreeTranslator() {
 @Override
 public void visitVarDef(JCTree.JCVariableDecl jcVariableDecl) {
 super.visitVarDef(jcVariableDecl);
 jcVariableDecl.mods.flags &= ~Flags.PRIVATE;
 }
 });
 final BindView bindView = e.getAnnotation(BindView.class);
 bindViewBlock.addStatement("(($T) this).$L = ($T) findViewById($L)",
 ClassName.get(originElement), e.getSimpleName(), ClassName.get(e.asType()), bindView.value());
 return super.visitVariable(e, aVoid);
 }
  51. 61 Идём глубже @Override
 public Void visitVariable(VariableElement e, Void aVoid)

    {
 ((JCTree) trees.getTree(e)).accept(new TreeTranslator() {
 @Override
 public void visitVarDef(JCTree.JCVariableDecl jcVariableDecl) {
 super.visitVarDef(jcVariableDecl);
 jcVariableDecl.mods.flags &= ~Flags.PRIVATE;
 }
 });
 final BindView bindView = e.getAnnotation(BindView.class);
 bindViewBlock.addStatement("(($T) this).$L = ($T) findViewById($L)",
 ClassName.get(originElement), e.getSimpleName(), ClassName.get(e.asType()), bindView.value());
 return super.visitVariable(e, aVoid);
 }
  52. 64 Идём глубже String className = originElement.getSimpleName() + "$$ViewBinding";
 TypeSpec

    typeSpec = TypeSpec.classBuilder(className)
 .addModifiers(Modifier.ABSTRACT)
 .superclass(ClassName.get(originElement.getSuperclass()))
 .addOriginatingElement(originElement)
 .addMethod(MethodSpec.methodBuilder("setContentView")
 .addAnnotation(Override.class)
 .addModifiers(Modifier.PUBLIC)
 .addParameter(TypeName.INT, "layoutResId")
 .addStatement("super.setContentView(layoutResId)")
 .addCode(bindViewBlock.build())
 .build())
 .build();
 JavaFile javaFile = JavaFile.builder(originElement.getEnclosingElement().toString(), typeSpec)
 .addFileComment("Generated, do not modify")
 .build();
  53. 65 Идём глубже String className = originElement.getSimpleName() + "$$ViewBinding";
 TypeSpec

    typeSpec = TypeSpec.classBuilder(className)
 .addModifiers(Modifier.ABSTRACT)
 .superclass(ClassName.get(originElement.getSuperclass()))
 .addOriginatingElement(originElement)
 .addMethod(MethodSpec.methodBuilder("setContentView")
 .addAnnotation(Override.class)
 .addModifiers(Modifier.PUBLIC)
 .addParameter(TypeName.INT, "layoutResId")
 .addStatement("super.setContentView(layoutResId)")
 .addCode(bindViewBlock.build())
 .build())
 .build();
 JavaFile javaFile = JavaFile.builder(originElement.getEnclosingElement().toString(), typeSpec)
 .addFileComment("Generated, do not modify")
 .build();
  54. 66 Идём глубже String className = originElement.getSimpleName() + "$$ViewBinding";
 TypeSpec

    typeSpec = TypeSpec.classBuilder(className)
 .addModifiers(Modifier.ABSTRACT)
 .superclass(ClassName.get(originElement.getSuperclass()))
 .addOriginatingElement(originElement)
 .addMethod(MethodSpec.methodBuilder("setContentView")
 .addAnnotation(Override.class)
 .addModifiers(Modifier.PUBLIC)
 .addParameter(TypeName.INT, "layoutResId")
 .addStatement("super.setContentView(layoutResId)")
 .addCode(bindViewBlock.build())
 .build())
 .build();
 JavaFile javaFile = JavaFile.builder(originElement.getEnclosingElement().toString(), typeSpec)
 .addFileComment("Generated, do not modify")
 .build();
  55. 67 Идём глубже try {
 javaFile.writeTo(filer);
 JCTree.JCExpression selector = treeMaker.Ident(names.fromString(javaFile.packageName));


    selector = treeMaker.Select(selector, names.fromString(typeSpec.name));
 ((JCTree.JCClassDecl) trees.getTree(originElement)).extending = selector;
 } catch (IOException exception) {
 logger.printMessage(Diagnostic.Kind.ERROR, "Can't write file: " + exception.getMessage());
 }
  56. 68 Идём глубже try {
 javaFile.writeTo(filer);
 JCTree.JCExpression selector = treeMaker.Ident(names.fromString(javaFile.packageName));


    selector = treeMaker.Select(selector, names.fromString(typeSpec.name));
 ((JCTree.JCClassDecl) trees.getTree(originElement)).extending = selector;
 } catch (IOException exception) {
 logger.printMessage(Diagnostic.Kind.ERROR, "Can't write file: " + exception.getMessage());
 }
  57. 69 Идём глубже // Generated, do not modify
 package me.strlght.advanced_processor_sample;


    
 import android.support.v7.app.AppCompatActivity;
 import android.widget.RelativeLayout;
 import android.widget.TextView;
 import java.lang.Override;
 
 abstract class MainActivity$$ViewBinding extends AppCompatActivity {
 @Override
 public void setContentView(int layoutResId) {
 super.setContentView(layoutResId);
 ((MainActivity) this).rootLayout = (RelativeLayout) findViewById(2131492944);
 ((MainActivity) this).sampleText = (TextView) findViewById(2131492945);
 }
 } public class MainActivity extends MainActivity$$ViewBinding { /* … */ }
  58. 71 Что хорошо? А что не так? • Не работает

    со всем, что не Java • Документация? • В любой момент может отвалиться • Jack&Jill • Переделываем AST как угодно без ограничений, без регистрации и смс
  59. 72 Что хорошо? А что не так? • Не работает

    со всем, что не Java • Документация? • В любой момент может отвалиться • Jack&Jill А кто использует? • ¯\_(ツ)_/¯ • Переделываем AST как угодно без ограничений, без регистрации и смс
  60. 75 Assert assert sampleObject == null; public class Sample {


    static final boolean $assertionsDisabled;
 
 static {};
 Code: // $assertionsDisabled = !Class. desiredAssertionStatus();
 0: ldc #6 // class Sample
 2: invokevirtual #7 // Method java/lang/Class.desiredAssertionStatus:()Z
 5: ifne 12
 8: iconst_1
 9: goto 13
 12: iconst_0
 13: putstatic #3 // Field $assertionsDisabled:Z
 16: iconst_0
 17: putstatic #2 // Field sample:Z
 20: return
 }
  61. 76 Assert assert sampleObject == null; public class Sample {


    static final boolean $assertionsDisabled;
 
 static {};
 Code: // $assertionsDisabled = !Class. desiredAssertionStatus();
 0: ldc #6 // class Sample
 2: invokevirtual #7 // Method java/lang/Class.desiredAssertionStatus:()Z
 5: ifne 12
 8: iconst_1
 9: goto 13
 12: iconst_0
 13: putstatic #3 // Field $assertionsDisabled:Z
 16: iconst_0
 17: putstatic #2 // Field sample:Z
 20: return
 }
  62. 77 Transform API class AssertTransform extends Transform {
 @Override
 String

    getName() {
 return "assert"
 }
 
 @Override
 Set<QualifiedContent.ContentType> getInputTypes() {
 return EnumSet.of(QualifiedContent.DefaultContentType.CLASSES)
 }
 
 @Override
 Set<QualifiedContent.Scope> getScopes() {
 return EnumSet.of(QualifiedContent.Scope.PROJECT)
 }
 
 @Override
 boolean isIncremental() {
 return false
 } }
  63. 78 Transform API class AssertTransform extends Transform {
 @Override
 String

    getName() {
 return "assert"
 }
 
 @Override
 Set<QualifiedContent.ContentType> getInputTypes() {
 return EnumSet.of(QualifiedContent.DefaultContentType.CLASSES)
 }
 
 @Override
 Set<QualifiedContent.Scope> getScopes() {
 return EnumSet.of(QualifiedContent.Scope.PROJECT)
 }
 
 @Override
 boolean isIncremental() {
 return false
 } }
  64. 79 Transform API class AssertTransform extends Transform {
 @Override
 String

    getName() {
 return "assert"
 }
 
 @Override
 Set<QualifiedContent.ContentType> getInputTypes() {
 return EnumSet.of(QualifiedContent.DefaultContentType.CLASSES)
 }
 
 @Override
 Set<QualifiedContent.Scope> getScopes() {
 return EnumSet.of(QualifiedContent.Scope.PROJECT)
 }
 
 @Override
 boolean isIncremental() {
 return false
 } }
  65. 80 Transform API class AssertTransform extends Transform {
 @Override
 String

    getName() {
 return "assert"
 }
 
 @Override
 Set<QualifiedContent.ContentType> getInputTypes() {
 return EnumSet.of(QualifiedContent.DefaultContentType.CLASSES)
 }
 
 @Override
 Set<QualifiedContent.Scope> getScopes() {
 return EnumSet.of(QualifiedContent.Scope.PROJECT)
 }
 
 @Override
 boolean isIncremental() {
 return false
 } }
  66. 81 Transform API class AssertTransform extends Transform {
 @Override
 String

    getName() {
 return "assert"
 }
 
 @Override
 Set<QualifiedContent.ContentType> getInputTypes() {
 return EnumSet.of(QualifiedContent.DefaultContentType.CLASSES)
 }
 
 @Override
 Set<QualifiedContent.Scope> getScopes() {
 return EnumSet.of(QualifiedContent.Scope.PROJECT)
 }
 
 @Override
 boolean isIncremental() {
 return false
 } }
  67. 82 Transform API @Override
 void transform(Context context, Collection<TransformInput> inputs,
 Collection<TransformInput>

    referencedInputs, TransformOutputProvider outputProvider,
 boolean isIncremental) throws IOException, TransformException, InterruptedException {
 final DirectoryInput directoryInput = inputs.first().directoryInputs.first()
 final File output = outputProvider.getContentLocation(
 directoryInput.name, EnumSet.of(QualifiedContent.DefaultContentType.CLASSES),
 EnumSet.of(QualifiedContent.Scope.PROJECT), Format.DIRECTORY)
 final def processor = new AssertProcessor(directoryInput.file, output)
 try {
 processor.process()
 } catch (final Exception exception) {
 throw new TransformException(exception)
 }
 }
  68. 84 Bytecode class Patcher {
 fun copyAndPatchClasses(inputPath: File, outputPath: File)

    {
 for (sourceFile in inputPath.walk()) {
 val relativePath = sourceFile.toRelativeString(inputPath)
 val targetFile = File(outputPath, relativePath)
 if (sourceFile.isFile) {
 val type = getObjectTypeFromFile(relativePath)
 if (type != null) {
 patchClass(sourceFile, targetFile)
 }
 } else {
 targetFile.mkdirs()
 }
 }
 }
 
 private fun getObjectTypeFromFile(relativePath: String): Type? {
 if (relativePath.endsWith(".class")) {
 val internalName = relativePath.substringBeforeLast(".class")
 return Type.getObjectType(internalName)
 }
 return null
 }
 }
  69. 85 Bytecode class Patcher {
 fun copyAndPatchClasses(inputPath: File, outputPath: File)

    {
 for (sourceFile in inputPath.walk()) {
 val relativePath = sourceFile.toRelativeString(inputPath)
 val targetFile = File(outputPath, relativePath)
 if (sourceFile.isFile) {
 val type = getObjectTypeFromFile(relativePath)
 if (type != null) {
 patchClass(sourceFile, targetFile)
 }
 } else {
 targetFile.mkdirs()
 }
 }
 }
 
 private fun getObjectTypeFromFile(relativePath: String): Type? {
 if (relativePath.endsWith(".class")) {
 val internalName = relativePath.substringBeforeLast(".class")
 return Type.getObjectType(internalName)
 }
 return null
 }
 }
  70. 86 Bytecode private fun patchClass(sourceFile: File, targetFile: File) {
 val

    reader = ClassReader(sourceFile.readBytes())
 val writer = ClassWriter(reader, ClassWriter.COMPUTE_MAXS or ClassWriter.COMPUTE_FRAMES)
 val visitor = AssertionVisitor(writer)
 reader.accept(visitor, ClassReader.SKIP_FRAMES)
 targetFile.parentFile.mkdirs()
 targetFile.writeBytes(writer.toByteArray())
 } http://asm.ow2.org/ ClassReader AssertionVisitor ClassWriter
  71. 87 Bytecode private val STATIC_INITIALIZER_METHOD = Method("<clinit>", Type.VOID_TYPE, arrayOf())
 


    class AssertionVisitor(
 delegate: ClassVisitor
 ) : ClassVisitor(ASM5, delegate) {
 override fun visitMethod(access: Int, name: String?, desc: String?, signature: String?,
 exceptions: Array<out String>?): MethodVisitor {
 val visitor = super.visitMethod(access, name, desc, signature, exceptions)
 if (name == STATIC_INITIALIZER_METHOD.name) {
 return object : GeneratorAdapter(ASM5, visitor, access, name, desc) {
 override fun visitFieldInsn(opcode: Int, owner: String?, name: String?, desc: String?) {
 if (opcode == Opcodes.PUTSTATIC && name == "\$assertionsDisabled") {
 pop()
 push(false)
 }
 super.visitFieldInsn(opcode, owner, name, desc)
 }
 }
 } else {
 return visitor
 }
 }
 }
  72. 88 Bytecode private val STATIC_INITIALIZER_METHOD = Method("<clinit>", Type.VOID_TYPE, arrayOf())
 


    class AssertionVisitor(
 delegate: ClassVisitor
 ) : ClassVisitor(ASM5, delegate) {
 override fun visitMethod(access: Int, name: String?, desc: String?, signature: String?,
 exceptions: Array<out String>?): MethodVisitor {
 val visitor = super.visitMethod(access, name, desc, signature, exceptions)
 if (name == STATIC_INITIALIZER_METHOD.name) {
 return object : GeneratorAdapter(ASM5, visitor, access, name, desc) {
 override fun visitFieldInsn(opcode: Int, owner: String?, name: String?, desc: String?) {
 if (opcode == Opcodes.PUTSTATIC && name == "\$assertionsDisabled") {
 pop()
 push(false)
 }
 super.visitFieldInsn(opcode, owner, name, desc)
 }
 }
 } else {
 return visitor
 }
 }
 }
  73. 89 Bytecode private val STATIC_INITIALIZER_METHOD = Method("<clinit>", Type.VOID_TYPE, arrayOf())
 


    class AssertionVisitor(
 delegate: ClassVisitor
 ) : ClassVisitor(ASM5, delegate) {
 override fun visitMethod(access: Int, name: String?, desc: String?, signature: String?,
 exceptions: Array<out String>?): MethodVisitor {
 val visitor = super.visitMethod(access, name, desc, signature, exceptions)
 if (name == STATIC_INITIALIZER_METHOD.name) {
 return object : GeneratorAdapter(ASM5, visitor, access, name, desc) {
 override fun visitFieldInsn(opcode: Int, owner: String?, name: String?, desc: String?) {
 if (opcode == Opcodes.PUTSTATIC && name == "\$assertionsDisabled") {
 pop()
 push(false)
 }
 super.visitFieldInsn(opcode, owner, name, desc)
 }
 }
 } else {
 return visitor
 }
 }
 }
  74. 91 Что хорошо? • Работает с .class-файлами А что не

    так? • Как тестировать и дебажить? • Jack&Jill
  75. 92 Что хорошо? • Работает с .class-файлами А что не

    так? • Как тестировать и дебажить? • Jack&Jill А кто использует? • ProGuard • Retrolambda • AspectJ
  76. 93 Заключение • Инструменты для кодогенерации есть под все нужды

    и вкусы • Есть много полезных библиотек: JavaPoet, Compile Testing, AutoService, … • Но иногда для тестирования нужны костыли • Jack&Jill