/*
* 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) 2006 - 2013 Pentaho Corporation and Contributors. All rights reserved.
*/
package org.pentaho.reporting.libraries.formula.common;
import org.pentaho.reporting.libraries.base.config.Configuration;
import org.pentaho.reporting.libraries.formula.DefaultFormulaContext;
import org.pentaho.reporting.libraries.formula.EvaluationException;
import org.pentaho.reporting.libraries.formula.FormulaContext;
import org.pentaho.reporting.libraries.formula.LibFormulaBoot;
import org.pentaho.reporting.libraries.formula.LibFormulaErrorValue;
import org.pentaho.reporting.libraries.formula.LocalizationContext;
import org.pentaho.reporting.libraries.formula.function.FunctionRegistry;
import org.pentaho.reporting.libraries.formula.lvalues.ContextLookup;
import org.pentaho.reporting.libraries.formula.lvalues.LValue;
import org.pentaho.reporting.libraries.formula.operators.OperatorFactory;
import org.pentaho.reporting.libraries.formula.typing.ArrayCallback;
import org.pentaho.reporting.libraries.formula.typing.DefaultTypeRegistry;
import org.pentaho.reporting.libraries.formula.typing.Type;
import org.pentaho.reporting.libraries.formula.typing.TypeRegistry;
import org.pentaho.reporting.libraries.formula.typing.coretypes.AnyType;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableModel;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.TimeZone;
/**
* @author Cedric Pronzato
*/
public class TestFormulaContext implements FormulaContext {
private class InlineArrayCallback implements ArrayCallback {
private final String firstColumnName;
private final int firstRow;
private final int firstCol;
private final int count;
protected InlineArrayCallback( final String firstColumnName,
final int firstRow,
final int firstCol,
final int count ) {
this.firstColumnName = firstColumnName;
this.firstRow = firstRow;
this.firstCol = firstCol;
this.count = count;
}
public LValue getRaw( final int row, final int column ) throws EvaluationException {
if ( column == 0 ) {
final ContextLookup lookup = new ContextLookup( "." + firstColumnName + ( firstRow + row ) );
lookup.initialize( TestFormulaContext.this );
return lookup;
}
return null;
}
public Object getValue( final int row, final int column ) throws EvaluationException {
// System.out.println((firstRow+row) + " col " + column + " first col " + firstCol);
if ( column == 0 ) {
return model.getValueAt( firstRow + row, firstCol );
}
throw new RuntimeException( "cannot find symbol" );
}
public Type getType( final int row, final int column ) throws EvaluationException {
if ( column == 0 ) {
return resolveReferenceType( '.' + firstColumnName + ( firstRow + row ) );
}
return AnyType.TYPE;
}
public int getColumnCount() {
return 1;
}
public int getRowCount() {
return count;
}
}
public static Date createDate1( final int year, final int month, final int day, final int hour,
final int minute, final int sec, final int millisec ) {
final Calendar cal = GregorianCalendar.getInstance();
cal.set( GregorianCalendar.YEAR, year );
cal.set( GregorianCalendar.MONTH, month );
cal.set( GregorianCalendar.DAY_OF_MONTH, day );
cal.set( GregorianCalendar.HOUR_OF_DAY, hour );
cal.set( GregorianCalendar.MINUTE, minute );
cal.set( GregorianCalendar.SECOND, sec );
cal.set( GregorianCalendar.MILLISECOND, millisec );
return cal.getTime();
}
/*
* id B C 3 ="7" 4 =2 4 5 =3 5 6 =1=1 7 7 ="Hello" 2005-01-31 8 2006-01-31 9
* =1/0 02:00:00 10 =0 23:00:00 11 3 5 12 4 6 13 2005-01-31T01:00:00 8 14 1
* 4 15 2 3 16 3 2 17 4 1
*/
public static Date createDate1() {
final Calendar cal = GregorianCalendar.getInstance();
cal.set( GregorianCalendar.YEAR, 2005 );
cal.set( GregorianCalendar.MONTH, GregorianCalendar.JANUARY );
cal.set( GregorianCalendar.DAY_OF_MONTH, 31 );
cal.set( GregorianCalendar.MILLISECOND, 0 );
cal.set( GregorianCalendar.HOUR_OF_DAY, 0 );
cal.set( GregorianCalendar.MINUTE, 0 );
cal.set( GregorianCalendar.SECOND, 0 );
return cal.getTime();
}
public static java.sql.Date createDate( final int year, final int month, final int day ) {
final Calendar cal = GregorianCalendar.getInstance();
cal.set( GregorianCalendar.YEAR, year );
cal.set( GregorianCalendar.MONTH, month );
cal.set( GregorianCalendar.DAY_OF_MONTH, day );
cal.set( GregorianCalendar.MILLISECOND, 0 );
cal.set( GregorianCalendar.HOUR_OF_DAY, 0 );
cal.set( GregorianCalendar.MINUTE, 0 );
cal.set( GregorianCalendar.SECOND, 0 );
return new java.sql.Date( cal.getTime().getTime() );
}
private static class TestCaseTableModel extends AbstractTableModel {
private Object[][] data = new Object[][]
{
// B , C
{ null, null }, // 0
{ null, null }, // 1
{ null, null }, // 2
{ "7", null }, // 3
{ new BigDecimal( 2 ), new BigDecimal( 4 ) }, // 4
{ new BigDecimal( 3 ), new BigDecimal( 5 ) }, // 5
{ Boolean.TRUE, new BigDecimal( 7 ) }, // 6
{ "Hello", createDate( 2005, Calendar.JANUARY, 31 ) }, // 7
{ null, createDate1( 2006, Calendar.JANUARY, 31, 0, 0, 0, 0 ) }, // 8
{ LibFormulaErrorValue.ERROR_ARITHMETIC_VALUE,
createDate1( 0, 0, 0, 2, 0, 0, 0 ) }, // 9
{ new BigDecimal( 0 ), createDate1( 0, 0, 0, 23, 0, 0, 0 ) }, // 10
{ new BigDecimal( 3 ), new BigDecimal( 5 ) }, // 11
{ new BigDecimal( 4 ), new BigDecimal( 6 ) }, // 12
{ null, null }, // 13
{ new BigDecimal( 1 ), new BigDecimal( 4 ) }, // 14
{ new BigDecimal( 2 ), new BigDecimal( 3 ) }, // 15
{ new BigDecimal( 3 ), new BigDecimal( 2 ) }, // 16
{ new BigDecimal( 4 ), new BigDecimal( 1 ) }, // 17
{ new Object[] { new BigDecimal( 1 ), new BigDecimal( 2 ), new BigDecimal( 3 ) }, // B18
Arrays.asList( new Object[] { new BigDecimal( 1 ), new BigDecimal( 2 ), new BigDecimal( 3 ) } ) }, // C18
{ new Object[] { new Object[ 0 ] }, // B19
Arrays.asList( new Object[] { new ArrayList(), new BigDecimal( 42 ), new BigDecimal( 43 ) } ) }, // C19
};
public int getColumnCount() {
return 2;
}
public String getColumnName( final int column ) {
if ( column == 0 ) {
return "B";
} else if ( column == 1 ) {
return "C";
}
return null;
}
public int getRowCount() {
return 18;
}
public Object getValueAt( final int rowIndex, final int columnIndex ) {
return data[ rowIndex ][ columnIndex ];
}
}
private FormulaContext formulaContext;
private TableModel model;
private boolean useGuessType;
private DefaultTypeRegistry typeRegistry;
public static final TableModel testCaseDataset = new TestCaseTableModel();
/**
* Creates an empty formula context. It means that no references will be available.
*/
public TestFormulaContext() {
this( new DefaultTableModel(), true );
}
/**
* Creates a formula context using the given model for references. The references type will always be of type
* <code>Any</code>.
*
* @param model The model.
*/
public TestFormulaContext( final TableModel model ) {
this( model, true );
}
/**
* Creates a formula context using the given model for references.
*
* @param model The table model to use
* @param guessType if <code>resolveReferenceType</code> should guess the type of the reference or return a type
* <code>Any</code>.
*/
public TestFormulaContext( final TableModel model, final boolean guessType ) {
formulaContext = new DefaultFormulaContext
( LibFormulaBoot.getInstance().getGlobalConfig(), Locale.US, TimeZone.getDefault() );
this.model = model;
useGuessType = guessType;
this.typeRegistry = new DefaultTypeRegistry();
this.typeRegistry.initialize( this );
}
public Configuration getConfiguration() {
return formulaContext.getConfiguration();
}
public FunctionRegistry getFunctionRegistry() {
return formulaContext.getFunctionRegistry();
}
public LocalizationContext getLocalizationContext() {
return formulaContext.getLocalizationContext();
}
public OperatorFactory getOperatorFactory() {
return formulaContext.getOperatorFactory();
}
public TypeRegistry getTypeRegistry() {
return typeRegistry;
}
public boolean isReferenceDirty( final Object name )
throws EvaluationException {
return formulaContext.isReferenceDirty( name );
}
public Object resolveReference( final Object name ) throws EvaluationException {
if ( name instanceof String ) {
final String ref = (String) name;
final String[] split = ref.split( ":" );
if ( split.length == 0 ) {
return null;
}
// assuming references with the following format:
// - starting with a .
// - followed by the column name identified by one letter
// - followed by digits representing the row number
final String firstColumnName = split[ 0 ].substring( 1, 2 );
int col = -1;
for ( int i = 0; i < model.getColumnCount(); i++ ) {
if ( firstColumnName.equalsIgnoreCase( model.getColumnName( i ) ) ) {
col = i;
break;
}
}
final int firstCol = col;
final int firstRow = Integer.parseInt( split[ 0 ].substring( 2 ) );
if ( split.length == 2 ) {
// array of reference assuming same column name
final int secondRow = Integer.parseInt( split[ 1 ].substring( 2 ) );
final int count = secondRow - firstRow;
if ( count >= 0 ) {
return new InlineArrayCallback( firstColumnName, firstRow, firstCol, count + 1 );
} // else error
} else {
// one reference
return model.getValueAt( firstRow, firstCol );
}
}
return null;
}
public Type resolveReferenceType( final Object name )
throws EvaluationException {
if ( name instanceof String ) {
final String ref = (String) name;
final String[] split = ref.split( ":" );
if ( split.length == 2 ) {
return AnyType.ANY_ARRAY;
}
}
if ( useGuessType ) {
final Object value = resolveReference( name );
return getTypeRegistry().guessTypeOfObject( value );
} else {
return AnyType.TYPE;
}
}
public Date getCurrentDate() {
final GregorianCalendar gcal = new GregorianCalendar( 2011, Calendar.APRIL, 7, 15, 0, 0 );
gcal.setTimeZone( getLocalizationContext().getTimeZone() );
return gcal.getTime();
}
}