001 /**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018 package org.apache.hadoop.hdfs.tools;
019
020 import java.util.ArrayList;
021 import java.util.LinkedList;
022
023 import org.apache.commons.lang.StringUtils;
024 import org.apache.commons.lang.WordUtils;
025 import org.apache.hadoop.classification.InterfaceAudience;
026
027 /**
028 * This class implements a "table listing" with column headers.
029 *
030 * Example:
031 *
032 * NAME OWNER GROUP MODE WEIGHT
033 * pool1 andrew andrew rwxr-xr-x 100
034 * pool2 andrew andrew rwxr-xr-x 100
035 * pool3 andrew andrew rwxr-xr-x 100
036 *
037 */
038 @InterfaceAudience.Private
039 public class TableListing {
040 public enum Justification {
041 LEFT,
042 RIGHT;
043 }
044
045 private static class Column {
046 private final ArrayList<String> rows;
047 private final Justification justification;
048 private final boolean wrap;
049
050 private int wrapWidth = Integer.MAX_VALUE;
051 private int maxWidth;
052
053 Column(String title, Justification justification, boolean wrap) {
054 this.rows = new ArrayList<String>();
055 this.justification = justification;
056 this.wrap = wrap;
057 this.maxWidth = 0;
058 addRow(title);
059 }
060
061 private void addRow(String val) {
062 if (val == null) {
063 val = "";
064 }
065 if ((val.length() + 1) > maxWidth) {
066 maxWidth = val.length() + 1;
067 }
068 // Ceiling at wrapWidth, because it'll get wrapped
069 if (maxWidth > wrapWidth) {
070 maxWidth = wrapWidth;
071 }
072 rows.add(val);
073 }
074
075 private int getMaxWidth() {
076 return maxWidth;
077 }
078
079 private void setWrapWidth(int width) {
080 wrapWidth = width;
081 // Ceiling the maxLength at wrapWidth
082 if (maxWidth > wrapWidth) {
083 maxWidth = wrapWidth;
084 }
085 // Else we need to traverse through and find the real maxWidth
086 else {
087 maxWidth = 0;
088 for (int i=0; i<rows.size(); i++) {
089 int length = rows.get(i).length();
090 if (length > maxWidth) {
091 maxWidth = length;
092 }
093 }
094 }
095 }
096
097 /**
098 * Return the ith row of the column as a set of wrapped strings, each at
099 * most wrapWidth in length.
100 */
101 String[] getRow(int idx) {
102 String raw = rows.get(idx);
103 // Line-wrap if it's too long
104 String[] lines = new String[] {raw};
105 if (wrap) {
106 lines = WordUtils.wrap(lines[0], wrapWidth, "\n", true).split("\n");
107 }
108 for (int i=0; i<lines.length; i++) {
109 if (justification == Justification.LEFT) {
110 lines[i] = StringUtils.rightPad(lines[i], maxWidth);
111 } else if (justification == Justification.RIGHT) {
112 lines[i] = StringUtils.leftPad(lines[i], maxWidth);
113 }
114 }
115 return lines;
116 }
117 }
118
119 public static class Builder {
120 private final LinkedList<Column> columns = new LinkedList<Column>();
121 private boolean showHeader = true;
122 private int wrapWidth = Integer.MAX_VALUE;
123
124 /**
125 * Create a new Builder.
126 */
127 public Builder() {
128 }
129
130 public Builder addField(String title) {
131 return addField(title, Justification.LEFT, false);
132 }
133
134 public Builder addField(String title, Justification justification) {
135 return addField(title, justification, false);
136 }
137
138 public Builder addField(String title, boolean wrap) {
139 return addField(title, Justification.LEFT, wrap);
140 }
141
142 /**
143 * Add a new field to the Table under construction.
144 *
145 * @param title Field title.
146 * @param justification Right or left justification. Defaults to left.
147 * @param wrap Width at which to auto-wrap the content of the cell.
148 * Defaults to Integer.MAX_VALUE.
149 * @return This Builder object
150 */
151 public Builder addField(String title, Justification justification,
152 boolean wrap) {
153 columns.add(new Column(title, justification, wrap));
154 return this;
155 }
156
157 /**
158 * Whether to hide column headers in table output
159 */
160 public Builder hideHeaders() {
161 this.showHeader = false;
162 return this;
163 }
164
165 /**
166 * Whether to show column headers in table output. This is the default.
167 */
168 public Builder showHeaders() {
169 this.showHeader = true;
170 return this;
171 }
172
173 /**
174 * Set the maximum width of a row in the TableListing. Must have one or
175 * more wrappable fields for this to take effect.
176 */
177 public Builder wrapWidth(int width) {
178 this.wrapWidth = width;
179 return this;
180 }
181
182 /**
183 * Create a new TableListing.
184 */
185 public TableListing build() {
186 return new TableListing(columns.toArray(new Column[0]), showHeader,
187 wrapWidth);
188 }
189 }
190
191 private final Column columns[];
192
193 private int numRows;
194 private final boolean showHeader;
195 private final int wrapWidth;
196
197 TableListing(Column columns[], boolean showHeader, int wrapWidth) {
198 this.columns = columns;
199 this.numRows = 0;
200 this.showHeader = showHeader;
201 this.wrapWidth = wrapWidth;
202 }
203
204 /**
205 * Add a new row.
206 *
207 * @param row The row of objects to add-- one per column.
208 */
209 public void addRow(String... row) {
210 if (row.length != columns.length) {
211 throw new RuntimeException("trying to add a row with " + row.length +
212 " columns, but we have " + columns.length + " columns.");
213 }
214 for (int i = 0; i < columns.length; i++) {
215 columns[i].addRow(row[i]);
216 }
217 numRows++;
218 }
219
220 @Override
221 public String toString() {
222 StringBuilder builder = new StringBuilder();
223 // Calculate the widths of each column based on their maxWidths and
224 // the wrapWidth for the entire table
225 int width = (columns.length-1)*2; // inter-column padding
226 for (int i=0; i<columns.length; i++) {
227 width += columns[i].maxWidth;
228 }
229 // Decrease the column size of wrappable columns until the goal width
230 // is reached, or we can't decrease anymore
231 while (width > wrapWidth) {
232 boolean modified = false;
233 for (int i=0; i<columns.length; i++) {
234 Column column = columns[i];
235 if (column.wrap) {
236 int maxWidth = column.getMaxWidth();
237 if (maxWidth > 4) {
238 column.setWrapWidth(maxWidth-1);
239 modified = true;
240 width -= 1;
241 if (width <= wrapWidth) {
242 break;
243 }
244 }
245 }
246 }
247 if (!modified) {
248 break;
249 }
250 }
251
252 int startrow = 0;
253 if (!showHeader) {
254 startrow = 1;
255 }
256 String[][] columnLines = new String[columns.length][];
257 for (int i = startrow; i < numRows + 1; i++) {
258 int maxColumnLines = 0;
259 for (int j = 0; j < columns.length; j++) {
260 columnLines[j] = columns[j].getRow(i);
261 if (columnLines[j].length > maxColumnLines) {
262 maxColumnLines = columnLines[j].length;
263 }
264 }
265
266 for (int c = 0; c < maxColumnLines; c++) {
267 // First column gets no left-padding
268 String prefix = "";
269 for (int j = 0; j < columns.length; j++) {
270 // Prepend padding
271 builder.append(prefix);
272 prefix = " ";
273 if (columnLines[j].length > c) {
274 builder.append(columnLines[j][c]);
275 } else {
276 builder.append(StringUtils.repeat(" ", columns[j].maxWidth));
277 }
278 }
279 builder.append("\n");
280 }
281 }
282 return builder.toString();
283 }
284 }