Snap for 7080740 from 5eb4fe3f2cb140f0cfd4be36388663458845dcab to mainline-networkstack-release
Change-Id: I1db19cd28aaaa04f9f007ab063fed617ccfd7557
diff --git a/aidl_checkapi.cpp b/aidl_checkapi.cpp
index f0228d9..b2a884c 100644
--- a/aidl_checkapi.cpp
+++ b/aidl_checkapi.cpp
@@ -48,6 +48,7 @@
// - a new implementation might start accepting null values (add @nullable)
static const set<std::string> kIgnoreAnnotations{
"nullable",
+ "JavaDerive",
};
set<AidlAnnotation> annotations;
for (const AidlAnnotation& annotation : node.GetAnnotations()) {
diff --git a/aidl_language.cpp b/aidl_language.cpp
index 5beaea1..be1221d 100644
--- a/aidl_language.cpp
+++ b/aidl_language.cpp
@@ -131,6 +131,7 @@
static const string kJavaStableParcelable("JavaOnlyStableParcelable");
static const string kHide("Hide");
static const string kBacking("Backing");
+static const string kJavaDerive("JavaDerive");
static const std::map<string, std::map<std::string, std::string>> kAnnotationParameters{
{kNullable, {}},
@@ -144,7 +145,8 @@
{"trackingBug", "long"}}},
{kJavaStableParcelable, {}},
{kHide, {}},
- {kBacking, {{"type", "String"}}}};
+ {kBacking, {{"type", "String"}}},
+ {kJavaDerive, {{"toString", "boolean"}}}};
AidlAnnotation* AidlAnnotation::Parse(
const AidlLocation& location, const string& name,
@@ -308,6 +310,10 @@
return HasAnnotation(annotations_, kHide);
}
+const AidlAnnotation* AidlAnnotatable::JavaDerive() const {
+ return GetAnnotation(annotations_, kJavaDerive);
+}
+
void AidlAnnotatable::DumpAnnotations(CodeWriter* writer) const {
if (annotations_.empty()) return;
diff --git a/aidl_language.h b/aidl_language.h
index 7a40427..920970f 100644
--- a/aidl_language.h
+++ b/aidl_language.h
@@ -244,6 +244,7 @@
bool IsVintfStability() const;
bool IsStableApiParcelable(Options::Language lang) const;
bool IsHide() const;
+ const AidlAnnotation* JavaDerive() const;
void DumpAnnotations(CodeWriter* writer) const;
diff --git a/aidl_to_java.cpp b/aidl_to_java.cpp
index 8910365..f4706a2 100644
--- a/aidl_to_java.cpp
+++ b/aidl_to_java.cpp
@@ -762,6 +762,50 @@
return true;
}
+void ToStringFor(const CodeGeneratorContext& c) {
+ if (c.type.IsArray()) {
+ // Arrays can be null
+ c.writer << c.var << " == null ? \"null\" : ";
+ c.writer << "java.util.Arrays.toString(" << c.var << ")";
+ return;
+ }
+
+ const std::string name = c.type.GetName();
+
+ if (AidlTypenames::IsPrimitiveTypename(name)) {
+ c.writer << c.var;
+ return;
+ }
+
+ const AidlDefinedType* t = c.typenames.TryGetDefinedType(name);
+ if (t != nullptr && t->AsEnumDeclaration()) {
+ c.writer << c.var;
+ return;
+ }
+
+ // FileDescriptor doesn't have a good toString() impl.
+ if (name == "FileDescriptor") {
+ c.writer << c.var << " == null ? \"null\" : ";
+ c.writer << c.var << ".getInt$()";
+ return;
+ }
+
+ // Rest of the built-in types have reasonable toString() impls.
+ if (AidlTypenames::IsBuiltinTypename(name)) {
+ c.writer << "java.util.Objects.toString(" << c.var << ")";
+ return;
+ }
+
+ // For user-defined types, we also use toString() that we are generating here, but just make sure
+ // that they are actually user-defined types.
+ AIDL_FATAL_IF(t == nullptr, c.type) << "Unknown type";
+ if (t->AsInterface() != nullptr || t->AsParcelable() != nullptr) {
+ c.writer << "java.util.Objects.toString(" << c.var << ")";
+ return;
+ }
+ CHECK(true) << "Unhandled typename: " << name << endl;
+}
+
} // namespace java
} // namespace aidl
} // namespace android
diff --git a/aidl_to_java.h b/aidl_to_java.h
index d33b25b..2ebe2c3 100644
--- a/aidl_to_java.h
+++ b/aidl_to_java.h
@@ -99,6 +99,10 @@
// array or a List.
bool ReadFromParcelFor(const CodeGeneratorContext& c);
+// Writes an expression that returns the string representation of a field
+// in a parcelable
+void ToStringFor(const CodeGeneratorContext& c);
+
} // namespace java
} // namespace aidl
} // namespace android
diff --git a/aidl_typenames.cpp b/aidl_typenames.cpp
index ad89c1c..51c26d7 100644
--- a/aidl_typenames.cpp
+++ b/aidl_typenames.cpp
@@ -82,9 +82,9 @@
}
bool AidlTypenames::IsIgnorableImport(const string& import) const {
- static set<string> ignore_import = {"android.os.IInterface", "android.os.IBinder",
- "android.os.Parcelable", "android.os.Parcel",
- "android.content.Context", "java.lang.String"};
+ static set<string> ignore_import = {
+ "android.os.IInterface", "android.os.IBinder", "android.os.Parcelable", "android.os.Parcel",
+ "android.content.Context", "java.lang.String", "java.lang.CharSequence"};
// these known built-in types don't need to be imported
const bool in_ignore_import = ignore_import.find(import) != ignore_import.end();
// an already defined type doesn't need to be imported again unless it is from
diff --git a/aidl_unittest.cpp b/aidl_unittest.cpp
index 6827980..fea3419 100644
--- a/aidl_unittest.cpp
+++ b/aidl_unittest.cpp
@@ -21,6 +21,7 @@
#include <android-base/stringprintf.h>
#include <gtest/gtest.h>
+#include <gmock/gmock.h>
#include "aidl.h"
#include "aidl_checkapi.h"
@@ -397,6 +398,24 @@
EXPECT_NE(nullptr, Parse("a/IFoo.aidl", oneway_method, typenames_, Options::Language::JAVA));
}
+TEST_F(AidlTest, ParsesJavaDeriveAnnotation) {
+ io_delegate_.SetFileContents("a/IFoo.aidl", R"(package a;
+ @JavaDerive(toString=true) parcelable IFoo { int a; float b; })");
+ Options java_options = Options::From("aidl --lang=java -o out a/IFoo.aidl");
+ EXPECT_EQ(0, ::android::aidl::compile_aidl(java_options, io_delegate_));
+
+ string java_out;
+ EXPECT_TRUE(io_delegate_.GetWrittenContents("out/a/IFoo.java", &java_out));
+ EXPECT_THAT(java_out, testing::HasSubstr("public String toString() {"));
+
+ // Other backends shouldn't be bothered
+ Options cpp_options = Options::From("aidl --lang=cpp -o out -h out a/IFoo.aidl");
+ EXPECT_EQ(0, ::android::aidl::compile_aidl(cpp_options, io_delegate_));
+
+ Options ndk_options = Options::From("aidl --lang=ndk -o out -h out a/IFoo.aidl");
+ EXPECT_EQ(0, ::android::aidl::compile_aidl(ndk_options, io_delegate_));
+}
+
TEST_F(AidlTest, WritesComments) {
string foo_interface =
"package a; /* foo */ interface IFoo {"
diff --git a/code_writer.h b/code_writer.h
index d890d52..08516bf 100644
--- a/code_writer.h
+++ b/code_writer.h
@@ -39,6 +39,14 @@
// The buffer gets updated only after Close() is called or the CodeWriter
// is deleted -- much like a real file.
static CodeWriterPtr ForString(std::string* buf);
+ // Run a Code Generater (which accepts CodeWriter& as a first parameter)
+ // and return a result as a string.
+ template <typename... Args>
+ static std::string RunWith(void (*gen)(CodeWriter&, Args...), Args&&... args) {
+ std::string code;
+ (*gen)(*ForString(&code), std::forward<Args>(args)...);
+ return code;
+ }
// Write a formatted string to this writer in the usual printf sense.
// Returns false on error.
virtual bool Write(const char* format, ...) __attribute__((format(printf, 2, 3)));
diff --git a/generate_java.cpp b/generate_java.cpp
index 5d572a4..f120ac5 100644
--- a/generate_java.cpp
+++ b/generate_java.cpp
@@ -33,6 +33,49 @@
using ::android::aidl::java::Variable;
using std::string;
+namespace {
+using android::aidl::java::CodeGeneratorContext;
+using android::aidl::java::ConstantValueDecorator;
+
+void GenerateToString(CodeWriter& out, const AidlStructuredParcelable& parcel,
+ const AidlTypenames& typenames) {
+ out << "@Override\n";
+ out << "public String toString() {\n";
+ out.Indent();
+ out << "java.util.StringJoiner _aidl_sj = new java.util.StringJoiner(";
+ out << "\", \", \"{\", \"}\");\n";
+ for (const auto& field : parcel.GetFields()) {
+ CodeGeneratorContext ctx{
+ .writer = out,
+ .typenames = typenames,
+ .type = field->GetType(),
+ .var = field->GetName(),
+ };
+ out << "_aidl_sj.add(\"" << field->GetName() << ": \" + (";
+ ToStringFor(ctx);
+ out << "));\n";
+ }
+ out << "return \"" << parcel.GetCanonicalName() << "\" + _aidl_sj.toString() ;\n";
+ out.Dedent();
+ out << "}\n";
+}
+
+template <typename ParcelableType>
+void GenerateDerivedMethods(CodeWriter& out, const ParcelableType& parcel,
+ const AidlTypenames& typenames) {
+ if (auto java_derive = parcel.JavaDerive(); java_derive) {
+ auto synthetic_methods = java_derive->AnnotationParams(ConstantValueDecorator);
+ for (const auto& [method_name, generate] : synthetic_methods) {
+ if (generate == "true") {
+ if (method_name == "toString") {
+ GenerateToString(out, parcel, typenames);
+ }
+ }
+ }
+ }
+}
+} // namespace
+
namespace android {
namespace aidl {
namespace java {
@@ -227,6 +270,9 @@
parcel_class->elements.push_back(read_method);
+ auto method = CodeWriter::RunWith(GenerateDerivedMethods, *parcel, typenames);
+ parcel_class->elements.push_back(std::make_shared<LiteralClassElement>(method));
+
auto describe_contents_method = std::make_shared<Method>();
describe_contents_method->modifiers = PUBLIC | OVERRIDE;
describe_contents_method->returnType = "int";
diff --git a/tests/android/aidl/tests/OtherParcelableForToString.aidl b/tests/android/aidl/tests/OtherParcelableForToString.aidl
new file mode 100644
index 0000000..f042ab5
--- /dev/null
+++ b/tests/android/aidl/tests/OtherParcelableForToString.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2020 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 android.aidl.tests;
+
+@JavaDerive(toString=true)
+parcelable OtherParcelableForToString {
+ String field;
+}
diff --git a/tests/android/aidl/tests/ParcelableForToString.aidl b/tests/android/aidl/tests/ParcelableForToString.aidl
new file mode 100644
index 0000000..2ad414c
--- /dev/null
+++ b/tests/android/aidl/tests/ParcelableForToString.aidl
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2020 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 android.aidl.tests;
+
+import android.aidl.tests.OtherParcelableForToString;
+import android.aidl.tests.IntEnum;
+
+@JavaDerive(toString=true)
+parcelable ParcelableForToString {
+ int intValue;
+ int[] intArray;
+ long longValue;
+ long[] longArray;
+ double doubleValue;
+ double[] doubleArray;
+ float floatValue;
+ float[] floatArray;
+ byte byteValue;
+ byte[] byteArray;
+ boolean booleanValue;
+ boolean[] booleanArray;
+ String stringValue;
+ String[] stringArray;
+ List<String> stringList;
+ OtherParcelableForToString parcelableValue;
+ OtherParcelableForToString[] parcelableArray;
+ IntEnum enumValue;
+ IntEnum[] enumArray;
+ String[] nullArray;
+ List<String> nullList;
+}
diff --git a/tests/android/aidl/tests/StructuredParcelable.aidl b/tests/android/aidl/tests/StructuredParcelable.aidl
index 28046e2..5e316ee 100644
--- a/tests/android/aidl/tests/StructuredParcelable.aidl
+++ b/tests/android/aidl/tests/StructuredParcelable.aidl
@@ -21,6 +21,7 @@
import android.aidl.tests.LongEnum;
import android.aidl.tests.ConstantExpressionEnum;
+@JavaDerive(toString=true)
parcelable StructuredParcelable {
int[] shouldContainThreeFs;
int f;
diff --git a/tests/java_app/src/android/aidl/tests/TestServiceClient.java b/tests/java_app/src/android/aidl/tests/TestServiceClient.java
index 01df091..3415280 100644
--- a/tests/java_app/src/android/aidl/tests/TestServiceClient.java
+++ b/tests/java_app/src/android/aidl/tests/TestServiceClient.java
@@ -920,6 +920,65 @@
"parcelable.const_exprs_10 should be 1 but is " + parcelable.const_exprs_10);
}
+ final String expected = "android.aidl.tests.StructuredParcelable{" +
+ "shouldContainThreeFs: [17, 17, 17], " +
+ "f: 17, " +
+ "shouldBeJerry: Jerry, " +
+ "shouldBeByteBar: 2, " +
+ "shouldBeIntBar: 2000, " +
+ "shouldBeLongBar: 200000000000, " +
+ "shouldContainTwoByteFoos: [1, 1], " +
+ "shouldContainTwoIntFoos: [1000, 1000], " +
+ "shouldContainTwoLongFoos: [100000000000, 100000000000], " +
+ "stringDefaultsToFoo: foo, " +
+ "byteDefaultsToFour: 4, " +
+ "intDefaultsToFive: 5, " +
+ "longDefaultsToNegativeSeven: -7, " +
+ "booleanDefaultsToTrue: true, " +
+ "charDefaultsToC: C, " +
+ "floatDefaultsToPi: 3.14, " +
+ "doubleWithDefault: -3.14E17, " +
+ "arrayDefaultsTo123: [1, 2, 3], " +
+ "arrayDefaultsToEmpty: [], " +
+ "boolDefault: false, " +
+ "byteDefault: 0, " +
+ "intDefault: 0, " +
+ "longDefault: 0, " +
+ "floatDefault: 0.0, " +
+ "doubleDefault: 0.0, " +
+ "checkDoubleFromFloat: 3.14, " +
+ "checkStringArray1: [a, b], " +
+ "checkStringArray2: [a, b], " +
+ "int32_min: -2147483648, " +
+ "int32_max: 2147483647, " +
+ "int64_max: 9223372036854775807, " +
+ "hexInt32_neg_1: -1, " +
+ "ibinder: null, " +
+ "int32_1: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, " +
+ "1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, " +
+ "1, 1, 1, 1], " +
+ "int64_1: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1], " +
+ "hexInt32_pos_1: 1, " +
+ "hexInt64_pos_1: 1, " +
+ "const_exprs_1: 1, " +
+ "const_exprs_2: 1, " +
+ "const_exprs_3: 1, " +
+ "const_exprs_4: 1, " +
+ "const_exprs_5: 1, " +
+ "const_exprs_6: 1, " +
+ "const_exprs_7: 1, " +
+ "const_exprs_8: 1, " +
+ "const_exprs_9: 1, " +
+ "const_exprs_10: 1, " +
+ "addString1: hello world!, " +
+ "addString2: The quick brown fox jumps over the lazy dog." +
+ "}";
+ if (!expected.equals(parcelable.toString())) {
+ mLog.logAndThrow(
+ "parcelable.toString() should be \"" + expected + "\" " +
+ "but is \"" + parcelable.toString() + "\"");
+ }
+
mLog.log("Successfully verified the StructuredParcelable");
}
@@ -953,6 +1012,64 @@
}
}
+ private void checkToString() throws TestFailException {
+ ParcelableForToString p = new ParcelableForToString();
+ p.intValue = 10;
+ p.intArray = new int[]{20, 30};
+ p.longValue = 100L;
+ p.longArray = new long[]{200L, 300L};
+ p.doubleValue = 3.14d;
+ p.doubleArray = new double[]{1.1d, 1.2d};
+ p.floatValue = 3.14f;
+ p.floatArray = new float[]{1.1f, 1.2f};
+ p.byteValue = 3;
+ p.byteArray = new byte[]{5, 6};
+ p.booleanValue = true;
+ p.booleanArray = new boolean[]{true, false};
+ p.stringValue = "this is a string";
+ p.stringArray = new String[]{"hello", "world"};
+ p.stringList = Arrays.asList(new String[]{"alice", "bob"});
+ OtherParcelableForToString op = new OtherParcelableForToString();
+ op.field = "other";
+ p.parcelableValue = op;
+ p.parcelableArray = new OtherParcelableForToString[]{op, op};
+ p.enumValue = IntEnum.FOO;
+ p.enumArray = new int[]{IntEnum.FOO, IntEnum.BAR};
+ p.nullArray = null;
+ p.nullList = null;
+
+ final String expected = "android.aidl.tests.ParcelableForToString{" +
+ "intValue: 10, " +
+ "intArray: [20, 30], " +
+ "longValue: 100, " +
+ "longArray: [200, 300], " +
+ "doubleValue: 3.14, " +
+ "doubleArray: [1.1, 1.2], " +
+ "floatValue: 3.14, " +
+ "floatArray: [1.1, 1.2], " +
+ "byteValue: 3, " +
+ "byteArray: [5, 6], " +
+ "booleanValue: true, " +
+ "booleanArray: [true, false], " +
+ "stringValue: this is a string, " +
+ "stringArray: [hello, world], " +
+ "stringList: [alice, bob], " +
+ "parcelableValue: android.aidl.tests.OtherParcelableForToString{field: other}, " +
+ "parcelableArray: [" +
+ "android.aidl.tests.OtherParcelableForToString{field: other}, " +
+ "android.aidl.tests.OtherParcelableForToString{field: other}], " +
+ "enumValue: 1000, " +
+ "enumArray: [1000, 2000], " +
+ "nullArray: null, " +
+ "nullList: null" +
+ "}";
+ if (!expected.equals(p.toString())) {
+ mLog.logAndThrow(
+ "parcelable.toString() should be \"" + expected + "\" " +
+ "but is \"" + p.toString() + "\"");
+ }
+ }
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -975,6 +1092,7 @@
new MapTests(mLog).runTests();
new GenericTests(mLog).runTests();
checkDefaultImpl(service);
+ checkToString();
mLog.log(mSuccessSentinel);
} catch (TestFailException e) {