001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2003-2006, GeoTools Project Managment Committee (PMC)
005: * (C) 2001, Institut de Recherche pour le Développement
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation;
010: * version 2.1 of the License.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: */
017: package org.geotools.io;
018:
019: // Input/output
020: import java.io.FilterWriter;
021: import java.io.IOException;
022: import java.io.StringWriter;
023: import java.io.Writer;
024: import java.util.ArrayList;
025: import java.util.Arrays;
026: import java.util.Iterator;
027: import java.util.List;
028: import java.util.StringTokenizer;
029:
030: import javax.swing.text.StyleConstants;
031:
032: import org.geotools.resources.Utilities;
033: import org.geotools.resources.XArray;
034:
035: /**
036: * A character stream that can be used to format tables. Columns are separated
037: * by tabulations (<code>'\t'</code>) and rows are separated by line terminators
038: * (<code>'\r'</code>, <code>'\n'</code> or <code>"\r\n"</code>). Every table's
039: * cells are stored in memory until {@link #flush()} is invoked. When invoked,
040: * {@link #flush()} copy cell's contents to the underlying stream while replacing
041: * tabulations by some amount of spaces. The exact number of spaces is computed
042: * from cell's widths. {@code TableWriter} produces correct output when
043: * displayed with a monospace font.
044: * <br><br>
045: * For example, the following code...
046: *
047: * <blockquote><pre>
048: * TableWriter out = new TableWriter(new OutputStreamWriter(System.out), 3);
049: * out.write("Prénom\tNom\n");
050: * out.nextLine('-');
051: * out.write("Idéphonse\tLaporte\nSarah\tCoursi\nYvan\tDubois");
052: * out.flush();
053: * </pre></blockquote>
054: *
055: * ...produces the following output:
056: *
057: * <blockquote><pre>
058: * Prénom Nom
059: * --------- -------
060: * Idéphonse Laporte
061: * Sarah Coursi
062: * Yvan Dubois
063: * </pre></blockquote>
064: *
065: * @since 2.0
066: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/metadata/src/main/java/org/geotools/io/TableWriter.java $
067: * @version $Id: TableWriter.java 22691 2006-11-10 22:30:28Z desruisseaux $
068: * @author Martin Desruisseaux
069: */
070: public class TableWriter extends FilterWriter {
071: /**
072: * A possible value for cell alignment. This
073: * specifies that the text is aligned to the left
074: * indent and extra whitespace should be placed on
075: * the right.
076: */
077: public static final int ALIGN_LEFT = StyleConstants.ALIGN_LEFT;
078:
079: /**
080: * A possible value for cell alignment. This
081: * specifies that the text is aligned to the right
082: * indent and extra whitespace should be placed on
083: * the left.
084: */
085: public static final int ALIGN_RIGHT = StyleConstants.ALIGN_RIGHT;
086:
087: /**
088: * A possible value for cell alignment. This
089: * specifies that the text is aligned to the center
090: * and extra whitespace should be placed equally on
091: * the left and right.
092: */
093: public static final int ALIGN_CENTER = StyleConstants.ALIGN_CENTER;
094:
095: /**
096: * Drawing-box characters. The last two characters
097: * are horizontal and vertical line respectively.
098: */
099: private static final char[][] BOX = new char[][] {
100: {// [0000]: single horizontal, single vertical
101: '\u250C', '\u252C', '\u2510', '\u251C', '\u253C', '\u2524',
102: '\u2514', '\u2534', '\u2518', '\u2500', '\u2502' },
103: {// [0001]: single horizontal, double vertical
104: '\u2553', '\u2565', '\u2556', '\u255F', '\u256B', '\u2562',
105: '\u2559', '\u2568', '\u255C', '\u2500', '\u2551' },
106: {// [0010]: double horizontal, single vertical
107: '\u2552', '\u2564', '\u2555', '\u255E', '\u256A', '\u2561',
108: '\u2558', '\u2567', '\u255B', '\u2550', '\u2502' },
109: {// [0011]: double horizontal, double vertical
110: '\u2554', '\u2566', '\u2557', '\u2560', '\u256C', '\u2563',
111: '\u255A', '\u2569', '\u255D', '\u2550', '\u2551' },
112: {// [0100]: ASCII characters only
113: '+', '+', '+', '+', '+', '+', '+', '+', '+', '-', '|' } };
114:
115: /**
116: * Default character for space.
117: */
118: private static final char SPACE = ' ';
119:
120: /**
121: * Temporary string buffer. This buffer
122: * contains only one cell's content.
123: */
124: private final StringBuffer buffer = new StringBuffer();
125:
126: /**
127: * List of {@link Cell} objects, from left to right and top to bottom.
128: * By convention, a {@code null} value or a {@link Cell} object
129: * with <code>{@link Cell#text}==null</code> are move to the next line.
130: */
131: private final List cells = new ArrayList();
132:
133: /**
134: * Alignment for current and next cells.
135: */
136: private int alignment = ALIGN_LEFT;
137:
138: /**
139: * Column position of the cell currently being written. The field
140: * is incremented each time {@link #nextColumn()} is invoked.
141: */
142: private int column;
143:
144: /**
145: * Line position of the cell currently being written. The field
146: * is incremented each time {@link #nextLine()} is invoked.
147: */
148: private int row;
149:
150: /**
151: * Maximum width for each columns. This array's length must
152: * be equals to the number of columns in this table.
153: */
154: private int width[] = new int[0];
155:
156: /**
157: * The column separator.
158: */
159: private final String separator;
160:
161: /**
162: * The left table border.
163: */
164: private final String leftBorder;
165:
166: /**
167: * The right table border.
168: */
169: private final String rightBorder;
170:
171: /**
172: * Tells if cells can span more than one line. If {@code true},
173: * then EOL characters likes '\n' move to the next line <em>inside</em>
174: * the current cell. If {@code false}, then EOL characters move to
175: * the next table's row. Default value is {@code false}.
176: */
177: private boolean multiLinesCells;
178:
179: /**
180: * {@code true} if this {@code TableWriter}
181: * has been constructed with the no-arg constructor.
182: */
183: private final boolean stringOnly;
184:
185: /**
186: * Tells if the next '\n' character must be ignored. This field
187: * is used in order to avoid writing two EOL in place of "\r\n".
188: */
189: private boolean skipCR;
190:
191: /**
192: * Creates a new table writer with a default column separator.
193: * Note: this writer may produces bad output on Windows console,
194: * unless the underlying stream use the correct codepage (e.g.
195: * <code>OutputStreamWriter(System.out, "Cp437")</code>).
196: * To display the appropriate codepage for a Windows NT console,
197: * type {@code chcp} on the command line.
198: *
199: * @param out Writer object to provide the underlying stream,
200: * or {@code null} if there is no underlying stream.
201: * If {@code out} is null, then the {@link #toString}
202: * method is the only way to get the table's content.
203: */
204: public TableWriter(final Writer out) {
205: super (out != null ? out : new StringWriter());
206: stringOnly = (out == null);
207: leftBorder = "\u2551 ";
208: rightBorder = " \u2551";
209: separator = " \u2502 ";
210: }
211:
212: /**
213: * Creates a new table writer with the specified
214: * amount of spaces as column separator.
215: *
216: * @param out Writer object to provide the underlying stream,
217: * or {@code null} if there is no underlying stream.
218: * If {@code out} is null, then the {@link #toString}
219: * method is the only way to get the table's content.
220: * @param spaces Amount of white spaces to use as column separator.
221: */
222: public TableWriter(final Writer out, final int spaces) {
223: this (out, Utilities.spaces(spaces));
224: }
225:
226: /**
227: * Creates a new table writer with the specified column separator.
228: *
229: * @param out Writer object to provide the underlying stream,
230: * or {@code null} if there is no underlying stream.
231: * If {@code out} is null, then the {@link #toString}
232: * method is the only way to get the table's content.
233: * @param separator String to write between columns. Drawing box characters
234: * are treated specially. For example <code>" \\u2502 "</code> can be
235: * used for a single-line box.
236: */
237: public TableWriter(final Writer out, final String separator) {
238: super (out != null ? out : new StringWriter());
239: stringOnly = (out == null);
240: final int length = separator.length();
241: int lower = 0;
242: int upper = length;
243: while (lower < length
244: && Character.isSpaceChar(separator.charAt(lower)))
245: lower++;
246: while (upper > 0
247: && Character.isSpaceChar(separator.charAt(upper - 1)))
248: upper--;
249: this .leftBorder = separator.substring(lower);
250: this .rightBorder = separator.substring(0, upper);
251: this .separator = separator;
252: }
253:
254: /**
255: * Writes a border or a corner to the specified stream.
256: *
257: * @param out The destination stream.
258: * @param horizontalBorder -1 for left border, +1 for right border, 0 for center.
259: * @param verticalBorder -1 for top border, +1 for bottom border, 0 for center.
260: * @param horizontalChar Character to use for horizontal line.
261: * @throws IOException if the writting operation failed.
262: */
263: private void writeBorder(final Writer out,
264: final int horizontalBorder, final int verticalBorder,
265: final char horizontalChar) throws IOException {
266: /*
267: * Obtiens les ensembles de caractères qui
268: * conviennent pour la ligne horizontale.
269: */
270: int boxCount = 0;
271: final char[][] box = new char[BOX.length][];
272: for (int i = 0; i < BOX.length; i++) {
273: if (BOX[i][9] == horizontalChar) {
274: box[boxCount++] = BOX[i];
275: }
276: }
277: /*
278: * Obtient une chaine contenant les lignes verticales à
279: * dessiner à gauche, à droite ou au centre de la table.
280: */
281: final String border;
282: switch (horizontalBorder) {
283: case -1:
284: border = leftBorder;
285: break;
286: case +1:
287: border = rightBorder;
288: break;
289: case 0:
290: border = separator;
291: break;
292: default:
293: throw new IllegalArgumentException(String
294: .valueOf(horizontalBorder));
295: }
296: if (verticalBorder < -1 || verticalBorder > +1) {
297: throw new IllegalArgumentException(String
298: .valueOf(verticalBorder));
299: }
300: /*
301: * Remplace les espaces par la ligne horizontale,
302: * et les lignes verticales par une intersection.
303: */
304: final int index = (horizontalBorder + 1) + (verticalBorder + 1)
305: * 3;
306: final int borderLength = border.length();
307: for (int i = 0; i < borderLength; i++) {
308: char c = border.charAt(i);
309: if (Character.isSpaceChar(c)) {
310: c = horizontalChar;
311: } else {
312: for (int j = 0; j < boxCount; j++) {
313: if (box[j][10] == c) {
314: c = box[j][index];
315: break;
316: }
317: }
318: }
319: out.write(c);
320: }
321: }
322:
323: /**
324: * Set the desired behavior for EOL and tabulations characters.
325: * <ul>
326: * <li>If {@code true}, EOL (<code>'\r'</code>, <code>'\n'</code> or
327: * <code>"\r\n"</code>) and tabulations (<code>'\t'</code>) characters
328: * are copied straight into the current cell, which mean that next write
329: * operations will continue inside the same cell.</li>
330: * <li>If {@code false}, then tabulations move to next column and EOL move
331: * to the first cell of next row (i.e. tabulation and EOL are equivalent to
332: * {@link #nextColumn()} and {@link #nextLine()} calls respectively).</li>
333: * </ul>
334: * The default value is {@code false}.
335: *
336: * @param multiLines {@code true} true if EOL are used for line feeds inside
337: * current cells, or {@code false} if EOL move to the next row.
338: */
339: public void setMultiLinesCells(final boolean multiLines) {
340: synchronized (lock) {
341: multiLinesCells = multiLines;
342: }
343: }
344:
345: /**
346: * Tells if EOL characters are used for line feeds inside current cells.
347: */
348: public boolean isMultiLinesCells() {
349: synchronized (lock) {
350: return multiLinesCells;
351: }
352: }
353:
354: /**
355: * Set the alignment for all cells in the specified column. This method
356: * overwrite the alignment for all previous cells in the specified column.
357: *
358: * @param column The 0-based column number.
359: * @param alignment Cell alignment. Must be {@link #ALIGN_LEFT}
360: * {@link #ALIGN_RIGHT} or {@link #ALIGN_CENTER}.
361: */
362: public void setColumnAlignment(final int column, final int alignment) {
363: if (alignment != ALIGN_LEFT && alignment != ALIGN_RIGHT
364: && alignment != ALIGN_CENTER) {
365: throw new IllegalArgumentException(String
366: .valueOf(alignment));
367: }
368: synchronized (lock) {
369: int current = 0;
370: for (final Iterator it = cells.iterator(); it.hasNext();) {
371: final Cell cell = (Cell) it.next();
372: if (cell == null || cell.text == null) {
373: current = 0;
374: continue;
375: }
376: if (current == column) {
377: cell.alignment = alignment;
378: }
379: current++;
380: }
381: }
382: }
383:
384: /**
385: * Set the alignment for current and next cells. Change to the
386: * alignment doesn't affect the alignment of previous cells and
387: * previous rows. The default alignment is {@link #ALIGN_LEFT}.
388: *
389: * @param alignment Cell alignment. Must be {@link #ALIGN_LEFT}
390: * {@link #ALIGN_RIGHT} or {@link #ALIGN_CENTER}.
391: */
392: public void setAlignment(final int alignment) {
393: if (alignment != ALIGN_LEFT && alignment != ALIGN_RIGHT
394: && alignment != ALIGN_CENTER) {
395: throw new IllegalArgumentException(String
396: .valueOf(alignment));
397: }
398: synchronized (lock) {
399: this .alignment = alignment;
400: }
401: }
402:
403: /**
404: * Returns the alignment for current and next cells.
405: *
406: * @return Cell alignment: {@link #ALIGN_LEFT} (the default),
407: * {@link #ALIGN_RIGHT} or {@link #ALIGN_CENTER}.
408: */
409: public int getAlignment() {
410: synchronized (lock) {
411: return alignment;
412: }
413: }
414:
415: /**
416: * Returns the number of rows in this table.
417: * This count is reset to 0 by {@link #flush}.
418: */
419: private int getRowCount() {
420: int count = row;
421: if (column != 0) {
422: count++;
423: }
424: return count;
425: }
426:
427: /**
428: * Returns the number of columns in this table.
429: */
430: private int getColumnCount() {
431: return width.length;
432: }
433:
434: /**
435: * Write a single character. If {@link #isMultiLinesCells()}
436: * is false (which is the default), then:
437: * <ul>
438: * <li>Tabulations (<code>'\t'</code>) are replaced
439: * by {@link #nextColumn()} invocations.</li>
440: * <li>Line separators (<code>'\r'</code>, <code>'\n'</code>
441: * or <code>"\r\n"</code>) are replaced
442: * by {@link #nextLine()} invocations.</li>
443: * </ul>
444: *
445: * @param c Character to write.
446: */
447: public void write(final int c) {
448: synchronized (lock) {
449: if (!multiLinesCells) {
450: switch (c) {
451: case '\t': {
452: nextColumn();
453: skipCR = false;
454: return;
455: }
456: case '\r': {
457: nextLine();
458: skipCR = true;
459: return;
460: }
461: case '\n': {
462: if (!skipCR) {
463: nextLine();
464: }
465: skipCR = false;
466: return;
467: }
468: }
469: }
470: if (c < Character.MIN_VALUE || c > Character.MAX_VALUE) {
471: throw new IllegalArgumentException(String.valueOf(c));
472: }
473: buffer.append((char) c);
474: skipCR = false;
475: }
476: }
477:
478: /**
479: * Write a string. Tabulations and line separators
480: * are interpreted as by {@link #write(int)}.
481: *
482: * @param string String to write.
483: */
484: public void write(final String string) {
485: write(string, 0, string.length());
486: }
487:
488: /**
489: * Write a portion of a string. Tabulations and line
490: * separators are interpreted as by {@link #write(int)}.
491: *
492: * @param string String to write.
493: * @param offset Offset from which to start writing characters.
494: * @param length Number of characters to write.
495: */
496: public void write(final String string, int offset, int length) {
497: if (offset < 0 || length < 0
498: || (offset + length) > string.length()) {
499: throw new IndexOutOfBoundsException();
500: }
501: if (length == 0) {
502: return;
503: }
504: synchronized (lock) {
505: if (skipCR && string.charAt(offset) == '\n') {
506: offset++;
507: length--;
508: }
509: if (!multiLinesCells) {
510: int upper = offset;
511: for (; length != 0; length--) {
512: switch (string.charAt(upper++)) {
513: case '\t': {
514: buffer.append(string.substring(offset,
515: upper - 1));
516: nextColumn();
517: offset = upper;
518: break;
519: }
520: case '\r': {
521: buffer.append(string.substring(offset,
522: upper - 1));
523: nextLine();
524: if (length != 0 && string.charAt(upper) == '\n') {
525: upper++;
526: length--;
527: }
528: offset = upper;
529: break;
530: }
531: case '\n': {
532: buffer.append(string.substring(offset,
533: upper - 1));
534: nextLine();
535: offset = upper;
536: break;
537: }
538: }
539: }
540: length = upper - offset;
541: }
542: skipCR = (string.charAt(offset + length - 1) == '\r');
543: buffer.append(string.substring(offset, offset + length));
544: }
545: }
546:
547: /**
548: * Write an array of characters. Tabulations and line
549: * separators are interpreted as by {@link #write(int)}.
550: *
551: * @param cbuf Array of characters to be written.
552: */
553: public void write(final char cbuf[]) {
554: write(cbuf, 0, cbuf.length);
555: }
556:
557: /**
558: * Write a portion of an array of characters. Tabulations and
559: * line separators are interpreted as by {@link #write(int)}.
560: *
561: * @param cbuf Array of characters.
562: * @param offset Offset from which to start writing characters.
563: * @param length Number of characters to write.
564: */
565: public void write(final char cbuf[], int offset, int length) {
566: if (offset < 0 || length < 0 || (offset + length) > cbuf.length) {
567: throw new IndexOutOfBoundsException();
568: }
569: if (length == 0) {
570: return;
571: }
572: synchronized (lock) {
573: if (skipCR && cbuf[offset] == '\n') {
574: offset++;
575: length--;
576: }
577: if (!multiLinesCells) {
578: int upper = offset;
579: for (; length != 0; length--) {
580: switch (cbuf[upper++]) {
581: case '\t': {
582: buffer.append(cbuf, offset, upper - offset - 1);
583: nextColumn();
584: offset = upper;
585: break;
586: }
587: case '\r': {
588: buffer.append(cbuf, offset, upper - offset - 1);
589: nextLine();
590: if (length != 0 && cbuf[upper] == '\n') {
591: upper++;
592: length--;
593: }
594: offset = upper;
595: break;
596: }
597: case '\n': {
598: buffer.append(cbuf, offset, upper - offset - 1);
599: nextLine();
600: offset = upper;
601: break;
602: }
603: }
604: }
605: length = upper - offset;
606: }
607: skipCR = (cbuf[offset + length - 1] == '\r');
608: buffer.append(cbuf, offset, length);
609: }
610: }
611:
612: /**
613: * Write an horizontal separator.
614: */
615: public void writeHorizontalSeparator() {
616: synchronized (lock) {
617: if (column != 0 || buffer.length() != 0) {
618: nextLine();
619: }
620: nextLine('\u2500');
621: }
622: }
623:
624: /**
625: * Moves one column to the right. Next write
626: * operations will occur in a new cell on the
627: * same row.
628: */
629: public void nextColumn() {
630: nextColumn(SPACE);
631: }
632:
633: /**
634: * Moves one column to the right. Next write operations will occur in a
635: * new cell on the same row. This method fill every remaining space in
636: * the current cell with the specified character. For example calling
637: * <code>nextColumn('*')</code> from the first character of a cell is
638: * a convenient way to put a pad value in this cell.
639: *
640: * @param fill Character filling the cell (default to whitespace).
641: */
642: public void nextColumn(final char fill) {
643: synchronized (lock) {
644: final String cellText = buffer.toString();
645: cells.add(new Cell(cellText, alignment, fill));
646: if (column >= width.length) {
647: width = XArray.resize(width, column + 1);
648: }
649: int length = 0;
650: final StringTokenizer tk = new StringTokenizer(cellText,
651: "\r\n");
652: while (tk.hasMoreTokens()) {
653: final int lg = tk.nextToken().length();
654: if (lg > length) {
655: length = lg;
656: }
657: }
658: if (length > width[column]) {
659: width[column] = length;
660: }
661: column++;
662: buffer.setLength(0);
663: }
664: }
665:
666: /**
667: * Moves to the first column on the next row.
668: * Next write operations will occur on a new row.
669: */
670: public void nextLine() {
671: nextLine(SPACE);
672: }
673:
674: /**
675: * Moves to the first column on the next row. Next write operations will
676: * occur on a new row. This method fill every remaining cell in the current
677: * row with the specified character. Calling <code>nextLine('-')</code>
678: * from the first column of a row is a convenient way to fill this row
679: * with a line separator.
680: *
681: * @param fill Character filling the rest of the line
682: * (default to whitespace). This caracter may
683: * be use as a row separator.
684: */
685: public void nextLine(final char fill) {
686: synchronized (lock) {
687: if (buffer.length() != 0) {
688: nextColumn(fill);
689: }
690: assert buffer.length() == 0;
691: cells.add(!Character.isSpaceChar(fill) ? new Cell(null,
692: alignment, fill) : null);
693: column = 0;
694: row++;
695: }
696: }
697:
698: /**
699: * Flush the table content to the underlying stream.
700: * This method should not be called before the table
701: * is completed (otherwise, columns may have the
702: * wrong width).
703: *
704: * @throws IOException if an output operation failed.
705: */
706: public void flush() throws IOException {
707: synchronized (lock) {
708: if (buffer.length() != 0) {
709: nextLine();
710: assert buffer.length() == 0;
711: }
712: flushTo(out);
713: row = column = 0;
714: cells.clear();
715: if (!(out instanceof TableWriter)) {
716: /*
717: * Flush only if this table is not included in an outer (bigger) table.
718: * This is because flushing the outer table would break its formatting.
719: */
720: out.flush();
721: }
722: }
723: }
724:
725: /**
726: * Flush the table content and close the underlying stream.
727: *
728: * @throws IOException if an output operation failed.
729: */
730: public void close() throws IOException {
731: synchronized (lock) {
732: flush();
733: out.close();
734: }
735: }
736:
737: /**
738: * Ecrit vers le flot spécifié toutes les cellules qui avaient été disposées
739: * dans le tableau. Ces cellules seront automatiquement alignées en colonnes.
740: * Cette méthode peut être appelée plusieurs fois pour écrire le même tableau
741: * par exemple vers plusieurs flots.
742: *
743: * @param out Flot vers où écrire les données.
744: * @throws IOException si une erreur est survenue lors de l'écriture dans
745: * {@code out}.
746: */
747: private void flushTo(final Writer out) throws IOException {
748: final String columnSeparator = this .separator;
749: final String lineSeparator = System.getProperty(
750: "line.separator", "\n");
751: final Cell[] currentLine = new Cell[width.length];
752: final int cellCount = cells.size();
753: for (int cellIndex = 0; cellIndex < cellCount; cellIndex++) {
754: /*
755: * Copie dans {@code currentLine} toutes les données qui seront à écrire
756: * sur la ligne courante de la table. Ces données excluent le {@code null}
757: * terminal. La liste {@code currentLine} ne contiendra donc initialement
758: * aucun élément nul, mais ses éléments seront progressivement modifiés (et mis
759: * à {@code null}) pendant l'écriture de la ligne dans la boucle qui suit.
760: */
761: Cell lineFill = null;
762: int currentCount = 0;
763: do {
764: final Cell cell = (Cell) cells.get(cellIndex);
765: if (cell == null) {
766: break;
767: }
768: if (cell.text == null) {
769: lineFill = new Cell("", cell.alignment, cell.fill);
770: break;
771: }
772: currentLine[currentCount++] = cell;
773: } while (++cellIndex < cellCount);
774: Arrays.fill(currentLine, currentCount, currentLine.length,
775: lineFill);
776: /*
777: * La boucle suivante sera exécutée tant qu'il reste des lignes à écrire
778: * (c'est-à-dire tant qu'au moins un élément de {@code currentLine}
779: * est non-nul). Si une cellule contient un texte avec des caractères EOL,
780: * alors cette cellule devra s'écrire sur plusieurs lignes dans la cellule
781: * courante.
782: */
783: while (!isEmpty(currentLine)) {
784: for (int j = 0; j < currentLine.length; j++) {
785: final boolean isFirstColumn = (j == 0);
786: final boolean isLastColumn = (j + 1 == currentLine.length);
787: final Cell cell = currentLine[j];
788: final int cellWidth = width[j];
789: if (cell == null) {
790: if (isFirstColumn) {
791: out.write(leftBorder);
792: }
793: repeat(out, SPACE, cellWidth);
794: out.write(isLastColumn ? rightBorder
795: : columnSeparator);
796: continue;
797: }
798: String cellText = cell.toString();
799: int endCR = cellText.indexOf('\r');
800: int endLF = cellText.indexOf('\n');
801: int end = (endCR < 0) ? endLF : (endLF < 0) ? endCR
802: : Math.min(endCR, endLF);
803: if (end >= 0) {
804: /*
805: * Si un retour chariot a été trouvé, n'écrit que la première
806: * ligne de la cellule. L'élément {@code currentLine[j]}
807: * sera modifié pour ne contenir que les lignes restantes qui
808: * seront écrites lors d'un prochain passage dans la boucle.
809: */
810: int top = end + 1;
811: if (endCR >= 0 && endCR + 1 == endLF)
812: top++;
813: int scan = top;
814: final int textLength = cellText.length();
815: while (scan < textLength
816: && Character.isWhitespace(cellText
817: .charAt(scan))) {
818: scan++;
819: }
820: currentLine[j] = (scan < textLength) ? cell
821: .substring(top) : null;
822: cellText = cellText.substring(0, end);
823: } else
824: currentLine[j] = null;
825: final int textLength = cellText.length();
826: /*
827: * Si la cellule à écrire est en fait une bordure,
828: * on fera un traitement spécial pour utiliser les
829: * caractˆres de jointures {@link #BOX}.
830: */
831: if (currentCount == 0) {
832: assert textLength == 0;
833: final int verticalBorder;
834: if (cellIndex == 0)
835: verticalBorder = -1;
836: else if (cellIndex >= cellCount - 1)
837: verticalBorder = +1;
838: else
839: verticalBorder = 0;
840: if (isFirstColumn) {
841: writeBorder(out, -1, verticalBorder,
842: cell.fill);
843: }
844: repeat(out, cell.fill, cellWidth);
845: writeBorder(out, isLastColumn ? +1 : 0,
846: verticalBorder, cell.fill);
847: continue;
848: }
849: /*
850: * Si la cellule n'est pas une bordure, il s'agit
851: * d'une cellule "normale". Procˆde maintenant à
852: * l'écriture d'une ligne de la cellule.
853: */
854: if (isFirstColumn) {
855: out.write(leftBorder);
856: }
857: final Writer tabExpander = (cellText.indexOf('\t') >= 0) ? new ExpandedTabWriter(
858: out)
859: : out;
860: switch (cell.alignment) {
861: default: {
862: // Should not happen.
863: throw new AssertionError(cell.alignment);
864: }
865: case ALIGN_LEFT: {
866: tabExpander.write(cellText);
867: repeat(tabExpander, cell.fill, cellWidth
868: - textLength);
869: break;
870: }
871: case ALIGN_RIGHT: {
872: repeat(tabExpander, cell.fill, cellWidth
873: - textLength);
874: tabExpander.write(cellText);
875: break;
876: }
877: case ALIGN_CENTER: {
878: final int rightMargin = (cellWidth - textLength) / 2;
879: repeat(tabExpander, cell.fill, rightMargin);
880: tabExpander.write(cellText);
881: repeat(tabExpander, cell.fill,
882: (cellWidth - rightMargin) - textLength);
883: break;
884: }
885: }
886: out.write(isLastColumn ? rightBorder
887: : columnSeparator);
888: }
889: out.write(lineSeparator);
890: }
891: }
892: }
893:
894: /**
895: * Checks if {@code array} contains
896: * only {@code null} elements.
897: */
898: private static boolean isEmpty(final Object[] array) {
899: for (int i = array.length; --i >= 0;) {
900: if (array[i] != null) {
901: return false;
902: }
903: }
904: return true;
905: }
906:
907: /**
908: * Repeat a character.
909: *
910: * @param out The destination stream.
911: * @param car Character to write (usually ' ').
912: * @param count Number of repetition.
913: */
914: private static void repeat(final Writer out, final char car,
915: int count) throws IOException {
916: while (--count >= 0) {
917: out.write(car);
918: }
919: }
920:
921: /**
922: * Returns the table content as a string.
923: */
924: public String toString() {
925: synchronized (lock) {
926: int capacity = 2; // Room for EOL.
927: for (int i = 0; i < width.length; i++) {
928: capacity += width[i];
929: }
930: capacity *= getRowCount();
931: final StringWriter writer;
932: if (stringOnly) {
933: writer = (StringWriter) out;
934: final StringBuffer buffer = writer.getBuffer();
935: buffer.setLength(0);
936: buffer.ensureCapacity(capacity);
937: } else {
938: writer = new StringWriter(capacity);
939: }
940: try {
941: flushTo(writer);
942: } catch (IOException exception) {
943: // Should not happen
944: throw new AssertionError(exception);
945: }
946: return writer.toString();
947: }
948: }
949:
950: /**
951: * A class wrapping a cell's content and its text's alignment.
952: * This class if for internal use only.
953: *
954: * @version $Id: TableWriter.java 22691 2006-11-10 22:30:28Z desruisseaux $
955: * @author Martin Desruisseaux
956: */
957: private static final class Cell {
958: /**
959: * The text to write inside the cell.
960: */
961: public final String text;
962:
963: /**
964: * The alignment for {@link #text} inside the cell.
965: */
966: public int alignment;
967:
968: /**
969: * The fill character, used for filling space inside the cell.
970: */
971: public final char fill;
972:
973: /**
974: * Returns a new cell wrapping the specified string with the
975: * specified alignment and fill character.
976: */
977: public Cell(final String text, final int alignment,
978: final char fill) {
979: this .text = text;
980: this .alignment = alignment;
981: this .fill = fill;
982: }
983:
984: /**
985: * Returns a new cell which contains substring of this cell.
986: */
987: public Cell substring(final int lower) {
988: return new Cell(text.substring(lower), alignment, fill);
989: }
990:
991: /**
992: * Returns the cell's content.
993: */
994: public String toString() {
995: return text;
996: }
997: }
998: }
|