@JavaDerive(toString=true) for enum types
Enum types are translated into @interface class. When annotated with
@JavaDerive(toString=true), toString() method is generated in a nested
utility class "$".
For example, "Enum" type in AIDL will look like in the Java
@interface Enum {
public static final int FOO = 0;
interface $ {
static String toString(int v) { ... }
static String arrayToString(Object v) { ... }
}
arrayToString() will print enum arrays just like Arrays.toString().
Parcelable's toString() will use enum's toString when the enum type is
annotated with @JavaDerive(toString=true) as well.
Bug: 225289741
Test: aidl_integration_test
Change-Id: I854b6cbc52dbfdb78201a2cc5c27027ee93a7c9d
diff --git a/aidl_language.cpp b/aidl_language.cpp
index 62bde98..6e19d6e 100644
--- a/aidl_language.cpp
+++ b/aidl_language.cpp
@@ -154,7 +154,7 @@
/* repeatable= */ true},
{AidlAnnotation::Type::JAVA_DERIVE,
"JavaDerive",
- CONTEXT_TYPE_STRUCTURED_PARCELABLE | CONTEXT_TYPE_UNION,
+ CONTEXT_TYPE_STRUCTURED_PARCELABLE | CONTEXT_TYPE_UNION | CONTEXT_TYPE_ENUM,
{{"toString", kBooleanType}, {"equals", kBooleanType}}},
{AidlAnnotation::Type::JAVA_DEFAULT, "JavaDefault", CONTEXT_TYPE_INTERFACE, {}},
{AidlAnnotation::Type::JAVA_DELEGATOR, "JavaDelegator", CONTEXT_TYPE_INTERFACE, {}},
diff --git a/aidl_to_java.cpp b/aidl_to_java.cpp
index 3cc2472..dd74660 100644
--- a/aidl_to_java.cpp
+++ b/aidl_to_java.cpp
@@ -135,14 +135,14 @@
}
namespace {
-string JavaSignatureOfInternal(
- const AidlTypeSpecifier& aidl, bool instantiable, bool omit_array,
- bool boxing = false /* boxing can be true only if it is a type parameter */) {
+string JavaSignatureOfInternal(const AidlTypeSpecifier& aidl, bool instantiable, bool omit_array,
+ bool boxing) {
string ret = JavaNameOf(aidl, instantiable, boxing && !aidl.IsArray());
if (aidl.IsGeneric()) {
vector<string> arg_names;
for (const auto& ta : aidl.GetTypeParameters()) {
- arg_names.emplace_back(JavaSignatureOfInternal(*ta, false, false, true /* boxing */));
+ arg_names.emplace_back(JavaSignatureOfInternal(*ta, /*instantiable=*/false,
+ /*omit_array=*/false, /*boxing=*/true));
}
ret += "<" + Join(arg_names, ",") + ">";
}
@@ -185,11 +185,20 @@
} // namespace
string JavaSignatureOf(const AidlTypeSpecifier& aidl) {
- return JavaSignatureOfInternal(aidl, false, false);
+ return JavaSignatureOfInternal(aidl, /*instantiable=*/false, /*omit_array=*/false,
+ /*boxing=*/false);
}
+// Used for "new" expression. Ignore arrays because "new" expression handles it.
string InstantiableJavaSignatureOf(const AidlTypeSpecifier& aidl) {
- return JavaSignatureOfInternal(aidl, true, true);
+ return JavaSignatureOfInternal(aidl, /*instantiable=*/true, /*omit_array=*/true,
+ /*boxing=*/false);
+}
+
+string JavaBoxingTypeOf(const AidlTypeSpecifier& aidl) {
+ AIDL_FATAL_IF(!AidlTypenames::IsPrimitiveTypename(aidl.GetName()), aidl);
+ return JavaSignatureOfInternal(aidl, /*instantiable=*/false, /*omit_array=*/false,
+ /*boxing=*/true);
}
string DefaultJavaValueOf(const AidlTypeSpecifier& aidl) {
@@ -893,6 +902,17 @@
}
void ToStringFor(const CodeGeneratorContext& c) {
+ // Use derived toString() for enum type annotated with @JavaDerive(toString=true)
+ if (auto t = c.type.GetDefinedType();
+ t != nullptr && t->AsEnumDeclaration() && t->JavaDerive("toString")) {
+ if (c.type.IsArray()) {
+ c.writer << c.type.GetName() << ".$.arrayToString(" << c.var << ")";
+ } else {
+ c.writer << c.type.GetName() << ".$.toString(" << c.var << ")";
+ }
+ return;
+ }
+
if (c.type.IsArray()) {
if (c.type.IsDynamicArray() || c.type.GetFixedSizeArrayDimensions().size() == 1) {
c.writer << "java.util.Arrays.toString(" << c.var << ")";
diff --git a/aidl_to_java.h b/aidl_to_java.h
index 5a4632c..01634f4 100644
--- a/aidl_to_java.h
+++ b/aidl_to_java.h
@@ -47,6 +47,10 @@
// This includes generic type parameters with array modifiers.
string JavaSignatureOf(const AidlTypeSpecifier& aidl);
+// Returns the Java boxing type of the AIDL type spec.
+// aidl type should be a primitive type.
+string JavaBoxingTypeOf(const AidlTypeSpecifier& aidl);
+
// Returns the instantiable Jva type signature of the AIDL type spec
// This includes generic type parameters, but excludes array modifiers.
string InstantiableJavaSignatureOf(const AidlTypeSpecifier& aidl);
diff --git a/aidl_unittest.cpp b/aidl_unittest.cpp
index 4f94b16..54677df 100644
--- a/aidl_unittest.cpp
+++ b/aidl_unittest.cpp
@@ -620,14 +620,6 @@
EXPECT_FALSE(compile_aidl(java_options, io_delegate_));
EXPECT_THAT(GetCapturedStderr(), HasSubstr("@JavaDerive is not available."));
}
-
- {
- io_delegate_.SetFileContents("a/IFoo.aidl", "package a; @JavaDerive enum IFoo { A=1, }");
- Options java_options = Options::From("aidl --lang=java -o out a/IFoo.aidl");
- CaptureStderr();
- EXPECT_FALSE(compile_aidl(java_options, io_delegate_));
- EXPECT_THAT(GetCapturedStderr(), HasSubstr("@JavaDerive is not available."));
- }
}
TEST_P(AidlTest, ParseDescriptorAnnotation) {
diff --git a/generate_java.cpp b/generate_java.cpp
index 8dc1643..9ba025f 100644
--- a/generate_java.cpp
+++ b/generate_java.cpp
@@ -603,6 +603,9 @@
}
void GenerateEnumClass(CodeWriter& out, const AidlEnumDeclaration& enum_decl) {
+ const AidlTypeSpecifier& backing_type = enum_decl.GetBackingType();
+ std::string raw_type = JavaSignatureOf(backing_type);
+ std::string boxing_type = JavaBoxingTypeOf(backing_type);
out << GenerateComments(enum_decl);
out << GenerateAnnotations(enum_decl);
out << "public ";
@@ -614,9 +617,43 @@
for (const auto& enumerator : enum_decl.GetEnumerators()) {
out << GenerateComments(*enumerator);
out << GenerateAnnotations(*enumerator);
- out << fmt::format("public static final {} {} = {};\n",
- JavaSignatureOf(enum_decl.GetBackingType()), enumerator->GetName(),
- enumerator->ValueString(enum_decl.GetBackingType(), ConstantValueDecorator));
+ out << fmt::format("public static final {} {} = {};\n", raw_type, enumerator->GetName(),
+ enumerator->ValueString(backing_type, ConstantValueDecorator));
+ }
+ if (enum_decl.JavaDerive("toString")) {
+ out << "interface $ {\n";
+ out.Indent();
+ out << "static String toString(" << raw_type << " _aidl_v) {\n";
+ out.Indent();
+ for (const auto& enumerator : enum_decl.GetEnumerators()) {
+ out << "if (_aidl_v == " << enumerator->GetName() << ") return \"" << enumerator->GetName()
+ << "\";\n";
+ }
+ out << "return " << boxing_type << ".toString(_aidl_v);\n";
+ out.Dedent();
+ out << "}\n";
+ out << fmt::format(R"(static String arrayToString(Object _aidl_v) {{
+ if (_aidl_v == null) return "null";
+ Class<?> _aidl_cls = _aidl_v.getClass();
+ if (!_aidl_cls.isArray()) throw new IllegalArgumentException("not an array: " + _aidl_v);
+ Class<?> comp = _aidl_cls.getComponentType();
+ java.util.StringJoiner _aidl_sj = new java.util.StringJoiner(", ", "[", "]");
+ if (comp.isArray()) {{
+ for (int _aidl_i = 0; _aidl_i < java.lang.reflect.Array.getLength(_aidl_v); _aidl_i++) {{
+ _aidl_sj.add(arrayToString(java.lang.reflect.Array.get(_aidl_v, _aidl_i)));
+ }}
+ }} else {{
+ if (_aidl_cls != {raw_type}[].class) throw new IllegalArgumentException("wrong type: " + _aidl_cls);
+ for ({raw_type} e : ({raw_type}[]) _aidl_v) {{
+ _aidl_sj.add(toString(e));
+ }}
+ }}
+ return _aidl_sj.toString();
+}}
+)",
+ fmt::arg("raw_type", raw_type));
+ out.Dedent();
+ out << "}\n";
}
out.Dedent();
out << "}\n";
diff --git a/tests/android/aidl/tests/IntEnum.aidl b/tests/android/aidl/tests/IntEnum.aidl
index b241871..7ab63f3 100644
--- a/tests/android/aidl/tests/IntEnum.aidl
+++ b/tests/android/aidl/tests/IntEnum.aidl
@@ -16,6 +16,7 @@
package android.aidl.tests;
+@JavaDerive(toString=true)
@Backing(type="int")
enum IntEnum {
FOO = 1000,
diff --git a/tests/golden_output/aidl-test-interface-java-source/gen/android/aidl/tests/IntEnum.java b/tests/golden_output/aidl-test-interface-java-source/gen/android/aidl/tests/IntEnum.java
index 0e2dd06..4114fe7 100644
--- a/tests/golden_output/aidl-test-interface-java-source/gen/android/aidl/tests/IntEnum.java
+++ b/tests/golden_output/aidl-test-interface-java-source/gen/android/aidl/tests/IntEnum.java
@@ -9,4 +9,31 @@
/** @deprecated do not use this */
@Deprecated
public static final int QUX = 2002;
+ interface $ {
+ static String toString(int _aidl_v) {
+ if (_aidl_v == FOO) return "FOO";
+ if (_aidl_v == BAR) return "BAR";
+ if (_aidl_v == BAZ) return "BAZ";
+ if (_aidl_v == QUX) return "QUX";
+ return Integer.toString(_aidl_v);
+ }
+ static String arrayToString(Object _aidl_v) {
+ if (_aidl_v == null) return "null";
+ Class<?> _aidl_cls = _aidl_v.getClass();
+ if (!_aidl_cls.isArray()) throw new IllegalArgumentException("not an array: " + _aidl_v);
+ Class<?> comp = _aidl_cls.getComponentType();
+ java.util.StringJoiner _aidl_sj = new java.util.StringJoiner(", ", "[", "]");
+ if (comp.isArray()) {
+ for (int _aidl_i = 0; _aidl_i < java.lang.reflect.Array.getLength(_aidl_v); _aidl_i++) {
+ _aidl_sj.add(arrayToString(java.lang.reflect.Array.get(_aidl_v, _aidl_i)));
+ }
+ } else {
+ if (_aidl_cls != int[].class) throw new IllegalArgumentException("wrong type: " + _aidl_cls);
+ for (int e : (int[]) _aidl_v) {
+ _aidl_sj.add(toString(e));
+ }
+ }
+ return _aidl_sj.toString();
+ }
+ }
}
diff --git a/tests/golden_output/aidl-test-interface-java-source/gen/android/aidl/tests/ParcelableForToString.java b/tests/golden_output/aidl-test-interface-java-source/gen/android/aidl/tests/ParcelableForToString.java
index c588be1..03ce5be 100644
--- a/tests/golden_output/aidl-test-interface-java-source/gen/android/aidl/tests/ParcelableForToString.java
+++ b/tests/golden_output/aidl-test-interface-java-source/gen/android/aidl/tests/ParcelableForToString.java
@@ -150,8 +150,8 @@
_aidl_sj.add("stringList: " + (java.util.Objects.toString(stringList)));
_aidl_sj.add("parcelableValue: " + (java.util.Objects.toString(parcelableValue)));
_aidl_sj.add("parcelableArray: " + (java.util.Arrays.toString(parcelableArray)));
- _aidl_sj.add("enumValue: " + (enumValue));
- _aidl_sj.add("enumArray: " + (java.util.Arrays.toString(enumArray)));
+ _aidl_sj.add("enumValue: " + (android.aidl.tests.IntEnum.$.toString(enumValue)));
+ _aidl_sj.add("enumArray: " + (android.aidl.tests.IntEnum.$.arrayToString(enumArray)));
_aidl_sj.add("nullArray: " + (java.util.Arrays.toString(nullArray)));
_aidl_sj.add("nullList: " + (java.util.Objects.toString(nullList)));
_aidl_sj.add("parcelableGeneric: " + (java.util.Objects.toString(parcelableGeneric)));
diff --git a/tests/golden_output/aidl-test-interface-java-source/gen/android/aidl/tests/StructuredParcelable.java b/tests/golden_output/aidl-test-interface-java-source/gen/android/aidl/tests/StructuredParcelable.java
index b90fe2c..3d7685a 100644
--- a/tests/golden_output/aidl-test-interface-java-source/gen/android/aidl/tests/StructuredParcelable.java
+++ b/tests/golden_output/aidl-test-interface-java-source/gen/android/aidl/tests/StructuredParcelable.java
@@ -272,10 +272,10 @@
_aidl_sj.add("f: " + (f));
_aidl_sj.add("shouldBeJerry: " + (java.util.Objects.toString(shouldBeJerry)));
_aidl_sj.add("shouldBeByteBar: " + (shouldBeByteBar));
- _aidl_sj.add("shouldBeIntBar: " + (shouldBeIntBar));
+ _aidl_sj.add("shouldBeIntBar: " + (android.aidl.tests.IntEnum.$.toString(shouldBeIntBar)));
_aidl_sj.add("shouldBeLongBar: " + (shouldBeLongBar));
_aidl_sj.add("shouldContainTwoByteFoos: " + (java.util.Arrays.toString(shouldContainTwoByteFoos)));
- _aidl_sj.add("shouldContainTwoIntFoos: " + (java.util.Arrays.toString(shouldContainTwoIntFoos)));
+ _aidl_sj.add("shouldContainTwoIntFoos: " + (android.aidl.tests.IntEnum.$.arrayToString(shouldContainTwoIntFoos)));
_aidl_sj.add("shouldContainTwoLongFoos: " + (java.util.Arrays.toString(shouldContainTwoLongFoos)));
_aidl_sj.add("stringDefaultsToFoo: " + (java.util.Objects.toString(stringDefaultsToFoo)));
_aidl_sj.add("byteDefaultsToFour: " + (byteDefaultsToFour));
@@ -322,7 +322,7 @@
_aidl_sj.add("shouldSetBit0AndBit2: " + (shouldSetBit0AndBit2));
_aidl_sj.add("u: " + (java.util.Objects.toString(u)));
_aidl_sj.add("shouldBeConstS1: " + (java.util.Objects.toString(shouldBeConstS1)));
- _aidl_sj.add("defaultWithFoo: " + (defaultWithFoo));
+ _aidl_sj.add("defaultWithFoo: " + (android.aidl.tests.IntEnum.$.toString(defaultWithFoo)));
return "android.aidl.tests.StructuredParcelable" + _aidl_sj.toString() ;
}
@Override
diff --git a/tests/golden_output/aidl-test-interface-java-source/gen/android/aidl/tests/unions/EnumUnion.java b/tests/golden_output/aidl-test-interface-java-source/gen/android/aidl/tests/unions/EnumUnion.java
index 038d813..937d9d5 100644
--- a/tests/golden_output/aidl-test-interface-java-source/gen/android/aidl/tests/unions/EnumUnion.java
+++ b/tests/golden_output/aidl-test-interface-java-source/gen/android/aidl/tests/unions/EnumUnion.java
@@ -112,7 +112,7 @@
@Override
public String toString() {
switch (_tag) {
- case intEnum: return "android.aidl.tests.unions.EnumUnion.intEnum(" + (getIntEnum()) + ")";
+ case intEnum: return "android.aidl.tests.unions.EnumUnion.intEnum(" + (android.aidl.tests.IntEnum.$.toString(getIntEnum())) + ")";
case longEnum: return "android.aidl.tests.unions.EnumUnion.longEnum(" + (getLongEnum()) + ")";
}
throw new IllegalStateException("unknown field: " + _tag);
diff --git a/tests/java/src/android/aidl/tests/TestServiceClient.java b/tests/java/src/android/aidl/tests/TestServiceClient.java
index 6dfacc9..098ad20 100644
--- a/tests/java/src/android/aidl/tests/TestServiceClient.java
+++ b/tests/java/src/android/aidl/tests/TestServiceClient.java
@@ -757,10 +757,10 @@
+ "f: 17, "
+ "shouldBeJerry: Jerry, "
+ "shouldBeByteBar: 2, "
- + "shouldBeIntBar: 2000, "
+ + "shouldBeIntBar: BAR, "
+ "shouldBeLongBar: 200000000000, "
+ "shouldContainTwoByteFoos: [1, 1], "
- + "shouldContainTwoIntFoos: [1000, 1000], "
+ + "shouldContainTwoIntFoos: [FOO, FOO], "
+ "shouldContainTwoLongFoos: [100000000000, 100000000000], "
+ "stringDefaultsToFoo: foo, "
+ "byteDefaultsToFour: 4, "
@@ -809,7 +809,7 @@
+ "shouldSetBit0AndBit2: 5, "
+ "u: android.aidl.tests.Union.ns([1, 2, 3]), "
+ "shouldBeConstS1: android.aidl.tests.Union.s(a string constant in union), "
- + "defaultWithFoo: 1000"
+ + "defaultWithFoo: FOO"
+ "}";
assertThat(p.toString(), is(expected));
}
@@ -888,8 +888,8 @@
+ "parcelableArray: ["
+ "android.aidl.tests.OtherParcelableForToString{field: other}, "
+ "android.aidl.tests.OtherParcelableForToString{field: other}], "
- + "enumValue: 1000, "
- + "enumArray: [1000, 2000], "
+ + "enumValue: FOO, "
+ + "enumArray: [FOO, BAR], "
+ "nullArray: null, "
+ "nullList: null, "
+ "parcelableGeneric: android.aidl.tests.GenericStructuredParcelable{a: 1, b: 2}, "
@@ -900,6 +900,21 @@
}
@Test
+ public void testEnumToString() {
+ assertThat(IntEnum.$.toString(IntEnum.FOO), is("FOO"));
+ assertThat(IntEnum.$.toString(0), is("0"));
+ assertThat(IntEnum.$.arrayToString(null), is("null"));
+ assertThat(IntEnum.$.arrayToString(new int[] {}), is("[]"));
+ assertThat(IntEnum.$.arrayToString(new int[] {IntEnum.FOO, IntEnum.BAR}), is("[FOO, BAR]"));
+ assertThat(IntEnum.$.arrayToString(new int[] {IntEnum.FOO, 0}), is("[FOO, 0]"));
+ assertThat(IntEnum.$.arrayToString(new int[][] {{IntEnum.FOO, IntEnum.BAR}, {IntEnum.BAZ}}),
+ is("[[FOO, BAR], [BAZ]]"));
+ assertThrows(IllegalArgumentException.class, () -> IntEnum.$.arrayToString(IntEnum.FOO));
+ assertThrows(
+ IllegalArgumentException.class, () -> IntEnum.$.arrayToString(new long[] {LongEnum.FOO}));
+ }
+
+ @Test
public void testRenamedInterface() throws RemoteException {
IOldName oldAsOld = service.GetOldNameInterface();
assertNotNull(oldAsOld);