Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nested choice clause in MessageFormat?

I'm trying to do a simple logic with java.text.MessageFormat:

MessageFormat cf = new MessageFormat(
"{0,choice, 1<hello|5<{1,choice,1<more than one|4<more than four}}");

 Object[] array = {3, 1};
 System.out.println(cf.format(array));

With words: If the first parameter is greater then 1 print "hello", if it is greater than 5 than if the second parameter is greater than 1 print "more than one" if the second parameter is greater than 4 print "more than four".

I found no one saying it is impossible but I get an IllegalArgumentException:

Choice Pattern incorrect: 1<hello|5<{1,choice,1<more than one|4<more than four}

Is there a way I could do this? Thanks!

The whole stacktrace:

Exception in thread "main" java.lang.IllegalArgumentException: Choice Pattern incorrect:  1<hello|5<{1,choice,1<more than one|4<more than four}
at java.text.MessageFormat.makeFormat(Unknown Source)
at java.text.MessageFormat.applyPattern(Unknown Source)
at java.text.MessageFormat.<init>(Unknown Source)
at test.Test5.main(Test5.java:18)
Caused by: java.lang.IllegalArgumentException
    at java.text.ChoiceFormat.applyPattern(Unknown Source)
    at java.text.ChoiceFormat.<init>(Unknown Source)
    ... 4 more
like image 347
user2424380 Avatar asked Apr 07 '14 12:04

user2424380


2 Answers

If you write the pattern like this, the ChoiceFormat can not parse the format, because it can not know if control characters like the format separator (|) are for the inner format or the outer format. But if you quote the format that is nested you can tell the parser that the quoted text does not contain any control characters it should parse. The ChoiceFormat will then just return the text that contains another ChoiceFormat pattern.

If the MessageFormat class applied a ChoiceFormat it parses the result again as a MessageFormat to handle additional parameter processing, which then handles the inner ChoiceFormat.

So the code works if you write the pattern like this:

MessageFormat cf = new MessageFormat(
    "{0,choice, 1<hello|5<'{1,choice,1<more than one|4<more than four}'}");

Object[] array = {3, 1};
System.out.println(cf.format(array));
like image 106
Reboot Avatar answered Oct 14 '22 12:10

Reboot


As mentioned by @Reboot, part of the confusion with these classes is that ChoiceFormat is treated specially by MessageFormat.subformat() here:

        subFormatter = formats[i];
        if (subFormatter instanceof ChoiceFormat) {
            arg = formats[i].format(obj);
            if (arg.indexOf('{') >= 0) {
                subFormatter = new MessageFormat(arg, locale);
                obj = arguments;
                arg = null;
            }
        }

This hack is what allows a MessageFormat to contain a ChoiceFormat which itself contains a MessageFormat:

new ChoiceFormat("0#none|1#one|1<{0}").format(3);                                // "{0}"
new MessageFormat("{0,choice,0#none|1#one|1<{0}}").format(new Object[] { 3 });   // "3"

Of course then, as a special case then, a ChoiceFormat nested within a MessageFormat may contain a nested ChoiceFormat, as long as you escape/quote properly.

These classes get away with a lot of looseness in the syntax/parsing. Unlike Java or bash where parsing/escaping/quoting is "nested", the parsing here is "eager"... but it works.

I wrote some classes to help fight the madness. These classes don't try to reinvent the wheel; they simply make visible the underlying structure and nesting that already exists. But they allow you to bypass all the quoting issues, and they support arbitrary nesting depth.

In my own project I've wired them for XML. This lets me define messages like this:

<message key="how.many.items">
    <text>There </text>
    <choice argnum="0">
        <option limit="0">
            <text>are no items</text>
        </option>
        <option limit="1">
            <text>is one item</text>
        </option>
        <option limit="1&lt;">
            <text>are </text>
            <choice argnum="0">
                <option limit="0">
                    <number argnum="0"/>
                </option>
                <option limit="100">
                    <text>way too many</text>
                </option>
            </choice>
            <text>items</text>
        </option>
    </choice>
    <text>.</text>
</message>

See MessageFmt for details.

like image 20
Archie Avatar answered Oct 14 '22 12:10

Archie