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