AFC - Abacus Formula Compiler for Java

Binding To Multiple Interface Instances

What if you need to provide multiple separate instances of source interfaces as input providers, or need AFC to implement multiple separate instances of output interfaces?

The example code on this page, while syntactically checked, is not yet part of an automated test.

Inputs

Let’s assume you have n separate customer categories. You want to bind all cells named after the pattern CC_DISCOUNT_ to the discount percentage associated with the customer category . Here’s how you go about it.

Let’s assume the following customer interface:

public static interface CustomerCategory
{
  // ...
  double getDiscount();
  // ...
}

First, you define an accessor for the customer categories on the main input interface:

public static interface Input
{
  CustomerCategory getCC( int _iCC );
}

Then, you bind input cells in a loop. The key is to use a call chain that supplies the proper index argument to the customer category accessor:

intfGetter = Input.class.getMethod( "getCC", Integer.TYPE );
valueGetter = CustomerCategory.class.getMethod( "getDiscount" );
for (Map.Entry<String, Spreadsheet.Range> def : spreadsheet.getRangeNames().entrySet()) {
  final Spreadsheet.Range range = def.getValue();
  if (range instanceof Spreadsheet.Cell) {
    final String name = def.getKey();
    if (name.startsWith( "CC_DISCOUNT_" )) {
      final int iCC = Integer.parseInt( name.substring( "CC_DISCOUNT_".length() ) );
      final Spreadsheet.Cell cell = (Spreadsheet.Cell) range;
      final CallFrame chain = builder.newCallFrame( intfGetter, iCC ).chain( valueGetter );
      binder.defineInputCell( cell, chain );
    }
  }
}

Outputs

Let’s assume a spreadsheet is used to update next year’s discount percentage and credit limit for each customer category based on this year’s input. The sheet computes the outputs for all of the categories simultaneously because there may be dependencies between the categories. As above, the number of categories is not fixed at compile-time. You cannot use bands to model this sheet because you do not want to force the user to use the same computation model for every category.

The input model for this sheet can be implemented as described above. How do you specify the outputs? You have to use parametrized accessors for each value separately:

public static interface Output
{
  double getNewDiscount( int _iCC );
  double getNewCreditLimit( int _iCC );
}

which you bind as follows:

outputGetter = Output.class.getMethod( "getNewDiscount", Integer.TYPE );
for (Map.Entry<String, Spreadsheet.Range> def : spreadsheet.getRangeNames().entrySet()) {
  final Spreadsheet.Range range = def.getValue();
  if (range instanceof Spreadsheet.Cell) {
    final String name = def.getKey();
    if (name.startsWith( "CC_NEWDISCOUNT_" )) {
      final int iCC = Integer.parseInt( name.substring( "CC_NEWDISCOUNT_".length() ) );
      final Spreadsheet.Cell cell = (Spreadsheet.Cell) range;
      binder.defineOutputCell( cell, outputGetter, iCC );
    }
    // ... dito for CreditLimit
  }
}

You might think that the following interface definition would be much nicer:

public static interface Output2
{
  CC getCC( int _iCC );

  public interface CC
  {
    double getNewDiscount();
    double getNewCreditLimit();
  }
}

I agree. Unfortunately, AFC does not directly support it (again a consequence of keeping the black box magic in AFC at a minimum). You are, however, free to put such a facade on top of the original interface:

public static class OutputFacade
{
  final Output output;

  public OutputFacade( Output _output )
  {
    super();
    this.output = _output;
  }

  public Output getOutput()
  {
    return this.output;
  }

  public CC getCC( int n )
  {
    return new CC( n );
  }

  private class CC
  {
    private int iCC;

    public CC( int _iCC )
    {
      super();
      this.iCC = _iCC;
    }

    public double getNewDiscount()
    {
      return getOutput().getNewDiscount( this.iCC );
    }

    public double getNewCreditLimit()
    {
      return getOutput().getNewCreditLimit( this.iCC );
    }
  }
}

You might for example need to do this if you have to feed the sheet’s output to some other system that expects its input in the above form. The facade would then have to implement the other system’s input interfaces, of course.