/*
* RHQ Management Platform
* Copyright (C) 2005-2008 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package org.rhq.bindings.output;
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.rhq.bindings.util.LazyLoadScenario;
import org.rhq.bindings.util.ShortOutput;
import org.rhq.bindings.util.SummaryFilter;
import org.rhq.core.domain.configuration.Configuration;
import org.rhq.core.domain.configuration.Property;
import org.rhq.core.domain.configuration.PropertyList;
import org.rhq.core.domain.configuration.PropertyMap;
import org.rhq.core.domain.configuration.PropertySimple;
import org.rhq.core.domain.measurement.AvailabilityType;
import org.rhq.core.domain.measurement.ResourceAvailability;
import org.rhq.core.domain.resource.ResourceType;
import au.com.bytecode.opencsv.CSVWriter;
/**
* @author Greg Hinkle
*/
public class TabularWriter {
private static final String CSV = "csv";
String[] headers;
int[] maxColumnLength;
boolean[] noShrinkColumns;
int width = 160;
PrintWriter out;
private String format = "raw";
private CSVWriter csvWriter;
private SummaryFilter summaryFilter = new SummaryFilter();
boolean exportMode;
boolean hideRowCount;
static Set<String> IGNORED_PROPS = new HashSet<String>();
static {
IGNORED_PROPS.add("mtime");
IGNORED_PROPS.add("ctime");
IGNORED_PROPS.add("itime");
IGNORED_PROPS.add("uuid");
IGNORED_PROPS.add("parentResource");
}
static Set<Class> SIMPLE_TYPES = new HashSet<Class>();
static {
SIMPLE_TYPES.add(Byte.class);
SIMPLE_TYPES.add(Byte.TYPE);
SIMPLE_TYPES.add(Character.class);
SIMPLE_TYPES.add(Character.TYPE);
SIMPLE_TYPES.add(Short.class);
SIMPLE_TYPES.add(Short.TYPE);
SIMPLE_TYPES.add(Integer.class);
SIMPLE_TYPES.add(Integer.TYPE);
SIMPLE_TYPES.add(Long.class);
SIMPLE_TYPES.add(Long.TYPE);
SIMPLE_TYPES.add(Float.class);
SIMPLE_TYPES.add(Float.TYPE);
SIMPLE_TYPES.add(Double.class);
SIMPLE_TYPES.add(Double.TYPE);
SIMPLE_TYPES.add(Boolean.class);
SIMPLE_TYPES.add(Boolean.TYPE);
SIMPLE_TYPES.add(String.class);
}
public TabularWriter(PrintWriter out, String... headers) {
this.headers = headers;
this.out = out;
}
public TabularWriter(PrintWriter out) {
this.out = out;
}
public TabularWriter(PrintWriter out, String format) {
this.out = out;
this.format = format;
if (CSV.equals(format)) {
csvWriter = new CSVWriter(out);
}
}
public void setHideRowCount(boolean hideRowCount) {
this.hideRowCount = hideRowCount;
}
public void printObject(Object object) {
print(object);
}
public void print(Object object) {
if (object == null) {
this.out.println("null");
return;
}
if (object instanceof Map) {
printMap((Map) object);
return;
}
if (object instanceof Collection) {
printCollection((Collection) object);
return;
}
if (object instanceof Configuration) {
printConfiguration((Configuration) object);
return;
}
if (object instanceof String[][]) {
printMultidimensionalStringArray((String[][])object);
return;
}
if (object != null && object.getClass().isArray()) {
if (!object.getClass().getComponentType().isPrimitive()) {
printArray((Object[]) object);
} else {
Class<?> oClass = object.getClass();
// note: we assume single-dimension arrays!
out.println("Array of " + (oClass.getComponentType().getName()));
if (oClass == byte[].class) {
for (byte i : (byte[]) object) {
this.out.println(i);
}
} else if (oClass == short[].class) {
for (short i : (short[]) object) {
this.out.println(i);
}
} else if (oClass == int[].class) {
for (int i : (int[]) object) {
this.out.println(i);
}
} else if (oClass == long[].class) {
for (long i : (long[]) object) {
this.out.println(i);
}
} else if (oClass == char[].class) {
for (char i : (char[]) object) {
this.out.println(i);
}
} else if (oClass == float[].class) {
for (float i : (float[]) object) {
this.out.println(i);
}
} else if (oClass == double[].class) {
for (double i : (double[]) object) {
this.out.println(i);
}
} else if (oClass == boolean[].class) {
for (boolean i : (boolean[]) object) {
this.out.println(i);
}
} else {
this.out.println("*Printing of this data type is not supported*");
}
}
return;
}
try {
if (SIMPLE_TYPES.contains(object.getClass())) {
this.out.println(String.valueOf(object));
return;
}
out.println(object.getClass().getSimpleName() + ":");
Map<String, PropertyInfo> properties = new LinkedHashMap<String, PropertyInfo>();
int maxLength = 0;
for (PropertyDescriptor pd : summaryFilter.getPropertyDescriptors(object, exportMode)) {
Method m = pd.getReadMethod();
Object val = null;
if (m != null) {
val = invoke(object, m);
}
if (val == null) {
maxLength = Math.max(maxLength, pd.getName().length());
properties.put(pd.getName(), new PropertyInfo(pd.getName(), null));
} else {
try {
String str = shortVersion(val);
maxLength = Math.max(maxLength, pd.getName().length());
properties.put(pd.getName(), new PropertyInfo(str, pd.getPropertyType()));
} catch (Exception e) {
}
}
}
for (String key : properties.keySet()) {
printProperty(key, properties.get(key), maxLength);
}
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IntrospectionException e) {
e.printStackTrace();
}
}
private static class PropertyInfo {
String value;
Class<?> type;
PropertyInfo(String propertyValue, Class<?> propertyType) {
value = propertyValue;
type = propertyType;
}
}
private void printProperty(String name, PropertyInfo propertyInfo, int maxLength) {
out.print("\t");
printPreSpaced(out, name, maxLength);
out.print(": ");
if (propertyInfo.type == null) {
out.println("");
} else if (exportMode || !String.class.equals(propertyInfo.type)) {
out.println(propertyInfo.value);
} else {
out.println(abbreviate(propertyInfo.value, width - 12 - maxLength));
}
}
// This method is taken verbatim from the Commons Lang project in the org.apache.commons.lang.StringUtils class
// TODO Should this method go into one our StringUtil classes?
private String abbreviate(String string, int maxWidth) {
int offset = 0;
if (string == null) {
return null;
}
if (maxWidth < 4) {
throw new IllegalArgumentException("Minimum abbreviation width is 4");
}
if (string.length() <= maxWidth) {
return string;
}
if (offset > string.length()) {
offset = string.length();
}
if ((string.length() - offset) < (maxWidth - 3)) {
offset = string.length() - (maxWidth - 3);
}
if (offset <= 4) {
return string.substring(0, maxWidth - 3) + "...";
}
if (maxWidth < 7) {
throw new IllegalArgumentException("Minimum abbreviation width with offset is 7");
}
if ((offset + (maxWidth - 3)) < string.length()) {
return "..." + abbreviate(string.substring(offset), maxWidth - 3);
}
return "..." + string.substring(string.length() - (maxWidth - 3));
}
/**
* @deprecated use {@link #printMap(Map)}
* @param map
*/
@Deprecated
public void print(Map map) {
print((Object) map);
}
public void printMap(Map map) {
String[][] data = new String[map.size()][];
int i = 0;
for (Object key : map.keySet()) {
Object value = map.get(key);
data[i] = new String[2];
data[i][0] = shortVersion(key);
data[i][1] = shortVersion(value);
i++;
}
this.headers = new String[] { "Key", "Value" };
printMultidimensionalStringArray(data);
}
/**
* @deprecated use {@link #printCollection(Collection)}
* @param map
*/
@Deprecated
public void print(Collection list) {
print((Object) list);
}
public void printCollection(Collection list) {
// List of arbitrary objects
if (list == null || list.size() == 0) {
if (!hideRowCount) {
out.println("0 rows");
}
} else if (list.size() == 1 && !CSV.equals(format)) {
if (!hideRowCount) {
out.println("one row");
}
print(list.iterator().next());
} else {
String[][] data;
if (!allOneType(list)) {
printStrings(list);
} else {
Object firstObject = list.iterator().next();
try {
if (firstObject instanceof String) {
headers = new String[] { "Value" };
data = new String[list.size()][1];
int i = 0;
for (Object object : list) {
data[i++][0] = (String) object;
}
printMultidimensionalStringArray(data);
} else {
if (consistentMaps(list)) {
// results printed
} else {
int i = 0;
List<PropertyDescriptor> pdList = new ArrayList<PropertyDescriptor>();
for (PropertyDescriptor pd : summaryFilter.getPropertyDescriptors(firstObject, exportMode)) {
try {
boolean allNull = true;
for (Object row : list) {
Method m = pd.getReadMethod();
Object val = null;
if (m != null) {
val = invoke(row, pd.getReadMethod());
}
if ((val != null && !(val instanceof Collection))
|| ((val != null && (val instanceof Collection) && !((Collection) val)
.isEmpty())))
allNull = false;
}
if (!allNull && !IGNORED_PROPS.contains(pd.getName())) {
pdList.add(pd);
}
} catch (Exception e) {
e.printStackTrace();
}
}
if (pdList.isEmpty()) {
printStrings(list);
} else {
headers = new String[pdList.size()];
data = new String[list.size()][pdList.size()];
for (PropertyDescriptor pd : pdList) {
headers[i++] = pd.getName();
}
i = 0;
for (Object row : list) {
int j = 0;
for (PropertyDescriptor pd : pdList) {
Object val = "?";
val = invoke(row, pd.getReadMethod());
if (val == null) {
data[i][j++] = "";
} else {
data[i][j++] = shortVersion(val);
}
}
i++;
}
printMultidimensionalStringArray(data);
}
}
}
} catch (Exception e) {
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
} finally {
headers = null;
}
}
}
}
private boolean consistentMaps(Collection list) {
List<String> keys = null;
String[][] data = new String[list.size()][];
int i = 0;
for (Object row : list) {
if (!(row instanceof Map) && !(row instanceof PropertyMap)) {
return false;
}
if (keys == null) {
keys = new ArrayList<String>();
//insert check for PropertyMap as they are not instances of Map
if (row instanceof PropertyMap) {
//TODO: spinder 1/15/10. PropertyMap is arbitrarily complex. Only serialize simple props?
for (Object key : ((PropertyMap) row).getMap().keySet()) {
String headerKey = stringValueOf(key);
keys.add(headerKey);
}
} else {//else row is a Map
for (Object key : ((Map) row).keySet()) {
String headerKey = stringValueOf(key);
keys.add(headerKey);
}
}
//conditionally put order on the headers to mimic core gui listing style/order
//Ex. Pid Name Size User Time Kernel Time
if (keys.contains("pid")) {
String[] processAttribute = { "pid", "name", "size", "userTime", "kernelTime" };
List<String> newKeyOrder = new ArrayList<String>();
for (String attribute : processAttribute) {
newKeyOrder.add(attribute);
keys.remove(attribute);
}
//postpend remaining keys if any to the newHeader list
for (String key : keys) {
newKeyOrder.add(key);
}
keys = newKeyOrder;
}
}
data[i] = new String[keys.size()];
if (row instanceof PropertyMap) {
for (String key : keys) {
if (!keys.contains(stringValueOf(key))) {
return false;
}
data[i][keys.lastIndexOf(stringValueOf(key))] = shortVersion(((PropertyMap) row).get(String
.valueOf(key)));
}
} else {//else row is a Map
for (Object key : ((Map) row).keySet()) {
if (!keys.contains(stringValueOf(key))) {
return false;
}
data[i][keys.lastIndexOf(stringValueOf(key))] = shortVersion(((Map) row).get(key));
}
}
i++;
}
if (keys != null) {
headers = keys.toArray(new String[keys.size()]);
printMultidimensionalStringArray(data);
return true;
} else {
return false;
}
}
/**
* @deprecated use {@link #printConfiguration(Configuration)}
* @param map
*/
@Deprecated
public void print(Configuration config) {
print((Object) config);
}
public void printConfiguration(Configuration config) {
out.println("Configuration [" + config.getId() + "] - " + config.getNotes());
for (PropertySimple p : config.getSimpleProperties().values()) {
print(p, 1);
}
for (PropertyList p : config.getListProperties().values()) {
print(p, 1);
}
for (PropertyMap p : config.getMapProperties().values()) {
print(p, 1);
}
}
public void print(PropertySimple p, int depth) {
out.println(indent(depth) + p.getName() + " = " + p.getStringValue());
}
public void print(PropertyList p, int depth) {
out.println(indent(depth) + p.getName() + " [" + p.getList().size() + "] {");
if (p.getList().size() > 0 && p.getList().get(0) instanceof PropertyMap) {
consistentMaps(p.getList());
} else {
for (Property entry : p.getList()) {
if (entry instanceof PropertySimple) {
print((PropertySimple) entry, depth + 1);
} else if (entry instanceof PropertyMap) {
print((PropertyMap) entry, depth + 1);
}
}
}
out.println(indent(depth) + "}");
}
public void print(PropertyMap p, int depth) {
out.println(indent(depth) + p.getName() + " [" + p.getMap().size() + "] {");
for (String key : p.getMap().keySet()) {
Property entry = p.getMap().get(key);
if (entry instanceof PropertySimple) {
print((PropertySimple) entry, depth + 1);
} else if (entry instanceof PropertyMap) {
print((PropertyMap) entry, depth + 1);
}
}
out.println(indent(depth) + "}");
}
private String indent(int x) {
StringBuilder buf = new StringBuilder();
for (int i = 0; i < x; i++) {
buf.append(" ");
}
return buf.toString();
}
private void printStrings(Collection list) {
for (Object object : list) {
out.println(stringValueOf(object));
}
}
private boolean allOneType(Collection list) {
Class lastClass = null;
for (Object object : list) {
if (lastClass == null) {
lastClass = object.getClass();
} else if (!object.getClass().equals(lastClass)) {
return false;
}
}
return true;
}
/**
* @deprecated use {@link #printArray(Object[])}
* @param map
*/
@Deprecated
public void print(Object[] data) {
print((Object) data);
}
public void printArray(Object[] data) {
if (data == null || data.length == 0) {
if (!hideRowCount) {
out.println("0 rows");
}
return;
}
out.println("Array of " + (data.getClass().getComponentType().getName()));
printCollection(Arrays.asList(data));
}
private void resizeColumns(int[] actualColumnWidths, int maxColumnWidth, List<Integer> columns) {
int extraSpace = 0;
Iterator<Integer> iterator = columns.iterator();
while (iterator.hasNext()) {
int col = iterator.next();
if (actualColumnWidths[col] < maxColumnWidth) {
extraSpace += maxColumnWidth - actualColumnWidths[col];
iterator.remove();
}
}
if (extraSpace == 0) {
// There is no extra space with which to work so at this point we have to
// truncate any columns that still need more space
for (Integer col : columns) {
actualColumnWidths[col] = maxColumnWidth;
}
} else if (columns.size() == 0) {
// If the columns list is empty then that means that there is enough available
// space for each column so we are done.
return;
} else if (extraSpace > 0) {
// Since we have extra space, we will go ahead and recalculate the widths for
// those columns still needing space
int newMaxColumnWidth = (maxColumnWidth + extraSpace) / columns.size();
resizeColumns(actualColumnWidths, newMaxColumnWidth, columns);
}
}
/**
* @deprecated use {@link #printMultidimensionalStringArray(String[][])}
* @param data
*/
@Deprecated
public void print(String[][] data) {
print((Object) data);
}
public void printMultidimensionalStringArray(String[][] data) {
if (data == null || data.length == 0) {
if (!hideRowCount) {
out.println("0 rows");
}
return;
}
int numberOfColumns = data[0].length;
int maxColumnWidth = width / numberOfColumns;
int[] actualColumnWidths = new int[numberOfColumns];
for (String[] row : data) {
for (int col = 0; col < row.length; ++col) {
if (row[col] == null) {
row[col] = "";
}
actualColumnWidths[col] = Math.max(actualColumnWidths[col], row[col].length());
}
}
if (headers != null) {
for (int col = 0; col < headers.length; ++col) {
actualColumnWidths[col] = Math.max(actualColumnWidths[col], headers[col].length());
}
}
List<Integer> columns = new LinkedList();
for (int col = 0; col < actualColumnWidths.length; ++col) {
columns.add(col);
}
resizeColumns(actualColumnWidths, maxColumnWidth, columns);
if (headers != null) {
if (CSV.equals(format)) {
csvWriter.writeNext(headers);
} else {
for (int i = 0; i < actualColumnWidths.length; i++) {
int colSize = actualColumnWidths[i];
printSpaced(out, headers[i], colSize);
if (i < actualColumnWidths.length - 1) {
out.print(" ");
}
}
out.println("");
for (int i = 1; i < width; i++) {
out.print("-");
}
}
out.println("");
}
if (CSV.equals(format)) {
for (String[] row : data) {
csvWriter.writeNext(row);
}
} else {
for (String[] row : data) {
for (int i = 0; i < actualColumnWidths.length; i++) {
int colSize = actualColumnWidths[i];
printSpaced(out, row[i], colSize);
if (i < actualColumnWidths.length - 1) {
out.print(" ");
}
}
out.println("");
}
}
if (!hideRowCount) {
out.print(data.length + " rows");
out.println("");
}
}
private void printSpaced(PrintWriter out, String data, int length) {
int dataLength = data.length();
if (dataLength > length) {
out.print(data.substring(0, length));
} else {
out.print(data);
for (int i = dataLength; i < length; i++) {
out.print(" ");
}
}
}
private void printPreSpaced(PrintWriter out, String data, int length) {
int dataLength = data.length();
if (dataLength > length) {
out.print(data.substring(0, length));
} else {
for (int i = dataLength; i < length; i++) {
out.print(" ");
}
out.print(data);
}
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public boolean isExportMode() {
return exportMode;
}
public void setExportMode(boolean exportMode) {
this.exportMode = exportMode;
}
private static String shortVersion(Object object) {
if (object instanceof ShortOutput) {
return ((ShortOutput) object).getShortOutput();
} else if (object instanceof PropertySimple) {
return ((PropertySimple) object).getStringValue();
} else if (object instanceof ResourceType) {
return ((ResourceType) object).getName();
} else if (object instanceof ResourceAvailability) {
AvailabilityType availType = ((ResourceAvailability) object).getAvailabilityType();
return (availType == null) ? "?" : availType.getName();
} else if (object != null && object.getClass().isArray()) {
return Arrays.toString((Object[]) object);
} else {
return stringValueOf(object);
}
}
private static Object invoke(Object o, Method m) throws IllegalAccessException, InvocationTargetException {
boolean access = m.isAccessible();
m.setAccessible(true);
try {
LazyLoadScenario.setShouldLoad(false);
return m.invoke(o);
} catch (Exception e) {
// That's fine
return null;
} finally {
LazyLoadScenario.setShouldLoad(true);
m.setAccessible(access);
}
}
private static String stringValueOf(Object object) {
try {
LazyLoadScenario.setShouldLoad(false);
return String.valueOf(object);
} catch (Exception e) {
return "null";
} finally {
LazyLoadScenario.setShouldLoad(true);
}
}
}