/* * JBoss, Home of Professional Open Source. * See the COPYRIGHT.txt file distributed with this work for information * regarding copyright ownership. Some portions may be licensed * to Red Hat, Inc. under one or more contributor license agreements. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ package org.teiid.example.util; import java.io.PrintStream; import java.util.ArrayList; import java.util.List; import java.util.Iterator; import java.util.StringTokenizer; /** * A <tt>TableRenderer</tt> can renderer table as a formated table output, The {@link #renderer} * method will output the formated table. Before execute the {@link #renderer} the{@link #addRow} * method should be invoked, The table header be initialized by constructor {@link #TableRenderer}. * * A usage Example: * <pre> * ColumnMetaData[] header = new ColumnMetaData [2]; * header[0] = new ColumnMetaData("COL0", ColumnMetaData.ALIGN_RIGHT); * header[1] = new ColumnMetaData("COL1", ColumnMetaData.ALIGN_RIGHT); * * TableRenderer renderer = new TableRenderer(header, out, "|", true, true); * * Column[] row = new Column[2]; * row[0] = new Column(0); * row[1] = new Column(1); * * renderer.addRow(row); * renderer.renderer(); * </pre> * * The rendered table like: * <pre> * +------+------+ * | COL0 | COL1 | * | 0 | 1 | * +------+------+ * </pre> * * Alternatively, There is a Factory class in <tt>ColumnMetaData</tt> and <tt>Column</tt> which used quick create * MetaData Header and Data Row, for example: * <pre> * ColumnMetaData[] header = ColumnMetaData.Factory.create("COL0", "COL1"); * Column[] row = Column.Factory.create(0, 1); * </pre> * */ public class TableRenderer{ private static final int MAX_CACHE_ELEMENTS = 500; private final List<Column[]> cacheRows; private boolean alreadyFlushed; private int writtenRows; private int separatorWidth; private boolean enableHeader; private boolean enableFooter; protected ColumnMetaData[] meta; protected final OutputDevice out; protected final String colSeparator; protected final String colPreSeparator; public TableRenderer(ColumnMetaData[] meta, OutputDevice out, String separator, boolean enableHeader, boolean enableFooter) { this.meta = meta; this.out = out; this.enableHeader = enableHeader; this.enableFooter = enableFooter; /* * we cache the rows in order to dynamically determine the * output width of each column. */ this.cacheRows = new ArrayList<Column[]>(MAX_CACHE_ELEMENTS); this.alreadyFlushed = false; this.writtenRows = 0; this.colSeparator = " " + separator; this.colPreSeparator = separator; this.separatorWidth = separator.length(); } public TableRenderer(ColumnMetaData[] meta, OutputDevice out) { this(meta, out, "|", true, true); } public TableRenderer(OutputDevice out) { this(null, out); } public TableRenderer(ColumnMetaData[] meta) { this(meta, new PrintStreamOutputDevice(System.out)); } public void addRow(Column[] row) { updateColumnWidths(row); addRowToCache(row); } protected void addRowToCache(Column[] row) { cacheRows.add(row); if (cacheRows.size() >= MAX_CACHE_ELEMENTS) { flush(); } } /** * return the meta data that is used to display this table. */ public ColumnMetaData[] getMetaData() { return meta; } public void setMeta(ColumnMetaData[] meta) { this.meta = meta; } /** * Overwrite this method if you need to handle customized columns. * @param row */ protected void updateColumnWidths(Column[] row) { for (int i = 0; i < meta.length; ++i) { if(row[i] == null) { continue; } row[i].setAutoWrap(meta[i].getAutoWrap()); meta[i].updateWidth(row[i].getWidth()); } } public void renderer() { flush(); if (writtenRows > 0 && enableFooter) { printHorizontalLine(); } } /** * flush the cached rows. */ public void flush() { if (!alreadyFlushed) { if (enableHeader) { printTableHeader(); } alreadyFlushed = true; } Iterator<Column[]> rowIterator = cacheRows.iterator(); while (rowIterator.hasNext()) { Column[] currentRow = rowIterator.next(); boolean hasMoreLines; do { hasMoreLines = false; hasMoreLines = printColumns(currentRow, hasMoreLines); out.println(); } while (hasMoreLines); ++writtenRows; } cacheRows.clear(); } protected boolean printColumns(Column[] currentRow, boolean hasMoreLines) { boolean first = true; for (int i = 0; i < meta.length; ++i) { if (!meta[i].doDisplay()) continue; if(first){ out.print(this.colPreSeparator); first = false; } hasMoreLines = printColumn(currentRow[i], hasMoreLines, i); } return hasMoreLines; } protected boolean printColumn(Column col, boolean hasMoreLines, int i) { String txt; out.print(" "); if(col == null) { txt = formatString( "null", ' ', meta[i].getWidth(),meta[i].getAlignment()); hasMoreLines = false; out.print(txt); } else { txt = formatString( col.getNextLine(), ' ', meta[i].getWidth(),meta[i].getAlignment()); hasMoreLines |= col.hasNextLine(); if (col.isNull()) out.attributeGrey(); out.print(txt); if (col.isNull()) out.attributeReset(); } out.print(colSeparator); return hasMoreLines; } private void printHorizontalLine() { boolean first = true; for (int i = 0; i < meta.length; ++i) { if (!meta[i].doDisplay()) continue; String txt = null;; if(first){ out.print("+"); first= false; } txt = formatString("", '-', meta[i].getWidth() + separatorWidth + 1, ColumnMetaData.ALIGN_LEFT); out.print(txt); out.print("+"); } out.println(); } private void printTableHeader() { printHorizontalLine(); boolean first = true; for (int i = 0; i < meta.length; ++i) { if (!meta[i].doDisplay()) continue; if(first) { out.print(colPreSeparator); first = false; } String txt; txt = formatString(meta[i].getLabel(), ' ', meta[i].getWidth() + 1, ColumnMetaData.ALIGN_CENTER); out.attributeBold(); out.print(txt); out.attributeReset(); out.print(colSeparator); } out.println(); printHorizontalLine(); } protected String formatString(String text, char fillchar, int len, int alignment) { StringBuffer fillstr = new StringBuffer(); if (len > 4000) { len = 4000; } if (text == null) { text = "[NULL]"; } int slen = text.length(); if (alignment == ColumnMetaData.ALIGN_LEFT) { fillstr.append(text); } int fillNumber = len - slen; int boundary = 0; if (alignment == ColumnMetaData.ALIGN_CENTER) { boundary = fillNumber / 2; } while (fillNumber > boundary) { fillstr.append(fillchar); --fillNumber; } if (alignment != ColumnMetaData.ALIGN_LEFT) { fillstr.append(text); } while (fillNumber > 0) { fillstr.append(fillchar); --fillNumber; } return fillstr.toString(); } public static class Column { private final static String NULL_TEXT = "[NULL]"; private final static int NULL_LENGTH = NULL_TEXT.length(); private String columnText[]; // multi-rows private int width; /** This holds a state for the renderer */ private int pos; public Column(long value) { this(String.valueOf(value)); } public Column(String text) { if (text == null) { width = NULL_LENGTH; columnText = null; } else { width = 0; StringTokenizer tok = new StringTokenizer(text, "\n\r"); columnText = new String[tok.countTokens()]; for (int i = 0; i < columnText.length; ++i) { String line = (String)tok.nextElement(); int lWidth = line.length(); columnText[i] = line; if (lWidth > width) { width = lWidth; } } } pos = 0; } /** * Split a line at the nearest whitespace. */ private String[] splitLine(String str, int autoCol) { ArrayList<String> tmpRows = new ArrayList<String>(5); int lastPos = 0; int pos = lastPos + autoCol; final int strLen = str.length(); while (pos < strLen) { while (pos > lastPos && !Character.isWhitespace(str.charAt(pos))) { pos--; } if (pos == lastPos) { // no whitespace found: hard cut tmpRows.add(str.substring(lastPos, lastPos + autoCol)); lastPos = lastPos + autoCol; } else { tmpRows.add(str.substring(lastPos, pos)); lastPos = pos + /* skip space: */ 1; } pos = lastPos + autoCol; } if (lastPos < strLen-1) { tmpRows.add(str.substring(lastPos)); } return (String[]) tmpRows.toArray(new String[ tmpRows.size() ]); } /** * replaces a row with multiple other rows. */ private String[] replaceRow(String[] orig, int pos, String[] other) { String result[] = new String[ orig.length + other.length - 1]; System.arraycopy(orig, 0, result, 0, pos); System.arraycopy(other, 0, result, pos, other.length); System.arraycopy(orig, pos+1, result, pos + other.length, orig.length-pos-1); return result; } /** * Set autowrapping at a given column. */ void setAutoWrap(int autoWrapCol) { if (autoWrapCol < 0 || columnText==null) return; width = 0; for (int i=0; i < columnText.length; ++i) { int colWidth = columnText[i].length(); if (colWidth > autoWrapCol) { String[] multiRows = splitLine(columnText[i], autoWrapCol); for (int j = 0; j < multiRows.length; ++j) { int l = multiRows[j].length(); if (l > width) { width = l; } } columnText = replaceRow(columnText, i, multiRows); i += multiRows.length; // next loop pos here. } else { if (colWidth > width) { width = colWidth; } } } } // package private methods for the table renderer. int getWidth() { return width; } boolean hasNextLine() { return (columnText != null && pos < columnText.length); } boolean isNull() { return (columnText == null); } String getNextLine() { String result = ""; if (columnText == null) { if (pos == 0) result = NULL_TEXT; } else if (pos < columnText.length) { result = columnText[pos]; } ++pos; return result; } public static class Factory { public static Column[] create(String... items) { Column[] row = new Column[items.length]; for(int i = 0 ; i < items.length ; i ++){ row[i] = new Column(items[i]); } return row; } public static Column[] create(long... items) { Column[] row = new Column[items.length]; for(int i = 0 ; i < items.length ; i ++){ row[i] = new Column(items[i]); } return row; } public static Column[] form(List<?> list) { Column[] row = new Column[list.size()]; for(int i = 0 ; i < list.size() ; i ++) { row[i] = new Column(String.valueOf(list.get(i))); } return row; } } } public static class ColumnMetaData { public static final int ALIGN_LEFT = 1; public static final int ALIGN_CENTER = 2; public static final int ALIGN_RIGHT = 3; /** alignment; one of left, center, right */ private final int alignment; /** the header of this column */ private final String label; /** minimum width of this column; usually set by the header width */ private final int initialWidth; /** wrap columns automatically at this column; -1 = disabled */ private int autoWrapCol; private int width; private boolean display; public ColumnMetaData(String header, int align) { this(header, align, -1); } /** * publically available constructor for the user. */ public ColumnMetaData(String header, int align, int autoWrap) { label = header; initialWidth = header.length(); width = initialWidth; alignment = align; display = true; autoWrapCol = autoWrap; } public ColumnMetaData(String header) { this(header, ALIGN_LEFT); } public void resetWidth() { width = initialWidth; } /** * set, whether a specific column should be displayed. */ public void setDisplay(boolean val) { display = val; } public boolean doDisplay() { return display; } public void setAutoWrap(int col) { autoWrapCol = col; } public int getAutoWrap() { return autoWrapCol; } int getWidth() { return width; } String getLabel() { return label; } public int getAlignment() { return alignment; } void updateWidth(int w) { if (w > width) { width = w; } } public static class Factory { public static ColumnMetaData[] create(int align, String... items) { ColumnMetaData[] metadata = new ColumnMetaData [items.length]; for(int i = 0 ; i < items.length ; i ++) { metadata[i] = new ColumnMetaData(items[i], align); } return metadata; } public static ColumnMetaData[] create(String... items) { return create(ALIGN_CENTER, items); } public static ColumnMetaData[] form(int align, String[] items) { ColumnMetaData[] metadata = new ColumnMetaData [items.length]; for(int i = 0 ; i < items.length ; i ++){ metadata[i] = new ColumnMetaData(items[i], align); } return metadata; } public static ColumnMetaData[] form(String[] items) { return form(ALIGN_CENTER, items); } } } public static interface OutputDevice { void flush(); void write(byte[] buffer, int off, int len); void print(String s); void println(String s); void println(); void attributeBold(); void attributeReset(); void attributeGrey(); void close(); boolean isTerminal(); } public static class PrintStreamOutputDevice implements OutputDevice { private final PrintStream out; public PrintStreamOutputDevice(PrintStream out) { super(); this.out = out; } @Override public void flush() { out.flush(); } @Override public void write(byte[] buf, int off, int len) { out.write(buf, off, len); } @Override public void print(String s) { out.print(s); } @Override public void println(String s) { out.println(s); } @Override public void println() { out.println(); } @Override public void attributeBold() { } @Override public void attributeReset() { } @Override public void attributeGrey() { } @Override public void close() { out.close(); } @Override public boolean isTerminal() { return false; } } }