/**
* VMware Continuent Tungsten Replicator
* Copyright (C) 2015 VMware, Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Initial developer(s): Robert Hodges
* Contributor(s): Edward Archibald
*/
package com.continuent.tungsten.common.utils;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
import java.util.Vector;
import com.continuent.tungsten.common.config.TungstenProperties;
/**
* Implements a client result formatter. Default result formatting is to call
* Object.toString(). Tabular values are formatted appropriately.
*
* @author <a href="mailto:robert.hodges@continuent.com">Robert Hodges</a>
* @version 1.0
*/
public class ResultFormatter
{
private static final String LINE_SEPARATOR = System.getProperty("line.separator");
private static final String keyHeader = "Key";
private static final String valueHeader = "Value";
private static final int keyMargin = 2;
private static boolean printHeader = true;
public static final String NEWLINE = "\n";
private static int NOWRAP = -1;
private static String indent = "";
public static final String DEFAULT_INDENT = " ";
public static final String ROW_SEPARATOR = "-";
public static final String ROW_BEGIN_END = "|";
public static final int DEFAULT_WIDTH = 75;
private static final int FIELD_SEPARATOR_WIDTH = 5;
private static final String FIELD_SEPARATOR = " ";
protected final Object result;
/**
* Creates a new formatter for a particular object.
*/
public ResultFormatter(Object result)
{
this.result = result;
printHeader = true;
}
public ResultFormatter(Object result, boolean printHeader,
String indentToUse)
{
this.result = result;
printHeader = false;
indent = indentToUse;
}
/**
* Returns a formatted value.
*/
public String format()
{
if (result == null)
{
return "(null)";
}
else if (result instanceof Map)
{
return format((Map<?, ?>) result);
}
else if (result instanceof TungstenProperties)
{
return format(((TungstenProperties) result).map());
}
else
{
return format(result);
}
}
public String format(int wrapColumn)
{
if (result == null)
{
return "(null)";
}
else if (result instanceof Map)
{
return format((Map<?, ?>) result, wrapColumn);
}
else if (result instanceof TungstenProperties)
{
return format(((TungstenProperties) result).map(), wrapColumn);
}
else
{
return format(result);
}
}
/**
* Default object formatter using Object.toString().
*/
protected String format(Object o)
{
return o.toString();
}
/**
* Object formatter for Map instance. This sorts keys and then formats the
* key value pairs into a nice tabular representation.
*/
protected String format(Map<?, ?> props, int maxLength)
{
// Create writer instances to print formatted text.
StringWriter writer = new StringWriter();
PrintWriter printer = new PrintWriter(writer, true);
// Organize the properties into a TreeMap for sorting and compute
// dimensions along the way.
Iterator<?> keyIterator = props.keySet().iterator();
int keyLength = keyHeader.length();
int valueLength = valueHeader.length();
TreeMap<String, String[]> sortedProperties = new TreeMap<String, String[]>();
while (keyIterator.hasNext())
{
// Fetch values.
String key = keyIterator.next().toString();
Object rawValue = props.get(key);
String value = (rawValue == null ? "null" : rawValue.toString());
// Check character widths.
if (key.length() > keyLength)
keyLength = key.length();
if (value.length() > valueLength)
valueLength = value.length();
String valueArray[] = wrap(value, maxLength, 0, false);
// Store values again.
sortedProperties.put(key, valueArray);
}
keyLength += keyMargin;
if (maxLength != NOWRAP)
valueLength = maxLength;
// Compute a format string.
String format = indent + "%-" + keyLength + "." + keyLength + "s %-"
+ valueLength + "." + valueLength + "s" + LINE_SEPARATOR;
String formatHeader = indent + "%-" + keyLength + "." + keyLength
+ "s %-" + valueHeader.length() + "." + valueHeader.length()
+ "s" + LINE_SEPARATOR;
// Write the output.
if (printHeader)
{
printer.printf(formatHeader, keyHeader, valueHeader);
printer.printf(formatHeader, "---", "-----");
}
for (String key : sortedProperties.keySet())
{
String values[] = sortedProperties.get(key);
printer.printf(format, key, values[0]);
for (int i = 1; i < values.length; i++)
{
printer.printf(format, "", values[i]);
}
}
printer.close();
return writer.toString();
}
protected String format(Map<?, ?> props)
{
return format(props, NOWRAP);
}
/*
* Utility method that wraps a string at an arbitrary length
*/
public static String[] wrap(String value, int maxLength)
{
return wrap(value, maxLength, 0, false);
}
/*
* Utility method that wraps a string at an arbitrary length
*/
public static String[] wrap2(String value, int maxLength,
boolean ignoreColon)
{
if (value.length() < maxLength || maxLength == NOWRAP)
return new String[]{value};
// Check to see if we are wrapping an pre-formatted
// 'table' and accomodate that.
int labelMarkerOffset = value.indexOf(":");
int valueOffset = 0;
if (labelMarkerOffset != -1 && !ignoreColon)
valueOffset = labelMarkerOffset + FIELD_SEPARATOR_WIDTH;
int currentLength = value.length();
String nextSegment = value;
Vector<String> results = new Vector<String>();
int segmentCount = 0;
while ((currentLength + valueOffset) > maxLength)
{
results.add(nextSegment.substring(0, maxLength));
segmentCount++;
if (nextSegment.length() > maxLength)
{
nextSegment = nextSegment.substring(maxLength);
currentLength = nextSegment.length();
if (segmentCount > 0 && nextSegment.length() > 0
&& labelMarkerOffset != -1)
{
nextSegment = padBefore(nextSegment, valueOffset, " ");
}
}
}
if (nextSegment.length() > 0)
{
results.add(nextSegment);
}
return results.toArray(new String[results.size()]);
}
public static String[] wrap(String value, int maxLength, boolean ignoreColon)
{
return wrap(value, maxLength, 0, ignoreColon);
}
public static String[] wrap(String value, int maxLength, int hangingIndent,
boolean ignoreColon)
{
if (value.length() < maxLength || maxLength == NOWRAP)
return new String[]{value};
Vector<String> results = new Vector<String>();
// Check to see if we are wrapping an pre-formatted
// 'table' and accomodate that.
int labelMarkerOffset = value.indexOf(":");
int valueOffset = 0;
String label = "";
String nextSegment = "";
if (labelMarkerOffset != -1 && !ignoreColon)
{
valueOffset = labelMarkerOffset + FIELD_SEPARATOR_WIDTH;
label = value.substring(0, labelMarkerOffset + 1);
value = value.substring(labelMarkerOffset + 2).trim();
if (value.length() + valueOffset <= maxLength)
{
results.add(label + FIELD_SEPARATOR + value);
return results.toArray(new String[results.size()]);
}
nextSegment = label + FIELD_SEPARATOR;
}
int indentToUse = 0;
if (!ignoreColon && valueOffset > 0)
indentToUse = valueOffset;
else if (hangingIndent > 0)
indentToUse = hangingIndent;
String[] elements = value.split("\\s");
// If elements, when split by spaces, are still too long, split them
// up by length only.
Vector<String> convertedElements = new Vector<String>();
String elementToUse = "";
for (String element : elements)
{
elementToUse = element;
if (elementToUse.length() + valueOffset < maxLength)
{
convertedElements.add(elementToUse);
elementToUse = "";
}
else
{
while (elementToUse.length() + valueOffset >= maxLength)
{
convertedElements.add(elementToUse.substring(0, maxLength
- valueOffset - 1));
elementToUse = elementToUse.substring(maxLength
- valueOffset - 1);
}
}
}
if (elementToUse.length() > 0)
convertedElements.add(elementToUse);
elements = convertedElements.toArray(new String[convertedElements
.size()]);
int segmentElementCount = 0;
int segmentCount = 0;
for (String element : elements)
{
elementToUse = element.trim();
int totalSegmentLength = nextSegment.length()
+ elementToUse.length();
if (segmentCount > 0)
totalSegmentLength += valueOffset;
if (totalSegmentLength > maxLength)
{
if (indentToUse > 0 && segmentCount > 0)
{
nextSegment = padBefore(nextSegment, indentToUse, " ");
}
results.add(nextSegment);
segmentCount++;
nextSegment = "";
}
if (segmentElementCount++ > 0 && nextSegment.length() > 0)
nextSegment = nextSegment + " ";
nextSegment = nextSegment + elementToUse;
}
if (nextSegment.length() > 0)
{
if (indentToUse > 0 && results.size() > 1)
{
nextSegment = padBefore(nextSegment, indentToUse, " ");
}
results.add(nextSegment);
}
return results.toArray(new String[results.size()]);
}
public static String formatProperties(String entityName,
TungstenProperties properties, boolean printHeader)
{
String[] columnNames = properties.map().keySet()
.toArray((new String[properties.map().size()]));
Vector<String[]> results = new Vector<String[]>();
results.add(properties.map().values()
.toArray(new String[properties.map().size()]));
return formatResults(entityName, columnNames, results, 60, false, true);
}
public static String formatResults(String entityName, String[] columnNames,
Vector<String[]> results, int entryWidth)
{
return formatResults(entityName, columnNames, results, entryWidth,
false, true);
}
public static String formatResults(String entityName, String[] columnNames,
Vector<String[]> results, int entryWidth, boolean ignoreColon)
{
return formatResults(entityName, columnNames, results, entryWidth,
ignoreColon, true);
}
public static String formatResults(String entityName, String[] columnNames,
Vector<String[]> results, int entryWidth, boolean ignoreColon,
boolean useDelimiters)
{
StringBuilder builder = new StringBuilder();
if (results == null)
{
return builder.toString();
}
int columnCount = columnNames != null ? columnNames.length : 1;
String separator = makeSeparator(entryWidth, columnCount, useDelimiters);
if (useDelimiters)
builder.append(separator).append(NEWLINE);
String row = makeRow(new String[]{entityName}, entryWidth, 0,
ignoreColon, useDelimiters);
builder.append(row);
if (useDelimiters)
builder.append(separator).append(NEWLINE);
if (columnNames != null)
{
row = makeRow(columnNames, entryWidth, 0, ignoreColon,
useDelimiters);
builder.append(row).append(NEWLINE);
if (useDelimiters)
builder.append(separator).append(NEWLINE);
}
int rowCount = results.size();
for (int i = 0; i < rowCount; i++)
{
row = makeRow(results.get(i), entryWidth, 0, ignoreColon,
useDelimiters);
builder.append(row);
}
if (useDelimiters)
builder.append(separator).append(NEWLINE);
return builder.toString();
}
public static String formatResult(String entityName, String result)
{
return formatResult(entityName, result, false, true);
}
public static String formatResult(String entityName, String result,
boolean ignoreColon)
{
return formatResult(entityName, result, ignoreColon, true);
}
public static String formatResult(String entityName, String result,
boolean ignoreColon, boolean useDelimiters)
{
StringBuilder builder = new StringBuilder();
Vector<String[]> results = new Vector<String[]>();
results.add(new String[]{result});
builder.append(
ResultFormatter.formatResults(entityName, null, results,
DEFAULT_WIDTH, ignoreColon, useDelimiters))
.append("\n");
return builder.toString();
}
public static String makeRow(String[] entries, int entryWidth)
{
return makeRow(entries, entryWidth, 0, false, true);
}
public static String makeRow(String[] entries, int entryWidth,
boolean ignoreColon)
{
return makeRow(entries, entryWidth, 0, ignoreColon, true);
}
public static String makeRow(String[] entries, int entryWidth,
int indentToUse, boolean ignoreColon)
{
return makeRow(entries, entryWidth, indentToUse, ignoreColon, true);
}
public static String makeRow(String[] entries, int entryWidth,
int indentToUse, boolean ignoreColon, boolean useDelimiters)
{
String row = "";
String delimiter = "|";
if (!useDelimiters)
delimiter = " ";
for (int i = 0; i < entries.length; i++)
{
String[] elements = entries[i].split("\\n");
for (String element : elements)
{
String[] subElements = ResultFormatter.wrap(
element.replace("\t", " "), entryWidth - 2,
indentToUse, ignoreColon);
for (String subElement : subElements)
{
row = row + delimiter;
row = row + padAfter(subElement, entryWidth, " ");
row = row + " " + delimiter + "\n";
}
}
}
return (row);
}
public static String makeDataRow(String[] entries, int entryWidth,
int indentToUse, boolean ignoreColon, boolean useDelimiters)
{
String row = "";
String delimiter = "|";
if (!useDelimiters)
delimiter = " ";
for (int i = 0; i < entries.length; i++)
{
row = row + delimiter;
row = row + padAfter(entries[i], entryWidth, " ");
row = row + " ";// + delimiter + " ";
}
row = row + delimiter;
return (row);
}
public static String makeSeparator(int entryWidth, int columnCount)
{
return makeSeparator(entryWidth, columnCount, true);
}
public static String makeSeparator(int entryWidth, int columnCount,
boolean useDelimiters)
{
String entry = padAfter("", entryWidth + 1, "-");
String delimiter = "+";
String separator = delimiter;
if (!useDelimiters)
{
separator = "";
delimiter = "";
}
for (int i = 0; i < columnCount; i++)
{
separator = separator + entry + delimiter;
}
return (separator);
}
private static String padAfter(String orig, int size, String padChar)
{
if (orig == null)
{
orig = "<null>";
}
// Use StringBuffer, not just repeated String concatenation
// to avoid creating too many temporary Strings.
StringBuffer buffer = new StringBuffer("");
buffer.append(orig);
int extraChars = size - orig.length();
for (int i = 0; i < extraChars; i++)
{
buffer.append(padChar);
}
return (buffer.toString());
}
private static String padBefore(String orig, int count, String padChar)
{
if (orig == null)
{
orig = "<null>";
}
// Use StringBuffer, not just repeated String concatenation
// to avoid creating too many temporary Strings.
StringBuffer buffer = new StringBuffer("");
for (int i = 0; i < count; i++)
{
buffer.append(padChar);
}
buffer.append(orig);
return (buffer.toString());
}
public static String formatMap(Map<String, ?> map)
{
StringBuilder builder = new StringBuilder();
for (String key : map.keySet())
{
Object value = map.get(key);
builder.append(String.format("%30s: %s\n", key, value));
}
return builder.toString();
}
}