DualDumpOutputStream.java revision 371a3b879ba82bbe5a4d914328a20659131d0220
1/*
2 * Copyright (C) 2018 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
17package com.android.internal.util.dump;
18
19import android.annotation.NonNull;
20import android.annotation.Nullable;
21import android.util.Log;
22import android.util.proto.ProtoOutputStream;
23
24import com.android.internal.util.IndentingPrintWriter;
25
26import java.nio.charset.StandardCharsets;
27import java.util.ArrayList;
28import java.util.Arrays;
29import java.util.LinkedHashMap;
30import java.util.LinkedList;
31
32/**
33 * Dump either to a proto or a print writer using the same interface.
34 *
35 * <p>This mirrors the interface of {@link ProtoOutputStream}.
36 */
37public class DualDumpOutputStream {
38    private static final String LOG_TAG = DualDumpOutputStream.class.getSimpleName();
39
40    // When writing to a proto, the proto
41    private final @Nullable ProtoOutputStream mProtoStream;
42
43    // When printing in clear text, the writer
44    private final @Nullable IndentingPrintWriter mIpw;
45    // Temporary storage of data when printing to mIpw
46    private final LinkedList<DumpObject> mDumpObjects = new LinkedList<>();
47
48    private static abstract class Dumpable {
49        final String name;
50
51        private Dumpable(String name) {
52            this.name = name;
53        }
54
55        abstract void print(IndentingPrintWriter ipw, boolean printName);
56    }
57
58    private static class DumpObject extends Dumpable {
59        private final LinkedHashMap<String, ArrayList<Dumpable>> mSubObjects = new LinkedHashMap<>();
60
61        private DumpObject(String name) {
62            super(name);
63        }
64
65        @Override
66        void print(IndentingPrintWriter ipw, boolean printName) {
67            if (printName) {
68                ipw.println(name + "={");
69            } else {
70                ipw.println("{");
71            }
72            ipw.increaseIndent();
73
74            for (ArrayList<Dumpable> subObject: mSubObjects.values()) {
75                int numDumpables = subObject.size();
76
77                if (numDumpables == 1) {
78                    subObject.get(0).print(ipw, true);
79                } else {
80                    ipw.println(subObject.get(0).name + "=[");
81                    ipw.increaseIndent();
82
83                    for (int i = 0; i < numDumpables; i++) {
84                        subObject.get(i).print(ipw, false);
85                    }
86
87                    ipw.decreaseIndent();
88                    ipw.println("]");
89                }
90            }
91
92            ipw.decreaseIndent();
93            ipw.println("}");
94        }
95
96        /**
97         * Add new field / subobject to this object.
98         *
99         * <p>If a name is added twice, they will be printed as a array
100         *
101         * @param fieldName name of the field added
102         * @param d The dumpable to add
103         */
104        public void add(String fieldName, Dumpable d) {
105            ArrayList<Dumpable> l = mSubObjects.get(fieldName);
106
107            if (l == null) {
108                l = new ArrayList<>(1);
109                mSubObjects.put(fieldName, l);
110            }
111
112            l.add(d);
113        }
114    }
115
116    private static class DumpField extends Dumpable {
117        private final String mValue;
118
119        private DumpField(String name, String value) {
120            super(name);
121            this.mValue = value;
122        }
123
124        @Override
125        void print(IndentingPrintWriter ipw, boolean printName) {
126            if (printName) {
127                ipw.println(name + "=" + mValue);
128            } else {
129                ipw.println(mValue);
130            }
131        }
132    }
133
134    /**
135     * Create a new DualDumpOutputStream.
136     *
137     * @param proto the {@link ProtoOutputStream}
138     */
139    public DualDumpOutputStream(@NonNull ProtoOutputStream proto) {
140        mProtoStream = proto;
141        mIpw = null;
142    }
143
144    /**
145     * Create a new DualDumpOutputStream.
146     *
147     * @param ipw the {@link IndentingPrintWriter}
148     */
149    public DualDumpOutputStream(@NonNull IndentingPrintWriter ipw) {
150        mProtoStream = null;
151        mIpw = ipw;
152
153        // Add root object
154        mDumpObjects.add(new DumpObject(null));
155    }
156
157    public void write(@NonNull String fieldName, long fieldId, double val) {
158        if (mProtoStream != null) {
159            mProtoStream.write(fieldId, val);
160        } else {
161            mDumpObjects.getLast().add(fieldName, new DumpField(fieldName, String.valueOf(val)));
162        }
163    }
164
165    public void write(@NonNull String fieldName, long fieldId, boolean val) {
166        if (mProtoStream != null) {
167            mProtoStream.write(fieldId, val);
168        } else {
169            mDumpObjects.getLast().add(fieldName, new DumpField(fieldName, String.valueOf(val)));
170        }
171    }
172
173    public void write(@NonNull String fieldName, long fieldId, int val) {
174        if (mProtoStream != null) {
175            mProtoStream.write(fieldId, val);
176        } else {
177            mDumpObjects.getLast().add(fieldName, new DumpField(fieldName, String.valueOf(val)));
178        }
179    }
180
181    public void write(@NonNull String fieldName, long fieldId, float val) {
182        if (mProtoStream != null) {
183            mProtoStream.write(fieldId, val);
184        } else {
185            mDumpObjects.getLast().add(fieldName, new DumpField(fieldName, String.valueOf(val)));
186        }
187    }
188
189    public void write(@NonNull String fieldName, long fieldId, byte[] val) {
190        if (mProtoStream != null) {
191            mProtoStream.write(fieldId, val);
192        } else {
193            mDumpObjects.getLast().add(fieldName, new DumpField(fieldName, Arrays.toString(val)));
194        }
195    }
196
197    public void write(@NonNull String fieldName, long fieldId, long val) {
198        if (mProtoStream != null) {
199            mProtoStream.write(fieldId, val);
200        } else {
201            mDumpObjects.getLast().add(fieldName, new DumpField(fieldName, String.valueOf(val)));
202        }
203    }
204
205    public void write(@NonNull String fieldName, long fieldId, @Nullable String val) {
206        if (mProtoStream != null) {
207            mProtoStream.write(fieldId, val);
208        } else {
209            mDumpObjects.getLast().add(fieldName, new DumpField(fieldName, String.valueOf(val)));
210        }
211    }
212
213    public long start(@NonNull String fieldName, long fieldId) {
214        if (mProtoStream != null) {
215            return mProtoStream.start(fieldId);
216        } else {
217            DumpObject d = new DumpObject(fieldName);
218            mDumpObjects.getLast().add(fieldName, d);
219            mDumpObjects.addLast(d);
220            return System.identityHashCode(d);
221        }
222    }
223
224    public void end(long token) {
225        if (mProtoStream != null) {
226            mProtoStream.end(token);
227        } else {
228            if (System.identityHashCode(mDumpObjects.getLast()) != token) {
229                Log.w(LOG_TAG, "Unexpected token for ending " + mDumpObjects.getLast().name
230                                + " at " + Arrays.toString(Thread.currentThread().getStackTrace()));
231            }
232            mDumpObjects.removeLast();
233        }
234    }
235
236    public void flush() {
237        if (mProtoStream != null) {
238            mProtoStream.flush();
239        } else {
240            if (mDumpObjects.size() == 1) {
241                mDumpObjects.getFirst().print(mIpw, false);
242
243                // Reset root object
244                mDumpObjects.clear();
245                mDumpObjects.add(new DumpObject(null));
246            }
247
248            mIpw.flush();
249        }
250    }
251
252    /**
253     * Add a dump from a different service into this dump.
254     *
255     * <p>Only for clear text dump. For proto dump use {@link #write(String, long, byte[])}.
256     *
257     * @param fieldName The name of the field
258     * @param nestedState The state of the dump
259     */
260    public void writeNested(@NonNull String fieldName, byte[] nestedState) {
261        if (mIpw == null) {
262            Log.w(LOG_TAG, "writeNested does not work for proto logging");
263            return;
264        }
265
266        mDumpObjects.getLast().add(fieldName,
267                new DumpField(fieldName, (new String(nestedState, StandardCharsets.UTF_8)).trim()));
268    }
269
270    /**
271     * @return {@code true} iff we are dumping to a proto
272     */
273    public boolean isProto() {
274        return mProtoStream != null;
275    }
276}
277