Codegen for parcelable/dataclass boilerplate

This is the initial implementation of the `codegen` cli utility
for in-place java boilerplate generation

See DataClass and SampleDataClass for documentation/guide/examples.

See tools/codegen/ for implementation and tests/Codegen/ for tests.

Bug: 64221737
Test: . frameworks/base/tests/Codegen/runTest.sh
Change-Id: I75177cb770f1beabc87dbae9e77ce4b93ca08e7f
diff --git a/core/java/com/android/internal/util/AnnotationValidations.java b/core/java/com/android/internal/util/AnnotationValidations.java
new file mode 100644
index 0000000..c8afdd4
--- /dev/null
+++ b/core/java/com/android/internal/util/AnnotationValidations.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2019 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 com.android.internal.util;
+
+import static com.android.internal.util.BitUtils.flagsUpTo;
+
+import android.annotation.AppIdInt;
+import android.annotation.ColorInt;
+import android.annotation.FloatRange;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Size;
+import android.annotation.UserIdInt;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.PackageInfoFlags;
+import android.content.pm.PackageManager.PermissionResult;
+import android.os.UserHandle;
+
+import java.lang.annotation.Annotation;
+
+/**
+ * Validations for common annotations, e.g. {@link IntRange}, {@link UserIdInt}, etc.
+ *
+ * For usability from generated {@link DataClass} code, all validations are overloads of
+ * {@link #validate} with the following shape:
+ * {@code
+ *      <A extends Annotation> void validate(
+ *              Class<A> cls, A ignored, Object value[, (String, Object)... annotationParams])
+ * }
+ * The ignored {@link Annotation} parameter is used to differentiate between overloads that would
+ * otherwise have the same jvm signature. It's usually null at runtime.
+ */
+public class AnnotationValidations {
+    private AnnotationValidations() {}
+
+    public static void validate(Class<UserIdInt> annotation, UserIdInt ignored, int value) {
+        if ((value != UserHandle.USER_NULL && value < -3)
+                || value > Integer.MAX_VALUE / UserHandle.PER_USER_RANGE) {
+            invalid(annotation, value);
+        }
+    }
+
+    public static void validate(Class<AppIdInt> annotation, AppIdInt ignored, int value) {
+        if (value / UserHandle.PER_USER_RANGE != 0 || value < 0) {
+            invalid(annotation, value);
+        }
+    }
+
+    public static void validate(Class<IntRange> annotation, IntRange ignored, int value,
+            String paramName1, int param1, String paramName2, int param2) {
+        validate(annotation, ignored, value, paramName1, param1);
+        validate(annotation, ignored, value, paramName2, param2);
+    }
+
+    public static void validate(Class<IntRange> annotation, IntRange ignored, int value,
+            String paramName, int param) {
+        switch (paramName) {
+            case "from": if (value < param) invalid(annotation, value, paramName, param); break;
+            case "to": if (value > param) invalid(annotation, value, paramName, param); break;
+        }
+    }
+
+    public static void validate(Class<FloatRange> annotation, FloatRange ignored, float value,
+            String paramName1, float param1, String paramName2, float param2) {
+        validate(annotation, ignored, value, paramName1, param1);
+        validate(annotation, ignored, value, paramName2, param2);
+    }
+
+    public static void validate(Class<FloatRange> annotation, FloatRange ignored, float value,
+            String paramName, float param) {
+        switch (paramName) {
+            case "from": if (value < param) invalid(annotation, value, paramName, param); break;
+            case "to": if (value > param) invalid(annotation, value, paramName, param); break;
+        }
+    }
+
+    public static void validate(Class<NonNull> annotation, NonNull ignored, Object value) {
+        if (value == null) {
+            throw new NullPointerException();
+        }
+    }
+
+    public static void validate(Class<Size> annotation, Size ignored, int value,
+            String paramName1, int param1, String paramName2, int param2) {
+        validate(annotation, ignored, value, paramName1, param1);
+        validate(annotation, ignored, value, paramName2, param2);
+    }
+
+    public static void validate(Class<Size> annotation, Size ignored, int value,
+            String paramName, int param) {
+        switch (paramName) {
+            case "value": {
+                if (param != -1 && value != param) invalid(annotation, value, paramName, param);
+            } break;
+            case "min": {
+                if (value < param) invalid(annotation, value, paramName, param);
+            } break;
+            case "max": {
+                if (value > param) invalid(annotation, value, paramName, param);
+            } break;
+            case "multiple": {
+                if (value % param != 0) invalid(annotation, value, paramName, param);
+            } break;
+        }
+    }
+
+    public static void validate(
+            Class<PermissionResult> annotation, PermissionResult ignored, int value) {
+        validateIntEnum(annotation, value, PackageManager.PERMISSION_GRANTED);
+    }
+
+    public static void validate(
+            Class<PackageInfoFlags> annotation, PackageInfoFlags ignored, int value) {
+        validateIntFlags(annotation, value,
+                flagsUpTo(PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS));
+    }
+
+    public static void validate(
+            Class<Intent.Flags> annotation, Intent.Flags ignored, int value) {
+        validateIntFlags(annotation, value, flagsUpTo(Intent.FLAG_RECEIVER_OFFLOAD));
+    }
+
+
+    @Deprecated
+    public static void validate(Class<? extends Annotation> annotation,
+            Annotation ignored, Object value, Object... params) {}
+    @Deprecated
+    public static void validate(Class<? extends Annotation> annotation,
+            Annotation ignored, Object value) {}
+    @Deprecated
+    public static void validate(Class<? extends Annotation> annotation,
+            Annotation ignored, int value, Object... params) {}
+    public static void validate(Class<? extends Annotation> annotation,
+            Annotation ignored, int value) {
+        if (("android.annotation".equals(annotation.getPackageName$())
+                && annotation.getSimpleName().endsWith("Res"))
+                || ColorInt.class.equals(annotation)) {
+            if (value < 0) {
+                invalid(annotation, value);
+            }
+        }
+    }
+    public static void validate(Class<? extends Annotation> annotation,
+            Annotation ignored, long value) {
+        if ("android.annotation".equals(annotation.getPackageName$())
+                && annotation.getSimpleName().endsWith("Long")) {
+            if (value < 0L) {
+                invalid(annotation, value);
+            }
+        }
+    }
+
+    private static void validateIntEnum(
+            Class<? extends Annotation> annotation, int value, int lastValid) {
+        if (value > lastValid) {
+            invalid(annotation, value);
+        }
+    }
+    private static void validateIntFlags(
+            Class<? extends Annotation> annotation, int value, int validBits) {
+        if ((validBits & value) != validBits) {
+            invalid(annotation, "0x" + Integer.toHexString(value));
+        }
+    }
+
+    private static void invalid(Class<? extends Annotation> annotation, Object value) {
+        invalid("@" + annotation.getSimpleName(), value);
+    }
+
+    private static void invalid(Class<? extends Annotation> annotation, Object value,
+            String paramName, Object param) {
+        String paramPrefix = "value".equals(paramName) ? "" : paramName + " = ";
+        invalid("@" + annotation.getSimpleName() + "(" + paramPrefix + param + ")", value);
+    }
+
+    private static void invalid(String valueKind, Object value) {
+        throw new IllegalStateException("Invalid " + valueKind + ": " + value);
+    }
+}
diff --git a/core/java/com/android/internal/util/BitUtils.java b/core/java/com/android/internal/util/BitUtils.java
index 6158145..b4bab80 100644
--- a/core/java/com/android/internal/util/BitUtils.java
+++ b/core/java/com/android/internal/util/BitUtils.java
@@ -158,4 +158,18 @@
     public static byte[] toBytes(long l) {
         return ByteBuffer.allocate(8).putLong(l).array();
     }
+
+    /**
+     * 0b01000 -> 0b01111
+     */
+    public static int flagsUpTo(int lastFlag) {
+        return lastFlag <= 0 ? 0 : lastFlag | flagsUpTo(lastFlag >> 1);
+    }
+
+    /**
+     * 0b00010, 0b01000 -> 0b01110
+     */
+    public static int flagsWithin(int firstFlag, int lastFlag) {
+        return (flagsUpTo(lastFlag) & ~flagsUpTo(firstFlag)) | firstFlag;
+    }
 }
