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

io.realm.transformer.build.FullBuild.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.build

import com.android.SdkConstants
import com.android.build.api.transform.Format
import com.android.build.api.transform.TransformInput
import com.android.build.api.transform.TransformOutputProvider
import io.realm.transformer.BytecodeModifier
import io.realm.transformer.RealmTransformer
import io.realm.transformer.ext.safeSubtypeOf
import io.realm.transformer.logger
import javassist.CtClass
import javassist.CtField
import org.gradle.api.Project
import java.io.File
import java.util.jar.JarFile

class FullBuild(project: Project, outputProvider: TransformOutputProvider, transformer: RealmTransformer)
    : BuildTemplate(project, outputProvider, transformer) {

    private val allModelClasses: ArrayList = arrayListOf()

    override fun prepareOutputClasses(inputs: MutableCollection) {
        this.inputs = inputs;
        categorizeClassNames(inputs, outputClassNames, outputReferencedClassNames)
        logger.debug("Full build. Files being processed: ${outputClassNames.size}.")
    }

    override fun categorizeClassNames(inputs: Collection,
                                      directoryFiles: MutableSet,
                                      jarFiles: MutableSet) {
        inputs.forEach {
            it.directoryInputs.forEach {
                val dirPath: String = it.file.absolutePath
                // Non-incremental build: Include all files
                it.file.walkTopDown().forEach {
                    if (it.isFile) {
                        if (it.absolutePath.endsWith(SdkConstants.DOT_CLASS)) {
                            val className: String = it.absolutePath
                                    .substring(dirPath.length + 1, it.absolutePath.length - SdkConstants.DOT_CLASS.length)
                                    .replace(File.separatorChar, '.')
                            directoryFiles.add(className)
                        }
                    }
                }
            }

            it.jarInputs.forEach {
                val jarFile = JarFile(it.file)
                jarFile.entries()
                        .toList()
                        .filter {
                            !it.isDirectory && it.name.endsWith(SdkConstants.DOT_CLASS)
                        }
                        .forEach {
                            val path: String = it.name
                            // The jar might not using File.separatorChar as the path separator. So we just replace both `\` and
                            // `/`. It depends on how the jar file was created.
                            // See http://stackoverflow.com/questions/13846000/file-separators-of-path-name-of-zipentry
                            val className: String = path
                                    .substring(0, path.length - SdkConstants.DOT_CLASS.length)
                                    .replace('/', '.')
                                    .replace('\\', '.')
                            jarFiles.add(className)
                        }
                jarFile.close() // Crash transformer if this fails
            }
        }
    }

    override fun findModelClasses(classNames: Set): Collection {
        val realmObjectProxyInterface: CtClass = classPool.get("io.realm.internal.RealmObjectProxy")

        // For full builds, we are currently finding model classes by assuming that only
        // the annotation processor is generating files ending with `RealmProxy`. This is
        // a lot faster as we only need to compare the name of the type before we load
        // the CtClass.
        // Find the model classes
        return classNames
            // Quick and loose filter where we assume that classes ending with RealmProxy are
            // a Realm model proxy class generated by the annotation processor. This can
            // produce false positives: https://github.com/realm/realm-java/issues/3709
            .filter { it.endsWith("RealmProxy") }
            .mapNotNull {
                // Verify the file is in fact a proxy class, in which case the super
                // class is always present and is the real model class.
                val clazz: CtClass = classPool.getCtClass(it)
                if (clazz.safeSubtypeOf(realmObjectProxyInterface)) {
                    return@mapNotNull clazz.superclass;
                } else {
                    return@mapNotNull null
                }
            }
    }

    override fun filterForModelClasses(classNames: Set, extraClassNames: Set) {

        val allClassNames: Set = merge(classNames, extraClassNames)

        allModelClasses.addAll(findModelClasses(allClassNames))

        outputModelClasses.addAll(allModelClasses.filter {
            outputClassNames.contains(it.name)
        })
    }

    override fun transformDirectAccessToModelFields() {
        // Populate a list of the fields that need to be managed with bytecode manipulation
        val allManagedFields: ArrayList = arrayListOf()
        allModelClasses.forEach {
            allManagedFields.addAll(it.declaredFields.filter {
                BytecodeModifier.isModelField(it)
            })
        }
        logger.debug("Managed Fields: ${allManagedFields.joinToString(",") { it.name }}")

        // Use accessors instead of direct field access
        outputClassNames.forEach {
            logger.debug("Modifying accessors in class: $it")
            try {
                val ctClass: CtClass = classPool.getCtClass(it)
                BytecodeModifier.useRealmAccessors(classPool, ctClass, allManagedFields)
                ctClass.writeFile(getOutputFile(outputProvider, Format.DIRECTORY).canonicalPath)
            } catch (e: Exception) {
                throw RuntimeException("Failed to transform $it.", e)
            }
        }
    }

    private fun merge(set1: Set, set2: Set): Set  {
        val merged: MutableSet = hashSetOf()
        merged.addAll(set1)
        merged.addAll(set2)
        return merged
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy