/* * 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 - 2013 Object Refinery Ltd, Pentaho Corporation and Contributors.. All rights reserved. */ package org.pentaho.reporting.engine.classic.core.modules.output.csv; 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.metadata.ElementMetaData; 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; import java.io.IOException; import java.io.Writer; import java.util.ArrayList; import java.util.Iterator; /** * 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. * @noinspection HardCodedStringLiteral * @deprecated Will be removed in the future, as PDI is a better CSV generator. */ 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<Object> 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<Object>(); 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( 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<InlineSubreportMarker> 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<InlineSubreportMarker>(); } /** * 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() ) || ( isEnableGroupHeader() == false ) ) { final int currentIndex = event.getState().getCurrentGroupIndex(); final Group g = event.getReport().getGroup( currentIndex ); collectSubReports( g, ElementMetaData.TypeClassification.HEADER ); 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, ElementMetaData.TypeClassification.RELATIONAL_HEADER ); } 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() ) || ( isEnableGroupFooter() == false ) ) { final int currentIndex = event.getState().getCurrentGroupIndex(); final Group g = event.getReport().getGroup( currentIndex ); collectSubReports( g, ElementMetaData.TypeClassification.FOOTER ); 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, ElementMetaData.TypeClassification.RELATIONAL_FOOTER ); } 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<InlineSubreportMarker>) inlineSubreports.clone(); return o; } public InlineSubreportMarker[] getInlineSubreports() { return 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 Group g, final ElementMetaData.TypeClassification type ) throws FunctionProcessingException { final int elementCount = g.getElementCount(); for ( int i = 0; i < elementCount; i += 1 ) { final Element e = g.getElement( i ); if ( e.getMetaData().getReportElementType() != type ) { continue; } if ( e instanceof Band == false ) { continue; } collectSubReports( (Band) e ); } } 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 ) { final InlineSubreportMarker marker = new InlineSubreportMarker( (SubReport) element.clone(), null, SubReportProcessType.BANDED ); inlineSubreports.add( marker ); } else if ( element instanceof Band ) { collectSubReports( (Band) element ); } } } public void groupBodyFinished( final ReportEvent event ) { } public void restart( final ReportState state ) { } public boolean createRollbackInformation() { return false; } }