Add toString to Java code

* toString is mainly for debugging purposes.
* For structs and interfaces, add Object.toString.
* For enums, add MyEnum.toString(int) and MyEnum.dumpBitfield(int).

Use them as follows:
* For enums, use the static method E.toString(int).
* For bitfields, use the static method E.dumpBitfield(int).
* For all arrays, use java.utils.Arrays.deepToString(o)
* For everything else, use one of the following:
    * o.toString(), if o is not null
    * Object.toString(o)
    * String.valueOf(o)
* Note that for array / vec of enums / bitfields, the raw integer
  value is dumped.

Bug: 33459772
Test: hidl_test_java

Change-Id: Ifb1ed519770b907e0a4e345b2c3109dc322a23b2
diff --git a/ArrayType.cpp b/ArrayType.cpp
index 7753031..b47ba3b 100644
--- a/ArrayType.cpp
+++ b/ArrayType.cpp
@@ -318,6 +318,17 @@
     out << "}\n\n";
 }
 
+void ArrayType::emitJavaDump(
+        Formatter &out,
+        const std::string &streamName,
+        const std::string &name) const {
+    out << streamName << ".append(java.util.Arrays."
+        << (countDimensions() > 1 ? "deepToString" : "toString")
+        << "("
+        << name << "));\n";
+}
+
+
 bool ArrayType::needsEmbeddedReadWrite() const {
     return mElementType->needsEmbeddedReadWrite();
 }
diff --git a/ArrayType.h b/ArrayType.h
index e17df10..a0c2a08 100644
--- a/ArrayType.h
+++ b/ArrayType.h
@@ -95,6 +95,11 @@
             const std::string &parentName,
             const std::string &offsetText) const override;
 
+    void emitJavaDump(
+            Formatter &out,
+            const std::string &streamName,
+            const std::string &name) const override;
+
     bool needsEmbeddedReadWrite() const override;
     bool needsResolveReferences() const override;
     bool resultNeedsDeref() const override;
diff --git a/CompoundType.cpp b/CompoundType.cpp
index 685ba85..dcd426a 100644
--- a/CompoundType.cpp
+++ b/CompoundType.cpp
@@ -619,6 +619,23 @@
 
     ////////////////////////////////////////////////////////////////////////////
 
+    out << "@Override\npublic final String toString() ";
+    out.block([&] {
+        out << "java.lang.StringBuilder builder = new java.lang.StringBuilder();\n"
+            << "builder.append(\"{\");\n";
+        for (const auto &field : *mFields) {
+            out << "builder.append(\"";
+            if (field != *(mFields->begin())) {
+                out << ", ";
+            }
+            out << "." << field->name() << " = \");\n";
+            field->type().emitJavaDump(out, "builder", "this." + field->name());
+        }
+        out << "builder.append(\"}\");\nreturn builder.toString();\n";
+    }).endl().endl();
+
+    ////////////////////////////////////////////////////////////////////////////
+
     out << "public final void readFromParcel(android.os.HwParcel parcel) {\n";
     out.indent();
     out << "android.os.HwBlob blob = parcel.readBuffer();\n";
diff --git a/EnumType.cpp b/EnumType.cpp
index 7aa0231..0ef2590 100644
--- a/EnumType.cpp
+++ b/EnumType.cpp
@@ -32,6 +32,8 @@
     : Scope(localName, location),
       mValues(),
       mStorageType(storageType) {
+    mBitfieldType = new BitFieldType();
+    mBitfieldType->setElementType(this);
 }
 
 const Type *EnumType::storageType() const {
@@ -101,6 +103,10 @@
     return "TYPE_ENUM";
 }
 
+BitFieldType *EnumType::getBitfieldType() const {
+    return mBitfieldType;
+}
+
 LocalIdentifier *EnumType::lookupIdentifier(const std::string &name) const {
     std::vector<const EnumType *> chain;
     getTypeChain(&chain);
@@ -343,11 +349,13 @@
     return OK;
 }
 
-status_t EnumType::emitJavaTypeDeclarations(Formatter &out, bool) const {
+status_t EnumType::emitJavaTypeDeclarations(Formatter &out, bool atTopLevel) const {
     const ScalarType *scalarType = mStorageType->resolveToScalarType();
     CHECK(scalarType != NULL);
 
-    out << "public final class "
+    out << "public "
+        << (atTopLevel ? "" : "static ")
+        << "final class "
         << localName()
         << " {\n";
 
@@ -385,6 +393,42 @@
         }
     }
 