diff --git a/core/java/com/android/internal/util/DataClass.java b/core/java/com/android/internal/util/DataClass.java
new file mode 100644
index 0000000..146f546
--- /dev/null
+++ b/core/java/com/android/internal/util/DataClass.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2019 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 com.android.internal.util;
+
+import static java.lang.annotation.ElementType.*;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.annotation.StringDef;
+import android.os.Parcelable;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+
+@Retention(RetentionPolicy.SOURCE)
+@Target(ElementType.TYPE)
+public @interface DataClass {
+
+    /**
+     * Generates {@link Parcelable#writeToParcel}, {@link Parcelable#describeContents} and a
+     * {@link Parcelable.Creator}.
+     *
+     * Can be implicitly requested by adding "implements Parcelable" to class signature
+     *
+     * You can provide custom parceling logic by using a {@link ParcelWith} annotation with a
+     * custom {@link Parcelling} subclass.
+     *
+     * Alternatively, for one-off customizations you can declare methods like:
+     * {@code void parcelFieldName(Parcel dest, int flags)}
+     * {@code static FieldType unparcelFieldName(Parcel in)}
+     */
+    boolean genParcelable() default false;
+
+    /**
+     * Generates a simple "parcelable" .aidl file alongside the original .java file
+     *
+     * If not explicitly requested/suppressed, is on iff {@link #genParcelable} is on
+     */
+    boolean genAidl() default false;
+
+    /**
+     * Generates getters for each field.
+     *
+     * You can request for getter to lazily initialize your field by declaring a method like:
+     * {@code FieldType lazyInitFieldName()}
+     *
+     * You can request for the lazy initialization to be thread safe my marking the field volatile.
+     */
+    boolean genGetters() default true;
+
+    /**
+     * Generates setters for each field.
+     */
+    boolean genSetters() default false;
+
+    /**
+     * Generates a public constructor with each field initialized from a parameter and optionally
+     * some user-defined state validation at the end.
+     *
+     * Uses field {@link Nullable nullability}/default value presence to determine optional
+     * parameters.
+     *
+     * Requesting a {@link #genBuilder} suppresses public constructor generation by default.
+     *
+     * You receive a callback at the end of constructor call by declaring the method:
+     * {@code void onConstructed()}
+     * This is the place to put any custom validation logic.
+     */
+    boolean genConstructor() default true;
+
+    /**
+     * Generates a Builder for your class.
+     *
+     * Uses a package-private constructor under the hood, so same rules hold as for
+     * {@link #genConstructor()}
+     */
+    boolean genBuilder() default false;
+
+    /**
+     * Generates a structural {@link Object#equals} + {@link Object#hashCode}.
+     *
+     * You can customize individual fields' logic by declaring methods like:
+     * {@link boolean fieldNameEquals(ClassName otherInstance)}
+     * {@link boolean fieldNameEquals(FieldType otherValue)}
+     * {@link int fieldNameHashCode()}
+     */
+    boolean genEqualsHashCode() default false;
+
+    /**
+     * Generates a structural {@link Object#toString}.
+     *
+     * You can customize individual fields' logic by declaring methods like:
+     * {@link String fieldNameToString()}
+     */
+    boolean genToString() default false;
+
+    /**
+     * Generates a utility method that takes a {@link PerObjectFieldAction per-field callback}
+     * and calls it once for each field with its name and value.
+     *
+     * If some fields are of primitive types, and additional overload is generated that takes
+     * multiple callbacks, specialized for used primitive types to avoid auto-boxing, e.g.
+     * {@link PerIntFieldAction}.
+     */
+    boolean genForEachField() default false;
+
+    /**
+     * Generates a constructor that copies the given instance of the same class.
+     */
+    boolean genCopyConstructor() default false;
+
+    /**
+     * Generates constant annotations({@link IntDef}/{@link StringDef}) for any constant groups
+     * with common prefix.
+     * The annotation names are based on the common prefix.
+     *
+     * For int constants this additionally generates the corresponding static *ToString method and
+     * uses it in {@link Object#toString}.
+     *
+     * Additionally, any fields you annotate with the generated constants will be automatically
+     * validated in constructor.
+     *
+     * Int constants specified as hex(0x..) are considered to be flags, which is taken into account
+     * for in their *ToString and validation.
+     *
+     * You can optionally override the name of the generated annotation by annotating each constant
+     * with the desired annotation name.
+     *
+     * Unless suppressed, is implied by presence of constants with common prefix.
+     */
+    boolean genConstDefs() default true;
+
+
+    /**
+     * Allows specifying custom parcelling logic based on reusable
+     * {@link Parcelling} implementations
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @Target(FIELD)
+    @interface ParcelWith {
+        Class<? extends Parcelling> value();
+    }
+
+    /**
+     * Allows specifying a singular name for a builder's plural field name e.g. 'name' for 'mNames'
+     * Used for Builder's {@code addName(String name)} methods
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @Target(FIELD)
+    @interface PluralOf {
+        String value();
+    }
+
+    /**
+     * Marks that any annotations following it are applicable to each element of the
+     * collection/array, as opposed to itself.
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE})
+    @interface Each {}
+
+    /**
+     * @deprecated to be used by code generator exclusively
+     * @hide
+     */
+    @Deprecated
+    @Retention(RetentionPolicy.SOURCE)
+    @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, ANNOTATION_TYPE, CONSTRUCTOR, TYPE})
+    @interface Generated {
+        long time();
+        String codegenVersion();
+        String sourceFile();
+        String inputSignatures() default "";
+
+        /**
+         * @deprecated to be used by code generator exclusively
+         * @hide
+         */
+        @Deprecated
+        @Retention(RetentionPolicy.SOURCE)
+        @Target({FIELD, METHOD, ANNOTATION_TYPE, CONSTRUCTOR, TYPE})
+        @interface Member {}
+    }
+
+    /**
+     * Callback used by {@link #genForEachField}.
+     *
+     * @param <THIS> The enclosing data class instance.
+     *              Can be used to try and avoid capturing values from outside of the lambda,
+     *              minimizing allocations.
+     */
+    interface PerObjectFieldAction<THIS> {
+        void acceptObject(THIS self, String fieldName, Object fieldValue);
+    }
+
+    /**
+     * A specialization of {@link PerObjectFieldAction} called exclusively for int fields to avoid
+     * boxing.
+     */
+    interface PerIntFieldAction<THIS> {
+        void acceptInt(THIS self, String fieldName, int fieldValue);
+    }
+}
diff --git a/core/java/com/android/internal/util/Parcelling.java b/core/java/com/android/internal/util/Parcelling.java
new file mode 100644
index 0000000..63530dc
--- /dev/null
+++ b/core/java/com/android/internal/util/Parcelling.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2019 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 com.android.internal.util;
+
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.util.ArrayMap;
+
+import java.util.regex.Pattern;
+
+/**
+ * Describes a 2-way parcelling contract of type {@code T} into/out of a {@link Parcel}
+ *
+ * @param <T> the type being [un]parcelled
+ */
+public interface Parcelling<T> {
+
+    /**
+     * Write an item into parcel.
+     */
+    void parcel(T item, Parcel dest, int parcelFlags);
+
+    /**
+     * Read an item from parcel.
+     */
+    T unparcel(Parcel source);
+
+
+    /**
+     * A registry of {@link Parcelling} singletons.
+     */
+    class Cache {
+        private Cache() {}
+
+        private static ArrayMap<Class, Parcelling> sCache = new ArrayMap<>();
+
+        /**
+         * Retrieves an instance of a given {@link Parcelling} class if present.
+         */
+        public static @Nullable <P extends Parcelling<?>> P get(Class<P> clazz) {
+            return (P) sCache.get(clazz);
+        }
+
+        /**
+         * Stores an instance of a given {@link Parcelling}.
+         *
+         * @return the provided parcelling for convenience.
+         */
+        public static <P extends Parcelling<?>> P put(P parcelling) {
+            sCache.put(parcelling.getClass(), parcelling);
+            return parcelling;
+        }
+
+        /**
+         * Produces an instance of a given {@link Parcelling} class, by either retrieving a cached
+         * instance or reflectively creating one.
+         */
+        public static <P extends Parcelling<?>> P getOrCreate(Class<P> clazz) {
+            P cached = get(clazz);
+            if (cached != null) {
+                return cached;
+            } else {
+                try {
+                    return put(clazz.newInstance());
+                } catch (Exception e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        }
+    }
+
+    /**
+     * Common {@link Parcelling} implementations.
+     */
+    interface BuiltIn {
+
+        class ForPattern implements Parcelling<Pattern> {
+
+            @Override
+            public void parcel(Pattern item, Parcel dest, int parcelFlags) {
+                dest.writeString(item == null ? null : item.pattern());
+            }
+
+            @Override
+            public Pattern unparcel(Parcel source) {
+                String s = source.readString();
+                return s == null ? null : Pattern.compile(s);
+            }
+        }
+    }
+}
diff --git a/tests/Codegen/Android.bp b/tests/Codegen/Android.bp
new file mode 100644
index 0000000..966c560
--- /dev/null
+++ b/tests/Codegen/Android.bp
@@ -0,0 +1,25 @@
+android_test {
+    name: "CodegenTests",
+    srcs: [
+        "**/*.java",
+    ],
+
+    platform_apis: true,
+    test_suites: ["device-tests"],
+    certificate: "platform",
+
+    optimize: {
+        enabled: false,
+    },
+
+    plugins: [
+        "staledataclass-annotation-processor",
+    ],
+    static_libs: [
+        "junit",
+        "hamcrest",
+        "hamcrest-library",
+        "androidx.test.runner",
+        "androidx.test.rules",
+    ],
+}
diff --git a/tests/Codegen/AndroidManifest.xml b/tests/Codegen/AndroidManifest.xml
new file mode 100644
index 0000000..2f18550
--- /dev/null
+++ b/tests/Codegen/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<!--
+  ~ Copyright (C) 2015 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
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.codegentest">
+
+    <application/>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.codegentest"
+        android:label="Codegen test" />
+</manifest>
diff --git a/tests/Codegen/AndroidTest.xml b/tests/Codegen/AndroidTest.xml
new file mode 100644
index 0000000..4dbbc55
--- /dev/null
+++ b/tests/Codegen/AndroidTest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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.
+-->
+<configuration description="Runs Codegen Tests.">
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.codegentest" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+        <option name="hidden-api-checks" value="false"/>
+    </test>
+</configuration>
diff --git a/tests/Codegen/OWNERS b/tests/Codegen/OWNERS
new file mode 100644
index 0000000..da723b3
--- /dev/null
+++ b/tests/Codegen/OWNERS
@@ -0,0 +1 @@
[email protected]
\ No newline at end of file
diff --git a/tests/Codegen/runTest.sh b/tests/Codegen/runTest.sh
new file mode 100755
index 0000000..fe3adf9
--- /dev/null
+++ b/tests/Codegen/runTest.sh
@@ -0,0 +1,26 @@
+#!/usr/bin/env bash
+
+if [[ "$0" = *"/Codegen/runTest.sh" ]]; then
+	#running in subshell - print code to eval and exit
+	echo "source $0"
+else
+    function header_and_eval() {
+        printf "\n[ $* ]\n" 1>&2
+        eval "$@"
+        return $?
+    }
+
+    header_and_eval m -j16 codegen && \
+        header_and_eval codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/SampleDataClass.java && \
+        cd $ANDROID_BUILD_TOP &&
+        header_and_eval mmma -j16 frameworks/base/tests/Codegen && \
+        header_and_eval adb install -r -t $ANDROID_PRODUCT_OUT/testcases/CodegenTests/arm64/CodegenTests.apk && \
+        # header_and_eval adb shell am set-debug-app -w com.android.codegentest && \
+        header_and_eval adb shell am instrument -w -e package com.android.codegentest com.android.codegentest/androidx.test.runner.AndroidJUnitRunner
+
+        exitCode=$?
+
+        # header_and_eval adb shell am clear-debug-app
+
+        return $exitCode
+fi
\ No newline at end of file
diff --git a/tests/Codegen/src/com/android/codegentest/DateParcelling.java b/tests/Codegen/src/com/android/codegentest/DateParcelling.java
new file mode 100644
index 0000000..b0b00d0
--- /dev/null
+++ b/tests/Codegen/src/com/android/codegentest/DateParcelling.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2019 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 com.android.codegentest;
+
+import android.os.Parcel;
+
+import com.android.internal.util.Parcelling;
+
+import java.util.Date;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Sample {@link Parcelling} implementation for {@link Date}.
+ *
+ * See {@link SampleDataClass#mDate} for usage.
+ * See {@link SampleDataClass#writeToParcel} + {@link SampleDataClass#sParcellingForDate}
+ * for resulting generated code.
+ *
+ * Ignore {@link #sInstanceCount} - used for testing.
+ */
+public class DateParcelling implements Parcelling<Date> {
+
+    static AtomicInteger sInstanceCount = new AtomicInteger(0);
+
+    public DateParcelling() {
+        sInstanceCount.getAndIncrement();
+    }
+
+    @Override
+    public void parcel(Date item, Parcel dest, int parcelFlags) {
+        dest.writeLong(item.getTime());
+    }
+
+    @Override
+    public Date unparcel(Parcel source) {
+        return new Date(source.readLong());
+    }
+}
diff --git a/tests/Codegen/src/com/android/codegentest/SampleDataClass.aidl b/tests/Codegen/src/com/android/codegentest/SampleDataClass.aidl
new file mode 100644
index 0000000..f14d47c
--- /dev/null
+++ b/tests/Codegen/src/com/android/codegentest/SampleDataClass.aidl
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2019 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 com.android.codegentest;
+
+parcelable SampleDataClass;
diff --git a/tests/Codegen/src/com/android/codegentest/SampleDataClass.java b/tests/Codegen/src/com/android/codegentest/SampleDataClass.java
new file mode 100644
index 0000000..03127ec
--- /dev/null
+++ b/tests/Codegen/src/com/android/codegentest/SampleDataClass.java
@@ -0,0 +1,1542 @@
+/*
+ * Copyright (C) 2019 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 com.android.codegentest;
+
+import android.annotation.FloatRange;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.Size;
+import android.annotation.StringDef;
+import android.annotation.StringRes;
+import android.annotation.UserIdInt;
+import android.net.LinkAddress;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import com.android.internal.util.AnnotationValidations;
+import com.android.internal.util.DataClass;
+import com.android.internal.util.DataClass.Each;
+import com.android.internal.util.Parcelling;
+import com.android.internal.util.Preconditions;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Objects;
+import java.util.regex.Pattern;
+
+/**
+ * Sample data class, showing off various code generation features.
+ *
+ * See javadoc on non-generated code for the explanation of the various features.
+ *
+ * See {@link SampleDataClassTest} for various invariants the generated code is expected to hold.
+ */
+@DataClass(
+//        genParcelable = true, // implied by `implements Parcelable`
+//        genAidl = true,       // implied by `implements Parcelable`
+//        genGetters = true,    // on by default
+//        genConstDefs = true,  // implied by presence of constants with common prefix
+        genEqualsHashCode = true,
+        genBuilder = true,
+        genToString = true,
+        genForEachField = true,
+        genConstructor = true   // on by default but normally suppressed by genBuilder
+)
+public final class SampleDataClass implements Parcelable {
+
+    /**
+     * For any group of {@link int} or {@link String} constants like these, a corresponding
+     * {@link IntDef}/{@link StringDef} will get generated, with name based on common prefix
+     * by default.
+     *
+     * When {@link #SampleDataClass constructing} an instance, fields annotated with these
+     * annotations get automatically validated, with only provided constants being a valid value.
+     *
+     * @see StateName, the generated {@link StringDef}
+     * @see #mStateName annotated with {@link StateName}
+     */
+    public static final String STATE_NAME_UNDEFINED = "?";
+    public static final String STATE_NAME_ON = "on";
+    public static final String STATE_NAME_OFF = "off";
+
+    /**
+     * Additionally, for any generated {@link IntDef} a corresponding static
+     * *ToString method will be also generated, and used in {@link #toString()}.
+     *
+     * @see #stateToString(int)
+     * @see #toString()
+     * @see State
+     */
+    public static final int STATE_UNDEFINED = -1;
+    public static final int STATE_ON = 1;
+    public static final int STATE_OFF = 0;
+
+    /**
+     * {@link IntDef}s with values specified in hex("0x...") are considered to be
+     * {@link IntDef#flag flags}, while ones specified with regular int literals are considered
+     * not to be flags.
+     *
+     * This affects their string representation, e.g. see the difference in
+     * {@link #requestFlagsToString} vs {@link #stateToString}.
+     *
+     * This also affects the validation logic when {@link #SampleDataClass constructing}
+     * an instance, with any flag combination("|") being valid.
+     *
+     * You can customize the name of the generated {@link IntDef}/{@link StringDef} annotation
+     * by annotating each constant with the desired name before running the generation.
+     *
+     * Here the annotation is named {@link RequestFlags} instead of the default {@code Flags}.
+     */
+    public static final @RequestFlags int FLAG_MANUAL_REQUEST = 0x1;
+    public static final @RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST = 0x2;
+    public static final @RequestFlags int FLAG_AUGMENTED_REQUEST = 0x80000000;
+
+
+    /**
+     * Any property javadoc should go onto the field, and will be copied where appropriate,
+     * including getters, constructor parameters, builder setters, etc.
+     *
+     * <p>
+     * This allows to avoid the burden of maintaining copies of the same documentation
+     * pieces in multiple places for each field.
+     */
+    private int mNum;
+    /**
+     * Various javadoc features should work as expected when copied, e.g {@code code},
+     * {@link #mName links}, <a href="https://google.com">html links</a>, etc.
+     *
+     * @see #mNum2 ..and so should blocks at the bottom, e.g. {@code @see} blocks.
+     */
+    private int mNum2;
+    /**
+     * {@code @hide} javadoc annotation is also propagated, which can be used to adjust the
+     * desired public API surface.
+     *
+     * @see #getNum4() is hidden
+     * @see Builder#setNum4(int) also hidden
+     * @hide
+     */
+    private int mNum4;
+
+    /**
+     * {@link Nullable} fields are considered optional and will not throw an exception if omitted
+     * (or set to null) when creating an instance either via a {@link Builder} or constructor.
+     */
+    private @Nullable String mName;
+    /**
+     * Fields with default value expressions ("mFoo = ...") are also optional, and are automatically
+     * initialized to the provided default expression, unless explicitly set.
+     */
+    private String mName2 = "Bob";
+    /**
+     * Fields without {@link Nullable} annotation or default value are considered required.
+     *
+     * {@link NonNull} annotation is recommended on such non-primitive fields for documentation.
+     */
+    private @NonNull String mName4;
+
+    /**
+     * For parcelling, any field type supported by {@link Parcel} is supported out of the box.
+     * E.g. {@link Parcelable} subclasses, {@link String}, {@link int}, {@link boolean}, etc.
+     */
+    private AccessibilityNodeInfo mOtherParcelable = null;
+    /**
+     * Additionally, support for parcelling other types can be added by implementing a
+     * {@link Parcelling}, and referencing it in the {@link DataClass.ParcelWith} field annotation.
+     *
+     * @see DateParcelling an example {@link Parcelling} implementation
+     */
+    @DataClass.ParcelWith(DateParcelling.class)
+    private Date mDate = new Date(42 * 42);
+    /**
+     * If a {@link Parcelling} is fairly common, consider putting it in {@link Parcelling.BuiltIn}
+     * to encourage its reuse.
+     */
+    @DataClass.ParcelWith(Parcelling.BuiltIn.ForPattern.class)
+    private Pattern mPattern = Pattern.compile("");
+
+    /**
+     * For lists, when using a {@link Builder}, other than a regular
+     * {@link Builder#setLinkAddresses2(List) setter}, and additional
+     * {@link Builder#addLinkAddresses2(LinkAddress) add} method is generated for convenience.
+     */
+    private List<LinkAddress> mLinkAddresses2 = new ArrayList<>();
+    /**
+     * For aesthetics, you may want to consider providing a singular version of the plural field
+     * name, which would be used for the {@link #mLinkAddresses2 above mentioned} "add" method.
+     *
+     * @see Builder#addLinkAddress(LinkAddress)
+     */
+    @DataClass.PluralOf("linkAddress")
+    private ArrayList<LinkAddress> mLinkAddresses = new ArrayList<>();
+    /**
+     * For array fields, when using a {@link Builder}, vararg argument format is used for
+     * convenience.
+     *
+     * @see Builder#setLinkAddresses4(LinkAddress...)
+     */
+    private @Nullable LinkAddress[] mLinkAddresses4 = null;
+    /**
+     * For boolean fields, when using a {@link Builder}, in addition to a regular setter, methods
+     * like {@link Builder#markActive()} and {@link Builder#markNotActive()} are generated.
+     */
+    private boolean mActive = true;
+
+    /**
+     * {@link IntDef}/{@link StringDef}-annotated fields propagate the annotation to
+     * getter/constructor/setter/builder parameters, making for a nicer api.
+     *
+     * @see #getStateName
+     * @see Builder#setStateName
+     */
+    private @StateName String mStateName = STATE_NAME_UNDEFINED;
+    /**
+     * Fields annotated with {@link IntDef} annotations also get a proper {@link #toString()} value.
+     */
+    private @RequestFlags int mFlags;
+    /**
+     * Above is true for both {@link IntDef#flag flags} and enum-like {@link IntDef}s
+     */
+    private @State int mState = STATE_UNDEFINED;
+
+
+    /**
+     * Making a field public will suppress getter generation in favor of accessing it directly.
+     */
+    public CharSequence charSeq = "";
+    /**
+     * Final fields suppress generating a setter (when setters are requested).
+     */
+    private final LinkAddress[] mLinkAddresses5;
+    /**
+     * Transient fields are completely ignored and can be used for caching.
+     */
+    private transient LinkAddress[] mLinkAddresses6;
+    /**
+     * When using transient fields for caching it's often also a good idea to initialize them
+     * lazily.
+     *
+     * You can declare a special method like {@link #lazyInitTmpStorage()}, to let the
+     * {@link #getTmpStorage getter} lazily-initialize the value on demand.
+     */
+    transient int[] mTmpStorage;
+    private int[] lazyInitTmpStorage() {
+        return new int[100];
+    }
+
+    /**
+     * Fields with certain annotations are automatically validated in constructor
+     *
+     * You can see overloads in {@link AnnotationValidations} for a list of currently
+     * supported ones.
+     *
+     * You can also extend support to your custom annotations by creating another corresponding
+     * overloads like
+     * {@link AnnotationValidations#validate(Class, UserIdInt, int)}.
+     *
+     * @see #SampleDataClass
+     */
+    private @StringRes int mStringRes = 0;
+    /**
+     * Validation annotations may also have parameters.
+     *
+     * Parameter values will be supplied to validation method as name-value pairs.
+     *
+     * @see AnnotationValidations#validate(Class, Size, int, String, int, String, int)
+     */
+    private @android.annotation.IntRange(from = 0, to = 4) int mLimited = 3;
+    /**
+     * Unnamed validation annotation parameter gets supplied to the validating method named as
+     * "value".
+     *
+     * Validation annotations following {@link Each} annotation, will be applied for each
+     * array/collection element instead.
+     *
+     * @see AnnotationValidations#validate(Class, Size, int, String, int)
+     */
+    @Size(2)
+    @Each @FloatRange(from = 0f)
+    private float[] mCoords = new float[] {0f, 0f};
+
+
+    /**
+     * Manually declaring any method that would otherwise be generated suppresses its generation,
+     * allowing for fine-grained overrides of the generated behavior.
+     */
+    public LinkAddress[] getLinkAddresses4() {
+        //Suppress autogen
+        return null;
+    }
+
+    /**
+     * Additionally, some methods like {@link #equals}, {@link #hashCode}, {@link #toString},
+     * {@link #writeToParcel}, {@link Parcelable.Creator#createFromParcel} allow you to define
+     * special methods to override their behavior on a per-field basis.
+     *
+     * See the generateted methods' descriptions for the detailed instructions of what the method
+     * signatures for such methods are expected to be.
+     *
+     * Here we use this to "fix" {@link Pattern} not implementing equals/hashCode.
+     *
+     * @see #equals
+     * @see #hashCode
+     */
+    private boolean patternEquals(Pattern other) {
+        return Objects.equals(mPattern.pattern(), other.pattern());
+    }
+    private int patternHashCode() {
+        return Objects.hashCode(mPattern.pattern());
+    }
+
+    /**
+     * Similarly, {@link #onConstructed()}, if defined, gets called at the end of constructing an
+     * instance.
+     *
+     * At this point all fields should be in place, so this is the right place to put any custom
+     * validation logic.
+     */
+    private void onConstructed() {
+        Preconditions.checkState(mNum2 == mNum4);
+    }
+
+    /**
+     * {@link DataClass#genForEachField} can be used to generate a generic {@link #forEachField}
+     * utility, which can be used for various use-cases not covered out of the box.
+     * Callback passed to {@link #forEachField} will be called once per each property with its name
+     * and value.
+     *
+     * Here for example it's used to implement a typical dump method.
+     *
+     * Note that there are 2 {@link #forEachField} versions provided, one that treats each field
+     * value as an {@link Object}, thus boxing primitives if any, and one that additionally takes
+     * specialized callbacks for particular primitive field types used in given class.
+     *
+     * Some primitives like {@link Boolean}s and {@link Integer}s within [-128, 127] don't allocate
+     * when boxed, so it's up to you to decide which one to use for a given use-case.
+     */
+    public void dump(PrintWriter pw) {
+        forEachField((self, name, value) -> {
+            pw.append("  ").append(name).append(": ").append(String.valueOf(value)).append('\n');
+        });
+    }
+
+
+
+    // Code below generated by codegen v0.0.1.
+    //   on Jul 17, 2019, 5:10:26 PM PDT
+    //
+    // DO NOT MODIFY!
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/SampleDataClass.java
+    //
+    // CHECKSTYLE:OFF Generated code
+
+    @IntDef(prefix = "STATE_", value = {
+        STATE_UNDEFINED,
+        STATE_ON,
+        STATE_OFF
+    })
+    @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+    @DataClass.Generated.Member
+    public @interface State {}
+
+    @DataClass.Generated.Member
+    public static String stateToString(@State int value) {
+        switch (value) {
+            case STATE_UNDEFINED:
+                    return "STATE_UNDEFINED";
+            case STATE_ON:
+                    return "STATE_ON";
+            case STATE_OFF:
+                    return "STATE_OFF";
+            default: return Integer.toHexString(value);
+        }
+    }
+
+    @IntDef(flag = true, prefix = "FLAG_", value = {
+        FLAG_MANUAL_REQUEST,
+        FLAG_COMPATIBILITY_MODE_REQUEST,
+        FLAG_AUGMENTED_REQUEST
+    })
+    @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+    @DataClass.Generated.Member
+    public @interface RequestFlags {}
+
+    @DataClass.Generated.Member
+    public static String requestFlagsToString(@RequestFlags int value) {
+        return com.android.internal.util.BitUtils.flagsToString(
+                value, SampleDataClass::singleRequestFlagsToString);
+    }
+
+    @DataClass.Generated.Member
+    static String singleRequestFlagsToString(@RequestFlags int value) {
+        switch (value) {
+            case FLAG_MANUAL_REQUEST:
+                    return "FLAG_MANUAL_REQUEST";
+            case FLAG_COMPATIBILITY_MODE_REQUEST:
+                    return "FLAG_COMPATIBILITY_MODE_REQUEST";
+            case FLAG_AUGMENTED_REQUEST:
+                    return "FLAG_AUGMENTED_REQUEST";
+            default: return Integer.toHexString(value);
+        }
+    }
+
+    @StringDef(prefix = "STATE_NAME_", value = {
+        STATE_NAME_UNDEFINED,
+        STATE_NAME_ON,
+        STATE_NAME_OFF
+    })
+    @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+    @DataClass.Generated.Member
+    public @interface StateName {}
+
+    @DataClass.Generated(
+            time = 1563408627046L,
+            codegenVersion = "0.0.1",
+            sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleDataClass.java",
+            inputSignatures = "public static final  java.lang.String STATE_NAME_UNDEFINED\npublic static final  java.lang.String STATE_NAME_ON\npublic static final  java.lang.String STATE_NAME_OFF\npublic static final  int STATE_UNDEFINED\npublic static final  int STATE_ON\npublic static final  int STATE_OFF\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_AUGMENTED_REQUEST\nprivate  int mNum\nprivate  int mNum2\nprivate  int mNum4\nprivate @android.annotation.Nullable java.lang.String mName\nprivate  java.lang.String mName2\nprivate @android.annotation.NonNull java.lang.String mName4\nprivate  android.view.accessibility.AccessibilityNodeInfo mOtherParcelable\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.codegentest.DateParcelling.class) java.util.Date mDate\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForPattern.class) java.util.regex.Pattern mPattern\nprivate  java.util.List<android.net.LinkAddress> mLinkAddresses2\nprivate @com.android.internal.util.DataClass.PluralOf(\"linkAddress\") java.util.ArrayList<android.net.LinkAddress> mLinkAddresses\nprivate @android.annotation.Nullable android.net.LinkAddress[] mLinkAddresses4\nprivate  boolean mActive\nprivate @com.android.codegentest.SampleDataClass.StateName java.lang.String mStateName\nprivate @com.android.codegentest.SampleDataClass.RequestFlags int mFlags\nprivate @com.android.codegentest.SampleDataClass.State int mState\npublic  java.lang.CharSequence charSeq\nprivate final  android.net.LinkAddress[] mLinkAddresses5\nprivate transient  android.net.LinkAddress[] mLinkAddresses6\ntransient  int[] mTmpStorage\nprivate @android.annotation.StringRes int mStringRes\nprivate @android.annotation.IntRange(from=0L, to=4L) int mLimited\nprivate @android.annotation.Size(2L) @com.android.internal.util.DataClass.Each @android.annotation.FloatRange(from=0.0) float[] mCoords\nprivate  int[] lazyInitTmpStorage()\npublic  android.net.LinkAddress[] getLinkAddresses4()\nprivate  boolean patternEquals(java.util.regex.Pattern)\nprivate  int patternHashCode()\nprivate  void onConstructed()\npublic  void dump(java.io.PrintWriter)")
+
+/**
+     * @param num
+     *   Any property javadoc should go onto the field, and will be copied where appropriate,
+     *   including getters, constructor parameters, builder setters, etc.
+     *
+     *   <p>
+     *   This allows to avoid the burden of maintaining copies of the same documentation
+     *   pieces in multiple places for each field.
+     * @param num2
+     *   Various javadoc features should work as expected when copied, e.g {@code code},
+     *   {@link #mName links}, <a href="https://google.com">html links</a>, etc.
+     * @param num4
+     *   {@code @hide} javadoc annotation is also propagated, which can be used to adjust the
+     *   desired public API surface.
+     * @param name
+     *   {@link Nullable} fields are considered optional and will not throw an exception if omitted
+     *   (or set to null) when creating an instance either via a {@link Builder} or constructor.
+     * @param name2
+     *   Fields with default value expressions ("mFoo = ...") are also optional, and are automatically
+     *   initialized to the provided default expression, unless explicitly set.
+     * @param name4
+     *   Fields without {@link Nullable} annotation or default value are considered required.
+     *
+     *   {@link NonNull} annotation is recommended on such non-primitive fields for documentation.
+     * @param otherParcelable
+     *   For parcelling, any field type supported by {@link Parcel} is supported out of the box.
+     *   E.g. {@link Parcelable} subclasses, {@link String}, {@link int}, {@link boolean}, etc.
+     * @param date
+     *   Additionally, support for parcelling other types can be added by implementing a
+     *   {@link Parcelling}, and referencing it in the {@link DataClass.ParcelWith} field annotation.
+     * @param pattern
+     *   If a {@link Parcelling} is fairly common, consider putting it in {@link Parcelling.BuiltIn}
+     *   to encourage its reuse.
+     * @param linkAddresses2
+     *   For lists, when using a {@link Builder}, other than a regular
+     *   {@link Builder#setLinkAddresses2(List) setter}, and additional
+     *   {@link Builder#addLinkAddresses2(LinkAddress) add} method is generated for convenience.
+     * @param linkAddresses
+     *   For aesthetics, you may want to consider providing a singular version of the plural field
+     *   name, which would be used for the {@link #mLinkAddresses2 above mentioned} "add" method.
+     * @param linkAddresses4
+     *   For array fields, when using a {@link Builder}, vararg argument format is used for
+     *   convenience.
+     * @param active
+     *   For boolean fields, when using a {@link Builder}, in addition to a regular setter, methods
+     *   like {@link Builder#markActive()} and {@link Builder#markNotActive()} are generated.
+     * @param stateName
+     *   {@link IntDef}/{@link StringDef}-annotated fields propagate the annotation to
+     *   getter/constructor/setter/builder parameters, making for a nicer api.
+     * @param flags
+     *   Fields annotated with {@link IntDef} annotations also get a proper {@link #toString()} value.
+     * @param state
+     *   Above is true for both {@link IntDef#flag flags} and enum-like {@link IntDef}s
+     * @param charSeq
+     *   Making a field public will suppress getter generation in favor of accessing it directly.
+     * @param linkAddresses5
+     *   Final fields suppress generating a setter (when setters are requested).
+     * @param stringRes
+     *   Fields with certain annotations are automatically validated in constructor
+     *
+     *   You can see overloads in {@link AnnotationValidations} for a list of currently
+     *   supported ones.
+     *
+     *   You can also extend support to your custom annotations by creating another corresponding
+     *   overloads like
+     *   {@link AnnotationValidations#validate(Class, UserIdInt, int)}.
+     * @param limited
+     *   Validation annotations may also have parameters.
+     *
+     *   Parameter values will be supplied to validation method as name-value pairs.
+     * @param coords
+     *   Unnamed validation annotation parameter gets supplied to the validating method named as
+     *   "value".
+     *
+     *   Validation annotations following {@link Each} annotation, will be applied for each
+     *   array/collection element instead.
+     */
+    @DataClass.Generated.Member
+    public SampleDataClass(
+            int num,
+            int num2,
+            int num4,
+            @Nullable String name,
+            String name2,
+            @NonNull String name4,
+            AccessibilityNodeInfo otherParcelable,
+            Date date,
+            Pattern pattern,
+            List<LinkAddress> linkAddresses2,
+            ArrayList<LinkAddress> linkAddresses,
+            @Nullable LinkAddress[] linkAddresses4,
+            boolean active,
+            @StateName String stateName,
+            @RequestFlags int flags,
+            @State int state,
+            CharSequence charSeq,
+            LinkAddress[] linkAddresses5,
+            @StringRes int stringRes,
+            @android.annotation.IntRange(from = 0, to = 4) int limited,
+            @Size(2) @FloatRange(from = 0f) float[] coords) {
+        this.mNum = num;
+        this.mNum2 = num2;
+        this.mNum4 = num4;
+        this.mName = name;
+        this.mName2 = name2;
+        this.mName4 = Preconditions.checkNotNull(name4);
+        this.mOtherParcelable = otherParcelable;
+        this.mDate = date;
+        this.mPattern = pattern;
+        this.mLinkAddresses2 = linkAddresses2;
+        this.mLinkAddresses = linkAddresses;
+        this.mLinkAddresses4 = linkAddresses4;
+        this.mActive = active;
+        this.mStateName = stateName;
+        this.mFlags = flags;
+        this.mState = state;
+        this.charSeq = charSeq;
+        this.mLinkAddresses5 = linkAddresses5;
+        this.mStringRes = stringRes;
+        this.mLimited = limited;
+        this.mCoords = coords;
+        AnnotationValidations.validate(
+                NonNull.class, null, mName4);
+
+        //noinspection PointlessBooleanExpression
+        if (true
+                && !(Objects.equals(mStateName, STATE_NAME_UNDEFINED))
+                && !(Objects.equals(mStateName, STATE_NAME_ON))
+                && !(Objects.equals(mStateName, STATE_NAME_OFF))) {
+            throw new java.lang.IllegalArgumentException(
+                    "stateName was " + mStateName + " but must be one of: "
+                            + "STATE_NAME_UNDEFINED(" + STATE_NAME_UNDEFINED + "), "
+                            + "STATE_NAME_ON(" + STATE_NAME_ON + "), "
+                            + "STATE_NAME_OFF(" + STATE_NAME_OFF + ")");
+        }
+
+
+        //noinspection PointlessBitwiseExpression
+        Preconditions.checkFlagsArgument(
+                mFlags, 0
+                        | FLAG_MANUAL_REQUEST
+                        | FLAG_COMPATIBILITY_MODE_REQUEST
+                        | FLAG_AUGMENTED_REQUEST);
+
+        //noinspection PointlessBooleanExpression
+        if (true
+                && !(mState == STATE_UNDEFINED)
+                && !(mState == STATE_ON)
+                && !(mState == STATE_OFF)) {
+            throw new java.lang.IllegalArgumentException(
+                    "state was " + mState + " but must be one of: "
+                            + "STATE_UNDEFINED(" + STATE_UNDEFINED + "), "
+                            + "STATE_ON(" + STATE_ON + "), "
+                            + "STATE_OFF(" + STATE_OFF + ")");
+        }
+
+        AnnotationValidations.validate(
+                StringRes.class, null, mStringRes);
+        AnnotationValidations.validate(
+                android.annotation.IntRange.class, null, mLimited,
+                "from", 0,
+                "to", 4);
+        AnnotationValidations.validate(
+                Size.class, null, mCoords.length,
+                "value", 2);
+        int coordsSize = mCoords.length;
+        for (int i = 0; i < coordsSize; i++) {
+            AnnotationValidations.validate(
+                    FloatRange.class, null, mCoords[i],
+                    "from", 0f);
+        }
+
+
+        onConstructed();
+    }
+
+    /**
+     * Any property javadoc should go onto the field, and will be copied where appropriate,
+     * including getters, constructor parameters, builder setters, etc.
+     *
+     * <p>
+     * This allows to avoid the burden of maintaining copies of the same documentation
+     * pieces in multiple places for each field.
+     */
+    @DataClass.Generated.Member
+    public int getNum() {
+        return mNum;
+    }
+
+    /**
+     * Various javadoc features should work as expected when copied, e.g {@code code},
+     * {@link #mName links}, <a href="https://google.com">html links</a>, etc.
+     *
+     * @see #mNum2 ..and so should blocks at the bottom, e.g. {@code @see} blocks.
+     */
+    @DataClass.Generated.Member
+    public int getNum2() {
+        return mNum2;
+    }
+
+    /**
+     * {@code @hide} javadoc annotation is also propagated, which can be used to adjust the
+     * desired public API surface.
+     *
+     * @see #getNum4() is hidden
+     * @see Builder#setNum4(int) also hidden
+     * @hide
+     */
+    @DataClass.Generated.Member
+    public int getNum4() {
+        return mNum4;
+    }
+
+    /**
+     * {@link Nullable} fields are considered optional and will not throw an exception if omitted
+     * (or set to null) when creating an instance either via a {@link Builder} or constructor.
+     */
+    @DataClass.Generated.Member
+    public @Nullable String getName() {
+        return mName;
+    }
+
+    /**
+     * Fields with default value expressions ("mFoo = ...") are also optional, and are automatically
+     * initialized to the provided default expression, unless explicitly set.
+     */
+    @DataClass.Generated.Member
+    public String getName2() {
+        return mName2;
+    }
+
+    /**
+     * Fields without {@link Nullable} annotation or default value are considered required.
+     *
+     * {@link NonNull} annotation is recommended on such non-primitive fields for documentation.
+     */
+    @DataClass.Generated.Member
+    public @NonNull String getName4() {
+        return mName4;
+    }
+
+    /**
+     * For parcelling, any field type supported by {@link Parcel} is supported out of the box.
+     * E.g. {@link Parcelable} subclasses, {@link String}, {@link int}, {@link boolean}, etc.
+     */
+    @DataClass.Generated.Member
+    public AccessibilityNodeInfo getOtherParcelable() {
+        return mOtherParcelable;
+    }
+
+    /**
+     * Additionally, support for parcelling other types can be added by implementing a
+     * {@link Parcelling}, and referencing it in the {@link DataClass.ParcelWith} field annotation.
+     *
+     * @see DateParcelling an example {@link Parcelling} implementation
+     */
+    @DataClass.Generated.Member
+    public Date getDate() {
+        return mDate;
+    }
+
+    /**
+     * If a {@link Parcelling} is fairly common, consider putting it in {@link Parcelling.BuiltIn}
+     * to encourage its reuse.
+     */
+    @DataClass.Generated.Member
+    public Pattern getPattern() {
+        return mPattern;
+    }
+
+    /**
+     * For lists, when using a {@link Builder}, other than a regular
+     * {@link Builder#setLinkAddresses2(List) setter}, and additional
+     * {@link Builder#addLinkAddresses2(LinkAddress) add} method is generated for convenience.
+     */
+    @DataClass.Generated.Member
+    public List<LinkAddress> getLinkAddresses2() {
+        return mLinkAddresses2;
+    }
+
+    /**
+     * For aesthetics, you may want to consider providing a singular version of the plural field
+     * name, which would be used for the {@link #mLinkAddresses2 above mentioned} "add" method.
+     *
+     * @see Builder#addLinkAddress(LinkAddress)
+     */
+    @DataClass.Generated.Member
+    public ArrayList<LinkAddress> getLinkAddresses() {
+        return mLinkAddresses;
+    }
+
+    /**
+     * For boolean fields, when using a {@link Builder}, in addition to a regular setter, methods
+     * like {@link Builder#markActive()} and {@link Builder#markNotActive()} are generated.
+     */
+    @DataClass.Generated.Member
+    public boolean isActive() {
+        return mActive;
+    }
+
+    /**
+     * {@link IntDef}/{@link StringDef}-annotated fields propagate the annotation to
+     * getter/constructor/setter/builder parameters, making for a nicer api.
+     *
+     * @see #getStateName
+     * @see Builder#setStateName
+     */
+    @DataClass.Generated.Member
+    public @StateName String getStateName() {
+        return mStateName;
+    }
+
+    /**
+     * Fields annotated with {@link IntDef} annotations also get a proper {@link #toString()} value.
+     */
+    @DataClass.Generated.Member
+    public @RequestFlags int getFlags() {
+        return mFlags;
+    }
+
+    /**
+     * Above is true for both {@link IntDef#flag flags} and enum-like {@link IntDef}s
+     */
+    @DataClass.Generated.Member
+    public @State int getState() {
+        return mState;
+    }
+
+    /**
+     * Final fields suppress generating a setter (when setters are requested).
+     */
+    @DataClass.Generated.Member
+    public LinkAddress[] getLinkAddresses5() {
+        return mLinkAddresses5;
+    }
+
+    /**
+     * Fields with certain annotations are automatically validated in constructor
+     *
+     * You can see overloads in {@link AnnotationValidations} for a list of currently
+     * supported ones.
+     *
+     * You can also extend support to your custom annotations by creating another corresponding
+     * overloads like
+     * {@link AnnotationValidations#validate(Class, UserIdInt, int)}.
+     *
+     * @see #SampleDataClass
+     */
+    @DataClass.Generated.Member
+    public @StringRes int getStringRes() {
+        return mStringRes;
+    }
+
+    /**
+     * Validation annotations may also have parameters.
+     *
+     * Parameter values will be supplied to validation method as name-value pairs.
+     *
+     * @see AnnotationValidations#validate(Class, Size, int, String, int, String, int)
+     */
+    @DataClass.Generated.Member
+    public @android.annotation.IntRange(from = 0, to = 4) int getLimited() {
+        return mLimited;
+    }
+
+    /**
+     * Unnamed validation annotation parameter gets supplied to the validating method named as
+     * "value".
+     *
+     * Validation annotations following {@link Each} annotation, will be applied for each
+     * array/collection element instead.
+     *
+     * @see AnnotationValidations#validate(Class, Size, int, String, int)
+     */
+    @DataClass.Generated.Member
+    public @Size(2) @FloatRange(from = 0f) float[] getCoords() {
+        return mCoords;
+    }
+
+    /**
+     * When using transient fields for caching it's often also a good idea to initialize them
+     * lazily.
+     *
+     * You can declare a special method like {@link #lazyInitTmpStorage()}, to let the
+     * {@link #getTmpStorage getter} lazily-initialize the value on demand.
+     */
+    @DataClass.Generated.Member
+    public int[] getTmpStorage() {
+        int[] tmpStorage = mTmpStorage;
+        if (tmpStorage == null) {
+            // You can mark field as volatile for thread-safe double-check init
+            tmpStorage = mTmpStorage = lazyInitTmpStorage();
+        }
+        return tmpStorage;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "SampleDataClass { " +
+                "num = " + mNum + ", " +
+                "num2 = " + mNum2 + ", " +
+                "num4 = " + mNum4 + ", " +
+                "name = " + mName + ", " +
+                "name2 = " + mName2 + ", " +
+                "name4 = " + mName4 + ", " +
+                "otherParcelable = " + mOtherParcelable + ", " +
+                "date = " + mDate + ", " +
+                "pattern = " + mPattern + ", " +
+                "linkAddresses2 = " + mLinkAddresses2 + ", " +
+                "linkAddresses = " + mLinkAddresses + ", " +
+                "linkAddresses4 = " + java.util.Arrays.toString(mLinkAddresses4) + ", " +
+                "active = " + mActive + ", " +
+                "stateName = " + mStateName + ", " +
+                "flags = " + requestFlagsToString(mFlags) + ", " +
+                "state = " + stateToString(mState) + ", " +
+                "charSeq = " + charSeq + ", " +
+                "linkAddresses5 = " + java.util.Arrays.toString(mLinkAddresses5) + ", " +
+                "stringRes = " + mStringRes + ", " +
+                "limited = " + mLimited + ", " +
+                "coords = " + java.util.Arrays.toString(mCoords) +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public boolean equals(Object o) {
+        // You can override field equality logic by defining either of the methods like:
+        // boolean fieldNameEquals(SampleDataClass other) { ... }
+        // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        SampleDataClass that = (SampleDataClass) o;
+        //noinspection PointlessBooleanExpression
+        return true
+                && mNum == that.mNum
+                && mNum2 == that.mNum2
+                && mNum4 == that.mNum4
+                && Objects.equals(mName, that.mName)
+                && Objects.equals(mName2, that.mName2)
+                && Objects.equals(mName4, that.mName4)
+                && Objects.equals(mOtherParcelable, that.mOtherParcelable)
+                && Objects.equals(mDate, that.mDate)
+                && patternEquals(that.mPattern)
+                && Objects.equals(mLinkAddresses2, that.mLinkAddresses2)
+                && Objects.equals(mLinkAddresses, that.mLinkAddresses)
+                && java.util.Arrays.equals(mLinkAddresses4, that.mLinkAddresses4)
+                && mActive == that.mActive
+                && Objects.equals(mStateName, that.mStateName)
+                && mFlags == that.mFlags
+                && mState == that.mState
+                && Objects.equals(charSeq, that.charSeq)
+                && java.util.Arrays.equals(mLinkAddresses5, that.mLinkAddresses5)
+                && mStringRes == that.mStringRes
+                && mLimited == that.mLimited
+                && java.util.Arrays.equals(mCoords, that.mCoords);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int hashCode() {
+        // You can override field hashCode logic by defining methods like:
+        // int fieldNameHashCode() { ... }
+
+        int _hash = 1;
+        _hash = 31 * _hash + mNum;
+        _hash = 31 * _hash + mNum2;
+        _hash = 31 * _hash + mNum4;
+        _hash = 31 * _hash + Objects.hashCode(mName);
+        _hash = 31 * _hash + Objects.hashCode(mName2);
+        _hash = 31 * _hash + Objects.hashCode(mName4);
+        _hash = 31 * _hash + Objects.hashCode(mOtherParcelable);
+        _hash = 31 * _hash + Objects.hashCode(mDate);
+        _hash = 31 * _hash + patternHashCode();
+        _hash = 31 * _hash + Objects.hashCode(mLinkAddresses2);
+        _hash = 31 * _hash + Objects.hashCode(mLinkAddresses);
+        _hash = 31 * _hash + java.util.Arrays.hashCode(mLinkAddresses4);
+        _hash = 31 * _hash + Boolean.hashCode(mActive);
+        _hash = 31 * _hash + Objects.hashCode(mStateName);
+        _hash = 31 * _hash + mFlags;
+        _hash = 31 * _hash + mState;
+        _hash = 31 * _hash + Objects.hashCode(charSeq);
+        _hash = 31 * _hash + java.util.Arrays.hashCode(mLinkAddresses5);
+        _hash = 31 * _hash + mStringRes;
+        _hash = 31 * _hash + mLimited;
+        _hash = 31 * _hash + java.util.Arrays.hashCode(mCoords);
+        return _hash;
+    }
+
+    @DataClass.Generated.Member
+    void forEachField(
+            DataClass.PerIntFieldAction<SampleDataClass> actionInt,
+            DataClass.PerObjectFieldAction<SampleDataClass> actionObject) {
+        actionInt.acceptInt(this, "num", mNum);
+        actionInt.acceptInt(this, "num2", mNum2);
+        actionInt.acceptInt(this, "num4", mNum4);
+        actionObject.acceptObject(this, "name", mName);
+        actionObject.acceptObject(this, "name2", mName2);
+        actionObject.acceptObject(this, "name4", mName4);
+        actionObject.acceptObject(this, "otherParcelable", mOtherParcelable);
+        actionObject.acceptObject(this, "date", mDate);
+        actionObject.acceptObject(this, "pattern", mPattern);
+        actionObject.acceptObject(this, "linkAddresses2", mLinkAddresses2);
+        actionObject.acceptObject(this, "linkAddresses", mLinkAddresses);
+        actionObject.acceptObject(this, "linkAddresses4", mLinkAddresses4);
+        actionObject.acceptObject(this, "active", mActive);
+        actionObject.acceptObject(this, "stateName", mStateName);
+        actionInt.acceptInt(this, "flags", mFlags);
+        actionInt.acceptInt(this, "state", mState);
+        actionObject.acceptObject(this, "charSeq", charSeq);
+        actionObject.acceptObject(this, "linkAddresses5", mLinkAddresses5);
+        actionInt.acceptInt(this, "stringRes", mStringRes);
+        actionInt.acceptInt(this, "limited", mLimited);
+        actionObject.acceptObject(this, "coords", mCoords);
+    }
+
+    /** @deprecated May cause boxing allocations - use with caution! */
+    @Deprecated
+    @DataClass.Generated.Member
+    void forEachField(DataClass.PerObjectFieldAction<SampleDataClass> action) {
+        action.acceptObject(this, "num", mNum);
+        action.acceptObject(this, "num2", mNum2);
+        action.acceptObject(this, "num4", mNum4);
+        action.acceptObject(this, "name", mName);
+        action.acceptObject(this, "name2", mName2);
+        action.acceptObject(this, "name4", mName4);
+        action.acceptObject(this, "otherParcelable", mOtherParcelable);
+        action.acceptObject(this, "date", mDate);
+        action.acceptObject(this, "pattern", mPattern);
+        action.acceptObject(this, "linkAddresses2", mLinkAddresses2);
+        action.acceptObject(this, "linkAddresses", mLinkAddresses);
+        action.acceptObject(this, "linkAddresses4", mLinkAddresses4);
+        action.acceptObject(this, "active", mActive);
+        action.acceptObject(this, "stateName", mStateName);
+        action.acceptObject(this, "flags", mFlags);
+        action.acceptObject(this, "state", mState);
+        action.acceptObject(this, "charSeq", charSeq);
+        action.acceptObject(this, "linkAddresses5", mLinkAddresses5);
+        action.acceptObject(this, "stringRes", mStringRes);
+        action.acceptObject(this, "limited", mLimited);
+        action.acceptObject(this, "coords", mCoords);
+    }
+
+    @DataClass.Generated.Member
+    static Parcelling<Date> sParcellingForDate =
+            Parcelling.Cache.get(
+                    DateParcelling.class);
+    static {
+        if (sParcellingForDate == null) {
+            sParcellingForDate = Parcelling.Cache.put(
+                    new DateParcelling());
+        }
+    }
+
+    @DataClass.Generated.Member
+    static Parcelling<Pattern> sParcellingForPattern =
+            Parcelling.Cache.get(
+                    Parcelling.BuiltIn.ForPattern.class);
+    static {
+        if (sParcellingForPattern == null) {
+            sParcellingForPattern = Parcelling.Cache.put(
+                    new Parcelling.BuiltIn.ForPattern());
+        }
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        long flg = 0;
+        if (mActive) flg |= 0x1000;
+        if (mName != null) flg |= 0x8;
+        if (mName2 != null) flg |= 0x10;
+        if (mOtherParcelable != null) flg |= 0x40;
+        if (mDate != null) flg |= 0x80;
+        if (mPattern != null) flg |= 0x100;
+        if (mLinkAddresses2 != null) flg |= 0x200;
+        if (mLinkAddresses != null) flg |= 0x400;
+        if (mLinkAddresses4 != null) flg |= 0x800;
+        if (mStateName != null) flg |= 0x2000;
+        if (charSeq != null) flg |= 0x10000;
+        if (mLinkAddresses5 != null) flg |= 0x20000;
+        if (mCoords != null) flg |= 0x100000;
+        dest.writeLong(flg);
+        dest.writeInt(mNum);
+        dest.writeInt(mNum2);
+        dest.writeInt(mNum4);
+        if (mName != null) dest.writeString(mName);
+        if (mName2 != null) dest.writeString(mName2);
+        dest.writeString(mName4);
+        if (mOtherParcelable != null) dest.writeTypedObject(mOtherParcelable, flags);
+        sParcellingForDate.parcel(mDate, dest, flags);
+        sParcellingForPattern.parcel(mPattern, dest, flags);
+        if (mLinkAddresses2 != null) dest.writeParcelableList(mLinkAddresses2, flags);
+        if (mLinkAddresses != null) dest.writeParcelableList(mLinkAddresses, flags);
+        if (mLinkAddresses4 != null) dest.writeTypedArray(mLinkAddresses4, flags);
+        if (mStateName != null) dest.writeString(mStateName);
+        dest.writeInt(mFlags);
+        dest.writeInt(mState);
+        if (charSeq != null) dest.writeCharSequence(charSeq);
+        if (mLinkAddresses5 != null) dest.writeTypedArray(mLinkAddresses5, flags);
+        dest.writeInt(mStringRes);
+        dest.writeInt(mLimited);
+        if (mCoords != null) dest.writeFloatArray(mCoords);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<SampleDataClass> CREATOR
+            = new Parcelable.Creator<SampleDataClass>() {
+        @Override
+        public SampleDataClass[] newArray(int size) {
+            return new SampleDataClass[size];
+        }
+
+        @Override
+        @SuppressWarnings({"unchecked", "RedundantCast"})
+        public SampleDataClass createFromParcel(Parcel in) {
+            // You can override field unparcelling by defining methods like:
+            // static FieldType unparcelFieldName(Parcel in) { ... }
+
+            long flg = in.readLong();
+            boolean active = (flg & 0x1000) != 0;
+            int num = in.readInt();
+            int num2 = in.readInt();
+            int num4 = in.readInt();
+            String name = (flg & 0x8) == 0 ? null : in.readString();
+            String name2 = (flg & 0x10) == 0 ? null : in.readString();
+            String name4 = in.readString();
+            AccessibilityNodeInfo otherParcelable = (flg & 0x40) == 0 ? null : (AccessibilityNodeInfo) in.readTypedObject(AccessibilityNodeInfo.CREATOR);
+            Date date = sParcellingForDate.unparcel(in);
+            Pattern pattern = sParcellingForPattern.unparcel(in);
+            List<LinkAddress> linkAddresses2 = null;
+            if ((flg & 0x200) != 0) {
+                linkAddresses2 = new ArrayList<>();
+                in.readParcelableList(linkAddresses2, LinkAddress.class.getClassLoader());
+            }
+            ArrayList<LinkAddress> linkAddresses = null;
+            if ((flg & 0x400) != 0) {
+                linkAddresses = new ArrayList<>();
+                in.readParcelableList(linkAddresses, LinkAddress.class.getClassLoader());
+            }
+            LinkAddress[] linkAddresses4 = (flg & 0x800) == 0 ? null : (LinkAddress[]) in.createTypedArray(LinkAddress.CREATOR);
+            String stateName = (flg & 0x2000) == 0 ? null : in.readString();
+            int flags = in.readInt();
+            int state = in.readInt();
+            CharSequence _charSeq = (flg & 0x10000) == 0 ? null : (CharSequence) in.readCharSequence();
+            LinkAddress[] linkAddresses5 = (flg & 0x20000) == 0 ? null : (LinkAddress[]) in.createTypedArray(LinkAddress.CREATOR);
+            int stringRes = in.readInt();
+            int limited = in.readInt();
+            float[] coords = (flg & 0x100000) == 0 ? null : in.createFloatArray();
+            return new SampleDataClass(
+                    num,
+                    num2,
+                    num4,
+                    name,
+                    name2,
+                    name4,
+                    otherParcelable,
+                    date,
+                    pattern,
+                    linkAddresses2,
+                    linkAddresses,
+                    linkAddresses4,
+                    active,
+                    stateName,
+                    flags,
+                    state,
+                    _charSeq,
+                    linkAddresses5,
+                    stringRes,
+                    limited,
+                    coords);
+        }
+    };
+
+    /**
+     * A builder for {@link SampleDataClass}
+     */
+    @SuppressWarnings("WeakerAccess")
+    @DataClass.Generated.Member
+    public static class Builder
+            extends android.provider.OneTimeUseBuilder<SampleDataClass> {
+
+        protected int mNum;
+        protected int mNum2;
+        protected int mNum4;
+        protected @Nullable String mName;
+        protected String mName2;
+        protected @NonNull String mName4;
+        protected AccessibilityNodeInfo mOtherParcelable;
+        protected Date mDate;
+        protected Pattern mPattern;
+        protected List<LinkAddress> mLinkAddresses2;
+        protected ArrayList<LinkAddress> mLinkAddresses;
+        protected @Nullable LinkAddress[] mLinkAddresses4;
+        protected boolean mActive;
+        protected @StateName String mStateName;
+        protected @RequestFlags int mFlags;
+        protected @State int mState;
+        protected CharSequence charSeq;
+        protected LinkAddress[] mLinkAddresses5;
+        protected @StringRes int mStringRes;
+        protected @android.annotation.IntRange(from = 0, to = 4) int mLimited;
+        protected @Size(2) @FloatRange(from = 0f) float[] mCoords;
+
+        protected long mBuilderFieldsSet = 0L;
+
+        public Builder() {};
+
+        /**
+         * Any property javadoc should go onto the field, and will be copied where appropriate,
+         * including getters, constructor parameters, builder setters, etc.
+         *
+         * <p>
+         * This allows to avoid the burden of maintaining copies of the same documentation
+         * pieces in multiple places for each field.
+         */
+        @DataClass.Generated.Member
+        public Builder setNum(int value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x1;
+            mNum = value;
+            return this;
+        }
+
+        /**
+         * Various javadoc features should work as expected when copied, e.g {@code code},
+         * {@link #mName links}, <a href="https://google.com">html links</a>, etc.
+         *
+         * @see #mNum2 ..and so should blocks at the bottom, e.g. {@code @see} blocks.
+         */
+        @DataClass.Generated.Member
+        public Builder setNum2(int value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x2;
+            mNum2 = value;
+            return this;
+        }
+
+        /**
+         * {@code @hide} javadoc annotation is also propagated, which can be used to adjust the
+         * desired public API surface.
+         *
+         * @see #getNum4() is hidden
+         * @see Builder#setNum4(int) also hidden
+         * @hide
+         */
+        @DataClass.Generated.Member
+        public Builder setNum4(int value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x4;
+            mNum4 = value;
+            return this;
+        }
+
+        /**
+         * {@link Nullable} fields are considered optional and will not throw an exception if omitted
+         * (or set to null) when creating an instance either via a {@link Builder} or constructor.
+         */
+        @DataClass.Generated.Member
+        public Builder setName(@Nullable String value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x8;
+            mName = value;
+            return this;
+        }
+
+        /**
+         * Fields with default value expressions ("mFoo = ...") are also optional, and are automatically
+         * initialized to the provided default expression, unless explicitly set.
+         */
+        @DataClass.Generated.Member
+        public Builder setName2(String value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x10;
+            mName2 = value;
+            return this;
+        }
+
+        /**
+         * Fields without {@link Nullable} annotation or default value are considered required.
+         *
+         * {@link NonNull} annotation is recommended on such non-primitive fields for documentation.
+         */
+        @DataClass.Generated.Member
+        public Builder setName4(@NonNull String value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x20;
+            mName4 = value;
+            return this;
+        }
+
+        /**
+         * For parcelling, any field type supported by {@link Parcel} is supported out of the box.
+         * E.g. {@link Parcelable} subclasses, {@link String}, {@link int}, {@link boolean}, etc.
+         */
+        @DataClass.Generated.Member
+        public Builder setOtherParcelable(AccessibilityNodeInfo value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x40;
+            mOtherParcelable = value;
+            return this;
+        }
+
+        /**
+         * Additionally, support for parcelling other types can be added by implementing a
+         * {@link Parcelling}, and referencing it in the {@link DataClass.ParcelWith} field annotation.
+         *
+         * @see DateParcelling an example {@link Parcelling} implementation
+         */
+        @DataClass.Generated.Member
+        public Builder setDate(Date value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x80;
+            mDate = value;
+            return this;
+        }
+
+        /**
+         * If a {@link Parcelling} is fairly common, consider putting it in {@link Parcelling.BuiltIn}
+         * to encourage its reuse.
+         */
+        @DataClass.Generated.Member
+        public Builder setPattern(Pattern value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x100;
+            mPattern = value;
+            return this;
+        }
+
+        /**
+         * For lists, when using a {@link Builder}, other than a regular
+         * {@link Builder#setLinkAddresses2(List) setter}, and additional
+         * {@link Builder#addLinkAddresses2(LinkAddress) add} method is generated for convenience.
+         */
+        @DataClass.Generated.Member
+        public Builder setLinkAddresses2(List<LinkAddress> value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x200;
+            mLinkAddresses2 = value;
+            return this;
+        }
+
+        /** @see #setLinkAddresses2 */
+        @DataClass.Generated.Member
+        public Builder addLinkAddresses2(@NonNull LinkAddress value) {
+            // You can refine this method's name by providing item's singular name, e.g.:
+            // @DataClass.PluralOf("item")) mItems = ...
+
+            if (mLinkAddresses2 == null) setLinkAddresses2(new ArrayList<>());
+            mLinkAddresses2.add(value);
+            return this;
+        }
+
+        /**
+         * For aesthetics, you may want to consider providing a singular version of the plural field
+         * name, which would be used for the {@link #mLinkAddresses2 above mentioned} "add" method.
+         *
+         * @see Builder#addLinkAddress(LinkAddress)
+         */
+        @DataClass.Generated.Member
+        public Builder setLinkAddresses(ArrayList<LinkAddress> value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x400;
+            mLinkAddresses = value;
+            return this;
+        }
+
+        /** @see #setLinkAddresses */
+        @DataClass.Generated.Member
+        public Builder addLinkAddress(@NonNull LinkAddress value) {
+            if (mLinkAddresses == null) setLinkAddresses(new ArrayList<>());
+            mLinkAddresses.add(value);
+            return this;
+        }
+
+        /**
+         * For array fields, when using a {@link Builder}, vararg argument format is used for
+         * convenience.
+         *
+         * @see Builder#setLinkAddresses4(LinkAddress...)
+         */
+        @DataClass.Generated.Member
+        public Builder setLinkAddresses4(@Nullable LinkAddress... value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x800;
+            mLinkAddresses4 = value;
+            return this;
+        }
+
+        /**
+         * For boolean fields, when using a {@link Builder}, in addition to a regular setter, methods
+         * like {@link Builder#markActive()} and {@link Builder#markNotActive()} are generated.
+         */
+        @DataClass.Generated.Member
+        public Builder setActive(boolean value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x1000;
+            mActive = value;
+            return this;
+        }
+
+        /** @see #setActive */
+        @DataClass.Generated.Member
+        public Builder markActive() {
+            return setActive(true);
+        }
+
+        /** @see #setActive */
+        @DataClass.Generated.Member
+        public Builder markNotActive() {
+            return setActive(false);
+        }
+
+        /**
+         * {@link IntDef}/{@link StringDef}-annotated fields propagate the annotation to
+         * getter/constructor/setter/builder parameters, making for a nicer api.
+         *
+         * @see #getStateName
+         * @see Builder#setStateName
+         */
+        @DataClass.Generated.Member
+        public Builder setStateName(@StateName String value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x2000;
+            mStateName = value;
+            return this;
+        }
+
+        /**
+         * Fields annotated with {@link IntDef} annotations also get a proper {@link #toString()} value.
+         */
+        @DataClass.Generated.Member
+        public Builder setFlags(@RequestFlags int value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x4000;
+            mFlags = value;
+            return this;
+        }
+
+        /**
+         * Above is true for both {@link IntDef#flag flags} and enum-like {@link IntDef}s
+         */
+        @DataClass.Generated.Member
+        public Builder setState(@State int value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x8000;
+            mState = value;
+            return this;
+        }
+
+        /**
+         * Making a field public will suppress getter generation in favor of accessing it directly.
+         */
+        @DataClass.Generated.Member
+        public Builder setCharSeq(CharSequence value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x10000;
+            charSeq = value;
+            return this;
+        }
+
+        /**
+         * Final fields suppress generating a setter (when setters are requested).
+         */
+        @DataClass.Generated.Member
+        public Builder setLinkAddresses5(LinkAddress... value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x20000;
+            mLinkAddresses5 = value;
+            return this;
+        }
+
+        /**
+         * Fields with certain annotations are automatically validated in constructor
+         *
+         * You can see overloads in {@link AnnotationValidations} for a list of currently
+         * supported ones.
+         *
+         * You can also extend support to your custom annotations by creating another corresponding
+         * overloads like
+         * {@link AnnotationValidations#validate(Class, UserIdInt, int)}.
+         *
+         * @see #SampleDataClass
+         */
+        @DataClass.Generated.Member
+        public Builder setStringRes(@StringRes int value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x40000;
+            mStringRes = value;
+            return this;
+        }
+
+        /**
+         * Validation annotations may also have parameters.
+         *
+         * Parameter values will be supplied to validation method as name-value pairs.
+         *
+         * @see AnnotationValidations#validate(Class, Size, int, String, int, String, int)
+         */
+        @DataClass.Generated.Member
+        public Builder setLimited(@android.annotation.IntRange(from = 0, to = 4) int value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x80000;
+            mLimited = value;
+            return this;
+        }
+
+        /**
+         * Unnamed validation annotation parameter gets supplied to the validating method named as
+         * "value".
+         *
+         * Validation annotations following {@link Each} annotation, will be applied for each
+         * array/collection element instead.
+         *
+         * @see AnnotationValidations#validate(Class, Size, int, String, int)
+         */
+        @DataClass.Generated.Member
+        public Builder setCoords(@Size(2) @FloatRange(from = 0f) float... value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x100000;
+            mCoords = value;
+            return this;
+        }
+
+        /** Builds the instance. This builder should not be touched after calling this! */
+        public SampleDataClass build() {
+            markUsed();
+            if ((mBuilderFieldsSet & 0x1) == 0) {
+                throw new IllegalStateException("Required field not set: num");
+            }
+            if ((mBuilderFieldsSet & 0x2) == 0) {
+                throw new IllegalStateException("Required field not set: num2");
+            }
+            if ((mBuilderFieldsSet & 0x4) == 0) {
+                throw new IllegalStateException("Required field not set: num4");
+            }
+            if ((mBuilderFieldsSet & 0x10) == 0) {
+                mName2 = "Bob";
+            }
+            if ((mBuilderFieldsSet & 0x20) == 0) {
+                throw new IllegalStateException("Required field not set: name4");
+            }
+            if ((mBuilderFieldsSet & 0x40) == 0) {
+                mOtherParcelable = null;
+            }
+            if ((mBuilderFieldsSet & 0x80) == 0) {
+                mDate = new Date(42 * 42);
+            }
+            if ((mBuilderFieldsSet & 0x100) == 0) {
+                mPattern = Pattern.compile("");
+            }
+            if ((mBuilderFieldsSet & 0x200) == 0) {
+                mLinkAddresses2 = new ArrayList<>();
+            }
+            if ((mBuilderFieldsSet & 0x400) == 0) {
+                mLinkAddresses = new ArrayList<>();
+            }
+            if ((mBuilderFieldsSet & 0x800) == 0) {
+                mLinkAddresses4 = null;
+            }
+            if ((mBuilderFieldsSet & 0x1000) == 0) {
+                mActive = true;
+            }
+            if ((mBuilderFieldsSet & 0x2000) == 0) {
+                mStateName = STATE_NAME_UNDEFINED;
+            }
+            if ((mBuilderFieldsSet & 0x4000) == 0) {
+                throw new IllegalStateException("Required field not set: flags");
+            }
+            if ((mBuilderFieldsSet & 0x8000) == 0) {
+                mState = STATE_UNDEFINED;
+            }
+            if ((mBuilderFieldsSet & 0x10000) == 0) {
+                charSeq = "";
+            }
+            if ((mBuilderFieldsSet & 0x20000) == 0) {
+                throw new IllegalStateException("Required field not set: linkAddresses5");
+            }
+            if ((mBuilderFieldsSet & 0x40000) == 0) {
+                mStringRes = 0;
+            }
+            if ((mBuilderFieldsSet & 0x80000) == 0) {
+                mLimited = 3;
+            }
+            if ((mBuilderFieldsSet & 0x100000) == 0) {
+                mCoords = new float[] { 0f, 0f };
+            }
+            SampleDataClass o = new SampleDataClass(
+                    mNum,
+                    mNum2,
+                    mNum4,
+                    mName,
+                    mName2,
+                    mName4,
+                    mOtherParcelable,
+                    mDate,
+                    mPattern,
+                    mLinkAddresses2,
+                    mLinkAddresses,
+                    mLinkAddresses4,
+                    mActive,
+                    mStateName,
+                    mFlags,
+                    mState,
+                    charSeq,
+                    mLinkAddresses5,
+                    mStringRes,
+                    mLimited,
+                    mCoords);
+            return o;
+        }
+    }
+
+}
diff --git a/tests/Codegen/src/com/android/codegentest/SampleDataClassTest.java b/tests/Codegen/src/com/android/codegentest/SampleDataClassTest.java
new file mode 100644
index 0000000..71e85ab
--- /dev/null
+++ b/tests/Codegen/src/com/android/codegentest/SampleDataClassTest.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2019 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 com.android.codegentest;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThat;
+
+import android.net.LinkAddress;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.Date;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Tests {@link SampleDataClass} after it's augmented with dataclass codegen.
+ *
+ * Use {@code $ . runTest.sh} to run.
+ */
+@RunWith(AndroidJUnit4.class)
+public class SampleDataClassTest {
+
+    private SampleDataClass mSpecimen = newBuilder().build();
+
+    private static SampleDataClass.Builder newBuilder() {
+        return newIncompleteBuilder()
+                .setNum(42)
+                .setNum2(42)
+                .setNum4(42)
+                .setName4("foobar")
+                .setLinkAddresses5();
+    }
+
+    private static SampleDataClass.Builder newIncompleteBuilder() {
+        return new SampleDataClass.Builder()
+                .markActive()
+                .setName("some parcelable")
+                .setFlags(SampleDataClass.FLAG_MANUAL_REQUEST);
+    }
+
+    @Test
+    public void testParcelling_producesEqualInstance() {
+        SampleDataClass copy = parcelAndUnparcel(mSpecimen, SampleDataClass.CREATOR);
+        assertEquals(mSpecimen, copy);
+        assertEquals(mSpecimen.hashCode(), copy.hashCode());
+    }
+
+    @Test
+    public void testParcelling_producesInstanceWithEqualFields() {
+        SampleDataClass copy = parcelAndUnparcel(mSpecimen, SampleDataClass.CREATOR);
+        copy.forEachField((self, copyFieldName, copyFieldValue) -> {
+            mSpecimen.forEachField((self2, specimenFieldName, specimenFieldValue) -> {
+                if (copyFieldName.equals(specimenFieldName)
+                        && !copyFieldName.equals("pattern")
+                        && (specimenFieldValue == null
+                                || !specimenFieldValue.getClass().isArray())) {
+                    assertEquals("Mismatched field values for " + copyFieldName,
+                            specimenFieldValue, copyFieldValue);
+                }
+            });
+        });
+    }
+
+    @Test
+    public void testCustomParcelling_instanceIsCached() {
+        parcelAndUnparcel(mSpecimen, SampleDataClass.CREATOR);
+        parcelAndUnparcel(mSpecimen, SampleDataClass.CREATOR);
+        assertEquals(1, DateParcelling.sInstanceCount.get());
+    }
+
+    @Test
+    public void testDefaultFieldValue_isPropagated() {
+        assertEquals(new Date(42 * 42), mSpecimen.getDate());
+    }
+
+    @Test
+    public void testForEachField_avoidsBoxing() {
+        AtomicInteger intFieldCount = new AtomicInteger(0);
+        mSpecimen.forEachField(
+                (self, name, intValue) -> intFieldCount.getAndIncrement(),
+                (self, name, objectValue) -> {
+                    if (objectValue != null) {
+                        assertThat("Boxed field " + name,
+                                objectValue, not(instanceOf(Integer.class)));
+                    }
+                });
+        assertThat(intFieldCount.get(), greaterThanOrEqualTo(1));
+    }
+
+    @Test
+    public void testToString_containsEachField() {
+        String toString = mSpecimen.toString();
+
+        mSpecimen.forEachField((self, name, value) -> {
+            assertThat(toString, containsString(name));
+            if (value instanceof Integer) {
+                // Could be flags, their special toString tested separately
+            } else if (value instanceof Object[]) {
+                assertThat(toString, containsString(Arrays.toString((Object[]) value)));
+            } else if (value != null && value.getClass().isArray()) {
+                // Primitive array, uses multiple specialized Arrays.toString overloads
+            } else {
+                assertThat(toString, containsString("" + value));
+            }
+        });
+    }
+
+    @Test
+    public void testBuilder_propagatesValuesToInstance() {
+        assertEquals(43, newBuilder().setNum(43).build().getNum());
+    }
+
+    @Test
+    public void testPluralFields_canHaveCustomSingularBuilderName() {
+        newBuilder().addLinkAddress(new LinkAddress("127.0.0.1/24"));
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testBuilder_usableOnlyOnce() {
+        SampleDataClass.Builder builder = newBuilder();
+        builder.build();
+        builder.build();
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testBuilder_throwsWhenRequiredFieldMissing() {
+        newIncompleteBuilder().build();
+    }
+
+    @Test
+    public void testIntDefs_haveCorrectToString() {
+        int flagsAsInt = SampleDataClass.FLAG_MANUAL_REQUEST
+                | SampleDataClass.FLAG_COMPATIBILITY_MODE_REQUEST;
+        String flagsAsString = SampleDataClass.requestFlagsToString(flagsAsInt);
+
+        assertThat(flagsAsString, containsString("MANUAL_REQUEST"));
+        assertThat(flagsAsString, containsString("COMPATIBILITY_MODE_REQUEST"));
+        assertThat(flagsAsString, not(containsString("1")));
+        assertThat(flagsAsString, not(containsString("" + flagsAsInt)));
+
+        String dataclassToString = newBuilder()
+                .setFlags(flagsAsInt)
+                .setState(SampleDataClass.STATE_UNDEFINED)
+                .build()
+                .toString();
+        assertThat(dataclassToString, containsString(flagsAsString));
+        assertThat(dataclassToString, containsString("UNDEFINED"));
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testFlags_getValidated() {
+        newBuilder().setFlags(12345).build();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testIntEnums_getValidated() {
+        newBuilder().setState(12345).build();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testStringEnums_getValidated() {
+        newBuilder().setStateName("foo").build();
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testCustomValidation_isTriggered() {
+        newBuilder().setNum2(-1).setNum4(1).build();
+    }
+
+    @Test
+    public void testLazyInit_isLazilyCalledOnce() {
+        assertNull(mSpecimen.mTmpStorage);
+
+        int[] tmpStorage = mSpecimen.getTmpStorage();
+        assertNotNull(tmpStorage);
+        assertSame(tmpStorage, mSpecimen.mTmpStorage);
+
+        int[] tmpStorageAgain = mSpecimen.getTmpStorage();
+        assertSame(tmpStorage, tmpStorageAgain);
+    }
+
+    private static <T extends Parcelable> T parcelAndUnparcel(
+            T original, Parcelable.Creator<T> creator) {
+        Parcel p = Parcel.obtain();
+        try {
+            original.writeToParcel(p, 0);
+            p.setDataPosition(0);
+            return creator.createFromParcel(p);
+        } finally {
+            p.recycle();
+        }
+    }
+}
diff --git a/tools/codegen/.gitignore b/tools/codegen/.gitignore
new file mode 100755
index 0000000..9fb18b4
--- /dev/null
+++ b/tools/codegen/.gitignore
@@ -0,0 +1,2 @@
+.idea
+out
diff --git a/tools/codegen/Android.bp b/tools/codegen/Android.bp
new file mode 100644
index 0000000..805b296
--- /dev/null
+++ b/tools/codegen/Android.bp
@@ -0,0 +1,18 @@
+java_binary_host {
+    name: "codegen",
+    manifest: "manifest.txt",
+    srcs: [
+        "src/**/*.kt",
+    ],
+    static_libs: [
+        "javaparser",
+    ],
+}
+
+java_library_host {
+    name: "codegen-version-info",
+
+    srcs: [
+        "src/**/SharedConstants.kt",
+    ],
+}
diff --git a/tools/codegen/OWNERS b/tools/codegen/OWNERS
new file mode 100644
index 0000000..da723b3
--- /dev/null
+++ b/tools/codegen/OWNERS
@@ -0,0 +1 @@
[email protected]
\ No newline at end of file
diff --git a/tools/codegen/manifest.txt b/tools/codegen/manifest.txt
new file mode 100644
index 0000000..6e1018b
--- /dev/null
+++ b/tools/codegen/manifest.txt
@@ -0,0 +1 @@
+Main-class: com.android.codegen.MainKt
diff --git a/tools/codegen/src/com/android/codegen/ClassInfo.kt b/tools/codegen/src/com/android/codegen/ClassInfo.kt
new file mode 100644
index 0000000..7ee79f6
--- /dev/null
+++ b/tools/codegen/src/com/android/codegen/ClassInfo.kt
@@ -0,0 +1,49 @@
+package com.android.codegen
+
+import com.github.javaparser.JavaParser
+import com.github.javaparser.ParseProblemException
+import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration
+
+open class ClassInfo(val sourceLines: List<String>) {
+
+    private val userSourceCode = (sourceLines + "}").joinToString("\n")
+    val fileAst = try {
+        JavaParser.parse(userSourceCode)!!
+    } catch (e: ParseProblemException) {
+        throw RuntimeException("Failed to parse code:\n" +
+                userSourceCode
+                        .lines()
+                        .mapIndexed { lnNum, ln -> "/*$lnNum*/$ln" }
+                        .joinToString("\n"),
+                e)
+    }
+    val classAst = fileAst.types[0] as ClassOrInterfaceDeclaration
+
+    fun hasMethod(name: String, vararg argTypes: String): Boolean {
+        return classAst.methods.any {
+            it.name.asString() == name &&
+                    it.parameters.map { it.type.asString() } == argTypes.toList()
+        }
+    }
+
+    val superInterfaces = (fileAst.types[0] as ClassOrInterfaceDeclaration)
+            .implementedTypes.map { it.asString() }
+
+    val ClassName = classAst.nameAsString
+    private val genericArgsAst = classAst.typeParameters
+    val genericArgs = if (genericArgsAst.isEmpty()) "" else {
+        genericArgsAst.map { it.nameAsString }.joinToString(", ").let { "<$it>" }
+    }
+    val ClassType = ClassName + genericArgs
+
+    val constDefs = mutableListOf<ConstDef>()
+
+    val fields = classAst.fields
+            .filterNot { it.isTransient || it.isStatic }
+            .mapIndexed { i, node -> FieldInfo(index = i, fieldAst = node, classInfo = this) }
+            .apply { lastOrNull()?.isLast = true }
+    val lazyTransientFields = classAst.fields
+            .filter { it.isTransient && !it.isStatic }
+            .mapIndexed { i, node -> FieldInfo(index = i, fieldAst = node, classInfo = this) }
+            .filter { hasMethod("lazyInit${it.NameUpperCamel}") }
+}
\ No newline at end of file
diff --git a/tools/codegen/src/com/android/codegen/ClassPrinter.kt b/tools/codegen/src/com/android/codegen/ClassPrinter.kt
new file mode 100644
index 0000000..33256b7
--- /dev/null
+++ b/tools/codegen/src/com/android/codegen/ClassPrinter.kt
@@ -0,0 +1,311 @@
+package com.android.codegen
+
+import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration
+import com.github.javaparser.ast.body.TypeDeclaration
+import com.github.javaparser.ast.expr.BooleanLiteralExpr
+import com.github.javaparser.ast.expr.NormalAnnotationExpr
+import com.github.javaparser.ast.type.ClassOrInterfaceType
+
+/**
+ * [ClassInfo] + utilities for printing out new class code with proper indentation and imports
+ */
+class ClassPrinter(
+    source: List<String>,
+    private val stringBuilder: StringBuilder,
+    var cliArgs: Array<String>
+) : ClassInfo(source) {
+
+    val GENERATED_MEMBER_HEADER by lazy { "@$GeneratedMember" }
+
+    // Imports
+    val NonNull by lazy { classRef("android.annotation.NonNull") }
+    val NonEmpty by lazy { classRef("android.annotation.NonEmpty") }
+    val Nullable by lazy { classRef("android.annotation.Nullable") }
+    val TextUtils by lazy { classRef("android.text.TextUtils") }
+    val LinkedHashMap by lazy { classRef("java.util.LinkedHashMap") }
+    val Collections by lazy { classRef("java.util.Collections") }
+    val Preconditions by lazy { classRef("com.android.internal.util.Preconditions") }
+    val ArrayList by lazy { classRef("java.util.ArrayList") }
+    val DataClass by lazy { classRef("com.android.internal.util.DataClass") }
+    val DataClassEnum by lazy { classRef("com.android.internal.util.DataClass.Enum") }
+    val ParcelWith by lazy { classRef("com.android.internal.util.DataClass.ParcelWith") }
+    val PluralOf by lazy { classRef("com.android.internal.util.DataClass.PluralOf") }
+    val Each by lazy { classRef("com.android.internal.util.DataClass.Each") }
+    val DataClassGenerated by lazy { classRef("com.android.internal.util.DataClass.Generated") }
+    val GeneratedMember by lazy { classRef("com.android.internal.util.DataClass.Generated.Member") }
+    val Parcelling by lazy { classRef("com.android.internal.util.Parcelling") }
+    val UnsupportedAppUsage by lazy { classRef("android.annotation.UnsupportedAppUsage") }
+
+
+    /**
+     * Optionally shortens a class reference if there's a corresponding import present
+     */
+    fun classRef(fullName: String): String {
+        if (cliArgs.contains(FLAG_NO_FULL_QUALIFIERS)) {
+            return fullName.split(".").dropWhile { it[0].isLowerCase() }.joinToString(".")
+        }
+
+        val pkg = fullName.substringBeforeLast(".")
+        val simpleName = fullName.substringAfterLast(".")
+        if (fileAst.imports.any { imprt ->
+                    imprt.nameAsString == fullName
+                            || (imprt.isAsterisk && imprt.nameAsString == pkg)
+                }) {
+            return simpleName
+        } else {
+            val outerClass = pkg.substringAfterLast(".", "")
+            if (outerClass.firstOrNull()?.isUpperCase() ?: false) {
+                return classRef(pkg) + "." + simpleName
+            }
+        }
+        return fullName
+    }
+
+    /** @see classRef */
+    inline fun <reified T : Any> classRef(): String {
+        return classRef(T::class.java.name)
+    }
+
+    /** @see classRef */
+    fun memberRef(fullName: String): String {
+        val className = fullName.substringBeforeLast(".")
+        val methodName = fullName.substringAfterLast(".")
+        return if (fileAst.imports.any {
+                    it.isStatic
+                            && (it.nameAsString == fullName
+                            || (it.isAsterisk && it.nameAsString == className))
+                }) {
+            className.substringAfterLast(".") + "." + methodName
+        } else {
+            classRef(className) + "." + methodName
+        }
+    }
+
+    val dataClassAnnotationFeatures = classAst.annotations
+            .find { it.nameAsString == DataClass }
+            ?.let { it as? NormalAnnotationExpr }
+            ?.pairs
+            ?.map { pair -> pair.nameAsString to (pair.value as BooleanLiteralExpr).value }
+            ?.toMap()
+            ?: emptyMap()
+
+    val internalAnnotations = setOf(ParcelWith, DataClassEnum, PluralOf, Each, UnsupportedAppUsage)
+
+    /**
+     * @return whether the given feature is enabled
+     */
+    operator fun FeatureFlag.invoke(): Boolean {
+        if (cliArgs.contains("--no-$kebabCase")) return false
+        if (cliArgs.contains("--$kebabCase")) return true
+
+        val annotationKey = "gen$upperCamelCase"
+        if (dataClassAnnotationFeatures.containsKey(annotationKey)) {
+            return dataClassAnnotationFeatures[annotationKey]!!
+        }
+
+        if (cliArgs.contains("--all")) return true
+        if (hidden) return true
+
+        return when (this) {
+            FeatureFlag.SETTERS ->
+                !FeatureFlag.CONSTRUCTOR() && !FeatureFlag.BUILDER() && fields.any { !it.isFinal }
+            FeatureFlag.BUILDER -> cliArgs.contains(FLAG_BUILDER_PROTECTED_SETTERS) || onByDefault
+            FeatureFlag.CONSTRUCTOR -> !FeatureFlag.BUILDER()
+            FeatureFlag.PARCELABLE -> "Parcelable" in superInterfaces
+            FeatureFlag.AIDL -> FeatureFlag.PARCELABLE()
+            FeatureFlag.IMPLICIT_NONNULL -> fields.any { it.isNullable }
+                    && fields.none { "@$NonNull" in it.annotations }
+            else -> onByDefault
+        }
+    }
+
+    val FeatureFlag.hidden
+        get(): Boolean = when {
+            cliArgs.contains("--hidden-$kebabCase") -> true
+            this == FeatureFlag.BUILD_UPON -> FeatureFlag.BUILDER.hidden
+            else -> false
+        }
+
+    var currentIndent = INDENT_SINGLE
+        private set
+
+    fun pushIndent() {
+        currentIndent += INDENT_SINGLE
+    }
+
+    fun popIndent() {
+        currentIndent = currentIndent.substring(0, currentIndent.length - INDENT_SINGLE.length)
+    }
+
+    fun backspace() = stringBuilder.setLength(stringBuilder.length - 1)
+    val lastChar get() = stringBuilder[stringBuilder.length - 1]
+
+    private fun appendRaw(s: String) {
+        stringBuilder.append(s)
+    }
+
+    fun append(s: String) {
+        if (s.isBlank() && s != "\n") {
+            appendRaw(s)
+        } else {
+            appendRaw(s.lines().map { line ->
+                if (line.startsWith(" *")) line else line.trimStart()
+            }.joinToString("\n$currentIndent"))
+        }
+    }
+
+    fun appendSameLine(s: String) {
+        while (lastChar.isWhitespace() || lastChar.isNewline()) {
+            backspace()
+        }
+        appendRaw(s)
+    }
+
+    fun rmEmptyLine() {
+        while (lastChar.isWhitespaceNonNewline()) backspace()
+        if (lastChar.isNewline()) backspace()
+    }
+
+    /**
+     * Syntactic sugar for:
+     * ```
+     * +"code()";
+     * ```
+     * to append the given string plus a newline
+     */
+    operator fun String.unaryPlus() = append("$this\n")
+
+    /**
+     * Syntactic sugar for:
+     * ```
+     * !"code()";
+     * ```
+     * to append the given string without a newline
+     */
+    operator fun String.not() = append(this)
+
+    /**
+     * Syntactic sugar for:
+     * ```
+     * ... {
+     *     ...
+     * }+";"
+     * ```
+     * to append a ';' on same line after a block, and a newline afterwards
+     */
+    operator fun Unit.plus(s: String) {
+        appendSameLine(s)
+        +""
+    }
+
+    /**
+     * A multi-purpose syntactic sugar for appending the given string plus anything generated in
+     * the given [block], the latter with the appropriate deeper indent,
+     * and resetting the indent back to original at the end
+     *
+     * Usage examples:
+     *
+     * ```
+     * "if (...)" {
+     *     ...
+     * }
+     * ```
+     * to append a corresponding if block appropriate indentation
+     *
+     * ```
+     * "void foo(...)" {
+     *      ...
+     * }
+     * ```
+     * similar to the previous one, plus an extra empty line after the function body
+     *
+     * ```
+     * "void foo(" {
+     *      <args code>
+     * }
+     * ```
+     * to use proper indentation for args code and close the bracket on same line at end
+     *
+     * ```
+     * "..." {
+     *     ...
+     * }
+     * to use the correct indentation for inner code, resetting it at the end
+     */
+    inline operator fun String.invoke(block: ClassPrinter.() -> Unit) {
+        if (this == " {") {
+            appendSameLine(this)
+        } else {
+            append(this)
+        }
+        when {
+            endsWith("(") -> {
+                indentedBy(2, block)
+                appendSameLine(")")
+            }
+            endsWith("{") || endsWith(")") -> {
+                if (!endsWith("{")) appendSameLine(" {")
+                indentedBy(1, block)
+                +"}"
+                if ((endsWith(") {") || endsWith(")") || this == " {")
+                        && !startsWith("synchronized")
+                        && !startsWith("switch")
+                        && !startsWith("if ")
+                        && !contains(" else ")
+                        && !contains("new ")
+                        && !contains("return ")) {
+                    +"" // extra line after function definitions
+                }
+            }
+            else -> indentedBy(2, block)
+        }
+    }
+
+    inline fun indentedBy(level: Int, block: ClassPrinter.() -> Unit) {
+        append("\n")
+        level times {
+            append(INDENT_SINGLE)
+            pushIndent()
+        }
+        block()
+        level times { popIndent() }
+        rmEmptyLine()
+        +""
+    }
+
+    inline fun Iterable<FieldInfo>.forEachTrimmingTrailingComma(b: FieldInfo.() -> Unit) {
+        forEachApply {
+            b()
+            if (isLast) {
+                while (lastChar == ' ' || lastChar == '\n') backspace()
+                if (lastChar == ',') backspace()
+            }
+        }
+    }
+
+    inline operator fun <R> invoke(f: ClassPrinter.() -> R): R = run(f)
+
+    var BuilderClass = CANONICAL_BUILDER_CLASS
+    var BuilderType = BuilderClass + genericArgs
+
+    init {
+        val builderFactoryOverride = classAst.methods.find {
+            it.isStatic && it.nameAsString == "builder"
+        }
+        if (builderFactoryOverride != null) {
+            BuilderClass = (builderFactoryOverride.type as ClassOrInterfaceType).nameAsString
+            BuilderType = builderFactoryOverride.type.asString()
+        } else {
+            val builderExtension = (fileAst.types
+                    + classAst.childNodes.filterIsInstance(TypeDeclaration::class.java)).find {
+                it.nameAsString == CANONICAL_BUILDER_CLASS
+            }
+            if (builderExtension != null) {
+                BuilderClass = GENERATED_BUILDER_CLASS
+                val tp = (builderExtension as ClassOrInterfaceDeclaration).typeParameters
+                BuilderType = if (tp.isEmpty()) BuilderClass
+                else "$BuilderClass<${tp.map { it.nameAsString }.joinToString(", ")}>"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tools/codegen/src/com/android/codegen/ConstDef.kt b/tools/codegen/src/com/android/codegen/ConstDef.kt
new file mode 100644
index 0000000..f559d6f
--- /dev/null
+++ b/tools/codegen/src/com/android/codegen/ConstDef.kt
@@ -0,0 +1,17 @@
+package com.android.codegen
+
+import com.github.javaparser.ast.body.FieldDeclaration
+
+/**
+ * `@IntDef` or `@StringDef`
+ */
+data class ConstDef(val type: Type, val AnnotationName: String, val values: List<FieldDeclaration>) {
+
+    enum class Type {
+        INT, INT_FLAGS, STRING;
+
+        val isInt get() = this == INT || this == INT_FLAGS
+    }
+
+    val CONST_NAMES get() = values.flatMap { it.variables }.map { it.nameAsString }
+}
\ No newline at end of file
diff --git a/tools/codegen/src/com/android/codegen/FeatureFlag.kt b/tools/codegen/src/com/android/codegen/FeatureFlag.kt
new file mode 100644
index 0000000..24150d6
--- /dev/null
+++ b/tools/codegen/src/com/android/codegen/FeatureFlag.kt
@@ -0,0 +1,27 @@
+package com.android.codegen
+
+
+/**
+ * See also [ClassPrinter.invoke] for more default flag values resolution rules
+ */
+enum class FeatureFlag(val onByDefault: Boolean, val desc: String = "") {
+    PARCELABLE(false, "implement Parcelable contract"),
+    AIDL(false, "generate a 'parcelable declaration' .aidl file alongside"),
+    CONSTRUCTOR(true, "an all-argument constructor"),
+    BUILDER(false, "e.g. MyClass.builder().setFoo(..).build();"),
+    GETTERS(true, "getters, e.g. getFoo()"),
+    SETTERS(false, "chainable/fluent setters, e.g. setFoo(..).setBar(..)"),
+    WITHERS(false, "'immutable setters' returning a new instance, " +
+            "e.g. newFoo = foo.withBar(barValue)"),
+    EQUALS_HASH_CODE(false, "equals + hashCode based on fields"),
+    TO_STRING(false, "toString based on fields"),
+    BUILD_UPON(false, "builder factory from existing instance, " +
+            "e.g. instance.buildUpon().setFoo(..).build()"),
+    IMPLICIT_NONNULL(true, "treat lack of @Nullable as @NonNull for Object fields"),
+    COPY_CONSTRUCTOR(false, "a constructor for an instance identical to the given one"),
+    CONST_DEFS(true, "@Int/StringDef's based on declared static constants"),
+    FOR_EACH_FIELD(false, "forEachField((name, value) -> ...)");
+
+    val kebabCase = name.toLowerCase().replace("_", "-")
+    val upperCamelCase = name.split("_").map { it.toLowerCase().capitalize() }.joinToString("")
+}
diff --git a/tools/codegen/src/com/android/codegen/FieldInfo.kt b/tools/codegen/src/com/android/codegen/FieldInfo.kt
new file mode 100644
index 0000000..f326fd5
--- /dev/null
+++ b/tools/codegen/src/com/android/codegen/FieldInfo.kt
@@ -0,0 +1,216 @@
+package com.android.codegen
+
+import com.github.javaparser.JavaParser
+import com.github.javaparser.ast.body.FieldDeclaration
+import com.github.javaparser.ast.expr.ClassExpr
+import com.github.javaparser.ast.expr.Name
+import com.github.javaparser.ast.expr.SingleMemberAnnotationExpr
+import com.github.javaparser.ast.expr.StringLiteralExpr
+import com.github.javaparser.ast.type.ArrayType
+import com.github.javaparser.ast.type.ClassOrInterfaceType
+import com.github.javaparser.javadoc.Javadoc
+import java.lang.Long
+
+data class FieldInfo(
+    val index: Int,
+    val fieldAst: FieldDeclaration,
+    private val classInfo: ClassInfo
+) {
+
+    val classPrinter = classInfo as ClassPrinter
+
+    // AST
+    internal val variableAst = fieldAst.variables[0]
+    val typeAst = variableAst.type
+
+    // Field type
+    val Type = typeAst.asString()
+    val FieldClass = Type.takeWhile { it != '<' }
+    val isPrimitive = Type in PRIMITIVE_TYPES
+
+    // Javadoc
+    val javadoc: Javadoc? = fieldAst.javadoc.orElse(null)
+    private val javadocText = javadoc?.toText()?.let {
+        // Workaround for a bug in Javaparser for javadocs starting with {
+        if (it.hasUnbalancedCurlyBrace()) "{$it" else it
+    }
+    val javadocTextNoAnnotationLines = javadocText
+            ?.lines()
+            ?.dropLastWhile { it.startsWith("@") || it.isBlank() }
+            ?.let { if (it.isEmpty()) null else it }
+    val javadocFull = javadocText
+            ?.trimBlankLines()
+            ?.mapLines { " * $this" }
+            ?.let { "/**\n$it\n */" }
+
+
+    // Field name
+    val name = variableAst.name.asString()!!
+    private val isNameHungarian = name[0] == 'm' && name[1].isUpperCase()
+    val NameUpperCamel = if (isNameHungarian) name.substring(1) else name.capitalize()
+    val nameLowerCamel = if (isNameHungarian) NameUpperCamel.decapitalize() else name
+    val _name = if (name != nameLowerCamel) nameLowerCamel else "_$nameLowerCamel"
+    val SingularNameOrNull by lazy {
+        classPrinter {
+            fieldAst.annotations
+                    .find { it.nameAsString == PluralOf }
+                    ?.let { it as? SingleMemberAnnotationExpr }
+                    ?.memberValue
+                    ?.let { it as? StringLiteralExpr }
+                    ?.value
+                    ?.toLowerCamel()
+                    ?.capitalize()
+        }
+    }
+    val SingularName by lazy { SingularNameOrNull ?: NameUpperCamel }
+
+
+    // Field value
+    val mayBeNull: Boolean
+        get() = when {
+            isPrimitive -> false
+            "@${classPrinter.NonNull}" in annotations -> false
+            "@${classPrinter.NonEmpty}" in annotations -> false
+            isNullable -> true
+            lazyInitializer != null -> true
+            else -> classPrinter { !FeatureFlag.IMPLICIT_NONNULL() }
+        }
+    val lazyInitializer
+        get() = classInfo.classAst.methods.find { method ->
+            method.nameAsString == "lazyInit$NameUpperCamel" && method.parameters.isEmpty()
+        }?.nameAsString
+    val internalGetter get() = if (lazyInitializer != null) "get$NameUpperCamel()" else name
+    val defaultExpr: Any?
+        get() {
+            variableAst.initializer.orElse(null)?.let { return it }
+            classInfo.classAst.methods.find {
+                it.nameAsString == "default$NameUpperCamel" && it.parameters.isEmpty()
+            }?.run { "$nameAsString()" }?.let { return it }
+            if (FieldClass == "List") return "${classPrinter.memberRef("java.util.Collections.emptyList")}()"
+            return null
+        }
+    val hasDefault get() = defaultExpr != null
+
+
+    // Generic args
+    val isArray = Type.endsWith("[]")
+    val isList = FieldClass == "List" || FieldClass == "ArrayList"
+    val fieldBit = "0x${Long.toHexString(1L shl index)}"
+    var isLast = false
+    val isFinal = fieldAst.isFinal
+    val fieldTypeGenegicArgs = when (typeAst) {
+        is ArrayType -> listOf(fieldAst.elementType.asString())
+        is ClassOrInterfaceType -> {
+            typeAst.typeArguments.orElse(null)?.map { it.asString() } ?: emptyList()
+        }
+        else -> emptyList()
+    }
+    val FieldInnerType = fieldTypeGenegicArgs.firstOrNull()
+    val FieldInnerClass = FieldInnerType?.takeWhile { it != '<' }
+
+
+    // Annotations
+    var intOrStringDef = null as ConstDef?
+    val annotations by lazy {
+        if (FieldClass in BUILTIN_SPECIAL_PARCELLINGS) {
+            classPrinter {
+                fieldAst.addAnnotation(SingleMemberAnnotationExpr(
+                        Name(ParcelWith),
+                        ClassExpr(JavaParser.parseClassOrInterfaceType(
+                                "$Parcelling.BuiltIn.For$FieldClass"))))
+            }
+        }
+        fieldAst.annotations.map { it.removeComment().toString() }
+    }
+    val annotationsNoInternal by lazy {
+        annotations.filterNot { ann ->
+            classPrinter {
+                internalAnnotations.any {
+                    it in ann
+                }
+            }
+        }
+    }
+
+    fun hasAnnotation(a: String) = annotations.any { it.startsWith(a) }
+    val isNullable by lazy { hasAnnotation("@Nullable") }
+    val isNonEmpty by lazy { hasAnnotation("@${classPrinter.NonEmpty}") }
+    val customParcellingClass by lazy {
+        fieldAst.annotations.find { it.nameAsString == classPrinter.ParcelWith }
+                ?.singleArgAs<ClassExpr>()
+                ?.type
+                ?.asString()
+    }
+    val annotationsAndType by lazy { (annotationsNoInternal + Type).joinToString(" ") }
+    val sParcelling by lazy { customParcellingClass?.let { "sParcellingFor$NameUpperCamel" } }
+    val annotatedTypeForSetterParam by lazy {
+        (annotationsNoInternal + if (isArray) "$FieldInnerType..." else Type).joinToString(" ")
+    }
+
+    // Utilities
+
+    /**
+     * `mFoo.size()`
+     */
+    val ClassPrinter.sizeExpr get() = when {
+        isArray && FieldInnerClass !in PRIMITIVE_TYPES ->
+            memberRef("com.android.internal.util.ArrayUtils.size") + "($name)"
+        isArray -> "$name.length"
+        listOf("List", "Set", "Map").any { FieldClass.endsWith(it) } ->
+            memberRef("com.android.internal.util.CollectionUtils.size") + "($name)"
+        Type == "String" -> memberRef("android.text.TextUtils.length") + "($name)"
+        Type == "CharSequence" -> "$name.length()"
+        else -> "$name.size()"
+    }
+    /**
+     * `mFoo.get(0)`
+     */
+    fun elemAtIndexExpr(indexExpr: String) = when {
+        isArray -> "$name[$indexExpr]"
+        FieldClass == "ArraySet" -> "$name.valueAt($indexExpr)"
+        else -> "$name.get($indexExpr)"
+    }
+    /**
+     * `mFoo.isEmpty()`
+     */
+    val ClassPrinter.isEmptyExpr get() = when {
+        isArray || Type == "CharSequence" -> "$sizeExpr == 0"
+        else -> "$name.isEmpty()"
+    }
+
+    /**
+     * `mFoo == that` or `Objects.equals(mFoo, that)`, etc.
+     */
+    fun ClassPrinter.isEqualToExpr(that: String) = when {
+        Type in PRIMITIVE_TYPES -> "$internalGetter == $that"
+        isArray -> "${memberRef("java.util.Arrays.equals")}($internalGetter, $that)"
+        else -> "${memberRef("java.util.Objects.equals")}($internalGetter, $that)"
+    }
+
+    /**
+     * Parcel.write* and Parcel.read* method name wildcard values
+     */
+    val ParcelMethodsSuffix = when {
+        FieldClass in PRIMITIVE_TYPES - "char" - "boolean" +
+                listOf("String", "CharSequence", "Exception", "Size", "SizeF", "Bundle",
+                        "FileDescriptor", "SparseBooleanArray", "SparseIntArray", "SparseArray") ->
+            FieldClass
+        FieldClass == "Map" && fieldTypeGenegicArgs[0] == "String" -> "Map"
+        isArray -> when {
+            FieldInnerType!! in (PRIMITIVE_TYPES + "String") -> FieldInnerType + "Array"
+            isBinder(FieldInnerType) -> "BinderArray"
+            else -> "TypedArray"
+        }
+        isList -> when {
+            FieldInnerType == "String" -> "StringList"
+            isBinder(FieldInnerType!!) -> "BinderList"
+            else -> "ParcelableList"
+        }
+        isIInterface(Type) -> "StrongInterface"
+        isBinder(Type) -> "StrongBinder"
+        else -> "TypedObject"
+    }.capitalize()
+
+    private fun isBinder(type: String) = type == "Binder" || type == "IBinder" || isIInterface(type)
+    private fun isIInterface(type: String) = type.length >= 2 && type[0] == 'I' && type[1].isUpperCase()
+}
\ No newline at end of file
diff --git a/tools/codegen/src/com/android/codegen/Generators.kt b/tools/codegen/src/com/android/codegen/Generators.kt
new file mode 100644
index 0000000..ab64f4e
--- /dev/null
+++ b/tools/codegen/src/com/android/codegen/Generators.kt
@@ -0,0 +1,847 @@
+package com.android.codegen
+
+import com.github.javaparser.ast.body.FieldDeclaration
+import com.github.javaparser.ast.body.VariableDeclarator
+import com.github.javaparser.ast.expr.*
+import java.io.File
+
+
+/**
+ * IntDefs and StringDefs based on constants
+ */
+fun ClassPrinter.generateConstDefs() {
+    val consts = classAst.fields.filter {
+        it.isStatic && it.isFinal && it.variables.all { variable ->
+            val initializer = variable.initializer.orElse(null)
+            val isLiteral = initializer is LiteralExpr
+                    || (initializer is UnaryExpr && initializer.expression is LiteralExpr)
+            isLiteral && variable.type.asString() in listOf("int", "String")
+        }
+    }.flatMap { field -> field.variables.map { it to field } }
+    val intConsts = consts.filter { it.first.type.asString() == "int" }
+    val strConsts = consts.filter { it.first.type.asString() == "String" }
+    val intGroups = intConsts.groupBy { it.first.nameAsString.split("_")[0] }.values
+    val strGroups = strConsts.groupBy { it.first.nameAsString.split("_")[0] }.values
+    intGroups.forEach {
+        generateConstDef(it)
+    }
+    strGroups.forEach {
+        generateConstDef(it)
+    }
+}
+
+fun ClassPrinter.generateConstDef(consts: List<Pair<VariableDeclarator, FieldDeclaration>>) {
+    if (consts.size <= 1) return
+
+    val names = consts.map { it.first.nameAsString!! }
+    val prefix = names
+            .reduce { a, b -> a.commonPrefixWith(b) }
+            .dropLastWhile { it != '_' }
+            .dropLast(1)
+    if (prefix.isEmpty()) {
+        println("Failed to generate const def for $names")
+        return
+    }
+    var AnnotationName = prefix.split("_")
+            .filterNot { it.isBlank() }
+            .map { it.toLowerCase().capitalize() }
+            .joinToString("")
+    val annotatedConst = consts.find { it.second.annotations.isNonEmpty }
+    if (annotatedConst != null) {
+        AnnotationName = annotatedConst.second.annotations.first().nameAsString
+    }
+    val type = consts[0].first.type.asString()
+    val flag = type == "int" && consts.all { it.first.initializer.get().toString().startsWith("0x") }
+    val constDef = ConstDef(type = when {
+        type == "String" -> ConstDef.Type.STRING
+        flag -> ConstDef.Type.INT_FLAGS
+        else -> ConstDef.Type.INT
+    },
+            AnnotationName = AnnotationName,
+            values = consts.map { it.second }
+    )
+    constDefs += constDef
+    fields.forEachApply {
+        if (fieldAst.annotations.any { it.nameAsString == AnnotationName }) {
+            this.intOrStringDef = constDef
+        }
+    }
+
+    val visibility = if (consts[0].second.isPublic) "public" else "/* package-*/"
+
+    val Retention = classRef("java.lang.annotation.Retention")
+    val RetentionPolicySource = memberRef("java.lang.annotation.RetentionPolicy.SOURCE")
+    val ConstDef = classRef("android.annotation.${type.capitalize()}Def")
+
+    "@$ConstDef(${if_(flag, "flag = true, ")}prefix = \"${prefix}_\", value = {" {
+        names.forEachLastAware { name, isLast ->
+            +"$name${if_(!isLast, ",")}"
+        }
+    } + ")"
+    +"@$Retention($RetentionPolicySource)"
+    +GENERATED_MEMBER_HEADER
+    +"$visibility @interface $AnnotationName {}"
+    +""
+
+    if (type == "int") {
+        +GENERATED_MEMBER_HEADER
+        val methodDefLine = "$visibility static String ${AnnotationName.decapitalize()}ToString(" +
+                "@$AnnotationName int value)"
+        if (flag) {
+            val flg2str = memberRef("com.android.internal.util.BitUtils.flagsToString")
+            methodDefLine {
+                "return $flg2str(" {
+                    +"value, $ClassName::single${AnnotationName}ToString"
+                } + ";"
+            }
+            +GENERATED_MEMBER_HEADER
+            !"static String single${AnnotationName}ToString(@$AnnotationName int value)"
+        } else {
+            !methodDefLine
+        }
+        " {" {
+            "switch (value) {" {
+                names.forEach { name ->
+                    "case $name:" {
+                        +"return \"$name\";"
+                    }
+                }
+                +"default: return Integer.toHexString(value);"
+            }
+        }
+    }
+}
+
+fun ClassPrinter.generateAidl(javaFile: File) {
+    val aidl = File(javaFile.path.substringBeforeLast(".java") + ".aidl")
+    if (aidl.exists()) return
+    aidl.writeText(buildString {
+        sourceLines.dropLastWhile { !it.startsWith("package ") }.forEach {
+            appendln(it)
+        }
+        append("\nparcelable $ClassName;\n")
+    })
+}
+
+/**
+ * ```
+ * Foo newFoo = oldFoo.withBar(newBar);
+ * ```
+ */
+fun ClassPrinter.generateWithers() {
+    fields.forEachApply {
+        val metodName = "with$NameUpperCamel"
+        if (!hasMethod(metodName, Type)) {
+            generateFieldJavadoc(forceHide = FeatureFlag.WITHERS.hidden)
+            """@$NonNull
+                        $GENERATED_MEMBER_HEADER
+                        public $ClassType $metodName($annotatedTypeForSetterParam value)""" {
+                val changedFieldName = name
+
+                "return new $ClassType(" {
+                    fields.forEachTrimmingTrailingComma {
+                        if (name == changedFieldName) +"value," else +"$name,"
+                    }
+                } + ";"
+            }
+        }
+    }
+}
+
+fun ClassPrinter.generateCopyConstructor() {
+    if (classAst.constructors.any {
+                it.parameters.size == 1 &&
+                        it.parameters[0].type.asString() == ClassType
+            }) {
+        return
+    }
+
+    +"/** Copy constructor */"
+    +GENERATED_MEMBER_HEADER
+    "public $ClassName(@$NonNull $ClassName orig)" {
+        fields.forEachApply {
+            +"$name = orig.$name;"
+        }
+    }
+}
+
+/**
+ * ```
+ * Foo newFoo = oldFoo.buildUpon().setBar(newBar).build();
+ * ```
+ */
+fun ClassPrinter.generateBuildUpon() {
+    if (hasMethod("buildUpon")) return
+
+    +"/**"
+    +" * Provides an instance of {@link $BuilderClass} with state corresponding to this instance."
+    if (FeatureFlag.BUILD_UPON.hidden) {
+        +" * @hide"
+    }
+    +" */"
+    +GENERATED_MEMBER_HEADER
+    "public $BuilderType buildUpon()" {
+        "return new $BuilderType()" {
+            fields.forEachApply {
+                +".set$NameUpperCamel($internalGetter)"
+            } + ";"
+        }
+    }
+}
+
+fun ClassPrinter.generateBuilder() {
+    val setterVisibility = if (cliArgs.contains(FLAG_BUILDER_PROTECTED_SETTERS))
+        "protected" else "public"
+    val constructorVisibility = if (BuilderClass == CANONICAL_BUILDER_CLASS)
+        "public" else "/* package-*/"
+
+    val OneTimeUseBuilder = classRef("android.provider.OneTimeUseBuilder")
+
+    +"/**"
+    +" * A builder for {@link $ClassName}"
+    if (FeatureFlag.BUILDER.hidden) +" * @hide"
+    +" */"
+    +"@SuppressWarnings(\"WeakerAccess\")"
+    +GENERATED_MEMBER_HEADER
+    "public static class $BuilderClass$genericArgs" {
+        +"extends $OneTimeUseBuilder<$ClassType>"
+    }
+    " {" {
+
+        +""
+        fields.forEachApply {
+            +"protected $annotationsAndType $name;"
+        }
+        +""
+        +"protected long mBuilderFieldsSet = 0L;"
+        +""
+        +"$constructorVisibility $BuilderClass() {};"
+        +""
+
+        generateBuilderSetters(setterVisibility)
+
+        generateBuilderBuild()
+
+        rmEmptyLine()
+    }
+}
+
+private fun ClassPrinter.generateBuilderSetters(visibility: String) {
+
+    fields.forEachApply {
+        val maybeCast =
+                if_(BuilderClass != CANONICAL_BUILDER_CLASS, " ($CANONICAL_BUILDER_CLASS)")
+
+        generateFieldJavadoc()
+        +GENERATED_MEMBER_HEADER
+        "$visibility $CANONICAL_BUILDER_CLASS set$NameUpperCamel($annotatedTypeForSetterParam value)" {
+            +"checkNotUsed();"
+            +"mBuilderFieldsSet |= $fieldBit;"
+            +"$name = value;"
+            +"return$maybeCast this;"
+        }
+
+
+        val javadocSeeSetter = "/** @see #set$NameUpperCamel */"
+        val singularNameCustomizationHint = if (SingularNameOrNull == null) {
+            "// You can refine this method's name by providing item's singular name, e.g.:\n" +
+                    "// @DataClass.PluralOf(\"item\")) mItems = ...\n\n"
+        } else ""
+
+        if (isList && FieldInnerType != null) {
+
+            +javadocSeeSetter
+            +GENERATED_MEMBER_HEADER
+            "$visibility $CANONICAL_BUILDER_CLASS add$SingularName(@$NonNull $FieldInnerType value)" {
+                !singularNameCustomizationHint
+                +"if ($name == null) set$NameUpperCamel(new $ArrayList<>());"
+                +"$name.add(value);"
+                +"return$maybeCast this;"
+            }
+        }
+
+        if (Type.contains("Map<")) {
+            val (Key, Value) = fieldTypeGenegicArgs
+
+            +javadocSeeSetter
+            +GENERATED_MEMBER_HEADER
+            "$visibility $CANONICAL_BUILDER_CLASS add$SingularName($Key key, $Value value)" {
+                !singularNameCustomizationHint
+                +"if ($name == null) set$NameUpperCamel(new $LinkedHashMap());"
+                +"$name.put(key, value);"
+                +"return$maybeCast this;"
+            }
+        }
+
+        if (Type == "boolean") {
+            +javadocSeeSetter
+            +GENERATED_MEMBER_HEADER
+            "$visibility $CANONICAL_BUILDER_CLASS mark$NameUpperCamel()" {
+                +"return set$NameUpperCamel(true);"
+            }
+
+            +javadocSeeSetter
+            +GENERATED_MEMBER_HEADER
+            "$visibility $CANONICAL_BUILDER_CLASS markNot$NameUpperCamel()" {
+                +"return set$NameUpperCamel(false);"
+            }
+        }
+    }
+}
+
+private fun ClassPrinter.generateBuilderBuild() {
+    +"/** Builds the instance. This builder should not be touched after calling this! */"
+    "public $ClassType build()" {
+        +"markUsed();"
+        fields.forEachApply {
+            if (!isNullable || hasDefault) {
+                "if ((mBuilderFieldsSet & $fieldBit) == 0)" {
+                    if (!isNullable && !hasDefault) {
+                        +"throw new IllegalStateException(\"Required field not set: $nameLowerCamel\");"
+                    } else {
+                        +"$name = $defaultExpr;"
+                    }
+                }
+            }
+        }
+        "$ClassType o = new $ClassType(" {
+            fields.forEachTrimmingTrailingComma {
+                +"$name,"
+            }
+        } + ";"
+        +"return o;"
+    }
+}
+
+fun ClassPrinter.generateParcelable() {
+    val booleanFields = fields.filter { it.Type == "boolean" }
+    val objectFields = fields.filter { it.Type !in PRIMITIVE_TYPES }
+    val nullableFields = objectFields.filter { it.mayBeNull }
+    val nonBooleanFields = fields - booleanFields
+
+
+    val flagStorageType = when (fields.size) {
+        in 0..7 -> "byte"
+        in 8..15 -> "int"
+        in 16..31 -> "long"
+        else -> throw NotImplementedError("32+ field classes not yet supported")
+    }
+    val FlagStorageType = flagStorageType.capitalize()
+
+    fields.forEachApply {
+        if (sParcelling != null) {
+            +GENERATED_MEMBER_HEADER
+            "static $Parcelling<$Type> $sParcelling =" {
+                "$Parcelling.Cache.get(" {
+                    +"$customParcellingClass.class"
+                } + ";"
+            }
+            "static {" {
+                "if ($sParcelling == null)" {
+                    "$sParcelling = $Parcelling.Cache.put(" {
+                        +"new $customParcellingClass()"
+                    } + ";"
+                }
+            }
+            +""
+        }
+    }
+
+    val Parcel = classRef("android.os.Parcel")
+    if (!hasMethod("writeToParcel", Parcel, "int")) {
+        +"@Override"
+        +GENERATED_MEMBER_HEADER
+        "public void writeToParcel($Parcel dest, int flags)" {
+            +"// You can override field parcelling by defining methods like:"
+            +"// void parcelFieldName(Parcel dest, int flags) { ... }"
+            +""
+
+            if (booleanFields.isNotEmpty() || nullableFields.isNotEmpty()) {
+                +"$flagStorageType flg = 0;"
+                booleanFields.forEachApply {
+                    +"if ($internalGetter) flg |= $fieldBit;"
+                }
+                nullableFields.forEachApply {
+                    +"if ($internalGetter != null) flg |= $fieldBit;"
+                }
+                +"dest.write$FlagStorageType(flg);"
+            }
+
+            nonBooleanFields.forEachApply {
+                val customParcellingMethod = "parcel$NameUpperCamel"
+                when {
+                    hasMethod(customParcellingMethod, Parcel, "int") ->
+                        +"$customParcellingMethod(dest, flags);"
+                    customParcellingClass != null -> +"$sParcelling.parcel($name, dest, flags);"
+                    hasAnnotation("@$DataClassEnum") ->
+                        +"dest.writeInt($internalGetter == null ? -1 : $internalGetter.ordinal());"
+                    else -> {
+                        if (mayBeNull) !"if ($internalGetter != null) "
+                        var args = internalGetter
+                        if (ParcelMethodsSuffix.startsWith("Parcelable")
+                                || ParcelMethodsSuffix.startsWith("TypedObject")
+                                || ParcelMethodsSuffix == "TypedArray") {
+                            args += ", flags"
+                        }
+                        +"dest.write$ParcelMethodsSuffix($args);"
+                    }
+                }
+            }
+        }
+    }
+
+    if (!hasMethod("describeContents")) {
+        +"@Override"
+        +GENERATED_MEMBER_HEADER
+        +"public int describeContents() { return 0; }"
+        +""
+    }
+
+    if (classAst.fields.none { it.variables[0].nameAsString == "CREATOR" }) {
+        val Creator = classRef("android.os.Parcelable.Creator")
+
+        +GENERATED_MEMBER_HEADER
+        "public static final @$NonNull $Creator<$ClassName> CREATOR" {
+            +"= new $Creator<$ClassName>()"
+        }; " {" {
+
+            +"@Override"
+            "public $ClassName[] newArray(int size)" {
+                +"return new $ClassName[size];"
+            }
+
+            +"@Override"
+            +"@SuppressWarnings({\"unchecked\", \"RedundantCast\"})"
+            "public $ClassName createFromParcel($Parcel in)" {
+                +"// You can override field unparcelling by defining methods like:"
+                +"// static FieldType unparcelFieldName(Parcel in) { ... }"
+                +""
+                if (booleanFields.isNotEmpty() || nullableFields.isNotEmpty()) {
+                    +"$flagStorageType flg = in.read$FlagStorageType();"
+                }
+                booleanFields.forEachApply {
+                    +"$Type $_name = (flg & $fieldBit) != 0;"
+                }
+                nonBooleanFields.forEachApply {
+
+                    // Handle customized parceling
+                    val customParcellingMethod = "unparcel$NameUpperCamel"
+                    if (hasMethod(customParcellingMethod, Parcel)) {
+                        +"$Type $_name = $customParcellingMethod(in);"
+                    } else if (customParcellingClass != null) {
+                        +"$Type $_name = $sParcelling.unparcel(in);"
+                    } else if (hasAnnotation("@$DataClassEnum")) {
+                        val ordinal = "${_name}Ordinal"
+                        +"int $ordinal = in.readInt();"
+                        +"$Type $_name = $ordinal < 0 ? null : $FieldClass.values()[$ordinal];"
+                    } else {
+                        val methodArgs = mutableListOf<String>()
+
+                        // Create container if any
+                        val containerInitExpr = when {
+                            FieldClass.endsWith("Map") -> "new $LinkedHashMap<>()"
+                            FieldClass == "List" || FieldClass == "ArrayList" ->
+                                "new ${classRef("java.util.ArrayList")}<>()"
+//                            isArray && FieldInnerType in (PRIMITIVE_TYPES + "String") ->
+//                                "new $FieldInnerType[in.readInt()]"
+                            else -> ""
+                        }
+                        val passContainer = containerInitExpr.isNotEmpty()
+
+                        // nullcheck +
+                        // "FieldType fieldName = (FieldType)"
+                        if (passContainer) {
+                            methodArgs.add(_name)
+                            !"$Type $_name = "
+                            if (mayBeNull) {
+                                +"null;"
+                                !"if ((flg & $fieldBit) != 0) {"
+                                pushIndent()
+                                +""
+                                !"$_name = "
+                            }
+                            +"$containerInitExpr;"
+                        } else {
+                            !"$Type $_name = "
+                            if (mayBeNull) !"(flg & $fieldBit) == 0 ? null : "
+                            if (ParcelMethodsSuffix == "StrongInterface") {
+                                !"$FieldClass.Stub.asInterface("
+                            } else if (Type !in PRIMITIVE_TYPES + "String" + "Bundle" &&
+                                    (!isArray || FieldInnerType !in PRIMITIVE_TYPES + "String") &&
+                                    ParcelMethodsSuffix != "Parcelable") {
+                                !"($Type) "
+                            }
+                        }
+
+                        // Determine method args
+                        when {
+                            ParcelMethodsSuffix == "Parcelable" ->
+                                methodArgs += "$FieldClass.class.getClassLoader()"
+                            ParcelMethodsSuffix == "TypedObject" ->
+                                methodArgs += "$FieldClass.CREATOR"
+                            ParcelMethodsSuffix == "TypedArray" ->
+                                methodArgs += "$FieldInnerClass.CREATOR"
+                            ParcelMethodsSuffix.startsWith("Parcelable")
+                                    || FieldClass == "Map"
+                                    || (isList || isArray)
+                                    && FieldInnerType !in PRIMITIVE_TYPES + "String" ->
+                                methodArgs += "$FieldInnerClass.class.getClassLoader()"
+                        }
+
+                        // ...in.readFieldType(args...);
+                        when {
+                            ParcelMethodsSuffix == "StrongInterface" -> !"in.readStrongBinder"
+                            isArray -> !"in.create$ParcelMethodsSuffix"
+                            else -> !"in.read$ParcelMethodsSuffix"
+                        }
+                        !"(${methodArgs.joinToString(", ")})"
+                        if (ParcelMethodsSuffix == "StrongInterface") !")"
+                        +";"
+
+                        // Cleanup if passContainer
+                        if (passContainer && mayBeNull) {
+                            popIndent()
+                            rmEmptyLine()
+                            +"\n}"
+                        }
+                    }
+                }
+                "return new $ClassType(" {
+                    fields.forEachTrimmingTrailingComma {
+                        +"$_name,"
+                    }
+                } + ";"
+            }
+            rmEmptyLine()
+        } + ";"
+        +""
+    }
+}
+
+fun ClassPrinter.generateEqualsHashcode() {
+    if (!hasMethod("equals", "Object")) {
+        +"@Override"
+        +GENERATED_MEMBER_HEADER
+        "public boolean equals(Object o)" {
+            +"// You can override field equality logic by defining either of the methods like:"
+            +"// boolean fieldNameEquals($ClassName other) { ... }"
+            +"// boolean fieldNameEquals(FieldType otherValue) { ... }"
+            +""
+            """if (this == o) return true;
+                        if (o == null || getClass() != o.getClass()) return false;
+                        @SuppressWarnings("unchecked")
+                        $ClassType that = ($ClassType) o;
+                        //noinspection PointlessBooleanExpression
+                        return true""" {
+                fields.forEachApply {
+                    val sfx = if (isLast) ";" else ""
+                    val customEquals = "${nameLowerCamel}Equals"
+                    when {
+                        hasMethod(customEquals, Type) -> +"&& $customEquals(that.$internalGetter)$sfx"
+                        hasMethod(customEquals, ClassType) -> +"&& $customEquals(that)$sfx"
+                        else -> +"&& ${isEqualToExpr("that.$internalGetter")}$sfx"
+                    }
+                }
+            }
+        }
+    }
+
+    if (!hasMethod("hashCode")) {
+        +"@Override"
+        +GENERATED_MEMBER_HEADER
+        "public int hashCode()" {
+            +"// You can override field hashCode logic by defining methods like:"
+            +"// int fieldNameHashCode() { ... }"
+            +""
+            +"int _hash = 1;"
+            fields.forEachApply {
+                !"_hash = 31 * _hash + "
+                val customHashCode = "${nameLowerCamel}HashCode"
+                when {
+                    hasMethod(customHashCode) -> +"$customHashCode();"
+                    Type == "int" || Type == "byte" -> +"$internalGetter;"
+                    Type in PRIMITIVE_TYPES -> +"${Type.capitalize()}.hashCode($internalGetter);"
+                    isArray -> +"${memberRef("java.util.Arrays.hashCode")}($internalGetter);"
+                    else -> +"${memberRef("java.util.Objects.hashCode")}($internalGetter);"
+                }
+            }
+            +"return _hash;"
+        }
+    }
+}
+
+//TODO support IntDef flags?
+fun ClassPrinter.generateToString() {
+    if (!hasMethod("toString")) {
+        +"@Override"
+        +GENERATED_MEMBER_HEADER
+        "public String toString()" {
+            +"// You can override field toString logic by defining methods like:"
+            +"// String fieldNameToString() { ... }"
+            +""
+            "return \"$ClassName { \" +" {
+                fields.forEachApply {
+                    val customToString = "${nameLowerCamel}ToString"
+                    val expr = when {
+                        hasMethod(customToString) -> "$customToString()"
+                        isArray -> "${memberRef("java.util.Arrays.toString")}($internalGetter)"
+                        intOrStringDef?.type?.isInt == true ->
+                            "${intOrStringDef!!.AnnotationName.decapitalize()}ToString($name)"
+                        else -> internalGetter
+                    }
+                    +"\"$nameLowerCamel = \" + $expr${if_(!isLast, " + \", \"")} +"
+                }
+            }
+            +"\" }\";"
+        }
+    }
+}
+
+fun ClassPrinter.generateSetters() {
+    fields.forEachApply {
+        if (!hasMethod("set$NameUpperCamel", Type)
+                && !fieldAst.isPublic
+                && !isFinal) {
+
+            generateFieldJavadoc(forceHide = FeatureFlag.SETTERS.hidden)
+            +GENERATED_MEMBER_HEADER
+            "public $ClassType set$NameUpperCamel($annotatedTypeForSetterParam value)" {
+                generateSetFrom("value")
+                +"return this;"
+            }
+        }
+    }
+}
+
+fun ClassPrinter.generateGetters() {
+    (fields + lazyTransientFields).forEachApply {
+        val methodPrefix = if (Type == "boolean") "is" else "get"
+        val methodName = methodPrefix + NameUpperCamel
+
+        if (!hasMethod(methodName) && !fieldAst.isPublic) {
+
+            generateFieldJavadoc(forceHide = FeatureFlag.GETTERS.hidden)
+            +GENERATED_MEMBER_HEADER
+            "public $annotationsAndType $methodName()" {
+                if (lazyInitializer == null) {
+                    +"return $name;"
+                } else {
+                    +"$Type $_name = $name;"
+                    "if ($_name == null)" {
+                        if (fieldAst.isVolatile) {
+                            "synchronized(this)" {
+                                +"$_name = $name;"
+                                "if ($_name == null)" {
+                                    +"$_name = $name = $lazyInitializer();"
+                                }
+                            }
+                        } else {
+                            +"// You can mark field as volatile for thread-safe double-check init"
+                            +"$_name = $name = $lazyInitializer();"
+                        }
+                    }
+                    +"return $_name;"
+                }
+            }
+        }
+    }
+}
+
+fun FieldInfo.generateFieldJavadoc(forceHide: Boolean = false) = classPrinter {
+    if (javadocFull != null || forceHide) {
+        var hidden = false
+        (javadocFull ?: "/**\n */").lines().forEach {
+            if (it.contains("@hide")) hidden = true
+            if (it.contains("*/") && forceHide && !hidden) {
+                if (javadocFull != null) +" *"
+                +" * @hide"
+            }
+            +it
+        }
+    }
+}
+
+fun FieldInfo.generateSetFrom(source: String) = classPrinter {
+    !"$name = "
+    if (Type in PRIMITIVE_TYPES || mayBeNull) {
+        +"$source;"
+    } else if (defaultExpr != null) {
+        "$source != null" {
+            +"? $source"
+            +": $defaultExpr;"
+        }
+    } else {
+        val checkNotNull = memberRef("com.android.internal.util.Preconditions.checkNotNull")
+        +"$checkNotNull($source);"
+    }
+    if (isNonEmpty) {
+        "if ($isEmptyExpr)" {
+            +"throw new IllegalArgumentException(\"$nameLowerCamel cannot be empty\");"
+        }
+    }
+}
+
+fun ClassPrinter.generateConstructor(visibility: String = "public") {
+    if (visibility == "public") {
+        generateConstructorJavadoc()
+    }
+    +GENERATED_MEMBER_HEADER
+    "$visibility $ClassName(" {
+        fields.forEachApply {
+            +"$annotationsAndType $nameLowerCamel${if_(!isLast, ",")}"
+        }
+    }
+    " {" {
+        fields.forEachApply {
+            !"this."
+            generateSetFrom(nameLowerCamel)
+        }
+
+        generateStateValidation()
+
+        generateOnConstructedCallback()
+    }
+}
+
+private fun ClassPrinter.generateConstructorJavadoc() {
+    if (fields.all { it.javadoc == null } && !FeatureFlag.CONSTRUCTOR.hidden) return
+    +"/**"
+    fields.filter { it.javadoc != null }.forEachApply {
+        javadocTextNoAnnotationLines?.apply {
+            +" * @param $nameLowerCamel"
+            forEach {
+                +" *   $it"
+            }
+        }
+    }
+    if (FeatureFlag.CONSTRUCTOR.hidden) +" * @hide"
+    +" */"
+}
+
+private fun ClassPrinter.generateStateValidation() {
+    val Size = classRef("android.annotation.Size")
+    val knownNonValidationAnnotations = internalAnnotations + Nullable
+
+    val validate = memberRef("com.android.internal.util.AnnotationValidations.validate")
+    fun appendValidateCall(annotation: AnnotationExpr, valueToValidate: String) {
+        "$validate(" {
+            !"${annotation.nameAsString}.class, null, $valueToValidate"
+            val params = when (annotation) {
+                is MarkerAnnotationExpr -> emptyMap()
+                is SingleMemberAnnotationExpr -> mapOf("value" to annotation.memberValue)
+                is NormalAnnotationExpr ->
+                    annotation.pairs.map { it.name.asString() to it.value }.toMap()
+                else -> throw IllegalStateException()
+            }
+            params.forEach { name, value ->
+                !",\n\"$name\", $value"
+            }
+        }
+        +";"
+    }
+
+    fields.forEachApply {
+        if (intOrStringDef != null) {
+            if (intOrStringDef!!.type == ConstDef.Type.INT_FLAGS) {
+                +""
+                +"//noinspection PointlessBitwiseExpression"
+                "$Preconditions.checkFlagsArgument(" {
+                    "$name, 0" {
+                        intOrStringDef!!.CONST_NAMES.forEach {
+                            +"| $it"
+                        }
+                    }
+                }
+                +";"
+            } else {
+                +""
+                +"//noinspection PointlessBooleanExpression"
+                "if (true" {
+                    intOrStringDef!!.CONST_NAMES.forEach { CONST_NAME ->
+                        +"&& !(${isEqualToExpr(CONST_NAME)})"
+                    }
+                }; rmEmptyLine(); ") {" {
+                    "throw new ${classRef<IllegalArgumentException>()}(" {
+                        "\"$nameLowerCamel was \" + $internalGetter + \" but must be one of: \"" {
+
+                            intOrStringDef!!.CONST_NAMES.forEachLastAware { CONST_NAME, isLast ->
+                                +"""+ "$CONST_NAME(" + $CONST_NAME + ")${if_(!isLast, ", ")}""""
+                            }
+                        }
+                    }
+                    +";"
+                }
+            }
+        }
+
+        val eachLine = fieldAst.annotations.find { it.nameAsString == Each }?.range?.orElse(null)?.end?.line
+        val perElementValidations = if (eachLine == null) emptyList() else fieldAst.annotations.filter {
+            it.nameAsString != Each &&
+                it.range.orElse(null)?.begin?.line?.let { it >= eachLine } ?: false
+        }
+
+        fieldAst.annotations.filterNot {
+            it.nameAsString == intOrStringDef?.AnnotationName
+                    || it.nameAsString in knownNonValidationAnnotations
+                    || it in perElementValidations
+        }.forEach { annotation ->
+            appendValidateCall(annotation,
+                    valueToValidate = if (annotation.nameAsString == Size) sizeExpr else name)
+        }
+
+        if (perElementValidations.isNotEmpty()) {
+            +"int ${nameLowerCamel}Size = $sizeExpr;"
+            "for (int i = 0; i < ${nameLowerCamel}Size; i++) {" {
+                perElementValidations.forEach { annotation ->
+                    appendValidateCall(annotation,
+                            valueToValidate = elemAtIndexExpr("i"))
+                }
+            }
+        }
+    }
+}
+
+private fun ClassPrinter.generateOnConstructedCallback(prefix: String = "") {
+    +""
+    val call = "${prefix}onConstructed();"
+    if (hasMethod("onConstructed")) {
+        +call
+    } else {
+        +"// $call // You can define this method to get a callback"
+    }
+}
+
+fun ClassPrinter.generateForEachField() {
+    val specializations = listOf("Object", "int")
+    val usedSpecializations = fields.map { if (it.Type in specializations) it.Type else "Object" }
+    val usedSpecializationsSet = usedSpecializations.toSet()
+
+    val PerObjectFieldAction = classRef("com.android.internal.util.DataClass.PerObjectFieldAction")
+
+    +GENERATED_MEMBER_HEADER
+    "void forEachField(" {
+        usedSpecializationsSet.toList().forEachLastAware { specType, isLast ->
+            val SpecType = specType.capitalize()
+            val ActionClass = classRef("com.android.internal.util.DataClass.Per${SpecType}FieldAction")
+            +"$ActionClass<$ClassType> action$SpecType${if_(!isLast, ",")}"
+        }
+    }; " {" {
+        usedSpecializations.forEachIndexed { i, specType ->
+            val SpecType = specType.capitalize()
+            fields[i].apply {
+                +"action$SpecType.accept$SpecType(this, \"$nameLowerCamel\", $name);"
+            }
+        }
+    }
+
+    if (usedSpecializationsSet.size > 1) {
+        +"/** @deprecated May cause boxing allocations - use with caution! */"
+        +"@Deprecated"
+        +GENERATED_MEMBER_HEADER
+        "void forEachField($PerObjectFieldAction<$ClassType> action)" {
+            fields.forEachApply {
+                +"action.acceptObject(this, \"$nameLowerCamel\", $name);"
+            }
+        }
+    }
+}
diff --git a/tools/codegen/src/com/android/codegen/InputSignaturesComputation.kt b/tools/codegen/src/com/android/codegen/InputSignaturesComputation.kt
new file mode 100644
index 0000000..d1dc88f
--- /dev/null
+++ b/tools/codegen/src/com/android/codegen/InputSignaturesComputation.kt
@@ -0,0 +1,122 @@
+package com.android.codegen
+
+import com.github.javaparser.ast.body.TypeDeclaration
+import com.github.javaparser.ast.expr.*
+import com.github.javaparser.ast.nodeTypes.NodeWithAnnotations
+import com.github.javaparser.ast.type.ClassOrInterfaceType
+import com.github.javaparser.ast.type.Type
+
+
+fun ClassPrinter.getInputSignatures(): List<String> {
+    return classAst.fields.map { fieldAst ->
+        buildString {
+            append(fieldAst.modifiers.joinToString(" ") {it.asString()})
+            append(" ")
+            append(annotationsToString(fieldAst))
+            append(" ")
+            append(getFullClassName(fieldAst.commonType))
+            append(" ")
+            append(fieldAst.variables.joinToString(", ") { it.nameAsString })
+        }
+    } + classAst.methods.map { methodAst ->
+        buildString {
+            append(methodAst.modifiers.joinToString(" ") {it.asString()})
+            append(" ")
+            append(annotationsToString(methodAst))
+            append(" ")
+            append(getFullClassName(methodAst.type))
+            append(" ")
+            append(methodAst.nameAsString)
+            append("(")
+            append(methodAst.parameters.joinToString(",") {getFullClassName(it.type)})
+            append(")")
+        }
+    }
+}
+
+private fun ClassPrinter.annotationsToString(annotatedAst: NodeWithAnnotations<*>): String {
+    return annotatedAst.annotations.joinToString(" ") {
+        annotationToString(it)
+    }
+}
+
+private fun ClassPrinter.annotationToString(ann: AnnotationExpr): String {
+    return buildString {
+        append("@")
+        append(getFullClassName(ann.nameAsString))
+        if (ann is MarkerAnnotationExpr) return@buildString
+
+        append("(")
+
+        when (ann) {
+            is SingleMemberAnnotationExpr -> {
+                appendExpr(this, ann.memberValue)
+            }
+            is NormalAnnotationExpr -> {
+                ann.pairs.forEachLastAware { pair, isLast ->
+                    append(pair.nameAsString)
+                    append("=")
+                    appendExpr(this, pair.value)
+                    if (!isLast) append(", ")
+                }
+            }
+        }
+
+        append(")")
+    }.replace("\"", "\\\"")
+}
+
+private fun ClassPrinter.appendExpr(sb: StringBuilder, ex: Expression?) {
+    when (ex) {
+        is ClassExpr -> sb.append(getFullClassName(ex.typeAsString)).append(".class")
+        is IntegerLiteralExpr -> sb.append(ex.asInt()).append("L")
+        is LongLiteralExpr -> sb.append(ex.asLong()).append("L")
+        is DoubleLiteralExpr -> sb.append(ex.asDouble())
+        else -> sb.append(ex)
+    }
+}
+
+private fun ClassPrinter.getFullClassName(type: Type): String {
+    return if (type is ClassOrInterfaceType) {
+        getFullClassName(buildString {
+            type.scope.ifPresent { append(it).append(".") }
+            type.isArrayType
+            append(type.nameAsString)
+        }) + (type.typeArguments.orElse(null)?.let { args -> args.joinToString(", ") {getFullClassName(it)}}?.let { "<$it>" } ?: "")
+    } else getFullClassName(type.asString())
+}
+
+private fun ClassPrinter.getFullClassName(className: String): String {
+    if (className.endsWith("[]")) return getFullClassName(className.removeSuffix("[]")) + "[]"
+
+    if (className.matches("\\.[a-z]".toRegex())) return className //qualified name
+
+    if ("." in className) return getFullClassName(className.substringBeforeLast(".")) + "." + className.substringAfterLast(".")
+
+    fileAst.imports.find { imp ->
+        imp.nameAsString.endsWith(".$className")
+    }?.nameAsString?.let { return it }
+
+    val thisPackagePrefix = fileAst.packageDeclaration.map { it.nameAsString + "." }.orElse("")
+    val thisClassPrefix = thisPackagePrefix + classAst.nameAsString + "."
+
+    classAst.childNodes.filterIsInstance<TypeDeclaration<*>>().find {
+        it.nameAsString == className
+    }?.let { return thisClassPrefix + it.nameAsString }
+
+    constDefs.find { it.AnnotationName == className }?.let { return thisClassPrefix + className }
+
+    if (tryOrNull { Class.forName("java.lang.$className") } != null) {
+        return "java.lang.$className"
+    }
+
+    if (className[0].isLowerCase()) return className //primitive
+
+    return thisPackagePrefix + className
+}
+
+private inline fun <T> tryOrNull(f: () -> T?) = try {
+    f()
+} catch (e: Exception) {
+    null
+}
diff --git a/tools/codegen/src/com/android/codegen/Main.kt b/tools/codegen/src/com/android/codegen/Main.kt
new file mode 100755
index 0000000..8fafa7c
--- /dev/null
+++ b/tools/codegen/src/com/android/codegen/Main.kt
@@ -0,0 +1,199 @@
+package com.android.codegen
+
+import java.io.File
+
+
+const val THIS_SCRIPT_LOCATION = ""
+const val GENERATED_WARNING_PREFIX = "Code below generated by $CODEGEN_NAME"
+const val INDENT_SINGLE = "    "
+
+val PRIMITIVE_TYPES = listOf("byte", "short", "int", "long", "char", "float", "double", "boolean")
+
+const val CANONICAL_BUILDER_CLASS = "Builder"
+const val GENERATED_BUILDER_CLASS = "GeneratedBuilder"
+
+val BUILTIN_SPECIAL_PARCELLINGS = listOf("Pattern")
+
+const val FLAG_BUILDER_PROTECTED_SETTERS = "--builder-protected-setters"
+const val FLAG_NO_FULL_QUALIFIERS = "--no-full-qualifiers"
+
+
+/** @see [FeatureFlag] */
+val USAGE = """
+Usage: $CODEGEN_NAME [--[PREFIX-]FEATURE...] JAVAFILE
+
+Generates boilerplade parcelable/data class code at the bottom of JAVAFILE, based o fields' declaration in the given JAVAFILE's top-level class
+
+FEATURE represents some generatable code, and can be among:
+${FeatureFlag.values().map { feature ->
+    "  ${feature.kebabCase}" to feature.desc
+}.columnize(" - ")}
+
+And PREFIX can be:
+  <empty> - request to generate the feature
+    no    - suppress generation of the feature
+  hidden  - request to generate the feature with @hide
+
+Extra options:
+  --help        - view this help
+  --update-only - auto-detect flags from the previously auto-generated comment within the file
+  $FLAG_NO_FULL_QUALIFIERS
+                - when referring to classes don't use package name prefix; handy with IDE auto-import
+  $FLAG_BUILDER_PROTECTED_SETTERS
+                - make builder's setters protected to expose them as public in a subclass on a whitelist basis
+
+
+Special field modifiers and annotations:
+  transient                 - ignore the field completely
+  @Nullable                 - support null value when parcelling, and never throw on null input
+  @NonNull                  - throw on null input and don't parcel the nullness bit for the field
+  @DataClass.Enum           - parcel field as an enum value by ordinal
+  @DataClass.PluralOf(..)   - provide a singular version of a collection field name to be used in the builder's 'addFoo(..)'
+  @DataClass.ParcelWith(..) - provide a custom Parcelling class, specifying the custom (un)parcelling logic for this field
+  = <initializer>;          - provide default value and never throw if this field was not provided e.g. when using builder
+  /** ... */                - copy given javadoc on field's getters/setters/constructor params/builder setters etc.
+  @hide (in javadoc)        - force field's getters/setters/withers/builder setters to be @hide-den if generated
+
+
+Special methods/etc. you can define:
+
+  <any auto-generatable method>
+      For any method to be generated, if a method with same name and argument types is already
+      defined, than that method will not be generated.
+      This allows you to override certain details on granular basis.
+
+  void onConstructed()
+      Will be called in constructor, after all the fields have been initialized.
+      This is a good place to put any custom validation logic that you may have
+
+  static class $CANONICAL_BUILDER_CLASS extends $GENERATED_BUILDER_CLASS
+      If a class extending $GENERATED_BUILDER_CLASS is specified, generated builder's setters will
+      return the provided $CANONICAL_BUILDER_CLASS type.
+      $GENERATED_BUILDER_CLASS's constructor(s) will be package-private to encourage using $CANONICAL_BUILDER_CLASS instead
+      This allows you to extend the generated builder, adding or overriding any methods you may want
+
+
+In addition, for any field mMyField(or myField) of type FieldType you can define the following methods:
+
+  void parcelMyField(Parcel dest, int flags)
+      Allows you to provide custom logic for storing mMyField into a Parcel
+
+  static FieldType unparcelMyField(Parcel in)
+      Allows you to provide custom logic to deserialize the value of mMyField from a Parcel
+
+  String myFieldToString()
+      Allows you to provide a custom toString representation of mMyField's value
+
+  FieldType lazyInitMyField()
+      Requests a lazy initialization in getMyField(), with the provided method being the constructor
+      You may additionally mark the fields as volatile to cause this to generate a thread-safe
+      double-check locking lazy initialization
+
+  FieldType defaultMyField()
+      Allows you to provide a default value to initialize the field to, in case an explicit one
+      was not provided.
+      This is an alternative to providing a field initializer that, unlike the initializer,
+      you can use with final fields.
+
+Version: $CODEGEN_VERSION
+Questions? Feedback? Contact: eugenesusla@
+"""
+
+fun main(args: Array<String>) {
+    if (args.contains("--help")) {
+        println(USAGE)
+        System.exit(0)
+    }
+    if (args.contains("--version")) {
+        println(CODEGEN_VERSION)
+        System.exit(0)
+    }
+    val file = File(args.last())
+    val sourceLinesNoClosingBrace = file.readLines().dropLastWhile {
+        it.startsWith("}") || it.all(Char::isWhitespace)
+    }
+    val cliArgs = handleUpdateFlag(args, sourceLinesNoClosingBrace)
+    val sourceLinesAsIs = discardGeneratedCode(sourceLinesNoClosingBrace)
+    val sourceLines = sourceLinesAsIs
+            .filterNot { it.trim().startsWith("//") }
+            .map { it.trimEnd().dropWhile { it == '\n' } }
+
+    val stringBuilder = StringBuilder(sourceLinesAsIs.joinToString("\n"))
+    ClassPrinter(sourceLines, stringBuilder, cliArgs).run {
+
+        val cliExecutable = "$THIS_SCRIPT_LOCATION$CODEGEN_NAME"
+        val fileEscaped = file.absolutePath.replace(
+                System.getenv("ANDROID_BUILD_TOP"), "\$ANDROID_BUILD_TOP")
+
+
+        +"""
+
+
+
+        // $GENERATED_WARNING_PREFIX v$CODEGEN_VERSION.
+        //   on ${currentTimestamp()}
+        //
+        // DO NOT MODIFY!
+        //
+        // To regenerate run:
+        // $ $cliExecutable ${cliArgs.dropLast(1).joinToString("") { "$it " }}$fileEscaped
+        //
+        // CHECKSTYLE:OFF Generated code
+        """
+
+        if (FeatureFlag.CONST_DEFS()) generateConstDefs()
+
+        "@$DataClassGenerated(" {
+            +"time = ${System.currentTimeMillis()}L,"
+            +"codegenVersion = \"$CODEGEN_VERSION\","
+            +"sourceFile = \"${file.relativeTo(File(System.getenv("ANDROID_BUILD_TOP")))}\","
+            +"inputSignatures = \"${getInputSignatures().joinToString("\\n")}\""
+        }
+        +"\n"
+
+
+        if (FeatureFlag.CONSTRUCTOR()) {
+            generateConstructor("public")
+        } else if (FeatureFlag.BUILDER()
+                || FeatureFlag.COPY_CONSTRUCTOR()
+                || FeatureFlag.WITHERS()
+                || FeatureFlag.PARCELABLE()) {
+            generateConstructor("/* package-private */")
+        }
+
+        if (FeatureFlag.GETTERS()) generateGetters()
+        if (FeatureFlag.SETTERS()) generateSetters()
+        if (FeatureFlag.TO_STRING()) generateToString()
+        if (FeatureFlag.EQUALS_HASH_CODE()) generateEqualsHashcode()
+
+        if (FeatureFlag.FOR_EACH_FIELD()) generateForEachField()
+
+        if (FeatureFlag.COPY_CONSTRUCTOR()) generateCopyConstructor()
+        if (FeatureFlag.WITHERS()) generateWithers()
+
+        if (FeatureFlag.PARCELABLE()) generateParcelable()
+
+        if (FeatureFlag.BUILDER() && FeatureFlag.BUILD_UPON()) generateBuildUpon()
+        if (FeatureFlag.BUILDER()) generateBuilder()
+
+        if (FeatureFlag.AIDL()) generateAidl(file)
+
+        rmEmptyLine()
+    }
+    stringBuilder.append("\n}\n")
+    file.writeText(stringBuilder.toString().mapLines { trimEnd() })
+}
+
+internal fun discardGeneratedCode(sourceLinesNoClosingBrace: List<String>): List<String> {
+    return sourceLinesNoClosingBrace
+            .takeWhile { GENERATED_WARNING_PREFIX !in it }
+            .dropLastWhile(String::isBlank)
+}
+
+private fun handleUpdateFlag(cliArgs: Array<String>, sourceLines: List<String>): Array<String> {
+    if ("--update-only" in cliArgs
+            && sourceLines.none { GENERATED_WARNING_PREFIX in it || it.startsWith("@DataClass") }) {
+        System.exit(0)
+    }
+    return cliArgs - "--update-only"
+}
\ No newline at end of file
diff --git a/tools/codegen/src/com/android/codegen/SharedConstants.kt b/tools/codegen/src/com/android/codegen/SharedConstants.kt
new file mode 100644
index 0000000..41641f6
--- /dev/null
+++ b/tools/codegen/src/com/android/codegen/SharedConstants.kt
@@ -0,0 +1,4 @@
+package com.android.codegen
+
+const val CODEGEN_NAME = "codegen"
+const val CODEGEN_VERSION = "0.0.1"
\ No newline at end of file
diff --git a/tools/codegen/src/com/android/codegen/Utils.kt b/tools/codegen/src/com/android/codegen/Utils.kt
new file mode 100644
index 0000000..95c9909
--- /dev/null
+++ b/tools/codegen/src/com/android/codegen/Utils.kt
@@ -0,0 +1,76 @@
+package com.android.codegen
+
+import com.github.javaparser.ast.expr.AnnotationExpr
+import com.github.javaparser.ast.expr.Expression
+import com.github.javaparser.ast.expr.SingleMemberAnnotationExpr
+import java.time.Instant
+import java.time.ZoneId
+import java.time.format.DateTimeFormatter
+import java.time.format.FormatStyle
+
+/**
+ * [Iterable.forEach] + [Any.apply]
+ */
+inline fun <T> Iterable<T>.forEachApply(block: T.() -> Unit) = forEach(block)
+
+inline fun String.mapLines(f: String.() -> String?) = lines().mapNotNull(f).joinToString("\n")
+inline fun <T> Iterable<T>.trim(f: T.() -> Boolean) = dropWhile(f).dropLastWhile(f)
+fun String.trimBlankLines() = lines().trim { isBlank() }.joinToString("\n")
+
+fun Char.isNewline() = this == '\n' || this == '\r'
+fun Char.isWhitespaceNonNewline() = isWhitespace() && !isNewline()
+
+fun if_(cond: Boolean, then: String) = if (cond) then else ""
+
+inline infix fun Int.times(action: () -> Unit) {
+    for (i in 1..this) action()
+}
+
+/**
+ * a bbb
+ * cccc dd
+ *
+ * ->
+ *
+ * a    bbb
+ * cccc dd
+ */
+fun Iterable<Pair<String, String>>.columnize(separator: String = " | "): String {
+    val col1w = map { (a, _) -> a.length }.max()!!
+    val col2w = map { (_, b) -> b.length }.max()!!
+    return map { it.first.padEnd(col1w) + separator + it.second.padEnd(col2w) }.joinToString("\n")
+}
+
+fun String.hasUnbalancedCurlyBrace(): Boolean {
+    var braces = 0
+    forEach {
+        if (it == '{') braces++
+        if (it == '}') braces--
+        if (braces < 0) return true
+    }
+    return false
+}
+
+fun String.toLowerCamel(): String {
+    if (length >= 2 && this[0] == 'm' && this[1].isUpperCase()) return substring(1).capitalize()
+    if (all { it.isLetterOrDigit() }) return decapitalize()
+    return split("[^a-zA-Z0-9]".toRegex())
+            .map { it.toLowerCase().capitalize() }
+            .joinToString("")
+            .decapitalize()
+}
+
+inline fun <T> List<T>.forEachLastAware(f: (T, Boolean) -> Unit) {
+    forEachIndexed { index, t -> f(t, index == size - 1) }
+}
+
+@Suppress("UNCHECKED_CAST")
+fun <T : Expression> AnnotationExpr.singleArgAs()
+        = ((this as SingleMemberAnnotationExpr).memberValue as T)
+
+inline operator fun <reified T> Array<T>.minus(item: T) = toList().minus(item).toTypedArray()
+
+fun currentTimestamp() = DateTimeFormatter
+        .ofLocalizedDateTime(/* date */ FormatStyle.MEDIUM, /* time */ FormatStyle.LONG)
+        .withZone(ZoneId.systemDefault())
+        .format(Instant.now())
\ No newline at end of file
diff --git a/tools/processors/staledataclass/Android.bp b/tools/processors/staledataclass/Android.bp
new file mode 100644
index 0000000..c81d410
--- /dev/null
+++ b/tools/processors/staledataclass/Android.bp
@@ -0,0 +1,27 @@
+
+java_plugin {
+    name: "staledataclass-annotation-processor",
+    processor_class: "android.processor.staledataclass.StaleDataclassProcessor",
+
+    java_resources: [
+        "META-INF/**/*",
+    ],
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
+    static_libs: [
+        "codegen-version-info",
+    ],
+    openjdk9: {
+        javacflags: [
+            "--add-modules=jdk.compiler",
+            "--add-exports jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED",
+            "--add-exports jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED",
+            "--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
+            "--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
+        ],
+    },
+
+    use_tools_jar: true,
+}
diff --git a/tools/processors/staledataclass/META-INF/services/javax.annotation.processing.Processor b/tools/processors/staledataclass/META-INF/services/javax.annotation.processing.Processor
new file mode 100644
index 0000000..15ee623
--- /dev/null
+++ b/tools/processors/staledataclass/META-INF/services/javax.annotation.processing.Processor
@@ -0,0 +1 @@
+android.processor.staledataclass.StaleDataclassProcessorOld
diff --git a/tools/processors/staledataclass/src/android/processor/staledataclass/StaleDataclassProcessor.kt b/tools/processors/staledataclass/src/android/processor/staledataclass/StaleDataclassProcessor.kt
new file mode 100644
index 0000000..9e51180
--- /dev/null
+++ b/tools/processors/staledataclass/src/android/processor/staledataclass/StaleDataclassProcessor.kt
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2019 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 android.processor.staledataclass
+
+import com.android.codegen.CODEGEN_NAME
+import com.android.codegen.CODEGEN_VERSION
+import com.sun.tools.javac.code.Symbol
+import com.sun.tools.javac.code.Type
+import java.io.File
+import java.io.FileNotFoundException
+import javax.annotation.processing.AbstractProcessor
+import javax.annotation.processing.RoundEnvironment
+import javax.annotation.processing.SupportedAnnotationTypes
+import javax.lang.model.SourceVersion
+import javax.lang.model.element.AnnotationMirror
+import javax.lang.model.element.Element
+import javax.lang.model.element.TypeElement
+import javax.tools.Diagnostic
+
+private const val STALE_FILE_THRESHOLD_MS = 1000
+private val WORKING_DIR = File(".").absoluteFile
+
+private const val DATACLASS_ANNOTATION_NAME = "com.android.internal.util.DataClass"
+private const val GENERATED_ANNOTATION_NAME = "com.android.internal.util.DataClass.Generated"
+private const val GENERATED_MEMBER_ANNOTATION_NAME
+        = "com.android.internal.util.DataClass.Generated.Member"
+
+
+@SupportedAnnotationTypes(DATACLASS_ANNOTATION_NAME, GENERATED_ANNOTATION_NAME)
+class StaleDataclassProcessor: AbstractProcessor() {
+
+    private var dataClassAnnotation: TypeElement? = null
+    private var generatedAnnotation: TypeElement? = null
+    private var repoRoot: File? = null
+
+    private val stale = mutableListOf<Stale>()
+
+    /**
+     * This is the main entry point in the processor, called by the compiler.
+     */
+    override fun process(annotations: Set<TypeElement>, roundEnv: RoundEnvironment): Boolean {
+
+        if (generatedAnnotation == null) {
+            generatedAnnotation = annotations.find {
+                it.qualifiedName.toString() == GENERATED_ANNOTATION_NAME
+            }
+        }
+        if (dataClassAnnotation == null) {
+            dataClassAnnotation = annotations.find {
+                it.qualifiedName.toString() == DATACLASS_ANNOTATION_NAME
+            }
+        }
+
+        val generatedAnnotatedElements = roundEnv.getElementsAnnotatedWith(generatedAnnotation)
+        generatedAnnotatedElements.forEach {
+            processSingleFile(it)
+        }
+
+
+        val dataClassesWithoutGeneratedPart =
+                roundEnv.getElementsAnnotatedWith(dataClassAnnotation) -
+                        generatedAnnotatedElements.map { it.enclosingElement }
+
+        dataClassesWithoutGeneratedPart.forEach { dataClass ->
+            stale += Stale(dataClass.toString(), file = null, lastGenerated = 0L)
+        }
+
+
+        if (!stale.isEmpty()) {
+            error("Stale generated dataclass(es) detected. " +
+                    "Run the following command(s) to update them:" +
+                    stale.joinToString("") { "\n" + it.refreshCmd })
+        }
+        return true
+    }
+
+    private fun elemToString(elem: Element): String {
+        return buildString {
+            append(elem.modifiers.joinToString(" ") { it.name.toLowerCase() }).append(" ")
+            append(elem.annotationMirrors.joinToString(" ")).append(" ")
+            if (elem is Symbol) {
+                if (elem.type is Type.MethodType) {
+                    append((elem.type as Type.MethodType).returnType)
+                } else {
+                    append(elem.type)
+                }
+                append(" ")
+            }
+            append(elem)
+        }
+    }
+
+    private fun processSingleFile(elementAnnotatedWithGenerated: Element) {
+
+        val inputSignatures = elementAnnotatedWithGenerated
+                .enclosingElement
+                .enclosedElements
+                .filterNot {
+                    it.annotationMirrors.any { "Generated" in it.annotationType.toString() }
+                }.map {
+                    elemToString(it)
+                }.toSet()
+
+        val annotationParams = elementAnnotatedWithGenerated
+                .annotationMirrors
+                .find { ann -> isGeneratedAnnotation(ann) }!!
+                .elementValues
+                .map { (k, v) -> k.getSimpleName().toString() to v.getValue() }
+                .toMap()
+
+        val lastGenerated = annotationParams["time"] as Long
+        val codegenVersion = annotationParams["codegenVersion"] as String
+        val sourceRelative = File(annotationParams["sourceFile"] as String)
+
+        val lastGenInputSignatures = (annotationParams["inputSignatures"] as String).lines().toSet()
+
+        if (repoRoot == null) {
+            repoRoot = generateSequence(WORKING_DIR) { it.parentFile }
+                    .find { it.resolve(sourceRelative).isFile }
+                    ?.canonicalFile
+                    ?: throw FileNotFoundException(
+                            "Failed to detect repository root: " +
+                                    "no parent of $WORKING_DIR contains $sourceRelative")
+        }
+
+        val source = repoRoot!!.resolve(sourceRelative)
+        val clazz = elementAnnotatedWithGenerated.enclosingElement.toString()
+
+        if (inputSignatures != lastGenInputSignatures) {
+            error(buildString {
+                append(sourceRelative).append(":\n")
+                append("  Added:\n").append((inputSignatures-lastGenInputSignatures).joinToString("\n"))
+                append("\n")
+                append("  Removed:\n").append((lastGenInputSignatures-inputSignatures).joinToString("\n"))
+            })
+            stale += Stale(clazz, source, lastGenerated)
+        }
+
+        if (codegenVersion != CODEGEN_VERSION) {
+            stale += Stale(clazz, source, lastGenerated)
+        }
+    }
+
+    private fun error(msg: String) {
+        processingEnv.messager.printMessage(Diagnostic.Kind.ERROR, msg)
+    }
+
+    private fun isGeneratedAnnotation(ann: AnnotationMirror): Boolean {
+        return generatedAnnotation!!.qualifiedName.toString() == ann.annotationType.toString()
+    }
+
+    data class Stale(val clazz: String, val file: File?, val lastGenerated: Long) {
+        val refreshCmd = if (file != null) {
+            "$CODEGEN_NAME $file"
+        } else {
+            "find \$ANDROID_BUILD_TOP -path */${clazz.replace('.', '/')}.java -exec $CODEGEN_NAME {} \\;"
+        }
+    }
+
+    override fun getSupportedSourceVersion(): SourceVersion {
+        return SourceVersion.latest()
+    }
+}
\ No newline at end of file