JSF EL Calculator bean – to overcome coercion errors and add functionality to EL Expressions

In a recent project I was working on JSF page that needed to display the result of a calculation performed on values taken from various bean properties. The beans were somewhat beyond my control, the properties were all of a numeric type so I tried an EL expression like:

<af:outputText value="#{SimpleBean.operand1 + SimpleBean.operand2}"/>

Simple as it may seem, JSF EL Evaluation resulted in errors such as: “(Coercions) -Attempt to coerce a value of type “oracle.jbo.domain.Number” to type “java.lang.Long”“. The two bean properties involved in this case were of different types. The standard EL evaluator in JSF 1.1 assumes that properties involved in numerical operations are either Long (no fraction) or Double (when a fraction is involved). If they are not one of those… too bad.

An attempt to base the rendering expression for this outputText element on one of the operandi also failed, with same error:

<af:outputText value="#{SimpleBean.operand1 + SimpleBean.operand2}" rendered="#{SimpleBean.operand1 &gt; 0}"/>

Again the operation requires a numerical value and EL Evaluation goes for Long. In my case I had used the oracle.jbo.domain.Number – hence the error.

Along the same lines are requirements for operations that EL evaluation does currently not support, such as rounding numbers (for example after calculating #{operand1/operand2*100} %, you can easily end up with output like “33.3333333333 %”, which is slightly overdoing it. Rounding the division result to 33.3 would be a boon, but EL cannot do it.

I have come up with a very simple class that can be used in any JSF application to overcome such challenges. Using expressions such as:

<af:outputText value="#{Calculator.RESET[SimpleBean.operand1][SimpleBean.operand2].ADD.EQUALS}" rendered="#{Calculator.RESET[SimpleBean.operand1].LONG.EQUALS &gt; 0}"/>

all the coercion problems disappear.

Using the Calculator bean, we can even create a fairly dynamic calculator, such as shown here:

JSF EL Calculator bean - to overcome coercion errors and add functionality to EL Expressions jsfelcalculator1

Any time we change one of the operands or the operator, the outcome is immediately recalculated. The essential piece
of JSF behind this calculator is this:

            <af:inputText id="oper1" label="Operand 1" columns="5"
                          value="#{SimpleBean.operand1}" autoSubmit="true"/>
            <af:selectOneChoice label="  " value="#{SimpleBean.operator}"
                                id="operator" autoSubmit="true">
              <f:selectItem itemLabel="+" itemValue="ADD"/>
              <f:selectItem itemLabel="-" itemValue="SUBSTRACT"/>
              <f:selectItem itemLabel="*" itemValue="MULTIPLY"/>
              <f:selectItem itemLabel="/" itemValue="DIVIDE"/>
            </af:selectOneChoice>
            <af:inputText id="oper2" label="  Operand 2" columns="5"
                          value="#{SimpleBean.operand2}" autoSubmit="true"/>
            <af:outputLabel value="="/>
            <af:panelGroup partialTriggers="oper1">
              <af:outputText value="#{Calculator.RESET[SimpleBean.operand1][SimpleBean.operand2][SimpleBean.operator].EQUALS}"
                             partialTriggers="oper1 operator oper2"
                             rendered="#{Calculator.RESET[SimpleBean.operand1].LONG.EQUALS > 0}"/>
            </af:panelGroup>

The one element that really does the trick if of course the outputText element. (Notice how the autoSubmit and partialTriggers attributes are used to get AJAX-style refresh whenever the operandi or operator are changed. This functionality in the Apache MyFaces Trinidad en Oracle ADF Faces JSF libraries)

To calculate a percentage and round it to 2 fractional digits, you could use:

#{Calculator.RESET[SimpleBean.operand1][SimpleBean.operand2].DIVIDE[1000].MULTIPLY.ROUND[10].DIVIDE.EQUALS}

The Calculator class is fairly simple. It most distinguishing feature is probably the fact that it extends the HashMap (or rather that it implements the Map interface):

package nl.amis.adffaces.matrix;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import oracle.jbo.domain.Number;

public class Calculator extends HashMap {

    private static final String reset ="RESET";
    private static final String add ="ADD";
    private static final String substract ="SUBSTRACT";
    private static final String divide ="DIVIDE";
    private static final String multiply ="MULTIPLY";
    private static final String round ="ROUND";
    private static final String aslong ="LONG";
    private static final String equals ="EQUALS";
    
    public List operands = new ArrayList();
    
    public Calculator() {
    }

    public Object get(Object key) {
        boolean processed = false;
        if (key instanceof String) {
            String command = (String)key;
            if (reset.equalsIgnoreCase(command)) {
                operands = new ArrayList();
                return this;
            }
            if (equals.equalsIgnoreCase(command)) {
                processed=true;            
                return operands.get(operands.size()-1);
            }
            Object result = null;
            if (add.equalsIgnoreCase(command)) {
                result = add( operands.get(operands.size()-2), operands.get(operands.size()-1));
                processed=true;
            }
            if (substract.equalsIgnoreCase(command)) {
                result = substract( operands.get(operands.size()-2), operands.get(operands.size()-1));
                processed=true;
            }
            if (multiply.equalsIgnoreCase(command)) {
                result = multiply( operands.get(operands.size()-2), operands.get(operands.size()-1));
                processed=true;
            }
            if (divide.equalsIgnoreCase(command)) {
                result = divide( operands.get(operands.size()-2), operands.get(operands.size()-1));
                processed=true;
            }
            if (aslong.equalsIgnoreCase(command)) {
                result = aslong(  operands.get(operands.size()-1));
                operands.add(null); // since this is a one operand operation
                processed=true;
            }
            if (round.equalsIgnoreCase(command)) {
                result = round( operands.get(operands.size()-1));
                operands.add(null); // since this is a one operand operation
                processed=true;
            }
            if (result!=null) {
                operands.remove(operands.size()-1);
                operands.remove(operands.size()-1);
                operands.add(result);                            
            }                       
        }
        if (!processed)
          operands
.add(key);    
      retur
n this;
    }
    
    private Object add(Object o1, Object o2) {
        return makeNumber(o1).add(makeNumber(o2));        
    }
    private Object substract(Object o1, Object o2) {
        return makeNumber(o1).subtract(makeNumber(o2));        
    }
    private Object multiply(Object o1, Object o2) {
        return makeNumber(o1).multiply(makeNumber(o2));        
    }
    private Object divide(Object o1, Object o2) {
        try {
            return makeNumber(o1).divide(makeNumber(o2));        
        }
        catch (Exception e) {
            return null;
        }
    }
    private Object round(Object o1) {
        try {
            return makeNumber(o1).round(0);        
        }
        catch (Exception e) {
            return null;
        }
    }
    private Object aslong(Object o1) {
        try {
            return new Long(makeNumber(o1).longValue());        
        }
        catch (Exception e) {
            return null;
        }
    }
    
    private Number makeNumber(Object o) {
       try {
       if (o instanceof Number) return (Number)o;
           if (o instanceof String) return new Number((String)o);
           if (o instanceof Long) return new Number((Long)o);
           if (o instanceof Integer) return new Number((Integer)o);
           if (o instanceof Double) return new Number((Double)o);
       } catch (Exception e) {
           return null;
       }
       return null;
    }

}

Note: by extending the Calculator class we can support an unlimited number of operations and functions, for formatting, string manipulation, calculations etc.