package org.oddjob.beanbus.destinations;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import javax.inject.Inject;
import org.apache.log4j.Logger;
import org.oddjob.arooa.ArooaSession;
import org.oddjob.arooa.convert.ArooaConversionException;
import org.oddjob.arooa.convert.ArooaConverter;
import org.oddjob.arooa.deploy.annotations.ArooaHidden;
import org.oddjob.arooa.life.ArooaSessionAware;
import org.oddjob.arooa.reflect.ArooaClass;
import org.oddjob.arooa.reflect.BeanOverview;
import org.oddjob.arooa.reflect.BeanView;
import org.oddjob.arooa.reflect.BeanViews;
import org.oddjob.arooa.reflect.FallbackBeanView;
import org.oddjob.arooa.reflect.PropertyAccessor;
import org.oddjob.beanbus.AbstractDestination;
import org.oddjob.beanbus.BusConductor;
import org.oddjob.beanbus.BusCrashException;
import org.oddjob.beanbus.BusEvent;
import org.oddjob.beanbus.Outbound;
import org.oddjob.beanbus.TrackingBusListener;
import org.oddjob.io.StdoutType;
/**
* @oddjob.description Create a simple database style report from a list
* of beans.
*
* @author rob
*
*/
public class BeanSheet extends AbstractDestination<Object>
implements ArooaSessionAware, Outbound<Object> {
private static final Logger logger = Logger.getLogger(BeanSheet.class);
/** The padding character. A space. */
private static final char PADDING = ' ';
/** The space between columns. */
private static final String COLUMN_SPACE =
pad(PADDING, 2);
/** The underline character. */
private static final char UNDERLINE = '-';
private OutputStream output;
private boolean noHeaders;
private PropertyAccessor accessor;
private ArooaConverter converter;
private BeanViews beanViews;
private final List<Object> beans = new ArrayList<Object>();
private String name;
private Collection<? super Object> to;
private final TrackingBusListener busListener =
new TrackingBusListener() {
@Override
public void busStarting(BusEvent event) throws BusCrashException {
if (output == null) {
try {
output = new StdoutType().toValue();
} catch (ArooaConversionException e) {
throw new RuntimeException(e);
}
}
}
@Override
public void tripEnding(BusEvent event) throws BusCrashException {
writeBeans(beans);
beans.clear();
}
@Override
public void busTerminated(BusEvent event) {
try {
if (output != null) {
output.close();
}
}
catch (IOException ioe) {
logger.error("Failed to close output.", ioe);
}
}
};
@ArooaHidden
@Override
public void setArooaSession(ArooaSession session) {
this.accessor =
session.getTools().getPropertyAccessor();
this.converter =
session.getTools().getArooaConverter();
}
@Override
public boolean add(Object bean) {
beans.add(bean);
if (to != null) {
to.add(bean);
}
return true;
}
public void writeBeans(Iterable<?> beans) {
if (beans == null) {
throw new NullPointerException("No beans.");
}
PrintStream out = new PrintStream(output);
List<Row> rows = new ArrayList<Row>();
Header header = null;
int count = 0;
for (Object bean : beans) {
if (header == null) {
header = new Header(bean);
rows.add(header);
}
Line line = header.process(bean);
rows.add(line);
++count;
}
logger.info("Reporting on " + count + " beans");
for (Row row : rows) {
row.printTo(out);
}
out.flush();
}
@ArooaHidden
@Inject
public void setBusConductor(BusConductor busConductor) {
busListener.setBusConductor(busConductor);
}
interface Row {
void printTo(PrintStream out);
}
class Line implements Row {
String[] values;
Header header;
Line(String[] values, Header header) {
this.values = values;
this.header = header;
}
@Override
public void printTo(PrintStream out) {
for (int i = 0; i < values.length; ++i) {
if (i != 0) {
out.print(COLUMN_SPACE);
}
out.print(values[i]);
if (i != values.length - 1) {
out.print(pad(PADDING,
header.widths[i] - values[i].length()));
}
}
out.println();
}
}
class Header implements Row {
private final String[] properties;
private final String[] headings;
private final int[] widths;
Header(Object bean) {
ArooaClass arooaClass = accessor.getClassName(bean);
BeanView view = null;
if (beanViews != null) {
view = beanViews.beanViewFor(arooaClass);
}
if (view == null) {
view = new FallbackBeanView(accessor, bean);
}
String[] allProperties = view.getProperties();
List<String> readables = new ArrayList<String>();
BeanOverview overview = arooaClass.getBeanOverview(accessor);
for (String property : allProperties) {
if ("class".equals(property)) {
continue;
}
if (overview.hasReadableProperty(property)) {
readables.add(property);
}
}
this.properties = readables.toArray(
new String[readables.size()]);
this.headings = new String[properties.length];
this.widths = new int[properties.length];
for (int i = 0; i < properties.length; ++i) {
headings[i] = view.titleFor(properties[i]);
widths[i] = headings[i].length();
}
logger.info("Headings: " + Arrays.toString(headings));
}
@Override
public void printTo(PrintStream out) {
if (noHeaders) {
return;
}
for (int i = 0; i < headings.length; ++i) {
if (i != 0) {
out.print(COLUMN_SPACE);
}
out.print(headings[i]);
if (i != headings.length - 1)
out.print(pad(PADDING,
widths[i] - headings[i].length()));
}
out.println();
for (int i = 0; i < headings.length; ++i) {
if (i != 0) {
out.print(COLUMN_SPACE);
}
out.print(pad(UNDERLINE,
widths[i]));
}
out.println();
}
Line process(Object bean) {
String[] values = new String[properties.length];
for (int i = 0; i < properties.length; ++i) {
String property = properties[i];
Object value = accessor.getProperty(bean, property);
String string = null;
try {
string = converter.convert(value, String.class);
}
catch (ArooaConversionException e) {
string = pad('#', widths[i]);
}
if (string == null) {
string = "";
}
if (widths[i] < string.length()) {
widths[i] = string.length();
}
values[i] = string;
}
return new Line(values, this);
}
}
@Override
public boolean isEmpty() {
return beans.size() == 0;
}
public int getBeanCount() {
return beans.size();
}
public OutputStream getOutput() {
return output;
}
public void setOutput(OutputStream output) {
this.output = output;
}
public boolean isNoHeaders() {
return noHeaders;
}
public void setNoHeaders(boolean noHeaders) {
this.noHeaders = noHeaders;
}
public BeanViews getBeanViews() {
return beanViews;
}
public void setBeanViews(BeanViews beanViews) {
this.beanViews = beanViews;
}
private static String pad(char padding, int size) {
if (size < 1) {
return new String("");
}
char[] buff = new char[size];
Arrays.fill(buff, padding);
return new String(buff);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
if (name == null) {
return getClass().getSimpleName();
}
else {
return name;
}
}
public Collection<? super Object> getTo() {
return to;
}
public void setTo(Collection<? super Object> to) {
this.to = to;
}
}