/*
* Geotools 2 - OpenSource mapping toolkit
* (C) 2003, Geotools Project Managment Committee (PMC)
* (C) 2001, Institut de Recherche pour le Développement
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.geotools.io;
// Input/output
import java.io.FilterWriter;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;
import javax.swing.text.StyleConstants;
import org.geotools.resources.Utilities;
import org.geotools.resources.XArray;
/**
* A character stream that can be used to format tables. Columns are separated
* by tabulations ('\t'
) and rows are separated by line terminators
* ('\r'
, '\n'
or "\r\n"
). Every table's
* cells are stored in memory until {@link #flush()} is invoked. When invoked,
* {@link #flush()} copy cell's contents to the underlying stream while replacing
* tabulations by some amount of spaces. The exact number of spaces is computed
* from cell's widths. {@code TableWriter} produces correct output when
* displayed with a monospace font.
*
* For example, the following code...
*
*
* * ...produces the following output: * ** TableWriter out = new TableWriter(new OutputStreamWriter(System.out), 3); * out.write("Prénom\tNom\n"); * out.nextLine('-'); * out.write("Idéphonse\tLaporte\nSarah\tCoursi\nYvan\tDubois"); * out.flush(); *
* * @version $Id$ * @author Martin Desruisseaux * * @since 2.0 */ public class TableWriter extends FilterWriter { /** * A possible value for cell alignment. This * specifies that the text is aligned to the left * indent and extra whitespace should be placed on * the right. */ public static final int ALIGN_LEFT = StyleConstants.ALIGN_LEFT; /** * A possible value for cell alignment. This * specifies that the text is aligned to the right * indent and extra whitespace should be placed on * the left. */ public static final int ALIGN_RIGHT = StyleConstants.ALIGN_RIGHT; /** * A possible value for cell alignment. This * specifies that the text is aligned to the center * and extra whitespace should be placed equally on * the left and right. */ public static final int ALIGN_CENTER = StyleConstants.ALIGN_CENTER; /** * Drawing-box characters. The last two characters * are horizontal and vertical line respectively. */ private static final char[][] BOX = new char[][] { {// [0000]: single horizontal, single vertical '\u250C','\u252C','\u2510', '\u251C','\u253C','\u2524', '\u2514','\u2534','\u2518', '\u2500','\u2502' }, {// [0001]: single horizontal, double vertical '\u2553','\u2565','\u2556', '\u255F','\u256B','\u2562', '\u2559','\u2568','\u255C', '\u2500','\u2551' }, {// [0010]: double horizontal, single vertical '\u2552','\u2564','\u2555', '\u255E','\u256A','\u2561', '\u2558','\u2567','\u255B', '\u2550','\u2502' }, {// [0011]: double horizontal, double vertical '\u2554','\u2566','\u2557', '\u2560','\u256C','\u2563', '\u255A','\u2569','\u255D', '\u2550','\u2551' }, {// [0100]: ASCII characters only '+','+','+', '+','+','+', '+','+','+', '-','|' } }; /** * Default character for space. */ private static final char SPACE = ' '; /** * Temporary string buffer. This buffer * contains only one cell's content. */ private final StringBuffer buffer = new StringBuffer(); /** * List of {@link Cell} objects, from left to right and top to bottom. * By convention, a {@code null} value or a {@link Cell} object * with* Prénom Nom * --------- ------- * Idéphonse Laporte * Sarah Coursi * Yvan Dubois *
{@link Cell#text}==null
are move to the next line.
*/
private final List cells = new ArrayList();
/**
* Alignment for current and next cells.
*/
private int alignment = ALIGN_LEFT;
/**
* Column position of the cell currently being written. The field
* is incremented each time {@link #nextColumn()} is invoked.
*/
private int column;
/**
* Line position of the cell currently being written. The field
* is incremented each time {@link #nextLine()} is invoked.
*/
private int row;
/**
* Maximum width for each columns. This array's length must
* be equals to the number of columns in this table.
*/
private int width[] = new int[0];
/**
* The column separator.
*/
private final String separator;
/**
* The left table border.
*/
private final String leftBorder;
/**
* The right table border.
*/
private final String rightBorder;
/**
* Tells if cells can span more than one line. If {@code true},
* then EOL characters likes '\n' move to the next line inside
* the current cell. If {@code false}, then EOL characters move to
* the next table's row. Default value is {@code false}.
*/
private boolean multiLinesCells;
/**
* {@code true} if this {@code TableWriter}
* has been constructed with the no-arg constructor.
*/
private final boolean stringOnly;
/**
* Tells if the next '\n' character must be ignored. This field
* is used in order to avoid writing two EOL in place of "\r\n".
*/
private boolean skipCR;
/**
* Create a new table writer with a default column separator.
* Note: this writer may produces bad output on Windows console,
* unless the underlying stream use the correct codepage (e.g.
* OutputStreamWriter(System.out, "Cp437")
).
* To display the appropriate codepage for a Windows NT console,
* type {@code chcp} on the command line.
*
* @param out Writer object to provide the underlying stream,
* or {@code null} if there is no underlying stream.
* If {@code out} is null, then the {@link #toString}
* method is the only way to get the table's content.
*/
public TableWriter(final Writer out) {
super(out!=null ? out : new StringWriter());
stringOnly = (out==null);
leftBorder = "\u2551 ";
rightBorder = " \u2551" ;
separator = " \u2502 ";
}
/**
* Create a new table writer with the specified
* amount of spaces as column separator.
*
* @param out Writer object to provide the underlying stream,
* or {@code null} if there is no underlying stream.
* If {@code out} is null, then the {@link #toString}
* method is the only way to get the table's content.
* @param spaces Amount of white spaces to use as column separator.
*/
public TableWriter(final Writer out, final int spaces) {
this(out, Utilities.spaces(spaces));
}
/**
* Create a new table writer with the specified column separator.
*
* @param out Writer object to provide the underlying stream,
* or {@code null} if there is no underlying stream.
* If {@code out} is null, then the {@link #toString}
* method is the only way to get the table's content.
* @param separator String to write between columns. Drawing box characters
* are treated specially. For example " \\u2502 "
can be
* used for a single-line box.
*/
public TableWriter(final Writer out, final String separator) {
super(out!=null ? out : new StringWriter());
stringOnly = (out==null);
final int length = separator.length();
int lower = 0;
int upper = length;
while (lower'\r'
, '\n'
or
* "\r\n"
) and tabulations ('\t'
) characters
* are copied straight into the current cell, which mean that next write
* operations will continue inside the same cell.'\t'
) are replaced
* by {@link #nextColumn()} invocations.'\r'
, '\n'
* or "\r\n"
) are replaced
* by {@link #nextLine()} invocations.nextColumn('*')
from the first character of a cell is
* a convenient way to put a pad value in this cell.
*
* @param fill Character filling the cell (default to whitespace).
*/
public void nextColumn(final char fill) {
synchronized (lock) {
final String cellText = buffer.toString();
cells.add(new Cell(cellText, alignment, fill));
if (column >= width.length) {
width = XArray.resize(width, column+1);
}
int length = 0;
final StringTokenizer tk = new StringTokenizer(cellText, "\r\n");
while (tk.hasMoreTokens()) {
final int lg = tk.nextToken().length();
if (lg > length) {
length = lg;
}
}
if (length>width[column]) {
width[column] = length;
}
column++;
buffer.setLength(0);
}
}
/**
* Moves to the first column on the next row.
* Next write operations will occur on a new row.
*/
public void nextLine() {
nextLine(SPACE);
}
/**
* Moves to the first column on the next row. Next write operations will
* occur on a new row. This method fill every remaining cell in the current
* row with the specified character. Calling nextLine('-')
* from the first column of a row is a convenient way to fill this row
* with a line separator.
*
* @param fill Character filling the rest of the line
* (default to whitespace). This caracter may
* be use as a row separator.
*/
public void nextLine(final char fill) {
synchronized (lock) {
if (buffer.length() != 0) {
nextColumn(fill);
}
assert buffer.length() == 0;
cells.add(!Character.isSpaceChar(fill) ? new Cell(null, alignment, fill) : null);
column = 0;
row++;
}
}
/**
* Flush the table content to the underlying stream.
* This method should not be called before the table
* is completed (otherwise, columns may have the
* wrong width).
*
* @throws IOException if an output operation failed.
*/
public void flush() throws IOException {
synchronized (lock) {
if (buffer.length() != 0) {
nextLine();
assert buffer.length() == 0;
}
flushTo(out);
row = column = 0;
cells.clear();
if (!(out instanceof TableWriter)) {
/*
* Flush only if this table is not included in an outer (bigger) table.
* This is because flushing the outer table would break its formatting.
*/
out.flush();
}
}
}
/**
* Flush the table content and close the underlying stream.
*
* @throws IOException if an output operation failed.
*/
public void close() throws IOException {
synchronized (lock) {
flush();
out.close();
}
}
/**
* Ecrit vers le flot spécifié toutes les cellules qui avaient été disposées
* dans le tableau. Ces cellules seront automatiquement alignées en colonnes.
* Cette méthode peut Štre appelée plusieurs fois pour écrire le mŠme tableau
* par exemple vers plusieurs flots.
*
* @param out Flot vers où écrire les données.
* @throws IOException si une erreur est survenue lors de l'écriture dans
* {@code out}.
*/
private void flushTo(final Writer out) throws IOException {
final String columnSeparator = this.separator;
final String lineSeparator = System.getProperty("line.separator", "\n");
final Cell[] currentLine = new Cell[width.length];
final int cellCount = cells.size();
for (int cellIndex=0; cellIndex