1 // 2016 and later: Unicode, Inc. and others. 2 // License & terms of use: http://www.unicode.org/copyright.html#License 3 /* 4 ******************************************************************************* 5 * Copyright (C) 2011-2012, International Business Machines 6 * Corporation and others. All Rights Reserved. 7 ******************************************************************************* 8 * created on: 2011aug12 9 * created by: Markus W. Scherer 10 */ 11 12 package com.ibm.icu.dev.test.format; 13 14 import java.util.ArrayList; 15 import java.util.Iterator; 16 import java.util.List; 17 import java.util.Locale; 18 19 import org.junit.Test; 20 import org.junit.runner.RunWith; 21 import org.junit.runners.JUnit4; 22 23 import com.ibm.icu.dev.test.TestFmwk; 24 import com.ibm.icu.text.MessagePattern; 25 import com.ibm.icu.text.MessagePatternUtil; 26 import com.ibm.icu.text.MessagePatternUtil.ArgNode; 27 import com.ibm.icu.text.MessagePatternUtil.ComplexArgStyleNode; 28 import com.ibm.icu.text.MessagePatternUtil.MessageContentsNode; 29 import com.ibm.icu.text.MessagePatternUtil.MessageNode; 30 import com.ibm.icu.text.MessagePatternUtil.TextNode; 31 import com.ibm.icu.text.MessagePatternUtil.VariantNode; 32 33 /** 34 * Test MessagePatternUtil (MessagePattern-as-tree-of-nodes API) 35 * by building parallel trees of nodes and verifying that they match. 36 */ 37 @RunWith(JUnit4.class) 38 public final class MessagePatternUtilTest extends TestFmwk { 39 // The following nested "Expect..." classes are used to build 40 // a tree structure parallel to what the MessagePatternUtil class builds. 41 // These nested test classes are not static so that they have access to TestFmwk methods. 42 43 private class ExpectMessageNode { 44 private ExpectMessageNode expectTextThatContains(String s) { 45 contents.add(new ExpectTextNode(s)); 46 return this; 47 } 48 private ExpectMessageNode expectReplaceNumber() { 49 contents.add(new ExpectMessageContentsNode()); 50 return this; 51 } 52 private ExpectMessageNode expectNoneArg(Object name) { 53 contents.add(new ExpectArgNode(name)); 54 return this; 55 } 56 private ExpectMessageNode expectSimpleArg(Object name, String type) { 57 contents.add(new ExpectArgNode(name, type)); 58 return this; 59 } 60 private ExpectMessageNode expectSimpleArg(Object name, String type, String style) { 61 contents.add(new ExpectArgNode(name, type, style)); 62 return this; 63 } 64 private ExpectComplexArgNode expectChoiceArg(Object name) { 65 return expectComplexArg(name, MessagePattern.ArgType.CHOICE); 66 } 67 private ExpectComplexArgNode expectPluralArg(Object name) { 68 return expectComplexArg(name, MessagePattern.ArgType.PLURAL); 69 } 70 private ExpectComplexArgNode expectSelectArg(Object name) { 71 return expectComplexArg(name, MessagePattern.ArgType.SELECT); 72 } 73 private ExpectComplexArgNode expectSelectOrdinalArg(Object name) { 74 return expectComplexArg(name, MessagePattern.ArgType.SELECTORDINAL); 75 } 76 private ExpectComplexArgNode expectComplexArg(Object name, MessagePattern.ArgType argType) { 77 ExpectComplexArgNode complexArg = new ExpectComplexArgNode(this, name, argType); 78 contents.add(complexArg); 79 return complexArg; 80 } 81 private ExpectComplexArgNode finishVariant() { 82 return parent; 83 } 84 private void checkMatches(MessageNode msg) { 85 // matches() prints all errors. 86 matches(msg); 87 } 88 private boolean matches(MessageNode msg) { 89 List<MessageContentsNode> msgContents = msg.getContents(); 90 boolean ok = assertEquals("different numbers of MessageContentsNode", 91 contents.size(), msgContents.size()); 92 if (ok) { 93 Iterator<MessageContentsNode> msgIter = msgContents.iterator(); 94 for (ExpectMessageContentsNode ec : contents) { 95 ok &= ec.matches(msgIter.next()); 96 } 97 } 98 if (!ok) { 99 errln("error in message: " + msg.toString()); 100 } 101 return ok; 102 } 103 private ExpectComplexArgNode parent; // for finishVariant() 104 private List<ExpectMessageContentsNode> contents = 105 new ArrayList<ExpectMessageContentsNode>(); 106 } 107 108 /** 109 * Base class for message contents nodes. 110 * Used directly for REPLACE_NUMBER nodes, subclassed for others. 111 */ 112 private class ExpectMessageContentsNode { 113 protected boolean matches(MessageContentsNode c) { 114 return assertEquals("not a REPLACE_NUMBER node", 115 MessageContentsNode.Type.REPLACE_NUMBER, c.getType()); 116 } 117 } 118 119 private class ExpectTextNode extends ExpectMessageContentsNode { 120 private ExpectTextNode(String subString) { 121 this.subString = subString; 122 } 123 @Override 124 protected boolean matches(MessageContentsNode c) { 125 return 126 assertEquals("not a TextNode", 127 MessageContentsNode.Type.TEXT, c.getType()) && 128 assertTrue("TextNode does not contain \"" + subString + "\"", 129 ((TextNode)c).getText().contains(subString)); 130 } 131 private String subString; 132 } 133 134 private class ExpectArgNode extends ExpectMessageContentsNode { 135 private ExpectArgNode(Object name) { 136 this(name, null, null); 137 } 138 private ExpectArgNode(Object name, String type) { 139 this(name, type, null); 140 } 141 private ExpectArgNode(Object name, String type, String style) { 142 if (name instanceof String) { 143 this.name = (String)name; 144 this.number = -1; 145 } else { 146 this.number = (Integer)name; 147 this.name = Integer.toString(this.number); 148 } 149 if (type == null) { 150 argType = MessagePattern.ArgType.NONE; 151 } else { 152 argType = MessagePattern.ArgType.SIMPLE; 153 } 154 this.type = type; 155 this.style = style; 156 } 157 @Override 158 protected boolean matches(MessageContentsNode c) { 159 boolean ok = 160 assertEquals("not an ArgNode", 161 MessageContentsNode.Type.ARG, c.getType()); 162 if (!ok) { 163 return ok; 164 } 165 ArgNode arg = (ArgNode)c; 166 ok &= assertEquals("unexpected ArgNode argType", 167 argType, arg.getArgType()); 168 ok &= assertEquals("unexpected ArgNode arg name", 169 name, arg.getName()); 170 ok &= assertEquals("unexpected ArgNode arg number", 171 number, arg.getNumber()); 172 ok &= assertEquals("unexpected ArgNode arg type name", 173 type, arg.getTypeName()); 174 ok &= assertEquals("unexpected ArgNode arg style", 175 style, arg.getSimpleStyle()); 176 if (argType == MessagePattern.ArgType.NONE || argType == MessagePattern.ArgType.SIMPLE) { 177 ok &= assertNull("unexpected non-null complex style", arg.getComplexStyle()); 178 } 179 return ok; 180 } 181 private String name; 182 private int number; 183 protected MessagePattern.ArgType argType; 184 private String type; 185 private String style; 186 } 187 188 private class ExpectComplexArgNode extends ExpectArgNode { 189 private ExpectComplexArgNode(ExpectMessageNode parent, 190 Object name, MessagePattern.ArgType argType) { 191 super(name, argType.toString().toLowerCase(Locale.ENGLISH)); 192 this.argType = argType; 193 this.parent = parent; 194 } 195 private ExpectComplexArgNode expectOffset(double offset) { 196 this.offset = offset; 197 explicitOffset = true; 198 return this; 199 } 200 private ExpectMessageNode expectVariant(String selector) { 201 ExpectVariantNode variant = new ExpectVariantNode(this, selector); 202 variants.add(variant); 203 return variant.msg; 204 } 205 private ExpectMessageNode expectVariant(String selector, double value) { 206 ExpectVariantNode variant = new ExpectVariantNode(this, selector, value); 207 variants.add(variant); 208 return variant.msg; 209 } 210 private ExpectMessageNode finishComplexArg() { 211 return parent; 212 } 213 @Override 214 protected boolean matches(MessageContentsNode c) { 215 boolean ok = super.matches(c); 216 if (!ok) { 217 return ok; 218 } 219 ArgNode arg = (ArgNode)c; 220 ComplexArgStyleNode complexStyle = arg.getComplexStyle(); 221 ok &= assertNotNull("unexpected null complex style", complexStyle); 222 if (!ok) { 223 return ok; 224 } 225 ok &= assertEquals("unexpected complex-style argType", 226 argType, complexStyle.getArgType()); 227 ok &= assertEquals("unexpected complex-style hasExplicitOffset()", 228 explicitOffset, complexStyle.hasExplicitOffset()); 229 ok &= assertEquals("unexpected complex-style offset", 230 offset, complexStyle.getOffset()); 231 List<VariantNode> complexVariants = complexStyle.getVariants(); 232 ok &= assertEquals("different number of variants", 233 variants.size(), complexVariants.size()); 234 if (!ok) { 235 return ok; 236 } 237 Iterator<VariantNode> complexIter = complexVariants.iterator(); 238 for (ExpectVariantNode variant : variants) { 239 ok &= variant.matches(complexIter.next()); 240 } 241 return ok; 242 } 243 private ExpectMessageNode parent; // for finishComplexArg() 244 private boolean explicitOffset; 245 private double offset; 246 private List<ExpectVariantNode> variants = new ArrayList<ExpectVariantNode>(); 247 } 248 249 private class ExpectVariantNode { 250 private ExpectVariantNode(ExpectComplexArgNode parent, String selector) { 251 this(parent, selector, MessagePattern.NO_NUMERIC_VALUE); 252 } 253 private ExpectVariantNode(ExpectComplexArgNode parent, String selector, double value) { 254 this.selector = selector; 255 numericValue = value; 256 msg = new ExpectMessageNode(); 257 msg.parent = parent; 258 } 259 private boolean matches(VariantNode v) { 260 boolean ok = assertEquals("different selector strings", 261 selector, v.getSelector()); 262 ok &= assertEquals("different selector strings", 263 isSelectorNumeric(), v.isSelectorNumeric()); 264 ok &= assertEquals("different selector strings", 265 numericValue, v.getSelectorValue()); 266 return ok & msg.matches(v.getMessage()); 267 } 268 private boolean isSelectorNumeric() { 269 return numericValue != MessagePattern.NO_NUMERIC_VALUE; 270 } 271 private String selector; 272 private double numericValue; 273 private ExpectMessageNode msg; 274 } 275 276 // The actual tests start here. ---------------------------------------- *** 277 // Sample message strings are mostly from the MessagePatternUtilDemo. 278 279 @Test 280 public void TestHello() { 281 // No syntax. 282 MessageNode msg = MessagePatternUtil.buildMessageNode("Hello!"); 283 ExpectMessageNode expect = new ExpectMessageNode().expectTextThatContains("Hello"); 284 expect.checkMatches(msg); 285 } 286 287 @Test 288 public void TestHelloWithApos() { 289 // Literal ASCII apostrophe. 290 MessageNode msg = MessagePatternUtil.buildMessageNode("Hel'lo!"); 291 ExpectMessageNode expect = new ExpectMessageNode().expectTextThatContains("Hel'lo"); 292 expect.checkMatches(msg); 293 } 294 295 @Test 296 public void TestHelloWithQuote() { 297 // Apostrophe starts quoted literal text. 298 MessageNode msg = MessagePatternUtil.buildMessageNode("Hel'{o!"); 299 ExpectMessageNode expect = new ExpectMessageNode().expectTextThatContains("Hel{o"); 300 expect.checkMatches(msg); 301 // Terminating the quote should yield the same result. 302 msg = MessagePatternUtil.buildMessageNode("Hel'{'o!"); 303 expect.checkMatches(msg); 304 // Double apostrophe inside quoted literal text still encodes a single apostrophe. 305 msg = MessagePatternUtil.buildMessageNode("a'{bc''de'f"); 306 expect = new ExpectMessageNode().expectTextThatContains("a{bc'def"); 307 expect.checkMatches(msg); 308 } 309 310 @Test 311 public void TestNoneArg() { 312 // Numbered argument. 313 MessageNode msg = MessagePatternUtil.buildMessageNode("abc{0}def"); 314 ExpectMessageNode expect = new ExpectMessageNode(). 315 expectTextThatContains("abc").expectNoneArg(0).expectTextThatContains("def"); 316 expect.checkMatches(msg); 317 // Named argument. 318 msg = MessagePatternUtil.buildMessageNode("abc{ arg }def"); 319 expect = new ExpectMessageNode(). 320 expectTextThatContains("abc").expectNoneArg("arg").expectTextThatContains("def"); 321 expect.checkMatches(msg); 322 // Numbered and named arguments. 323 msg = MessagePatternUtil.buildMessageNode("abc{1}def{arg}ghi"); 324 expect = new ExpectMessageNode(). 325 expectTextThatContains("abc").expectNoneArg(1).expectTextThatContains("def"). 326 expectNoneArg("arg").expectTextThatContains("ghi"); 327 expect.checkMatches(msg); 328 } 329 330 @Test 331 public void TestSimpleArg() { 332 MessageNode msg = MessagePatternUtil.buildMessageNode("a'{bc''de'f{0,number,g'hi''jk'l#}"); 333 ExpectMessageNode expect = new ExpectMessageNode(). 334 expectTextThatContains("a{bc'def").expectSimpleArg(0, "number", "g'hi''jk'l#"); 335 expect.checkMatches(msg); 336 } 337 338 @Test 339 public void TestSelectArg() { 340 MessageNode msg = MessagePatternUtil.buildMessageNode( 341 "abc{2, number}ghi{3, select, xx {xxx} other {ooo}} xyz"); 342 ExpectMessageNode expect = new ExpectMessageNode(). 343 expectTextThatContains("abc").expectSimpleArg(2, "number"). 344 expectTextThatContains("ghi"). 345 expectSelectArg(3). 346 expectVariant("xx").expectTextThatContains("xxx").finishVariant(). 347 expectVariant("other").expectTextThatContains("ooo").finishVariant(). 348 finishComplexArg(). 349 expectTextThatContains(" xyz"); 350 expect.checkMatches(msg); 351 } 352 353 @Test 354 public void TestPluralArg() { 355 // Plural with only keywords. 356 MessageNode msg = MessagePatternUtil.buildMessageNode( 357 "abc{num_people, plural, offset:17 few{fff} other {oooo}}xyz"); 358 ExpectMessageNode expect = new ExpectMessageNode(). 359 expectTextThatContains("abc"). 360 expectPluralArg("num_people"). 361 expectOffset(17). 362 expectVariant("few").expectTextThatContains("fff").finishVariant(). 363 expectVariant("other").expectTextThatContains("oooo").finishVariant(). 364 finishComplexArg(). 365 expectTextThatContains("xyz"); 366 expect.checkMatches(msg); 367 // Plural with explicit-value selectors. 368 msg = MessagePatternUtil.buildMessageNode( 369 "abc{ num , plural , offset: 2 =1 {1} =-1 {-1} =3.14 {3.14} other {oo} }xyz"); 370 expect = new ExpectMessageNode(). 371 expectTextThatContains("abc"). 372 expectPluralArg("num"). 373 expectOffset(2). 374 expectVariant("=1", 1).expectTextThatContains("1").finishVariant(). 375 expectVariant("=-1", -1).expectTextThatContains("-1").finishVariant(). 376 expectVariant("=3.14", 3.14).expectTextThatContains("3.14").finishVariant(). 377 expectVariant("other").expectTextThatContains("oo").finishVariant(). 378 finishComplexArg(). 379 expectTextThatContains("xyz"); 380 expect.checkMatches(msg); 381 // Plural with number replacement. 382 msg = MessagePatternUtil.buildMessageNode( 383 "a_{0,plural,other{num=#'#'=#'#'={1,number,##}!}}_z"); 384 expect = new ExpectMessageNode(). 385 expectTextThatContains("a_"). 386 expectPluralArg(0). 387 expectVariant("other"). 388 expectTextThatContains("num=").expectReplaceNumber(). 389 expectTextThatContains("#=").expectReplaceNumber(). 390 expectTextThatContains("#=").expectSimpleArg(1, "number", "##"). 391 expectTextThatContains("!").finishVariant(). 392 finishComplexArg(). 393 expectTextThatContains("_z"); 394 expect.checkMatches(msg); 395 // Plural with explicit offset:0. 396 msg = MessagePatternUtil.buildMessageNode( 397 "a_{0,plural,offset:0 other{num=#!}}_z"); 398 expect = new ExpectMessageNode(). 399 expectTextThatContains("a_"). 400 expectPluralArg(0). 401 expectOffset(0). 402 expectVariant("other"). 403 expectTextThatContains("num=").expectReplaceNumber(). 404 expectTextThatContains("!").finishVariant(). 405 finishComplexArg(). 406 expectTextThatContains("_z"); 407 expect.checkMatches(msg); 408 } 409 410 411 @Test 412 public void TestSelectOrdinalArg() { 413 MessageNode msg = MessagePatternUtil.buildMessageNode( 414 "abc{num, selectordinal, offset:17 =0{null} few{fff} other {oooo}}xyz"); 415 ExpectMessageNode expect = new ExpectMessageNode(). 416 expectTextThatContains("abc"). 417 expectSelectOrdinalArg("num"). 418 expectOffset(17). 419 expectVariant("=0", 0).expectTextThatContains("null").finishVariant(). 420 expectVariant("few").expectTextThatContains("fff").finishVariant(). 421 expectVariant("other").expectTextThatContains("oooo").finishVariant(). 422 finishComplexArg(). 423 expectTextThatContains("xyz"); 424 expect.checkMatches(msg); 425 } 426 427 @Test 428 public void TestChoiceArg() { 429 MessageNode msg = MessagePatternUtil.buildMessageNode( 430 "a_{0,choice,- #-inf| 5 five | 99 # ninety'|'nine }_z"); 431 ExpectMessageNode expect = new ExpectMessageNode(). 432 expectTextThatContains("a_"). 433 expectChoiceArg(0). 434 expectVariant("#", Double.NEGATIVE_INFINITY). 435 expectTextThatContains("-inf").finishVariant(). 436 expectVariant("", 5).expectTextThatContains(" five ").finishVariant(). 437 expectVariant("#", 99).expectTextThatContains(" ninety|nine ").finishVariant(). 438 finishComplexArg(). 439 expectTextThatContains("_z"); 440 expect.checkMatches(msg); 441 } 442 443 @Test 444 public void TestComplexArgs() { 445 MessageNode msg = MessagePatternUtil.buildMessageNode( 446 "I don't {a,plural,other{w'{'on't #'#'}} and "+ 447 "{b,select,other{shan't'}'}} '{'''know'''}' and "+ 448 "{c,choice,0#can't'|'}"+ 449 "{z,number,#'#'###.00'}'}."); 450 ExpectMessageNode expect = new ExpectMessageNode(). 451 expectTextThatContains("I don't "). 452 expectPluralArg("a"). 453 expectVariant("other"). 454 expectTextThatContains("w{on't ").expectReplaceNumber(). 455 expectTextThatContains("#").finishVariant(). 456 finishComplexArg(). 457 expectTextThatContains(" and "). 458 expectSelectArg("b"). 459 expectVariant("other").expectTextThatContains("shan't}").finishVariant(). 460 finishComplexArg(). 461 expectTextThatContains(" {'know'} and "). 462 expectChoiceArg("c"). 463 expectVariant("#", 0).expectTextThatContains("can't|").finishVariant(). 464 finishComplexArg(). 465 expectSimpleArg("z", "number", "#'#'###.00'}'"). 466 expectTextThatContains("."); 467 expect.checkMatches(msg); 468 } 469 470 /** 471 * @return the text string of the VariantNode's message; 472 * assumes that its message consists of only text 473 */ 474 private String variantText(VariantNode v) { 475 return ((TextNode)v.getMessage().getContents().get(0)).getText(); 476 } 477 478 @Test 479 public void TestPluralVariantsByType() { 480 MessageNode msg = MessagePatternUtil.buildMessageNode( 481 "{p,plural,a{A}other{O}=4{iv}b{B}other{U}=2{ii}}"); 482 ExpectMessageNode expect = new ExpectMessageNode(). 483 expectPluralArg("p"). 484 expectVariant("a").expectTextThatContains("A").finishVariant(). 485 expectVariant("other").expectTextThatContains("O").finishVariant(). 486 expectVariant("=4", 4).expectTextThatContains("iv").finishVariant(). 487 expectVariant("b").expectTextThatContains("B").finishVariant(). 488 expectVariant("other").expectTextThatContains("U").finishVariant(). 489 expectVariant("=2", 2).expectTextThatContains("ii").finishVariant(). 490 finishComplexArg(); 491 if (!expect.matches(msg)) { 492 return; 493 } 494 List<VariantNode> numericVariants = new ArrayList<VariantNode>(); 495 List<VariantNode> keywordVariants = new ArrayList<VariantNode>(); 496 VariantNode other = 497 ((ArgNode)msg.getContents().get(0)).getComplexStyle(). 498 getVariantsByType(numericVariants, keywordVariants); 499 assertEquals("'other' selector", "other", other.getSelector()); 500 assertEquals("message string of first 'other'", "O", variantText(other)); 501 502 assertEquals("numericVariants.size()", 2, numericVariants.size()); 503 VariantNode v = numericVariants.get(0); 504 assertEquals("numericVariants[0] selector", "=4", v.getSelector()); 505 assertEquals("numericVariants[0] selector value", 4., v.getSelectorValue()); 506 assertEquals("numericVariants[0] text", "iv", variantText(v)); 507 v = numericVariants.get(1); 508 assertEquals("numericVariants[1] selector", "=2", v.getSelector()); 509 assertEquals("numericVariants[1] selector value", 2., v.getSelectorValue()); 510 assertEquals("numericVariants[1] text", "ii", variantText(v)); 511 512 assertEquals("keywordVariants.size()", 2, keywordVariants.size()); 513 v = keywordVariants.get(0); 514 assertEquals("keywordVariants[0] selector", "a", v.getSelector()); 515 assertFalse("keywordVariants[0].isSelectorNumeric()", v.isSelectorNumeric()); 516 assertEquals("keywordVariants[0] text", "A", variantText(v)); 517 v = keywordVariants.get(1); 518 assertEquals("keywordVariants[1] selector", "b", v.getSelector()); 519 assertFalse("keywordVariants[1].isSelectorNumeric()", v.isSelectorNumeric()); 520 assertEquals("keywordVariants[1] text", "B", variantText(v)); 521 } 522 523 @Test 524 public void TestSelectVariantsByType() { 525 MessageNode msg = MessagePatternUtil.buildMessageNode( 526 "{s,select,a{A}other{O}b{B}other{U}}"); 527 ExpectMessageNode expect = new ExpectMessageNode(). 528 expectSelectArg("s"). 529 expectVariant("a").expectTextThatContains("A").finishVariant(). 530 expectVariant("other").expectTextThatContains("O").finishVariant(). 531 expectVariant("b").expectTextThatContains("B").finishVariant(). 532 expectVariant("other").expectTextThatContains("U").finishVariant(). 533 finishComplexArg(); 534 if (!expect.matches(msg)) { 535 return; 536 } 537 // Check that we can use numericVariants = null. 538 List<VariantNode> keywordVariants = new ArrayList<VariantNode>(); 539 VariantNode other = 540 ((ArgNode)msg.getContents().get(0)).getComplexStyle(). 541 getVariantsByType(null, keywordVariants); 542 assertEquals("'other' selector", "other", other.getSelector()); 543 assertEquals("message string of first 'other'", "O", variantText(other)); 544 545 assertEquals("keywordVariants.size()", 2, keywordVariants.size()); 546 VariantNode v = keywordVariants.get(0); 547 assertEquals("keywordVariants[0] selector", "a", v.getSelector()); 548 assertFalse("keywordVariants[0].isSelectorNumeric()", v.isSelectorNumeric()); 549 assertEquals("keywordVariants[0] text", "A", variantText(v)); 550 v = keywordVariants.get(1); 551 assertEquals("keywordVariants[1] selector", "b", v.getSelector()); 552 assertFalse("keywordVariants[1].isSelectorNumeric()", v.isSelectorNumeric()); 553 assertEquals("keywordVariants[1] text", "B", variantText(v)); 554 } 555 } 556