transformation.kt

/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * 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 androidx.lifecycle

import androidx.lifecycle.model.AdapterClass
import androidx.lifecycle.model.EventMethod
import androidx.lifecycle.model.EventMethodCall
import androidx.lifecycle.model.InputModel
import androidx.lifecycle.model.LifecycleObserverInfo
import com.google.common.collect.HashMultimap
import javax.annotation.processing.ProcessingEnvironment
import javax.lang.model.element.TypeElement
import javax.tools.Diagnostic

private fun mergeAndVerifyMethods(
    processingEnv: ProcessingEnvironment,
    type: TypeElement,
    classMethods: List<EventMethod>,
    parentMethods: List<EventMethod>
): List<EventMethod> {
    // need to update parent methods like that because:
    // 1. visibility can be expanded
    // 2. we want to preserve order
    val updatedParentMethods = parentMethods.map { parentMethod ->
        val overrideMethod = classMethods.find { (method) ->
            processingEnv.elementUtils.overrides(method, parentMethod.method, type)
        }
        if (overrideMethod != null) {
            if (overrideMethod.onLifecycleEvent != parentMethod.onLifecycleEvent) {
                processingEnv.messager.printMessage(Diagnostic.Kind.ERROR,
                        ErrorMessages.INVALID_STATE_OVERRIDE_METHOD, overrideMethod.method)
            }
            overrideMethod
        } else {
            parentMethod
        }
    }
    return updatedParentMethods + classMethods.filterNot { updatedParentMethods.contains(it) }
}

fun flattenObservers(
    processingEnv: ProcessingEnvironment,
    world: Map<TypeElement, LifecycleObserverInfo>
): List<LifecycleObserverInfo> {
    val flattened: MutableMap<LifecycleObserverInfo, LifecycleObserverInfo> = mutableMapOf()

    fun traverse(observer: LifecycleObserverInfo) {
        if (observer in flattened) {
            return
        }
        if (observer.parents.isEmpty()) {
            flattened[observer] = observer
            return
        }
        observer.parents.forEach(::traverse)
        val methods = observer.parents
                .map(flattened::get)
                .fold(emptyList<EventMethod>()) { list, parentObserver ->
                    mergeAndVerifyMethods(processingEnv, observer.type,
                            parentObserver!!.methods, list)
                }

        flattened[observer] = LifecycleObserverInfo(observer.type,
                mergeAndVerifyMethods(processingEnv, observer.type, observer.methods, methods))
    }

    world.values.forEach(::traverse)
    return flattened.values.toList()
}

private fun needsSyntheticAccess(type: TypeElement, eventMethod: EventMethod): Boolean {
    val executable = eventMethod.method
    return type.getPackageQName() != eventMethod.packageName() &&
            (executable.isPackagePrivate() || executable.isProtected())
}

private fun validateMethod(
    processingEnv: ProcessingEnvironment,
    world: InputModel,
    type: TypeElement,
    eventMethod: EventMethod
): Boolean {
    if (!needsSyntheticAccess(type, eventMethod)) {
        // no synthetic calls - no problems
        return true
    }

    if (world.isRootType(eventMethod.type)) {
        // we will generate adapters for them, so we can generate all accessors
        return true
    }

    if (world.hasSyntheticAccessorFor(eventMethod)) {
        // previously generated adapter already has synthetic
        return true
    }

    processingEnv.messager.printMessage(Diagnostic.Kind.WARNING,
            ErrorMessages.failedToGenerateAdapter(type, eventMethod), type)
    return false
}

fun transformToOutput(
    processingEnv: ProcessingEnvironment,
    world: InputModel
): List<AdapterClass> {
    val flatObservers = flattenObservers(processingEnv, world.observersInfo)
    val syntheticMethods = HashMultimap.create<TypeElement, EventMethodCall>()
    val adapterCalls = flatObservers
            // filter out everything that arrived from jars
            .filter { (type) -> world.isRootType(type) }
            // filter out if it needs SYNTHETIC access and we can't generate adapter for it
            .filter { (type, methods) ->
                methods.all { eventMethod ->
                    validateMethod(processingEnv, world, type, eventMethod)
                }
            }
            .map { (type, methods) ->
                val calls = methods.map { eventMethod ->
                    if (needsSyntheticAccess(type, eventMethod)) {
                        EventMethodCall(eventMethod, eventMethod.type)
                    } else {
                        EventMethodCall(eventMethod)
                    }
                }
                calls.filter { it.syntheticAccess != null }.forEach { eventMethod ->
                    syntheticMethods.put(eventMethod.method.type, eventMethod)
                }
                type to calls
            }.toMap()

    return adapterCalls
            .map { (type, calls) ->
                val methods = syntheticMethods.get(type) ?: emptySet()
                val synthetic = methods.map { eventMethod -> eventMethod!!.method.method }.toSet()
                AdapterClass(type, calls, synthetic)
            }
}