+    out << "public static final String toString("
+        << typeName << " o) ";
+    out.block([&] {
+        for (EnumValue *value : values()) {
+            out.sIf("o == " + value->name(), [&] {
+                out << "return \"" << value->name() << "\";\n";
+            }).endl();
+        }
+        out << "return \"0x\" + ";
+        scalarType->emitConvertToJavaHexString(out, "o");
+        out << ";\n";
+    }).endl();
+
+    auto bitfieldType = getBitfieldType()->getJavaType(false /* forInitializer */);
+    auto bitfieldWrapperType = getBitfieldType()->getJavaWrapperType();
+    out << "\n"
+        << "public static final String dumpBitfield("
+        << bitfieldType << " o) ";
+    out.block([&] {
+        out << "java.util.ArrayList<String> list = new java.util.ArrayList<>();\n";
+        out << bitfieldType << " flipped = 0;\n";
+        for (EnumValue *value : values()) {
+            out.sIf("(o & " + value->name() + ") == " + value->name(), [&] {
+                out << "list.add(\"" << value->name() << "\");\n";
+                out << "flipped |= " << value->name() << ";\n";
+            }).endl();
+        }
+        // put remaining bits
+        out.sIf("o != flipped", [&] {
+            out << "list.add(\"0x\" + ";
+            scalarType->emitConvertToJavaHexString(out, "o & (~flipped)");
+            out << ");\n";
+        }).endl();
+        out << "return String.join(\" | \", list);\n";
+    }).endl().endl();
+
     out.unindent();
     out << "};\n\n";
 
@@ -435,6 +479,14 @@
     return OK;
 }
 
+void EnumType::emitJavaDump(
+        Formatter &out,
+        const std::string &streamName,
+        const std::string &name) const {
+    out << streamName << ".append(" << fqName().javaName() << ".toString("
+        << name << "));\n";
+}
+
 void EnumType::getTypeChain(std::vector<const EnumType *> *out) const {
     out->clear();
     const EnumType *type = this;
@@ -741,6 +793,11 @@
             true /* needsCast */);
 }
 
+EnumType *BitFieldType::getEnumType() const {
+    CHECK(mElementType->isEnum());
+    return static_cast<EnumType *>(mElementType);
+}
+
 // a bitfield maps to the underlying scalar type in C++, so operator<< is
 // already defined. We can still emit useful information if the bitfield is
 // in a struct / union by overriding emitDump as below.
@@ -748,13 +805,19 @@
         Formatter &out,
         const std::string &streamName,
         const std::string &name) const {
-    CHECK(mElementType->isEnum());
-    const EnumType *enumType = static_cast<EnumType *>(mElementType);
-    out << streamName << " += "<< enumType->fqName().cppNamespace()
-        << "::toString<" << enumType->getCppStackType()
+    out << streamName << " += "<< getEnumType()->fqName().cppNamespace()
+        << "::toString<" << getEnumType()->getCppStackType()
         << ">(" << name << ");\n";
 }
 
