Port InstrospectionHelper to Kotlin.

Bug: 234116803
Test: AppSearchCompilerTest
Change-Id: I3819d8f33e588cd6f0a86ac121b2017b4fe43b5e
diff --git a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/AnnotatedGetterOrField.kt b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/AnnotatedGetterOrField.kt
index fcf22cd..8217ee7 100644
--- a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/AnnotatedGetterOrField.kt
+++ b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/AnnotatedGetterOrField.kt
@@ -195,12 +195,12 @@
             val jvmType = IntrospectionHelper.getPropertyType(getterOrField)
             val typeUtils = env.typeUtils
             val helper = IntrospectionHelper(env)
-            return if (typeUtils.isAssignable(typeUtils.erasure(jvmType), helper.mCollectionType)) {
+            return if (typeUtils.isAssignable(typeUtils.erasure(jvmType), helper.collectionType)) {
                 ElementTypeCategory.COLLECTION
             } else if (
                 jvmType.kind == TypeKind.ARRAY &&
-                    !typeUtils.isSameType(jvmType, helper.mBytePrimitiveArrayType) &&
-                    !typeUtils.isSameType(jvmType, helper.mByteBoxArrayType)
+                    !typeUtils.isSameType(jvmType, helper.bytePrimitiveArrayType) &&
+                    !typeUtils.isSameType(jvmType, helper.byteBoxArrayType)
             ) {
                 // byte[] has a native representation in Icing and should be considered a
                 // primitive by itself.
@@ -379,7 +379,7 @@
                 MetadataPropertyAnnotation.NAMESPACE ->
                     requireTypeIsOneOf(
                         getterOrField,
-                        listOf(helper.mStringType),
+                        listOf(helper.stringType),
                         env,
                         allowRepeated = false
                     )
@@ -388,10 +388,10 @@
                     requireTypeIsOneOf(
                         getterOrField,
                         listOf(
-                            helper.mLongPrimitiveType,
-                            helper.mIntPrimitiveType,
-                            helper.mLongBoxType,
-                            helper.mIntegerBoxType
+                            helper.longPrimitiveType,
+                            helper.intPrimitiveType,
+                            helper.longBoxType,
+                            helper.integerBoxType
                         ),
                         env,
                         allowRepeated = false
@@ -399,7 +399,7 @@
                 MetadataPropertyAnnotation.SCORE ->
                     requireTypeIsOneOf(
                         getterOrField,
-                        listOf(helper.mIntPrimitiveType, helper.mIntegerBoxType),
+                        listOf(helper.intPrimitiveType, helper.integerBoxType),
                         env,
                         allowRepeated = false
                     )
@@ -433,7 +433,7 @@
                     } else {
                         requireTypeIsOneOf(
                             getterOrField,
-                            listOf(helper.mStringType),
+                            listOf(helper.stringType),
                             env,
                             allowRepeated = true
                         )
@@ -453,10 +453,10 @@
                         requireTypeIsOneOf(
                             getterOrField,
                             listOf(
-                                helper.mLongPrimitiveType,
-                                helper.mIntPrimitiveType,
-                                helper.mLongBoxType,
-                                helper.mIntegerBoxType
+                                helper.longPrimitiveType,
+                                helper.intPrimitiveType,
+                                helper.longBoxType,
+                                helper.integerBoxType
                             ),
                             env,
                             allowRepeated = true
@@ -467,10 +467,10 @@
                     requireTypeIsOneOf(
                         getterOrField,
                         listOf(
-                            helper.mDoublePrimitiveType,
-                            helper.mFloatPrimitiveType,
-                            helper.mDoubleBoxType,
-                            helper.mFloatBoxType
+                            helper.doublePrimitiveType,
+                            helper.floatPrimitiveType,
+                            helper.doubleBoxType,
+                            helper.floatBoxType
                         ),
                         env,
                         allowRepeated = true
@@ -478,28 +478,28 @@
                 DataPropertyAnnotation.Kind.BOOLEAN_PROPERTY ->
                     requireTypeIsOneOf(
                         getterOrField,
-                        listOf(helper.mBooleanPrimitiveType, helper.mBooleanBoxType),
+                        listOf(helper.booleanPrimitiveType, helper.booleanBoxType),
                         env,
                         allowRepeated = true
                     )
                 DataPropertyAnnotation.Kind.BYTES_PROPERTY ->
                     requireTypeIsOneOf(
                         getterOrField,
-                        listOf(helper.mBytePrimitiveArrayType),
+                        listOf(helper.bytePrimitiveArrayType),
                         env,
                         allowRepeated = true
                     )
                 DataPropertyAnnotation.Kind.EMBEDDING_PROPERTY ->
                     requireTypeIsOneOf(
                         getterOrField,
-                        listOf(helper.mEmbeddingType),
+                        listOf(helper.embeddingType),
                         env,
                         allowRepeated = true
                     )
                 DataPropertyAnnotation.Kind.BLOB_HANDLE_PROPERTY ->
                     requireTypeIsOneOf(
                         getterOrField,
-                        listOf(helper.mBlobHandleType),
+                        listOf(helper.blobHandleType),
                         env,
                         allowRepeated = true
                     )
diff --git a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/IntrospectionHelper.java b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/IntrospectionHelper.java
deleted file mode 100644
index db31d5a..0000000
--- a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/IntrospectionHelper.java
+++ /dev/null
@@ -1,592 +0,0 @@
-/*
- * Copyright 2018 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.appsearch.compiler;
-
-import static com.google.auto.common.MoreTypes.asTypeElement;
-
-import static java.util.stream.Collectors.toCollection;
-
-import androidx.annotation.RestrictTo;
-import androidx.appsearch.compiler.annotationwrapper.DataPropertyAnnotation;
-import androidx.appsearch.compiler.annotationwrapper.DocumentPropertyAnnotation;
-import androidx.appsearch.compiler.annotationwrapper.PropertyAnnotation;
-
-import com.google.auto.value.AutoValue;
-import com.squareup.javapoet.ClassName;
-
-import kotlin.Metadata;
-import kotlin.metadata.Attributes;
-import kotlin.metadata.KmClass;
-import kotlin.metadata.KmProperty;
-import kotlin.metadata.jvm.KotlinClassHeader;
-import kotlin.metadata.jvm.KotlinClassMetadata;
-
-import org.jspecify.annotations.NonNull;
-import org.jspecify.annotations.Nullable;
-
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Deque;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-import java.util.WeakHashMap;
-import java.util.stream.Stream;
-
-import javax.annotation.processing.ProcessingEnvironment;
-import javax.lang.model.element.AnnotationMirror;
-import javax.lang.model.element.AnnotationValue;
-import javax.lang.model.element.Element;
-import javax.lang.model.element.ElementKind;
-import javax.lang.model.element.ExecutableElement;
-import javax.lang.model.element.Modifier;
-import javax.lang.model.element.TypeElement;
-import javax.lang.model.type.ArrayType;
-import javax.lang.model.type.DeclaredType;
-import javax.lang.model.type.ExecutableType;
-import javax.lang.model.type.TypeKind;
-import javax.lang.model.type.TypeMirror;
-import javax.lang.model.util.Elements;
-import javax.lang.model.util.Types;
-
-/**
- * Utilities for working with data structures representing parsed Java code.
- *
- * @exportToFramework:hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class IntrospectionHelper {
-    static final String GEN_CLASS_PREFIX = "$$__AppSearch__";
-    static final String APPSEARCH_PKG = "androidx.appsearch.app";
-
-    public static final ClassName APPSEARCH_SCHEMA_CLASS =
-            ClassName.get(APPSEARCH_PKG, "AppSearchSchema");
-
-    static final ClassName PROPERTY_CONFIG_CLASS =
-            APPSEARCH_SCHEMA_CLASS.nestedClass("PropertyConfig");
-
-    static final String APPSEARCH_EXCEPTION_PKG = "androidx.appsearch.exceptions";
-
-    static final ClassName APPSEARCH_EXCEPTION_CLASS =
-            ClassName.get(APPSEARCH_EXCEPTION_PKG, "AppSearchException");
-
-    public static final String APPSEARCH_ANNOTATION_PKG = "androidx.appsearch.annotation";
-
-    public static final String DOCUMENT_ANNOTATION_SIMPLE_CLASS_NAME = "Document";
-
-    public static final ClassName DOCUMENT_ANNOTATION_CLASS =
-            ClassName.get(APPSEARCH_ANNOTATION_PKG, DOCUMENT_ANNOTATION_SIMPLE_CLASS_NAME);
-
-    public static final ClassName GENERIC_DOCUMENT_CLASS =
-            ClassName.get(APPSEARCH_PKG, "GenericDocument");
-
-    public static final ClassName EMBEDDING_VECTOR_CLASS =
-            ClassName.get(APPSEARCH_PKG, "EmbeddingVector");
-
-    public static final ClassName APPSEARCH_BLOB_HANDLE_CLASS =
-            ClassName.get(APPSEARCH_PKG, "AppSearchBlobHandle");
-
-    public static final ClassName BUILDER_PRODUCER_CLASS =
-            DOCUMENT_ANNOTATION_CLASS.nestedClass("BuilderProducer");
-
-    static final ClassName DOCUMENT_CLASS_FACTORY_CLASS =
-            ClassName.get(APPSEARCH_PKG, "DocumentClassFactory");
-
-    static final ClassName RESTRICT_TO_ANNOTATION_CLASS =
-            ClassName.get("androidx.annotation", "RestrictTo");
-
-    static final ClassName RESTRICT_TO_SCOPE_CLASS =
-            RESTRICT_TO_ANNOTATION_CLASS.nestedClass("Scope");
-
-    static final ClassName DOCUMENT_CLASS_MAPPING_CONTEXT_CLASS =
-            ClassName.get(APPSEARCH_PKG, "DocumentClassMappingContext");
-
-    public final TypeMirror mStringType;
-    public final TypeMirror mLongPrimitiveType;
-    public final TypeMirror mIntPrimitiveType;
-    public final TypeMirror mBooleanPrimitiveType;
-    public final TypeMirror mBytePrimitiveArrayType;
-    public final TypeMirror mGenericDocumentType;
-    public final TypeMirror mEmbeddingType;
-    public final TypeMirror mDoublePrimitiveType;
-    public final TypeMirror mBlobHandleType;
-    final TypeMirror mCollectionType;
-    final TypeMirror mListType;
-    final TypeMirror mIntegerBoxType;
-    final TypeMirror mLongBoxType;
-    final TypeMirror mFloatBoxType;
-    final TypeMirror mFloatPrimitiveType;
-    final TypeMirror mDoubleBoxType;
-    final TypeMirror mBooleanBoxType;
-    final TypeMirror mByteBoxType;
-    final TypeMirror mByteBoxArrayType;
-    final TypeMirror mBytePrimitiveType;
-    private final ProcessingEnvironment mEnv;
-    private final Types mTypeUtils;
-    private final Elements mElementUtils;
-
-    private final WeakHashMap<TypeElement, LinkedHashSet<ExecutableElement>> mAllMethodsCache =
-            new WeakHashMap<>();
-
-    IntrospectionHelper(ProcessingEnvironment env) {
-        mEnv = env;
-
-        mElementUtils = env.getElementUtils();
-        mTypeUtils = env.getTypeUtils();
-        mCollectionType = mElementUtils.getTypeElement(Collection.class.getName()).asType();
-        mListType = mElementUtils.getTypeElement(List.class.getName()).asType();
-        mStringType = mElementUtils.getTypeElement(String.class.getName()).asType();
-        mIntegerBoxType = mElementUtils.getTypeElement(Integer.class.getName()).asType();
-        mIntPrimitiveType = mTypeUtils.unboxedType(mIntegerBoxType);
-        mLongBoxType = mElementUtils.getTypeElement(Long.class.getName()).asType();
-        mLongPrimitiveType = mTypeUtils.unboxedType(mLongBoxType);
-        mFloatBoxType = mElementUtils.getTypeElement(Float.class.getName()).asType();
-        mFloatPrimitiveType = mTypeUtils.unboxedType(mFloatBoxType);
-        mDoubleBoxType = mElementUtils.getTypeElement(Double.class.getName()).asType();
-        mDoublePrimitiveType = mTypeUtils.unboxedType(mDoubleBoxType);
-        mBooleanBoxType = mElementUtils.getTypeElement(Boolean.class.getName()).asType();
-        mBooleanPrimitiveType = mTypeUtils.unboxedType(mBooleanBoxType);
-        mByteBoxType = mElementUtils.getTypeElement(Byte.class.getName()).asType();
-        mByteBoxArrayType = mTypeUtils.getArrayType(mByteBoxType);
-        mBytePrimitiveType = mTypeUtils.unboxedType(mByteBoxType);
-        mBytePrimitiveArrayType = mTypeUtils.getArrayType(mBytePrimitiveType);
-        mGenericDocumentType =
-                mElementUtils.getTypeElement(GENERIC_DOCUMENT_CLASS.canonicalName()).asType();
-        mEmbeddingType = mElementUtils.getTypeElement(
-                EMBEDDING_VECTOR_CLASS.canonicalName()).asType();
-        mBlobHandleType = mElementUtils.getTypeElement(
-                APPSEARCH_BLOB_HANDLE_CLASS.canonicalName()).asType();
-    }
-
-    /**
-     * Returns {@code androidx.appsearch.annotation.Document} annotation element from the input
-     * element's annotations. Returns null if no such annotation is found.
-     */
-    public static @Nullable AnnotationMirror getDocumentAnnotation(@NonNull Element element) {
-        Objects.requireNonNull(element);
-        List<? extends AnnotationMirror> annotations = getAnnotations(element,
-                DOCUMENT_ANNOTATION_CLASS);
-        if (annotations.isEmpty()) {
-            return null;
-        } else {
-            return annotations.get(0);
-        }
-    }
-
-    /**
-     * Returns a list of annotations of a given kind from the input element's annotations,
-     * specified by the annotation's class name. Returns null if no annotation of such kind is
-     * found.
-     */
-    public static @NonNull List<? extends AnnotationMirror> getAnnotations(@NonNull Element element,
-            @NonNull ClassName className) {
-        Objects.requireNonNull(element);
-        Objects.requireNonNull(className);
-        return element.getAnnotationMirrors()
-                .stream()
-                .filter(annotation -> annotation.getAnnotationType().toString()
-                        .equals(className.canonicalName()))
-                .toList();
-    }
-
-    /**
-     * Returns the document property annotation that matches the given property name from a given
-     * class or interface element.
-     *
-     * <p>Returns null if the property cannot be found in the class or interface, or if the
-     * property matching the property name is not a document property.
-     */
-    public @Nullable DocumentPropertyAnnotation getDocumentPropertyAnnotation(
-            @NonNull TypeElement clazz, @NonNull String propertyName) throws ProcessingException {
-        Objects.requireNonNull(clazz);
-        Objects.requireNonNull(propertyName);
-        for (Element enclosedElement : clazz.getEnclosedElements()) {
-            AnnotatedGetterOrField getterOrField =
-                    AnnotatedGetterOrField.tryCreateFor(enclosedElement, mEnv);
-            if (getterOrField == null || !(getterOrField.getAnnotation().getPropertyKind()
-                    == PropertyAnnotation.Kind.DATA_PROPERTY)) {
-                continue;
-            }
-            if (((DataPropertyAnnotation) getterOrField.getAnnotation()).getDataPropertyKind()
-                    == DataPropertyAnnotation.Kind.DOCUMENT_PROPERTY) {
-                DocumentPropertyAnnotation documentPropertyAnnotation =
-                        (DocumentPropertyAnnotation) getterOrField.getAnnotation();
-                if (documentPropertyAnnotation.getName().equals(propertyName)) {
-                    return documentPropertyAnnotation;
-                }
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Returns the property type of the given property. Properties are represented by an
-     * annotated Java element that is either a Java field or a getter method.
-     */
-    public static @NonNull TypeMirror getPropertyType(@NonNull Element property) {
-        Objects.requireNonNull(property);
-
-        TypeMirror propertyType = property.asType();
-        if (property.getKind() == ElementKind.METHOD) {
-            propertyType = ((ExecutableType) propertyType).getReturnType();
-        }
-        return propertyType;
-    }
-
-    /** Checks whether the property data type is one of the valid types. */
-    public boolean isFieldOfExactType(
-            @NonNull Element property, TypeMirror @NonNull ... validTypes) {
-        TypeMirror propertyType = getPropertyType(property);
-        for (TypeMirror validType : validTypes) {
-            if (propertyType.getKind() == TypeKind.ARRAY) {
-                if (mTypeUtils.isSameType(
-                        ((ArrayType) propertyType).getComponentType(), validType)) {
-                    return true;
-                }
-            } else if (mTypeUtils.isAssignable(mTypeUtils.erasure(propertyType), mCollectionType)) {
-                if (mTypeUtils.isSameType(
-                        ((DeclaredType) propertyType).getTypeArguments().get(0), validType)) {
-                    return true;
-                }
-            } else if (mTypeUtils.isSameType(propertyType, validType)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /** Checks whether the property data type is of boolean type. */
-    public boolean isFieldOfBooleanType(@NonNull Element property) {
-        return isFieldOfExactType(property, mBooleanBoxType, mBooleanPrimitiveType);
-    }
-
-    /**
-     * Returns the annotation's params as a map. Includes the default values.
-     */
-    public @NonNull Map<String, Object> getAnnotationParams(@NonNull AnnotationMirror annotation) {
-        Map<? extends ExecutableElement, ? extends AnnotationValue> values =
-                mEnv.getElementUtils().getElementValuesWithDefaults(annotation);
-        Map<String, Object> ret = new HashMap<>();
-        for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry :
-                values.entrySet()) {
-            String key = entry.getKey().getSimpleName().toString();
-            ret.put(key, entry.getValue().getValue());
-        }
-        return ret;
-    }
-
-    /**
-     * Creates the name of output class. $$__AppSearch__Foo for Foo, $$__AppSearch__Foo$$__Bar
-     * for inner class Foo.Bar.
-     */
-    public static @NonNull ClassName getDocumentClassFactoryForClass(
-            @NonNull String pkg, @NonNull String className) {
-        String genClassName = GEN_CLASS_PREFIX + className.replace(".", "$$__");
-        return ClassName.get(pkg, genClassName);
-    }
-
-    /**
-     * Creates the name of output class. $$__AppSearch__Foo for Foo, $$__AppSearch__Foo$$__Bar
-     * for inner class Foo.Bar.
-     */
-    public static @NonNull ClassName getDocumentClassFactoryForClass(@NonNull ClassName clazz) {
-        String className = clazz.canonicalName().substring(clazz.packageName().length() + 1);
-        return getDocumentClassFactoryForClass(clazz.packageName(), className);
-    }
-
-    /**
-     * Returns all the methods within a class, whether inherited or declared directly.
-     *
-     * <p>Caches results internally, so it is cheap to call subsequently for the same input.
-     */
-    public @NonNull LinkedHashSet<ExecutableElement> getAllMethods(@NonNull TypeElement clazz) {
-        return mAllMethodsCache.computeIfAbsent(
-                clazz,
-                type -> mEnv.getElementUtils().getAllMembers(type).stream()
-                        .filter(element -> element.getKind() == ElementKind.METHOD)
-                        .map(element -> (ExecutableElement) element)
-                        .collect(toCollection(LinkedHashSet::new)));
-    }
-
-    /**
-     * Whether a type is the same as {@code long[]}.
-     */
-    public boolean isPrimitiveLongArray(@NonNull TypeMirror type) {
-        return isArrayOf(type, mLongPrimitiveType);
-    }
-
-    /**
-     * Whether a type is the same as {@code double[]}.
-     */
-    public boolean isPrimitiveDoubleArray(@NonNull TypeMirror type) {
-        return isArrayOf(type, mDoublePrimitiveType);
-    }
-
-    /**
-     * Whether a type is the same as {@code boolean[]}.
-     */
-    public boolean isPrimitiveBooleanArray(@NonNull TypeMirror type) {
-        return isArrayOf(type, mBooleanPrimitiveType);
-    }
-
-    private boolean isArrayOf(@NonNull TypeMirror type, @NonNull TypeMirror arrayComponentType) {
-        return mTypeUtils.isSameType(type, mTypeUtils.getArrayType(arrayComponentType));
-    }
-
-    /**
-     * Get a list of super classes of element annotated with @Document, in order starting with the
-     * class at the top of the hierarchy and descending down the class hierarchy. Note that this
-     * ordering is important because super classes must appear first in the list than child classes
-     * to make property overrides work.
-     */
-    public static @NonNull List<TypeElement> generateClassHierarchy(
-            @NonNull TypeElement element) throws ProcessingException {
-        Deque<TypeElement> hierarchy = new ArrayDeque<>();
-        if (element.getAnnotation(AutoValue.class) != null) {
-            // We don't allow classes annotated with both Document and AutoValue to extend classes.
-            // Because of how AutoValue is set up, there is no way to add a constructor to
-            // populate fields of super classes.
-            // There should just be the generated class and the original annotated class
-            TypeElement superClass = asTypeElement(element.getSuperclass());
-            if (!superClass.getQualifiedName().contentEquals(Object.class.getCanonicalName())) {
-                throw new ProcessingException(
-                        "A class annotated with AutoValue and Document cannot have a superclass",
-                        element);
-            }
-            hierarchy.add(element);
-        } else {
-            Set<TypeElement> visited = new HashSet<>();
-            generateClassHierarchyHelper(element, element, hierarchy, visited);
-        }
-        return new ArrayList<>(hierarchy);
-    }
-
-    /**
-     * Checks if a method is a valid getter and returns any errors.
-     *
-     * <p>Returns an empty list if no errors i.e. the method is a valid getter.
-     */
-    public static @NonNull List<ProcessingException> validateIsGetter(
-            @NonNull ExecutableElement method) {
-        List<ProcessingException> errors = new ArrayList<>();
-        if (!method.getParameters().isEmpty()) {
-            errors.add(new ProcessingException(
-                    "Getter cannot be used: should take no parameters", method));
-        }
-        if (method.getModifiers().contains(Modifier.PRIVATE)) {
-            errors.add(new ProcessingException(
-                    "Getter cannot be used: private visibility", method));
-        }
-        if (method.getModifiers().contains(Modifier.STATIC)) {
-            errors.add(new ProcessingException(
-                    "Getter cannot be used: must not be static", method));
-        }
-        return errors;
-    }
-
-    /**
-     * Same as {@link #validateIsGetter} but additionally verifies that the getter returns the
-     * specified type.
-     */
-    public @NonNull List<ProcessingException> validateIsGetterThatReturns(
-            @NonNull ExecutableElement method, @NonNull TypeMirror expectedReturnType) {
-        List<ProcessingException> errors = validateIsGetter(method);
-        if (!mTypeUtils.isAssignable(method.getReturnType(), expectedReturnType)) {
-            errors.add(new ProcessingException(
-                    "Getter cannot be used: Does not return " + expectedReturnType, method));
-        }
-        return errors;
-    }
-
-    /**
-     * A method's type and element (i.e. declaration).
-     *
-     * <p>Note: The parameter and return types may differ between the type and the element.
-     * For example,
-     *
-     * <pre>
-     * {@code
-     * public class StringSet implements Set<String> {...}
-     * }
-     * </pre>
-     *
-     * <p>Here, the type of {@code StringSet.add()} is {@code (String) -> boolean} and the element
-     * points to the generic declaration within {@code Set<T>} with a return type of
-     * {@code boolean} and a single parameter of type {@code T}.
-     */
-    public static class MethodTypeAndElement {
-        private final ExecutableType mType;
-        private final ExecutableElement mElement;
-
-        public MethodTypeAndElement(
-                @NonNull ExecutableType type, @NonNull ExecutableElement element) {
-            mType = type;
-            mElement = element;
-        }
-
-        public @NonNull ExecutableType getType() {
-            return mType;
-        }
-
-        public @NonNull ExecutableElement getElement() {
-            return mElement;
-        }
-    }
-
-    /**
-     * Returns a stream of all the methods (including inherited) within a {@link DeclaredType}.
-     *
-     * <p>Does not include constructors.
-     */
-    public @NonNull Stream<MethodTypeAndElement> getAllMethods(@NonNull DeclaredType type) {
-        return mElementUtils.getAllMembers((TypeElement) type.asElement()).stream()
-                .filter(el -> el.getKind() == ElementKind.METHOD)
-                .map(el -> new MethodTypeAndElement(
-                        (ExecutableType) mTypeUtils.asMemberOf(type, el),
-                        (ExecutableElement) el));
-    }
-
-    /**
-     * Whether the method returns the specified type (or subtype).
-     */
-    public boolean isReturnTypeMatching(
-            @NonNull ExecutableType method, @NonNull TypeMirror type) {
-        return mTypeUtils.isAssignable(method.getReturnType(), type);
-    }
-
-    /**
-     * Returns a type that the source type should be casted to coerce it to the target type.
-     *
-     * <p>Handles the following cases:
-     * <pre>
-     * {@code
-     * long|Long -> int|Integer = (int) ...
-     * double|Double -> float|Float = (float) ...
-     * }
-     * </pre>
-     *
-     * <p>Returns null if no cast is necessary.
-     */
-    public @Nullable TypeMirror getNarrowingCastType(
-            @NonNull TypeMirror sourceType, @NonNull TypeMirror targetType) {
-        if (mTypeUtils.isSameType(targetType, mIntPrimitiveType)
-                || mTypeUtils.isSameType(targetType, mIntegerBoxType)) {
-            if (mTypeUtils.isSameType(sourceType, mLongPrimitiveType)
-                    || mTypeUtils.isSameType(sourceType, mLongBoxType)) {
-                return mIntPrimitiveType;
-            }
-        }
-        if (mTypeUtils.isSameType(targetType, mFloatPrimitiveType)
-                || mTypeUtils.isSameType(targetType, mFloatBoxType)) {
-            if (mTypeUtils.isSameType(sourceType, mDoublePrimitiveType)
-                    || mTypeUtils.isSameType(sourceType, mDoubleBoxType)) {
-                return mFloatPrimitiveType;
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Whether the element is a static method that returns the class it's enclosed within.
-     */
-    public boolean isStaticFactoryMethod(@NonNull Element element) {
-        if (element.getKind() != ElementKind.METHOD
-                || !element.getModifiers().contains(Modifier.STATIC)) {
-            return false;
-        }
-        ExecutableElement method = (ExecutableElement) element;
-        TypeMirror enclosingType = method.getEnclosingElement().asType();
-        return mTypeUtils.isSameType(method.getReturnType(), enclosingType);
-    }
-
-    private static void generateClassHierarchyHelper(@NonNull TypeElement leafElement,
-            @NonNull TypeElement currentClass, @NonNull Deque<TypeElement> hierarchy,
-            @NonNull Set<TypeElement> visited)
-            throws ProcessingException {
-        if (currentClass.getQualifiedName().contentEquals(Object.class.getCanonicalName())) {
-            return;
-        }
-        // If you inherit from an AutoValue class, you have to implement the static methods.
-        // That defeats the purpose of AutoValue
-        if (currentClass.getAnnotation(AutoValue.class) != null) {
-            throw new ProcessingException(
-                    "A class annotated with Document cannot inherit from a class "
-                            + "annotated with AutoValue", leafElement);
-        }
-
-        // It's possible to revisit the same interface more than once, so this check exists to
-        // catch that.
-        if (visited.contains(currentClass)) {
-            return;
-        }
-        visited.add(currentClass);
-
-        if (getDocumentAnnotation(currentClass) != null) {
-            hierarchy.addFirst(currentClass);
-        }
-        TypeMirror superclass = currentClass.getSuperclass();
-        // If currentClass is an interface, then superclass could be NONE.
-        if (superclass.getKind() != TypeKind.NONE) {
-            generateClassHierarchyHelper(leafElement, asTypeElement(superclass),
-                    hierarchy, visited);
-        }
-        for (TypeMirror implementedInterface : currentClass.getInterfaces()) {
-            generateClassHierarchyHelper(leafElement, asTypeElement(implementedInterface),
-                    hierarchy, visited);
-        }
-    }
-
-    /**
-     * Determines if a field is from Kotlin and is NonNull by checking for a Metadata annotation.
-     */
-    public static boolean isNonNullKotlinField(@NonNull AnnotatedGetterOrField getterOrField) {
-        Objects.requireNonNull(getterOrField);
-        Metadata metadata = getterOrField.getElement().getEnclosingElement()
-                .getAnnotation(Metadata.class);
-        if (metadata != null) {
-            // The kotlin metadata annotation contains information about the class, but we first
-            // need to parse it with KotlinClassHeader
-            KotlinClassHeader header = new KotlinClassHeader(
-                    /*kind=*/metadata.k(),
-                    /*metadataVersion=*/metadata.mv(),
-                    /*data1=*/metadata.d1(),
-                    /*data2=*/metadata.d2(),
-                    /*extraString=*/metadata.xs(),
-                    /*packageName=*/metadata.pn(),
-                    /*extraInt=*/metadata.xi()
-            );
-            KotlinClassMetadata kotlinMetadata = KotlinClassMetadata.readStrict(header);
-
-            if (kotlinMetadata instanceof KotlinClassMetadata.Class) {
-                KmClass kmClass = ((KotlinClassMetadata.Class) kotlinMetadata).getKmClass();
-
-                List<KmProperty> properties = kmClass.getProperties();
-                for (KmProperty property : properties) {
-                    if (property.getName().equals(getterOrField.getJvmName())) {
-                        return !Attributes.isNullable(property.getReturnType());
-                    }
-                }
-            }
-        }
-        // It is not a kotlin property.
-        return false;
-    }
-}
diff --git a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/IntrospectionHelper.kt b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/IntrospectionHelper.kt
new file mode 100644
index 0000000..81cd239
--- /dev/null
+++ b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/IntrospectionHelper.kt
@@ -0,0 +1,543 @@
+/*
+ * Copyright 2018 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.appsearch.compiler
+
+import androidx.appsearch.compiler.AnnotatedGetterOrField.Companion.tryCreateFor
+import androidx.appsearch.compiler.annotationwrapper.DataPropertyAnnotation
+import androidx.appsearch.compiler.annotationwrapper.DocumentPropertyAnnotation
+import androidx.appsearch.compiler.annotationwrapper.PropertyAnnotation
+import com.google.auto.common.MoreTypes
+import com.google.auto.value.AutoValue
+import com.squareup.javapoet.ClassName
+import java.util.ArrayDeque
+import java.util.Deque
+import java.util.WeakHashMap
+import java.util.stream.Collectors
+import java.util.stream.Stream
+import javax.annotation.processing.ProcessingEnvironment
+import javax.lang.model.element.AnnotationMirror
+import javax.lang.model.element.Element
+import javax.lang.model.element.ElementKind
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.element.Modifier
+import javax.lang.model.element.TypeElement
+import javax.lang.model.type.ArrayType
+import javax.lang.model.type.DeclaredType
+import javax.lang.model.type.ExecutableType
+import javax.lang.model.type.TypeKind
+import javax.lang.model.type.TypeMirror
+import javax.lang.model.util.Elements
+import javax.lang.model.util.Types
+import kotlin.metadata.isNullable
+import kotlin.metadata.jvm.KotlinClassMetadata
+
+/** Utilities for working with data structures representing parsed Java code. */
+class IntrospectionHelper internal constructor(private val env: ProcessingEnvironment) {
+    private val typeUtils: Types = env.typeUtils
+    private val elementUtils: Elements = env.elementUtils
+
+    // Non-boxable objects
+    val blobHandleType: TypeMirror =
+        elementUtils.getTypeElement(APPSEARCH_BLOB_HANDLE_CLASS.canonicalName()).asType()
+    val collectionType: TypeMirror =
+        elementUtils.getTypeElement(java.util.Collection::class.java.name).asType()
+    val embeddingType: TypeMirror =
+        elementUtils.getTypeElement(EMBEDDING_VECTOR_CLASS.canonicalName()).asType()
+    val genericDocumentType: TypeMirror =
+        elementUtils.getTypeElement(GENERIC_DOCUMENT_CLASS.canonicalName()).asType()
+    val listType: TypeMirror = elementUtils.getTypeElement(java.util.List::class.java.name).asType()
+    @JvmField
+    val stringType: TypeMirror =
+        elementUtils.getTypeElement(java.lang.String::class.java.name).asType()
+
+    // Boxable objects
+    val booleanBoxType: TypeMirror =
+        elementUtils.getTypeElement(java.lang.Boolean::class.java.name).asType()
+    val byteBoxType: TypeMirror =
+        elementUtils.getTypeElement(java.lang.Byte::class.java.name).asType()
+    val byteBoxArrayType: TypeMirror = typeUtils.getArrayType(byteBoxType)
+    val doubleBoxType: TypeMirror =
+        elementUtils.getTypeElement(java.lang.Double::class.java.name).asType()
+    val floatBoxType: TypeMirror =
+        elementUtils.getTypeElement(java.lang.Float::class.java.name).asType()
+    val integerBoxType: TypeMirror =
+        elementUtils.getTypeElement(java.lang.Integer::class.java.name).asType()
+    val longBoxType: TypeMirror =
+        elementUtils.getTypeElement(java.lang.Long::class.java.name).asType()
+
+    // Primitive versions of boxable objects
+    @JvmField val booleanPrimitiveType: TypeMirror = typeUtils.unboxedType(booleanBoxType)
+    val bytePrimitiveType: TypeMirror = typeUtils.unboxedType(byteBoxType)
+    @JvmField val bytePrimitiveArrayType: TypeMirror = typeUtils.getArrayType(bytePrimitiveType)
+    @JvmField val doublePrimitiveType: TypeMirror = typeUtils.unboxedType(doubleBoxType)
+    val floatPrimitiveType: TypeMirror = typeUtils.unboxedType(floatBoxType)
+    @JvmField val intPrimitiveType: TypeMirror = typeUtils.unboxedType(integerBoxType)
+    @JvmField val longPrimitiveType: TypeMirror = typeUtils.unboxedType(longBoxType)
+
+    private val allMethodsCache = WeakHashMap<TypeElement, LinkedHashSet<ExecutableElement>>()
+
+    companion object {
+        const val GEN_CLASS_PREFIX: String = "$\$__AppSearch__"
+        const val APPSEARCH_PKG: String = "androidx.appsearch.app"
+
+        @JvmField
+        val APPSEARCH_SCHEMA_CLASS: ClassName = ClassName.get(APPSEARCH_PKG, "AppSearchSchema")
+
+        @JvmField
+        val PROPERTY_CONFIG_CLASS: ClassName = APPSEARCH_SCHEMA_CLASS.nestedClass("PropertyConfig")
+
+        const val APPSEARCH_EXCEPTION_PKG: String = "androidx.appsearch.exceptions"
+
+        @JvmField
+        val APPSEARCH_EXCEPTION_CLASS: ClassName =
+            ClassName.get(APPSEARCH_EXCEPTION_PKG, "AppSearchException")
+
+        const val APPSEARCH_ANNOTATION_PKG: String = "androidx.appsearch.annotation"
+
+        const val DOCUMENT_ANNOTATION_SIMPLE_CLASS_NAME: String = "Document"
+
+        @JvmField
+        val DOCUMENT_ANNOTATION_CLASS: ClassName =
+            ClassName.get(APPSEARCH_ANNOTATION_PKG, DOCUMENT_ANNOTATION_SIMPLE_CLASS_NAME)
+
+        @JvmField
+        val GENERIC_DOCUMENT_CLASS: ClassName = ClassName.get(APPSEARCH_PKG, "GenericDocument")
+
+        val EMBEDDING_VECTOR_CLASS: ClassName = ClassName.get(APPSEARCH_PKG, "EmbeddingVector")
+
+        val APPSEARCH_BLOB_HANDLE_CLASS: ClassName =
+            ClassName.get(APPSEARCH_PKG, "AppSearchBlobHandle")
+
+        val BUILDER_PRODUCER_CLASS: ClassName =
+            DOCUMENT_ANNOTATION_CLASS.nestedClass("BuilderProducer")
+
+        @JvmField
+        val DOCUMENT_CLASS_FACTORY_CLASS: ClassName =
+            ClassName.get(APPSEARCH_PKG, "DocumentClassFactory")
+
+        @JvmField
+        val RESTRICT_TO_ANNOTATION_CLASS: ClassName =
+            ClassName.get("androidx.annotation", "RestrictTo")
+
+        @JvmField
+        val RESTRICT_TO_SCOPE_CLASS: ClassName = RESTRICT_TO_ANNOTATION_CLASS.nestedClass("Scope")
+
+        @JvmField
+        val DOCUMENT_CLASS_MAPPING_CONTEXT_CLASS: ClassName =
+            ClassName.get(APPSEARCH_PKG, "DocumentClassMappingContext")
+
+        /**
+         * Returns `androidx.appsearch.annotation.Document` annotation element from the input
+         * element's annotations. Returns null if no such annotation is found.
+         */
+        @JvmStatic
+        fun getDocumentAnnotation(element: Element): AnnotationMirror? {
+            val annotations: List<AnnotationMirror> =
+                getAnnotations(element, DOCUMENT_ANNOTATION_CLASS)
+            return annotations.firstOrNull()
+        }
+
+        /**
+         * Returns a list of annotations of a given kind from the input element's annotations,
+         * specified by the annotation's class name. Returns null if no annotation of such kind is
+         * found.
+         */
+        fun getAnnotations(element: Element, className: ClassName): List<AnnotationMirror> {
+            return element.annotationMirrors
+                .stream()
+                .filter { it.annotationType.toString() == className.canonicalName() }
+                .toList()
+        }
+
+        /**
+         * Returns the property type of the given property. Properties are represented by an
+         * annotated Java element that is either a Java field or a getter method.
+         */
+        fun getPropertyType(property: Element): TypeMirror {
+            var propertyType = property.asType()
+            if (property.kind == ElementKind.METHOD) {
+                propertyType = (propertyType as ExecutableType).returnType
+            }
+            return propertyType
+        }
+
+        /**
+         * Creates the name of output class. $$__AppSearch__Foo for Foo, $$__AppSearch__Foo$$__Bar
+         * for inner class Foo.Bar.
+         */
+        @JvmStatic
+        fun getDocumentClassFactoryForClass(pkg: String, className: String): ClassName {
+            val genClassName: String = GEN_CLASS_PREFIX + className.replace(".", "$\$__")
+            return ClassName.get(pkg, genClassName)
+        }
+
+        /**
+         * Creates the name of output class. $$__AppSearch__Foo for Foo, $$__AppSearch__Foo$$__Bar
+         * for inner class Foo.Bar.
+         */
+        @JvmStatic
+        fun getDocumentClassFactoryForClass(clazz: ClassName): ClassName {
+            val className = clazz.canonicalName().substring(clazz.packageName().length + 1)
+            return getDocumentClassFactoryForClass(clazz.packageName(), className)
+        }
+
+        /**
+         * Get a list of super classes of element annotated with @Document, in order starting with
+         * the class at the top of the hierarchy and descending down the class hierarchy. Note that
+         * this ordering is important because super classes must appear first in the list than child
+         * classes to make property overrides work.
+         */
+        @Throws(ProcessingException::class)
+        @JvmStatic
+        fun generateClassHierarchy(element: TypeElement): List<TypeElement> {
+            val hierarchy: Deque<TypeElement> = ArrayDeque<TypeElement>()
+            if (element.getAnnotation(AutoValue::class.java) != null) {
+                // We don't allow classes annotated with both Document and AutoValue to extend
+                // classes.
+                // Because of how AutoValue is set up, there is no way to add a constructor to
+                // populate fields of super classes.
+                // There should just be the generated class and the original annotated class
+                val superClass = MoreTypes.asTypeElement(element.superclass)
+                if (
+                    !superClass.qualifiedName.contentEquals(
+                        java.lang.Object::class.java.canonicalName
+                    )
+                ) {
+                    throw ProcessingException(
+                        "A class annotated with AutoValue and Document cannot have a superclass",
+                        element
+                    )
+                }
+                hierarchy.add(element)
+            } else {
+                val visited = mutableSetOf<TypeElement>()
+                generateClassHierarchyHelper(element, element, hierarchy, visited)
+            }
+            return hierarchy.toList()
+        }
+
+        /**
+         * Checks if a method is a valid getter and returns any errors.
+         *
+         * Returns an empty list if no errors i.e. the method is a valid getter.
+         */
+        fun validateIsGetter(method: ExecutableElement): MutableList<ProcessingException> {
+            val errors = mutableListOf<ProcessingException>()
+            if (method.parameters.isNotEmpty()) {
+                errors.add(
+                    ProcessingException("Getter cannot be used: should take no parameters", method)
+                )
+            }
+            if (method.modifiers.contains(Modifier.PRIVATE)) {
+                errors.add(ProcessingException("Getter cannot be used: private visibility", method))
+            }
+            if (method.modifiers.contains(Modifier.STATIC)) {
+                errors.add(ProcessingException("Getter cannot be used: must not be static", method))
+            }
+            return errors
+        }
+
+        @Throws(ProcessingException::class)
+        private fun generateClassHierarchyHelper(
+            leafElement: TypeElement,
+            currentClass: TypeElement,
+            hierarchy: Deque<TypeElement>,
+            visited: MutableSet<TypeElement>
+        ) {
+            if (
+                currentClass.qualifiedName.contentEquals(java.lang.Object::class.java.canonicalName)
+            ) {
+                return
+            }
+            // If you inherit from an AutoValue class, you have to implement the static methods.
+            // That defeats the purpose of AutoValue
+            if (currentClass.getAnnotation(AutoValue::class.java) != null) {
+                throw ProcessingException(
+                    "A class annotated with Document cannot inherit from a class " +
+                        "annotated with AutoValue",
+                    leafElement
+                )
+            }
+
+            // It's possible to revisit the same interface more than once, so this check exists to
+            // catch that.
+            if (visited.contains(currentClass)) {
+                return
+            }
+            visited.add(currentClass)
+
+            if (getDocumentAnnotation(currentClass) != null) {
+                hierarchy.addFirst(currentClass)
+            }
+            val superclass = currentClass.superclass
+            // If currentClass is an interface, then superclass could be NONE.
+            if (superclass.kind != TypeKind.NONE) {
+                generateClassHierarchyHelper(
+                    leafElement,
+                    MoreTypes.asTypeElement(superclass),
+                    hierarchy,
+                    visited
+                )
+            }
+            for (implementedInterface in currentClass.interfaces) {
+                generateClassHierarchyHelper(
+                    leafElement,
+                    MoreTypes.asTypeElement(implementedInterface),
+                    hierarchy,
+                    visited
+                )
+            }
+        }
+
+        /**
+         * Determines if a field is from Kotlin and is NonNull by checking for a Metadata
+         * annotation.
+         */
+        @JvmStatic
+        fun isNonNullKotlinField(getterOrField: AnnotatedGetterOrField): Boolean {
+            val metadata =
+                getterOrField.element.enclosingElement.getAnnotation(Metadata::class.java)
+            if (metadata != null) {
+                val kotlinMetadata: KotlinClassMetadata = KotlinClassMetadata.readStrict(metadata)
+                if (kotlinMetadata is KotlinClassMetadata.Class) {
+                    val kmClass = kotlinMetadata.kmClass
+                    val properties = kmClass.properties
+                    for (property in properties) {
+                        if (property.name == getterOrField.jvmName) {
+                            return !property.returnType.isNullable
+                        }
+                    }
+                }
+            }
+            // It is not a kotlin property.
+            return false
+        }
+    }
+
+    /**
+     * Returns the document property annotation that matches the given property name from a given
+     * class or interface element.
+     *
+     * Returns null if the property cannot be found in the class or interface, or if the property
+     * matching the property name is not a document property.
+     */
+    @Throws(ProcessingException::class)
+    fun getDocumentPropertyAnnotation(
+        clazz: TypeElement,
+        propertyName: String
+    ): DocumentPropertyAnnotation? {
+        for (enclosedElement in clazz.enclosedElements) {
+            val getterOrField = tryCreateFor(enclosedElement, env)
+            if (
+                getterOrField == null ||
+                    getterOrField.annotation.propertyKind != PropertyAnnotation.Kind.DATA_PROPERTY
+            ) {
+                continue
+            }
+            if (
+                (getterOrField.annotation as DataPropertyAnnotation).dataPropertyKind ==
+                    DataPropertyAnnotation.Kind.DOCUMENT_PROPERTY
+            ) {
+                val documentPropertyAnnotation =
+                    getterOrField.annotation as DocumentPropertyAnnotation
+                if (documentPropertyAnnotation.name == propertyName) {
+                    return documentPropertyAnnotation
+                }
+            }
+        }
+        return null
+    }
+
+    /** Checks whether the property data type is one of the valid types. */
+    fun isFieldOfExactType(property: Element, vararg validTypes: TypeMirror): Boolean {
+        val propertyType: TypeMirror = getPropertyType(property)
+        for (validType in validTypes) {
+            if (propertyType.kind == TypeKind.ARRAY) {
+                if (typeUtils.isSameType((propertyType as ArrayType).componentType, validType)) {
+                    return true
+                }
+            } else if (typeUtils.isAssignable(typeUtils.erasure(propertyType), collectionType)) {
+                if (
+                    typeUtils.isSameType(
+                        (propertyType as DeclaredType).typeArguments.first(),
+                        validType
+                    )
+                ) {
+                    return true
+                }
+            } else if (typeUtils.isSameType(propertyType, validType)) {
+                return true
+            }
+        }
+        return false
+    }
+
+    /** Checks whether the property data type is of boolean type. */
+    fun isFieldOfBooleanType(property: Element): Boolean {
+        return isFieldOfExactType(property, booleanBoxType, booleanPrimitiveType)
+    }
+
+    /** Returns the annotation's params as a map. Includes the default values. */
+    fun getAnnotationParams(annotation: AnnotationMirror): Map<String, Any?> {
+        val values = env.elementUtils.getElementValuesWithDefaults(annotation)
+        val ret = mutableMapOf<String, Any?>()
+        for (entry in values.entries) {
+            val key = entry.key.simpleName.toString()
+            ret[key] = entry.value.value
+        }
+        return ret
+    }
+
+    /**
+     * Returns all the methods within a class, whether inherited or declared directly.
+     *
+     * Caches results internally, so it is cheap to call subsequently for the same input.
+     */
+    fun getAllMethods(clazz: TypeElement): LinkedHashSet<ExecutableElement> {
+        return allMethodsCache.computeIfAbsent(clazz) { type: TypeElement ->
+            env.elementUtils
+                .getAllMembers(type)
+                .stream()
+                .filter { it.kind == ElementKind.METHOD }
+                .map { it as ExecutableElement }
+                .collect(Collectors.toCollection { LinkedHashSet() })
+        }
+    }
+
+    /** Whether a type is the same as `long[]`. */
+    fun isPrimitiveLongArray(type: TypeMirror): Boolean {
+        return isArrayOf(type, longPrimitiveType)
+    }
+
+    /** Whether a type is the same as `double[]`. */
+    fun isPrimitiveDoubleArray(type: TypeMirror): Boolean {
+        return isArrayOf(type, doublePrimitiveType)
+    }
+
+    /** Whether a type is the same as `boolean[]`. */
+    fun isPrimitiveBooleanArray(type: TypeMirror): Boolean {
+        return isArrayOf(type, booleanPrimitiveType)
+    }
+
+    private fun isArrayOf(type: TypeMirror, arrayComponentType: TypeMirror): Boolean {
+        return typeUtils.isSameType(type, typeUtils.getArrayType(arrayComponentType))
+    }
+
+    /**
+     * Same as [.validateIsGetter] but additionally verifies that the getter returns the specified
+     * type.
+     */
+    fun validateIsGetterThatReturns(
+        method: ExecutableElement,
+        expectedReturnType: TypeMirror
+    ): MutableList<ProcessingException> {
+        val errors: MutableList<ProcessingException> = validateIsGetter(method)
+        if (!typeUtils.isAssignable(method.returnType, expectedReturnType)) {
+            errors.add(
+                ProcessingException(
+                    "Getter cannot be used: Does not return $expectedReturnType",
+                    method
+                )
+            )
+        }
+        return errors
+    }
+
+    /**
+     * A method's type and element (i.e. declaration).
+     *
+     * Note: The parameter and return types may differ between the type and the element. For
+     * example,
+     * <pre>
+     * public class StringSet implements Set<String> {...}
+     * </pre>
+     *
+     * Here, the type of `StringSet.add()` is `(String) -> boolean` and the element points to the
+     * generic declaration within `Set<T>` with a return type of `boolean` and a single parameter of
+     * type `T`.
+     */
+    class MethodTypeAndElement(val type: ExecutableType, val element: ExecutableElement)
+
+    /**
+     * Returns a stream of all the methods (including inherited) within a [DeclaredType].
+     *
+     * Does not include constructors.
+     */
+    fun getAllMethods(type: DeclaredType): Stream<MethodTypeAndElement> {
+        return elementUtils
+            .getAllMembers(type.asElement() as TypeElement)
+            .stream()
+            .filter { it.kind == ElementKind.METHOD }
+            .map {
+                MethodTypeAndElement(
+                    typeUtils.asMemberOf(type, it) as ExecutableType,
+                    it as ExecutableElement
+                )
+            }
+    }
+
+    /** Whether the method returns the specified type (or subtype). */
+    fun isReturnTypeMatching(method: ExecutableType, type: TypeMirror): Boolean {
+        return typeUtils.isAssignable(method.returnType, type)
+    }
+
+    /**
+     * Returns a type that the source type should be casted to coerce it to the target type.
+     *
+     * Handles the following cases:
+     * <pre>
+     * long|Long -> int|Integer = (int) ...
+     * double|Double -> float|Float = (float) ...
+     * </pre>
+     *
+     * Returns null if no cast is necessary.
+     */
+    fun getNarrowingCastType(sourceType: TypeMirror, targetType: TypeMirror): TypeMirror? {
+        if (
+            typeUtils.isSameType(targetType, intPrimitiveType) ||
+                typeUtils.isSameType(targetType, integerBoxType)
+        ) {
+            if (
+                typeUtils.isSameType(sourceType, longPrimitiveType) ||
+                    typeUtils.isSameType(sourceType, longBoxType)
+            ) {
+                return intPrimitiveType
+            }
+        }
+        if (
+            typeUtils.isSameType(targetType, floatPrimitiveType) ||
+                typeUtils.isSameType(targetType, floatBoxType)
+        ) {
+            if (
+                typeUtils.isSameType(sourceType, doublePrimitiveType) ||
+                    typeUtils.isSameType(sourceType, doubleBoxType)
+            ) {
+                return floatPrimitiveType
+            }
+        }
+        return null
+    }
+
+    /** Whether the element is a static method that returns the class it's enclosed within. */
+    fun isStaticFactoryMethod(element: Element): Boolean {
+        if (element.kind != ElementKind.METHOD || !element.modifiers.contains(Modifier.STATIC)) {
+            return false
+        }
+        val method = element as ExecutableElement
+        val enclosingType = method.enclosingElement.asType()
+        return typeUtils.isSameType(method.returnType, enclosingType)
+    }
+}
diff --git a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/PropertyAccessor.kt b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/PropertyAccessor.kt
index 84d27ef..96a6c47 100644
--- a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/PropertyAccessor.kt
+++ b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/PropertyAccessor.kt
@@ -130,8 +130,8 @@
             val isBooleanField =
                 helper.isFieldOfExactType(
                     privateField.element,
-                    helper.mBooleanPrimitiveType,
-                    helper.mBooleanBoxType
+                    helper.booleanPrimitiveType,
+                    helper.booleanBoxType
                 )
             if (isBooleanField && privateField.elementTypeCategory == ElementTypeCategory.SINGLE) {
                 getterNames.add("is$upperCamelCase")
diff --git a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/ToGenericDocumentCodeGenerator.java b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/ToGenericDocumentCodeGenerator.java
index f0d8d01..0d0c11a 100644
--- a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/ToGenericDocumentCodeGenerator.java
+++ b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/ToGenericDocumentCodeGenerator.java
@@ -281,7 +281,7 @@
                             return collectionForLoopAssign(
                                     annotation,
                                     getterOrField,
-                                    /* targetArrayComponentType= */mHelper.mLongPrimitiveType);
+                                    /* targetArrayComponentType= */mHelper.longPrimitiveType);
                         }
                     case ARRAY:
                         if (longSerializer != null) { // CustomType[]: 2d
@@ -293,7 +293,7 @@
                             return arrayForLoopAssign(
                                     annotation,
                                     getterOrField,
-                                    /* targetArrayComponentType= */mHelper.mLongPrimitiveType);
+                                    /* targetArrayComponentType= */mHelper.longPrimitiveType);
                         }
                     case SINGLE:
                         if (longSerializer != null) { // CustomType: 3d
@@ -313,7 +313,7 @@
                         return collectionForLoopAssign(
                                 annotation,
                                 getterOrField,
-                                /* targetArrayComponentType= */mHelper.mDoublePrimitiveType);
+                                /* targetArrayComponentType= */mHelper.doublePrimitiveType);
                     case ARRAY:
                         if (mHelper.isPrimitiveDoubleArray(getterOrField.getJvmType())) {
                             return arrayUseDirectly(annotation, getterOrField); // double[]: 2b
@@ -322,7 +322,7 @@
                             return arrayForLoopAssign(
                                     annotation,
                                     getterOrField,
-                                    /* targetArrayComponentType= */mHelper.mDoublePrimitiveType);
+                                    /* targetArrayComponentType= */mHelper.doublePrimitiveType);
                         }
                     case SINGLE:
                         if (getterOrField.getJvmType() instanceof PrimitiveType) {
@@ -341,7 +341,7 @@
                         return collectionForLoopAssign(
                                 annotation,
                                 getterOrField,
-                                /* targetArrayComponentType= */mHelper.mBooleanPrimitiveType);
+                                /* targetArrayComponentType= */mHelper.booleanPrimitiveType);
                     case ARRAY:
                         if (mHelper.isPrimitiveBooleanArray(getterOrField.getJvmType())) {
                             return arrayUseDirectly(annotation, getterOrField); // boolean[]: 2b
@@ -350,7 +350,7 @@
                             return arrayForLoopAssign(
                                     annotation,
                                     getterOrField,
-                                    /* targetArrayComponentType= */mHelper.mBooleanPrimitiveType);
+                                    /* targetArrayComponentType= */mHelper.booleanPrimitiveType);
                         }
                     case SINGLE:
                         if (getterOrField.getJvmType() instanceof PrimitiveType) {
@@ -369,7 +369,7 @@
                         return collectionForLoopAssign(
                                 annotation,
                                 getterOrField,
-                                /* targetArrayComponentType= */mHelper.mBytePrimitiveArrayType);
+                                /* targetArrayComponentType= */mHelper.bytePrimitiveArrayType);
                     case ARRAY: // byte[][]: 2b
                         return arrayUseDirectly(annotation, getterOrField);
                     case SINGLE: // byte[]: 2e
diff --git a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/BlobHandlePropertyAnnotation.kt b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/BlobHandlePropertyAnnotation.kt
index a02ee10..5702026 100644
--- a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/BlobHandlePropertyAnnotation.kt
+++ b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/BlobHandlePropertyAnnotation.kt
@@ -63,5 +63,5 @@
         get() = Kind.BLOB_HANDLE_PROPERTY
 
     override fun getUnderlyingTypeWithinGenericDoc(helper: IntrospectionHelper): TypeMirror =
-        helper.mBlobHandleType
+        helper.blobHandleType
 }
diff --git a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/BooleanPropertyAnnotation.kt b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/BooleanPropertyAnnotation.kt
index 88535e8..a00e4e8 100644
--- a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/BooleanPropertyAnnotation.kt
+++ b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/BooleanPropertyAnnotation.kt
@@ -58,5 +58,5 @@
         get() = Kind.BOOLEAN_PROPERTY
 
     override fun getUnderlyingTypeWithinGenericDoc(helper: IntrospectionHelper): TypeMirror =
-        helper.mBooleanPrimitiveType
+        helper.booleanPrimitiveType
 }
diff --git a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/BytesPropertyAnnotation.kt b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/BytesPropertyAnnotation.kt
index efe3f25..4896524 100644
--- a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/BytesPropertyAnnotation.kt
+++ b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/BytesPropertyAnnotation.kt
@@ -58,5 +58,5 @@
         get() = Kind.BYTES_PROPERTY
 
     override fun getUnderlyingTypeWithinGenericDoc(helper: IntrospectionHelper): TypeMirror =
-        helper.mBytePrimitiveArrayType
+        helper.bytePrimitiveArrayType
 }
diff --git a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/DocumentPropertyAnnotation.kt b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/DocumentPropertyAnnotation.kt
index 0b6646d..21c08c8 100644
--- a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/DocumentPropertyAnnotation.kt
+++ b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/DocumentPropertyAnnotation.kt
@@ -59,7 +59,7 @@
          *   params do not mention an explicit name.
          */
         fun parse(
-            annotationParams: Map<String?, Any?>,
+            annotationParams: Map<String, Any?>,
             defaultName: String
         ): DocumentPropertyAnnotation {
             val name = annotationParams["name"] as? String
@@ -85,5 +85,5 @@
         get() = Kind.DOCUMENT_PROPERTY
 
     override fun getUnderlyingTypeWithinGenericDoc(helper: IntrospectionHelper): TypeMirror =
-        helper.mGenericDocumentType
+        helper.genericDocumentType
 }
diff --git a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/DoublePropertyAnnotation.kt b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/DoublePropertyAnnotation.kt
index 262bc95..90f062e 100644
--- a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/DoublePropertyAnnotation.kt
+++ b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/DoublePropertyAnnotation.kt
@@ -58,5 +58,5 @@
         get() = Kind.DOUBLE_PROPERTY
 
     override fun getUnderlyingTypeWithinGenericDoc(helper: IntrospectionHelper): TypeMirror =
-        helper.mDoublePrimitiveType
+        helper.doublePrimitiveType
 }
diff --git a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/EmbeddingPropertyAnnotation.kt b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/EmbeddingPropertyAnnotation.kt
index dd8654b..4c02902 100644
--- a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/EmbeddingPropertyAnnotation.kt
+++ b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/EmbeddingPropertyAnnotation.kt
@@ -70,5 +70,5 @@
         get() = Kind.EMBEDDING_PROPERTY
 
     override fun getUnderlyingTypeWithinGenericDoc(helper: IntrospectionHelper): TypeMirror =
-        helper.mEmbeddingType
+        helper.embeddingType
 }
diff --git a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/LongPropertyAnnotation.kt b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/LongPropertyAnnotation.kt
index adb5c5c8..ebb6e19 100644
--- a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/LongPropertyAnnotation.kt
+++ b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/LongPropertyAnnotation.kt
@@ -94,5 +94,5 @@
         get() = Kind.LONG_PROPERTY
 
     override fun getUnderlyingTypeWithinGenericDoc(helper: IntrospectionHelper): TypeMirror =
-        helper.mLongPrimitiveType
+        helper.longPrimitiveType
 }
diff --git a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/MetadataPropertyAnnotation.java b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/MetadataPropertyAnnotation.java
index 8990fb5..92bf1d4 100644
--- a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/MetadataPropertyAnnotation.java
+++ b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/MetadataPropertyAnnotation.java
@@ -101,12 +101,12 @@
         switch (this) {
             case ID: // fall-through
             case NAMESPACE:
-                return helper.mStringType;
+                return helper.stringType;
             case CREATION_TIMESTAMP_MILLIS: // fall-through
             case TTL_MILLIS:
-                return helper.mLongPrimitiveType;
+                return helper.longPrimitiveType;
             case SCORE:
-                return helper.mIntPrimitiveType;
+                return helper.intPrimitiveType;
             default:
                 throw new IllegalStateException("Unhandled metadata property annotation: " + this);
         }
diff --git a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/StringPropertyAnnotation.kt b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/StringPropertyAnnotation.kt
index 52ad643..b0dc14d 100644
--- a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/StringPropertyAnnotation.kt
+++ b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/StringPropertyAnnotation.kt
@@ -102,5 +102,5 @@
         get() = Kind.STRING_PROPERTY
 
     override fun getUnderlyingTypeWithinGenericDoc(helper: IntrospectionHelper): TypeMirror =
-        helper.mStringType
+        helper.stringType
 }