blob: 7e266d5346a6dd6b40812cbe96fea50ad724a53b [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.alibaba.dubbo.qos.textui;
import org.apache.commons.lang3.StringUtils;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import static java.lang.Math.abs;
import static java.lang.Math.max;
import static java.lang.String.format;
import static org.apache.commons.lang3.StringUtils.EMPTY;
import static org.apache.commons.lang3.StringUtils.length;
import static org.apache.commons.lang3.StringUtils.repeat;
/**
* Table
*/
public class TTable implements TComponent {
// column definition
private final ColumnDefine[] columnDefineArray;
// border
private final Border border = new Border();
// padding
private int padding;
public TTable(ColumnDefine[] columnDefineArray) {
this.columnDefineArray = null == columnDefineArray
? new ColumnDefine[0]
: columnDefineArray;
}
public TTable(int columnNum) {
this.columnDefineArray = new ColumnDefine[columnNum];
for (int index = 0; index < this.columnDefineArray.length; index++) {
columnDefineArray[index] = new ColumnDefine();
}
}
@Override
public String rendering() {
final StringBuilder tableSB = new StringBuilder();
// process width cache
final int[] widthCacheArray = new int[getColumnCount()];
for (int index = 0; index < widthCacheArray.length; index++) {
widthCacheArray[index] = abs(columnDefineArray[index].getWidth());
}
final int rowCount = getRowCount();
for (int rowIndex = 0; rowIndex < rowCount; rowIndex++) {
final boolean isFirstRow = rowIndex == 0;
final boolean isLastRow = rowIndex == rowCount - 1;
// print first separation line
if (isFirstRow
&& border.has(Border.BORDER_OUTER_TOP)) {
tableSB.append(drawSeparationLine(widthCacheArray)).append("\n");
}
// print inner separation lines
if (!isFirstRow
&& border.has(Border.BORDER_INNER_H)) {
tableSB.append(drawSeparationLine(widthCacheArray)).append("\n");
}
// draw one line
tableSB.append(drawRow(widthCacheArray, rowIndex));
// print ending separation line
if (isLastRow
&& border.has(Border.BORDER_OUTER_BOTTOM)) {
tableSB.append(drawSeparationLine(widthCacheArray)).append("\n");
}
}
return tableSB.toString();
}
private String drawRow(int[] widthCacheArray, int rowIndex) {
final StringBuilder rowSB = new StringBuilder();
final Scanner[] scannerArray = new Scanner[getColumnCount()];
try {
boolean hasNextLine;
do {
hasNextLine = false;
final StringBuilder segmentSB = new StringBuilder();
for (int colIndex = 0; colIndex < getColumnCount(); colIndex++) {
final int width = widthCacheArray[colIndex];
final boolean isFirstColOfRow = colIndex == 0;
final boolean isLastColOfRow = colIndex == widthCacheArray.length - 1;
final String borderChar;
if (isFirstColOfRow
&& border.has(Border.BORDER_OUTER_LEFT)) {
borderChar = "|";
} else if (!isFirstColOfRow
&& border.has(Border.BORDER_INNER_V)) {
borderChar = "|";
} else {
borderChar = EMPTY;
}
if (null == scannerArray[colIndex]) {
scannerArray[colIndex] = new Scanner(
new StringReader(wrap(getData(rowIndex, columnDefineArray[colIndex]), width)));
}
final Scanner scanner = scannerArray[colIndex];
final String data;
if (scanner.hasNextLine()) {
data = scanner.nextLine();
hasNextLine = true;
} else {
data = EMPTY;
}
if (width > 0) {
final ColumnDefine columnDefine = columnDefineArray[colIndex];
final String dataFormat = getDataFormat(columnDefine, width, data);
final String paddingChar = repeat(" ", padding);
segmentSB.append(format(borderChar + paddingChar + dataFormat + paddingChar, data));
}
if (isLastColOfRow) {
if (border.has(Border.BORDER_OUTER_RIGHT)) {
segmentSB.append("|");
}
segmentSB.append("\n");
}
}
if (hasNextLine) {
rowSB.append(segmentSB);
}
} while (hasNextLine);
return rowSB.toString();
} finally {
for (Scanner scanner : scannerArray) {
if (null != scanner) {
scanner.close();
}
}
}
}
private String getData(int rowIndex, ColumnDefine columnDefine) {
return columnDefine.getRowCount() <= rowIndex
? EMPTY
: columnDefine.rows.get(rowIndex);
}
private String getDataFormat(ColumnDefine columnDefine, int width, String data) {
switch (columnDefine.align) {
case MIDDLE: {
final int length = StringUtils.length(data);
final int diff = width - length;
final int left = diff / 2;
return repeat(" ", diff - left) + "%s" + repeat(" ", left);
}
case RIGHT: {
return "%" + width + "s";
}
case LEFT:
default: {
return "%-" + width + "s";
}
}
}
/**
* get row count
*/
private int getRowCount() {
int rowCount = 0;
for (ColumnDefine columnDefine : columnDefineArray) {
rowCount = max(rowCount, columnDefine.getRowCount());
}
return rowCount;
}
/**
* position to last column
*/
private int indexLastCol(final int[] widthCacheArray) {
for (int colIndex = widthCacheArray.length - 1; colIndex >= 0; colIndex--) {
final int width = widthCacheArray[colIndex];
if (width <= 0) {
continue;
}
return colIndex;
}
return 0;
}
/**
* draw separation line
*/
private String drawSeparationLine(final int[] widthCacheArray) {
final StringBuilder separationLineSB = new StringBuilder();
final int lastCol = indexLastCol(widthCacheArray);
final int colCount = widthCacheArray.length;
for (int colIndex = 0; colIndex < colCount; colIndex++) {
final int width = widthCacheArray[colIndex];
if (width <= 0) {
continue;
}
final boolean isFirstCol = colIndex == 0;
final boolean isLastCol = colIndex == lastCol;
if (isFirstCol
&& border.has(Border.BORDER_OUTER_LEFT)) {
separationLineSB.append("+");
}
if (!isFirstCol
&& border.has(Border.BORDER_INNER_V)) {
separationLineSB.append("+");
}
separationLineSB.append(repeat("-", width + 2 * padding));
if (isLastCol
&& border.has(Border.BORDER_OUTER_RIGHT)) {
separationLineSB.append("+");
}
}
return separationLineSB.toString();
}
/**
* Add a row
*/
public TTable addRow(Object... columnDataArray) {
if (null != columnDataArray) {
for (int index = 0; index < columnDefineArray.length; index++) {
final ColumnDefine columnDefine = columnDefineArray[index];
if (index < columnDataArray.length
&& null != columnDataArray[index]) {
columnDefine.rows.add(replaceTab(columnDataArray[index].toString()));
} else {
columnDefine.rows.add(EMPTY);
}
}
}
return this;
}
/**
* alignment
*/
public enum Align {
/**
* left-alignment
*/
LEFT,
/**
* right-alignment
*/
RIGHT,
/**
* middle-alignment
*/
MIDDLE
}
/**
* column definition
*/
public static class ColumnDefine {
// column width
private final int width;
// whether to auto resize
private final boolean isAutoResize;
// alignment
private final Align align;
// data rows
private final List<String> rows = new ArrayList<String>();
public ColumnDefine(int width, boolean isAutoResize, Align align) {
this.width = width;
this.isAutoResize = isAutoResize;
this.align = align;
}
public ColumnDefine(Align align) {
this(0, true, align);
}
public ColumnDefine(int width) {
this(width, false, Align.LEFT);
}
public ColumnDefine(int width, Align align) {
this(width, false, align);
}
public ColumnDefine() {
this(Align.LEFT);
}
/**
* get current width
*
* @return width
*/
public int getWidth() {
// if not auto resize, return preset width
if (!isAutoResize) {
return width;
}
// if it's auto resize, then calculate the possible max width
int maxWidth = 0;
for (String data : rows) {
maxWidth = max(width(data), maxWidth);
}
return maxWidth;
}
/**
* get rows for the current column
*
* @return current column's rows
*/
public int getRowCount() {
return rows.size();
}
}
/**
* set padding
*
* @param padding padding
*/
public TTable padding(int padding) {
this.padding = padding;
return this;
}
/**
* get column count
*
* @return column count
*/
public int getColumnCount() {
return columnDefineArray.length;
}
/**
* replace tab to four spaces
*
* @param string the original string
* @return the replaced string
*/
private static String replaceTab(String string) {
return StringUtils.replace(string, "\t", " ");
}
/**
* visible width for the given string.
*
* for example: "abc\n1234"'s width is 4.
*
* @param string the given string
* @return visible width
*/
private static int width(String string) {
int maxWidth = 0;
final Scanner scanner = new Scanner(new StringReader(string));
try {
while (scanner.hasNextLine()) {
maxWidth = max(length(scanner.nextLine()), maxWidth);
}
} finally {
scanner.close();
}
return maxWidth;
}
/**
* get border
*
* @return table border
*/
public Border getBorder() {
return border;
}
/**
* border style
*/
public class Border {
private int borders = BORDER_OUTER | BORDER_INNER;
/**
* border outer top
*/
public static final int BORDER_OUTER_TOP = 1 << 0;
/**
* border outer right
*/
public static final int BORDER_OUTER_RIGHT = 1 << 1;
/**
* border outer bottom
*/
public static final int BORDER_OUTER_BOTTOM = 1 << 2;
/**
* border outer left
*/
public static final int BORDER_OUTER_LEFT = 1 << 3;
/**
* inner border: horizon
*/
public static final int BORDER_INNER_H = 1 << 4;
/**
* inner border: vertical
*/
public static final int BORDER_INNER_V = 1 << 5;
/**
* outer border
*/
public static final int BORDER_OUTER = BORDER_OUTER_TOP | BORDER_OUTER_BOTTOM | BORDER_OUTER_LEFT | BORDER_OUTER_RIGHT;
/**
* inner border
*/
public static final int BORDER_INNER = BORDER_INNER_H | BORDER_INNER_V;
/**
* no border
*/
public static final int BORDER_NON = 0;
/**
* whether has one of the specified border styles
*
* @param borderArray border styles
* @return whether has one of the specified border styles
*/
public boolean has(int... borderArray) {
if (null == borderArray) {
return false;
}
for (int b : borderArray) {
if ((this.borders & b) == b) {
return true;
}
}
return false;
}
/**
* get border style
*
* @return border style
*/
public int get() {
return borders;
}
/**
* set border style
*
* @param border border style
* @return this
*/
public Border set(int border) {
this.borders = border;
return this;
}
public Border add(int border) {
return set(get() | border);
}
public Border remove(int border) {
return set(get() ^ border);
}
}
public static String wrap(String string, int width) {
final StringBuilder sb = new StringBuilder();
final char[] buffer = string.toCharArray();
int count = 0;
for (char c : buffer) {
if (count == width) {
count = 0;
sb.append('\n');
if (c == '\n') {
continue;
}
}
if (c == '\n') {
count = 0;
} else {
count++;
}
sb.append(c);
}
return sb.toString();
}
}