Merge "-Landroidbp-impl shouldn't require libhwbinder"
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 2dfc2bc..dcd426a 100644
--- a/CompoundType.cpp
+++ b/CompoundType.cpp
@@ -565,8 +565,19 @@
     ////////////////////////////////////////////////////////////////////////////
 
     if (canCheckEquality()) {
-        out << "public final boolean equals(" << localName() << " other) ";
+        out << "@Override\npublic final boolean equals(Object otherObject) ";
         out.block([&] {
+            out.sIf("this == otherObject", [&] {
+                out << "return true;\n";
+            }).endl();
+            out.sIf("otherObject == null", [&] {
+                out << "return false;\n";
+            }).endl();
+            // Class is final, so we only need to check instanceof, not getClass
+            out.sIf("!(otherObject instanceof " + fullJavaName() + ")", [&] {
+                out << "return false;\n";
+            }).endl();
+            out << fullJavaName() << " other = (" << fullJavaName() << ")otherObject;\n";
             for (const auto &field : *mFields) {
                 std::string condition = field->type().isScalar()
                     ? "this." + field->name() + " != other." + field->name()
@@ -579,33 +590,52 @@
             out << "return true;\n";
         }).endl().endl();
 
-        out << "public final int hashCode() ";
+        out << "@Override\npublic final int hashCode() ";
         out.block([&] {
-            out << "return java.util.Objects.hash(";
-            bool first = true;
-            for (const auto &field : *mFields) {
-                if (!first) {
-                    out << ", ";
-                }
-                first = false;
-                if (field->type().isArray()) {
-                    const ArrayType &type = static_cast<const ArrayType &>(field->type());
-                    if (type.countDimensions() == 1 &&
-                        type.getElementType()->resolveToScalarType() != nullptr) {
-                        out << "java.util.Arrays.hashCode(this." << field->name() << ")";
-                    } else {
-                        out << "java.util.Arrays.deepHashCode(this." << field->name() << ")";
+            out << "return java.util.Objects.hash(\n";
+            out.indent(2, [&] {
+                bool first = true;
+                for (const auto &field : *mFields) {
+                    if (!first) {
+                        out << ", \n";
                     }
-                } else {
-                    out << "this." << field->name();
+                    first = false;
+                    if (field->type().isArray()) {
+                        const ArrayType &type = static_cast<const ArrayType &>(field->type());
+                        if (type.countDimensions() == 1 &&
+                            type.getElementType()->resolveToScalarType() != nullptr) {
+                            out << "java.util.Arrays.hashCode(this." << field->name() << ")";
+                        } else {
+                            out << "java.util.Arrays.deepHashCode(this." << field->name() << ")";
+                        }
+                    } else {
+                        out << "this." << field->name();
+                    }
                 }
-            }
+            });
             out << ");\n";
         }).endl().endl();
     }
 
     ////////////////////////////////////////////////////////////////////////////
 
+    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 87cc19b..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);
@@ -267,6 +273,7 @@
     emitBitFieldBitwiseAssignmentOperator(out, "|");
     emitBitFieldBitwiseAssignmentOperator(out, "&");
 
+    // toString for bitfields, equivalent to dumpBitfield in Java
     out << "template<typename>\n"
         << "std::string toString("
         << resolveToScalarType()->getCppArgumentType()
@@ -276,14 +283,10 @@
         << resolveToScalarType()->getCppArgumentType()
         << " o);\n\n";
 
-    out << "inline std::string toString("
+    // toString for enum itself
+    out << "std::string toString("
         << getCppArgumentType()
-        << " o) ";
-
-    out.block([&] {
-        out << "return toString<" << getCppStackType() << ">("
-            << "static_cast<" << resolveToScalarType()->getCppStackType() << ">(o));\n";
-    }).endl().endl();
+        << " o);\n\n";
 
     return OK;
 }
@@ -295,20 +298,30 @@
 
     out << "template<>\n"
         << "std::string toString<" << getCppStackType() << ">("
-        << resolveToScalarType()->getCppArgumentType()
+        << scalarType->getCppArgumentType()
         << " o) ";
     out.block([&] {
         // include toHexString for scalar types
         out << "using ::android::hardware::details::toHexString;\n"
-            << "std::string os;\n";
-        out << "bool first = true;\n";
+            << "std::string os;\n"
+            << getBitfieldType()->getCppStackType() << " flipped = 0;\n"
+            << "bool first = true;\n";
         for (EnumValue *value : values()) {
-            out.sIf("o & " + fullName() + "::" + value->name(), [&] {
-                out << "os += (first ? \"\" : \" | \"); "
+            std::string valueName = fullName() + "::" + value->name();
+            out.sIf("(o & " + valueName + ")" +
+                    " == static_cast<" + scalarType->getCppStackType() +
+                    ">(" + valueName + ")", [&] {
+                out << "os += (first ? \"\" : \" | \");\n"
+                    << "os += \"" << value->name() << "\";\n"
                     << "first = false;\n"
-                    << "os += \"" << value->name() << "\";\n";
+                    << "flipped |= " << valueName << ";\n";
             }).endl();
         }
+        // put remaining bits
+        out.sIf("o != flipped", [&] {
+            out << "os += (first ? \"\" : \" | \");\n";
+            scalarType->emitHexDump(out, "os", "o & (~flipped)");
+        });
         out << "os += \" (\";\n";
         scalarType->emitHexDump(out, "os", "o");
         out << "os += \")\";\n";
@@ -316,14 +329,33 @@
         out << "return os;\n";
     }).endl().endl();
 
+    out << "std::string toString("
+        << getCppArgumentType()
+        << " o) ";
+
+    out.block([&] {
+        out << "using ::android::hardware::details::toHexString;\n";
+        for (EnumValue *value : values()) {
+            out.sIf("o == " + fullName() + "::" + value->name(), [&] {
+                out << "return \"" << value->name() << "\";\n";
+            }).endl();
+        }
+        out << "std::string os;\n";
+        scalarType->emitHexDump(out, "os",
+            "static_cast<" + scalarType->getCppStackType() + ">(o)");
+        out << "return os;\n";
+    }).endl().endl();
+
     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";
 
@@ -361,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";
 
@@ -411,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;
@@ -682,6 +758,10 @@
     return resolveToScalarType()->isElidableType();
 }
 
+bool BitFieldType::canCheckEquality() const {
+    return resolveToScalarType()->canCheckEquality();
+}
+
 status_t BitFieldType::emitVtsAttributeType(Formatter &out) const {
     out << "type: " << getVtsType() << "\n";
     out << "scalar_type: \""
@@ -713,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.
@@ -720,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 db5efdc..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);
 };
@@ -144,6 +154,8 @@
 
     bool isElidableType() const override;
 
+    bool canCheckEquality() const override;
+
     const ScalarType *resolveToScalarType() const override;
 
     std::string getCppType(StorageMode mode,
@@ -157,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;
@@ -174,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/test/main.cpp b/test/main.cpp
index 6dda6b2..959eca6 100644
--- a/test/main.cpp
+++ b/test/main.cpp
@@ -582,6 +582,22 @@
     // statement can be written here.
 }
 
+TEST_F(HidlTest, EnumToStringTest) {
+    using namespace std::string_literals;
+    using ::android::hardware::tests::foo::V1_0::toString;
+    // toString for enum
+    EXPECT_EQ(toString(IFoo::BitField::V0), "V0"s);
+    EXPECT_EQ(toString(static_cast<IFoo::BitField>(0)), "0"s)
+            << "Invalid enum isn't stringified correctly.";
+    EXPECT_EQ(toString(static_cast<IFoo::BitField>(IFoo::BitField::V0 | IFoo::BitField::V2)), "0x5"s)
+            << "Invalid enum isn't stringified correctly.";
+    // dump bitfields
+    EXPECT_EQ(toString<IFoo::BitField>(0 | IFoo::BitField::V0), "V0 (0x1)"s);
+    EXPECT_EQ(toString<IFoo::BitField>(0 | IFoo::BitField::V0 | IFoo::BitField::V2), "V0 | V2 (0x5)"s);
+    EXPECT_EQ(toString<IFoo::BitField>(0xF), "V0 | V1 | V2 | V3 | VALL (0xf)"s);
+    EXPECT_EQ(toString<IFoo::BitField>(0xFF), "V0 | V1 | V2 | V3 | VALL | 0xf0 (0xff)"s);
+}
+
 TEST_F(HidlTest, PingTest) {
     EXPECT_OK(manager->ping());
 }
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);