All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.realm.transformer.ByteCodeModifier.kt Maven / Gradle / Ivy

Go to download

Android Gradle Transformer for Realm. Realm is a mobile database: Build better apps, faster.

There is a newer version: 10.19.0
Show newest version
/*
 * Copyright 2018 Realm Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.realm.transformer

import io.realm.annotations.Ignore
import io.realm.annotations.RealmClass
import io.realm.transformer.ext.safeSubtypeOf
import javassist.*
import javassist.expr.ExprEditor
import javassist.expr.FieldAccess

/**
 * This class encapsulates the bytecode manipulation code needed to transform model classes
 * and the classes using them.
 */
class BytecodeModifier {

    companion object {

        fun isModelField(field: CtField): Boolean {
            return !field.hasAnnotation(Ignore::class.java) && !Modifier.isTransient(field.getModifiers()) && !Modifier.isStatic(field.getModifiers())
        }

        /**
         * Adds Realm specific accessors to a model class.
         * All the declared fields will be associated with a getter and a setter.
         *
         * @param clazz the CtClass to add accessors to.
         */
        @JvmStatic
        fun addRealmAccessors(clazz: CtClass) {
            val methods: List = clazz.declaredMethods.map { it.name }
            clazz.declaredFields.forEach { field: CtField ->
                if (isModelField(field)) {
                    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))
                    }
                }
            }
        }

        /**
         * Modifies a class replacing field accesses with the appropriate Realm accessors.
         *
         * @param clazz The CtClass to modify
         * @param managedFields List of fields whose access should be replaced
         */
        @JvmStatic
        fun useRealmAccessors(classPool: ClassPool, clazz: CtClass, managedFields: List?) {
            clazz.declaredBehaviors.forEach { behavior ->
                logger.debug("    Behavior: ${behavior.name}")
                if (
                        (
                                behavior is CtMethod &&
                                        !behavior.name.startsWith("realmGet$") &&
                                        !behavior.name.startsWith("realmSet$")
                                ) || (
                                behavior is CtConstructor
                                )
                ) {
                    if (managedFields != null) {
                        behavior.instrument(FieldAccessToAccessorConverterUsingList(managedFields, clazz, behavior))
                    } else {
                        behavior.instrument(FieldAccessToAccessorConverterUsingClassPool(classPool, clazz, behavior))
                    }

                }
            }
        }

        /**
         * Modifies a class adding its RealmProxy interface.
         *
         * @param clazz The CtClass to modify
         * @param classPool the Javassist class pool
         */
        @JvmStatic
        fun addRealmProxyInterface(clazz: CtClass, classPool: ClassPool) {
            val proxyInterface: CtClass = classPool.get("io.realm.${clazz.getName().replace(".", "_")}RealmProxyInterface")
            clazz.addInterface(proxyInterface)
        }

        fun callInjectObjectContextFromConstructors(clazz: CtClass) {
            clazz.constructors.forEach {
                it.insertBeforeBody("if ($0 instanceof io.realm.internal.RealmObjectProxy) {" +
                        " ((io.realm.internal.RealmObjectProxy) $0).realm\$injectObjectContext();" +
                        " }")
            }
        }


        /**
         * Adds a method to indicate that Realm transformer has been applied.
         *
         * @param clazz The CtClass to modify.
         */
        fun overrideTransformedMarker(clazz: CtClass) {
            logger.debug("  Realm: Marking as transformed ${clazz.simpleName}")
            try {
                clazz.getDeclaredMethod("transformerApplied")
            } catch (ignored: NotFoundException) {
                clazz.addMethod(CtNewMethod.make(Modifier.PUBLIC, CtClass.booleanType, "transformerApplied",
                        arrayOf(), arrayOf(), "{return true;}", clazz))
            }
        }

    }

    /**
     * This class goes through all the field access behaviours of a class and replaces field accesses with
     * the appropriate accessor.
     */
    private class FieldAccessToAccessorConverterUsingList(val managedFields: List,
                                                          val ctClass: CtClass,
                                                          val behaviour: CtBehavior) : ExprEditor() {

        @Throws(CannotCompileException::class)
        override fun edit(fieldAccess: FieldAccess) {
            logger.debug("      Field being accessed: ${fieldAccess.className}.${fieldAccess.fieldName}")
            managedFields.find {
                fieldAccess.className.equals(it.declaringClass.name) && fieldAccess.fieldName.equals(it.name)
            }?.run {
                logger.debug("        Realm: Manipulating ${ctClass.simpleName}.${behaviour.name}(): ${fieldAccess.fieldName}")
                logger.debug("        Methods: ${ctClass.declaredMethods}")
                val fieldName: String = fieldAccess.fieldName
                if (fieldAccess.isReader) {
                    fieldAccess.replace("\$_ = $0.realmGet\$$fieldName();")
                } else if (fieldAccess.isWriter()) {
                    fieldAccess.replace("\$0.realmSet\$$fieldName(\$1);")
                }
            }
        }
    }

    /**
     * This class goes through all the field access behaviours of a class and replaces field accesses with
     * the appropriate accessor.
     */
    private class FieldAccessToAccessorConverterUsingClassPool(val classPool: ClassPool,
                                                               val ctClass: CtClass,
                                                               val behaviour: CtBehavior) : ExprEditor() {

        val realmObjectProxyInterface: CtClass = classPool.get("io.realm.internal.RealmObjectProxy")

        @Throws(CannotCompileException::class)
        override fun edit(fieldAccess: FieldAccess) {
            logger.debug("      Field being accessed: ${fieldAccess.className}.${fieldAccess.fieldName}")

            val fieldAccessCtClass: CtClass? = try { classPool.get(fieldAccess.className) } catch (e: NotFoundException) { null }
            if (fieldAccessCtClass != null && isRealmModelClass(fieldAccessCtClass) && isModelField(fieldAccess.field)) {
                logger.debug("        Realm: Manipulating ${ctClass.simpleName}.${behaviour.name}(): ${fieldAccess.fieldName}")
                logger.debug("        Methods: ${ctClass.declaredMethods}")

                // make sure accessors are added, otherwise javassist will fail with
                // javassist.CannotCompileException: [source error] realmGet$id() not found in 'foo.Model'
                addRealmAccessors(fieldAccessCtClass)

                val fieldName: String = fieldAccess . fieldName
                if (fieldAccess.isReader) {
                    fieldAccess.replace("\$_ = \$0.realmGet\$$fieldName();")
                } else if (fieldAccess.isWriter) {
                    fieldAccess.replace("\$0.realmSet\$$fieldName(\$1);")
                }
            }
        }

        fun isRealmModelClass(clazz: CtClass): Boolean {
            return arrayOf(clazz).filter {
                if (it.hasAnnotation(RealmClass::class.java)) {
                    return true
                } else {
                    try {
                        return it.superclass?.hasAnnotation(RealmClass::class.java) == true
                    } catch (ignored: NotFoundException) {
                        return false
                    }
                }
            }
            .filter { !it.safeSubtypeOf(realmObjectProxyInterface) }
            .any { it.name != "io.realm.RealmObject" }
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy