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 package com.android.tradefed.util; 17 18 import java.util.LinkedList; 19 import java.util.List; 20 import java.util.stream.IntStream; 21 22 /** Helper class to display a matrix of String elements in a table. */ 23 public class TableBuilder { 24 /* 25 * Sample: 26 * 27 * +======================Metric Regressions=======================+ 28 * | Metric Name | Pre Avg | Post Avg | False Positive Probability | 29 * +===============================================================+ 30 * | Run Metrics (1 compared, 0 changed) | 31 * +---------------------------------------------------------------+ 32 * | Test Metrics (9 compared, 3 changed) | 33 * +---------------------------------------------------------------+ 34 * | > com.android.uiautomator.samples.skeleton.DemoTestCase#testD | 35 * | emo3 | 36 * | stat1 | 100.54 | 817.40 | 0.101 | 37 * | | 38 * | > com.android.uiautomator.samples.skeleton.DemoTestCase#testD | 39 * | emo2 | 40 * | stat3 | 12.32 | 100.91 | 0.101 | 41 * | stat4 | 104.46 | 24.61 | 0.101 | 42 * | | 43 * +===============================================================+ 44 */ 45 // Blank space on the left of the table. 46 private int mTableOffset = 2; 47 // Padding on left & right side of each column. 48 private int mPadding = 1; 49 private final int mColumns; 50 private int mRowLength; 51 private int[] mColumnWidth; 52 53 private List<Line> mTable; 54 private StringBuilder mBuilder; 55 56 private interface Line { build()57 void build(); 58 } 59 60 private class Separator implements Line { 61 char mEnd; 62 char mPipe; 63 String mText; 64 Separator(char end, char pipe, String text)65 Separator(char end, char pipe, String text) { 66 mEnd = end; 67 mPipe = pipe; 68 mText = text; 69 } 70 71 @Override build()72 public void build() { 73 addPadding(mTableOffset); 74 mBuilder.append(mEnd); 75 int blanks = mRowLength - mText.length() - 2; 76 for (int i = 0; i < blanks / 2; i++) { 77 mBuilder.append(mPipe); 78 } 79 mBuilder.append(mText); 80 for (int i = 0; i < blanks / 2; i++) { 81 mBuilder.append(mPipe); 82 } 83 // Correct for odd length 84 if (blanks % 2 != 0) { 85 mBuilder.append(mPipe); 86 } 87 mBuilder.append(mEnd).append('\n'); 88 } 89 } 90 91 private class SingleColumn implements Line { 92 String mText; 93 SingleColumn(String text)94 SingleColumn(String text) { 95 mText = text; 96 } 97 98 @Override build()99 public void build() { 100 int width = mRowLength - 2 - mPadding * 2; // Deduct boundaries 101 String text = mText; 102 while (text != null) { 103 addPadding(mTableOffset); 104 mBuilder.append('|'); 105 addPadding(mPadding); 106 if (text.length() > width) { 107 mBuilder.append(text.substring(0, width)); 108 text = text.substring(width); 109 } else { 110 mBuilder.append(text); 111 addPadding(width - text.length()); 112 text = null; 113 } 114 addPadding(mPadding); 115 mBuilder.append("|\n"); 116 } 117 } 118 } 119 120 private class MultiColumn implements Line { 121 String[] mColumns; 122 MultiColumn(String[] columns)123 MultiColumn(String[] columns) { 124 mColumns = columns; 125 } 126 127 @Override build()128 public void build() { 129 addPadding(mTableOffset); 130 mBuilder.append("|"); 131 for (int i = 0; i < mColumns.length; i++) { 132 addPadding(mPadding); 133 mBuilder.append(mColumns[i]); 134 addPadding(mColumnWidth[i] - mColumns[i].length() + mPadding); 135 mBuilder.append('|'); 136 } 137 mBuilder.append('\n'); 138 } 139 } 140 141 /** 142 * Constructs a TableBuilder with specific number of columns. 143 * 144 * @param numColumns number of columns in this table. 145 */ TableBuilder(int numColumns)146 public TableBuilder(int numColumns) { 147 mColumns = numColumns; 148 mColumnWidth = new int[numColumns]; 149 mTable = new LinkedList<>(); 150 } 151 152 /** 153 * Sets the number of white space before and after each column element 154 * 155 * @param padding the number of white space 156 * @return this 157 */ setPadding(int padding)158 public TableBuilder setPadding(int padding) { 159 mPadding = padding; 160 return this; 161 } 162 163 /** 164 * Sets the number of white space on the left of the whole table 165 * 166 * @param offset the number of white space 167 * @return this 168 */ setOffset(int offset)169 public TableBuilder setOffset(int offset) { 170 mTableOffset = offset; 171 return this; 172 } 173 174 /** 175 * Adds a title to this table. Sample: +======================TITLE=======================+ 176 * 177 * @param title title 178 * @return this 179 */ addTitle(String title)180 public TableBuilder addTitle(String title) { 181 mTable.add(new Separator('+', '=', title)); 182 return this; 183 } 184 185 /** 186 * Adds a row separator like: +---------------------------------------------+ 187 * 188 * @return this 189 */ addSingleLineSeparator()190 public TableBuilder addSingleLineSeparator() { 191 return addSeparator('+', '-'); 192 } 193 194 /** 195 * Adds a row separator like: +=============================================+ 196 * 197 * @return this 198 */ addDoubleLineSeparator()199 public TableBuilder addDoubleLineSeparator() { 200 return addSeparator('+', '='); 201 } 202 203 /** 204 * Adds a row separator like: | | (blank space between two pipes) 205 * 206 * @return this 207 */ addBlankLineSeparator()208 public TableBuilder addBlankLineSeparator() { 209 return addSeparator('|', ' '); 210 } 211 212 /** 213 * Adds a custom row separator. 214 * 215 * @param end the two end character. 216 * @param pipe the character connecting two ends 217 * @return this 218 */ addSeparator(char end, char pipe)219 public TableBuilder addSeparator(char end, char pipe) { 220 mTable.add(new Separator(end, pipe, "")); 221 return this; 222 } 223 224 /** 225 * Adds a single long line. TableBuilder will wrap it if it is too long. See example above. 226 * 227 * @param line the line. 228 * @return this 229 */ addLine(String line)230 public TableBuilder addLine(String line) { 231 mTable.add(new SingleColumn(line)); 232 return this; 233 } 234 235 /** 236 * Adds a line. The number of columns in line must equal numColumns provided in the constructor. 237 * 238 * @param line the line. 239 * @return this 240 * @throws IllegalArgumentException when the number of columns in line does not agree with 241 * numColumns provided in the constructor. 242 */ addLine(String[] line)243 public TableBuilder addLine(String[] line) { 244 if (line.length != mColumns) { 245 throw new IllegalArgumentException( 246 String.format("Expect %d columns, actual %d columns", mColumns, line.length)); 247 } 248 mTable.add(new MultiColumn(line)); 249 for (int i = 0; i < mColumns; i++) { 250 if (line[i].length() > mColumnWidth[i]) { 251 mColumnWidth[i] = line[i].length(); 252 } 253 } 254 return this; 255 } 256 257 /** 258 * Builds the table and return as a string. 259 * 260 * @return the table in string format. 261 */ build()262 public String build() { 263 mBuilder = new StringBuilder(); 264 mRowLength = IntStream.of(mColumnWidth).map(i -> i + 2 * mPadding + 1).sum() + 1; 265 for (Line line : mTable) { 266 line.build(); 267 } 268 return mBuilder.toString(); 269 } 270 addPadding(int padding)271 private void addPadding(int padding) { 272 for (int i = 0; i < padding; i++) { 273 mBuilder.append(' '); 274 } 275 } 276 } 277