/*
* Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the Classpath exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code 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
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.btrace.comm;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.List;
import com.sun.btrace.aggregation.HistogramData;
import java.util.regex.Pattern;
/**
* A data command that holds tabular data.
*
* The elements contained within the grid must be of type Number, String or HistogramData.
*
* @author Christian Glencross
*/
public class GridDataCommand extends DataCommand {
private static final Pattern INDEX_PATTERN = Pattern.compile("%(\\d)+\\$");
private List<Object[]> data;
private String format;
/**
* Used when deserializing a {@linkplain GridDataCommand} instance.<br/>
* The instance is then initialized by calling the {@linkplain GridDataCommand#read(java.io.ObjectInput) } method
*/
public GridDataCommand() {
this(null, null);
}
/**
* Creates a new instance of {@linkplain GridDataCommand} with implicit format
* @param name The aggregation name
* @param data The aggregation data
*/
public GridDataCommand(String name, List<Object[]> data) {
this(name, data, null);
}
/**
* Creates a new instance of {@linkplain GridDataCommand} with explicit format
* @param name The aggregation name
* @param data The aggregation data
* @param format The format to use. It mimics {@linkplain String#format(java.lang.String, java.lang.Object[]) } behaviour
* with the addition of the ability to address the key title as a 0-indexed item
* @see String#format(java.lang.String, java.lang.Object[])
*/
public GridDataCommand(String name, List<Object[]> data, String format) {
super(GRID_DATA, name);
this.data = data;
this.format = format;
}
public List<Object[]> getData() {
return data;
}
/**
* Calculates the necessary field width of each column
* @param objects a list of objects
* @return a map containing as key the column number and as value the width of the longest value
*/
private Map<Integer, Integer> getColumnWidth(List<Object[]> objects) {
Map<Integer, Integer> columnWidth = new LinkedHashMap<>();
for (Object[] obj : objects) {
for (int column = 0; column < obj.length; ++column) {
int length = obj[column].toString().length();
Integer width = 0;
if (columnWidth.containsKey(column)) {
width = columnWidth.get(column);
}
if (length > width) {
columnWidth.put(column, length);
}
}
}
return columnWidth;
}
@Override
public void print(PrintWriter out) {
if (data != null) {
if (name != null && !name.isEmpty()) {
out.println(name);
}
Map<Integer, Integer> columnWidth = getColumnWidth(data);
for (Object[] dataRow : data) {
// Convert histograms to strings, and pretty-print multi-line text
Object[] printRow = dataRow.clone();
for (int i = 0; i < printRow.length; i++) {
if (printRow[i] == null) {
printRow[i] = "<null>";
}
if (printRow[i] instanceof HistogramData) {
StringWriter buffer = new StringWriter();
PrintWriter writer = new PrintWriter(buffer);
((HistogramData) printRow[i]).print(writer);
writer.flush();
printRow[i] = buffer.toString();
}
if (printRow[i] instanceof String) {
String value = (String) printRow[i];
if (value.contains("\n")) {
printRow[i] = reformatMultilineValue(value);
}
}
}
// Format the text
String usedFormat = this.format;
if (usedFormat == null || usedFormat.length() == 0) {
StringBuilder buffer = new StringBuilder();
for (int i = 0; i < printRow.length; i++) {
buffer.append(" ");
buffer.append(getFormat(printRow[i], columnWidth, i));
}
usedFormat = buffer.toString();
}
String line = String.format(usedFormat, printRow);
out.println(line);
}
}
out.flush();
}
private static final HashMap<Class< ? >, String> typeFormats = new HashMap<Class< ? >, String>();
static {
typeFormats.put(Integer.class, "%15d");
typeFormats.put(Short.class, "%15d");
typeFormats.put(Byte.class, "%15d");
typeFormats.put(Long.class, "%15d");
typeFormats.put(BigInteger.class, "%15d");
typeFormats.put(Double.class, "%15f");
typeFormats.put(Float.class, "%15f");
typeFormats.put(BigDecimal.class, "%15f");
typeFormats.put(String.class, "%-50s");
}
/**
* Get the format of an object
* @param object get the format of this object
* @param columnWidth a map containing as key the column number and as value the width of the longest value
* @param column get the format of that column number
* @return the format of an object
*/
private String getFormat(Object object, Map<Integer, Integer> columnWidth, Integer column) {
if (object == null) {
return "%-15s";
}
String usedFormat = typeFormats.get(object.getClass());
if (usedFormat == null) {
return "%-15s";
}
if (columnWidth != null && column != null && columnWidth.containsKey(column)) {
usedFormat = usedFormat.replaceFirst("\\d+", String.valueOf(columnWidth.get(column)));
}
return usedFormat;
}
/**
* Takes a multi-line value, prefixes and appends a blank line, and inserts tab characters at the start of every
* line. This is derived from how dtrace displays stack traces, and it makes for pretty readable output.
*/
private String reformatMultilineValue(String value) {
StringBuilder result = new StringBuilder();
result.append("\n");
for (String line : value.split("\n")) {
result.append("\t").append(line);
result.append("\n");
}
return result.toString();
}
@Override
protected void write(ObjectOutput out) throws IOException {
out.writeUTF(name != null ? name : "");
if (data != null) {
out.writeUTF(format != null ? format : "");
out.writeInt(data.size());
for (Object[] row : data) {
out.writeInt(row.length);
for (Object cell : row) {
out.writeObject(cell);
}
}
} else {
out.writeInt(0);
}
}
@Override
protected void read(ObjectInput in) throws IOException, ClassNotFoundException {
name = in.readUTF();
format = in.readUTF();
if (format.length() == 0) format = null;
int rowCount = in.readInt();
data = new ArrayList<Object[]>(rowCount);
for (int i = 0; i < rowCount; i++) {
int cellCount = in.readInt();
Object[] row = new Object[cellCount];
for (int j = 0; j < cellCount; j++) {
row[j] = in.readObject();
}
data.add(row);
}
}
}