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