| /* |
| * 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(); |
| } |
| |
| } |