/*
* Copyright (c) 2006-2009 by Abacus Research AG, Switzerland.
* All rights reserved.
*
* This file is part of the Abacus Formula Compiler (AFC).
*
* For commercial licensing, please contact sales(at)formulacompiler.com.
*
* AFC is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AFC 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AFC. If not, see <http://www.gnu.org/licenses/>.
*/
package org.formulacompiler.spreadsheet.internal.binding;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.SortedSet;
import org.formulacompiler.compiler.CallFrame;
import org.formulacompiler.compiler.CompilerException;
import org.formulacompiler.compiler.internal.AbstractDescribable;
import org.formulacompiler.compiler.internal.DescriptionBuilder;
import org.formulacompiler.compiler.internal.Util;
import org.formulacompiler.runtime.New;
import org.formulacompiler.runtime.Resettable;
import org.formulacompiler.spreadsheet.Orientation;
import org.formulacompiler.spreadsheet.Spreadsheet;
import org.formulacompiler.spreadsheet.SpreadsheetException;
import org.formulacompiler.spreadsheet.internal.CellIndex;
import org.formulacompiler.spreadsheet.internal.CellRange;
/**
* Subsections are sorted.
* <p>
* Note: this class has a natural ordering that is inconsistent with equals.
*
* @author peo
*/
public class SectionBinding extends ElementBinding implements Comparable<SectionBinding>
{
private final WorkbookBinding workbook;
private final CallFrame callChainToCall;
private final CallFrame callToImplement;
private final CellRange range;
private final Orientation orientation;
private final Class inputClass;
private final Class outputClass;
private final Map<CellIndex, InputCellBinding> inputs = New.map();
private final Map<CallFrame, OutputCellBinding> outputs = New.map();
private final SortedSet<SectionBinding> sections = New.sortedSet();
private SectionBinding( SectionBinding _space, CallFrame _callChainToCall, Class _inputClass,
CallFrame _callToImplement, Class _outputClass, CellRange _range, Orientation _orientation )
throws CompilerException
{
super( _space );
this.workbook = _space.getWorkbook();
this.callChainToCall = _callChainToCall;
this.callToImplement = _callToImplement;
this.range = _range;
this.orientation = _orientation;
this.inputClass = _inputClass;
this.outputClass = _outputClass;
if (!_space.contains( _range.getFrom() ) || !_space.contains( _range.getTo() )) {
notInSection( toString(), _range );
}
}
/**
* Constructs the root binding of a workbook, which encompasses the entire workbook, but does not
* constitute a repeating section. Nevertheless, it has a default orientation, vertical, which
* determines the sort order of its subsections.
*/
public SectionBinding( WorkbookBinding _workbook, Class _inputClass, Class _outputClass )
{
super( null );
this.workbook = _workbook;
this.callChainToCall = null;
this.callToImplement = null;
this.range = CellRange.getEntireWorkbook( _workbook.getWorkbook() );
this.orientation = Orientation.VERTICAL;
this.inputClass = _inputClass;
this.outputClass = _outputClass;
}
public WorkbookBinding getWorkbook()
{
return this.workbook;
}
public CallFrame getCallChainToCall()
{
return this.callChainToCall;
}
public CallFrame getCallToImplement()
{
return this.callToImplement;
}
public CellRange getRange()
{
return this.range;
}
public Orientation getOrientation()
{
return this.orientation;
}
public Class getInputClass()
{
return this.inputClass;
}
public Class getOutputClass()
{
return this.outputClass;
}
/**
* The subsections are sorted to enable efficient splitting of aggregated ranges into the parts
* overlapping sections.
*/
public SortedSet<SectionBinding> getSections()
{
return this.sections;
}
// ------------------------------------------------ Definition By Interface
public void defineInputCell( Spreadsheet.Cell _cell, CallFrame _callChainToCall ) throws CompilerException
{
validateAccessible( _callChainToCall );
final CellIndex cellIndex = (CellIndex) _cell;
if (this.inputs.containsKey( cellIndex )) {
throw new CompilerException.DuplicateDefinition( "Input cell '"
+ cellIndex.getShortName() + "' is already defined" );
}
final InputCellBinding def = new InputCellBinding( this, _callChainToCall, cellIndex );
this.inputs.put( def.getIndex(), def );
this.workbook.add( def );
}
public void defineOutputCell( Spreadsheet.Cell _cell, CallFrame _call ) throws CompilerException
{
validateImplementable( _call );
if (this.outputs.containsKey( _call )) {
throw new CompilerException.DuplicateDefinition( "Output method '" + _call.toString() + "' is already defined" );
}
final CellIndex cellIndex = (CellIndex) _cell;
final OutputCellBinding def = new OutputCellBinding( this, _call, cellIndex );
this.outputs.put( _call, def );
this.workbook.add( def );
}
public SectionBinding defineRepeatingSection( Spreadsheet.Range _range, Orientation _orientation,
CallFrame _inputCallChainReturningIterable, Class _inputClass, CallFrame _outputCallToImplementIterable,
Class _outputClass ) throws CompilerException
{
if (_inputClass == null) throw new IllegalArgumentException( "inputClass is null" );
validateAccessible( _inputCallChainReturningIterable );
Util.validateIsAccessible( _inputClass, "input class" );
if (_outputCallToImplementIterable != null) {
if (_outputClass == null) throw new IllegalArgumentException( "outputClass is null" );
validateImplementable( _outputCallToImplementIterable );
Util.validateIsImplementable( _outputClass, "output class" );
}
final CellRange cellRange = (CellRange) _range;
checkSection( cellRange, _orientation );
SectionBinding result = new SectionBinding( this, _inputCallChainReturningIterable, _inputClass,
_outputCallToImplementIterable, _outputClass, cellRange, _orientation );
this.sections.add( result );
return result;
}
// ------------------------------------------------ Utils
protected void validateAccessible( CallFrame _chain )
{
Util.validateCallable( getInputClass(), _chain.getHead().getMethod() );
for (CallFrame frame : _chain.getFrames()) {
Util.validateIsAccessible( frame.getMethod(), "Input" );
}
}
protected void validateImplementable( CallFrame _chain )
{
if (_chain.getHead() != _chain) throw new IllegalArgumentException( "Cannot bind outputs to chains of calls" );
Util.validateIsImplementable( _chain.getMethod(), "Output" );
}
protected void checkSection( CellRange _range, Orientation _orientation ) throws CompilerException
{
for (SectionBinding sub : this.getSections()) {
if (sub.getRange().overlaps( _range, _orientation )) {
throw new SpreadsheetException.SectionOverlap( "Section '"
+ _range.getShortName() + "' overlaps '" + sub.toString() + "'" );
}
}
}
public int compareTo( SectionBinding _other )
{
if (this == _other) return 0;
int thisFrom = this.getRange().getFrom().getIndex( this.getOrientation() );
int otherFrom = _other.getRange().getFrom().getIndex( _other.getOrientation() );
if (thisFrom < otherFrom) return -1;
if (thisFrom > otherFrom) return +1;
return 0;
}
public boolean contains( CellIndex _cellIndex )
{
return getRange().contains( _cellIndex );
}
public SectionBinding getContainingSection( CellIndex _cellIndex )
{
for (SectionBinding section : getSections()) {
if (section.contains( _cellIndex )) return section;
}
return null;
}
public SectionBinding getSectionFor( CellIndex _index )
{
SectionBinding section = getContainingSection( _index );
if (null == section) {
return this;
}
else {
return section.getSectionFor( _index );
}
}
public CellRange getPrototypeRange( CellRange _range ) throws CompilerException
{
CellIndex from = _range.getFrom();
CellIndex to = _range.getTo();
int wantFrom = this.range.getFrom().getIndex( this.orientation );
int wantTo = this.range.getTo().getIndex( this.orientation );
int isFrom = from.getIndex( this.orientation );
int isTo = to.getIndex( this.orientation );
if ((isFrom != wantFrom) || (isTo != wantTo)) {
throw new SpreadsheetException.SectionExtentNotCovered( _range.getShortName(), this.toString(), this.orientation );
}
if (!contains( _range.getFrom() ) || !contains( _range.getTo() )) {
throw new SpreadsheetException.NotInSection( null, _range.getShortName(), this.toString(), this.getRange()
.getShortName() );
}
if (isTo > isFrom) {
return CellRange.getCellRange( from, to.setIndex( this.orientation, isFrom ) );
}
else {
return _range;
}
}
public void validate() throws CompilerException
{
if (this.outputClass != null) {
validateOutputIsFullyImplemented();
}
for (SectionBinding sub : this.sections) {
sub.validate();
}
}
private void validateOutputIsFullyImplemented() throws CompilerException
{
Map<String, Method> abstractMethods = Util.abstractMethodsOf( this.outputClass );
if (abstractMethods.size() > 0) {
if (Resettable.class.isAssignableFrom( this.outputClass )) {
abstractMethods.remove( "reset()V" );
}
for (CallFrame cf : SectionBinding.this.outputs.keySet()) {
abstractMethods.remove( Util.nameAndSignatureOf( cf.getMethod() ) );
}
for (SectionBinding sub : SectionBinding.this.sections) {
if (sub.callToImplement != null) {
abstractMethods.remove( Util.nameAndSignatureOf( sub.callToImplement.getMethod() ) );
}
}
if (abstractMethods.size() > 0) {
final Method m = abstractMethods.values().iterator().next();
throw new CompilerException.MethodNotImplemented( m );
}
}
}
@Override
public void describeTo( DescriptionBuilder _to )
{
getRange().describeTo( _to );
if (this.callChainToCall != null) {
_to.append( " (which iterates " );
((AbstractDescribable) this.callChainToCall).describeTo( _to );
_to.append( ")" );
}
}
}