Centralize method / field collection
This CL replaces MethodCollector with a generic DeclarationCollector
that can be re-used for collecting declarations from a type element
and its parents. This new class is also used to collect fields instead
of the custom implementations in each XTypeElement (javac, ksp).
As part of it, I also fixed the JavacTypeElement implementation not to
include enum constant in declared fields list.
In a followup CL, i'll change those methods to return sequence instead
of list in XTypeElement to avoid collecting too many methods.
Bug: n/a
Test: existing tests
Change-Id: Ia805c862a0a841edc8f50a84d1ad7d17399fb660
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/DeclarationCollector.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/DeclarationCollector.kt
new file mode 100644
index 0000000..792a88d
--- /dev/null
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/DeclarationCollector.kt
@@ -0,0 +1,166 @@
+/*
+ * 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
+
+/**
+ * Helper method to collect declarations from a type element and its parents.
+ *
+ * To be able to benefit from multi level caching (e.g. each type element caching its important
+ * fields / declarations), parents are not visited recursively. Instead, callers are expected to
+ * call the right methods when traversing immediate parents / interfaces.
+ */
+private fun <T> collectDeclarations(
+ /**
+ * The root element
+ */
+ target: XTypeElement,
+ /**
+ * Elements that will be added without verification. These are usually inherited from the
+ * root element.
+ */
+ initialList: List<T>,
+ /**
+ * Returns a list of possible elements that can be included in the final list.
+ * The receiver is either a super class or interface.
+ */
+ getCandidateDeclarations: XTypeElement.() -> Iterable<T>,
+ /**
+ * Returns a partitan key for each element to optimize override checks etc.
+ */
+ getPartitionKey: T.() -> String,
+ /**
+ * Returns true if this item should be added to the list, false otherwise.
+ */
+ isAcceptable: (candidate: T, existing: List<T>) -> Boolean,
+ /**
+ * Copies the element to the root element
+ */
+ copyToTarget: (T) -> T,
+ /**
+ * If true, parent class will be visited.
+ */
+ visitParent: Boolean,
+ /**
+ * If true, parent interfaces will be visited.
+ */
+ visitInterfaces: Boolean
+): Sequence<T> {
+ val parents = sequence {
+ if (visitParent) {
+ target.superType?.typeElement?.let {
+ yield(it)
+ }
+ }
+ if (visitInterfaces) {
+ yieldAll(target.getSuperInterfaceElements())
+ }
+ }
+ return sequence {
+ // group members by name for faster override checks
+ val selectionByName = mutableMapOf<String, MutableList<T>>()
+ suspend fun SequenceScope<T>.addToSelection(item: T) {
+ val key = getPartitionKey(item)
+ selectionByName.getOrPut(key) {
+ mutableListOf()
+ }.add(item)
+ yield(item)
+ }
+
+ suspend fun SequenceScope<T>.maybeAddToSelection(candidate: T) {
+ val partitionKey = candidate.getPartitionKey()
+ val existing = selectionByName[partitionKey] ?: emptyList()
+ if (isAcceptable(candidate, existing)) {
+ addToSelection(copyToTarget(candidate))
+ }
+ }
+
+ // yield everything in the root list
+ initialList.forEach {
+ addToSelection(it)
+ }
+ // now start yielding it from parents
+ parents.flatMap { it.getCandidateDeclarations() }.forEach {
+ maybeAddToSelection(copyToTarget(it))
+ }
+ }
+}
+
+private fun XTypeElement.canAccessSuperMethod(other: XMethodElement): Boolean {
+ if (other.isPublic() || other.isProtected()) {
+ return true
+ }
+ if (other.isPrivate()) {
+ return false
+ }
+ // check package
+ return packageName == other.enclosingElement.className.packageName()
+}
+
+/**
+ * see [XTypeElement.getAllFieldsIncludingPrivateSupers]
+ */
+internal fun collectFieldsIncludingPrivateSupers(
+ xTypeElement: XTypeElement
+): Sequence<XFieldElement> {
+ return collectDeclarations(
+ target = xTypeElement,
+ visitParent = true,
+ visitInterfaces = false,
+ getPartitionKey = XFieldElement::name,
+ initialList = xTypeElement.getDeclaredFields(),
+ copyToTarget = {
+ it.copyTo(xTypeElement)
+ },
+ isAcceptable = { _, existing ->
+ existing.isEmpty()
+ },
+ getCandidateDeclarations = XTypeElement::getAllFieldsIncludingPrivateSupers
+ )
+}
+
+/**
+ * see [XTypeElement.getAllMethods]
+ */
+internal fun collectAllMethods(
+ xTypeElement: XTypeElement
+): Sequence<XMethodElement> {
+ return collectDeclarations(
+ target = xTypeElement,
+ visitParent = true,
+ visitInterfaces = true,
+ getPartitionKey = XMethodElement::name,
+ initialList = xTypeElement.getDeclaredMethods(),
+ copyToTarget = {
+ it.copyTo(xTypeElement)
+ },
+ isAcceptable = { candidate, existing ->
+ when {
+ // my method, accept all
+ candidate.enclosingElement == xTypeElement -> true
+ // cannot access, reject
+ !xTypeElement.canAccessSuperMethod(candidate) -> false
+ // static in an interface
+ candidate.isStatic() &&
+ (candidate.enclosingElement as? XTypeElement)?.isInterface() == true ->
+ false
+ // accept if not overridden
+ else -> existing.none { it.overrides(candidate, xTypeElement) }
+ }
+ },
+ getCandidateDeclarations = XTypeElement::getAllMethods,
+ )
+}
\ No newline at end of file
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/MethodCollector.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/MethodCollector.kt
deleted file mode 100644
index 7cac32a..0000000
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/MethodCollector.kt
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright 2020 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
-
-/**
- * Helper class to collect all methods of an [XTypeElement] to implement
- * [XTypeElement.getAllMethods].
- */
-private class MethodCollector(
- val target: XTypeElement
-) {
- // group methods by name for fast overrides check
- private val selectionByName = mutableMapOf<String, MutableList<XMethodElement>>()
-
- // we keep a duplicate list to preserve declaration order, makes the generated code match
- // user code
- private val selection = mutableListOf<XMethodElement>()
-
- fun collect() {
- val selection = target.getDeclaredMethods().forEach(::addToSelection)
-
- target.superType
- ?.typeElement
- ?.getAllMethods()
- ?.forEach(::addIfNotOverridden)
- target.getSuperInterfaceElements().forEach {
- it.getAllMethods().forEach {
- if (!it.isStatic()) {
- addIfNotOverridden(it)
- }
- }
- }
- return selection
- }
-
- fun getResult(): List<XMethodElement> {
- return selection
- }
-
- private fun addIfNotOverridden(candidate: XMethodElement) {
- if (!target.canAccessSuperMethod(candidate)) {
- return
- }
- val overridden = selectionByName[candidate.name]?.any { existing ->
- existing.overrides(candidate, target)
- } ?: false
- if (!overridden) {
- addToSelection(candidate.copyTo(target))
- }
- }
-
- private fun addToSelection(method: XMethodElement) {
- selectionByName.getOrPut(method.name) {
- mutableListOf()
- }.add(method)
- selection.add(method)
- }
-
- private fun XTypeElement.canAccessSuperMethod(other: XMethodElement): Boolean {
- if (other.isPublic() || other.isProtected()) {
- return true
- }
- if (other.isPrivate()) {
- return false
- }
- // check package
- return packageName == other.enclosingElement.className.packageName()
- }
-}
-
-internal fun XTypeElement.collectAllMethods(): List<XMethodElement> {
- val collector = MethodCollector(this)
- collector.collect()
- return collector.getResult()
-}
\ No newline at end of file
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XFieldElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XFieldElement.kt
index 6d9540f..f25053d 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XFieldElement.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XFieldElement.kt
@@ -36,4 +36,6 @@
override val fallbackLocationText: String
get() = "$name in ${enclosingElement.fallbackLocationText}"
+
+ fun copyTo(newContainer: XTypeElement): XFieldElement
}
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XTypeElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XTypeElement.kt
index 90fe22c..9773a05 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XTypeElement.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XTypeElement.kt
@@ -140,7 +140,7 @@
* does not include static methods in parent interfaces
*/
fun getAllMethods(): List<XMethodElement> {
- return collectAllMethods()
+ return collectAllMethods(this).toList()
}
/**
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/ElementExt.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/ElementExt.kt
index d9bee51..14e4b06 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/ElementExt.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/ElementExt.kt
@@ -18,14 +18,7 @@
import androidx.room.compiler.processing.XNullability
import com.google.auto.common.MoreElements
-import com.google.auto.common.MoreTypes
import javax.lang.model.element.Element
-import javax.lang.model.element.ElementKind
-import javax.lang.model.element.TypeElement
-import javax.lang.model.element.VariableElement
-import javax.lang.model.type.TypeKind
-import javax.lang.model.util.ElementFilter
-import javax.lang.model.util.Elements
private val NONNULL_ANNOTATIONS = arrayOf(
androidx.annotation.NonNull::class.java,
@@ -37,36 +30,6 @@
org.jetbrains.annotations.Nullable::class.java
)
-/**
- * Returns all fields including private fields (including private fields in super). Removes
- * duplicate fields if class has a field with the same name as the parent.
- * Note that enum constants are not included in the list even thought they are fields in java.
- * To access enum constants, use [JavacTypeElement.JavacEnumTypeElement].
- */
-internal fun TypeElement.getAllFieldsIncludingPrivateSupers(
- elementUtils: Elements
-): Set<VariableElement> {
- val selection = ElementFilter
- .fieldsIn(elementUtils.getAllMembers(this))
- .filterIsInstance<VariableElement>()
- .filterNot { it.kind == ElementKind.ENUM_CONSTANT }
- .toMutableSet()
- val selectionNames = selection.mapTo(mutableSetOf()) {
- it.simpleName
- }
- if (superclass.kind != TypeKind.NONE) {
- val superFields = MoreTypes.asTypeElement(superclass)
- .getAllFieldsIncludingPrivateSupers(elementUtils)
- // accept super fields only if the name does not conflict
- superFields.forEach { superField ->
- if (selectionNames.add(superField.simpleName)) {
- selection.add(superField)
- }
- }
- }
- return selection
-}
-
@Suppress("UnstableApiUsage")
private fun Element.hasAnyOf(annotations: Array<Class<out Annotation>>) = annotations.any {
MoreElements.isAnnotationPresent(this, it)
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacFieldElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacFieldElement.kt
index dca2f56..3f0b3ba 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacFieldElement.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacFieldElement.kt
@@ -41,4 +41,15 @@
override val enclosingElement: XTypeElement by lazy {
element.requireEnclosingType(env)
}
+
+ override fun copyTo(newContainer: XTypeElement): JavacFieldElement {
+ check(newContainer is JavacTypeElement) {
+ "Unexpected container (${newContainer::class}), expected JavacTypeElement"
+ }
+ return JavacFieldElement(
+ env = env,
+ containing = newContainer,
+ element = element
+ )
+ }
}
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacTypeElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacTypeElement.kt
index 85189bb..36d311f 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacTypeElement.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacTypeElement.kt
@@ -22,6 +22,7 @@
import androidx.room.compiler.processing.XHasModifiers
import androidx.room.compiler.processing.XMethodElement
import androidx.room.compiler.processing.XTypeElement
+import androidx.room.compiler.processing.collectFieldsIncludingPrivateSupers
import androidx.room.compiler.processing.javac.kotlin.KotlinMetadataElement
import com.google.auto.common.MoreElements
import com.google.auto.common.MoreTypes
@@ -59,13 +60,15 @@
}
private val _declaredFields by lazy {
- ElementFilter.fieldsIn(element.enclosedElements).map {
- JavacFieldElement(
- env = env,
- element = it,
- containing = this
- )
- }
+ ElementFilter.fieldsIn(element.enclosedElements)
+ .filterNot { it.kind == ElementKind.ENUM_CONSTANT }
+ .map {
+ JavacFieldElement(
+ env = env,
+ element = it,
+ containing = this
+ )
+ }
}
override fun getDeclaredFields(): List<XFieldElement> {
@@ -73,15 +76,7 @@
}
private val _allFieldsIncludingPrivateSupers by lazy {
- element.getAllFieldsIncludingPrivateSupers(
- env.elementUtils
- ).map {
- JavacFieldElement(
- env = env,
- element = it,
- containing = this
- )
- }
+ collectFieldsIncludingPrivateSupers(this).toList()
}
override fun getAllFieldsIncludingPrivateSupers(): List<XFieldElement> {
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspFieldElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspFieldElement.kt
index 11a870b..689698e 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspFieldElement.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspFieldElement.kt
@@ -20,6 +20,7 @@
import androidx.room.compiler.processing.XFieldElement
import androidx.room.compiler.processing.XHasModifiers
import androidx.room.compiler.processing.XType
+import androidx.room.compiler.processing.XTypeElement
import androidx.room.compiler.processing.ksp.KspAnnotated.UseSiteFilter.Companion.NO_USE_SITE_OR_FIELD
import com.google.devtools.ksp.symbol.KSPropertyDeclaration
@@ -63,11 +64,16 @@
)
}
- fun copyTo(newContaining: KspTypeElement) = KspFieldElement(
- env = env,
- declaration = declaration,
- containing = newContaining
- )
+ override fun copyTo(newContainer: XTypeElement): KspFieldElement {
+ check(newContainer is KspTypeElement) {
+ "Unexpected container (${newContainer::class}), expected KspTypeElement"
+ }
+ return KspFieldElement(
+ env = env,
+ declaration = declaration,
+ containing = newContainer
+ )
+ }
companion object {
fun create(
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeElement.kt
index e58d3cc..1138d76 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeElement.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeElement.kt
@@ -25,10 +25,10 @@
import androidx.room.compiler.processing.XMethodElement
import androidx.room.compiler.processing.XType
import androidx.room.compiler.processing.XTypeElement
+import androidx.room.compiler.processing.collectFieldsIncludingPrivateSupers
import androidx.room.compiler.processing.ksp.KspAnnotated.UseSiteFilter.Companion.NO_USE_SITE
import androidx.room.compiler.processing.ksp.synthetic.KspSyntheticPropertyMethodElement
import androidx.room.compiler.processing.tryBox
-import com.google.devtools.ksp.getAllSuperTypes
import com.google.devtools.ksp.getConstructors
import com.google.devtools.ksp.getDeclaredFunctions
import com.google.devtools.ksp.getDeclaredProperties
@@ -37,7 +37,6 @@
import com.google.devtools.ksp.isPrivate
import com.google.devtools.ksp.symbol.ClassKind
import com.google.devtools.ksp.symbol.KSClassDeclaration
-import com.google.devtools.ksp.symbol.KSPropertyDeclaration
import com.google.devtools.ksp.symbol.Modifier
import com.google.devtools.ksp.symbol.Origin
import com.squareup.javapoet.ClassName
@@ -134,32 +133,7 @@
}
private val _declaredFieldsIncludingSupers by lazy {
- // Read all properties from all supers and select the ones that are not overridden.
- val myPropertyFields = getDeclaredFields()
- val selectedNames = myPropertyFields.mapTo(mutableSetOf()) {
- it.name
- }
- val selection = mutableListOf<KSPropertyDeclaration>()
- declaration.getAllSuperTypes().map {
- it.declaration
- }.filterIsInstance(KSClassDeclaration::class.java)
- .filter {
- it.classKind != ClassKind.INTERFACE
- }
- .flatMap {
- it.getDeclaredProperties().asSequence()
- }.forEach {
- if (selectedNames.add(it.simpleName.asString())) {
- selection.add(it)
- }
- }
- myPropertyFields + selection.map {
- KspFieldElement(
- env = env,
- declaration = it,
- containing = this
- )
- }
+ collectFieldsIncludingPrivateSupers(this).toList()
}
private val syntheticGetterSetterMethods: List<XMethodElement> by lazy {
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeElementTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeElementTest.kt
index 8ea3fae..bd4fdbd 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeElementTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeElementTest.kt
@@ -974,6 +974,9 @@
contains("x")
containsNoneOf("VAL1", "VAL2")
}
+ assertWithMessage("$qName does not report enum constants in declared fields")
+ .that(typeElement.getDeclaredFields().map { it.name })
+ .containsExactly("x")
}
}
}