From a88d0cab3caee99d585e9dd22823b9c4f80f796c Mon Sep 17 00:00:00 2001
From: matthew_swift <matthew_swift@localhost>
Date: Fri, 22 Jun 2007 15:59:44 +0000
Subject: [PATCH] Add various classes which facilitate generating tables in text-based applications.

---
 opends/src/server/org/opends/server/util/table/TableBuilder.java             |  381 +++++++++++++
 opends/src/server/org/opends/server/util/table/package-info.java             |   80 ++
 opends/src/server/org/opends/server/util/table/TablePrinter.java             |   56 +
 opends/src/server/org/opends/server/util/table/TextTablePrinter.java         |  531 ++++++++++++++++++
 opends/src/server/org/opends/server/util/table/TabSeparatedTablePrinter.java |  220 +++++++
 opends/src/server/org/opends/server/util/table/CSVTablePrinter.java          |  253 ++++++++
 opends/src/server/org/opends/server/util/table/TableSerializer.java          |  160 +++++
 7 files changed, 1,681 insertions(+), 0 deletions(-)

diff --git a/opends/src/server/org/opends/server/util/table/CSVTablePrinter.java b/opends/src/server/org/opends/server/util/table/CSVTablePrinter.java
new file mode 100644
index 0000000..c2ca6cc
--- /dev/null
+++ b/opends/src/server/org/opends/server/util/table/CSVTablePrinter.java
@@ -0,0 +1,253 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.util.table;
+
+
+
+import java.io.BufferedWriter;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.Writer;
+
+
+
+/**
+ * An interface for creating a CSV formatted table.
+ */
+public final class CSVTablePrinter extends TablePrinter {
+
+  /**
+   * Table serializer implementation.
+   */
+  private static final class Serializer extends TableSerializer {
+
+    // The current column being output.
+    private int column = 0;
+
+    // Indicates whether or not the headings should be output.
+    private final boolean displayHeadings;
+
+    // Counts the number of separators that should be output the next
+    // time a non-empty cell is displayed. The comma separators are
+    // not displayed immediately so that we can avoid displaying
+    // unnecessary trailing separators.
+    private int requiredSeparators = 0;
+
+    // The output destination.
+    private final PrintWriter writer;
+
+
+
+    // Private constructor.
+    private Serializer(PrintWriter writer, boolean displayHeadings) {
+      this.writer = writer;
+      this.displayHeadings = displayHeadings;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void addCell(String s) {
+      // Avoid printing comma separators for trailing empty cells.
+      if (s.length() == 0) {
+        requiredSeparators++;
+      } else {
+        for (int i = 0; i < requiredSeparators; i++) {
+          writer.print(',');
+        }
+        requiredSeparators = 1;
+      }
+
+      boolean needsQuoting = false;
+
+      if (s.contains(",")) {
+        needsQuoting = true;
+      }
+
+      if (s.contains("\n")) {
+        needsQuoting = true;
+      }
+
+      if (s.contains("\r")) {
+        needsQuoting = true;
+      }
+
+      if (s.contains("\"")) {
+        needsQuoting = true;
+        s = s.replace("\"", "\"\"");
+      }
+
+      if (s.startsWith(" ")) {
+        needsQuoting = true;
+      }
+
+      if (s.endsWith(" ")) {
+        needsQuoting = true;
+      }
+
+      StringBuilder builder = new StringBuilder();
+      if (needsQuoting) {
+        builder.append("\"");
+      }
+
+      builder.append(s);
+
+      if (needsQuoting) {
+        builder.append("\"");
+      }
+
+      writer.print(builder.toString());
+      column++;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void addHeading(String s) {
+      if (displayHeadings) {
+        addCell(s);
+      }
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void endHeader() {
+      if (displayHeadings) {
+        writer.println();
+      }
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void endRow() {
+      writer.println();
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void endTable() {
+      writer.flush();
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void startHeader() {
+      column = 0;
+      requiredSeparators = 0;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void startRow() {
+      column = 0;
+      requiredSeparators = 0;
+    }
+  }
+
+  // Indicates whether or not the headings should be output.
+  private boolean displayHeadings = false;
+
+  // The output destination.
+  private PrintWriter writer = null;
+
+
+
+  /**
+   * Creates a new CSV table printer for the specified output stream.
+   * Headings will not be displayed by default.
+   *
+   * @param stream
+   *          The stream to output tables to.
+   */
+  public CSVTablePrinter(OutputStream stream) {
+    this(new BufferedWriter(new OutputStreamWriter(stream)));
+  }
+
+
+
+  /**
+   * Creates a new CSV table printer for the specified writer.
+   * Headings will not be displayed by default.
+   *
+   * @param writer
+   *          The writer to output tables to.
+   */
+  public CSVTablePrinter(Writer writer) {
+    this.writer = new PrintWriter(writer);
+  }
+
+
+
+  /**
+   * Specify whether or not table headings should be displayed.
+   *
+   * @param displayHeadings
+   *          <code>true</code> if table headings should be
+   *          displayed.
+   */
+  public void setDisplayHeadings(boolean displayHeadings) {
+    this.displayHeadings = displayHeadings;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  protected TableSerializer getSerializer() {
+    return new Serializer(writer, displayHeadings);
+  }
+
+}
diff --git a/opends/src/server/org/opends/server/util/table/TabSeparatedTablePrinter.java b/opends/src/server/org/opends/server/util/table/TabSeparatedTablePrinter.java
new file mode 100644
index 0000000..09b627d
--- /dev/null
+++ b/opends/src/server/org/opends/server/util/table/TabSeparatedTablePrinter.java
@@ -0,0 +1,220 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.util.table;
+
+
+
+import java.io.BufferedWriter;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.Writer;
+
+
+
+/**
+ * An interface for creating a tab-separated formatted table.
+ * <p>
+ * This table printer will replace any tab, line-feeds, or carriage
+ * return control characters encountered in a cell with a single
+ * space.
+ */
+public final class TabSeparatedTablePrinter extends TablePrinter {
+
+  /**
+   * Table serializer implementation.
+   */
+  private static final class Serializer extends TableSerializer {
+
+    // The current column being output.
+    private int column = 0;
+
+    // Indicates whether or not the headings should be output.
+    private final boolean displayHeadings;
+
+    // Counts the number of separators that should be output the next
+    // time a non-empty cell is displayed. The tab separators are
+    // not displayed immediately so that we can avoid displaying
+    // unnecessary trailing separators.
+    private int requiredSeparators = 0;
+
+    // The output destination.
+    private final PrintWriter writer;
+
+
+
+    // Private constructor.
+    private Serializer(PrintWriter writer, boolean displayHeadings) {
+      this.writer = writer;
+      this.displayHeadings = displayHeadings;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void addCell(String s) {
+      // Avoid printing tab separators for trailing empty cells.
+      if (s.length() == 0) {
+        requiredSeparators++;
+      } else {
+        for (int i = 0; i < requiredSeparators; i++) {
+          writer.print('\t');
+        }
+        requiredSeparators = 1;
+      }
+
+      // Replace all new-lines and tabs with a single space.
+      writer.print(s.replaceAll("[\\t\\n\\r]", " "));
+      column++;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void addHeading(String s) {
+      if (displayHeadings) {
+        addCell(s);
+      }
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void endHeader() {
+      if (displayHeadings) {
+        writer.println();
+      }
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void endRow() {
+      writer.println();
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void endTable() {
+      writer.flush();
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void startHeader() {
+      column = 0;
+      requiredSeparators = 0;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void startRow() {
+      column = 0;
+      requiredSeparators = 0;
+    }
+  }
+
+  // Indicates whether or not the headings should be output.
+  private boolean displayHeadings = false;
+
+  // The output destination.
+  private PrintWriter writer = null;
+
+
+
+  /**
+   * Creates a new tab separated table printer for the specified
+   * output stream. Headings will not be displayed by default.
+   *
+   * @param stream
+   *          The stream to output tables to.
+   */
+  public TabSeparatedTablePrinter(OutputStream stream) {
+    this(new BufferedWriter(new OutputStreamWriter(stream)));
+  }
+
+
+
+  /**
+   * Creates a new tab separated table printer for the specified
+   * writer. Headings will not be displayed by default.
+   *
+   * @param writer
+   *          The writer to output tables to.
+   */
+  public TabSeparatedTablePrinter(Writer writer) {
+    this.writer = new PrintWriter(writer);
+  }
+
+
+
+  /**
+   * Specify whether or not table headings should be displayed.
+   *
+   * @param displayHeadings
+   *          <code>true</code> if table headings should be
+   *          displayed.
+   */
+  public void setDisplayHeadings(boolean displayHeadings) {
+    this.displayHeadings = displayHeadings;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  protected TableSerializer getSerializer() {
+    return new Serializer(writer, displayHeadings);
+  }
+
+}
diff --git a/opends/src/server/org/opends/server/util/table/TableBuilder.java b/opends/src/server/org/opends/server/util/table/TableBuilder.java
new file mode 100644
index 0000000..7706250
--- /dev/null
+++ b/opends/src/server/org/opends/server/util/table/TableBuilder.java
@@ -0,0 +1,381 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.util.table;
+
+
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+
+
+/**
+ * A class which can be used to construct tables of information to be
+ * displayed in a terminal. Once built the table can be output using a
+ * {@link TableSerializer}.
+ */
+public final class TableBuilder {
+
+  // The current column number in the current row where 0 represents
+  // the left-most column in the table.
+  private int column = 0;
+
+  // The current with of each column.
+  private List<Integer> columnWidths = new ArrayList<Integer>();
+
+  // The list of column headings.
+  private List<String> header = new ArrayList<String>();
+
+  // The current number of rows in the table.
+  private int height = 0;
+
+  // The list of table rows.
+  private List<List<String>> rows = new ArrayList<List<String>>();
+
+  // The linked list of sort keys comparators.
+  private List<Comparator<String>> sortComparators =
+    new ArrayList<Comparator<String>>();
+
+  // The linked list of sort keys.
+  private List<Integer> sortKeys = new ArrayList<Integer>();
+
+  // The current number of columns in the table.
+  private int width = 0;
+
+
+
+  /**
+   * Creates a new table printer.
+   */
+  public TableBuilder() {
+    // No implementation required.
+  }
+
+
+
+  /**
+   * Adds a table sort key. The table will be sorted according to the
+   * case-insensitive string ordering of the cells in the specified
+   * column.
+   *
+   * @param column
+   *          The column which will be used as a sort key.
+   */
+  public void addSortKey(int column) {
+    addSortKey(column, String.CASE_INSENSITIVE_ORDER);
+  }
+
+
+
+  /**
+   * Adds a table sort key. The table will be sorted according to the
+   * provided string comparator.
+   *
+   * @param column
+   *          The column which will be used as a sort key.
+   * @param comparator
+   *          The string comparator.
+   */
+  public void addSortKey(int column, Comparator<String> comparator) {
+    sortKeys.add(column);
+    sortComparators.add(comparator);
+  }
+
+
+
+  /**
+   * Appends a new blank cell to the current row.
+   */
+  public void appendCell() {
+    appendCell("");
+  }
+
+
+
+  /**
+   * Appends a new cell to the current row containing the provided
+   * boolean value.
+   *
+   * @param value
+   *          The boolean value.
+   */
+  public void appendCell(boolean value) {
+    appendCell(String.valueOf(value));
+  }
+
+
+
+  /**
+   * Appends a new cell to the current row containing the provided
+   * byte value.
+   *
+   * @param value
+   *          The byte value.
+   */
+  public void appendCell(byte value) {
+    appendCell(String.valueOf(value));
+  }
+
+
+
+  /**
+   * Appends a new cell to the current row containing the provided
+   * char value.
+   *
+   * @param value
+   *          The char value.
+   */
+  public void appendCell(char value) {
+    appendCell(String.valueOf(value));
+  }
+
+
+
+  /**
+   * Appends a new cell to the current row containing the provided
+   * double value.
+   *
+   * @param value
+   *          The double value.
+   */
+  public void appendCell(double value) {
+    appendCell(String.valueOf(value));
+  }
+
+
+
+  /**
+   * Appends a new cell to the current row containing the provided
+   * float value.
+   *
+   * @param value
+   *          The float value.
+   */
+  public void appendCell(float value) {
+    appendCell(String.valueOf(value));
+  }
+
+
+
+  /**
+   * Appends a new cell to the current row containing the provided
+   * integer value.
+   *
+   * @param value
+   *          The boolean value.
+   */
+  public void appendCell(int value) {
+    appendCell(String.valueOf(value));
+  }
+
+
+
+  /**
+   * Appends a new cell to the current row containing the provided
+   * long value.
+   *
+   * @param value
+   *          The long value.
+   */
+  public void appendCell(long value) {
+    appendCell(String.valueOf(value));
+  }
+
+
+
+  /**
+   * Appends a new cell to the current row containing the provided
+   * object value.
+   *
+   * @param value
+   *          The object value.
+   */
+  public void appendCell(Object value) {
+    // Make sure that the first row has been created.
+    if (height == 0) {
+      startRow();
+    }
+
+    // Create the cell.
+    String s = String.valueOf(value);
+    rows.get(height - 1).add(String.valueOf(value));
+    column++;
+
+    // Update statistics.
+    if (column > width) {
+      width = column;
+      columnWidths.add(s.length());
+    } else if (columnWidths.get(column - 1) < s.length()) {
+      columnWidths.set(column - 1, s.length());
+    }
+  }
+
+
+
+  /**
+   * Appends a new blank column heading to the header row.
+   */
+  public void appendHeading() {
+    appendHeading("");
+  }
+
+
+
+  /**
+   * Appends a new column heading to the header row.
+   *
+   * @param value
+   *          The column heading value.
+   */
+  public void appendHeading(String value) {
+    header.add(value);
+
+    // Update statistics.
+    if (header.size() > width) {
+      width = header.size();
+      columnWidths.add(value.length());
+    } else if (columnWidths.get(header.size() - 1) < value.length()) {
+      columnWidths.set(header.size() - 1, value.length());
+    }
+  }
+
+
+
+  /**
+   * Gets the width of the current row.
+   *
+   * @return Returns the width of the current row.
+   */
+  public int getRowWidth() {
+    return column;
+  }
+
+
+
+  /**
+   * Gets the number of rows in table.
+   *
+   * @return Returns the number of rows in table.
+   */
+  public int getTableHeight() {
+    return height;
+  }
+
+
+
+  /**
+   * Gets the number of columns in table.
+   *
+   * @return Returns the number of columns in table.
+   */
+  public int getTableWidth() {
+    return width;
+  }
+
+
+
+  /**
+   * Prints the table in its current state using the provided table
+   * printer.
+   *
+   * @param printer
+   *          The table printer.
+   */
+  public void print(TablePrinter printer) {
+    // Create a new printer instance.
+    TableSerializer serializer = printer.getSerializer();
+
+    // First sort the table.
+    List<List<String>> sortedRows = new ArrayList<List<String>>(rows);
+
+    Comparator<List<String>> comparator = new Comparator<List<String>>() {
+
+      public int compare(List<String> row1, List<String> row2) {
+        for (int i = 0; i < sortKeys.size(); i++) {
+          String cell1 = row1.get(sortKeys.get(i));
+          String cell2 = row2.get(sortKeys.get(i));
+
+          int rc = sortComparators.get(i).compare(cell1, cell2);
+          if (rc != 0) {
+            return rc;
+          }
+        }
+
+        // Both rows are equal.
+        return 0;
+      }
+
+    };
+
+    Collections.sort(sortedRows, comparator);
+
+    // Now ouput the table.
+    serializer.startTable(height, width);
+    for (int i = 0; i < width; i++) {
+      serializer.addColumn(columnWidths.get(i));
+    }
+
+    // Column headings.
+    serializer.startHeader();
+    for (String s : header) {
+      serializer.addHeading(s);
+    }
+    serializer.endHeader();
+
+    // Table contents.
+    serializer.startContent();
+    for (List<String> row : sortedRows) {
+      serializer.startRow();
+
+      // Print each cell in the row, padding missing trailing cells.
+      for (int i = 0; i < width; i++) {
+        if (i < row.size()) {
+          serializer.addCell(row.get(i));
+        } else {
+          serializer.addCell("");
+        }
+      }
+
+      serializer.endRow();
+    }
+    serializer.endContent();
+    serializer.endTable();
+  }
+
+
+
+  /**
+   * Appends a new row to the table.
+   */
+  public void startRow() {
+    rows.add(new ArrayList<String>());
+    height++;
+    column = 0;
+  }
+}
diff --git a/opends/src/server/org/opends/server/util/table/TablePrinter.java b/opends/src/server/org/opends/server/util/table/TablePrinter.java
new file mode 100644
index 0000000..28e42e6
--- /dev/null
+++ b/opends/src/server/org/opends/server/util/table/TablePrinter.java
@@ -0,0 +1,56 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.util.table;
+
+
+
+/**
+ * An interface for incrementally configuring a table serializer. Once
+ * configured, the table printer can be used to create a new
+ * {@link TableSerializer} instance using the {@link #getSerializer()}
+ * method.
+ */
+public abstract class TablePrinter {
+
+  /**
+   * Creates a new abstract table printer.
+   */
+  protected TablePrinter() {
+    // No implementation required.
+  }
+
+
+
+  /**
+   * Creates a new table serializer based on the configuration of this
+   * table printer.
+   *
+   * @return Returns a new table serializer based on the configuration
+   *         of this table printer.
+   */
+  protected abstract TableSerializer getSerializer();
+}
diff --git a/opends/src/server/org/opends/server/util/table/TableSerializer.java b/opends/src/server/org/opends/server/util/table/TableSerializer.java
new file mode 100644
index 0000000..be6610b
--- /dev/null
+++ b/opends/src/server/org/opends/server/util/table/TableSerializer.java
@@ -0,0 +1,160 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.util.table;
+
+
+
+/**
+ * An interface for serializing tables.
+ * <p>
+ * The default implementation for each method is to do nothing.
+ * Implementations must override methods as required.
+ */
+public abstract class TableSerializer {
+
+  /**
+   * Create a new table serializer.
+   */
+  protected TableSerializer() {
+    // No implementation required.
+  }
+
+
+
+  /**
+   * Prints a table cell.
+   *
+   * @param s
+   *          The cell contents.
+   */
+  public void addCell(String s) {
+    // Default implementation.
+  }
+
+
+
+  /**
+   * Defines a column in the table.
+   *
+   * @param width
+   *          The width of the column in characters.
+   */
+  public void addColumn(int width) {
+    // Default implementation.
+  }
+
+
+
+  /**
+   * Prints a column heading.
+   *
+   * @param s
+   *          The column heading.
+   */
+  public void addHeading(String s) {
+    // Default implementation.
+  }
+
+
+
+  /**
+   * Finish printing the table contents.
+   */
+  public void endContent() {
+    // Default implementation.
+  }
+
+
+
+  /**
+   * Finish printing the column headings.
+   */
+  public void endHeader() {
+    // Default implementation.
+  }
+
+
+
+  /**
+   * Finish printing the current row of the table.
+   */
+  public void endRow() {
+    // Default implementation.
+  }
+
+
+
+  /**
+   * Finish printing the table.
+   */
+  public void endTable() {
+    // Default implementation.
+  }
+
+
+
+  /**
+   * Prepare to start printing the table contents.
+   */
+  public void startContent() {
+    // Default implementation.
+  }
+
+
+
+  /**
+   * Prepare to start printing the column headings.
+   */
+  public void startHeader() {
+    // Default implementation.
+  }
+
+
+
+  /**
+   * Prepare to start printing a new row of the table.
+   */
+  public void startRow() {
+    // Default implementation.
+  }
+
+
+
+  /**
+   * Start a new table having the specified number of rows and
+   * columns.
+   *
+   * @param height
+   *          The number of rows in the table.
+   * @param width
+   *          The number of columns in the table.
+   */
+  public void startTable(int height, int width) {
+    // Default implementation.
+  }
+
+}
diff --git a/opends/src/server/org/opends/server/util/table/TextTablePrinter.java b/opends/src/server/org/opends/server/util/table/TextTablePrinter.java
new file mode 100644
index 0000000..1010c78
--- /dev/null
+++ b/opends/src/server/org/opends/server/util/table/TextTablePrinter.java
@@ -0,0 +1,531 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.util.table;
+
+
+
+import static org.opends.server.util.ServerConstants.*;
+
+import java.io.BufferedWriter;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+
+
+/**
+ * An interface for creating a text based table. Tables have
+ * configurable column widths, padding, and column separators.
+ */
+public final class TextTablePrinter extends TablePrinter {
+
+  /**
+   * Table serializer implementation.
+   */
+  private static final class Serializer extends TableSerializer {
+
+    // The current column being output.
+    private int column = 0;
+
+    // The string which should be used to separate one column
+    // from the next (not including padding).
+    private final String columnSeparator;
+
+    // The real column widths taking into account size constraints but
+    // not including padding or separators.
+    private final List<Integer> columnWidths = new ArrayList<Integer>();
+
+    // The cells in the current row.
+    private final List<String> currentRow = new ArrayList<String>();
+
+    // Indicates whether or not the headings should be output.
+    private final boolean displayHeadings;
+
+    // Table indicating whether or not a column is fixed width.
+    private final Map<Integer, Integer> fixedColumns;
+
+    // The character which should be used to separate the table
+    // heading row from the rows beneath.
+    private final char headingSeparator;
+
+    // The padding which will be used to separate a cell's
+    // contents from its adjacent column separators.
+    private final int padding;
+
+    // Width of the table in columns.
+    private int totalColumns = 0;
+
+    // Total permitted width for the table which expandable columns
+    // can use up.
+    private final int totalWidth;
+
+    // The output writer.
+    private final PrintWriter writer;
+
+
+
+    // Private constructor.
+    private Serializer(PrintWriter writer, String columnSeparator,
+        Map<Integer, Integer> fixedColumns, boolean displayHeadings,
+        char headingSeparator, int padding, int totalWidth) {
+      this.writer = writer;
+      this.columnSeparator = columnSeparator;
+      this.fixedColumns = new HashMap<Integer, Integer>(fixedColumns);
+      this.displayHeadings = displayHeadings;
+      this.headingSeparator = headingSeparator;
+      this.padding = padding;
+      this.totalWidth = totalWidth;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void addCell(String s) {
+      currentRow.add(s);
+      column++;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void addColumn(int width) {
+      columnWidths.add(width);
+      totalColumns++;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void addHeading(String s) {
+      if (displayHeadings) {
+        addCell(s);
+      }
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void endHeader() {
+      if (displayHeadings) {
+        endRow();
+
+        // Print the header separator.
+        StringBuilder builder = new StringBuilder();
+        for (int i = 0; i < totalColumns; i++) {
+          int width = columnWidths.get(i);
+          if (totalColumns > 1) {
+            if (i == 0 || i == (totalColumns - 1)) {
+              // Only one lot of padding for first and last columns.
+              width += padding;
+            } else {
+              width += padding * 2;
+            }
+          }
+
+          for (int j = 0; j < width; j++) {
+            builder.append(headingSeparator);
+          }
+
+          if (i < (totalColumns - 1)) {
+            builder.append(columnSeparator);
+          }
+        }
+        writer.println(builder.toString());
+      }
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void endRow() {
+      boolean isRemainingText;
+      do {
+        isRemainingText = false;
+        for (int i = 0; i < currentRow.size(); i++) {
+          int width = columnWidths.get(i);
+          String contents = currentRow.get(i);
+
+          // Determine what parts of contents can be displayed on this
+          // line.
+          String head;
+          String tail = null;
+
+          if (contents == null) {
+            // This cell has been displayed fully.
+            head = "";
+          } else if (contents.length() > width) {
+            // We're going to have to split the cell on next word
+            // boundary.
+            int endIndex = contents.lastIndexOf(' ', width);
+            if (endIndex == -1) {
+              // Problem - we have a word which is too big to fit in
+              // the
+              // cell.
+              head = contents.substring(0, width);
+              tail = contents.substring(width);
+            } else {
+              head = contents.substring(0, endIndex);
+              tail = contents.substring(endIndex + 1);
+            }
+          } else {
+            // The contents fits ok.
+            head = contents;
+          }
+
+          // Display this line.
+          StringBuilder builder = new StringBuilder();
+          if (i > 0) {
+            // Add right padding for previous cell.
+            for (int j = 0; j < padding; j++) {
+              builder.append(' ');
+            }
+
+            // Add separator.
+            builder.append(columnSeparator);
+
+            // Add left padding for this cell.
+            for (int j = 0; j < padding; j++) {
+              builder.append(' ');
+            }
+          }
+
+          // Add cell contents.
+          builder.append(head);
+
+          // Now pad with extra space to make up the width.
+          for (int j = head.length(); j < width; j++) {
+            builder.append(' ');
+          }
+
+          writer.print(builder.toString());
+
+          // Update the row contents.
+          currentRow.set(i, tail);
+          if (tail != null) {
+            isRemainingText = true;
+          }
+        }
+        writer.println();
+      } while (isRemainingText);
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void endTable() {
+      writer.flush();
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void startHeader() {
+      determineColumnWidths();
+
+      column = 0;
+      currentRow.clear();
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void startRow() {
+      column = 0;
+      currentRow.clear();
+    }
+
+
+
+    // We need to calculate the effective width of each column.
+    private void determineColumnWidths() {
+      // First calculate the minimum width so that we know how much
+      // expandable columns can expand.
+      int minWidth = 0;
+      int expandableColumnSize = 0;
+
+      for (int i = 0; i < totalColumns; i++) {
+        int actualSize = columnWidths.get(i);
+
+        if (fixedColumns.containsKey(i)) {
+          int requestedSize = fixedColumns.get(i);
+
+          if (requestedSize == 0) {
+            expandableColumnSize += actualSize;
+          } else {
+            columnWidths.set(i, requestedSize);
+            minWidth += requestedSize;
+          }
+        } else {
+          minWidth += actualSize;
+        }
+
+        // Must also include padding and separators.
+        if (i > 0) {
+          minWidth += padding * 2 + columnSeparator.length();
+        }
+      }
+
+      if (minWidth > totalWidth) {
+        // The table is too big: leave expandable columns at their
+        // requested width, as there's not much else that can be done.
+      } else {
+        int available = totalWidth - minWidth;
+
+        if (expandableColumnSize > available) {
+          // Only modify column sizes if necessary.
+          for (int i = 0; i < totalColumns; i++) {
+            int actualSize = columnWidths.get(i);
+
+            if (fixedColumns.containsKey(i)) {
+              int requestedSize = fixedColumns.get(i);
+              if (requestedSize == 0) {
+                // Calculate size based on requested actual size as a
+                // proportion of the total.
+                requestedSize =
+                  ((actualSize * available) / expandableColumnSize);
+                columnWidths.set(i, requestedSize);
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * The default string which should be used to separate one column
+   * from the next (not including padding).
+   */
+  private static final String DEFAULT_COLUMN_SEPARATOR = "";
+
+  /**
+   * The default character which should be used to separate the table
+   * heading row from the rows beneath.
+   */
+  private static final char DEFAULT_HEADING_SEPARATOR = '-';
+
+  /**
+   * The default padding which will be used to separate a cell's
+   * contents from its adjacent column separators.
+   */
+  private static final int DEFAULT_PADDING = 1;
+
+  // The string which should be used to separate one column
+  // from the next (not including padding).
+  private String columnSeparator = DEFAULT_COLUMN_SEPARATOR;
+
+  // Indicates whether or not the headings should be output.
+  private boolean displayHeadings = true;
+
+  // Table indicating whether or not a column is fixed width.
+  private final Map<Integer, Integer> fixedColumns =
+    new HashMap<Integer, Integer>();
+
+  // The character which should be used to separate the table
+  // heading row from the rows beneath.
+  private char headingSeparator = DEFAULT_HEADING_SEPARATOR;
+
+  // The padding which will be used to separate a cell's
+  // contents from its adjacent column separators.
+  private int padding = DEFAULT_PADDING;
+
+  // Total permitted width for the table which expandable columns
+  // can use up.
+  private int totalWidth = MAX_LINE_WIDTH;
+
+  // The output destination.
+  private PrintWriter writer = null;
+
+
+
+  /**
+   * Creates a new text table printer for the specified output stream.
+   * The text table printer will have the following initial settings:
+   * <ul>
+   * <li>headings will be displayed
+   * <li>no separators between columns
+   * <li>columns are padded by one character
+   * </ul>
+   *
+   * @param stream
+   *          The stream to output tables to.
+   */
+  public TextTablePrinter(OutputStream stream) {
+    this(new BufferedWriter(new OutputStreamWriter(stream)));
+  }
+
+
+
+  /**
+   * Creates a new text table printer for the specified writer. The
+   * text table printer will have the following initial settings:
+   * <ul>
+   * <li>headings will be displayed
+   * <li>no separators between columns
+   * <li>columns are padded by one character
+   * </ul>
+   *
+   * @param writer
+   *          The writer to output tables to.
+   */
+  public TextTablePrinter(Writer writer) {
+    this.writer = new PrintWriter(writer);
+  }
+
+
+
+  /**
+   * Sets the column separator which should be used to separate one
+   * column from the next (not including padding).
+   *
+   * @param columnSeparator
+   *          The column separator.
+   */
+  public final void setColumnSeparator(String columnSeparator) {
+    this.columnSeparator = columnSeparator;
+  }
+
+
+
+  /**
+   * Set the maximum width for a column. If a cell is too big to fit
+   * in its column then it will be wrapped.
+   *
+   * @param column
+   *          The column to make fixed width (0 is the first column).
+   * @param width
+   *          The width of the column (this should not include column
+   *          separators or padding), or <code>0</code> to indicate
+   *          that this column should be expandable.
+   * @throws IllegalArgumentException
+   *           If column is less than 0 .
+   */
+  public final void setColumnWidth(int column, int width)
+      throws IllegalArgumentException {
+    if (column < 0) {
+      throw new IllegalArgumentException("Negative column " + column);
+    }
+
+    if (width < 0) {
+      throw new IllegalArgumentException("Negative width " + width);
+    }
+
+    fixedColumns.put(column, width);
+  }
+
+
+
+  /**
+   * Specify whether the column headings should be displayed or not.
+   *
+   * @param displayHeadings
+   *          <code>true</code> if column headings should be
+   *          displayed.
+   */
+  public final void setDisplayHeadings(boolean displayHeadings) {
+    this.displayHeadings = displayHeadings;
+  }
+
+
+
+  /**
+   * Sets the heading separator which should be used to separate the
+   * table heading row from the rows beneath.
+   *
+   * @param headingSeparator
+   *          The heading separator.
+   */
+  public final void setHeadingSeparator(char headingSeparator) {
+    this.headingSeparator = headingSeparator;
+  }
+
+
+
+  /**
+   * Sets the padding which will be used to separate a cell's contents
+   * from its adjacent column separators.
+   *
+   * @param padding
+   *          The padding.
+   */
+  public final void setPadding(int padding) {
+    this.padding = padding;
+  }
+
+
+
+  /**
+   * Sets the total permitted width for the table which expandable
+   * columns can use up.
+   *
+   * @param totalWidth
+   *          The total width.
+   */
+  public final void setTotalWidth(int totalWidth) {
+    this.totalWidth = totalWidth;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  protected TableSerializer getSerializer() {
+    return new Serializer(writer, columnSeparator, fixedColumns,
+        displayHeadings, headingSeparator, padding, totalWidth);
+  }
+}
diff --git a/opends/src/server/org/opends/server/util/table/package-info.java b/opends/src/server/org/opends/server/util/table/package-info.java
new file mode 100644
index 0000000..dc86323
--- /dev/null
+++ b/opends/src/server/org/opends/server/util/table/package-info.java
@@ -0,0 +1,80 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
+ */
+
+/**
+ * Provides support for construction and display of tables in text based
+ * applications. Applications construct tables using the {@link TableBuilder}
+ * class and display them using on of the {@link TablePrinter}
+ * implementations. At the moment two types of table output are supported:
+ * <ul>
+ * <li>{@link CSVTablePrinter} - displays a table in comma-separated
+ *     value format
+ * <li>{@link TabSeparatedTablePrinter} - displays a table in tab separated
+ *     format
+ * <li>{@link TextTablePrinter} - displays a table in a human-readable
+ *     format. Using this implementation it is possible to configure
+ *     constraints on column widths. The implementation will take care of
+ *     wrapping table cells where required.
+ * </ul>
+ * The following code illustrates the construction of a text-based table:
+ * <pre>
+ * TableBuilder builder = new TableBuilder();
+ *
+ * builder.appendHeading("Name");
+ * builder.appendHeading("Age");
+ * builder.addSortKey(0);
+ *
+ * builder.startRow();
+ * builder.appendCell("Bob");
+ * builder.appendCell(11);
+ *
+ * builder.startRow();
+ * builder.appendCell("Alice");
+ * builder.appendCell(22);
+ *
+ * builder.startRow();
+ * builder.appendCell("Charlie");
+ * builder.appendCell(33);
+ *
+ * TextTablePrinter printer = new TextTablePrinter(System.out);
+ * printer.setColumnSeparator(":");
+ * builder.print(printer);
+ * </pre>
+ *
+ * Which will display the following table:
+ * <pre>
+ * Name    : Age
+ * --------:----
+ * Alice   : 22
+ * Bob     : 11
+ * Charlie : 33
+ * </pre>
+ */
+package org.opends.server.util.table;
+
+
+

--
Gitblit v1.10.0