OperationBuilder.java

package neureka.backend.api.template.operations;

import neureka.backend.api.Operation;
import neureka.math.Function;

import java.util.ArrayList;
import java.util.List;

/**
 *  This builder class builds instances of the {@link Operation} interface.
 *  Implementing the {@link Operation} interface manually can result in a lot of boilerplate code.
 *  A builder class is the perfect fit for the {@link Operation} because the interface mostly
 *  defines simple properties... <br>
 *  In order to ensure that all necessary properties have been set the builder keeps track
 *  of the passed parameters. If not all properties have been set, the builder will trow an exception.
 */
public final class OperationBuilder
{
    private Stringifier _stringifier = null;
    private Derivation _derivation = null;
    /**
     *  Concrete {@link Operation} types ought to be representable by a function name.
     *  This property will correspond to the {@link Operation#getIdentifier()} method.
     */
    private String _identifier = null;

    private String _operator = null;
    /**
     *  Arity is the number of arguments or operands that this function or operation takes.
     *  This property will correspond to the {@link Operation#getArity()} method.
     */
    private Integer _arity = null;
    /**
     *  An operator is an alternative to a function like "sum()" or "prod()". <br>
     *  Examples would be "+, -, * ..."!
     *
     *  This property will correspond to the {@link Operation#isOperator()} method.
     */
    private Boolean _isOperator = null;
    /**
     *  This boolean property tell the {@link Function} implementations that this {@link Operation}
     *  ought to be viewed as something to be indexed.
     *  The {@link Function} will use this information to iterate over all the provided inputs and
     *  then execute the function wile also passing the index to the function AST.
     *  The resulting array will then be available to this {@link Operation} as argument list.
     *  This feature works alongside the {@link Function} implementation found in
     *  {@link neureka.math.implementations.FunctionVariable}, which represents an input indexed
     *  by the identifier 'j'!
     *
     */
    private Boolean _isIndexer = null;
    private Boolean _isDifferentiable = null;
    private Boolean _isInline = null;
    private boolean _disposed = false;

    public interface Stringifier
    {
        String stringify( String[] children );
    }

    public interface Derivation
    {
        String asDerivative( Function[] children, int d );
    }

    public Stringifier getStringifier() { return _stringifier; }

    public Derivation getDerivator() { return _derivation; }

    public String getIdentifier() { return _identifier; }

    public String getOperator() { return _operator; }

    public Integer getArity() { return _arity; }

    public Boolean getIsOperator() { return _isOperator; }

    public Boolean getIsIndexer() { return _isIndexer; }

    public Boolean getIsDifferentiable() { return _isDifferentiable; }

    public Boolean getIsInline() { return _isInline; }

    public OperationBuilder stringifier( Stringifier stringifier ) {
        if ( _disposed ) throw new IllegalStateException("This builder has already been disposed!");
        if ( _stringifier != null ) throw new IllegalStateException("Stringifier has already been set!");
        _stringifier = stringifier;
        return this;
    }

    public OperationBuilder setDerivation( Derivation derivation) {
        if ( _disposed ) throw new IllegalStateException("This builder has already been disposed!");
        if ( _derivation != null ) throw new IllegalStateException("Derivation has already been set!");
        _derivation = derivation;
        return this;
    }

    public OperationBuilder identifier( String identifier ) {
        if ( _disposed ) throw new IllegalStateException("This builder has already been disposed!");
        if ( _identifier != null ) throw new IllegalStateException("Identifier has already been set!");
        _identifier = identifier;
        return this;
    }

    public OperationBuilder operator( String operator ) {
        if ( _disposed ) throw new IllegalStateException("This builder has already been disposed!");
        if ( _operator != null ) throw new IllegalStateException("Operator has already been set!");
        _operator = operator;
        return this;
    }

    public OperationBuilder arity( int arity ) {
        if ( _disposed ) throw new IllegalStateException("This builder has already been disposed!");
        if ( _arity != null ) throw new IllegalStateException("Arity has already been set!");
        _arity = arity;
        return this;
    }

    public OperationBuilder isOperator( boolean isOperator ) {
        if ( _disposed ) throw new IllegalStateException("This builder has already been disposed!");
        if ( _isOperator != null ) throw new IllegalStateException("IsOperator has already been set!");
        _isOperator = isOperator;
        return this;
    }

    public OperationBuilder isIndexer( boolean isIndexer ) {
        if ( _disposed ) throw new IllegalStateException("This builder has already been disposed!");
        if ( _isIndexer != null ) throw new IllegalStateException("IsIndexer has already been set!");
        _isIndexer = isIndexer;
        return this;
    }

    public OperationBuilder isDifferentiable( boolean isDifferentiable ) {
        if ( _disposed ) throw new IllegalStateException("This builder has already been disposed!");
        if ( _isDifferentiable != null ) throw new IllegalStateException("IsDifferentiable has already been set!");
        _isDifferentiable = isDifferentiable;
        return this;
    }

    public OperationBuilder isInline( boolean isInline ) {
        if ( _disposed ) throw new IllegalStateException("This builder has already been disposed!");
        if ( _isInline != null ) throw new IllegalStateException("IsInline has already been set!");
        _isInline = isInline;
        return this;
    }

    public void dispose() { _disposed = true; }

    private List<String> _listOfMissingProperties() {
        List<String> missing = new ArrayList<>();
        if ( _identifier       == null ) missing.add( "identifier" );
        if ( _operator         == null ) missing.add( "operator" );
        if ( _arity            == null ) missing.add( "arity" );
        if ( _isOperator       == null ) missing.add( "isOperator" );
        if ( _isIndexer        == null ) missing.add( "isIndexer" );
        if ( _isDifferentiable == null ) missing.add( "isDifferentiable" );
        if ( _isInline         == null ) missing.add( "isInline" );
        // Note: the stringifier is not a requirement! (there is a default impl in the AbstractOperation)
        return missing;
    }

    public Operation build()
    {
        if ( _disposed ) return null;
        List<String> missing = _listOfMissingProperties();
        if ( !missing.isEmpty() )
            throw new IllegalStateException("Factory not satisfied! The following properties are missing: '"+ String.join(", ", missing) +"'");
        else
            return new AbstractOperation( this ) {
                @Override
                public String stringify( String[] children ) {
                    if ( _stringifier == null )
                        return super.stringify( children );
                    else
                        return _stringifier.stringify( children );
                }

                @Override
                public String asDerivative( Function[] children, int derivationIndex) {
                    if ( _derivation == null )
                        return super.asDerivative( children, derivationIndex );
                    else
                        return _derivation.asDerivative( children, derivationIndex);
                }

                @Override
                public double calculate( double[] inputs, int j, int d, Function[] src ) {
                    return src[ 0 ].call( inputs, j );
                }

                @Override protected String operationName() { return "OptimizedOperation"; }
            };
    }
}