lshal: remove ListCommand addLine

Remove obnoxious addLine(...) in ListCommand.cpp by moving
the feature of selecting columns into the "Table" class.

Test: lshal
Test: lshal -m
Test: lshal -d (shows debug info for context hub)
Test: lshal_test
Bug: 35389839
Change-Id: Ieb4a6e544ef39c9f1a63b046a44b6a8e1416ea62
diff --git a/cmds/lshal/Android.bp b/cmds/lshal/Android.bp
index 2eed1ce..fab7bfd 100644
--- a/cmds/lshal/Android.bp
+++ b/cmds/lshal/Android.bp
@@ -28,6 +28,7 @@
         "Lshal.cpp",
         "ListCommand.cpp",
         "PipeRelay.cpp",
+        "TableEntry.cpp",
         "TextTable.cpp",
         "utils.cpp",
     ],
diff --git a/cmds/lshal/ListCommand.cpp b/cmds/lshal/ListCommand.cpp
index 31c42e7..6ee162b 100644
--- a/cmds/lshal/ListCommand.cpp
+++ b/cmds/lshal/ListCommand.cpp
@@ -35,7 +35,6 @@
 
 #include "Lshal.h"
 #include "PipeRelay.h"
-#include "TextTable.h"
 #include "Timeout.h"
 #include "utils.h"
 
@@ -207,48 +206,6 @@
     }
 }
 
