| // © 2016 and later: Unicode, Inc. and others. |
| // License & terms of use: http://www.unicode.org/copyright.html |
| /* |
| ******************************************************************************* |
| * Copyright (C) 2011-2012, International Business Machines |
| * Corporation and others. All Rights Reserved. |
| ******************************************************************************* |
| * created on: 2011aug12 |
| * created by: Markus W. Scherer |
| */ |
| |
| package com.ibm.icu.dev.test.format; |
| |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Locale; |
| |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.JUnit4; |
| |
| import com.ibm.icu.dev.test.TestFmwk; |
| import com.ibm.icu.text.MessagePattern; |
| import com.ibm.icu.text.MessagePatternUtil; |
| import com.ibm.icu.text.MessagePatternUtil.ArgNode; |
| import com.ibm.icu.text.MessagePatternUtil.ComplexArgStyleNode; |
| import com.ibm.icu.text.MessagePatternUtil.MessageContentsNode; |
| import com.ibm.icu.text.MessagePatternUtil.MessageNode; |
| import com.ibm.icu.text.MessagePatternUtil.TextNode; |
| import com.ibm.icu.text.MessagePatternUtil.VariantNode; |
| |
| /** |
| * Test MessagePatternUtil (MessagePattern-as-tree-of-nodes API) |
| * by building parallel trees of nodes and verifying that they match. |
| */ |
| @RunWith(JUnit4.class) |
| public final class MessagePatternUtilTest extends TestFmwk { |
| // The following nested "Expect..." classes are used to build |
| // a tree structure parallel to what the MessagePatternUtil class builds. |
| // These nested test classes are not static so that they have access to TestFmwk methods. |
| |
| private class ExpectMessageNode { |
| private ExpectMessageNode expectTextThatContains(String s) { |
| contents.add(new ExpectTextNode(s)); |
| return this; |
| } |
| private ExpectMessageNode expectReplaceNumber() { |
| contents.add(new ExpectMessageContentsNode()); |
| return this; |
| } |
| private ExpectMessageNode expectNoneArg(Object name) { |
| contents.add(new ExpectArgNode(name)); |
| return this; |
| } |
| private ExpectMessageNode expectSimpleArg(Object name, String type) { |
| contents.add(new ExpectArgNode(name, type)); |
| return this; |
| } |
| private ExpectMessageNode expectSimpleArg(Object name, String type, String style) { |
| contents.add(new ExpectArgNode(name, type, style)); |
| return this; |
| } |
| private ExpectComplexArgNode expectChoiceArg(Object name) { |
| return expectComplexArg(name, MessagePattern.ArgType.CHOICE); |
| } |
| private ExpectComplexArgNode expectPluralArg(Object name) { |
| return expectComplexArg(name, MessagePattern.ArgType.PLURAL); |
| } |
| private ExpectComplexArgNode expectSelectArg(Object name) { |
| return expectComplexArg(name, MessagePattern.ArgType.SELECT); |
| } |
| private ExpectComplexArgNode expectSelectOrdinalArg(Object name) { |
| return expectComplexArg(name, MessagePattern.ArgType.SELECTORDINAL); |
| } |
| private ExpectComplexArgNode expectComplexArg(Object name, MessagePattern.ArgType argType) { |
| ExpectComplexArgNode complexArg = new ExpectComplexArgNode(this, name, argType); |
| contents.add(complexArg); |
| return complexArg; |
| } |
| private ExpectComplexArgNode finishVariant() { |
| return parent; |
| } |
| private void checkMatches(MessageNode msg) { |
| // matches() prints all errors. |
| matches(msg); |
| } |
| private boolean matches(MessageNode msg) { |
| List<MessageContentsNode> msgContents = msg.getContents(); |
| boolean ok = assertEquals("different numbers of MessageContentsNode", |
| contents.size(), msgContents.size()); |
| if (ok) { |
| Iterator<MessageContentsNode> msgIter = msgContents.iterator(); |
| for (ExpectMessageContentsNode ec : contents) { |
| ok &= ec.matches(msgIter.next()); |
| } |
| } |
| if (!ok) { |
| errln("error in message: " + msg.toString()); |
| } |
| return ok; |
| } |
| private ExpectComplexArgNode parent; // for finishVariant() |
| private List<ExpectMessageContentsNode> contents = |
| new ArrayList<ExpectMessageContentsNode>(); |
| } |
| |
| /** |
| * Base class for message contents nodes. |
| * Used directly for REPLACE_NUMBER nodes, subclassed for others. |
| */ |
| private class ExpectMessageContentsNode { |
| protected boolean matches(MessageContentsNode c) { |
| return assertEquals("not a REPLACE_NUMBER node", |
| MessageContentsNode.Type.REPLACE_NUMBER, c.getType()); |
| } |
| } |
| |
| private class ExpectTextNode extends ExpectMessageContentsNode { |
| private ExpectTextNode(String subString) { |
| this.subString = subString; |
| } |
| @Override |
| protected boolean matches(MessageContentsNode c) { |
| return |
| assertEquals("not a TextNode", |
| MessageContentsNode.Type.TEXT, c.getType()) && |
| assertTrue("TextNode does not contain \"" + subString + "\"", |
| ((TextNode)c).getText().contains(subString)); |
| } |
| private String subString; |
| } |
| |
| private class ExpectArgNode extends ExpectMessageContentsNode { |
| private ExpectArgNode(Object name) { |
| this(name, null, null); |
| } |
| private ExpectArgNode(Object name, String type) { |
| this(name, type, null); |
| } |
| private ExpectArgNode(Object name, String type, String style) { |
| if (name instanceof String) { |
| this.name = (String)name; |
| this.number = -1; |
| } else { |
| this.number = (Integer)name; |
| this.name = Integer.toString(this.number); |
| } |
| if (type == null) { |
| argType = MessagePattern.ArgType.NONE; |
| } else { |
| argType = MessagePattern.ArgType.SIMPLE; |
| } |
| this.type = type; |
| this.style = style; |
| } |
| @Override |
| protected boolean matches(MessageContentsNode c) { |
| boolean ok = |
| assertEquals("not an ArgNode", |
| MessageContentsNode.Type.ARG, c.getType()); |
| if (!ok) { |
| return ok; |
| } |
| ArgNode arg = (ArgNode)c; |
| ok &= assertEquals("unexpected ArgNode argType", |
| argType, arg.getArgType()); |
| ok &= assertEquals("unexpected ArgNode arg name", |
| name, arg.getName()); |
| ok &= assertEquals("unexpected ArgNode arg number", |
| number, arg.getNumber()); |
| ok &= assertEquals("unexpected ArgNode arg type name", |
| type, arg.getTypeName()); |
| ok &= assertEquals("unexpected ArgNode arg style", |
| style, arg.getSimpleStyle()); |
| if (argType == MessagePattern.ArgType.NONE || argType == MessagePattern.ArgType.SIMPLE) { |
| ok &= assertNull("unexpected non-null complex style", arg.getComplexStyle()); |
| } |
| return ok; |
| } |
| private String name; |
| private int number; |
| protected MessagePattern.ArgType argType; |
| private String type; |
| private String style; |
| } |
| |
| private class ExpectComplexArgNode extends ExpectArgNode { |
| private ExpectComplexArgNode(ExpectMessageNode parent, |
| Object name, MessagePattern.ArgType argType) { |
| super(name, argType.toString().toLowerCase(Locale.ENGLISH)); |
| this.argType = argType; |
| this.parent = parent; |
| } |
| private ExpectComplexArgNode expectOffset(double offset) { |
| this.offset = offset; |
| explicitOffset = true; |
| return this; |
| } |
| private ExpectMessageNode expectVariant(String selector) { |
| ExpectVariantNode variant = new ExpectVariantNode(this, selector); |
| variants.add(variant); |
| return variant.msg; |
| } |
| private ExpectMessageNode expectVariant(String selector, double value) { |
| ExpectVariantNode variant = new ExpectVariantNode(this, selector, value); |
| variants.add(variant); |
| return variant.msg; |
| } |
| private ExpectMessageNode finishComplexArg() { |
| return parent; |
| } |
| @Override |
| protected boolean matches(MessageContentsNode c) { |
| boolean ok = super.matches(c); |
| if (!ok) { |
| return ok; |
| } |
| ArgNode arg = (ArgNode)c; |
| ComplexArgStyleNode complexStyle = arg.getComplexStyle(); |
| ok &= assertNotNull("unexpected null complex style", complexStyle); |
| if (!ok) { |
| return ok; |
| } |
| ok &= assertEquals("unexpected complex-style argType", |
| argType, complexStyle.getArgType()); |
| ok &= assertEquals("unexpected complex-style hasExplicitOffset()", |
| explicitOffset, complexStyle.hasExplicitOffset()); |
| ok &= assertEquals("unexpected complex-style offset", |
| offset, complexStyle.getOffset()); |
| List<VariantNode> complexVariants = complexStyle.getVariants(); |
| ok &= assertEquals("different number of variants", |
| variants.size(), complexVariants.size()); |
| if (!ok) { |
| return ok; |
| } |
| Iterator<VariantNode> complexIter = complexVariants.iterator(); |
| for (ExpectVariantNode variant : variants) { |
| ok &= variant.matches(complexIter.next()); |
| } |
| return ok; |
| } |
| private ExpectMessageNode parent; // for finishComplexArg() |
| private boolean explicitOffset; |
| private double offset; |
| private List<ExpectVariantNode> variants = new ArrayList<ExpectVariantNode>(); |
| } |
| |
| private class ExpectVariantNode { |
| private ExpectVariantNode(ExpectComplexArgNode parent, String selector) { |
| this(parent, selector, MessagePattern.NO_NUMERIC_VALUE); |
| } |
| private ExpectVariantNode(ExpectComplexArgNode parent, String selector, double value) { |
| this.selector = selector; |
| numericValue = value; |
| msg = new ExpectMessageNode(); |
| msg.parent = parent; |
| } |
| private boolean matches(VariantNode v) { |
| boolean ok = assertEquals("different selector strings", |
| selector, v.getSelector()); |
| ok &= assertEquals("different selector strings", |
| isSelectorNumeric(), v.isSelectorNumeric()); |
| ok &= assertEquals("different selector strings", |
| numericValue, v.getSelectorValue()); |
| return ok & msg.matches(v.getMessage()); |
| } |
| private boolean isSelectorNumeric() { |
| return numericValue != MessagePattern.NO_NUMERIC_VALUE; |
| } |
| private String selector; |
| private double numericValue; |
| private ExpectMessageNode msg; |
| } |
| |
| // The actual tests start here. ---------------------------------------- *** |
| // Sample message strings are mostly from the MessagePatternUtilDemo. |
| |
| @Test |
| public void TestHello() { |
| // No syntax. |
| MessageNode msg = MessagePatternUtil.buildMessageNode("Hello!"); |
| ExpectMessageNode expect = new ExpectMessageNode().expectTextThatContains("Hello"); |
| expect.checkMatches(msg); |
| } |
| |
| @Test |
| public void TestHelloWithApos() { |
| // Literal ASCII apostrophe. |
| MessageNode msg = MessagePatternUtil.buildMessageNode("Hel'lo!"); |
| ExpectMessageNode expect = new ExpectMessageNode().expectTextThatContains("Hel'lo"); |
| expect.checkMatches(msg); |
| } |
| |
| @Test |
| public void TestHelloWithQuote() { |
| // Apostrophe starts quoted literal text. |
| MessageNode msg = MessagePatternUtil.buildMessageNode("Hel'{o!"); |
| ExpectMessageNode expect = new ExpectMessageNode().expectTextThatContains("Hel{o"); |
| expect.checkMatches(msg); |
| // Terminating the quote should yield the same result. |
| msg = MessagePatternUtil.buildMessageNode("Hel'{'o!"); |
| expect.checkMatches(msg); |
| // Double apostrophe inside quoted literal text still encodes a single apostrophe. |
| msg = MessagePatternUtil.buildMessageNode("a'{bc''de'f"); |
| expect = new ExpectMessageNode().expectTextThatContains("a{bc'def"); |
| expect.checkMatches(msg); |
| } |
| |
| @Test |
| public void TestNoneArg() { |
| // Numbered argument. |
| MessageNode msg = MessagePatternUtil.buildMessageNode("abc{0}def"); |
| ExpectMessageNode expect = new ExpectMessageNode(). |
| expectTextThatContains("abc").expectNoneArg(0).expectTextThatContains("def"); |
| expect.checkMatches(msg); |
| // Named argument. |
| msg = MessagePatternUtil.buildMessageNode("abc{ arg }def"); |
| expect = new ExpectMessageNode(). |
| expectTextThatContains("abc").expectNoneArg("arg").expectTextThatContains("def"); |
| expect.checkMatches(msg); |
| // Numbered and named arguments. |
| msg = MessagePatternUtil.buildMessageNode("abc{1}def{arg}ghi"); |
| expect = new ExpectMessageNode(). |
| expectTextThatContains("abc").expectNoneArg(1).expectTextThatContains("def"). |
| expectNoneArg("arg").expectTextThatContains("ghi"); |
| expect.checkMatches(msg); |
| } |
| |
| @Test |
| public void TestSimpleArg() { |
| MessageNode msg = MessagePatternUtil.buildMessageNode("a'{bc''de'f{0,number,g'hi''jk'l#}"); |
| ExpectMessageNode expect = new ExpectMessageNode(). |
| expectTextThatContains("a{bc'def").expectSimpleArg(0, "number", "g'hi''jk'l#"); |
| expect.checkMatches(msg); |
| } |
| |
| @Test |
| public void TestSelectArg() { |
| MessageNode msg = MessagePatternUtil.buildMessageNode( |
| "abc{2, number}ghi{3, select, xx {xxx} other {ooo}} xyz"); |
| ExpectMessageNode expect = new ExpectMessageNode(). |
| expectTextThatContains("abc").expectSimpleArg(2, "number"). |
| expectTextThatContains("ghi"). |
| expectSelectArg(3). |
| expectVariant("xx").expectTextThatContains("xxx").finishVariant(). |
| expectVariant("other").expectTextThatContains("ooo").finishVariant(). |
| finishComplexArg(). |
| expectTextThatContains(" xyz"); |
| expect.checkMatches(msg); |
| } |
| |
| @Test |
| public void TestPluralArg() { |
| // Plural with only keywords. |
| MessageNode msg = MessagePatternUtil.buildMessageNode( |
| "abc{num_people, plural, offset:17 few{fff} other {oooo}}xyz"); |
| ExpectMessageNode expect = new ExpectMessageNode(). |
| expectTextThatContains("abc"). |
| expectPluralArg("num_people"). |
| expectOffset(17). |
| expectVariant("few").expectTextThatContains("fff").finishVariant(). |
| expectVariant("other").expectTextThatContains("oooo").finishVariant(). |
| finishComplexArg(). |
| expectTextThatContains("xyz"); |
| expect.checkMatches(msg); |
| // Plural with explicit-value selectors. |
| msg = MessagePatternUtil.buildMessageNode( |
| "abc{ num , plural , offset: 2 =1 {1} =-1 {-1} =3.14 {3.14} other {oo} }xyz"); |
| expect = new ExpectMessageNode(). |
| expectTextThatContains("abc"). |
| expectPluralArg("num"). |
| expectOffset(2). |
| expectVariant("=1", 1).expectTextThatContains("1").finishVariant(). |
| expectVariant("=-1", -1).expectTextThatContains("-1").finishVariant(). |
| expectVariant("=3.14", 3.14).expectTextThatContains("3.14").finishVariant(). |
| expectVariant("other").expectTextThatContains("oo").finishVariant(). |
| finishComplexArg(). |
| expectTextThatContains("xyz"); |
| expect.checkMatches(msg); |
| // Plural with number replacement. |
| msg = MessagePatternUtil.buildMessageNode( |
| "a_{0,plural,other{num=#'#'=#'#'={1,number,##}!}}_z"); |
| expect = new ExpectMessageNode(). |
| expectTextThatContains("a_"). |
| expectPluralArg(0). |
| expectVariant("other"). |
| expectTextThatContains("num=").expectReplaceNumber(). |
| expectTextThatContains("#=").expectReplaceNumber(). |
| expectTextThatContains("#=").expectSimpleArg(1, "number", "##"). |
| expectTextThatContains("!").finishVariant(). |
| finishComplexArg(). |
| expectTextThatContains("_z"); |
| expect.checkMatches(msg); |
| // Plural with explicit offset:0. |
| msg = MessagePatternUtil.buildMessageNode( |
| "a_{0,plural,offset:0 other{num=#!}}_z"); |
| expect = new ExpectMessageNode(). |
| expectTextThatContains("a_"). |
| expectPluralArg(0). |
| expectOffset(0). |
| expectVariant("other"). |
| expectTextThatContains("num=").expectReplaceNumber(). |
| expectTextThatContains("!").finishVariant(). |
| finishComplexArg(). |
| expectTextThatContains("_z"); |
| expect.checkMatches(msg); |
| } |
| |
| |
| @Test |
| public void TestSelectOrdinalArg() { |
| MessageNode msg = MessagePatternUtil.buildMessageNode( |
| "abc{num, selectordinal, offset:17 =0{null} few{fff} other {oooo}}xyz"); |
| ExpectMessageNode expect = new ExpectMessageNode(). |
| expectTextThatContains("abc"). |
| expectSelectOrdinalArg("num"). |
| expectOffset(17). |
| expectVariant("=0", 0).expectTextThatContains("null").finishVariant(). |
| expectVariant("few").expectTextThatContains("fff").finishVariant(). |
| expectVariant("other").expectTextThatContains("oooo").finishVariant(). |
| finishComplexArg(). |
| expectTextThatContains("xyz"); |
| expect.checkMatches(msg); |
| } |
| |
| @Test |
| public void TestChoiceArg() { |
| MessageNode msg = MessagePatternUtil.buildMessageNode( |
| "a_{0,choice,-∞ #-inf| 5≤ five | 99 # ninety'|'nine }_z"); |
| ExpectMessageNode expect = new ExpectMessageNode(). |
| expectTextThatContains("a_"). |
| expectChoiceArg(0). |
| expectVariant("#", Double.NEGATIVE_INFINITY). |
| expectTextThatContains("-inf").finishVariant(). |
| expectVariant("≤", 5).expectTextThatContains(" five ").finishVariant(). |
| expectVariant("#", 99).expectTextThatContains(" ninety|nine ").finishVariant(). |
| finishComplexArg(). |
| expectTextThatContains("_z"); |
| expect.checkMatches(msg); |
| } |
| |
| @Test |
| public void TestComplexArgs() { |
| MessageNode msg = MessagePatternUtil.buildMessageNode( |
| "I don't {a,plural,other{w'{'on't #'#'}} and "+ |
| "{b,select,other{shan't'}'}} '{'''know'''}' and "+ |
| "{c,choice,0#can't'|'}"+ |
| "{z,number,#'#'###.00'}'}."); |
| ExpectMessageNode expect = new ExpectMessageNode(). |
| expectTextThatContains("I don't "). |
| expectPluralArg("a"). |
| expectVariant("other"). |
| expectTextThatContains("w{on't ").expectReplaceNumber(). |
| expectTextThatContains("#").finishVariant(). |
| finishComplexArg(). |
| expectTextThatContains(" and "). |
| expectSelectArg("b"). |
| expectVariant("other").expectTextThatContains("shan't}").finishVariant(). |
| finishComplexArg(). |
| expectTextThatContains(" {'know'} and "). |
| expectChoiceArg("c"). |
| expectVariant("#", 0).expectTextThatContains("can't|").finishVariant(). |
| finishComplexArg(). |
| expectSimpleArg("z", "number", "#'#'###.00'}'"). |
| expectTextThatContains("."); |
| expect.checkMatches(msg); |
| } |
| |
| /** |
| * @return the text string of the VariantNode's message; |
| * assumes that its message consists of only text |
| */ |
| private String variantText(VariantNode v) { |
| return ((TextNode)v.getMessage().getContents().get(0)).getText(); |
| } |
| |
| @Test |
| public void TestPluralVariantsByType() { |
| MessageNode msg = MessagePatternUtil.buildMessageNode( |
| "{p,plural,a{A}other{O}=4{iv}b{B}other{U}=2{ii}}"); |
| ExpectMessageNode expect = new ExpectMessageNode(). |
| expectPluralArg("p"). |
| expectVariant("a").expectTextThatContains("A").finishVariant(). |
| expectVariant("other").expectTextThatContains("O").finishVariant(). |
| expectVariant("=4", 4).expectTextThatContains("iv").finishVariant(). |
| expectVariant("b").expectTextThatContains("B").finishVariant(). |
| expectVariant("other").expectTextThatContains("U").finishVariant(). |
| expectVariant("=2", 2).expectTextThatContains("ii").finishVariant(). |
| finishComplexArg(); |
| if (!expect.matches(msg)) { |
| return; |
| } |
| List<VariantNode> numericVariants = new ArrayList<VariantNode>(); |
| List<VariantNode> keywordVariants = new ArrayList<VariantNode>(); |
| VariantNode other = |
| ((ArgNode)msg.getContents().get(0)).getComplexStyle(). |
| getVariantsByType(numericVariants, keywordVariants); |
| assertEquals("'other' selector", "other", other.getSelector()); |
| assertEquals("message string of first 'other'", "O", variantText(other)); |
| |
| assertEquals("numericVariants.size()", 2, numericVariants.size()); |
| VariantNode v = numericVariants.get(0); |
| assertEquals("numericVariants[0] selector", "=4", v.getSelector()); |
| assertEquals("numericVariants[0] selector value", 4., v.getSelectorValue()); |
| assertEquals("numericVariants[0] text", "iv", variantText(v)); |
| v = numericVariants.get(1); |
| assertEquals("numericVariants[1] selector", "=2", v.getSelector()); |
| assertEquals("numericVariants[1] selector value", 2., v.getSelectorValue()); |
| assertEquals("numericVariants[1] text", "ii", variantText(v)); |
| |
| assertEquals("keywordVariants.size()", 2, keywordVariants.size()); |
| v = keywordVariants.get(0); |
| assertEquals("keywordVariants[0] selector", "a", v.getSelector()); |
| assertFalse("keywordVariants[0].isSelectorNumeric()", v.isSelectorNumeric()); |
| assertEquals("keywordVariants[0] text", "A", variantText(v)); |
| v = keywordVariants.get(1); |
| assertEquals("keywordVariants[1] selector", "b", v.getSelector()); |
| assertFalse("keywordVariants[1].isSelectorNumeric()", v.isSelectorNumeric()); |
| assertEquals("keywordVariants[1] text", "B", variantText(v)); |
| } |
| |
| @Test |
| public void TestSelectVariantsByType() { |
| MessageNode msg = MessagePatternUtil.buildMessageNode( |
| "{s,select,a{A}other{O}b{B}other{U}}"); |
| ExpectMessageNode expect = new ExpectMessageNode(). |
| expectSelectArg("s"). |
| expectVariant("a").expectTextThatContains("A").finishVariant(). |
| expectVariant("other").expectTextThatContains("O").finishVariant(). |
| expectVariant("b").expectTextThatContains("B").finishVariant(). |
| expectVariant("other").expectTextThatContains("U").finishVariant(). |
| finishComplexArg(); |
| if (!expect.matches(msg)) { |
| return; |
| } |
| // Check that we can use numericVariants = null. |
| List<VariantNode> keywordVariants = new ArrayList<VariantNode>(); |
| VariantNode other = |
| ((ArgNode)msg.getContents().get(0)).getComplexStyle(). |
| getVariantsByType(null, keywordVariants); |
| assertEquals("'other' selector", "other", other.getSelector()); |
| assertEquals("message string of first 'other'", "O", variantText(other)); |
| |
| assertEquals("keywordVariants.size()", 2, keywordVariants.size()); |
| VariantNode v = keywordVariants.get(0); |
| assertEquals("keywordVariants[0] selector", "a", v.getSelector()); |
| assertFalse("keywordVariants[0].isSelectorNumeric()", v.isSelectorNumeric()); |
| assertEquals("keywordVariants[0] text", "A", variantText(v)); |
| v = keywordVariants.get(1); |
| assertEquals("keywordVariants[1] selector", "b", v.getSelector()); |
| assertFalse("keywordVariants[1].isSelectorNumeric()", v.isSelectorNumeric()); |
| assertEquals("keywordVariants[1] text", "B", variantText(v)); |
| } |
| } |