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