-void ListCommand::addLine(TextTable *textTable, const std::string &interfaceName,
-                          const std::string &transport, const std::string &arch,
-                          const std::string &threadUsage, const std::string &server,
-                          const std::string &serverCmdline, const std::string &address,
-                          const std::string &clients, const std::string &clientCmdlines) const {
-    std::vector<std::string> columns;
-    for (TableColumnType type : mSelectedColumns) {
-        switch (type) {
-            case TableColumnType::INTERFACE_NAME: {
-                columns.push_back(interfaceName);
-            } break;
-            case TableColumnType::TRANSPORT: {
-                columns.push_back(transport);
-            } break;
-            case TableColumnType::ARCH: {
-                columns.push_back(arch);
-            } break;
-            case TableColumnType::THREADS: {
-                columns.push_back(threadUsage);
-            } break;
-            case TableColumnType::SERVER_ADDR: {
-                columns.push_back(address);
-            } break;
-            case TableColumnType::SERVER_PID: {
-                if (mEnableCmdlines) {
-                    columns.push_back(serverCmdline);
-                } else {
-                    columns.push_back(server);
-                }
-            } break;
-            case TableColumnType::CLIENT_PIDS: {
-                if (mEnableCmdlines) {
-                    columns.push_back(clientCmdlines);
-                } else {
-                    columns.push_back(clients);
-                }
-            } break;
-        }
-    }
-    textTable->add(std::move(columns));
-}
-
 static inline bool findAndBumpVersion(vintf::ManifestHal* hal, const vintf::Version& version) {
     for (vintf::Version& v : hal->versions) {
         if (v.majorVer == version.majorVer) {
@@ -372,24 +329,6 @@
     mOut << vintf::gHalManifestConverter(manifest);
 }
 
-static const std::string &getArchString(Architecture arch) {
-    static const std::string sStr64 = "64";
-    static const std::string sStr32 = "32";
-    static const std::string sStrBoth = "32+64";
-    static const std::string sStrUnknown = "";
-    switch (arch) {
-        case ARCH64:
-            return sStr64;
-        case ARCH32:
-            return sStr32;
-        case ARCH_BOTH:
-            return sStrBoth;
-        case ARCH_UNKNOWN: // fall through
-        default:
-            return sStrUnknown;
-    }
-}
-
 static Architecture fromBaseArchitecture(::android::hidl::base::V1_0::DebugInfo::Architecture a) {
     switch (a) {
         case ::android::hidl::base::V1_0::DebugInfo::Architecture::IS_64BIT:
@@ -402,63 +341,43 @@
     }
 }
 
-void ListCommand::addLine(TextTable *table, const TableEntry &entry) {
-    addLine(table, entry.interfaceName, entry.transport, getArchString(entry.arch),
-            entry.getThreadUsage(),
-            entry.serverPid == NO_PID ? "N/A" : std::to_string(entry.serverPid),
-            entry.serverCmdline,
-            entry.serverObjectAddress == NO_PTR ? "N/A" : toHexString(entry.serverObjectAddress),
-            join(entry.clientPids, " "), join(entry.clientCmdlines, ";"));
-}
-
 void ListCommand::dumpTable() {
     if (mNeat) {
-        TextTable textTable;
-        forEachTable([this, &textTable](const Table &table) {
-            for (const auto &entry : table) addLine(&textTable, entry);
-        });
-        textTable.dump(mOut.buf());
+        MergedTable({&mServicesTable, &mPassthroughRefTable, &mImplementationsTable})
+            .createTextTable().dump(mOut.buf());
         return;
     }
 
-    mServicesTable.description =
-            "All binderized services (registered services through hwservicemanager)";
-    mPassthroughRefTable.description =
+    mServicesTable.setDescription(
+            "All binderized services (registered services through hwservicemanager)");
+    mPassthroughRefTable.setDescription(
             "All interfaces that getService() has ever return as a passthrough interface;\n"
             "PIDs / processes shown below might be inaccurate because the process\n"
             "might have relinquished the interface or might have died.\n"
             "The Server / Server CMD column can be ignored.\n"
             "The Clients / Clients CMD column shows all process that have ever dlopen'ed \n"
-            "the library and successfully fetched the passthrough implementation.";
-    mImplementationsTable.description =
-            "All available passthrough implementations (all -impl.so files)";
+            "the library and successfully fetched the passthrough implementation.");
+    mImplementationsTable.setDescription(
+            "All available passthrough implementations (all -impl.so files)");
 
     forEachTable([this](const Table &table) {
-        TextTable textTable;
 
-        textTable.add(table.description);
-        addLine(&textTable, "Interface", "Transport", "Arch", "Thread Use", "Server", "Server CMD",
-                "PTR", "Clients", "Clients CMD");
-
-        for (const auto &entry : table) {
-            addLine(&textTable, entry);
-            // We're only interested in dumping debug info for already
-            // instantiated services. There's little value in dumping the
-            // debug info for a service we create on the fly, so we only operate
-            // on the "mServicesTable".
-            if (mEmitDebugInfo && &table == &mServicesTable) {
+        // We're only interested in dumping debug info for already
+        // instantiated services. There's little value in dumping the
+        // debug info for a service we create on the fly, so we only operate
+        // on the "mServicesTable".
+        std::function<std::string(const std::string&)> emitDebugInfo = nullptr;
+        if (mEmitDebugInfo && &table == &mServicesTable) {
+            emitDebugInfo = [this](const auto& iName) {
                 std::stringstream out;
-                auto pair = splitFirst(entry.interfaceName, '/');
+                auto pair = splitFirst(iName, '/');
                 mLshal.emitDebugInfo(pair.first, pair.second, {}, out,
                                      NullableOStream<std::ostream>(nullptr));
-                textTable.add(out.str());
-            }
+                return out.str();
+            };
         }
-
-        // Add empty line after each table
-        textTable.add();
-
-        textTable.dump(mOut.buf());
+        table.createTextTable(mNeat, emitDebugInfo).dump(mOut.buf());
+        mOut << std::endl;
     });
 }
 
@@ -489,7 +408,7 @@
             mErr << "Error: Unknown source of entry " << source << std::endl;
     }
     if (table) {
-        table->entries.push_back(std::forward<TableEntry>(entry));
+        table->add(std::forward<TableEntry>(entry));
     }
 }
 
@@ -693,6 +612,9 @@
         { 0,          0,                 0,  0  }
     };
 
+    std::vector<TableColumnType> selectedColumns;
+    bool enableCmdlines = false;
+
     int optionIndex;
     int c;
     // Lshal::parseArgs has set optind to the next option to parse
