blob: 3ac38edaddb435a700ece3d79a613582f02da4d5 [file] [log] [blame]
/*
* Copyright (C) 2018 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.
*/
package com.android.internal.util.dump;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.util.Log;
import android.util.proto.ProtoOutputStream;
import com.android.internal.util.IndentingPrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.LinkedList;
/**
* Dump either to a proto or a print writer using the same interface.
*
* <p>This mirrors the interface of {@link ProtoOutputStream}.
*/
public class DualDumpOutputStream {
private static final String LOG_TAG = DualDumpOutputStream.class.getSimpleName();
// When writing to a proto, the proto
private final @Nullable ProtoOutputStream mProtoStream;
// When printing in clear text, the writer
private final @Nullable IndentingPrintWriter mIpw;
// Temporary storage of data when printing to mIpw
private final LinkedList<DumpObject> mDumpObjects = new LinkedList<>();
private static abstract class Dumpable {
final String name;
private Dumpable(String name) {
this.name = name;
}
abstract void print(IndentingPrintWriter ipw, boolean printName);
}
private static class DumpObject extends Dumpable {
private final LinkedHashMap<String, ArrayList<Dumpable>> mSubObjects = new LinkedHashMap<>();
private DumpObject(String name) {
super(name);
}
@Override
void print(IndentingPrintWriter ipw, boolean printName) {
if (printName) {
ipw.println(name + "={");
} else {
ipw.println("{");
}
ipw.increaseIndent();
for (ArrayList<Dumpable> subObject: mSubObjects.values()) {
int numDumpables = subObject.size();
if (numDumpables == 1) {
subObject.get(0).print(ipw, true);
} else {
ipw.println(subObject.get(0).name + "=[");
ipw.increaseIndent();
for (int i = 0; i < numDumpables; i++) {
subObject.get(i).print(ipw, false);
}
ipw.decreaseIndent();
ipw.println("]");
}
}
ipw.decreaseIndent();
ipw.println("}");
}
/**
* Add new field / subobject to this object.
*
* <p>If a name is added twice, they will be printed as a array
*
* @param fieldName name of the field added
* @param d The dumpable to add
*/
public void add(String fieldName, Dumpable d) {
ArrayList<Dumpable> l = mSubObjects.get(fieldName);
if (l == null) {
l = new ArrayList<>(1);
mSubObjects.put(fieldName, l);
}
l.add(d);
}
}
private static class DumpField extends Dumpable {
private final String mValue;
private DumpField(String name, String value) {
super(name);
this.mValue = value;
}
@Override
void print(IndentingPrintWriter ipw, boolean printName) {
if (printName) {
ipw.println(name + "=" + mValue);
} else {
ipw.println(mValue);
}
}
}
/**
* Create a new DualDumpOutputStream.
*
* @param proto the {@link ProtoOutputStream}
*/
public DualDumpOutputStream(@NonNull ProtoOutputStream proto) {
mProtoStream = proto;
mIpw = null;
}
/**
* Create a new DualDumpOutputStream.
*
* @param ipw the {@link IndentingPrintWriter}
*/
public DualDumpOutputStream(@NonNull IndentingPrintWriter ipw) {
mProtoStream = null;
mIpw = ipw;
// Add root object
mDumpObjects.add(new DumpObject(null));
}
public void write(@NonNull String fieldName, long fieldId, double val) {
if (mProtoStream != null) {
mProtoStream.write(fieldId, val);
} else {
mDumpObjects.getLast().add(fieldName, new DumpField(fieldName, String.valueOf(val)));
}
}
public void write(@NonNull String fieldName, long fieldId, boolean val) {
if (mProtoStream != null) {
mProtoStream.write(fieldId, val);
} else {
mDumpObjects.getLast().add(fieldName, new DumpField(fieldName, String.valueOf(val)));
}
}
public void write(@NonNull String fieldName, long fieldId, int val) {
if (mProtoStream != null) {
mProtoStream.write(fieldId, val);
} else {
mDumpObjects.getLast().add(fieldName, new DumpField(fieldName, String.valueOf(val)));
}
}
public void write(@NonNull String fieldName, long fieldId, float val) {
if (mProtoStream != null) {
mProtoStream.write(fieldId, val);
} else {
mDumpObjects.getLast().add(fieldName, new DumpField(fieldName, String.valueOf(val)));
}
}
public void write(@NonNull String fieldName, long fieldId, byte[] val) {
if (mProtoStream != null) {
mProtoStream.write(fieldId, val);
} else {
mDumpObjects.getLast().add(fieldName, new DumpField(fieldName, Arrays.toString(val)));
}
}
public void write(@NonNull String fieldName, long fieldId, long val) {
if (mProtoStream != null) {
mProtoStream.write(fieldId, val);
} else {
mDumpObjects.getLast().add(fieldName, new DumpField(fieldName, String.valueOf(val)));
}
}
public void write(@NonNull String fieldName, long fieldId, @Nullable String val) {
if (mProtoStream != null) {
mProtoStream.write(fieldId, val);
} else {
mDumpObjects.getLast().add(fieldName, new DumpField(fieldName, String.valueOf(val)));
}
}
public long start(@NonNull String fieldName, long fieldId) {
if (mProtoStream != null) {
return mProtoStream.start(fieldId);
} else {
DumpObject d = new DumpObject(fieldName);
mDumpObjects.getLast().add(fieldName, d);
mDumpObjects.addLast(d);
return System.identityHashCode(d);
}
}
public void end(long token) {
if (mProtoStream != null) {
mProtoStream.end(token);
} else {
if (System.identityHashCode(mDumpObjects.getLast()) != token) {
Log.w(LOG_TAG, "Unexpected token for ending " + mDumpObjects.getLast().name
+ " at " + Arrays.toString(Thread.currentThread().getStackTrace()));
}
mDumpObjects.removeLast();
}
}
public void flush() {
if (mProtoStream != null) {
mProtoStream.flush();
} else {
if (mDumpObjects.size() == 1) {
mDumpObjects.getFirst().print(mIpw, false);
// Reset root object
mDumpObjects.clear();
mDumpObjects.add(new DumpObject(null));
}
mIpw.flush();
}
}
/**
* Add a dump from a different service into this dump.
*
* <p>Only for clear text dump. For proto dump use {@link #write(String, long, byte[])}.
*
* @param fieldName The name of the field
* @param nestedState The state of the dump
*/
public void writeNested(@NonNull String fieldName, byte[] nestedState) {
if (mIpw == null) {
Log.w(LOG_TAG, "writeNested does not work for proto logging");
return;
}
mDumpObjects.getLast().add(fieldName,
new DumpField(fieldName, (new String(nestedState, StandardCharsets.UTF_8)).trim()));
}
/**
* @return {@code true} iff we are dumping to a proto
*/
public boolean isProto() {
return mProtoStream != null;
}
}