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

io.realm.transformer.build.IncrementalBuild.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.Status
import com.android.build.api.transform.TransformInput
import com.android.build.api.transform.TransformOutputProvider
import io.realm.annotations.RealmClass
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.NotFoundException
import org.gradle.api.Project
import java.io.File
import java.util.jar.JarFile

class IncrementalBuild(project: Project, outputProvider: TransformOutputProvider, transform: RealmTransformer)
    : BuildTemplate(project, outputProvider, transform) {

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

    override fun filterForModelClasses(outputClassNames: Set, outputReferencedClassNames: Set) {
        outputModelClasses.addAll(findModelClasses(outputClassNames))
    }

    override fun transformDirectAccessToModelFields() {
        // Use accessors instead of direct field access
        outputClassNames.forEach {
            logger.debug("Modify accessors in class: $it")
            val ctClass: CtClass = classPool.getCtClass(it)
            BytecodeModifier.useRealmAccessors(classPool, ctClass, null)
            ctClass.writeFile(getOutputFile(outputProvider, Format.DIRECTORY).canonicalPath)
        }
    }

    /**
     * Categorize the transform input into its two main categorizes: `directoryFiles` which are
     * source files in the current project and `jarFiles` which are source files found in jars.
     *
     * @param inputs set of input files
     * @param directoryFiles the set of files in directories getting compiled. These are candidates for the transformer.
     * @param jarFiles the set of files that are possible referenced but never transformed (required by JavaAssist).
     * @param isIncremental `true` if build is incremental.
     */
    override fun categorizeClassNames(inputs: Collection,
                             directoryFiles: MutableSet,
                             jarFiles: MutableSet) {
        inputs.forEach {
             it.directoryInputs.forEach {
                val dirPath: String = it.file.absolutePath

                 it.changedFiles.entries.forEach {
                    if (it.value == Status.NOTCHANGED || it.value == Status.REMOVED) {
                        return@forEach
                    }
                    val filePath: String = it.key.absolutePath
                    if (filePath.endsWith(SdkConstants.DOT_CLASS)) {
                        val className = filePath
                                .substring(dirPath.length + 1, filePath.length - SdkConstants.DOT_CLASS.length)
                                .replace(File.separatorChar, '.')
                        directoryFiles.add(className)
                    }
                }
            }

            it.jarInputs.forEach {
                if (it.status == Status.REMOVED) {
                    return@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 incremental builds we need to determine if a class is a model class file
        // based on information in the file itself. This require checks that are only
        // possible once we loaded the CtClass from the ClassPool and is slower
        // than the approach used when doing full builds.
        return classNames
                // Map strings to CtClass'es.
                .map { classPool.getCtClass(it) }
                // Model classes either have the @RealmClass annotation directly (if implementing RealmModel)
                // or their superclass has it (if extends RealmObject). The annotation processor
                // will have ensured the annotation is only present in these cases.
                .filter {
                    var result: Boolean
                    if (it.hasAnnotation(RealmClass::class.java)) {
                        result = true
                    } else {
                        try {
                            result = it.superclass?.hasAnnotation(RealmClass::class.java) == true
                        } catch (e: NotFoundException) {
                            // Can happen if the super class is part of the `android.jar` which might
                            // not have been loaded. In any case, any base class part of Android cannot
                            // be a Realm model class.
                            result = false
                        }
                    }
                    return@filter result
                }
                // Proxy classes are generated by the Realm Annotation Processor and might accidentally
                // pass the above check (e.g. if the model class has the @RealmClass annotation), so
                // ignore them.
                .filter { !it.safeSubtypeOf(realmObjectProxyInterface) }
                // Unfortunately the RealmObject base class passes all above checks, so explicitly
                // ignore it.
                .filter { !it.name.equals("io.realm.RealmObject") }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy