DeclarationCollector.kt

/*
 * Copyright 2021 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.room.compiler.processing

/**
 * see [XTypeElement.getAllFieldsIncludingPrivateSupers]
 */
internal fun collectFieldsIncludingPrivateSupers(
    xTypeElement: XTypeElement
): Sequence<XFieldElement> {
    return sequence {
        val existingFieldNames = mutableSetOf<String>()
        suspend fun SequenceScope<XFieldElement>.yieldAllFields(type: XTypeElement) {
            // yield all fields declared directly on this type
            type.getDeclaredFields()
                .filter { existingFieldNames.add(it.name) }
                .forEach { yield(it) }
            // visit all declared fields on super types
            type.superClass?.typeElement?.let { parent ->
                yieldAllFields(parent)
            }
        }
        yieldAllFields(xTypeElement)
    }
}

/**
 * see [XTypeElement.getAllMethods]
 */
internal fun collectAllMethods(
    xTypeElement: XTypeElement
): Sequence<XMethodElement> {
    return sequence {
        // group methods by name for faster override checks. Note that we are using name here
        // instead of jvmName because resolving jvmName is expensive and @JvmName is not allowed on
        // overriding methods or open methods (unless suppressed, which we don't support).
        // As a result of this, name is as good as jvmName for grouping.
        val methodsByName = mutableMapOf<String, LinkedHashSet<XMethodElement>>()
        val visitedInterfaces = mutableSetOf<XTypeElement>()
        fun collectAllMethodsByName(type: XTypeElement) {
            // First, visit all super interface methods.
            type.getSuperInterfaceElements().forEach {
                // Skip if we've already visited the methods in this interface.
                if (visitedInterfaces.add(it)) {
                    collectAllMethodsByName(it)
                }
            }
            // Next, visit all super class methods.
            type.superClass?.typeElement?.let {
                collectAllMethodsByName(it)
            }
            // Finally, visit all methods declared in this type.
            if (type == xTypeElement) {
                type.getDeclaredMethods().forEach {
                    methodsByName.getOrPut(it.name) { linkedSetOf() }.add(it)
                }
            } else {
                type.getDeclaredMethods()
                    .filter { it.isAccessibleFrom(xTypeElement.packageName) }
                    .filterNot { it.isStaticInterfaceMethod() }
                    .forEach {
                        methodsByName.getOrPut(it.name) { linkedSetOf() }.add(it)
                    }
            }
        }
        collectAllMethodsByName(xTypeElement)

        // Yield all non-overridden methods
        methodsByName.values.forEach { methodSet ->
            if (methodSet.size == 1) {
                // There's only one method with this name, so it can't be overridden
                yield(methodSet.single())
            } else {
                // There are multiple methods with the same name, so we must check for overridden
                // methods. The order of the methods should guarantee that any potentially
                // overridden method comes first in the list, so we only need to check each method
                // against subsequent methods.
                val methods = methodSet.toList()
                val overridden = mutableSetOf<XMethodElement>()
                methods.forEachIndexed { i, methodOne ->
                    methods.subList(i + 1, methods.size).forEach { methodTwo ->
                        if (methodTwo.overrides(methodOne, xTypeElement)) {
                            overridden.add(methodOne)
                            // Once we've added methodOne, we can break out of this inner loop since
                            // additional checks would only try to add methodOne again.
                            return@forEachIndexed
                        }
                    }
                }
                methods.filterNot { overridden.contains(it) }.forEach { yield(it) }
            }
        }
    }
}

private fun XMethodElement.isAccessibleFrom(packageName: String): Boolean {
    if (isPublic() || isProtected()) {
        return true
    }
    if (isPrivate()) {
        return false
    }
    // check package
    return packageName == closestMemberContainer.className.packageName()
}

private fun XMethodElement.isStaticInterfaceMethod(): Boolean {
    return isStatic() && (enclosingElement as? XTypeElement)?.isInterface() == true
}