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

Bytecode manipulation in Realm How and why?

zaki50
August 18, 2016

Bytecode manipulation in Realm How and why?

zaki50

August 18, 2016
Tweet

More Decks by zaki50

Other Decks in Technology

Transcript

  1. Bytecode manipulation in Realm
    How and why?
    Tokyo Android Meetup - August Edition
    [email protected]

    View full-size slide

  2. Makoto Yamazaki
    Realm Inc. / uPhyca Inc.
    [email protected]
    @zaki50

    View full-size slide

  3. [email protected]
    Bytecode manipulation is...
    modifying the bytecode at compile time
    or at the loading of the classes.
    Bytecode is the instruction set of JVM.

    View full-size slide

  4. [email protected]
    Realm is mobile database
    Runs directly inside the phones, tablets,
    wearables

    View full-size slide

  5. Fast
    [email protected]
    Advanced
    Easy
    Why mobile database?

    View full-size slide

  6. [email protected]
    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 comments;

    }

    View full-size slide

  7. [email protected]
    Realm is Fast
    Lazy loading

    View full-size slide

  8. [email protected]
    Realm is Fast
    RealmQuery query = realm.where(Article.class);
    query = query.contains("title", "realm");
    RealmResults 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.


    View full-size slide

  9. Annotation processing
    [email protected]
    Bytecode manipulation
    How to achieve
    lazy loading of field values

    View full-size slide

  10. [email protected]
    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

    View full-size slide

  11. [email protected]
    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

    View full-size slide

  12. 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...
    [email protected]

    View full-size slide

  13. Annotation processor
    [email protected]
    Bytecode manipulation
    How to achieve field lazy loading
    without restrictions!

    View full-size slide

  14. [email protected]
    Current Realm-java
    public class Article extends RealmObject {

    @PrimaryKey

    public String id;
    // other fields
    }
    User defines this

    View full-size slide

  15. [email protected]
    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

    View full-size slide

  16. [email protected]
    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

    View full-size slide

  17. [email protected]
    Current Realm-java
    RealmResults results = realm.where(Article.class).findAll();

    Article article = results.first();

    String title = article.title;
    String title = article.realmGet$title();
    by bytecode manipulation

    View full-size slide

  18. Transform API
    [email protected]
    Javassist
    Infrastructure

    View full-size slide

  19. [email protected]
    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
    ...

    View full-size slide

  20. [email protected]
    Transform API
    @Beta
    public abstract class Transform {
    // many other methods here
    public void transform(TransformInvocation transformInvocation)
    throws TransformException,
    InterruptedException,
    IOException {
    }
    }

    View full-size slide

  21. [email protected]
    Transform API
    class RealmTransformer extends Transform {
    @Override
    public void transform(TransformInvocation transformInvocation)
    throws TransformException,
    InterruptedException,
    IOException {
    Context context = transformInvocation.getContext();
    Collection inputs = transformInvocation.getInputs();
    Collection referencedInputs;
    referencedInputs = transformInvocation.getReferencedInputs();
    TransformOutputProvider outputProvider = transformInvocation.getOutputProvider();
    boolean isIncremental = transformInvocation.isIncremental();
    ...
    }
    }

    View full-size slide

  22. [email protected]
    Javassist
    Low level: ASM, Apache commons BCEL
    Middle level : javassist, CGLIB
    High level : Retro lambda, AspectJ
    many similar libraries

    View full-size slide

  23. [email protected]
    Javassist
    inputModelClasses.each {
    BytecodeModifier.addRealmAccessors(it)
    }
    inputClassNames.each {
    def ctClass = classPool.getCtClass(it)
    BytecodeModifier.useRealmAccessors(ctClass,
    allManagedFields, allModelClasses)
    ctClass.writeFile(getOutputFile(outputProvider).canonicalPath)
    }

    View full-size slide

  24. [email protected]
    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))

    }

    }

    }

    }

    View full-size slide

  25. [email protected]
    Javassist
    public static void useRealmAccessors(CtClass clazz,
    List managedFields,
    List 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))

    }

    }

    }

    View full-size slide

  26. [email protected]
    Javassist
    private static class FieldAccessToAccessorConverter extends ExprEditor {

    final List 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);')

    }

    }

    }

    }

    View full-size slide

  27. You can find everything in
    https://github.com/realm/realm-java
    [email protected]

    View full-size slide