Save 37% off PRO during our Black Friday Sale! »

Bytecode manipulation in Realm How and why?

358791a9c91e7615ffe62c7c6e02e470?s=47 zaki50
August 18, 2016

Bytecode manipulation in Realm How and why?

358791a9c91e7615ffe62c7c6e02e470?s=128

zaki50

August 18, 2016
Tweet

Transcript

  1. Bytecode manipulation in Realm How and why? Tokyo Android Meetup

    - August Edition my@realm.io
  2. Makoto Yamazaki Realm Inc. / uPhyca Inc. my@realm.io @zaki50

  3. Bytecode manipulation? my@realm.io

  4. my@realm.io Bytecode manipulation is... modifying the bytecode at compile time

    or at the loading of the classes. Bytecode is the instruction set of JVM.
  5. Realm? my@realm.io

  6. my@realm.io

  7. my@realm.io Realm is mobile database Runs directly inside the phones,

    tablets, wearables
  8. Fast my@realm.io Advanced Easy Why mobile database?

  9. my@realm.io Realm is Easy public class Article extends RealmObject {


    @PrimaryKey
 public String id;
 public String title;
 public String contents;
 public Date createdAt;
 public Date modifiedAt;
 public User user;
 public RealmList<Comment> comments;
 }
  10. my@realm.io Realm is Fast Lazy loading

  11. my@realm.io Realm is Fast RealmQuery<Article> query = realm.where(Article.class); query =

    query.contains("title", "realm"); RealmResults<Article> results = query.findAll();
 // results does not contain any actual Article instances here.
 
 Article article = results.first();
 // article does not contain any field values here.
 
 String title = article.title;
 // it looks just a field access. // but it fetches the actual value from the database.

  12. Annotation processing my@realm.io Bytecode manipulation How to achieve lazy loading

    of field values
  13. my@realm.io Old Realm-java public class Article extends RealmObject {
 @PrimaryKey


    private String id; // other fields public String getId() { return id; }
 public void setId(String id) { this.id = id; }
 } User defines this
  14. my@realm.io Old Realm-java public class ArticleRealmProxy extends Article { private

    Row row; @Override
 public String getId() { return row.get(columnInfo.columnIdIndex); }
 public void setId(String id) { row.set(columnInfo.columnIdIndex, id); }
 } Generated by annotation processor
  15. Many restrictions 1. fields must be private 2. must have

    public getters and setters 3. getters and setters must not have a logic 4. no custom methods 5. must be a subclass of RealmObject class 6. and so on... my@realm.io
  16. Annotation processor my@realm.io Bytecode manipulation How to achieve field lazy

    loading without restrictions!
  17. my@realm.io Current Realm-java public class Article extends RealmObject {
 @PrimaryKey


    public String id; // other fields } User defines this
  18. my@realm.io Current Realm-java public class ArticleRealmProxy extends Article { private

    Row row; public String realmGet$id() { return row.get(columnInfo.columnIdIndex); }
 public void realmSet$id(String id) { row.set(columnInfo.columnIdIndex, id); }
 } Generated by annotation processor
  19. my@realm.io Current Realm-java public class Article extends RealmObject { @PrimaryKey


    public String id; public String realmGet$id() { return return id; }
 public void realmSet$id(String id) { return row.set(columnInfo.columnIdIndex, id); }
 } by bytecode manipulation
  20. my@realm.io Current Realm-java RealmResults<Article> results = realm.where(Article.class).findAll();
 Article article =

    results.first();
 String title = article.title; String title = article.realmGet$title(); by bytecode manipulation
  21. Transform API my@realm.io Javassist Infrastructure

  22. my@realm.io Transform API project.android.registerTransform(new RealmTransformer(project)) in build.gradle $ ./gradlew assemble

    ... :app:compileDebugSources :app:prePackageMarkerForDebug :app:unzipJacocoAgent :app:transformClassesWithJacocoForDebug :app:transformClassesWithRealmTransformerForDebug :app:transformClassesWithDexForDebug ...
  23. my@realm.io Transform API @Beta public abstract class Transform { //

    many other methods here public void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException { } }
  24. my@realm.io Transform API class RealmTransformer extends Transform { @Override public

    void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException { Context context = transformInvocation.getContext(); Collection<TransformInput> inputs = transformInvocation.getInputs(); Collection<TransformInput> referencedInputs; referencedInputs = transformInvocation.getReferencedInputs(); TransformOutputProvider outputProvider = transformInvocation.getOutputProvider(); boolean isIncremental = transformInvocation.isIncremental(); ... } }
  25. my@realm.io Javassist Low level: ASM, Apache commons BCEL Middle level

    : javassist, CGLIB High level : Retro lambda, AspectJ many similar libraries
  26. my@realm.io Javassist inputModelClasses.each { BytecodeModifier.addRealmAccessors(it) } inputClassNames.each { def ctClass

    = classPool.getCtClass(it) BytecodeModifier.useRealmAccessors(ctClass, allManagedFields, allModelClasses) ctClass.writeFile(getOutputFile(outputProvider).canonicalPath) }
  27. my@realm.io Javassist public static void addRealmAccessors(CtClass clazz) {
 def methods

    = clazz.getDeclaredMethods()*.name
 clazz.declaredFields.each { CtField field ->
 if (!Modifier.isStatic(field.getModifiers()) && !field.hasAnnotation(Ignore.class)) {
 if (!methods.contains("realmGet\$${field.name}")) {
 clazz.addMethod(CtNewMethod.getter("realmGet\$${field.name}", field))
 }
 if (!methods.contains("realmSet\$${field.name}")) {
 clazz.addMethod(CtNewMethod.setter("realmSet\$${field.name}", field))
 }
 }
 }
 }
  28. my@realm.io Javassist public static void useRealmAccessors(CtClass clazz, List<CtField> managedFields, List<CtClass>

    modelClasses) {
 clazz.getDeclaredBehaviors().each { behavior ->
 if ((
 behavior instanceof CtMethod &&
 !behavior.name.startsWith('realmGet$') &&
 !behavior.name.startsWith('realmSet$')
 ) || (
 behavior instanceof CtConstructor &&
 !modelClasses.contains(clazz)
 )) {
 behavior.instrument(new FieldAccessToAccessorConverter(managedFields, clazz, behavior))
 }
 }
 }
  29. my@realm.io Javassist private static class FieldAccessToAccessorConverter extends ExprEditor {
 final

    List<CtField> managedFields
 final CtClass ctClass
 final CtBehavior behavior
 
 @Override
 void edit(FieldAccess fieldAccess) throws CannotCompileException {
 def isRealmFieldAccess = managedFields.find {
 fieldAccess.className.equals(it.declaringClass.name) && fieldAccess.fieldName.equals(it.name)
 }
 if (isRealmFieldAccess != null) {
 def fieldName = fieldAccess.fieldName
 if (fieldAccess.isReader()) {
 fieldAccess.replace('$_ = $0.realmGet$' + fieldName + '();')
 } else if (fieldAccess.isWriter()) {
 fieldAccess.replace('$0.realmSet$' + fieldName + '($1);')
 }
 }
 }
 }
  30. You can find everything in https://github.com/realm/realm-java my@realm.io