1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.internal.util.dump;
17 
18 import android.util.ArrayMap;
19 import android.util.Dumpable;
20 import android.util.DumpableContainer;
21 import android.util.IndentingPrintWriter;
22 import android.util.Log;
23 
24 import java.io.PrintWriter;
25 import java.util.Objects;
26 
27 /**
28  * Helper class for {@link DumpableContainer} implementations - they can "implement it by
29  * association", i.e., by delegating the interface methods to a {@code DumpableContainerImpl}.
30  *
31  * <p>This class is not thread safe.
32  *
33  * @hide
34  */
35 public final class DumpableContainerImpl implements DumpableContainer {
36 
37     private static final String TAG = DumpableContainerImpl.class.getSimpleName();
38 
39     private static final boolean DEBUG = false;
40 
41     private final ArrayMap<String, Dumpable> mDumpables = new ArrayMap<>();
42 
43     @Override
addDumpable(Dumpable dumpable)44     public boolean addDumpable(Dumpable dumpable) {
45         Objects.requireNonNull(dumpable, "dumpable");
46         String name = dumpable.getDumpableName();
47         Objects.requireNonNull(name, () -> "name of" + dumpable);
48 
49         if (mDumpables.containsKey(name)) {
50             if (DEBUG) {
51                 Log.d(TAG, "addDumpable(): ignoring " + dumpable + " as there is already a dumpable"
52                         + " with that name (" + name + "): " + mDumpables.get(name));
53             }
54             return false;
55         }
56 
57         if (DEBUG) {
58             Log.d(TAG, "Adding " + name + " -> " + dumpable);
59         }
60         mDumpables.put(name,  dumpable);
61         return true;
62     }
63 
64     @Override
removeDumpable(Dumpable dumpable)65     public boolean removeDumpable(Dumpable dumpable) {
66         Objects.requireNonNull(dumpable, "dumpable");
67         String name = dumpable.getDumpableName();
68         if (name == null) {
69             if (DEBUG) {
70                 Log.d(TAG, "Tried to remove nameless dumpable: " + dumpable);
71             }
72             return false;
73         }
74 
75         Dumpable candidate = mDumpables.get(name);
76         if (candidate == null) {
77             if (DEBUG) {
78                 Log.d(TAG, "Dumpable with name " + name + " not found");
79             }
80             return false;
81         }
82 
83         // Make sure it's the right one
84         if (candidate != dumpable) {
85             Log.w(TAG, "removeDumpable(): passed dumpable (" + dumpable + ") named " + name
86                     + ", but internal dumpable with that name is " + candidate);
87             return false;
88         }
89         if (DEBUG) {
90             Log.d(TAG, "Removing dumpable named " + name);
91         }
92         mDumpables.remove(name);
93         return true;
94     }
95 
96     /**
97      * Dumps the number of dumpable, without a newline.
98      */
dumpNumberDumpables(IndentingPrintWriter writer)99     private int dumpNumberDumpables(IndentingPrintWriter writer) {
100         int size = mDumpables.size();
101         if (size == 0) {
102             writer.print("No dumpables");
103         } else {
104             writer.print(size); writer.print(" dumpables");
105         }
106         return size;
107     }
108 
109     /**
110      * Lists the name of all dumpables to the given {@code writer}.
111      */
listDumpables(String prefix, PrintWriter writer)112     public void listDumpables(String prefix, PrintWriter writer) {
113         IndentingPrintWriter ipw = new IndentingPrintWriter(writer, prefix, prefix);
114 
115         int size = dumpNumberDumpables(ipw);
116         if (size == 0) {
117             ipw.println();
118             return;
119         }
120         ipw.print(": ");
121         for (int i = 0; i < size; i++) {
122             ipw.print(mDumpables.keyAt(i));
123             if (i < size - 1) ipw.print(' ');
124         }
125         ipw.println();
126     }
127 
128     /**
129      * Dumps the content of all dumpables to the given {@code writer}.
130      */
dumpAllDumpables(String prefix, PrintWriter writer, String[] args)131     public void dumpAllDumpables(String prefix, PrintWriter writer, String[] args) {
132         IndentingPrintWriter ipw = new IndentingPrintWriter(writer, prefix, prefix);
133         int size = dumpNumberDumpables(ipw);
134         if (size == 0) {
135             ipw.println();
136             return;
137         }
138         ipw.println(":");
139 
140         for (int i = 0; i < size; i++) {
141             String dumpableName = mDumpables.keyAt(i);
142             ipw.print('#'); ipw.print(i); ipw.print(": "); ipw.println(dumpableName);
143             Dumpable dumpable = mDumpables.valueAt(i);
144             indentAndDump(ipw, dumpable, args);
145         }
146     }
147 
indentAndDump(IndentingPrintWriter writer, Dumpable dumpable, String[] args)148     private void indentAndDump(IndentingPrintWriter writer, Dumpable dumpable, String[] args) {
149         writer.increaseIndent();
150         try {
151             dumpable.dump(writer, args);
152         } finally {
153             writer.decreaseIndent();
154         }
155     }
156 
157     /**
158      * Dumps the content of a specific dumpable to the given {@code writer}.
159      */
160     @SuppressWarnings("resource") // cannot close ipw as it would close writer
dumpOneDumpable(String prefix, PrintWriter writer, String dumpableName, String[] args)161     public void dumpOneDumpable(String prefix, PrintWriter writer, String dumpableName,
162             String[] args) {
163         IndentingPrintWriter ipw = new IndentingPrintWriter(writer, prefix, prefix);
164         Dumpable dumpable = mDumpables.get(dumpableName);
165         if (dumpable == null) {
166             ipw.print("No "); ipw.println(dumpableName);
167             return;
168         }
169         ipw.print(dumpableName); ipw.println(':');
170         indentAndDump(ipw, dumpable, args);
171     }
172 }
173