@@ -728,35 +650,35 @@
             mVintf = true;
         }
         case 'i': {
-            mSelectedColumns.push_back(TableColumnType::INTERFACE_NAME);
+            selectedColumns.push_back(TableColumnType::INTERFACE_NAME);
             break;
         }
         case 't': {
-            mSelectedColumns.push_back(TableColumnType::TRANSPORT);
+            selectedColumns.push_back(TableColumnType::TRANSPORT);
             break;
         }
         case 'r': {
-            mSelectedColumns.push_back(TableColumnType::ARCH);
+            selectedColumns.push_back(TableColumnType::ARCH);
             break;
         }
         case 'p': {
-            mSelectedColumns.push_back(TableColumnType::SERVER_PID);
+            selectedColumns.push_back(TableColumnType::SERVER_PID);
             break;
         }
         case 'a': {
-            mSelectedColumns.push_back(TableColumnType::SERVER_ADDR);
+            selectedColumns.push_back(TableColumnType::SERVER_ADDR);
             break;
         }
         case 'c': {
-            mSelectedColumns.push_back(TableColumnType::CLIENT_PIDS);
+            selectedColumns.push_back(TableColumnType::CLIENT_PIDS);
             break;
         }
         case 'e': {
-            mSelectedColumns.push_back(TableColumnType::THREADS);
+            selectedColumns.push_back(TableColumnType::THREADS);
             break;
         }
         case 'm': {
-            mEnableCmdlines = true;
+            enableCmdlines = true;
             break;
         }
         case 'd': {
@@ -796,10 +718,26 @@
         return USAGE;
     }
 
