Addresses further review comments from ag/8000041
Including:
- An API to opt out of Int/StringDefs generation on per-field basis
- A way to customize Builder
- Non-optional fields are passed in Builder constructor
- Various adjustments to SampleDataclass examples, as requested
Test: . $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/runTest.sh
Change-Id: I32d2eec52f05d505ff07779d923e4793d3036579
diff --git a/core/java/com/android/internal/util/DataClass.java b/core/java/com/android/internal/util/DataClass.java
index 146f546..da33f99 100644
--- a/core/java/com/android/internal/util/DataClass.java
+++ b/core/java/com/android/internal/util/DataClass.java
@@ -177,11 +177,10 @@
/**
* @deprecated to be used by code generator exclusively
- * @hide
*/
@Deprecated
@Retention(RetentionPolicy.SOURCE)
- @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, ANNOTATION_TYPE, CONSTRUCTOR, TYPE})
+ @Target({METHOD})
@interface Generated {
long time();
String codegenVersion();
@@ -190,7 +189,6 @@
/**
* @deprecated to be used by code generator exclusively
- * @hide
*/
@Deprecated
@Retention(RetentionPolicy.SOURCE)
@@ -199,6 +197,22 @@
}
/**
+ * Opt out of generating {@link #genConstDefs IntDef/StringDef}s for annotated constant
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @Target({FIELD})
+ @interface SuppressConstDefsGeneration {}
+
+ /**
+ * A class-level annotation to suppress methods' generation by name
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @Target({TYPE})
+ @interface Suppress {
+ String[] value();
+ }
+
+ /**
* Callback used by {@link #genForEachField}.
*
* @param <THIS> The enclosing data class instance.
diff --git a/core/java/com/android/internal/util/Parcelling.java b/core/java/com/android/internal/util/Parcelling.java
index 63530dc..390c596 100644
--- a/core/java/com/android/internal/util/Parcelling.java
+++ b/core/java/com/android/internal/util/Parcelling.java
@@ -24,6 +24,8 @@
/**
* Describes a 2-way parcelling contract of type {@code T} into/out of a {@link Parcel}
*
+ * Implementations should be stateless.
+ *
* @param <T> the type being [un]parcelled
*/
public interface Parcelling<T> {
@@ -69,6 +71,7 @@
* instance or reflectively creating one.
*/
public static <P extends Parcelling<?>> P getOrCreate(Class<P> clazz) {
+ // No synchronization - creating an extra instance in a race case is ok
P cached = get(clazz);
if (cached != null) {
return cached;
diff --git a/tests/Codegen/runTest.sh b/tests/Codegen/runTest.sh
index bc1aae0..614cbb7 100755
--- a/tests/Codegen/runTest.sh
+++ b/tests/Codegen/runTest.sh
@@ -12,6 +12,7 @@
header_and_eval m -j16 codegen_cli && \
header_and_eval codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/SampleDataClass.java && \
+ header_and_eval codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.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 && \
diff --git a/tests/Codegen/src/com/android/codegentest/DateParcelling.java b/tests/Codegen/src/com/android/codegentest/MyDateParcelling.java
similarity index 94%
rename from tests/Codegen/src/com/android/codegentest/DateParcelling.java
rename to tests/Codegen/src/com/android/codegentest/MyDateParcelling.java
index b0b00d0..4faeb8e 100644
--- a/tests/Codegen/src/com/android/codegentest/DateParcelling.java
+++ b/tests/Codegen/src/com/android/codegentest/MyDateParcelling.java
@@ -31,11 +31,11 @@
*
* Ignore {@link #sInstanceCount} - used for testing.
*/
-public class DateParcelling implements Parcelling<Date> {
+public class MyDateParcelling implements Parcelling<Date> {
static AtomicInteger sInstanceCount = new AtomicInteger(0);
- public DateParcelling() {
+ public MyDateParcelling() {
sInstanceCount.getAndIncrement();
}
diff --git a/tests/Codegen/src/com/android/codegentest/SampleDataClass.java b/tests/Codegen/src/com/android/codegentest/SampleDataClass.java
index f69a092..b30fde4 100644
--- a/tests/Codegen/src/com/android/codegentest/SampleDataClass.java
+++ b/tests/Codegen/src/com/android/codegentest/SampleDataClass.java
@@ -53,11 +53,13 @@
// genAidl = true, // implied by `implements Parcelable`
// genGetters = true, // on by default
// genConstDefs = true, // implied by presence of constants with common prefix
+ genBuilder = true, // on by default if optional fields present, but suppressed by
+ // genConstructor
+ genConstructor = true, // on by default but normally suppressed by genBuilder
genEqualsHashCode = true,
- genBuilder = true,
genToString = true,
genForEachField = true,
- genConstructor = true // on by default but normally suppressed by genBuilder
+ genSetters = true
)
public final class SampleDataClass implements Parcelable {
@@ -136,48 +138,53 @@
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.
+ * {@link Nullable} or {@link NonNull} annotation is required on all non-primitive fields.
*/
private @Nullable String mName;
/**
- * Fields with default value expressions ("mFoo = ...") are also optional, and are automatically
+ * Fields with default value expressions ("mFoo = ...") are 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.
+ * When using a {@link Builder} optional fields are passed via a {@link Builder#setName2 setter}
+ * while mandatory fields are passed via {@link Builder#Builder constructor}.
+ */
+ private @NonNull String mName2 = "Bob";
+ /**
+ * Alternatively, when default value computation is expensive,
+ * {@link #defaultName4 defaultFieldName()} can be defined to compute the default value.
*/
private @NonNull String mName4;
+ private static String defaultName4() {
+ // Expensive computation
+ return "Bob4";
+ }
/**
* 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;
+ private @Nullable 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
+ * @see MyDateParcelling an example {@link Parcelling} implementation
*/
- @DataClass.ParcelWith(DateParcelling.class)
- private Date mDate = new Date(42 * 42);
+ @DataClass.ParcelWith(MyDateParcelling.class)
+ private @NonNull 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("");
+ private @NonNull 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<>();
+ private @NonNull 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.
@@ -185,7 +192,7 @@
* @see Builder#addLinkAddress(LinkAddress)
*/
@DataClass.PluralOf("linkAddress")
- private ArrayList<LinkAddress> mLinkAddresses = new ArrayList<>();
+ private @NonNull ArrayList<LinkAddress> mLinkAddresses = new ArrayList<>();
/**
* For array fields, when using a {@link Builder}, vararg argument format is used for
* convenience.
@@ -193,11 +200,6 @@
* @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
@@ -206,7 +208,7 @@
* @see #getStateName
* @see Builder#setStateName
*/
- private @StateName String mStateName = STATE_NAME_UNDEFINED;
+ private @StateName @NonNull String mStateName = STATE_NAME_UNDEFINED;
/**
* Fields annotated with {@link IntDef} annotations also get a proper {@link #toString()} value.
*/
@@ -220,11 +222,11 @@
/**
* Making a field public will suppress getter generation in favor of accessing it directly.
*/
- public CharSequence charSeq = "";
+ public @NonNull CharSequence charSeq = "";
/**
* Final fields suppress generating a setter (when setters are requested).
*/
- private final LinkAddress[] mLinkAddresses5;
+ private final @Nullable LinkAddress[] mLinkAddresses5;
/**
* Transient fields are completely ignored and can be used for caching.
*/
@@ -261,7 +263,7 @@
*
* @see AnnotationValidations#validate(Class, Size, int, String, int, String, int)
*/
- private @android.annotation.IntRange(from = 0, to = 4) int mLimited = 3;
+ private @android.annotation.IntRange(from = 0, to = 6) int mDayOfWeek = 3;
/**
* Unnamed validation annotation parameter gets supplied to the validating method named as
* "value".
@@ -272,6 +274,7 @@
* @see AnnotationValidations#validate(Class, Size, int, String, int)
*/
@Size(2)
+ @NonNull
@Each @FloatRange(from = 0f)
private float[] mCoords = new float[] {0f, 0f};
@@ -340,7 +343,6 @@
// Code below generated by codegen v1.0.0.
- // on Jul 29, 2019, 2:50:21 PM PDT
//
// DO NOT MODIFY!
//
@@ -408,13 +410,9 @@
@DataClass.Generated.Member
public @interface StateName {}
- @DataClass.Generated(
- time = 1564437021513L,
- codegenVersion = "1.0.0",
- 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)")
-
-/**
+ /**
+ * Creates a new SampleDataClass.
+ *
* @param num
* Any property javadoc should go onto the field, and will be copied where appropriate,
* including getters, constructor parameters, builder setters, etc.
@@ -429,15 +427,16 @@
* {@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.
+ * {@link Nullable} or {@link NonNull} annotation is required on all non-primitive fields.
* @param name2
- * Fields with default value expressions ("mFoo = ...") are also optional, and are automatically
+ * Fields with default value expressions ("mFoo = ...") are 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.
+ * When using a {@link Builder} optional fields are passed via a {@link Builder#setName2 setter}
+ * while mandatory fields are passed via {@link Builder#Builder constructor}.
+ * @param name4
+ * Alternatively, when default value computation is expensive,
+ * {@link #defaultName4 defaultFieldName()} can be defined to compute the default value.
* @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.
@@ -457,9 +456,6 @@
* @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.
@@ -480,7 +476,7 @@
* You can also extend support to your custom annotations by creating another corresponding
* overloads like
* {@link AnnotationValidations#validate(Class, UserIdInt, int)}.
- * @param limited
+ * @param dayOfWeek
* Validation annotations may also have parameters.
*
* Parameter values will be supplied to validation method as name-value pairs.
@@ -497,50 +493,49 @@
int num2,
int num4,
@Nullable String name,
- String name2,
+ @NonNull String name2,
@NonNull String name4,
- AccessibilityNodeInfo otherParcelable,
- Date date,
- Pattern pattern,
- List<LinkAddress> linkAddresses2,
- ArrayList<LinkAddress> linkAddresses,
+ @Nullable AccessibilityNodeInfo otherParcelable,
+ @NonNull Date date,
+ @NonNull Pattern pattern,
+ @NonNull List<LinkAddress> linkAddresses2,
+ @NonNull ArrayList<LinkAddress> linkAddresses,
@Nullable LinkAddress[] linkAddresses4,
- boolean active,
- @StateName String stateName,
+ @StateName @NonNull String stateName,
@RequestFlags int flags,
@State int state,
- CharSequence charSeq,
- LinkAddress[] linkAddresses5,
+ @NonNull CharSequence charSeq,
+ @Nullable LinkAddress[] linkAddresses5,
@StringRes int stringRes,
- @android.annotation.IntRange(from = 0, to = 4) int limited,
- @Size(2) @FloatRange(from = 0f) float[] coords) {
+ @android.annotation.IntRange(from = 0, to = 6) int dayOfWeek,
+ @Size(2) @NonNull @Each @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, mName2);
+ this.mName4 = name4;
AnnotationValidations.validate(
NonNull.class, null, mName4);
+ this.mOtherParcelable = otherParcelable;
+ this.mDate = date;
+ AnnotationValidations.validate(
+ NonNull.class, null, mDate);
+ this.mPattern = pattern;
+ AnnotationValidations.validate(
+ NonNull.class, null, mPattern);
+ this.mLinkAddresses2 = linkAddresses2;
+ AnnotationValidations.validate(
+ NonNull.class, null, mLinkAddresses2);
+ this.mLinkAddresses = linkAddresses;
+ AnnotationValidations.validate(
+ NonNull.class, null, mLinkAddresses);
+ this.mLinkAddresses4 = linkAddresses4;
+ this.mStateName = stateName;
- //noinspection PointlessBooleanExpression
- if (true
- && !(Objects.equals(mStateName, STATE_NAME_UNDEFINED))
+ if (!(Objects.equals(mStateName, STATE_NAME_UNDEFINED))
&& !(Objects.equals(mStateName, STATE_NAME_ON))
&& !(Objects.equals(mStateName, STATE_NAME_OFF))) {
throw new java.lang.IllegalArgumentException(
@@ -550,17 +545,18 @@
+ "STATE_NAME_OFF(" + STATE_NAME_OFF + ")");
}
+ AnnotationValidations.validate(
+ NonNull.class, null, mStateName);
+ this.mFlags = flags;
- //noinspection PointlessBitwiseExpression
Preconditions.checkFlagsArgument(
- mFlags, 0
- | FLAG_MANUAL_REQUEST
+ mFlags,
+ FLAG_MANUAL_REQUEST
| FLAG_COMPATIBILITY_MODE_REQUEST
| FLAG_AUGMENTED_REQUEST);
+ this.mState = state;
- //noinspection PointlessBooleanExpression
- if (true
- && !(mState == STATE_UNDEFINED)
+ if (!(mState == STATE_UNDEFINED)
&& !(mState == STATE_ON)
&& !(mState == STATE_OFF)) {
throw new java.lang.IllegalArgumentException(
@@ -570,15 +566,24 @@
+ "STATE_OFF(" + STATE_OFF + ")");
}
+ this.charSeq = charSeq;
+ AnnotationValidations.validate(
+ NonNull.class, null, charSeq);
+ this.mLinkAddresses5 = linkAddresses5;
+ this.mStringRes = stringRes;
AnnotationValidations.validate(
StringRes.class, null, mStringRes);
+ this.mDayOfWeek = dayOfWeek;
AnnotationValidations.validate(
- android.annotation.IntRange.class, null, mLimited,
+ android.annotation.IntRange.class, null, mDayOfWeek,
"from", 0,
- "to", 4);
+ "to", 6);
+ this.mCoords = coords;
AnnotationValidations.validate(
Size.class, null, mCoords.length,
"value", 2);
+ AnnotationValidations.validate(
+ NonNull.class, null, mCoords);
int coordsSize = mCoords.length;
for (int i = 0; i < coordsSize; i++) {
AnnotationValidations.validate(
@@ -628,8 +633,7 @@
}
/**
- * {@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.
+ * {@link Nullable} or {@link NonNull} annotation is required on all non-primitive fields.
*/
@DataClass.Generated.Member
public @Nullable String getName() {
@@ -637,18 +641,20 @@
}
/**
- * Fields with default value expressions ("mFoo = ...") are also optional, and are automatically
+ * Fields with default value expressions ("mFoo = ...") are optional, and are automatically
* initialized to the provided default expression, unless explicitly set.
+ *
+ * When using a {@link Builder} optional fields are passed via a {@link Builder#setName2 setter}
+ * while mandatory fields are passed via {@link Builder#Builder constructor}.
*/
@DataClass.Generated.Member
- public String getName2() {
+ public @NonNull 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.
+ * Alternatively, when default value computation is expensive,
+ * {@link #defaultName4 defaultFieldName()} can be defined to compute the default value.
*/
@DataClass.Generated.Member
public @NonNull String getName4() {
@@ -660,7 +666,7 @@
* E.g. {@link Parcelable} subclasses, {@link String}, {@link int}, {@link boolean}, etc.
*/
@DataClass.Generated.Member
- public AccessibilityNodeInfo getOtherParcelable() {
+ public @Nullable AccessibilityNodeInfo getOtherParcelable() {
return mOtherParcelable;
}
@@ -668,10 +674,10 @@
* 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
+ * @see MyDateParcelling an example {@link Parcelling} implementation
*/
@DataClass.Generated.Member
- public Date getDate() {
+ public @NonNull Date getDate() {
return mDate;
}
@@ -680,7 +686,7 @@
* to encourage its reuse.
*/
@DataClass.Generated.Member
- public Pattern getPattern() {
+ public @NonNull Pattern getPattern() {
return mPattern;
}
@@ -690,7 +696,7 @@
* {@link Builder#addLinkAddresses2(LinkAddress) add} method is generated for convenience.
*/
@DataClass.Generated.Member
- public List<LinkAddress> getLinkAddresses2() {
+ public @NonNull List<LinkAddress> getLinkAddresses2() {
return mLinkAddresses2;
}
@@ -701,20 +707,11 @@
* @see Builder#addLinkAddress(LinkAddress)
*/
@DataClass.Generated.Member
- public ArrayList<LinkAddress> getLinkAddresses() {
+ public @NonNull 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.
*
@@ -722,7 +719,7 @@
* @see Builder#setStateName
*/
@DataClass.Generated.Member
- public @StateName String getStateName() {
+ public @StateName @NonNull String getStateName() {
return mStateName;
}
@@ -746,7 +743,7 @@
* Final fields suppress generating a setter (when setters are requested).
*/
@DataClass.Generated.Member
- public LinkAddress[] getLinkAddresses5() {
+ public @Nullable LinkAddress[] getLinkAddresses5() {
return mLinkAddresses5;
}
@@ -775,8 +772,8 @@
* @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;
+ public @android.annotation.IntRange(from = 0, to = 6) int getDayOfWeek() {
+ return mDayOfWeek;
}
/**
@@ -789,7 +786,7 @@
* @see AnnotationValidations#validate(Class, Size, int, String, int)
*/
@DataClass.Generated.Member
- public @Size(2) @FloatRange(from = 0f) float[] getCoords() {
+ public @Size(2) @NonNull @Each @FloatRange(from = 0f) float[] getCoords() {
return mCoords;
}
@@ -810,6 +807,282 @@
return tmpStorage;
}
+ /**
+ * 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 SampleDataClass setNum(int value) {
+ 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 SampleDataClass setNum2(int value) {
+ 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 SampleDataClass setNum4(int value) {
+ mNum4 = value;
+ return this;
+ }
+
+ /**
+ * {@link Nullable} or {@link NonNull} annotation is required on all non-primitive fields.
+ */
+ @DataClass.Generated.Member
+ public SampleDataClass setName(@Nullable String value) {
+ mName = value;
+ return this;
+ }
+
+ /**
+ * Fields with default value expressions ("mFoo = ...") are optional, and are automatically
+ * initialized to the provided default expression, unless explicitly set.
+ *
+ * When using a {@link Builder} optional fields are passed via a {@link Builder#setName2 setter}
+ * while mandatory fields are passed via {@link Builder#Builder constructor}.
+ */
+ @DataClass.Generated.Member
+ public SampleDataClass setName2(@NonNull String value) {
+ mName2 = value;
+ AnnotationValidations.validate(
+ NonNull.class, null, mName2);
+ return this;
+ }
+
+ /**
+ * Alternatively, when default value computation is expensive,
+ * {@link #defaultName4 defaultFieldName()} can be defined to compute the default value.
+ */
+ @DataClass.Generated.Member
+ public SampleDataClass setName4(@NonNull String value) {
+ mName4 = value;
+ AnnotationValidations.validate(
+ NonNull.class, null, mName4);
+ 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 SampleDataClass setOtherParcelable(@Nullable AccessibilityNodeInfo value) {
+ 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 MyDateParcelling an example {@link Parcelling} implementation
+ */
+ @DataClass.Generated.Member
+ public SampleDataClass setDate(@NonNull Date value) {
+ mDate = value;
+ AnnotationValidations.validate(
+ NonNull.class, null, mDate);
+ return this;
+ }
+
+ /**
+ * If a {@link Parcelling} is fairly common, consider putting it in {@link Parcelling.BuiltIn}
+ * to encourage its reuse.
+ */
+ @DataClass.Generated.Member
+ public SampleDataClass setPattern(@NonNull Pattern value) {
+ mPattern = value;
+ AnnotationValidations.validate(
+ NonNull.class, null, mPattern);
+ 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 SampleDataClass setLinkAddresses2(@NonNull List<LinkAddress> value) {
+ mLinkAddresses2 = value;
+ AnnotationValidations.validate(
+ NonNull.class, null, mLinkAddresses2);
+ 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 SampleDataClass setLinkAddresses(@NonNull ArrayList<LinkAddress> value) {
+ mLinkAddresses = value;
+ AnnotationValidations.validate(
+ NonNull.class, null, mLinkAddresses);
+ 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 SampleDataClass setLinkAddresses4(@Nullable LinkAddress... value) {
+ mLinkAddresses4 = value;
+ return this;
+ }
+
+ /**
+ * {@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 SampleDataClass setStateName(@StateName @NonNull String value) {
+ mStateName = value;
+
+ if (!(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 + ")");
+ }
+
+ AnnotationValidations.validate(
+ NonNull.class, null, mStateName);
+ return this;
+ }
+
+ /**
+ * Fields annotated with {@link IntDef} annotations also get a proper {@link #toString()} value.
+ */
+ @DataClass.Generated.Member
+ public SampleDataClass setFlags(@RequestFlags int value) {
+ mFlags = value;
+
+ Preconditions.checkFlagsArgument(
+ mFlags,
+ FLAG_MANUAL_REQUEST
+ | FLAG_COMPATIBILITY_MODE_REQUEST
+ | FLAG_AUGMENTED_REQUEST);
+ return this;
+ }
+
+ /**
+ * Above is true for both {@link IntDef#flag flags} and enum-like {@link IntDef}s
+ */
+ @DataClass.Generated.Member
+ public SampleDataClass setState(@State int value) {
+ mState = value;
+
+ if (!(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 + ")");
+ }
+
+ 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 SampleDataClass setStringRes(@StringRes int value) {
+ mStringRes = value;
+ AnnotationValidations.validate(
+ StringRes.class, null, mStringRes);
+ 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 SampleDataClass setDayOfWeek(@android.annotation.IntRange(from = 0, to = 6) int value) {
+ mDayOfWeek = value;
+ AnnotationValidations.validate(
+ android.annotation.IntRange.class, null, mDayOfWeek,
+ "from", 0,
+ "to", 6);
+ 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 SampleDataClass setCoords(@Size(2) @NonNull @Each @FloatRange(from = 0f) float... value) {
+ mCoords = value;
+ AnnotationValidations.validate(
+ Size.class, null, mCoords.length,
+ "value", 2);
+ AnnotationValidations.validate(
+ NonNull.class, null, mCoords);
+ int coordsSize = mCoords.length;
+ for (int i = 0; i < coordsSize; i++) {
+ AnnotationValidations.validate(
+ FloatRange.class, null, mCoords[i],
+ "from", 0f);
+ }
+
+ return this;
+ }
+
@Override
@DataClass.Generated.Member
public String toString() {
@@ -829,14 +1102,13 @@
"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 + ", " +
+ "dayOfWeek = " + mDayOfWeek + ", " +
"coords = " + java.util.Arrays.toString(mCoords) +
" }";
}
@@ -866,14 +1138,13 @@
&& 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
+ && mDayOfWeek == that.mDayOfWeek
&& java.util.Arrays.equals(mCoords, that.mCoords);
}
@@ -896,14 +1167,13 @@
_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 + mDayOfWeek;
_hash = 31 * _hash + java.util.Arrays.hashCode(mCoords);
return _hash;
}
@@ -924,14 +1194,13 @@
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);
+ actionInt.acceptInt(this, "dayOfWeek", mDayOfWeek);
actionObject.acceptObject(this, "coords", mCoords);
}
@@ -951,25 +1220,24 @@
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, "dayOfWeek", mDayOfWeek);
action.acceptObject(this, "coords", mCoords);
}
@DataClass.Generated.Member
static Parcelling<Date> sParcellingForDate =
Parcelling.Cache.get(
- DateParcelling.class);
+ MyDateParcelling.class);
static {
if (sParcellingForDate == null) {
sParcellingForDate = Parcelling.Cache.put(
- new DateParcelling());
+ new MyDateParcelling());
}
}
@@ -991,40 +1259,31 @@
// 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;
+ if (mLinkAddresses5 != null) flg |= 0x10000;
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(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);
+ dest.writeParcelableList(mLinkAddresses2, flags);
+ dest.writeParcelableList(mLinkAddresses, flags);
if (mLinkAddresses4 != null) dest.writeTypedArray(mLinkAddresses4, flags);
- if (mStateName != null) dest.writeString(mStateName);
+ dest.writeString(mStateName);
dest.writeInt(mFlags);
dest.writeInt(mState);
- if (charSeq != null) dest.writeCharSequence(charSeq);
+ dest.writeCharSequence(charSeq);
if (mLinkAddresses5 != null) dest.writeTypedArray(mLinkAddresses5, flags);
dest.writeInt(mStringRes);
- dest.writeInt(mLimited);
- if (mCoords != null) dest.writeFloatArray(mCoords);
+ dest.writeInt(mDayOfWeek);
+ dest.writeFloatArray(mCoords);
}
@Override
@@ -1046,35 +1305,28 @@
// 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 name2 = 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());
- }
+ List<LinkAddress> linkAddresses2 = new ArrayList<>();
+ in.readParcelableList(linkAddresses2, LinkAddress.class.getClassLoader());
+ ArrayList<LinkAddress> 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();
+ String stateName = 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);
+ CharSequence _charSeq = (CharSequence) in.readCharSequence();
+ LinkAddress[] linkAddresses5 = (flg & 0x10000) == 0 ? null : (LinkAddress[]) in.createTypedArray(LinkAddress.CREATOR);
int stringRes = in.readInt();
- int limited = in.readInt();
- float[] coords = (flg & 0x100000) == 0 ? null : in.createFloatArray();
+ int dayOfWeek = in.readInt();
+ float[] coords = in.createFloatArray();
return new SampleDataClass(
num,
num2,
@@ -1088,14 +1340,13 @@
linkAddresses2,
linkAddresses,
linkAddresses4,
- active,
stateName,
flags,
state,
_charSeq,
linkAddresses5,
stringRes,
- limited,
+ dayOfWeek,
coords);
}
};
@@ -1105,34 +1356,74 @@
*/
@SuppressWarnings("WeakerAccess")
@DataClass.Generated.Member
- public static class Builder
- extends android.provider.OneTimeUseBuilder<SampleDataClass> {
+ public static class Builder {
- 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;
+ private int mNum;
+ private int mNum2;
+ private int mNum4;
+ private @Nullable String mName;
+ private @NonNull String mName2;
+ private @NonNull String mName4;
+ private @Nullable AccessibilityNodeInfo mOtherParcelable;
+ private @NonNull Date mDate;
+ private @NonNull Pattern mPattern;
+ private @NonNull List<LinkAddress> mLinkAddresses2;
+ private @NonNull ArrayList<LinkAddress> mLinkAddresses;
+ private @Nullable LinkAddress[] mLinkAddresses4;
+ private @StateName @NonNull String mStateName;
+ private @RequestFlags int mFlags;
+ private @State int mState;
+ private @NonNull CharSequence charSeq;
+ private @Nullable LinkAddress[] mLinkAddresses5;
+ private @StringRes int mStringRes;
+ private @android.annotation.IntRange(from = 0, to = 6) int mDayOfWeek;
+ private @Size(2) @NonNull @Each @FloatRange(from = 0f) float[] mCoords;
- protected long mBuilderFieldsSet = 0L;
+ private long mBuilderFieldsSet = 0L;
- public Builder() {};
+ /**
+ * Creates a new Builder.
+ *
+ * @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} or {@link NonNull} annotation is required on all non-primitive fields.
+ * @param flags
+ * Fields annotated with {@link IntDef} annotations also get a proper {@link #toString()} value.
+ * @param linkAddresses5
+ * Final fields suppress generating a setter (when setters are requested).
+ */
+ public Builder(
+ int num,
+ int num2,
+ int num4,
+ @Nullable String name,
+ @RequestFlags int flags,
+ @Nullable LinkAddress[] linkAddresses5) {
+ mNum = num;
+ mNum2 = num2;
+ mNum4 = num4;
+ mName = name;
+ mFlags = flags;
+
+ Preconditions.checkFlagsArgument(
+ mFlags,
+ FLAG_MANUAL_REQUEST
+ | FLAG_COMPATIBILITY_MODE_REQUEST
+ | FLAG_AUGMENTED_REQUEST);
+ mLinkAddresses5 = linkAddresses5;
+ }
/**
* Any property javadoc should go onto the field, and will be copied where appropriate,
@@ -1143,7 +1434,7 @@
* pieces in multiple places for each field.
*/
@DataClass.Generated.Member
- public Builder setNum(int value) {
+ public @NonNull Builder setNum(int value) {
checkNotUsed();
mBuilderFieldsSet |= 0x1;
mNum = value;
@@ -1157,7 +1448,7 @@
* @see #mNum2 ..and so should blocks at the bottom, e.g. {@code @see} blocks.
*/
@DataClass.Generated.Member
- public Builder setNum2(int value) {
+ public @NonNull Builder setNum2(int value) {
checkNotUsed();
mBuilderFieldsSet |= 0x2;
mNum2 = value;
@@ -1173,7 +1464,7 @@
* @hide
*/
@DataClass.Generated.Member
- public Builder setNum4(int value) {
+ public @NonNull Builder setNum4(int value) {
checkNotUsed();
mBuilderFieldsSet |= 0x4;
mNum4 = value;
@@ -1181,11 +1472,10 @@
}
/**
- * {@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.
+ * {@link Nullable} or {@link NonNull} annotation is required on all non-primitive fields.
*/
@DataClass.Generated.Member
- public Builder setName(@Nullable String value) {
+ public @NonNull Builder setName(@Nullable String value) {
checkNotUsed();
mBuilderFieldsSet |= 0x8;
mName = value;
@@ -1193,11 +1483,14 @@
}
/**
- * Fields with default value expressions ("mFoo = ...") are also optional, and are automatically
+ * Fields with default value expressions ("mFoo = ...") are optional, and are automatically
* initialized to the provided default expression, unless explicitly set.
+ *
+ * When using a {@link Builder} optional fields are passed via a {@link Builder#setName2 setter}
+ * while mandatory fields are passed via {@link Builder#Builder constructor}.
*/
@DataClass.Generated.Member
- public Builder setName2(String value) {
+ public @NonNull Builder setName2(@NonNull String value) {
checkNotUsed();
mBuilderFieldsSet |= 0x10;
mName2 = value;
@@ -1205,12 +1498,11 @@
}
/**
- * Fields without {@link Nullable} annotation or default value are considered required.
- *
- * {@link NonNull} annotation is recommended on such non-primitive fields for documentation.
+ * Alternatively, when default value computation is expensive,
+ * {@link #defaultName4 defaultFieldName()} can be defined to compute the default value.
*/
@DataClass.Generated.Member
- public Builder setName4(@NonNull String value) {
+ public @NonNull Builder setName4(@NonNull String value) {
checkNotUsed();
mBuilderFieldsSet |= 0x20;
mName4 = value;
@@ -1222,7 +1514,7 @@
* E.g. {@link Parcelable} subclasses, {@link String}, {@link int}, {@link boolean}, etc.
*/
@DataClass.Generated.Member
- public Builder setOtherParcelable(AccessibilityNodeInfo value) {
+ public @NonNull Builder setOtherParcelable(@Nullable AccessibilityNodeInfo value) {
checkNotUsed();
mBuilderFieldsSet |= 0x40;
mOtherParcelable = value;
@@ -1233,10 +1525,10 @@
* 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
+ * @see MyDateParcelling an example {@link Parcelling} implementation
*/
@DataClass.Generated.Member
- public Builder setDate(Date value) {
+ public @NonNull Builder setDate(@NonNull Date value) {
checkNotUsed();
mBuilderFieldsSet |= 0x80;
mDate = value;
@@ -1248,7 +1540,7 @@
* to encourage its reuse.
*/
@DataClass.Generated.Member
- public Builder setPattern(Pattern value) {
+ public @NonNull Builder setPattern(@NonNull Pattern value) {
checkNotUsed();
mBuilderFieldsSet |= 0x100;
mPattern = value;
@@ -1261,7 +1553,7 @@
* {@link Builder#addLinkAddresses2(LinkAddress) add} method is generated for convenience.
*/
@DataClass.Generated.Member
- public Builder setLinkAddresses2(List<LinkAddress> value) {
+ public @NonNull Builder setLinkAddresses2(@NonNull List<LinkAddress> value) {
checkNotUsed();
mBuilderFieldsSet |= 0x200;
mLinkAddresses2 = value;
@@ -1270,7 +1562,7 @@
/** @see #setLinkAddresses2 */
@DataClass.Generated.Member
- public Builder addLinkAddresses2(@NonNull LinkAddress value) {
+ public @NonNull Builder addLinkAddresses2(LinkAddress value) {
// You can refine this method's name by providing item's singular name, e.g.:
// @DataClass.PluralOf("item")) mItems = ...
@@ -1286,7 +1578,7 @@
* @see Builder#addLinkAddress(LinkAddress)
*/
@DataClass.Generated.Member
- public Builder setLinkAddresses(ArrayList<LinkAddress> value) {
+ public @NonNull Builder setLinkAddresses(@NonNull ArrayList<LinkAddress> value) {
checkNotUsed();
mBuilderFieldsSet |= 0x400;
mLinkAddresses = value;
@@ -1295,7 +1587,7 @@
/** @see #setLinkAddresses */
@DataClass.Generated.Member
- public Builder addLinkAddress(@NonNull LinkAddress value) {
+ public @NonNull Builder addLinkAddress(LinkAddress value) {
if (mLinkAddresses == null) setLinkAddresses(new ArrayList<>());
mLinkAddresses.add(value);
return this;
@@ -1308,7 +1600,7 @@
* @see Builder#setLinkAddresses4(LinkAddress...)
*/
@DataClass.Generated.Member
- public Builder setLinkAddresses4(@Nullable LinkAddress... value) {
+ public @NonNull Builder setLinkAddresses4(@Nullable LinkAddress... value) {
checkNotUsed();
mBuilderFieldsSet |= 0x800;
mLinkAddresses4 = value;
@@ -1316,30 +1608,6 @@
}
/**
- * 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.
*
@@ -1347,9 +1615,9 @@
* @see Builder#setStateName
*/
@DataClass.Generated.Member
- public Builder setStateName(@StateName String value) {
+ public @NonNull Builder setStateName(@StateName @NonNull String value) {
checkNotUsed();
- mBuilderFieldsSet |= 0x2000;
+ mBuilderFieldsSet |= 0x1000;
mStateName = value;
return this;
}
@@ -1358,9 +1626,9 @@
* Fields annotated with {@link IntDef} annotations also get a proper {@link #toString()} value.
*/
@DataClass.Generated.Member
- public Builder setFlags(@RequestFlags int value) {
+ public @NonNull Builder setFlags(@RequestFlags int value) {
checkNotUsed();
- mBuilderFieldsSet |= 0x4000;
+ mBuilderFieldsSet |= 0x2000;
mFlags = value;
return this;
}
@@ -1369,9 +1637,9 @@
* Above is true for both {@link IntDef#flag flags} and enum-like {@link IntDef}s
*/
@DataClass.Generated.Member
- public Builder setState(@State int value) {
+ public @NonNull Builder setState(@State int value) {
checkNotUsed();
- mBuilderFieldsSet |= 0x8000;
+ mBuilderFieldsSet |= 0x4000;
mState = value;
return this;
}
@@ -1380,9 +1648,9 @@
* Making a field public will suppress getter generation in favor of accessing it directly.
*/
@DataClass.Generated.Member
- public Builder setCharSeq(CharSequence value) {
+ public @NonNull Builder setCharSeq(@NonNull CharSequence value) {
checkNotUsed();
- mBuilderFieldsSet |= 0x10000;
+ mBuilderFieldsSet |= 0x8000;
charSeq = value;
return this;
}
@@ -1391,9 +1659,9 @@
* Final fields suppress generating a setter (when setters are requested).
*/
@DataClass.Generated.Member
- public Builder setLinkAddresses5(LinkAddress... value) {
+ public @NonNull Builder setLinkAddresses5(@Nullable LinkAddress... value) {
checkNotUsed();
- mBuilderFieldsSet |= 0x20000;
+ mBuilderFieldsSet |= 0x10000;
mLinkAddresses5 = value;
return this;
}
@@ -1411,9 +1679,9 @@
* @see #SampleDataClass
*/
@DataClass.Generated.Member
- public Builder setStringRes(@StringRes int value) {
+ public @NonNull Builder setStringRes(@StringRes int value) {
checkNotUsed();
- mBuilderFieldsSet |= 0x40000;
+ mBuilderFieldsSet |= 0x20000;
mStringRes = value;
return this;
}
@@ -1426,10 +1694,10 @@
* @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) {
+ public @NonNull Builder setDayOfWeek(@android.annotation.IntRange(from = 0, to = 6) int value) {
checkNotUsed();
- mBuilderFieldsSet |= 0x80000;
- mLimited = value;
+ mBuilderFieldsSet |= 0x40000;
+ mDayOfWeek = value;
return this;
}
@@ -1443,30 +1711,23 @@
* @see AnnotationValidations#validate(Class, Size, int, String, int)
*/
@DataClass.Generated.Member
- public Builder setCoords(@Size(2) @FloatRange(from = 0f) float... value) {
+ public @NonNull Builder setCoords(@Size(2) @NonNull @Each @FloatRange(from = 0f) float... value) {
checkNotUsed();
- mBuilderFieldsSet |= 0x100000;
+ mBuilderFieldsSet |= 0x80000;
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");
- }
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x100000; // Mark builder used
+
if ((mBuilderFieldsSet & 0x10) == 0) {
mName2 = "Bob";
}
if ((mBuilderFieldsSet & 0x20) == 0) {
- throw new IllegalStateException("Required field not set: name4");
+ mName4 = defaultName4();
}
if ((mBuilderFieldsSet & 0x40) == 0) {
mOtherParcelable = null;
@@ -1487,30 +1748,21 @@
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) {
+ if ((mBuilderFieldsSet & 0x8000) == 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 & 0x40000) == 0) {
+ mDayOfWeek = 3;
}
- if ((mBuilderFieldsSet & 0x100000) == 0) {
+ if ((mBuilderFieldsSet & 0x80000) == 0) {
mCoords = new float[] { 0f, 0f };
}
SampleDataClass o = new SampleDataClass(
@@ -1526,17 +1778,31 @@
mLinkAddresses2,
mLinkAddresses,
mLinkAddresses4,
- mActive,
mStateName,
mFlags,
mState,
charSeq,
mLinkAddresses5,
mStringRes,
- mLimited,
+ mDayOfWeek,
mCoords);
return o;
}
+
+ private void checkNotUsed() {
+ if ((mBuilderFieldsSet & 0x100000) != 0) {
+ throw new IllegalStateException(
+ "This Builder should not be reused. Use a new Builder instance instead");
+ }
+ }
}
+ @DataClass.Generated(
+ time = 1565048798524L,
+ codegenVersion = "1.0.0",
+ 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 @android.annotation.NonNull java.lang.String mName2\nprivate @android.annotation.NonNull java.lang.String mName4\nprivate @android.annotation.Nullable android.view.accessibility.AccessibilityNodeInfo mOtherParcelable\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.codegentest.MyDateParcelling.class) @android.annotation.NonNull java.util.Date mDate\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForPattern.class) @android.annotation.NonNull java.util.regex.Pattern mPattern\nprivate @android.annotation.NonNull java.util.List<android.net.LinkAddress> mLinkAddresses2\nprivate @com.android.internal.util.DataClass.PluralOf(\"linkAddress\") @android.annotation.NonNull java.util.ArrayList<android.net.LinkAddress> mLinkAddresses\nprivate @android.annotation.Nullable android.net.LinkAddress[] mLinkAddresses4\nprivate @com.android.codegentest.SampleDataClass.StateName @android.annotation.NonNull java.lang.String mStateName\nprivate @com.android.codegentest.SampleDataClass.RequestFlags int mFlags\nprivate @com.android.codegentest.SampleDataClass.State int mState\npublic @android.annotation.NonNull java.lang.CharSequence charSeq\nprivate final @android.annotation.Nullable 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=6L) int mDayOfWeek\nprivate @android.annotation.Size(2L) @android.annotation.NonNull @com.android.internal.util.DataClass.Each @android.annotation.FloatRange(from=0.0) float[] mCoords\nprivate static java.lang.String defaultName4()\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)\nclass SampleDataClass extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genConstructor=true, genEqualsHashCode=true, genToString=true, genForEachField=true, genSetters=true)")
+ @Deprecated
+ private void __metadata() {}
+
}
diff --git a/tests/Codegen/src/com/android/codegentest/SampleDataClassTest.java b/tests/Codegen/src/com/android/codegentest/SampleDataClassTest.java
index 71e85ab..6636207 100644
--- a/tests/Codegen/src/com/android/codegentest/SampleDataClassTest.java
+++ b/tests/Codegen/src/com/android/codegentest/SampleDataClassTest.java
@@ -49,7 +49,7 @@
private SampleDataClass mSpecimen = newBuilder().build();
private static SampleDataClass.Builder newBuilder() {
- return newIncompleteBuilder()
+ return newInvalidBuilder()
.setNum(42)
.setNum2(42)
.setNum4(42)
@@ -57,9 +57,8 @@
.setLinkAddresses5();
}
- private static SampleDataClass.Builder newIncompleteBuilder() {
- return new SampleDataClass.Builder()
- .markActive()
+ private static SampleDataClass.Builder newInvalidBuilder() {
+ return new SampleDataClass.Builder(1, 2, 3, "a", 0, null)
.setName("some parcelable")
.setFlags(SampleDataClass.FLAG_MANUAL_REQUEST);
}
@@ -91,7 +90,7 @@
public void testCustomParcelling_instanceIsCached() {
parcelAndUnparcel(mSpecimen, SampleDataClass.CREATOR);
parcelAndUnparcel(mSpecimen, SampleDataClass.CREATOR);
- assertEquals(1, DateParcelling.sInstanceCount.get());
+ assertEquals(1, MyDateParcelling.sInstanceCount.get());
}
@Test
@@ -149,8 +148,8 @@
}
@Test(expected = IllegalStateException.class)
- public void testBuilder_throwsWhenRequiredFieldMissing() {
- newIncompleteBuilder().build();
+ public void testBuilder_performsValidation() {
+ newInvalidBuilder().build();
}
@Test
@@ -205,6 +204,11 @@
assertSame(tmpStorage, tmpStorageAgain);
}
+ @Test(expected = IllegalStateException.class)
+ public void testCustomAnnotationValidation_isRun() {
+ newBuilder().setDayOfWeek(42).build();
+ }
+
private static <T extends Parcelable> T parcelAndUnparcel(
T original, Parcelable.Creator<T> creator) {
Parcel p = Parcel.obtain();
diff --git a/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java b/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java
new file mode 100644
index 0000000..d88035c
--- /dev/null
+++ b/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java
@@ -0,0 +1,186 @@
+/*
+ * 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.NonNull;
+import android.os.SystemClock;
+
+import com.android.internal.util.DataClass;
+
+import java.util.concurrent.TimeUnit;
+
+@DataClass(genBuilder = true)
+public class SampleWithCustomBuilder {
+
+ long delayAmount = 0;
+ @NonNull
+ TimeUnit delayUnit = TimeUnit.MILLISECONDS;
+
+ long creationTimestamp = SystemClock.uptimeMillis();
+
+ /**
+ * You can declare a class named {@code BaseBuilder} to have the generated builder extend from
+ * it instead.
+ *
+ * Same rules apply where defining a non-abstract method will suppress the generation of a
+ * method with the same signature.
+ *
+ * For abstract generatable methods, implementations are generated as normal, but original
+ * visibility is used, allowing you to hide methods.
+ *
+ * Here for example, we hide {@link #setDelayUnit} and {@link #setDelayAmount} from public API,
+ * replacing it with {@link #setDelay} instead.
+ */
+ // Suppress setter generation for a field that is not supposed to come from user input.
+ @DataClass.Suppress("setCreationTimestamp")
+ static abstract class BaseBuilder {
+
+ /**
+ * Hide methods by declaring them with reduced (package-private) visibility.
+ */
+ abstract Builder setDelayAmount(long value);
+
+ /**
+ * Alternatively, hide methods by using @hide, to hide them from public API only.
+ *
+ * @hide
+ */
+ public abstract Builder setDelayUnit(TimeUnit value);
+
+ /**
+ * Can provide additional method on the builder, e.g. as a replacement for the ones we've
+ * just hidden.
+ */
+ public Builder setDelay(long amount, TimeUnit unit) {
+ setDelayAmount(amount);
+ setDelayUnit(unit);
+ return (Builder) this;
+ }
+ }
+
+
+
+ // Code below generated by codegen v1.0.0.
+ //
+ // DO NOT MODIFY!
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java
+ //
+ // CHECKSTYLE:OFF Generated code
+
+ @DataClass.Generated.Member
+ /* package-private */ SampleWithCustomBuilder(
+ long delayAmount,
+ @NonNull TimeUnit delayUnit,
+ long creationTimestamp) {
+ this.delayAmount = delayAmount;
+ this.delayUnit = delayUnit;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, delayUnit);
+ this.creationTimestamp = creationTimestamp;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public long getDelayAmount() {
+ return delayAmount;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull TimeUnit getDelayUnit() {
+ return delayUnit;
+ }
+
+ @DataClass.Generated.Member
+ public long getCreationTimestamp() {
+ return creationTimestamp;
+ }
+
+ /**
+ * A builder for {@link SampleWithCustomBuilder}
+ */
+ @SuppressWarnings("WeakerAccess")
+ @DataClass.Generated.Member
+ public static class Builder extends BaseBuilder {
+
+ private long delayAmount;
+ private @NonNull TimeUnit delayUnit;
+ private long creationTimestamp;
+
+ private long mBuilderFieldsSet = 0L;
+
+ public Builder() {
+ }
+
+ @DataClass.Generated.Member
+ @Override
+ @NonNull Builder setDelayAmount(long value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x1;
+ delayAmount = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ @Override
+ public @NonNull Builder setDelayUnit(@NonNull TimeUnit value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x2;
+ delayUnit = value;
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ public SampleWithCustomBuilder build() {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x8; // Mark builder used
+
+ if ((mBuilderFieldsSet & 0x1) == 0) {
+ delayAmount = 0;
+ }
+ if ((mBuilderFieldsSet & 0x2) == 0) {
+ delayUnit = TimeUnit.MILLISECONDS;
+ }
+ if ((mBuilderFieldsSet & 0x4) == 0) {
+ creationTimestamp = SystemClock.uptimeMillis();
+ }
+ SampleWithCustomBuilder o = new SampleWithCustomBuilder(
+ delayAmount,
+ delayUnit,
+ creationTimestamp);
+ return o;
+ }
+
+ private void checkNotUsed() {
+ if ((mBuilderFieldsSet & 0x8) != 0) {
+ throw new IllegalStateException(
+ "This Builder should not be reused. Use a new Builder instance instead");
+ }
+ }
+ }
+
+ @DataClass.Generated(
+ time = 1565048799396L,
+ codegenVersion = "1.0.0",
+ sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java",
+ inputSignatures = " long delayAmount\n @android.annotation.NonNull java.util.concurrent.TimeUnit delayUnit\n long creationTimestamp\nclass SampleWithCustomBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genBuilder=true)\nabstract com.android.codegentest.SampleWithCustomBuilder.Builder setDelayAmount(long)\npublic abstract com.android.codegentest.SampleWithCustomBuilder.Builder setDelayUnit(java.util.concurrent.TimeUnit)\npublic com.android.codegentest.SampleWithCustomBuilder.Builder setDelay(long,java.util.concurrent.TimeUnit)\nclass BaseBuilder extends java.lang.Object implements []")
+ @Deprecated
+ private void __metadata() {}
+
+}
diff --git a/tools/codegen/src/com/android/codegen/ClassInfo.kt b/tools/codegen/src/com/android/codegen/ClassInfo.kt
index 7ee79f6..578fb28 100644
--- a/tools/codegen/src/com/android/codegen/ClassInfo.kt
+++ b/tools/codegen/src/com/android/codegen/ClassInfo.kt
@@ -18,13 +18,7 @@
e)
}
val classAst = fileAst.types[0] as ClassOrInterfaceDeclaration
-
- fun hasMethod(name: String, vararg argTypes: String): Boolean {
- return classAst.methods.any {
- it.name.asString() == name &&
- it.parameters.map { it.type.asString() } == argTypes.toList()
- }
- }
+ val nestedClasses = classAst.members.filterIsInstance<ClassOrInterfaceDeclaration>()
val superInterfaces = (fileAst.types[0] as ClassOrInterfaceDeclaration)
.implementedTypes.map { it.asString() }
@@ -42,8 +36,4 @@
.filterNot { it.isTransient || it.isStatic }
.mapIndexed { i, node -> FieldInfo(index = i, fieldAst = node, classInfo = this) }
.apply { lastOrNull()?.isLast = true }
- val lazyTransientFields = classAst.fields
- .filter { it.isTransient && !it.isStatic }
- .mapIndexed { i, node -> FieldInfo(index = i, fieldAst = node, classInfo = this) }
- .filter { hasMethod("lazyInit${it.NameUpperCamel}") }
}
\ No newline at end of file
diff --git a/tools/codegen/src/com/android/codegen/ClassPrinter.kt b/tools/codegen/src/com/android/codegen/ClassPrinter.kt
index 33256b7..f1645ea 100644
--- a/tools/codegen/src/com/android/codegen/ClassPrinter.kt
+++ b/tools/codegen/src/com/android/codegen/ClassPrinter.kt
@@ -1,9 +1,9 @@
package com.android.codegen
+import com.github.javaparser.ast.Modifier
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration
import com.github.javaparser.ast.body.TypeDeclaration
-import com.github.javaparser.ast.expr.BooleanLiteralExpr
-import com.github.javaparser.ast.expr.NormalAnnotationExpr
+import com.github.javaparser.ast.expr.*
import com.github.javaparser.ast.type.ClassOrInterfaceType
/**
@@ -32,10 +32,31 @@
val PluralOf by lazy { classRef("com.android.internal.util.DataClass.PluralOf") }
val Each by lazy { classRef("com.android.internal.util.DataClass.Each") }
val DataClassGenerated by lazy { classRef("com.android.internal.util.DataClass.Generated") }
+ val DataClassSuppressConstDefs by lazy { classRef("com.android.internal.util.DataClass.SuppressConstDefsGeneration") }
+ val DataClassSuppress by lazy { classRef("com.android.internal.util.DataClass.Suppress") }
val GeneratedMember by lazy { classRef("com.android.internal.util.DataClass.Generated.Member") }
val Parcelling by lazy { classRef("com.android.internal.util.Parcelling") }
+ val Parcelable by lazy { classRef("android.os.Parcelable") }
val UnsupportedAppUsage by lazy { classRef("android.annotation.UnsupportedAppUsage") }
+ init {
+ val fieldsWithMissingNullablity = fields.filter { field ->
+ !field.isPrimitive
+ && Modifier.TRANSIENT !in field.fieldAst.modifiers
+ && "@$Nullable" !in field.annotations
+ && "@$NonNull" !in field.annotations
+ }
+ if (fieldsWithMissingNullablity.isNotEmpty()) {
+ abort("Non-primitive fields must have @$Nullable or @$NonNull annotation.\n" +
+ "Missing nullability annotations on: "
+ + fieldsWithMissingNullablity.joinToString(", ") { it.name })
+ }
+
+ if (!classAst.isFinal &&
+ classAst.extendedTypes.any { it.nameAsString == Parcelable }) {
+ abort("Parcelable classes must be final")
+ }
+ }
/**
* Optionally shortens a class reference if there's a corresponding import present
@@ -54,7 +75,7 @@
return simpleName
} else {
val outerClass = pkg.substringAfterLast(".", "")
- if (outerClass.firstOrNull()?.isUpperCase() ?: false) {
+ if (outerClass.firstOrNull()?.isUpperCase() == true) {
return classRef(pkg) + "." + simpleName
}
}
@@ -89,7 +110,9 @@
?.toMap()
?: emptyMap()
- val internalAnnotations = setOf(ParcelWith, DataClassEnum, PluralOf, Each, UnsupportedAppUsage)
+ val internalAnnotations = setOf(ParcelWith, DataClassEnum, PluralOf, UnsupportedAppUsage,
+ DataClassSuppressConstDefs)
+ val knownNonValidationAnnotations = internalAnnotations + Each + Nullable
/**
* @return whether the given feature is enabled
@@ -109,7 +132,9 @@
return when (this) {
FeatureFlag.SETTERS ->
!FeatureFlag.CONSTRUCTOR() && !FeatureFlag.BUILDER() && fields.any { !it.isFinal }
- FeatureFlag.BUILDER -> cliArgs.contains(FLAG_BUILDER_PROTECTED_SETTERS) || onByDefault
+ FeatureFlag.BUILDER -> cliArgs.contains(FLAG_BUILDER_PROTECTED_SETTERS)
+ || fields.any { it.hasDefault }
+ || onByDefault
FeatureFlag.CONSTRUCTOR -> !FeatureFlag.BUILDER()
FeatureFlag.PARCELABLE -> "Parcelable" in superInterfaces
FeatureFlag.AIDL -> FeatureFlag.PARCELABLE()
@@ -287,6 +312,48 @@
var BuilderClass = CANONICAL_BUILDER_CLASS
var BuilderType = BuilderClass + genericArgs
+ val customBaseBuilderAst: ClassOrInterfaceDeclaration? by lazy {
+ nestedClasses.find { it.nameAsString == BASE_BUILDER_CLASS }
+ }
+
+ val suppressedMembers by lazy {
+ getSuppressedMembers(classAst)
+ }
+ val builderSuppressedMembers by lazy {
+ getSuppressedMembers(customBaseBuilderAst)
+ }
+
+ private fun getSuppressedMembers(clazz: ClassOrInterfaceDeclaration?): List<String> {
+ return clazz
+ ?.annotations
+ ?.find { it.nameAsString == DataClassSuppress }
+ ?.as_<SingleMemberAnnotationExpr>()
+ ?.memberValue
+ ?.run {
+ when (this) {
+ is ArrayInitializerExpr -> values.map { it.asLiteralStringValueExpr().value }
+ is StringLiteralExpr -> listOf(value)
+ else -> abort("Can't parse annotation arg: $this")
+ }
+ }
+ ?: emptyList()
+ }
+
+ fun isMethodGenerationSuppressed(name: String, vararg argTypes: String): Boolean {
+ return name in suppressedMembers || hasMethod(name, *argTypes)
+ }
+
+ fun hasMethod(name: String, vararg argTypes: String): Boolean {
+ return classAst.methods.any {
+ it.name.asString() == name &&
+ it.parameters.map { it.type.asString() } == argTypes.toList()
+ }
+ }
+
+ val lazyTransientFields = classAst.fields
+ .filter { it.isTransient && !it.isStatic }
+ .mapIndexed { i, node -> FieldInfo(index = i, fieldAst = node, classInfo = this) }
+ .filter { hasMethod("lazyInit${it.NameUpperCamel}") }
init {
val builderFactoryOverride = classAst.methods.find {
@@ -301,7 +368,7 @@
it.nameAsString == CANONICAL_BUILDER_CLASS
}
if (builderExtension != null) {
- BuilderClass = GENERATED_BUILDER_CLASS
+ BuilderClass = BASE_BUILDER_CLASS
val tp = (builderExtension as ClassOrInterfaceDeclaration).typeParameters
BuilderType = if (tp.isEmpty()) BuilderClass
else "$BuilderClass<${tp.map { it.nameAsString }.joinToString(", ")}>"
diff --git a/tools/codegen/src/com/android/codegen/FieldInfo.kt b/tools/codegen/src/com/android/codegen/FieldInfo.kt
index f326fd5..74e7948 100644
--- a/tools/codegen/src/com/android/codegen/FieldInfo.kt
+++ b/tools/codegen/src/com/android/codegen/FieldInfo.kt
@@ -9,7 +9,6 @@
import com.github.javaparser.ast.type.ArrayType
import com.github.javaparser.ast.type.ClassOrInterfaceType
import com.github.javaparser.javadoc.Javadoc
-import java.lang.Long
data class FieldInfo(
val index: Int,
@@ -85,7 +84,7 @@
variableAst.initializer.orElse(null)?.let { return it }
classInfo.classAst.methods.find {
it.nameAsString == "default$NameUpperCamel" && it.parameters.isEmpty()
- }?.run { "$nameAsString()" }?.let { return it }
+ }?.run { return "$nameAsString()" }
if (FieldClass == "List") return "${classPrinter.memberRef("java.util.Collections.emptyList")}()"
return null
}
@@ -95,7 +94,7 @@
// Generic args
val isArray = Type.endsWith("[]")
val isList = FieldClass == "List" || FieldClass == "ArrayList"
- val fieldBit = "0x${Long.toHexString(1L shl index)}"
+ val fieldBit = bitAtExpr(index)
var isLast = false
val isFinal = fieldAst.isFinal
val fieldTypeGenegicArgs = when (typeAst) {
@@ -143,8 +142,10 @@
}
val annotationsAndType by lazy { (annotationsNoInternal + Type).joinToString(" ") }
val sParcelling by lazy { customParcellingClass?.let { "sParcellingFor$NameUpperCamel" } }
+
+ val SetterParamType = if (isArray) "$FieldInnerType..." else Type
val annotatedTypeForSetterParam by lazy {
- (annotationsNoInternal + if (isArray) "$FieldInnerType..." else Type).joinToString(" ")
+ (annotationsNoInternal + SetterParamType).joinToString(" ")
}
// Utilities
diff --git a/tools/codegen/src/com/android/codegen/Generators.kt b/tools/codegen/src/com/android/codegen/Generators.kt
index ab64f4e..c6e0a06 100644
--- a/tools/codegen/src/com/android/codegen/Generators.kt
+++ b/tools/codegen/src/com/android/codegen/Generators.kt
@@ -1,6 +1,7 @@
package com.android.codegen
import com.github.javaparser.ast.body.FieldDeclaration
+import com.github.javaparser.ast.body.MethodDeclaration
import com.github.javaparser.ast.body.VariableDeclarator
import com.github.javaparser.ast.expr.*
import java.io.File
@@ -16,7 +17,7 @@
val isLiteral = initializer is LiteralExpr
|| (initializer is UnaryExpr && initializer.expression is LiteralExpr)
isLiteral && variable.type.asString() in listOf("int", "String")
- }
+ } && it.annotations.none { it.nameAsString == DataClassSuppressConstDefs }
}.flatMap { field -> field.variables.map { it to field } }
val intConsts = consts.filter { it.first.type.asString() == "int" }
val strConsts = consts.filter { it.first.type.asString() == "String" }
@@ -131,7 +132,7 @@
fun ClassPrinter.generateWithers() {
fields.forEachApply {
val metodName = "with$NameUpperCamel"
- if (!hasMethod(metodName, Type)) {
+ if (!isMethodGenerationSuppressed(metodName, Type)) {
generateFieldJavadoc(forceHide = FeatureFlag.WITHERS.hidden)
"""@$NonNull
$GENERATED_MEMBER_HEADER
@@ -171,7 +172,7 @@
* ```
*/
fun ClassPrinter.generateBuildUpon() {
- if (hasMethod("buildUpon")) return
+ if (isMethodGenerationSuppressed("buildUpon")) return
+"/**"
+" * Provides an instance of {@link $BuilderClass} with state corresponding to this instance."
@@ -195,7 +196,15 @@
val constructorVisibility = if (BuilderClass == CANONICAL_BUILDER_CLASS)
"public" else "/* package-*/"
- val OneTimeUseBuilder = classRef("android.provider.OneTimeUseBuilder")
+ val providedSubclassAst = nestedClasses.find {
+ it.extendedTypes.any { it.nameAsString == BASE_BUILDER_CLASS }
+ }
+
+ val BuilderSupertype = if (customBaseBuilderAst != null) {
+ customBaseBuilderAst!!.nameAsString
+ } else {
+ "Object"
+ }
+"/**"
+" * A builder for {@link $ClassName}"
@@ -203,104 +212,155 @@
+" */"
+"@SuppressWarnings(\"WeakerAccess\")"
+GENERATED_MEMBER_HEADER
- "public static class $BuilderClass$genericArgs" {
- +"extends $OneTimeUseBuilder<$ClassType>"
+ !"public static class $BuilderClass$genericArgs"
+ if (BuilderSupertype != "Object") {
+ appendSameLine(" extends $BuilderSupertype")
}
" {" {
+""
fields.forEachApply {
- +"protected $annotationsAndType $name;"
+ +"private $annotationsAndType $name;"
}
+""
- +"protected long mBuilderFieldsSet = 0L;"
+ +"private long mBuilderFieldsSet = 0L;"
+""
- +"$constructorVisibility $BuilderClass() {};"
- +""
+
+ val requiredFields = fields.filter { !it.hasDefault }
+
+ generateConstructorJavadoc(
+ fields = requiredFields,
+ ClassName = BuilderClass,
+ hidden = false)
+ "$constructorVisibility $BuilderClass(" {
+ requiredFields.forEachLastAware { field, isLast ->
+ +"${field.annotationsAndType} ${field._name}${if_(!isLast, ",")}"
+ }
+ }; " {" {
+ requiredFields.forEachApply {
+ generateSetFrom(_name)
+ }
+ }
generateBuilderSetters(setterVisibility)
generateBuilderBuild()
+ "private void checkNotUsed() {" {
+ "if ((mBuilderFieldsSet & ${bitAtExpr(fields.size)}) != 0)" {
+ "throw new IllegalStateException(" {
+ +"\"This Builder should not be reused. Use a new Builder instance instead\""
+ }
+ +";"
+ }
+ }
+
rmEmptyLine()
}
}
+private fun ClassPrinter.generateBuilderMethod(
+ defVisibility: String,
+ name: String,
+ ParamAnnotations: String? = null,
+ paramTypes: List<String>,
+ paramNames: List<String> = listOf("value"),
+ genJavadoc: ClassPrinter.() -> Unit,
+ genBody: ClassPrinter.() -> Unit) {
+
+ val providedMethod = customBaseBuilderAst?.members?.find {
+ it is MethodDeclaration
+ && it.nameAsString == name
+ && it.parameters.map { it.typeAsString } == paramTypes.toTypedArray().toList()
+ } as? MethodDeclaration
+
+ if ((providedMethod == null || providedMethod.isAbstract)
+ && name !in builderSuppressedMembers) {
+ val visibility = providedMethod?.visibility?.asString() ?: defVisibility
+ val ReturnType = providedMethod?.typeAsString ?: CANONICAL_BUILDER_CLASS
+ val Annotations = providedMethod?.annotations?.joinToString("\n")
+
+ genJavadoc()
+ +GENERATED_MEMBER_HEADER
+ if (providedMethod?.isAbstract == true) +"@Override"
+ if (!Annotations.isNullOrEmpty()) +Annotations
+ "$visibility @$NonNull $ReturnType $name(${if_(!ParamAnnotations.isNullOrEmpty(), "$ParamAnnotations ")}${
+ paramTypes.zip(paramNames).joinToString(", ") { (Type, paramName) -> "$Type $paramName" }
+ })" {
+ genBody()
+ }
+ }
+}
+
private fun ClassPrinter.generateBuilderSetters(visibility: String) {
fields.forEachApply {
val maybeCast =
if_(BuilderClass != CANONICAL_BUILDER_CLASS, " ($CANONICAL_BUILDER_CLASS)")
- generateFieldJavadoc()
- +GENERATED_MEMBER_HEADER
- "$visibility $CANONICAL_BUILDER_CLASS set$NameUpperCamel($annotatedTypeForSetterParam value)" {
+ val setterName = "set$NameUpperCamel"
+
+ generateBuilderMethod(
+ name = setterName,
+ defVisibility = visibility,
+ ParamAnnotations = annotationsNoInternal.joinToString(" "),
+ paramTypes = listOf(SetterParamType),
+ genJavadoc = { generateFieldJavadoc() }) {
+"checkNotUsed();"
+"mBuilderFieldsSet |= $fieldBit;"
+"$name = value;"
+"return$maybeCast this;"
}
+ val javadocSeeSetter = "/** @see #$setterName */"
+ val adderName = "add$SingularName"
- val javadocSeeSetter = "/** @see #set$NameUpperCamel */"
val singularNameCustomizationHint = if (SingularNameOrNull == null) {
"// You can refine this method's name by providing item's singular name, e.g.:\n" +
"// @DataClass.PluralOf(\"item\")) mItems = ...\n\n"
} else ""
- if (isList && FieldInnerType != null) {
- +javadocSeeSetter
- +GENERATED_MEMBER_HEADER
- "$visibility $CANONICAL_BUILDER_CLASS add$SingularName(@$NonNull $FieldInnerType value)" {
+ if (isList && FieldInnerType != null) {
+ generateBuilderMethod(
+ name = adderName,
+ defVisibility = visibility,
+ paramTypes = listOf(FieldInnerType),
+ genJavadoc = { +javadocSeeSetter }) {
+
!singularNameCustomizationHint
- +"if ($name == null) set$NameUpperCamel(new $ArrayList<>());"
+ +"if ($name == null) $setterName(new $ArrayList<>());"
+"$name.add(value);"
+"return$maybeCast this;"
}
}
if (Type.contains("Map<")) {
- val (Key, Value) = fieldTypeGenegicArgs
-
- +javadocSeeSetter
- +GENERATED_MEMBER_HEADER
- "$visibility $CANONICAL_BUILDER_CLASS add$SingularName($Key key, $Value value)" {
+ generateBuilderMethod(
+ name = adderName,
+ defVisibility = visibility,
+ paramTypes = fieldTypeGenegicArgs,
+ paramNames = listOf("key", "value"),
+ genJavadoc = { +javadocSeeSetter }) {
!singularNameCustomizationHint
- +"if ($name == null) set$NameUpperCamel(new $LinkedHashMap());"
+ +"if ($name == null) $setterName(new $LinkedHashMap());"
+"$name.put(key, value);"
+"return$maybeCast this;"
}
}
-
- if (Type == "boolean") {
- +javadocSeeSetter
- +GENERATED_MEMBER_HEADER
- "$visibility $CANONICAL_BUILDER_CLASS mark$NameUpperCamel()" {
- +"return set$NameUpperCamel(true);"
- }
-
- +javadocSeeSetter
- +GENERATED_MEMBER_HEADER
- "$visibility $CANONICAL_BUILDER_CLASS markNot$NameUpperCamel()" {
- +"return set$NameUpperCamel(false);"
- }
- }
}
}
private fun ClassPrinter.generateBuilderBuild() {
+"/** Builds the instance. This builder should not be touched after calling this! */"
"public $ClassType build()" {
- +"markUsed();"
+ +"checkNotUsed();"
+ +"mBuilderFieldsSet |= ${bitAtExpr(fields.size)}; // Mark builder used"
+ +""
fields.forEachApply {
- if (!isNullable || hasDefault) {
+ if (hasDefault) {
"if ((mBuilderFieldsSet & $fieldBit) == 0)" {
- if (!isNullable && !hasDefault) {
- +"throw new IllegalStateException(\"Required field not set: $nameLowerCamel\");"
- } else {
- +"$name = $defaultExpr;"
- }
+ +"$name = $defaultExpr;"
}
}
}
@@ -348,7 +408,7 @@
}
val Parcel = classRef("android.os.Parcel")
- if (!hasMethod("writeToParcel", Parcel, "int")) {
+ if (!isMethodGenerationSuppressed("writeToParcel", Parcel, "int")) {
+"@Override"
+GENERATED_MEMBER_HEADER
"public void writeToParcel($Parcel dest, int flags)" {
@@ -390,7 +450,7 @@
}
}
- if (!hasMethod("describeContents")) {
+ if (!isMethodGenerationSuppressed("describeContents")) {
+"@Override"
+GENERATED_MEMBER_HEADER
+"public int describeContents() { return 0; }"
@@ -442,8 +502,6 @@
FieldClass.endsWith("Map") -> "new $LinkedHashMap<>()"
FieldClass == "List" || FieldClass == "ArrayList" ->
"new ${classRef("java.util.ArrayList")}<>()"
-// isArray && FieldInnerType in (PRIMITIVE_TYPES + "String") ->
-// "new $FieldInnerType[in.readInt()]"
else -> ""
}
val passContainer = containerInitExpr.isNotEmpty()
@@ -519,7 +577,7 @@
}
fun ClassPrinter.generateEqualsHashcode() {
- if (!hasMethod("equals", "Object")) {
+ if (!isMethodGenerationSuppressed("equals", "Object")) {
+"@Override"
+GENERATED_MEMBER_HEADER
"public boolean equals(Object o)" {
@@ -546,7 +604,7 @@
}
}
- if (!hasMethod("hashCode")) {
+ if (!isMethodGenerationSuppressed("hashCode")) {
+"@Override"
+GENERATED_MEMBER_HEADER
"public int hashCode()" {
@@ -572,7 +630,7 @@
//TODO support IntDef flags?
fun ClassPrinter.generateToString() {
- if (!hasMethod("toString")) {
+ if (!isMethodGenerationSuppressed("toString")) {
+"@Override"
+GENERATED_MEMBER_HEADER
"public String toString()" {
@@ -599,7 +657,7 @@
fun ClassPrinter.generateSetters() {
fields.forEachApply {
- if (!hasMethod("set$NameUpperCamel", Type)
+ if (!isMethodGenerationSuppressed("set$NameUpperCamel", Type)
&& !fieldAst.isPublic
&& !isFinal) {
@@ -618,7 +676,7 @@
val methodPrefix = if (Type == "boolean") "is" else "get"
val methodName = methodPrefix + NameUpperCamel
- if (!hasMethod(methodName) && !fieldAst.isPublic) {
+ if (!isMethodGenerationSuppressed(methodName) && !fieldAst.isPublic) {
generateFieldJavadoc(forceHide = FeatureFlag.GETTERS.hidden)
+GENERATED_MEMBER_HEADER
@@ -662,23 +720,8 @@
}
fun FieldInfo.generateSetFrom(source: String) = classPrinter {
- !"$name = "
- if (Type in PRIMITIVE_TYPES || mayBeNull) {
- +"$source;"
- } else if (defaultExpr != null) {
- "$source != null" {
- +"? $source"
- +": $defaultExpr;"
- }
- } else {
- val checkNotNull = memberRef("com.android.internal.util.Preconditions.checkNotNull")
- +"$checkNotNull($source);"
- }
- if (isNonEmpty) {
- "if ($isEmptyExpr)" {
- +"throw new IllegalArgumentException(\"$nameLowerCamel cannot be empty\");"
- }
- }
+ +"$name = $source;"
+ generateFieldValidation(field = this@generateSetFrom)
}
fun ClassPrinter.generateConstructor(visibility: String = "public") {
@@ -697,15 +740,18 @@
generateSetFrom(nameLowerCamel)
}
- generateStateValidation()
-
generateOnConstructedCallback()
}
}
-private fun ClassPrinter.generateConstructorJavadoc() {
+private fun ClassPrinter.generateConstructorJavadoc(
+ fields: List<FieldInfo> = this.fields,
+ ClassName: String = this.ClassName,
+ hidden: Boolean = FeatureFlag.CONSTRUCTOR.hidden) {
if (fields.all { it.javadoc == null } && !FeatureFlag.CONSTRUCTOR.hidden) return
+"/**"
+ +" * Creates a new $ClassName."
+ +" *"
fields.filter { it.javadoc != null }.forEachApply {
javadocTextNoAnnotationLines?.apply {
+" * @param $nameLowerCamel"
@@ -718,87 +764,97 @@
+" */"
}
-private fun ClassPrinter.generateStateValidation() {
- val Size = classRef("android.annotation.Size")
- val knownNonValidationAnnotations = internalAnnotations + Nullable
-
- val validate = memberRef("com.android.internal.util.AnnotationValidations.validate")
- fun appendValidateCall(annotation: AnnotationExpr, valueToValidate: String) {
- "$validate(" {
- !"${annotation.nameAsString}.class, null, $valueToValidate"
- val params = when (annotation) {
- is MarkerAnnotationExpr -> emptyMap()
- is SingleMemberAnnotationExpr -> mapOf("value" to annotation.memberValue)
- is NormalAnnotationExpr ->
- annotation.pairs.map { it.name.asString() to it.value }.toMap()
- else -> throw IllegalStateException()
- }
- params.forEach { name, value ->
- !",\n\"$name\", $value"
+private fun ClassPrinter.appendLinesWithContinuationIndent(text: String) {
+ val lines = text.lines()
+ if (lines.isNotEmpty()) {
+ !lines[0]
+ }
+ if (lines.size >= 2) {
+ "" {
+ lines.drop(1).forEach {
+ +it
}
}
- +";"
}
+}
- fields.forEachApply {
- if (intOrStringDef != null) {
- if (intOrStringDef!!.type == ConstDef.Type.INT_FLAGS) {
- +""
- +"//noinspection PointlessBitwiseExpression"
- "$Preconditions.checkFlagsArgument(" {
- "$name, 0" {
- intOrStringDef!!.CONST_NAMES.forEach {
- +"| $it"
+private fun ClassPrinter.generateFieldValidation(field: FieldInfo) = field.run {
+ if (isNonEmpty) {
+ "if ($isEmptyExpr)" {
+ +"throw new IllegalArgumentException(\"$nameLowerCamel cannot be empty\");"
+ }
+ }
+ if (intOrStringDef != null) {
+ if (intOrStringDef!!.type == ConstDef.Type.INT_FLAGS) {
+ +""
+ "$Preconditions.checkFlagsArgument(" {
+ +"$name, "
+ appendLinesWithContinuationIndent(intOrStringDef!!.CONST_NAMES.joinToString("\n| "))
+ }
+ +";"
+ } else {
+ +""
+ !"if ("
+ appendLinesWithContinuationIndent(intOrStringDef!!.CONST_NAMES.joinToString("\n&& ") {
+ "!(${isEqualToExpr(it)})"
+ })
+ rmEmptyLine(); ") {" {
+ "throw new ${classRef<IllegalArgumentException>()}(" {
+ "\"$nameLowerCamel was \" + $internalGetter + \" but must be one of: \"" {
+
+ intOrStringDef!!.CONST_NAMES.forEachLastAware { CONST_NAME, isLast ->
+ +"""+ "$CONST_NAME(" + $CONST_NAME + ")${if_(!isLast, ", ")}""""
}
}
}
+";"
- } else {
- +""
- +"//noinspection PointlessBooleanExpression"
- "if (true" {
- intOrStringDef!!.CONST_NAMES.forEach { CONST_NAME ->
- +"&& !(${isEqualToExpr(CONST_NAME)})"
- }
- }; rmEmptyLine(); ") {" {
- "throw new ${classRef<IllegalArgumentException>()}(" {
- "\"$nameLowerCamel was \" + $internalGetter + \" but must be one of: \"" {
-
- intOrStringDef!!.CONST_NAMES.forEachLastAware { CONST_NAME, isLast ->
- +"""+ "$CONST_NAME(" + $CONST_NAME + ")${if_(!isLast, ", ")}""""
- }
- }
- }
- +";"
- }
- }
- }
-
- val eachLine = fieldAst.annotations.find { it.nameAsString == Each }?.range?.orElse(null)?.end?.line
- val perElementValidations = if (eachLine == null) emptyList() else fieldAst.annotations.filter {
- it.nameAsString != Each &&
- it.range.orElse(null)?.begin?.line?.let { it >= eachLine } ?: false
- }
-
- fieldAst.annotations.filterNot {
- it.nameAsString == intOrStringDef?.AnnotationName
- || it.nameAsString in knownNonValidationAnnotations
- || it in perElementValidations
- }.forEach { annotation ->
- appendValidateCall(annotation,
- valueToValidate = if (annotation.nameAsString == Size) sizeExpr else name)
- }
-
- if (perElementValidations.isNotEmpty()) {
- +"int ${nameLowerCamel}Size = $sizeExpr;"
- "for (int i = 0; i < ${nameLowerCamel}Size; i++) {" {
- perElementValidations.forEach { annotation ->
- appendValidateCall(annotation,
- valueToValidate = elemAtIndexExpr("i"))
- }
}
}
}
+
+ val eachLine = fieldAst.annotations.find { it.nameAsString == Each }?.range?.orElse(null)?.end?.line
+ val perElementValidations = if (eachLine == null) emptyList() else fieldAst.annotations.filter {
+ it.nameAsString != Each &&
+ it.range.orElse(null)?.begin?.line?.let { it >= eachLine } ?: false
+ }
+
+ val Size = classRef("android.annotation.Size")
+ fieldAst.annotations.filterNot {
+ it.nameAsString == intOrStringDef?.AnnotationName
+ || it.nameAsString in knownNonValidationAnnotations
+ || it in perElementValidations
+ }.forEach { annotation ->
+ appendValidateCall(annotation,
+ valueToValidate = if (annotation.nameAsString == Size) sizeExpr else name)
+ }
+
+ if (perElementValidations.isNotEmpty()) {
+ +"int ${nameLowerCamel}Size = $sizeExpr;"
+ "for (int i = 0; i < ${nameLowerCamel}Size; i++) {" {
+ perElementValidations.forEach { annotation ->
+ appendValidateCall(annotation,
+ valueToValidate = elemAtIndexExpr("i"))
+ }
+ }
+ }
+}
+
+fun ClassPrinter.appendValidateCall(annotation: AnnotationExpr, valueToValidate: String) {
+ val validate = memberRef("com.android.internal.util.AnnotationValidations.validate")
+ "$validate(" {
+ !"${annotation.nameAsString}.class, null, $valueToValidate"
+ val params = when (annotation) {
+ is MarkerAnnotationExpr -> emptyMap()
+ is SingleMemberAnnotationExpr -> mapOf("value" to annotation.memberValue)
+ is NormalAnnotationExpr ->
+ annotation.pairs.map { it.name.asString() to it.value }.toMap()
+ else -> throw IllegalStateException()
+ }
+ params.forEach { name, value ->
+ !",\n\"$name\", $value"
+ }
+ }
+ +";"
}
private fun ClassPrinter.generateOnConstructedCallback(prefix: String = "") {
@@ -845,3 +901,15 @@
}
}
}
+
+fun ClassPrinter.generateMetadata(file: File) {
+ "@$DataClassGenerated(" {
+ +"time = ${System.currentTimeMillis()}L,"
+ +"codegenVersion = \"$CODEGEN_VERSION\","
+ +"sourceFile = \"${file.relativeTo(File(System.getenv("ANDROID_BUILD_TOP")))}\","
+ +"inputSignatures = \"${getInputSignatures().joinToString("\\n")}\""
+ }
+ +""
+ +"@Deprecated"
+ +"private void __metadata() {}\n"
+}
\ No newline at end of file
diff --git a/tools/codegen/src/com/android/codegen/InputSignaturesComputation.kt b/tools/codegen/src/com/android/codegen/InputSignaturesComputation.kt
index d1dc88f..1e7a267 100644
--- a/tools/codegen/src/com/android/codegen/InputSignaturesComputation.kt
+++ b/tools/codegen/src/com/android/codegen/InputSignaturesComputation.kt
@@ -1,6 +1,6 @@
package com.android.codegen
-import com.github.javaparser.ast.body.TypeDeclaration
+import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration
import com.github.javaparser.ast.expr.*
import com.github.javaparser.ast.nodeTypes.NodeWithAnnotations
import com.github.javaparser.ast.type.ClassOrInterfaceType
@@ -8,9 +8,17 @@
fun ClassPrinter.getInputSignatures(): List<String> {
+ return generateInputSignaturesForClass(classAst) +
+ annotationToString(classAst.annotations.find { it.nameAsString == DataClass }) +
+ generateInputSignaturesForClass(customBaseBuilderAst)
+}
+
+private fun ClassPrinter.generateInputSignaturesForClass(classAst: ClassOrInterfaceDeclaration?): List<String> {
+ if (classAst == null) return emptyList()
+
return classAst.fields.map { fieldAst ->
buildString {
- append(fieldAst.modifiers.joinToString(" ") {it.asString()})
+ append(fieldAst.modifiers.joinToString(" ") { it.asString() })
append(" ")
append(annotationsToString(fieldAst))
append(" ")
@@ -20,7 +28,7 @@
}
} + classAst.methods.map { methodAst ->
buildString {
- append(methodAst.modifiers.joinToString(" ") {it.asString()})
+ append(methodAst.modifiers.joinToString(" ") { it.asString() })
append(" ")
append(annotationsToString(methodAst))
append(" ")
@@ -28,19 +36,26 @@
append(" ")
append(methodAst.nameAsString)
append("(")
- append(methodAst.parameters.joinToString(",") {getFullClassName(it.type)})
+ append(methodAst.parameters.joinToString(",") { getFullClassName(it.type) })
append(")")
}
- }
+ } + ("class ${classAst.nameAsString}" +
+ " extends ${classAst.extendedTypes.map { getFullClassName(it) }.ifEmpty { listOf("java.lang.Object") }.joinToString(", ")}" +
+ " implements [${classAst.implementedTypes.joinToString(", ") { getFullClassName(it) }}]")
}
private fun ClassPrinter.annotationsToString(annotatedAst: NodeWithAnnotations<*>): String {
- return annotatedAst.annotations.joinToString(" ") {
- annotationToString(it)
- }
+ return annotatedAst
+ .annotations
+ .groupBy { it.nameAsString } // dedupe annotations by name (javaparser bug?)
+ .values
+ .joinToString(" ") {
+ annotationToString(it[0])
+ }
}
-private fun ClassPrinter.annotationToString(ann: AnnotationExpr): String {
+private fun ClassPrinter.annotationToString(ann: AnnotationExpr?): String {
+ if (ann == null) return ""
return buildString {
append("@")
append(getFullClassName(ann.nameAsString))
@@ -78,9 +93,9 @@
private fun ClassPrinter.getFullClassName(type: Type): String {
return if (type is ClassOrInterfaceType) {
+
getFullClassName(buildString {
type.scope.ifPresent { append(it).append(".") }
- type.isArrayType
append(type.nameAsString)
}) + (type.typeArguments.orElse(null)?.let { args -> args.joinToString(", ") {getFullClassName(it)}}?.let { "<$it>" } ?: "")
} else getFullClassName(type.asString())
@@ -100,10 +115,16 @@
val thisPackagePrefix = fileAst.packageDeclaration.map { it.nameAsString + "." }.orElse("")
val thisClassPrefix = thisPackagePrefix + classAst.nameAsString + "."
- classAst.childNodes.filterIsInstance<TypeDeclaration<*>>().find {
+ if (classAst.nameAsString == className) return thisPackagePrefix + classAst.nameAsString
+
+ nestedClasses.find {
it.nameAsString == className
}?.let { return thisClassPrefix + it.nameAsString }
+ if (className == CANONICAL_BUILDER_CLASS || className == BASE_BUILDER_CLASS) {
+ return thisClassPrefix + className
+ }
+
constDefs.find { it.AnnotationName == className }?.let { return thisClassPrefix + className }
if (tryOrNull { Class.forName("java.lang.$className") } != null) {
diff --git a/tools/codegen/src/com/android/codegen/Main.kt b/tools/codegen/src/com/android/codegen/Main.kt
index 8fafa7c..f71bfd3 100755
--- a/tools/codegen/src/com/android/codegen/Main.kt
+++ b/tools/codegen/src/com/android/codegen/Main.kt
@@ -9,9 +9,6 @@
val PRIMITIVE_TYPES = listOf("byte", "short", "int", "long", "char", "float", "double", "boolean")
-const val CANONICAL_BUILDER_CLASS = "Builder"
-const val GENERATED_BUILDER_CLASS = "GeneratedBuilder"
-
val BUILTIN_SPECIAL_PARCELLINGS = listOf("Pattern")
const val FLAG_BUILDER_PROTECTED_SETTERS = "--builder-protected-setters"
@@ -66,10 +63,10 @@
Will be called in constructor, after all the fields have been initialized.
This is a good place to put any custom validation logic that you may have
- static class $CANONICAL_BUILDER_CLASS extends $GENERATED_BUILDER_CLASS
- If a class extending $GENERATED_BUILDER_CLASS is specified, generated builder's setters will
+ static class $CANONICAL_BUILDER_CLASS extends $BASE_BUILDER_CLASS
+ If a class extending $BASE_BUILDER_CLASS is specified, generated builder's setters will
return the provided $CANONICAL_BUILDER_CLASS type.
- $GENERATED_BUILDER_CLASS's constructor(s) will be package-private to encourage using $CANONICAL_BUILDER_CLASS instead
+ $BASE_BUILDER_CLASS's constructor(s) will be package-private to encourage using $CANONICAL_BUILDER_CLASS instead
This allows you to extend the generated builder, adding or overriding any methods you may want
@@ -131,7 +128,6 @@
// $GENERATED_WARNING_PREFIX v$CODEGEN_VERSION.
- // on ${currentTimestamp()}
//
// DO NOT MODIFY!
//
@@ -143,14 +139,6 @@
if (FeatureFlag.CONST_DEFS()) generateConstDefs()
- "@$DataClassGenerated(" {
- +"time = ${System.currentTimeMillis()}L,"
- +"codegenVersion = \"$CODEGEN_VERSION\","
- +"sourceFile = \"${file.relativeTo(File(System.getenv("ANDROID_BUILD_TOP")))}\","
- +"inputSignatures = \"${getInputSignatures().joinToString("\\n")}\""
- }
- +"\n"
-
if (FeatureFlag.CONSTRUCTOR()) {
generateConstructor("public")
@@ -160,6 +148,7 @@
|| FeatureFlag.PARCELABLE()) {
generateConstructor("/* package-private */")
}
+ if (FeatureFlag.COPY_CONSTRUCTOR()) generateCopyConstructor()
if (FeatureFlag.GETTERS()) generateGetters()
if (FeatureFlag.SETTERS()) generateSetters()
@@ -168,7 +157,6 @@
if (FeatureFlag.FOR_EACH_FIELD()) generateForEachField()
- if (FeatureFlag.COPY_CONSTRUCTOR()) generateCopyConstructor()
if (FeatureFlag.WITHERS()) generateWithers()
if (FeatureFlag.PARCELABLE()) generateParcelable()
@@ -178,6 +166,8 @@
if (FeatureFlag.AIDL()) generateAidl(file)
+ generateMetadata(file)
+
rmEmptyLine()
}
stringBuilder.append("\n}\n")
diff --git a/tools/codegen/src/com/android/codegen/SharedConstants.kt b/tools/codegen/src/com/android/codegen/SharedConstants.kt
index 175eea6..7d50ad1 100644
--- a/tools/codegen/src/com/android/codegen/SharedConstants.kt
+++ b/tools/codegen/src/com/android/codegen/SharedConstants.kt
@@ -1,4 +1,7 @@
package com.android.codegen
const val CODEGEN_NAME = "codegen"
-const val CODEGEN_VERSION = "1.0.0"
\ No newline at end of file
+const val CODEGEN_VERSION = "1.0.0"
+
+const val CANONICAL_BUILDER_CLASS = "Builder"
+const val BASE_BUILDER_CLASS = "BaseBuilder"
diff --git a/tools/codegen/src/com/android/codegen/Utils.kt b/tools/codegen/src/com/android/codegen/Utils.kt
index 95c9909..73ceac4 100644
--- a/tools/codegen/src/com/android/codegen/Utils.kt
+++ b/tools/codegen/src/com/android/codegen/Utils.kt
@@ -1,8 +1,10 @@
package com.android.codegen
+import com.github.javaparser.ast.Modifier
import com.github.javaparser.ast.expr.AnnotationExpr
import com.github.javaparser.ast.expr.Expression
import com.github.javaparser.ast.expr.SingleMemberAnnotationExpr
+import com.github.javaparser.ast.nodeTypes.NodeWithModifiers
import java.time.Instant
import java.time.ZoneId
import java.time.format.DateTimeFormatter
@@ -22,6 +24,8 @@
fun if_(cond: Boolean, then: String) = if (cond) then else ""
+fun <T> Any?.as_(): T = this as T
+
inline infix fun Int.times(action: () -> Unit) {
for (i in 1..this) action()
}
@@ -73,4 +77,15 @@
fun currentTimestamp() = DateTimeFormatter
.ofLocalizedDateTime(/* date */ FormatStyle.MEDIUM, /* time */ FormatStyle.LONG)
.withZone(ZoneId.systemDefault())
- .format(Instant.now())
\ No newline at end of file
+ .format(Instant.now())
+
+val NodeWithModifiers<*>.visibility get() = Modifier.getAccessSpecifier(modifiers)
+
+fun abort(msg: String): Nothing {
+ System.err.println("ERROR: $msg")
+ System.exit(1)
+ throw InternalError() // can't get here
+}
+
+fun bitAtExpr(bitIndex: Int) = "0x${java.lang.Long.toHexString(1L shl bitIndex)}"
+
diff --git a/tools/processors/staledataclass/src/android/processor/staledataclass/StaleDataclassProcessor.kt b/tools/processors/staledataclass/src/android/processor/staledataclass/StaleDataclassProcessor.kt
index e5ec17a..26b15ae 100644
--- a/tools/processors/staledataclass/src/android/processor/staledataclass/StaleDataclassProcessor.kt
+++ b/tools/processors/staledataclass/src/android/processor/staledataclass/StaleDataclassProcessor.kt
@@ -17,6 +17,8 @@
package android.processor.staledataclass
+import com.android.codegen.BASE_BUILDER_CLASS
+import com.android.codegen.CANONICAL_BUILDER_CLASS
import com.android.codegen.CODEGEN_NAME
import com.android.codegen.CODEGEN_VERSION
import com.sun.tools.javac.code.Symbol
@@ -29,6 +31,7 @@
import javax.lang.model.SourceVersion
import javax.lang.model.element.AnnotationMirror
import javax.lang.model.element.Element
+import javax.lang.model.element.ElementKind
import javax.lang.model.element.TypeElement
import javax.tools.Diagnostic
@@ -107,20 +110,30 @@
private fun processSingleFile(elementAnnotatedWithGenerated: Element) {
- val inputSignatures = elementAnnotatedWithGenerated
- .enclosingElement
- .enclosedElements
- .filterNot {
- it.annotationMirrors.any { "Generated" in it.annotationType.toString() }
- }.map {
- elemToString(it)
- }.toSet()
+ val classElement = elementAnnotatedWithGenerated.enclosingElement
+
+ val inputSignatures = computeSignaturesForClass(classElement)
+ .plus(computeSignaturesForClass(classElement.enclosedElements.find {
+ it.kind == ElementKind.CLASS
+ && !isGenerated(it)
+ && it.simpleName.toString() == BASE_BUILDER_CLASS
+ }))
+ .plus(computeSignaturesForClass(classElement.enclosedElements.find {
+ it.kind == ElementKind.CLASS
+ && !isGenerated(it)
+ && it.simpleName.toString() == CANONICAL_BUILDER_CLASS
+ }))
+ .plus(classElement
+ .annotationMirrors
+ .find { it.annotationType.toString() == DATACLASS_ANNOTATION_NAME }
+ .toString())
+ .toSet()
val annotationParams = elementAnnotatedWithGenerated
.annotationMirrors
.find { ann -> isGeneratedAnnotation(ann) }!!
.elementValues
- .map { (k, v) -> k.getSimpleName().toString() to v.getValue() }
+ .map { (k, v) -> k.simpleName.toString() to v.value }
.toMap()
val lastGenerated = annotationParams["time"] as Long
@@ -140,7 +153,7 @@
}
val source = repoRoot!!.resolve(sourceRelative)
- val clazz = elementAnnotatedWithGenerated.enclosingElement.toString()
+ val clazz = classElement.toString()
if (inputSignatures != lastGenInputSignatures) {
error(buildString {
@@ -157,6 +170,23 @@
}
}
+ private fun computeSignaturesForClass(classElement: Element?): List<String> {
+ if (classElement == null) return emptyList()
+ val type = classElement as TypeElement
+ return classElement
+ .enclosedElements
+ .filterNot {
+ it.kind == ElementKind.CLASS
+ || it.kind == ElementKind.CONSTRUCTOR
+ || isGenerated(it)
+ }.map {
+ elemToString(it)
+ } + "class ${classElement.simpleName} extends ${type.superclass} implements [${type.interfaces.joinToString(", ")}]"
+ }
+
+ private fun isGenerated(it: Element) =
+ it.annotationMirrors.any { "Generated" in it.annotationType.toString() }
+
private fun error(msg: String) {
processingEnv.messager.printMessage(Diagnostic.Kind.ERROR, msg)
}