/*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* 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 Lesser General Public License for more details.
*
* Copyright (c) 2001 - 2009 Object Refinery Ltd, Pentaho Corporation and Contributors.. All rights reserved.
*/
package org.pentaho.reporting.engine.classic.core.modules.output.csv;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Iterator;
import org.pentaho.reporting.engine.classic.core.Band;
import org.pentaho.reporting.engine.classic.core.ClassicEngineBoot;
import org.pentaho.reporting.engine.classic.core.DataRow;
import org.pentaho.reporting.engine.classic.core.Element;
import org.pentaho.reporting.engine.classic.core.Group;
import org.pentaho.reporting.engine.classic.core.SubReport;
import org.pentaho.reporting.engine.classic.core.event.ReportEvent;
import org.pentaho.reporting.engine.classic.core.function.AbstractFunction;
import org.pentaho.reporting.engine.classic.core.function.FunctionProcessingException;
import org.pentaho.reporting.engine.classic.core.function.OutputFunction;
import org.pentaho.reporting.engine.classic.core.layout.InlineSubreportMarker;
import org.pentaho.reporting.engine.classic.core.states.LayoutProcess;
import org.pentaho.reporting.engine.classic.core.states.ReportState;
import org.pentaho.reporting.engine.classic.core.states.process.SubReportProcessType;
/**
* The CSV Writer is the content creation function used to create the CSV content. This implementation does no
* layouting, the DataRow's raw data is written to the supplied writer.
*
* @author Thomas Morgner.
*/
public class CSVWriter extends AbstractFunction implements OutputFunction
{
/**
* The CSVRow is used to collect the data of a single row of data.
*/
private static class CSVRow
{
/**
* The data.
*/
private final ArrayList data;
/**
* A quoter utility object.
*/
private final CSVQuoter quoter;
/**
* The line separator.
*/
private final String lineSeparator;
/**
* Creates a new CSVQuoter. The Quoter uses the system's default line separator.
*
* @param quoter a utility class for quoting CSV strings.
*/
protected CSVRow(final CSVQuoter quoter)
{
data = new ArrayList();
this.quoter = quoter;
lineSeparator =
ClassicEngineBoot.getInstance().getGlobalConfig().getConfigProperty
("line.separator", "\n");
}
/**
* appends the given integer value as java.lang.Integer to this row.
*
* @param value the appended int value
*/
public void append(final int value)
{
data.add(new Integer(value));
}
/**
* appends the given Object to this row.
*
* @param o the appended value
*/
public void append(final Object o)
{
data.add(o);
}
/**
* Writes the contents of the collected row, using the defined separator.
*
* @param w the writer.
* @throws IOException if an I/O error occurred.
*/
public void write(final Writer w)
throws IOException
{
final Iterator it = data.iterator();
while (it.hasNext())
{
w.write(quoter.doQuoting(String.valueOf(it.next())));
if (it.hasNext())
{
w.write(quoter.getSeparator());
}
}
w.write(lineSeparator);
}
}
/**
* the writer used to output the generated data.
*/
private Writer w;
/**
* the functions dependency level, -1 by default.
*/
private int depLevel;
/**
* the CSVQuoter used to encode the column values.
*/
private final CSVQuoter quoter;
/**
* a flag indicating whether to writer data row names as column header.
*/
private boolean writeDataRowNames;
private boolean writeStateColumns;
private boolean enableReportHeader;
private boolean enableReportFooter;
private boolean enableGroupHeader;
private boolean enableGroupFooter;
private boolean enableItemband;
private ArrayList inlineSubreports;
/**
* DefaulConstructor. Creates a CSVWriter with a dependency level of -1 and a default CSVQuoter.
*/
public CSVWriter()
{
setDependencyLevel(LayoutProcess.LEVEL_PAGINATE);
quoter = new CSVQuoter();
this.inlineSubreports = new ArrayList();
}
/**
* Returns whether to print dataRow column names as header.
*
* @return true, if column names are printed, false otherwise.
*/
public boolean isWriteDataRowNames()
{
return writeDataRowNames;
}
/**
* Defines, whether to print column names in the first row.
*
* @param writeDataRowNames true, if column names are printed, false otherwise
*/
public void setWriteDataRowNames(final boolean writeDataRowNames)
{
this.writeDataRowNames = writeDataRowNames;
}
public boolean isWriteStateColumns()
{
return writeStateColumns;
}
public void setWriteStateColumns(final boolean writeStateColumns)
{
this.writeStateColumns = writeStateColumns;
}
public boolean isEnableGroupFooter()
{
return enableGroupFooter;
}
public void setEnableGroupFooter(final boolean enableGroupFooter)
{
this.enableGroupFooter = enableGroupFooter;
}
public boolean isEnableGroupHeader()
{
return enableGroupHeader;
}
public void setEnableGroupHeader(final boolean enableGroupHeader)
{
this.enableGroupHeader = enableGroupHeader;
}
public boolean isEnableItemband()
{
return enableItemband;
}
public void setEnableItemband(final boolean enableItemband)
{
this.enableItemband = enableItemband;
}
public boolean isEnableReportFooter()
{
return enableReportFooter;
}
public void setEnableReportFooter(final boolean enableReportFooter)
{
this.enableReportFooter = enableReportFooter;
}
public boolean isEnableReportHeader()
{
return enableReportHeader;
}
public void setEnableReportHeader(final boolean enableReportHeader)
{
this.enableReportHeader = enableReportHeader;
}
/**
* Returns the writer used to output the generated data.
*
* @return the writer
*/
public Writer getWriter()
{
return w;
}
/**
* Defines the writer which should be used to output the generated data.
*
* @param w the writer
*/
public void setWriter(final Writer w)
{
this.w = w;
}
/**
* Defines the separator, which is used to separate columns in a row.
*
* @param separator the separator string, never null.
* @throws NullPointerException if the separator is null.
* @throws IllegalArgumentException if the separator is an empty string.
*/
public void setSeparator(final String separator)
{
if (separator == null)
{
throw new NullPointerException();
}
if (separator.length() == 0)
{
throw new IllegalArgumentException("Separator must not be an empty string");
}
this.quoter.setSeparator(separator);
}
/**
* Gets the separator which is used to separate columns in a row.
*
* @return the separator, never null.
*/
public String getSeparator()
{
return quoter.getSeparator();
}
/**
* Writes the contents of the dataRow into the CSVRow.
*
* @param dr the dataRow which should be written
* @param row the CSVRow used to collect the RowData.
*/
private void writeDataRow(final DataRow dr, final CSVRow row)
{
final String[] names = dr.getColumnNames();
final int length = names.length;
for (int i = 0; i < length; i++)
{
final Object o = dr.get(names[i]);
row.append(o);
}
}
/**
* Writes the names of the columns of the dataRow into the CSVRow.
*
* @param dr the dataRow which should be written
* @param row the CSVRow used to collect the RowData.
*/
private void writeDataRowNames(final DataRow dr, final CSVRow row)
{
final String[] names = dr.getColumnNames();
final int length = names.length;
for (int i = 0; i < length; i++)
{
final String columnName = names[i];
row.append(columnName);
}
}
/**
* Writes the ReportHeader and (if defined) the dataRow names.
*
* @param event the event.
*/
public void reportStarted(final ReportEvent event)
{
if (event.getState().isPrepareRun())
{
collectSubReports(event.getReport().getReportHeader());
return;
}
try
{
if (isWriteDataRowNames())
{
final CSVRow names = new CSVRow(quoter);
if (isWriteStateColumns())
{
names.append("report.currentgroup");
names.append("report.eventtype");
}
writeDataRowNames(event.getDataRow(), names);
names.write(getWriter());
}
if (isEnableReportHeader() == false)
{
return;
}
final CSVRow row = new CSVRow(quoter);
if (isWriteStateColumns())
{
row.append(-1);
row.append("reportheader");
}
writeDataRow(event.getDataRow(), row);
row.write(getWriter());
collectSubReports(event.getReport().getReportHeader());
}
catch (IOException ioe)
{
throw new FunctionProcessingException("Error writing the current datarow", ioe);
}
}
/**
* Writes the ReportFooter.
*
* @param event the event.
*/
public void reportFinished(final ReportEvent event)
{
if (event.getState().isPrepareRun())
{
collectSubReports(event.getReport().getReportFooter());
return;
}
if (isEnableReportFooter() == false)
{
collectSubReports(event.getReport().getReportFooter());
return;
}
try
{
final CSVRow row = new CSVRow(quoter);
if (isWriteStateColumns())
{
row.append(-1);
row.append("reportfooter");
}
writeDataRow(event.getDataRow(), row);
row.write(getWriter());
collectSubReports(event.getReport().getReportFooter());
}
catch (IOException ioe)
{
throw new FunctionProcessingException("Error writing the current datarow", ioe);
}
}
/**
* Writes the GroupHeader of the current group.
*
* @param event the event.
*/
public void groupStarted(final ReportEvent event)
{
if (event.getState().isPrepareRun())
{
final int currentIndex = event.getState().getCurrentGroupIndex();
final Group g = event.getReport().getGroup(currentIndex);
collectSubReports(g.getHeader());
return;
}
if (isEnableGroupHeader() == false)
{
final int currentIndex = event.getState().getCurrentGroupIndex();
final Group g = event.getReport().getGroup(currentIndex);
collectSubReports(g.getHeader());
return;
}
try
{
final int currentIndex = event.getState().getCurrentGroupIndex();
final CSVRow row = new CSVRow(quoter);
if (isWriteStateColumns())
{
row.append(currentIndex);
final Group g = event.getReport().getGroup(currentIndex);
final String bandInfo = "groupheader name=\"" + g.getName() + '\"';
row.append(bandInfo);
}
writeDataRow(event.getDataRow(), row);
row.write(getWriter());
final Group g = event.getReport().getGroup(currentIndex);
collectSubReports(g.getHeader());
}
catch (IOException ioe)
{
throw new FunctionProcessingException("Error writing the current datarow", ioe);
}
}
/**
* Writes the GroupFooter of the active group.
*
* @param event the event.
*/
public void groupFinished(final ReportEvent event)
{
if (event.getState().isPrepareRun())
{
final int currentIndex = event.getState().getCurrentGroupIndex();
final Group g = event.getReport().getGroup(currentIndex);
collectSubReports(g.getFooter());
return;
}
if (isEnableGroupFooter() == false)
{
final int currentIndex = event.getState().getCurrentGroupIndex();
final Group g = event.getReport().getGroup(currentIndex);
collectSubReports(g.getFooter());
return;
}
try
{
final int currentIndex = event.getState().getCurrentGroupIndex();
final CSVRow row = new CSVRow(quoter);
if (isWriteStateColumns())
{
row.append(currentIndex);
final Group g = event.getReport().getGroup(currentIndex);
final String bandInfo = "groupfooter name=\"" + g.getName() + '\"';
row.append(bandInfo);
}
writeDataRow(event.getDataRow(), row);
row.write(getWriter());
final Group g = event.getReport().getGroup(currentIndex);
collectSubReports(g.getFooter());
}
catch (IOException ioe)
{
throw new FunctionProcessingException("Error writing the current datarow", ioe);
}
}
/**
* Receives notification that a group of item bands is about to be processed. <P> The next events will be
* itemsAdvanced events until the itemsFinished event is raised.
*
* @param event The event.
*/
public void itemsStarted(final ReportEvent event)
{
collectSubReports(event.getReport().getDetailsHeader());
}
/**
* Receives notification that a group of item bands has been completed. <P> The itemBand is finished, the report
* starts to close open groups.
*
* @param event The event.
*/
public void itemsFinished(final ReportEvent event)
{
collectSubReports(event.getReport().getDetailsFooter());
}
/**
* Writes the current ItemBand.
*
* @param event the event.
*/
public void itemsAdvanced(final ReportEvent event)
{
if (event.getState().isPrepareRun())
{
collectSubReports(event.getReport().getItemBand());
return;
}
if (isEnableItemband() == false)
{
collectSubReports(event.getReport().getItemBand());
return;
}
try
{
final CSVRow row = new CSVRow(quoter);
if (isWriteStateColumns())
{
row.append(event.getState().getCurrentGroupIndex());
row.append("itemband");
}
writeDataRow(event.getDataRow(), row);
row.write(getWriter());
collectSubReports(event.getReport().getItemBand());
}
catch (IOException ioe)
{
throw new FunctionProcessingException("Error writing the current datarow", ioe);
}
}
/**
* Return a selfreference of this CSVWriter. This selfreference is used to confiugre the output process.
*
* @return this CSVWriter.
*/
public Object getValue()
{
return this;
}
/**
* The dependency level defines the level of execution for this function. Higher dependency functions are executed
* before lower dependency functions. For ordinary functions and expressions, the range for dependencies is defined to
* start from 0 (lowest dependency possible) to 2^31 (upper limit of int).
* <p/>
* PageLayouter functions override the default behaviour an place them self at depency level -1, an so before any
* user defined function.
*
* @return the level.
*/
public int getDependencyLevel()
{
return depLevel;
}
/**
* Overrides the depency level. Should be lower than any other function depency.
*
* @param deplevel the new depency level.
*/
public void setDependencyLevel(final int deplevel)
{
this.depLevel = deplevel;
}
/**
* This method simply clones the function. The CSVWriter does not maintain large internal states and therefore need
* not to be aware of any advanced optimizations.
*
* @return the derived function.
*/
public OutputFunction deriveForStorage()
{
try
{
return (OutputFunction) clone();
}
catch (CloneNotSupportedException e)
{
throw new IllegalStateException();
}
}
/**
* This method simply clones the function. The CSVWriter does not maintain large internal states and therefore need
* not to be aware of any advanced optimizations.
*
* @return the derived function.
*/
public OutputFunction deriveForPagebreak()
{
try
{
return (OutputFunction) clone();
}
catch (CloneNotSupportedException e)
{
throw new IllegalStateException();
}
}
/**
* Clones the expression. The expression should be reinitialized after the cloning. <P> Expressions maintain no
* state, cloning is done at the beginning of the report processing to disconnect the expression from any other object
* space.
*
* @return a clone of this expression.
* @throws CloneNotSupportedException this should never happen.
*/
public Object clone() throws CloneNotSupportedException
{
final CSVWriter o = (CSVWriter) super.clone();
o.inlineSubreports = (ArrayList) inlineSubreports.clone();
return o;
}
public InlineSubreportMarker[] getInlineSubreports()
{
return (InlineSubreportMarker[]) inlineSubreports.toArray
(new InlineSubreportMarker[inlineSubreports.size()]);
}
public void clearInlineSubreports(final SubReportProcessType inlineExecution)
{
final InlineSubreportMarker[] subreports = getInlineSubreports();
for (int i = 0; i < subreports.length; i++)
{
final InlineSubreportMarker subreport = subreports[i];
if (inlineExecution == subreport.getProcessType())
{
inlineSubreports.remove(i);
}
}
}
private void collectSubReports(final Band band) throws FunctionProcessingException
{
final Element[] elements = band.getElementArray();
for (int i = 0; i < elements.length; i++)
{
final Element element = elements[i];
if (element instanceof SubReport)
{
try
{
inlineSubreports.add(element.clone());
}
catch (CloneNotSupportedException e)
{
throw new FunctionProcessingException("Failed to clone subreport", e);
}
}
else if (element instanceof Band)
{
collectSubReports((Band) element);
}
}
}
public void restart(final ReportState state)
{
}
public boolean createRollbackInformation()
{
return false;
}
}