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 17 package com.android.car.internal.util; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 22 import java.io.PrintWriter; 23 import java.io.Writer; 24 import java.util.Arrays; 25 26 // Copy of frameworks/base/core/java/android/util/IndentingPrintWriter.java 27 /** 28 * Lightweight wrapper around {@link PrintWriter} that automatically indents 29 * newlines based on internal state. It also automatically wraps long lines 30 * based on given line length. 31 * <p> 32 * Delays writing indent until first actual write on a newline, enabling indent 33 * modification after newline. 34 * 35 * @hide 36 */ 37 public class IndentingPrintWriter extends PrintWriter { 38 private final String mSingleIndent; 39 private final int mWrapLength; 40 41 /** Mutable version of current indent */ 42 private StringBuilder mIndentBuilder = new StringBuilder(); 43 /** Cache of current {@link #mIndentBuilder} value */ 44 private char[] mCurrentIndent; 45 /** Length of current line being built, excluding any indent */ 46 private int mCurrentLength; 47 48 /** 49 * Flag indicating if we're currently sitting on an empty line, and that 50 * next write should be prefixed with the current indent. 51 */ 52 private boolean mEmptyLine = true; 53 54 private char[] mSingleChar = new char[1]; 55 IndentingPrintWriter(@onNull Writer writer)56 public IndentingPrintWriter(@NonNull Writer writer) { 57 this(writer, " ", -1); 58 } 59 IndentingPrintWriter(@onNull Writer writer, @NonNull String singleIndent)60 public IndentingPrintWriter(@NonNull Writer writer, @NonNull String singleIndent) { 61 this(writer, singleIndent, null, -1); 62 } 63 IndentingPrintWriter(@onNull Writer writer, @NonNull String singleIndent, String prefix)64 public IndentingPrintWriter(@NonNull Writer writer, @NonNull String singleIndent, 65 String prefix) { 66 this(writer, singleIndent, prefix, -1); 67 } 68 IndentingPrintWriter(@onNull Writer writer, @NonNull String singleIndent, int wrapLength)69 public IndentingPrintWriter(@NonNull Writer writer, @NonNull String singleIndent, 70 int wrapLength) { 71 this(writer, singleIndent, null, wrapLength); 72 } 73 IndentingPrintWriter(@onNull Writer writer, @NonNull String singleIndent, @Nullable String prefix, int wrapLength)74 public IndentingPrintWriter(@NonNull Writer writer, @NonNull String singleIndent, 75 @Nullable String prefix, int wrapLength) { 76 super(writer); 77 mSingleIndent = singleIndent; 78 mWrapLength = wrapLength; 79 if (prefix != null) { 80 mIndentBuilder.append(prefix); 81 } 82 } 83 84 /** 85 * Overrides the indent set in the constructor for the next printed line. 86 * 87 * @deprecated Use the "prefix" constructor parameter 88 */ 89 @NonNull 90 @Deprecated setIndent(@onNull String indent)91 public IndentingPrintWriter setIndent(@NonNull String indent) { 92 mIndentBuilder.setLength(0); 93 mIndentBuilder.append(indent); 94 mCurrentIndent = null; 95 return this; 96 } 97 98 /** 99 * Overrides the indent set in the constructor with {@code singleIndent} repeated {@code indent} 100 * times. 101 * 102 * @deprecated Use the "prefix" constructor parameter 103 */ 104 @NonNull 105 @Deprecated setIndent(int indent)106 public IndentingPrintWriter setIndent(int indent) { 107 mIndentBuilder.setLength(0); 108 for (int i = 0; i < indent; i++) { 109 increaseIndent(); 110 } 111 return this; 112 } 113 114 /** 115 * Increases the indent starting with the next printed line. 116 */ 117 @NonNull increaseIndent()118 public IndentingPrintWriter increaseIndent() { 119 mIndentBuilder.append(mSingleIndent); 120 mCurrentIndent = null; 121 return this; 122 } 123 124 /** 125 * Decreases the indent starting with the next printed line. 126 */ 127 @NonNull decreaseIndent()128 public IndentingPrintWriter decreaseIndent() { 129 mIndentBuilder.delete(0, mSingleIndent.length()); 130 mCurrentIndent = null; 131 return this; 132 } 133 134 /** 135 * Prints a key-value pair. 136 */ 137 @NonNull print(@onNull String key, @Nullable Object value)138 public IndentingPrintWriter print(@NonNull String key, @Nullable Object value) { 139 String string; 140 if (value == null) { 141 string = "null"; 142 } else if (value.getClass().isArray()) { 143 if (value.getClass() == boolean[].class) { 144 string = Arrays.toString((boolean[]) value); 145 } else if (value.getClass() == byte[].class) { 146 string = Arrays.toString((byte[]) value); 147 } else if (value.getClass() == char[].class) { 148 string = Arrays.toString((char[]) value); 149 } else if (value.getClass() == double[].class) { 150 string = Arrays.toString((double[]) value); 151 } else if (value.getClass() == float[].class) { 152 string = Arrays.toString((float[]) value); 153 } else if (value.getClass() == int[].class) { 154 string = Arrays.toString((int[]) value); 155 } else if (value.getClass() == long[].class) { 156 string = Arrays.toString((long[]) value); 157 } else if (value.getClass() == short[].class) { 158 string = Arrays.toString((short[]) value); 159 } else { 160 string = Arrays.toString((Object[]) value); 161 } 162 } else { 163 string = String.valueOf(value); 164 } 165 print(key + "=" + string + " "); 166 return this; 167 } 168 169 /** 170 * Prints a key-value pair, using hexadecimal format for the value. 171 */ 172 @NonNull printHexInt(@onNull String key, int value)173 public IndentingPrintWriter printHexInt(@NonNull String key, int value) { 174 print(key + "=0x" + Integer.toHexString(value) + " "); 175 return this; 176 } 177 178 @Override println()179 public void println() { 180 write('\n'); 181 } 182 183 @Override write(int c)184 public void write(int c) { 185 mSingleChar[0] = (char) c; 186 write(mSingleChar, 0, 1); 187 } 188 189 @Override write(@onNull String s, int off, int len)190 public void write(@NonNull String s, int off, int len) { 191 final char[] buf = new char[len]; 192 s.getChars(off, len - off, buf, 0); 193 write(buf, 0, len); 194 } 195 196 @Override write(@onNull char[] buf, int offset, int count)197 public void write(@NonNull char[] buf, int offset, int count) { 198 final int indentLength = mIndentBuilder.length(); 199 final int bufferEnd = offset + count; 200 int lineStart = offset; 201 int lineEnd = offset; 202 203 // March through incoming buffer looking for newlines 204 while (lineEnd < bufferEnd) { 205 char ch = buf[lineEnd++]; 206 mCurrentLength++; 207 if (ch == '\n') { 208 maybeWriteIndent(); 209 super.write(buf, lineStart, lineEnd - lineStart); 210 lineStart = lineEnd; 211 mEmptyLine = true; 212 mCurrentLength = 0; 213 } 214 215 // Wrap if we've pushed beyond line length 216 if (mWrapLength > 0 && mCurrentLength >= mWrapLength - indentLength) { 217 if (!mEmptyLine) { 218 // Give ourselves a fresh line to work with 219 super.write('\n'); 220 mEmptyLine = true; 221 mCurrentLength = lineEnd - lineStart; 222 } else { 223 // We need more than a dedicated line, slice it hard 224 maybeWriteIndent(); 225 super.write(buf, lineStart, lineEnd - lineStart); 226 super.write('\n'); 227 mEmptyLine = true; 228 lineStart = lineEnd; 229 mCurrentLength = 0; 230 } 231 } 232 } 233 234 if (lineStart != lineEnd) { 235 maybeWriteIndent(); 236 super.write(buf, lineStart, lineEnd - lineStart); 237 } 238 } 239 maybeWriteIndent()240 private void maybeWriteIndent() { 241 if (mEmptyLine) { 242 mEmptyLine = false; 243 if (mIndentBuilder.length() != 0) { 244 if (mCurrentIndent == null) { 245 mCurrentIndent = mIndentBuilder.toString().toCharArray(); 246 } 247 super.write(mCurrentIndent, 0, mCurrentIndent.length); 248 } 249 } 250 } 251 } 252