-    if (mSelectedColumns.empty()) {
-        mSelectedColumns = {TableColumnType::INTERFACE_NAME, TableColumnType::THREADS,
+    if (selectedColumns.empty()) {
+        selectedColumns = {TableColumnType::INTERFACE_NAME, TableColumnType::THREADS,
                             TableColumnType::SERVER_PID, TableColumnType::CLIENT_PIDS};
     }
+
+    if (enableCmdlines) {
+        for (size_t i = 0; i < selectedColumns.size(); ++i) {
+            if (selectedColumns[i] == TableColumnType::SERVER_PID) {
+                selectedColumns[i] = TableColumnType::SERVER_CMD;
+            }
+            if (selectedColumns[i] == TableColumnType::CLIENT_PIDS) {
+                selectedColumns[i] = TableColumnType::CLIENT_CMDS;
+            }
+        }
+    }
+
+    forEachTable([&selectedColumns] (Table& table) {
+        table.setSelectedColumns(selectedColumns);
+    });
+
     return OK;
 }
 
diff --git a/cmds/lshal/ListCommand.h b/cmds/lshal/ListCommand.h
index 176d5b9..d9d56a3 100644
--- a/cmds/lshal/ListCommand.h
+++ b/cmds/lshal/ListCommand.h
@@ -82,11 +82,7 @@
     NullableOStream<std::ostream> mOut;
     NullableOStream<std::ofstream> mFileOutput = nullptr;
     TableEntryCompare mSortColumn = nullptr;
-    std::vector<TableColumnType> mSelectedColumns;
-    // If true, cmdlines will be printed instead of pid.
-    bool mEnableCmdlines = false;
 
-    // If true, calls IBase::debug(...) on each service.
     bool mEmitDebugInfo = false;
 
     // If true, output in VINTF format.
diff --git a/cmds/lshal/TableEntry.cpp b/cmds/lshal/TableEntry.cpp
new file mode 100644
index 0000000..dc6ccae
--- /dev/null
+++ b/cmds/lshal/TableEntry.cpp
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#define LOG_TAG "lshal"
+#include <android-base/logging.h>
+
+#include "TableEntry.h"
+
+#include "TextTable.h"
+#include "utils.h"
+
+namespace android {
+namespace lshal {
+
+static const std::string &getArchString(Architecture arch) {
+    static const std::string sStr64 = "64";
+    static const std::string sStr32 = "32";
+    static const std::string sStrBoth = "32+64";
+    static const std::string sStrUnknown = "";
+    switch (arch) {
+        case ARCH64:
+            return sStr64;
+        case ARCH32:
+            return sStr32;
+        case ARCH_BOTH:
+            return sStrBoth;
+        case ARCH_UNKNOWN: // fall through
+        default:
+            return sStrUnknown;
+    }
+}
+
+static std::string getTitle(TableColumnType type) {
+    switch (type) {
+        case TableColumnType::INTERFACE_NAME: {
+            return "Interface";
+        } break;
+        case TableColumnType::TRANSPORT: {
+            return "Transport";
+        } break;
+        case TableColumnType::SERVER_PID: {
+            return "Server";
+        } break;
+        case TableColumnType::SERVER_CMD: {
+            return "Server CMD";
+        }
+        case TableColumnType::SERVER_ADDR: {
+            return "PTR";
+        } break;
+        case TableColumnType::CLIENT_PIDS: {
+            return "Clients";
+        } break;
+        case TableColumnType::CLIENT_CMDS: {
+            return "Clients CMD";
+        } break;
+        case TableColumnType::ARCH: {
+            return "Arch";
+        } break;
+        case TableColumnType::THREADS: {
+            return "Thread Use";
+        } break;
+        default: {
+            LOG(FATAL) << "Should not reach here.";
+            return "";
+        }
+    }
+}
+
+std::string TableEntry::getField(TableColumnType type) const {
+    switch (type) {
+        case TableColumnType::INTERFACE_NAME: {
+            return interfaceName;
+        } break;
+        case TableColumnType::TRANSPORT: {
+            return transport;
+        } break;
+        case TableColumnType::SERVER_PID: {
+            return serverPid == NO_PID ? "N/A" : std::to_string(serverPid);
+        } break;
+        case TableColumnType::SERVER_CMD: {
+            return serverCmdline;
+        } break;
+        case TableColumnType::SERVER_ADDR: {
+            return serverObjectAddress == NO_PTR ? "N/A" : toHexString(serverObjectAddress);
+        } break;
+        case TableColumnType::CLIENT_PIDS: {
+            return join(clientPids, " ");
+        } break;
+        case TableColumnType::CLIENT_CMDS: {
+            return join(clientCmdlines, ";");
+        } break;
+        case TableColumnType::ARCH: {
+            return getArchString(arch);
+        } break;
+        case TableColumnType::THREADS: {
+            return getThreadUsage();
+        } break;
+        default: {
+            LOG(FATAL) << "Should not reach here.";
+            return "";
+        }
+    }
+}
+
+TextTable Table::createTextTable(bool neat,
+    const std::function<std::string(const std::string&)>& emitDebugInfo) const {
+
+    TextTable textTable;
+    std::vector<std::string> row;
+    if (!neat) {
+        textTable.add(mDescription);
+
+        row.clear();
+        for (TableColumnType type : mSelectedColumns) {
+            row.push_back(getTitle(type));
+        }
+        textTable.add(std::move(row));
+    }
+
+    for (const auto& entry : mEntries) {
+        row.clear();
+        for (TableColumnType type : mSelectedColumns) {
+            row.push_back(entry.getField(type));
+        }
+        textTable.add(std::move(row));
+
+        if (emitDebugInfo) {
+            std::string debugInfo = emitDebugInfo(entry.interfaceName);
+            if (!debugInfo.empty()) textTable.add(debugInfo);
+        }
+    }
+    return textTable;
+}
+
+TextTable MergedTable::createTextTable() {
+    TextTable textTable;
+    for (const Table* table : mTables) {
+        textTable.addAll(table->createTextTable());
+    }
+    return textTable;
+}
+
+} // namespace lshal
+} // namespace android
diff --git a/cmds/lshal/TableEntry.h b/cmds/lshal/TableEntry.h
index f18f38a..dac8b5e 100644
--- a/cmds/lshal/TableEntry.h
+++ b/cmds/lshal/TableEntry.h
@@ -23,6 +23,8 @@
 #include <vector>
 #include <iostream>
 
+#include "TextTable.h"
+
 namespace android {
 namespace lshal {
 
@@ -43,6 +45,18 @@
 };
 using Architecture = unsigned int;
 
+enum class TableColumnType : unsigned int {
+    INTERFACE_NAME,
+    TRANSPORT,
+    SERVER_PID,
+    SERVER_CMD,
+    SERVER_ADDR,
+    CLIENT_PIDS,
+    CLIENT_CMDS,
+    ARCH,
+    THREADS,
+};
+
 struct TableEntry {
     std::string interfaceName;
     std::string transport;
@@ -69,29 +83,46 @@
 
         return std::to_string(threadUsage) + "/" + std::to_string(threadCount);
     }
+
+    std::string getField(TableColumnType type) const;
 };
 
-struct Table {
-    using Entries = std::vector<TableEntry>;
-    std::string description;
-    Entries entries;
+using SelectedColumns = std::vector<TableColumnType>;
 
-    Entries::iterator begin() { return entries.begin(); }
-    Entries::const_iterator begin() const { return entries.begin(); }
-    Entries::iterator end() { return entries.end(); }
-    Entries::const_iterator end() const { return entries.end(); }
+class Table {
+public:
+    using Entries = std::vector<TableEntry>;
+
+    Entries::iterator begin() { return mEntries.begin(); }
+    Entries::const_iterator begin() const { return mEntries.begin(); }
+    Entries::iterator end() { return mEntries.end(); }
+    Entries::const_iterator end() const { return mEntries.end(); }
+
+    void add(TableEntry&& entry) { mEntries.push_back(std::move(entry)); }
+
+    void setSelectedColumns(const SelectedColumns& s) { mSelectedColumns = s; }
+    const SelectedColumns& getSelectedColumns() const { return mSelectedColumns; }
+
+    void setDescription(std::string&& d) { mDescription = std::move(d); }
+
+    // Write table content.
+    TextTable createTextTable(bool neat = true,
+        const std::function<std::string(const std::string&)>& emitDebugInfo = nullptr) const;
+
+private:
+    std::string mDescription;
+    Entries mEntries;
+    SelectedColumns mSelectedColumns;
 };
 
 using TableEntryCompare = std::function<bool(const TableEntry &, const TableEntry &)>;
 
-enum class TableColumnType : unsigned int {
-    INTERFACE_NAME,
-    TRANSPORT,
-    SERVER_PID,
-    SERVER_ADDR,
-    CLIENT_PIDS,
-    ARCH,
-    THREADS,
+class MergedTable {
+public:
+    MergedTable(std::vector<const Table*>&& tables) : mTables(std::move(tables)) {}
+    TextTable createTextTable();
+private:
+    std::vector<const Table*> mTables;
 };
 
 enum {
diff --git a/cmds/lshal/TextTable.cpp b/cmds/lshal/TextTable.cpp
index a35917c..eca9061 100644
--- a/cmds/lshal/TextTable.cpp
+++ b/cmds/lshal/TextTable.cpp
@@ -53,5 +53,15 @@
     }
 }
 
+void TextTable::addAll(TextTable&& other) {
+    for (auto&& row : other.mTable) {
+        if (row.isRow()) {
+            computeWidth(row.fields());
+        }
+
+        mTable.emplace_back(std::move(row));
+    }
+}
+
 } // namespace lshal
 } // namespace android
diff --git a/cmds/lshal/TextTable.h b/cmds/lshal/TextTable.h
index 4636f15..91d522a 100644
--- a/cmds/lshal/TextTable.h
+++ b/cmds/lshal/TextTable.h
@@ -21,8 +21,6 @@
 #include <string>
 #include <vector>
 
-#include "TableEntry.h"
-
 namespace android {
 namespace lshal {
 
@@ -68,6 +66,8 @@
     void add(const std::string& s) { mTable.emplace_back(s); }
     void add(std::string&& s) { mTable.emplace_back(std::move(s)); }
 
+    void addAll(TextTable&& other);
+
     // Prints the table to out, with column widths adjusted appropriately according
     // to the content.
     void dump(std::ostream& out) const;