/*
* 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.states.crosstab;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.pentaho.reporting.engine.classic.core.DataRow;
import org.pentaho.reporting.engine.classic.core.InvalidReportStateException;
import org.pentaho.reporting.engine.classic.core.states.ReportStateKey;
import org.pentaho.reporting.libraries.base.util.ObjectUtilities;
import java.util.ArrayList;
/**
* Computed structural data of a crosstab. It basically contains the full dataset of the column axis, which then allows
* us to inject artificial rows into the dataset.
* <p/>
* This mode uses the order in which elements occur in the datastream as normalized order of dimension elements.
* <p/>
* We have the assumption, that the data is already pre-sorted in some way and that all rows are given in that order. As
* the order can be arbitrary, we do not attempt to sort or assume that items are comparable. This model is only
* guaranteed to work well, if the data set is properly normalized. A sane MDX datasource is guaranteed to return such a
* normalized dataset.
*
* @author Thomas Morgner
*/
public class OrderedMergeCrosstabSpecification implements CrosstabSpecification {
private static final Log logger = LogFactory.getLog( OrderedMergeCrosstabSpecification.class );
private int insertationCursor;
private ArrayList<Object[]> entries;
private String[] columnSet;
private String[] rowSet;
private ReportStateKey key;
public OrderedMergeCrosstabSpecification( final ReportStateKey key, final String[] dimensionColumnSet,
final String[] rowDimensionSet ) {
if ( key == null ) {
throw new NullPointerException();
}
if ( dimensionColumnSet == null ) {
throw new NullPointerException();
}
this.key = key;
this.columnSet = dimensionColumnSet.clone();
this.rowSet = rowDimensionSet.clone();
this.entries = new ArrayList<Object[]>();
}
public int indexOf( final int start, final Object[] key ) {
if ( key == null ) {
throw new NullPointerException();
}
if ( start < 0 ) {
throw new IndexOutOfBoundsException();
}
final int size = entries.size();
for ( int i = start; i < size; i++ ) {
final Object[] objects = entries.get( i );
if ( ObjectUtilities.equalArray( key, objects ) ) {
return i;
}
}
return -1;
}
public String[] getColumnDimensionNames() {
return columnSet.clone();
}
public String[] getRowDimensionNames() {
return rowSet.clone();
}
public ReportStateKey getKey() {
return key;
}
public void startRow() {
insertationCursor = 0;
}
public void endRow() {
}
public void endCrosstab() {
}
public void add( final DataRow dataRow ) {
final Object[] newKey = new Object[columnSet.length];
for ( int i = 0; i < columnSet.length; i++ ) {
final String columnName = columnSet[i];
newKey[i] = dataRow.get( columnName );
}
final int insertPosition = findInserationPoint( newKey, 0 );
if ( insertPosition != -1 ) {
if ( insertPosition < insertationCursor ) {
throw new InvalidReportStateException( "Conflicting data in crosstab. "
+ "Cannot use insertion-order as base for normalization. Use a SortedMerge-Specification instead." );
}
final Object[] existingKey = entries.get( insertPosition );
if ( ObjectUtilities.equalArray( existingKey, newKey ) ) {
// key already exists, so we skip forward to that position.
logger.debug( "Known Key: " + insertPosition + " " + insertationCursor + " -> " + printKey( newKey ) );
insertationCursor = insertPosition;
return;
}
}
insertationCursor = entries.size();
logger.debug( "Added Key: " + insertationCursor + " -> " + printKey( newKey ) );
entries.add( newKey );
}
private String printKey( final Object[] data ) {
final StringBuilder s = new StringBuilder( "{" );
for ( int i = 0; i < data.length; i++ ) {
if ( i > 0 ) {
s.append( ',' );
}
s.append( data[i] );
}
return s.append( '}' ).toString();
}
private int findInserationPoint( final Object[] key, final int inserationPoint ) {
for ( int i = inserationPoint; i < entries.size(); i++ ) {
final Object[] existingKey = entries.get( i );
if ( ObjectUtilities.equalArray( existingKey, key ) ) {
return i;
}
}
return -1;
}
public int size() {
return entries.size();
}
public Object[] getKeyAt( final int column ) {
final Object[] data = entries.get( column );
return data.clone();
}
//
// public static void main(String[] args)
// {
// final String[] NAMES = {"Product", "Year"};
// final DataRow r1 = new StaticDataRow (NAMES, new Object[]{"Planes", new Integer(2004)});
// final DataRow r2 = new StaticDataRow (NAMES, new Object[]{"Planes", new Integer(2005)});
// final DataRow r3 = new StaticDataRow (NAMES, new Object[]{"Planes", new Integer(2001)});
// final DataRow r4 = new StaticDataRow (NAMES, new Object[]{"Planes", new Integer(2002)});
// final DataRow r5 = new StaticDataRow (NAMES, new Object[]{"Planes", new Integer(2003)});
//
// CrosstabSpecification cs = new CrosstabSpecification(NAMES);
// cs.startRow();
// cs.add(r1);
// cs.add(r2);
// cs.endRow();
// cs.startRow();
// cs.add(r3);
// cs.add(r4);
// cs.add(r5);
// cs.endRow();
// }
}