0

Converter :

@FacesConverter("bigDecimalConverter")
public class BigDecimalConverter implements Converter {

    private static final int SCALE = 2;

    @Override
    public Object getAsObject(FacesContext context, UIComponent component, String value) {

        if (value == null || value.isEmpty()) {
            return null;
        }

        try {
            return new BigDecimal(value);
        } catch (NumberFormatException e) {
            throw new ConverterException(new FacesMessage(FacesMessage.SEVERITY_ERROR, null, "Message"), e);
        }
    }

    @Override
    public String getAsString(FacesContext context, UIComponent component, Object value) {

        if (value == null) {
            return "";
        }

        BigDecimal newValue;

        if (value instanceof Long) {
            newValue = BigDecimal.valueOf((Long) value);
        } else if (value instanceof Double) {
            newValue = BigDecimal.valueOf((Double) value);
        } else if (!(value instanceof BigDecimal)) {
            throw new ConverterException("Message");
        } else {
            newValue = (BigDecimal) value;
        }

        DecimalFormat formatter = (DecimalFormat) NumberFormat.getNumberInstance();
        formatter.setGroupingUsed(false);
        formatter.setMinimumFractionDigits(SCALE);
        formatter.setMaximumFractionDigits(SCALE);
        return formatter.format(newValue);
    }
}

List :

<p:selectOneMenu id="list" value="#{bean.value}">
    <f:selectItems var="row" value="#{bean.list}" itemLabel="#{row}" itemValue="#{row}"/>
    <f:converter converterId="bigDecimalConverter"/>
</p:selectOneMenu>

<p:message id="msg" for="list"/>
<p:commandButton value="Submit" update="list msg" actionListener="#{bean.action}"/>

The managed bean backed by the above <p:selectOneMenu> :

@ManagedBean
@ViewScoped
public class Bean implements Serializable {

    private List<BigDecimal> list; // Getter only.
    private BigDecimal value; // Getter & setter.
    private static final long serialVersionUID = 1L;

    public Bean() {}

    @PostConstruct
    private void init() {
        list = new ArrayList<BigDecimal>(){{
            add(BigDecimal.valueOf(10));
            add(BigDecimal.valueOf(20.11));
            add(BigDecimal.valueOf(30));
            add(BigDecimal.valueOf(40));
            add(BigDecimal.valueOf(50));
        }};
    }

    public void action() {
        System.out.println("action() called : " + value);
    }
}

A validation message, "Validation Error: Value is not valid" appears upon submission of the form. The getAsObject() method throws no exception upon form submission.

If a value with a scale like 20.11 is selected in the list, then the validation passes. It appears that the equals() method in the java.math.BigDecimal class goes fishy which for example, considers two BigDecimal objects equal, only if both of them are equal in value and scale thus 10.0 != 10.00 which requires compareTo() for them to be equal.

Any suggestion?

Tiny
  • 27,221
  • 105
  • 339
  • 599
  • Apart from the technical problem, the converter itself doesn't seem to be useful in this case. It's only applied on item values, not item labels. So effectively the enduser doesn't see any effect of its `getAsString()`. How exactly is JSF builtin `BigDecimalConverter` or perhaps `` insufficient? – BalusC Feb 05 '16 at 11:23
  • The converter is intended to be used for currency and grouping at some other places in addition to rounding up a value to a specified number of decimal places. – Tiny Feb 05 '16 at 11:39
  • I understand, however the converter as you have now seems to be designed for output only, not for input. I think you'd for pure output cases better extend standard `NumberConverter` in case you intend to have formatting defaults without the need to repeat them over and over. Coincidentally I posted a similar answer to that yesterday: http://stackoverflow.com/questions/35180537/composite-component-with-fconvertnumber-wont-work/ – BalusC Feb 05 '16 at 12:22

1 Answers1

1

You lose information when converting to String. The default JSF BigDecimalConverter does this right, it uses BigDecimal#toString in its getAsString. The BigDecimal#toString's javadoc says:

There is a one-to-one mapping between the distinguishable BigDecimal values and the result of this conversion. That is, every distinguishable BigDecimal value (unscaled value and scale) has a unique string representation as a result of using toString. If that string representation is converted back to a BigDecimal using the BigDecimal(String) constructor, then the original value will be recovered.

This is exactly what you need. Don't treat converters like they must produce user readable-writable results when converting to String. They mustn't and often don't. They produce a String representation of an object, or an object reference. That's it. The selectItems' itemLabel defines a user readable representation in this case. I'm assuming that you don't want user writable values here, that you really have a fixed list of values for user to choose from.

If you really mean that this data must always have a scale of 2, and you need a user writable value, then that would be better checked in a validator, and user input could be helped out with p:inputMask.

Finally, let's set aside the fact, that your converter is not the best. It says "data must have a scale of 2". Then your should provide conforming data in your selectItems. More generally, server defined values must conform to relevant converters and validators. E.g. you could have problems in the same vein, when using the DateTimeConverter with the pattern "dd.MM.yyyy", but setting the default value to be new Date() without getting rid of the time part.

See also (more general notions about converters): https://stackoverflow.com/a/30529976/1341535

Community
  • 1
  • 1
Vsevolod Golovanov
  • 4,068
  • 3
  • 31
  • 65
  • How can a user readable currency like `$10.00` and a value with grouping like `11,111,111.00` be displayed and submitted then? The `getAsObject()` method should converter them to `10.00` and `11111111.00` respectively before submitting them to a lower abstract layer and the `getAsString()` method should converter them back to `$10.00` and `11,111,111.00` respectively before presenting them to end-users (A currency could have a variable number of decimal places). I assume this is the coherent task of any converter as the result produced by `toString()` is often not useful to end-users. – Tiny Feb 05 '16 at 12:36
  • These values could indeed use custom converters. I'm just saying that generally a converter has no obligation to provide a useful-to-end-user representation, and in your original question's case it's completely unnecessary due to itemLabel. See JSF 2.2 Spec 3.3.1 (Conversion Model Overview): `responsibility of converting the internal data representation`. It's the combination of converters + presentation view that implements end-user-usefulness. Cases of user writable values can be perfectly validly solved with converters + inputText. – Vsevolod Golovanov Feb 05 '16 at 14:01