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/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();
+        }
+    }
+}