Loading core/java/com/android/internal/util/AnnotationValidations.java 0 → 100644 +193 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.internal.util; import static com.android.internal.util.BitUtils.flagsUpTo; import android.annotation.AppIdInt; import android.annotation.ColorInt; import android.annotation.FloatRange; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Size; import android.annotation.UserIdInt; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.PackageManager.PackageInfoFlags; import android.content.pm.PackageManager.PermissionResult; import android.os.UserHandle; import java.lang.annotation.Annotation; /** * Validations for common annotations, e.g. {@link IntRange}, {@link UserIdInt}, etc. * * For usability from generated {@link DataClass} code, all validations are overloads of * {@link #validate} with the following shape: * {@code * <A extends Annotation> void validate( * Class<A> cls, A ignored, Object value[, (String, Object)... annotationParams]) * } * The ignored {@link Annotation} parameter is used to differentiate between overloads that would * otherwise have the same jvm signature. It's usually null at runtime. */ public class AnnotationValidations { private AnnotationValidations() {} public static void validate(Class<UserIdInt> annotation, UserIdInt ignored, int value) { if ((value != UserHandle.USER_NULL && value < -3) || value > Integer.MAX_VALUE / UserHandle.PER_USER_RANGE) { invalid(annotation, value); } } public static void validate(Class<AppIdInt> annotation, AppIdInt ignored, int value) { if (value / UserHandle.PER_USER_RANGE != 0 || value < 0) { invalid(annotation, value); } } public static void validate(Class<IntRange> annotation, IntRange ignored, int value, String paramName1, int param1, String paramName2, int param2) { validate(annotation, ignored, value, paramName1, param1); validate(annotation, ignored, value, paramName2, param2); } public static void validate(Class<IntRange> annotation, IntRange ignored, int value, String paramName, int param) { switch (paramName) { case "from": if (value < param) invalid(annotation, value, paramName, param); break; case "to": if (value > param) invalid(annotation, value, paramName, param); break; } } public static void validate(Class<FloatRange> annotation, FloatRange ignored, float value, String paramName1, float param1, String paramName2, float param2) { validate(annotation, ignored, value, paramName1, param1); validate(annotation, ignored, value, paramName2, param2); } public static void validate(Class<FloatRange> annotation, FloatRange ignored, float value, String paramName, float param) { switch (paramName) { case "from": if (value < param) invalid(annotation, value, paramName, param); break; case "to": if (value > param) invalid(annotation, value, paramName, param); break; } } public static void validate(Class<NonNull> annotation, NonNull ignored, Object value) { if (value == null) { throw new NullPointerException(); } } public static void validate(Class<Size> annotation, Size ignored, int value, String paramName1, int param1, String paramName2, int param2) { validate(annotation, ignored, value, paramName1, param1); validate(annotation, ignored, value, paramName2, param2); } public static void validate(Class<Size> annotation, Size ignored, int value, String paramName, int param) { switch (paramName) { case "value": { if (param != -1 && value != param) invalid(annotation, value, paramName, param); } break; case "min": { if (value < param) invalid(annotation, value, paramName, param); } break; case "max": { if (value > param) invalid(annotation, value, paramName, param); } break; case "multiple": { if (value % param != 0) invalid(annotation, value, paramName, param); } break; } } public static void validate( Class<PermissionResult> annotation, PermissionResult ignored, int value) { validateIntEnum(annotation, value, PackageManager.PERMISSION_GRANTED); } public static void validate( Class<PackageInfoFlags> annotation, PackageInfoFlags ignored, int value) { validateIntFlags(annotation, value, flagsUpTo(PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS)); } public static void validate( Class<Intent.Flags> annotation, Intent.Flags ignored, int value) { validateIntFlags(annotation, value, flagsUpTo(Intent.FLAG_RECEIVER_OFFLOAD)); } @Deprecated public static void validate(Class<? extends Annotation> annotation, Annotation ignored, Object value, Object... params) {} @Deprecated public static void validate(Class<? extends Annotation> annotation, Annotation ignored, Object value) {} @Deprecated public static void validate(Class<? extends Annotation> annotation, Annotation ignored, int value, Object... params) {} public static void validate(Class<? extends Annotation> annotation, Annotation ignored, int value) { if (("android.annotation".equals(annotation.getPackageName$()) && annotation.getSimpleName().endsWith("Res")) || ColorInt.class.equals(annotation)) { if (value < 0) { invalid(annotation, value); } } } public static void validate(Class<? extends Annotation> annotation, Annotation ignored, long value) { if ("android.annotation".equals(annotation.getPackageName$()) && annotation.getSimpleName().endsWith("Long")) { if (value < 0L) { invalid(annotation, value); } } } private static void validateIntEnum( Class<? extends Annotation> annotation, int value, int lastValid) { if (value > lastValid) { invalid(annotation, value); } } private static void validateIntFlags( Class<? extends Annotation> annotation, int value, int validBits) { if ((validBits & value) != validBits) { invalid(annotation, "0x" + Integer.toHexString(value)); } } private static void invalid(Class<? extends Annotation> annotation, Object value) { invalid("@" + annotation.getSimpleName(), value); } private static void invalid(Class<? extends Annotation> annotation, Object value, String paramName, Object param) { String paramPrefix = "value".equals(paramName) ? "" : paramName + " = "; invalid("@" + annotation.getSimpleName() + "(" + paramPrefix + param + ")", value); } private static void invalid(String valueKind, Object value) { throw new IllegalStateException("Invalid " + valueKind + ": " + value); } } core/java/com/android/internal/util/BitUtils.java +14 −0 Original line number Diff line number Diff line Loading @@ -158,4 +158,18 @@ public final class BitUtils { public static byte[] toBytes(long l) { return ByteBuffer.allocate(8).putLong(l).array(); } /** * 0b01000 -> 0b01111 */ public static int flagsUpTo(int lastFlag) { return lastFlag <= 0 ? 0 : lastFlag | flagsUpTo(lastFlag >> 1); } /** * 0b00010, 0b01000 -> 0b01110 */ public static int flagsWithin(int firstFlag, int lastFlag) { return (flagsUpTo(lastFlag) & ~flagsUpTo(firstFlag)) | firstFlag; } } core/java/com/android/internal/util/DataClass.java 0 → 100644 +219 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.internal.util; import static java.lang.annotation.ElementType.*; import android.annotation.IntDef; import android.annotation.Nullable; import android.annotation.StringDef; import android.os.Parcelable; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.SOURCE) @Target(ElementType.TYPE) public @interface DataClass { /** * Generates {@link Parcelable#writeToParcel}, {@link Parcelable#describeContents} and a * {@link Parcelable.Creator}. * * Can be implicitly requested by adding "implements Parcelable" to class signature * * You can provide custom parceling logic by using a {@link ParcelWith} annotation with a * custom {@link Parcelling} subclass. * * Alternatively, for one-off customizations you can declare methods like: * {@code void parcelFieldName(Parcel dest, int flags)} * {@code static FieldType unparcelFieldName(Parcel in)} */ boolean genParcelable() default false; /** * Generates a simple "parcelable" .aidl file alongside the original .java file * * If not explicitly requested/suppressed, is on iff {@link #genParcelable} is on */ boolean genAidl() default false; /** * Generates getters for each field. * * You can request for getter to lazily initialize your field by declaring a method like: * {@code FieldType lazyInitFieldName()} * * You can request for the lazy initialization to be thread safe my marking the field volatile. */ boolean genGetters() default true; /** * Generates setters for each field. */ boolean genSetters() default false; /** * Generates a public constructor with each field initialized from a parameter and optionally * some user-defined state validation at the end. * * Uses field {@link Nullable nullability}/default value presence to determine optional * parameters. * * Requesting a {@link #genBuilder} suppresses public constructor generation by default. * * You receive a callback at the end of constructor call by declaring the method: * {@code void onConstructed()} * This is the place to put any custom validation logic. */ boolean genConstructor() default true; /** * Generates a Builder for your class. * * Uses a package-private constructor under the hood, so same rules hold as for * {@link #genConstructor()} */ boolean genBuilder() default false; /** * Generates a structural {@link Object#equals} + {@link Object#hashCode}. * * You can customize individual fields' logic by declaring methods like: * {@link boolean fieldNameEquals(ClassName otherInstance)} * {@link boolean fieldNameEquals(FieldType otherValue)} * {@link int fieldNameHashCode()} */ boolean genEqualsHashCode() default false; /** * Generates a structural {@link Object#toString}. * * You can customize individual fields' logic by declaring methods like: * {@link String fieldNameToString()} */ boolean genToString() default false; /** * Generates a utility method that takes a {@link PerObjectFieldAction per-field callback} * and calls it once for each field with its name and value. * * If some fields are of primitive types, and additional overload is generated that takes * multiple callbacks, specialized for used primitive types to avoid auto-boxing, e.g. * {@link PerIntFieldAction}. */ boolean genForEachField() default false; /** * Generates a constructor that copies the given instance of the same class. */ boolean genCopyConstructor() default false; /** * Generates constant annotations({@link IntDef}/{@link StringDef}) for any constant groups * with common prefix. * The annotation names are based on the common prefix. * * For int constants this additionally generates the corresponding static *ToString method and * uses it in {@link Object#toString}. * * Additionally, any fields you annotate with the generated constants will be automatically * validated in constructor. * * Int constants specified as hex(0x..) are considered to be flags, which is taken into account * for in their *ToString and validation. * * You can optionally override the name of the generated annotation by annotating each constant * with the desired annotation name. * * Unless suppressed, is implied by presence of constants with common prefix. */ boolean genConstDefs() default true; /** * Allows specifying custom parcelling logic based on reusable * {@link Parcelling} implementations */ @Retention(RetentionPolicy.SOURCE) @Target(FIELD) @interface ParcelWith { Class<? extends Parcelling> value(); } /** * Allows specifying a singular name for a builder's plural field name e.g. 'name' for 'mNames' * Used for Builder's {@code addName(String name)} methods */ @Retention(RetentionPolicy.SOURCE) @Target(FIELD) @interface PluralOf { String value(); } /** * Marks that any annotations following it are applicable to each element of the * collection/array, as opposed to itself. */ @Retention(RetentionPolicy.SOURCE) @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE}) @interface Each {} /** * @deprecated to be used by code generator exclusively * @hide */ @Deprecated @Retention(RetentionPolicy.SOURCE) @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, ANNOTATION_TYPE, CONSTRUCTOR, TYPE}) @interface Generated { long time(); String codegenVersion(); String sourceFile(); String inputSignatures() default ""; /** * @deprecated to be used by code generator exclusively * @hide */ @Deprecated @Retention(RetentionPolicy.SOURCE) @Target({FIELD, METHOD, ANNOTATION_TYPE, CONSTRUCTOR, TYPE}) @interface Member {} } /** * Callback used by {@link #genForEachField}. * * @param <THIS> The enclosing data class instance. * Can be used to try and avoid capturing values from outside of the lambda, * minimizing allocations. */ interface PerObjectFieldAction<THIS> { void acceptObject(THIS self, String fieldName, Object fieldValue); } /** * A specialization of {@link PerObjectFieldAction} called exclusively for int fields to avoid * boxing. */ interface PerIntFieldAction<THIS> { void acceptInt(THIS self, String fieldName, int fieldValue); } } core/java/com/android/internal/util/Parcelling.java 0 → 100644 +104 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.internal.util; import android.annotation.Nullable; import android.os.Parcel; import android.util.ArrayMap; import java.util.regex.Pattern; /** * Describes a 2-way parcelling contract of type {@code T} into/out of a {@link Parcel} * * @param <T> the type being [un]parcelled */ public interface Parcelling<T> { /** * Write an item into parcel. */ void parcel(T item, Parcel dest, int parcelFlags); /** * Read an item from parcel. */ T unparcel(Parcel source); /** * A registry of {@link Parcelling} singletons. */ class Cache { private Cache() {} private static ArrayMap<Class, Parcelling> sCache = new ArrayMap<>(); /** * Retrieves an instance of a given {@link Parcelling} class if present. */ public static @Nullable <P extends Parcelling<?>> P get(Class<P> clazz) { return (P) sCache.get(clazz); } /** * Stores an instance of a given {@link Parcelling}. * * @return the provided parcelling for convenience. */ public static <P extends Parcelling<?>> P put(P parcelling) { sCache.put(parcelling.getClass(), parcelling); return parcelling; } /** * Produces an instance of a given {@link Parcelling} class, by either retrieving a cached * instance or reflectively creating one. */ public static <P extends Parcelling<?>> P getOrCreate(Class<P> clazz) { P cached = get(clazz); if (cached != null) { return cached; } else { try { return put(clazz.newInstance()); } catch (Exception e) { throw new RuntimeException(e); } } } } /** * Common {@link Parcelling} implementations. */ interface BuiltIn { class ForPattern implements Parcelling<Pattern> { @Override public void parcel(Pattern item, Parcel dest, int parcelFlags) { dest.writeString(item == null ? null : item.pattern()); } @Override public Pattern unparcel(Parcel source) { String s = source.readString(); return s == null ? null : Pattern.compile(s); } } } } tests/Codegen/Android.bp 0 → 100644 +25 −0 Original line number Diff line number Diff line android_test { name: "CodegenTests", srcs: [ "**/*.java", ], platform_apis: true, test_suites: ["device-tests"], certificate: "platform", optimize: { enabled: false, }, plugins: [ "staledataclass-annotation-processor", ], static_libs: [ "junit", "hamcrest", "hamcrest-library", "androidx.test.runner", "androidx.test.rules", ], } Loading
core/java/com/android/internal/util/AnnotationValidations.java 0 → 100644 +193 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.internal.util; import static com.android.internal.util.BitUtils.flagsUpTo; import android.annotation.AppIdInt; import android.annotation.ColorInt; import android.annotation.FloatRange; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Size; import android.annotation.UserIdInt; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.PackageManager.PackageInfoFlags; import android.content.pm.PackageManager.PermissionResult; import android.os.UserHandle; import java.lang.annotation.Annotation; /** * Validations for common annotations, e.g. {@link IntRange}, {@link UserIdInt}, etc. * * For usability from generated {@link DataClass} code, all validations are overloads of * {@link #validate} with the following shape: * {@code * <A extends Annotation> void validate( * Class<A> cls, A ignored, Object value[, (String, Object)... annotationParams]) * } * The ignored {@link Annotation} parameter is used to differentiate between overloads that would * otherwise have the same jvm signature. It's usually null at runtime. */ public class AnnotationValidations { private AnnotationValidations() {} public static void validate(Class<UserIdInt> annotation, UserIdInt ignored, int value) { if ((value != UserHandle.USER_NULL && value < -3) || value > Integer.MAX_VALUE / UserHandle.PER_USER_RANGE) { invalid(annotation, value); } } public static void validate(Class<AppIdInt> annotation, AppIdInt ignored, int value) { if (value / UserHandle.PER_USER_RANGE != 0 || value < 0) { invalid(annotation, value); } } public static void validate(Class<IntRange> annotation, IntRange ignored, int value, String paramName1, int param1, String paramName2, int param2) { validate(annotation, ignored, value, paramName1, param1); validate(annotation, ignored, value, paramName2, param2); } public static void validate(Class<IntRange> annotation, IntRange ignored, int value, String paramName, int param) { switch (paramName) { case "from": if (value < param) invalid(annotation, value, paramName, param); break; case "to": if (value > param) invalid(annotation, value, paramName, param); break; } } public static void validate(Class<FloatRange> annotation, FloatRange ignored, float value, String paramName1, float param1, String paramName2, float param2) { validate(annotation, ignored, value, paramName1, param1); validate(annotation, ignored, value, paramName2, param2); } public static void validate(Class<FloatRange> annotation, FloatRange ignored, float value, String paramName, float param) { switch (paramName) { case "from": if (value < param) invalid(annotation, value, paramName, param); break; case "to": if (value > param) invalid(annotation, value, paramName, param); break; } } public static void validate(Class<NonNull> annotation, NonNull ignored, Object value) { if (value == null) { throw new NullPointerException(); } } public static void validate(Class<Size> annotation, Size ignored, int value, String paramName1, int param1, String paramName2, int param2) { validate(annotation, ignored, value, paramName1, param1); validate(annotation, ignored, value, paramName2, param2); } public static void validate(Class<Size> annotation, Size ignored, int value, String paramName, int param) { switch (paramName) { case "value": { if (param != -1 && value != param) invalid(annotation, value, paramName, param); } break; case "min": { if (value < param) invalid(annotation, value, paramName, param); } break; case "max": { if (value > param) invalid(annotation, value, paramName, param); } break; case "multiple": { if (value % param != 0) invalid(annotation, value, paramName, param); } break; } } public static void validate( Class<PermissionResult> annotation, PermissionResult ignored, int value) { validateIntEnum(annotation, value, PackageManager.PERMISSION_GRANTED); } public static void validate( Class<PackageInfoFlags> annotation, PackageInfoFlags ignored, int value) { validateIntFlags(annotation, value, flagsUpTo(PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS)); } public static void validate( Class<Intent.Flags> annotation, Intent.Flags ignored, int value) { validateIntFlags(annotation, value, flagsUpTo(Intent.FLAG_RECEIVER_OFFLOAD)); } @Deprecated public static void validate(Class<? extends Annotation> annotation, Annotation ignored, Object value, Object... params) {} @Deprecated public static void validate(Class<? extends Annotation> annotation, Annotation ignored, Object value) {} @Deprecated public static void validate(Class<? extends Annotation> annotation, Annotation ignored, int value, Object... params) {} public static void validate(Class<? extends Annotation> annotation, Annotation ignored, int value) { if (("android.annotation".equals(annotation.getPackageName$()) && annotation.getSimpleName().endsWith("Res")) || ColorInt.class.equals(annotation)) { if (value < 0) { invalid(annotation, value); } } } public static void validate(Class<? extends Annotation> annotation, Annotation ignored, long value) { if ("android.annotation".equals(annotation.getPackageName$()) && annotation.getSimpleName().endsWith("Long")) { if (value < 0L) { invalid(annotation, value); } } } private static void validateIntEnum( Class<? extends Annotation> annotation, int value, int lastValid) { if (value > lastValid) { invalid(annotation, value); } } private static void validateIntFlags( Class<? extends Annotation> annotation, int value, int validBits) { if ((validBits & value) != validBits) { invalid(annotation, "0x" + Integer.toHexString(value)); } } private static void invalid(Class<? extends Annotation> annotation, Object value) { invalid("@" + annotation.getSimpleName(), value); } private static void invalid(Class<? extends Annotation> annotation, Object value, String paramName, Object param) { String paramPrefix = "value".equals(paramName) ? "" : paramName + " = "; invalid("@" + annotation.getSimpleName() + "(" + paramPrefix + param + ")", value); } private static void invalid(String valueKind, Object value) { throw new IllegalStateException("Invalid " + valueKind + ": " + value); } }
core/java/com/android/internal/util/BitUtils.java +14 −0 Original line number Diff line number Diff line Loading @@ -158,4 +158,18 @@ public final class BitUtils { public static byte[] toBytes(long l) { return ByteBuffer.allocate(8).putLong(l).array(); } /** * 0b01000 -> 0b01111 */ public static int flagsUpTo(int lastFlag) { return lastFlag <= 0 ? 0 : lastFlag | flagsUpTo(lastFlag >> 1); } /** * 0b00010, 0b01000 -> 0b01110 */ public static int flagsWithin(int firstFlag, int lastFlag) { return (flagsUpTo(lastFlag) & ~flagsUpTo(firstFlag)) | firstFlag; } }
core/java/com/android/internal/util/DataClass.java 0 → 100644 +219 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.internal.util; import static java.lang.annotation.ElementType.*; import android.annotation.IntDef; import android.annotation.Nullable; import android.annotation.StringDef; import android.os.Parcelable; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.SOURCE) @Target(ElementType.TYPE) public @interface DataClass { /** * Generates {@link Parcelable#writeToParcel}, {@link Parcelable#describeContents} and a * {@link Parcelable.Creator}. * * Can be implicitly requested by adding "implements Parcelable" to class signature * * You can provide custom parceling logic by using a {@link ParcelWith} annotation with a * custom {@link Parcelling} subclass. * * Alternatively, for one-off customizations you can declare methods like: * {@code void parcelFieldName(Parcel dest, int flags)} * {@code static FieldType unparcelFieldName(Parcel in)} */ boolean genParcelable() default false; /** * Generates a simple "parcelable" .aidl file alongside the original .java file * * If not explicitly requested/suppressed, is on iff {@link #genParcelable} is on */ boolean genAidl() default false; /** * Generates getters for each field. * * You can request for getter to lazily initialize your field by declaring a method like: * {@code FieldType lazyInitFieldName()} * * You can request for the lazy initialization to be thread safe my marking the field volatile. */ boolean genGetters() default true; /** * Generates setters for each field. */ boolean genSetters() default false; /** * Generates a public constructor with each field initialized from a parameter and optionally * some user-defined state validation at the end. * * Uses field {@link Nullable nullability}/default value presence to determine optional * parameters. * * Requesting a {@link #genBuilder} suppresses public constructor generation by default. * * You receive a callback at the end of constructor call by declaring the method: * {@code void onConstructed()} * This is the place to put any custom validation logic. */ boolean genConstructor() default true; /** * Generates a Builder for your class. * * Uses a package-private constructor under the hood, so same rules hold as for * {@link #genConstructor()} */ boolean genBuilder() default false; /** * Generates a structural {@link Object#equals} + {@link Object#hashCode}. * * You can customize individual fields' logic by declaring methods like: * {@link boolean fieldNameEquals(ClassName otherInstance)} * {@link boolean fieldNameEquals(FieldType otherValue)} * {@link int fieldNameHashCode()} */ boolean genEqualsHashCode() default false; /** * Generates a structural {@link Object#toString}. * * You can customize individual fields' logic by declaring methods like: * {@link String fieldNameToString()} */ boolean genToString() default false; /** * Generates a utility method that takes a {@link PerObjectFieldAction per-field callback} * and calls it once for each field with its name and value. * * If some fields are of primitive types, and additional overload is generated that takes * multiple callbacks, specialized for used primitive types to avoid auto-boxing, e.g. * {@link PerIntFieldAction}. */ boolean genForEachField() default false; /** * Generates a constructor that copies the given instance of the same class. */ boolean genCopyConstructor() default false; /** * Generates constant annotations({@link IntDef}/{@link StringDef}) for any constant groups * with common prefix. * The annotation names are based on the common prefix. * * For int constants this additionally generates the corresponding static *ToString method and * uses it in {@link Object#toString}. * * Additionally, any fields you annotate with the generated constants will be automatically * validated in constructor. * * Int constants specified as hex(0x..) are considered to be flags, which is taken into account * for in their *ToString and validation. * * You can optionally override the name of the generated annotation by annotating each constant * with the desired annotation name. * * Unless suppressed, is implied by presence of constants with common prefix. */ boolean genConstDefs() default true; /** * Allows specifying custom parcelling logic based on reusable * {@link Parcelling} implementations */ @Retention(RetentionPolicy.SOURCE) @Target(FIELD) @interface ParcelWith { Class<? extends Parcelling> value(); } /** * Allows specifying a singular name for a builder's plural field name e.g. 'name' for 'mNames' * Used for Builder's {@code addName(String name)} methods */ @Retention(RetentionPolicy.SOURCE) @Target(FIELD) @interface PluralOf { String value(); } /** * Marks that any annotations following it are applicable to each element of the * collection/array, as opposed to itself. */ @Retention(RetentionPolicy.SOURCE) @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE}) @interface Each {} /** * @deprecated to be used by code generator exclusively * @hide */ @Deprecated @Retention(RetentionPolicy.SOURCE) @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, ANNOTATION_TYPE, CONSTRUCTOR, TYPE}) @interface Generated { long time(); String codegenVersion(); String sourceFile(); String inputSignatures() default ""; /** * @deprecated to be used by code generator exclusively * @hide */ @Deprecated @Retention(RetentionPolicy.SOURCE) @Target({FIELD, METHOD, ANNOTATION_TYPE, CONSTRUCTOR, TYPE}) @interface Member {} } /** * Callback used by {@link #genForEachField}. * * @param <THIS> The enclosing data class instance. * Can be used to try and avoid capturing values from outside of the lambda, * minimizing allocations. */ interface PerObjectFieldAction<THIS> { void acceptObject(THIS self, String fieldName, Object fieldValue); } /** * A specialization of {@link PerObjectFieldAction} called exclusively for int fields to avoid * boxing. */ interface PerIntFieldAction<THIS> { void acceptInt(THIS self, String fieldName, int fieldValue); } }
core/java/com/android/internal/util/Parcelling.java 0 → 100644 +104 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.internal.util; import android.annotation.Nullable; import android.os.Parcel; import android.util.ArrayMap; import java.util.regex.Pattern; /** * Describes a 2-way parcelling contract of type {@code T} into/out of a {@link Parcel} * * @param <T> the type being [un]parcelled */ public interface Parcelling<T> { /** * Write an item into parcel. */ void parcel(T item, Parcel dest, int parcelFlags); /** * Read an item from parcel. */ T unparcel(Parcel source); /** * A registry of {@link Parcelling} singletons. */ class Cache { private Cache() {} private static ArrayMap<Class, Parcelling> sCache = new ArrayMap<>(); /** * Retrieves an instance of a given {@link Parcelling} class if present. */ public static @Nullable <P extends Parcelling<?>> P get(Class<P> clazz) { return (P) sCache.get(clazz); } /** * Stores an instance of a given {@link Parcelling}. * * @return the provided parcelling for convenience. */ public static <P extends Parcelling<?>> P put(P parcelling) { sCache.put(parcelling.getClass(), parcelling); return parcelling; } /** * Produces an instance of a given {@link Parcelling} class, by either retrieving a cached * instance or reflectively creating one. */ public static <P extends Parcelling<?>> P getOrCreate(Class<P> clazz) { P cached = get(clazz); if (cached != null) { return cached; } else { try { return put(clazz.newInstance()); } catch (Exception e) { throw new RuntimeException(e); } } } } /** * Common {@link Parcelling} implementations. */ interface BuiltIn { class ForPattern implements Parcelling<Pattern> { @Override public void parcel(Pattern item, Parcel dest, int parcelFlags) { dest.writeString(item == null ? null : item.pattern()); } @Override public Pattern unparcel(Parcel source) { String s = source.readString(); return s == null ? null : Pattern.compile(s); } } } }
tests/Codegen/Android.bp 0 → 100644 +25 −0 Original line number Diff line number Diff line android_test { name: "CodegenTests", srcs: [ "**/*.java", ], platform_apis: true, test_suites: ["device-tests"], certificate: "platform", optimize: { enabled: false, }, plugins: [ "staledataclass-annotation-processor", ], static_libs: [ "junit", "hamcrest", "hamcrest-library", "androidx.test.runner", "androidx.test.rules", ], }