+void BitFieldType::emitJavaDump(
+        Formatter &out,
+        const std::string &streamName,
+        const std::string &name) const {
+    out << streamName << ".append(" << getEnumType()->fqName().javaName() << ".dumpBitfield("
+        << name << "));\n";
+}
+
 void BitFieldType::emitJavaFieldReaderWriter(
         Formatter &out,
         size_t depth,
diff --git a/EnumType.h b/EnumType.h
index e259297..b853054 100644
--- a/EnumType.h
+++ b/EnumType.h
@@ -26,6 +26,7 @@
 namespace android {
 
 struct EnumValue;
+struct BitFieldType;
 
 struct EnumType : public Scope {
     EnumType(const char *localName,
@@ -56,6 +57,9 @@
 
     std::string getVtsType() const override;
 
+    // Return the type that corresponds to bitfield<T>.
+    BitFieldType *getBitfieldType() const;
+
     void emitReaderWriter(
             Formatter &out,
             const std::string &name,
@@ -83,6 +87,11 @@
     status_t emitVtsTypeDeclarations(Formatter &out) const override;
     status_t emitVtsAttributeType(Formatter &out) const override;
 
+    void emitJavaDump(
+            Formatter &out,
+            const std::string &streamName,
+            const std::string &name) const override;
+
     void getAlignmentAndSize(size_t *align, size_t *size) const override;
 
     void appendToExportedTypesVector(
@@ -106,6 +115,7 @@
 
     std::vector<EnumValue *> mValues;
     Type *mStorageType;
+    BitFieldType *mBitfieldType;
 
     DISALLOW_COPY_AND_ASSIGN(EnumType);
 };
@@ -159,6 +169,8 @@
 
     std::string getVtsType() const override;
 
+    EnumType *getEnumType() const;
+
     status_t emitVtsAttributeType(Formatter &out) const override;
 
     void getAlignmentAndSize(size_t *align, size_t *size) const override;
@@ -176,6 +188,11 @@
             const std::string &streamName,
             const std::string &name) const override;
 
+    void emitJavaDump(
+            Formatter &out,
+            const std::string &streamName,
+            const std::string &name) const override;
+
     void emitJavaFieldReaderWriter(
         Formatter &out,
         size_t depth,
diff --git a/ScalarType.cpp b/ScalarType.cpp
index 9b6c0c3..b8bd427 100644
--- a/ScalarType.cpp
+++ b/ScalarType.cpp
@@ -218,6 +218,40 @@
     out << streamName << " += toHexString(" << name << ");\n";
 }
 
+void ScalarType::emitConvertToJavaHexString(
+        Formatter &out,
+        const std::string &name) const {
+    switch(mKind) {
+        case KIND_BOOL: {
+            out << "((" << name << ") ? \"0x1\" : \"0x0\")";
+            break;
+        }
+        case KIND_INT8:     // fallthrough
+        case KIND_UINT8:    // fallthrough
+        case KIND_INT16:    // fallthrough
+        case KIND_UINT16: {
+            // Because Byte and Short doesn't have toHexString, we have to use Integer.toHexString.
+            out << "Integer.toHexString(" << getJavaWrapperType() << ".toUnsignedInt(("
+                << getJavaType(false /* forInitializer */) << ")(" << name << ")))";
+            break;
+        }
+        case KIND_INT32:    // fallthrough
+        case KIND_UINT32:   // fallthrough
+        case KIND_INT64:    // fallthrough
+        case KIND_UINT64: {
+            out << getJavaWrapperType() << ".toHexString(" << name << ")";
+            break;
+        }
+        case KIND_FLOAT:    // fallthrough
+        case KIND_DOUBLE:   // fallthrough
+        default: {
+            // no hex for floating point numbers.
+            out << name;
+            break;
+        }
+    }
+}
+
 void ScalarType::emitJavaFieldReaderWriter(
         Formatter &out,
         size_t /* depth */,
diff --git a/ScalarType.h b/ScalarType.h
index 8b70d72..a8a5f68 100644
--- a/ScalarType.h
+++ b/ScalarType.h
@@ -85,6 +85,10 @@
             const std::string &streamName,
             const std::string &name) const;
 
+    void emitConvertToJavaHexString(
+            Formatter &out,
+            const std::string &name) const;
+
     void emitJavaFieldReaderWriter(
             Formatter &out,
             size_t depth,
diff --git a/Type.cpp b/Type.cpp
index 71279c0..546d6ab 100644
--- a/Type.cpp
+++ b/Type.cpp
@@ -214,6 +214,13 @@
         << ");\n";
 }
 
+void Type::emitJavaDump(
+        Formatter &out,
+        const std::string &streamName,
+        const std::string &name) const {
+    out << streamName << ".append(" << name << ");\n";
+}
+
 bool Type::useParentInEmitResolveReferencesEmbedded() const {
     return needsResolveReferences();
 }
diff --git a/Type.h b/Type.h
index f926ed2..ce2a03a 100644
--- a/Type.h
+++ b/Type.h
@@ -158,6 +158,11 @@
             const std::string &streamName,
             const std::string &name) const;
 
+    virtual void emitJavaDump(
+            Formatter &out,
+            const std::string &streamName,
+            const std::string &name) const;
+
     virtual bool useParentInEmitResolveReferencesEmbedded() const;
 
     virtual bool useNameInEmitReaderWriterEmbedded(bool isReader) const;
diff --git a/generateJava.cpp b/generateJava.cpp
index 56ac2d6..3e8d493 100644
--- a/generateJava.cpp
+++ b/generateJava.cpp
@@ -303,6 +303,18 @@
     out.unindent();
     out << "}\n\n";
 
+
+    out << "@Override\npublic String toString() ";
+    out.block([&] {
+        out.sTry([&] {
+            out << "return this.interfaceDescriptor() + \"@Proxy\";\n";
+        }).sCatch("RemoteException ex", [&] {
+            out << "/* ignored; handled below. */\n";
+        }).endl();
+        out << "return \"[class or subclass of \" + "
+            << ifaceName << ".kInterfaceName + \"]@Proxy\";\n";
+    }).endl().endl();
+
     const Interface *prevInterface = nullptr;
     for (const auto &tuple : iface->allMethodsFromRoot()) {
         const Method *method = tuple.method();
@@ -488,6 +500,11 @@
     out.unindent();
     out << "}\n\n";
 
+    out << "@Override\npublic String toString() ";
+    out.block([&] {
+        out << "return this.interfaceDescriptor() + \"@Stub\";\n";
+    }).endl().endl();
+
     out << "@Override\n"
         << "public void onTransact("
         << "int _hidl_code, "
diff --git a/test/java_test/src/com/android/commands/hidl_test_java/HidlTestJava.java b/test/java_test/src/com/android/commands/hidl_test_java/HidlTestJava.java
index acafeae..4864371 100644
--- a/test/java_test/src/com/android/commands/hidl_test_java/HidlTestJava.java
+++ b/test/java_test/src/com/android/commands/hidl_test_java/HidlTestJava.java
@@ -25,6 +25,7 @@
 import android.util.Log;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 
 public final class HidlTestJava {
     private static final String TAG = "HidlTestJava";
@@ -367,6 +368,7 @@
             return;
         }
 
+        System.err.printf("Expected '%s', got '%s'\n", s, result);
         Log.e(TAG, "Expected '" + s + "', got '" + result + "'");
         throw new RuntimeException();
     }
@@ -759,6 +761,36 @@
         ExpectTrue(structs.size() == 5);
         ExpectTrue(structs.get(1).matrices.size() == 6);
 
+        {
+            IBaz.Everything e = new IBaz.Everything();
+            Expect(e.toString(),
+                "{.number = 0, .anotherNumber = 0, .s = , " +
+                ".vs = [], .multidimArray = [[null, null], [null, null]], " +
+                ".sArray = [null, null, null], .anotherStruct = {.first = , .last = }, .bf = }");
+            e.s = "string!";
+            e.number = 127;
+            e.anotherNumber = 100;
+            e.vs.addAll(Arrays.asList("One", "Two", "Three"));
+            for (int i = 0; i < e.multidimArray.length; i++)
+                for (int j = 0; j < e.multidimArray[i].length; j++)
+                    e.multidimArray[i][j] = Integer.toString(i) + Integer.toString(j);
+            e.bf = IBaz.BitField.VALL;
+            e.anotherStruct.first = "James";
+            e.anotherStruct.last = "Bond";
+            Expect(e.toString(),
+                "{.number = 127, .anotherNumber = 100, .s = string!, " +
+                ".vs = [One, Two, Three], .multidimArray = [[00, 01], [10, 11]], " +
+                ".sArray = [null, null, null], .anotherStruct = {.first = James, .last = Bond}, " +
+                ".bf = V0 | V1 | V2 | V3 | VALL}");
+            Expect(IBaz.BitField.toString(IBaz.BitField.VALL), "VALL");
+            Expect(IBaz.BitField.toString((byte)(IBaz.BitField.V0 | IBaz.BitField.V2)), "0x5");
+            Expect(IBaz.BitField.dumpBitfield(IBaz.BitField.VALL), "V0 | V1 | V2 | V3 | VALL");
+            Expect(IBaz.BitField.dumpBitfield((byte)(IBaz.BitField.V1 | IBaz.BitField.V3 | 0xF0)),
+                "V1 | V3 | 0xf0");
+
+            Expect(proxy.toString(), IBaz.kInterfaceName + "@Proxy");
+        }
+
         // --- DEATH RECIPIENT TESTING ---
         // This must always be done last, since it will kill the native server process
         HidlDeathRecipient recipient1 = new HidlDeathRecipient();
diff --git a/utils/Formatter.cpp b/utils/Formatter.cpp
index df3a5a4..4d3771b 100644
--- a/utils/Formatter.cpp
+++ b/utils/Formatter.cpp
@@ -86,6 +86,16 @@
     return this->block(block);
 }
 
+Formatter &Formatter::sTry(std::function<void(void)> block) {
+    (*this) << "try ";
+    return this->block(block);
+}
+
+Formatter &Formatter::sCatch(const std::string &exception, std::function<void(void)> block) {
+    (*this) << " catch (" << exception << ") ";
+    return this->block(block);
+}
+
 Formatter &Formatter::operator<<(const std::string &out) {
     const size_t len = out.length();
     size_t start = 0;
diff --git a/utils/include/hidl-util/Formatter.h b/utils/include/hidl-util/Formatter.h
index 7a27287..d1f76fb 100644
--- a/utils/include/hidl-util/Formatter.h
+++ b/utils/include/hidl-util/Formatter.h
@@ -79,6 +79,15 @@
     Formatter &sElseIf(const std::string &cond, std::function<void(void)> block);
     Formatter &sElse(std::function<void(void)> block);
 
+    // out.sTry([&] {
+    //     out << "throw RemoteException();\n"
+    // }).sCatch("RemoteException ex", [&] {
+    //     out << "ex.printStackTrace();\n"
+    // }).endl();
+    // note that there will be a space before the "catch"-s.
+    Formatter &sTry(std::function<void(void)> block);
+    Formatter &sCatch(const std::string &exception, std::function<void(void)> block);
+
     Formatter &operator<<(const std::string &out);
     Formatter &operator<<(size_t n);