From: Markus Scherer Date: Mon, 15 Aug 2011 21:09:39 +0000 (+0000) Subject: ICU-8745 merge MessagePatternUtil into trunk, from merge --reintegrate branches/marku... X-Git-Tag: milestone-59-0-1~4582 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=69c0605df2aca4007d36786e521f73fbee4b68d1;p=icu ICU-8745 merge MessagePatternUtil into trunk, from merge --reintegrate branches/markus/msgnodes from r30506 X-SVN-Rev: 30510 --- diff --git a/icu4j/demos/src/com/ibm/icu/dev/demo/messagepattern/MessagePatternUtilDemo.java b/icu4j/demos/src/com/ibm/icu/dev/demo/messagepattern/MessagePatternUtilDemo.java new file mode 100644 index 00000000000..47d41b49cfc --- /dev/null +++ b/icu4j/demos/src/com/ibm/icu/dev/demo/messagepattern/MessagePatternUtilDemo.java @@ -0,0 +1,403 @@ +/* +******************************************************************************* +* Copyright (C) 2011, International Business Machines +* Corporation and others. All Rights Reserved. +******************************************************************************* +* created on: 2011jul14 +* created by: Markus W. Scherer +*/ + +package com.ibm.icu.dev.demo.messagepattern; + +import com.ibm.icu.text.MessagePattern; +import com.ibm.icu.text.MessagePatternUtil; + +import java.util.ArrayList; +import java.util.List; + +/** + * Demo code for MessagePattern class. + * @author Markus Scherer + * @since 2011-jul-14 + */ +public class MessagePatternUtilDemo { + private static final String manySpaces=" "; + + private static final void printMessage(MessagePatternUtil.MessageNode msg, int depth) { + String indent = manySpaces.substring(0, depth * 2); + for (MessagePatternUtil.MessageContentsNode contents : msg.getContents()) { + switch (contents.getType()) { + case TEXT: + System.out.println(indent + "text: «" + + ((MessagePatternUtil.TextNode)contents).getText() + "»"); + break; + case ARG: + printArg((MessagePatternUtil.ArgNode)contents, depth); + break; + case REPLACE_NUMBER: + System.out.println(indent + "replace: number"); + break; + } + } + } + + private static final void printArg(MessagePatternUtil.ArgNode arg, int depth) { + System.out.print(manySpaces.substring(0, depth * 2) + "arg: «" + arg.getName() + "»"); + MessagePattern.ArgType argType = arg.getArgType(); + if (argType == MessagePattern.ArgType.NONE) { + System.out.println(" (no type)"); + } else { + System.out.print(" (" + arg.getTypeName() + ")"); + if (argType == MessagePattern.ArgType.SIMPLE) { + String styleString = arg.getSimpleStyle(); + if (styleString == null) { + System.out.println(" (no style)"); + } else { + System.out.println(" style: «" + styleString + "»"); + } + } else { + System.out.println(); + printComplexArgStyle(arg.getComplexStyle(), depth + 1); + } + } + } + + private static final void printComplexArgStyle(MessagePatternUtil.ComplexArgStyleNode style, + int depth) { + if (style.hasExplicitOffset()) { + System.out.println(manySpaces.substring(0, depth * 2) + "offset: " + style.getOffset()); + } + String indent = manySpaces.substring(0, depth * 2); + MessagePattern.ArgType argType = style.getArgType(); + for (MessagePatternUtil.VariantNode variant : style.getVariants()) { + double value; + switch (argType) { + case CHOICE: + System.out.println(indent + variant.getSelectorValue() + " " + variant.getSelector() + ":"); + break; + case PLURAL: + value = variant.getSelectorValue(); + if (value == MessagePattern.NO_NUMERIC_VALUE) { + System.out.println(indent + variant.getSelector() + ":"); + } else { + System.out.println(indent + variant.getSelector() + " (" + value + "):"); + } + break; + case SELECT: + System.out.println(indent + variant.getSelector() + ":"); + break; + } + printMessage(variant.getMessage(), depth + 1); + } + } + + /** + * This is a prototype/demo/sample for how we could use the MessagePatternUtil class + * for generating something like JavaScript code for evaluating some + * of the MessageFormat syntax. + * + *

This is not intended to be production code, nor to generate production code + * or even syntactically correct JavaScript. + * @param msg + */ + private static final void genCode(MessagePatternUtil.MessageNode msg) { + List args = new ArrayList(); + addArgs(msg, args); + System.out.print("def function("); + boolean firstArg = true; + for (String argName : args) { + if (firstArg) { + System.out.print(argName); + firstArg = false; + } else { + System.out.print(", " + argName); + } + } + System.out.println(") {"); + genCode(msg, 1, true, ""); + System.out.println(" return result"); + System.out.println("}"); + } + + private static final void genCode(MessagePatternUtil.MessageNode msg, + int depth, + boolean firstResult, + String pluralNumber) { + String prefix = manySpaces.substring(0, depth * 2) + "result "; + for (MessagePatternUtil.MessageContentsNode contents : msg.getContents()) { + String operator = firstResult ? "=" : "+="; + switch (contents.getType()) { + case TEXT: + System.out.println( + prefix + operator + " \"" + + escapeString(((MessagePatternUtil.TextNode)contents).getText()) + + "\""); + break; + case ARG: + genCode((MessagePatternUtil.ArgNode)contents, depth, firstResult); + break; + case REPLACE_NUMBER: + System.out.println(prefix + operator + " formatNumber(" + pluralNumber + ")"); + break; + } + firstResult = false; + } + } + + private static final void genCode(MessagePatternUtil.ArgNode arg, + int depth, + boolean firstResult) { + String prefix = manySpaces.substring(0, depth * 2) + "result "; + String operator = firstResult ? "=" : "+="; + String argName = arg.getName(); + if (arg.getNumber() >= 0) { + argName = "arg_" + argName; // Prefix for numbered argument. + } + switch (arg.getArgType()) { + case NONE: + System.out.println(prefix + operator + " " + argName); + break; + case SIMPLE: + case CHOICE: + System.out.println(prefix + operator + " \"(unsupported syntax)\""); + break; + case PLURAL: + genCodeForPlural(arg.getComplexStyle(), depth, firstResult, argName); + break; + case SELECT: + genCodeForSelect(arg.getComplexStyle(), depth, firstResult, argName); + break; + } + } + + private static final void genCodeForPlural(MessagePatternUtil.ComplexArgStyleNode style, + int depth, + boolean firstResult, + String argName) { + List numericVariants = + new ArrayList(); + List keywordVariants = + new ArrayList(); + MessagePatternUtil.VariantNode otherVariant = + style.getVariantsByType(numericVariants, keywordVariants); + double offset = style.getOffset(); + String pluralNumber = offset == 0. ? argName : argName + " - " + offset; + int origDepth = depth; + if (!numericVariants.isEmpty()) { + genCodeForNumericVariants(numericVariants, depth++, firstResult, argName, pluralNumber); + } + if (!keywordVariants.isEmpty()) { + System.out.println(manySpaces.substring(0, depth * 2) + + "_keyword = PluralRules.select(" + pluralNumber + ")"); + genCodeForKeywordVariants(keywordVariants, depth++, firstResult, "_keyword", pluralNumber); + } + genCode(otherVariant.getMessage(), depth, firstResult, pluralNumber); + if (origDepth < depth) { + System.out.println(manySpaces.substring(0, --depth * 2) + "}"); + if (origDepth < depth) { + System.out.println(manySpaces.substring(0, --depth * 2) + "}"); + } + } + } + + private static final void genCodeForSelect(MessagePatternUtil.ComplexArgStyleNode style, + int depth, + boolean firstResult, + String argName) { + List keywordVariants = + new ArrayList(); + MessagePatternUtil.VariantNode otherVariant = style.getVariantsByType(null, keywordVariants); + if (keywordVariants.isEmpty()) { + genCode(otherVariant.getMessage(), depth, firstResult, ""); + } else { + genCodeForKeywordVariants(keywordVariants, depth, firstResult, argName, ""); + genCode(otherVariant.getMessage(), depth + 1, firstResult, ""); + System.out.println(manySpaces.substring(0, depth * 2) + "}"); + } + } + + private static final void genCodeForNumericVariants(List variants, + int depth, + boolean firstResult, + String varName, + String pluralNumber) { + String indent = manySpaces.substring(0, depth++ * 2); + boolean firstVariant = true; + for (MessagePatternUtil.VariantNode variant : variants) { + System.out.println( + indent + + (firstVariant ? "if (" : "} else if (") + + varName + " == " + variant.getSelectorValue() + ") {"); + genCode(variant.getMessage(), depth, firstResult, pluralNumber); + firstVariant = false; + } + System.out.println(indent + "} else {"); + } + + private static final void genCodeForKeywordVariants(List variants, + int depth, + boolean firstResult, + String varName, + String pluralNumber) { + String indent = manySpaces.substring(0, depth++ * 2); + boolean firstVariant = true; + for (MessagePatternUtil.VariantNode variant : variants) { + System.out.println( + indent + + (firstVariant ? "if (" : "} else if (") + + varName + " == \"" + variant.getSelector() + "\") {"); + genCode(variant.getMessage(), depth, firstResult, pluralNumber); + firstVariant = false; + } + System.out.println(indent + "} else {"); + } + + /** + * Adds the message's argument names to the args list. + * Adds each argument only once, in the order of first appearance. + * Numbered arguments get an "arg_" prefix prepended. + * @param msg + * @param args + */ + private static final void addArgs(MessagePatternUtil.MessageNode msg, List args) { + for (MessagePatternUtil.MessageContentsNode contents : msg.getContents()) { + if (contents.getType() == MessagePatternUtil.MessageContentsNode.Type.ARG) { + MessagePatternUtil.ArgNode arg = (MessagePatternUtil.ArgNode)contents; + String argName; + if (arg.getNumber() >= 0) { + argName = "arg_" + arg.getNumber(); // Prefix for numbered argument. + } else { + argName = arg.getName(); + } + if (!args.contains(argName)) { + args.add(argName); + } + MessagePatternUtil.ComplexArgStyleNode complexStyle = arg.getComplexStyle(); + if (complexStyle != null) { + for (MessagePatternUtil.VariantNode variant : complexStyle.getVariants()) { + addArgs(variant.getMessage(), args); + } + } + } + } + } + + private static final String escapeString(String s) { + if (s.indexOf('"') < 0) { + return s; + } else { + return s.replace("\"", "\\\""); + } + } + + private static final MessagePatternUtil.MessageNode print(String s) { + System.out.println("message: «" + s + "»"); + try { + MessagePatternUtil.MessageNode msg = MessagePatternUtil.buildMessageNode(s); + printMessage(msg, 1); + genCode(msg); + return msg; + } catch(Exception e) { + System.out.println("Exception: "+e.getMessage()); + return null; + } + } + + public static void main(String[] argv) { + print("Hello!"); + print("Hel'lo!"); + print("Hel'{o"); + print("Hel'{'o"); + // double apostrophe inside quoted literal text still encodes a single apostrophe + print("a'{bc''de'f"); + print("a'{bc''de'f{0,number,g'hi''jk'l#}"); + print("abc{0}def"); + print("abc{ arg }def"); + print("abc{1}def{arg}ghi"); + print("abc{2, number}ghi{3, select, xx {xxx} other {ooo}} xyz"); + print("abc{gender,select,"+ + "other{His name is {tc,XMB,{$PERSON}}.}}xyz"); + print("abc{num_people, plural, offset:17 few{fff} other {oooo}}xyz"); + print("abc{ num , plural , offset: 2 =1 {1} =-1 {-1} =3.14 {3.14} other {oo} }xyz"); + print("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'}'}."); + print("a_{0,choice,-∞ #-inf| 5≤ five | 99 # ninety'|'nine }_z"); + print("a_{0,plural,other{num=#'#'=#'#'={1,number,##}!}}_z"); + print("}}}{0}}"); // yes, unmatched '}' are ok in ICU MessageFormat + print("Hello {0}!"); + String msg="++{0, select, female{{1} calls you her friend}"+ + "other{{1} calls you '{their}' friend}"+ + "male{{1} calls you his friend}}--"; + print(msg); + msg="_'__{gender, select, female{Her n'ame is {person_name}.}"+ + "other{His n'ame is {person_name}.}}__'_"; + print(msg); + print("{num,plural,offset:1 =0{no one} =1{one, that is one and # others} " + + "one{one and # (probably 1) others} few{one and # others} " + + "other{lots & lots}}"); + print( + "{p1_gender,select," + + "female{" + + "{p2_gender,select," + + "female{" + + "{num_people,plural,offset:1 "+ + "=0{she alone}" + + "=1{she and her girlfriend {p2}}" + + "=2{she and her girlfriend {p2} and another}" + + "other{she, her girlfriend {p2} and # others}}}" + + "male{" + + "{num_people,plural,offset:1 "+ + "=0{she alone}" + + "=1{she and her boyfriend {p2}}" + + "=2{she and her boyfriend {p2} and another}" + + "other{she, her boyfriend {p2} and # others}}}" + + "other{" + + "{num_people,plural,offset:1 "+ + "=0{she alone}" + + "=1{she and her friend {p2}}" + + "=2{she and her friend {p2} and another}" + + "other{she, her friend {p2} and # others}}}}}" + + "male{" + + "{p2_gender,select," + + "female{" + + "{num_people,plural,offset:1 "+ + "=0{he alone}" + + "=1{he and his girlfriend {p2}}" + + "=2{he and his girlfriend {p2} and another}" + + "other{he, his girlfriend {p2} and # others}}}" + + "male{" + + "{num_people,plural,offset:1 "+ + "=0{he alone}" + + "=1{he and his boyfriend {p2}}" + + "=2{he and his boyfriend {p2} and another}" + + "other{he, his boyfriend {p2} and # others}}}" + + "other{" + + "{num_people,plural,offset:1 "+ + "=0{she alone}" + + "=1{she and his friend {p2}}" + + "=2{she and his friend {p2} and another}" + + "other{she, his friend {p2} and # others}}}}}" + + "other{" + + "{p2_gender,select," + + "female{" + + "{num_people,plural,offset:1 "+ + "=0{they alone}" + + "=1{they and their girlfriend {p2}}" + + "=2{they and their girlfriend {p2} and another}" + + "other{they, their girlfriend {p2} and # others}}}" + + "male{" + + "{num_people,plural,offset:1 "+ + "=0{they alone}" + + "=1{they and their boyfriend {p2}}" + + "=2{they and their boyfriend {p2} and another}" + + "other{they, their boyfriend {p2} and # others}}}" + + "other{" + + "{num_people,plural,offset:1 "+ + "=0{they alone}" + + "=1{they and their friend {p2}}" + + "=2{they and their friend {p2} and another}" + + "other{they, their friend {p2} and # others}}}}}}"); + } +} diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/MessagePatternUtil.java b/icu4j/main/classes/core/src/com/ibm/icu/text/MessagePatternUtil.java new file mode 100644 index 00000000000..880f8264ae0 --- /dev/null +++ b/icu4j/main/classes/core/src/com/ibm/icu/text/MessagePatternUtil.java @@ -0,0 +1,613 @@ +/* +******************************************************************************* +* Copyright (C) 2011, International Business Machines +* Corporation and others. All Rights Reserved. +******************************************************************************* +* created on: 2011jul14 +* created by: Markus W. Scherer +*/ + +package com.ibm.icu.text; + +import com.ibm.icu.text.MessagePattern; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Utilities for working with a MessagePattern. + * Intended for use in tools when convenience is more important than + * minimizing runtime and object creations. + * + *

This class and its nested classes are not intended for public subclassing. + * @draft ICU 49 + * @provisional This API might change or be removed in a future release. + * @author Markus Scherer + */ +public final class MessagePatternUtil { + /** + * Factory method, builds and returns a MessageNode from a MessageFormat pattern string. + * @param patternString a MessageFormat pattern string + * @return a MessageNode or a ComplexArgStyleNode + * @throws IllegalArgumentException if the MessagePattern is empty + * or does not represent a MessageFormat pattern + * @draft ICU 49 + * @provisional This API might change or be removed in a future release. + */ + public static MessageNode buildMessageNode(String patternString) { + return buildMessageNode(new MessagePattern(patternString)); + } + + /** + * Factory method, builds and returns a MessageNode from a MessagePattern. + * @param pattern a parsed MessageFormat pattern string + * @return a MessageNode or a ComplexArgStyleNode + * @throws IllegalArgumentException if the MessagePattern is empty + * or does not represent a MessageFormat pattern + * @draft ICU 49 + * @provisional This API might change or be removed in a future release. + */ + public static MessageNode buildMessageNode(MessagePattern pattern) { + int limit = pattern.countParts() - 1; + if (limit < 0) { + throw new IllegalArgumentException("The MessagePattern is empty"); + } else if (pattern.getPartType(0) != MessagePattern.Part.Type.MSG_START) { + throw new IllegalArgumentException( + "The MessagePattern does not represent a MessageFormat pattern"); + } + return buildMessageNode(pattern, 0, limit); + } + + /** + * Common base class for all elements in a tree of nodes + * returned by {@link MessagePatternUtil#buildMessageNode(MessagePattern)}. + * @draft ICU 49 + * @provisional This API might change or be removed in a future release. + */ + public static class Node { + private Node() {} + } + + /** + * A Node representing a parsed MessageFormat pattern string. + * @draft ICU 49 + * @provisional This API might change or be removed in a future release. + */ + public static class MessageNode extends Node { + /** + * @return the list of MessageContentsNode nodes that this message contains + * @draft ICU 49 + * @provisional This API might change or be removed in a future release. + */ + public List getContents() { + return list; + } + /** + * {@inheritDoc} + * @draft ICU 49 + * @provisional This API might change or be removed in a future release. + */ + @Override + public String toString() { + return list.toString(); + } + + private MessageNode() { + super(); + } + private void addContentsNode(MessageContentsNode node) { + if (node instanceof TextNode && !list.isEmpty()) { + // Coalesce adjacent text nodes. + MessageContentsNode lastNode = list.get(list.size() - 1); + if (lastNode instanceof TextNode) { + TextNode textNode = (TextNode)lastNode; + textNode.text = textNode.text + ((TextNode)node).text; + return; + } + } + list.add(node); + } + private MessageNode freeze() { + list = Collections.unmodifiableList(list); + return this; + } + + private List list = new ArrayList(); + } + + /** + * A piece of MessageNode contents. + * Use getType() to determine the type and the actual Node subclass. + * @draft ICU 49 + * @provisional This API might change or be removed in a future release. + */ + public static class MessageContentsNode extends Node { + /** + * The type of a piece of MessageNode contents. + * @draft ICU 49 + * @provisional This API might change or be removed in a future release. + */ + public enum Type { + /** + * This is a TextNode containing literal text (downcast and call getText()). + * @draft ICU 49 + * @provisional This API might change or be removed in a future release. + */ + TEXT, + /** + * This is an ArgNode representing a message argument + * (downcast and use specific methods). + * @draft ICU 49 + * @provisional This API might change or be removed in a future release. + */ + ARG, + /** + * This Node represents a place in a plural argument's variant where + * the formatted (plural-offset) value is to be put. + * @draft ICU 49 + * @provisional This API might change or be removed in a future release. + */ + REPLACE_NUMBER + } + /** + * Returns the type of this piece of MessageNode contents. + * @draft ICU 49 + * @provisional This API might change or be removed in a future release. + */ + public Type getType() { + return type; + } + /** + * {@inheritDoc} + * @draft ICU 49 + * @provisional This API might change or be removed in a future release. + */ + @Override + public String toString() { + return "{REPLACE_NUMBER}"; + } + + private MessageContentsNode(Type type) { + super(); + this.type = type; + } + private static MessageContentsNode createReplaceNumberNode() { + return new MessageContentsNode(Type.REPLACE_NUMBER); + } + + private Type type; + } + + /** + * Literal text, a piece of MessageNode contents. + * @draft ICU 49 + * @provisional This API might change or be removed in a future release. + */ + public static class TextNode extends MessageContentsNode { + /** + * @return the literal text at this point in the message + * @draft ICU 49 + * @provisional This API might change or be removed in a future release. + */ + public String getText() { + return text; + } + /** + * {@inheritDoc} + * @draft ICU 49 + * @provisional This API might change or be removed in a future release. + */ + @Override + public String toString() { + return "«" + text + "»"; + } + + private TextNode(String text) { + super(Type.TEXT); + this.text = text; + } + + private String text; + } + + /** + * A piece of MessageNode contents representing a message argument and its details. + * @draft ICU 49 + * @provisional This API might change or be removed in a future release. + */ + public static class ArgNode extends MessageContentsNode { + /** + * @return the argument type + * @draft ICU 49 + * @provisional This API might change or be removed in a future release. + */ + public MessagePattern.ArgType getArgType() { + return argType; + } + /** + * @return the argument name string (the decimal-digit string if the argument has a number) + * @draft ICU 49 + * @provisional This API might change or be removed in a future release. + */ + public String getName() { + return name; + } + /** + * @return the argument number, or -1 if none (for a named argument) + * @draft ICU 49 + * @provisional This API might change or be removed in a future release. + */ + public int getNumber() { + return number; + } + /** + * @return the argument type string, or null if none was specified + * @draft ICU 49 + * @provisional This API might change or be removed in a future release. + */ + public String getTypeName() { + return typeName; + } + /** + * @return the simple-argument style string, + * or null if no style is specified and for other argument types + * @draft ICU 49 + * @provisional This API might change or be removed in a future release. + */ + public String getSimpleStyle() { + return style; + } + /** + * @return the complex-argument-style object, + * or null if the argument type is NONE_ARG or SIMPLE_ARG + * @draft ICU 49 + * @provisional This API might change or be removed in a future release. + */ + public ComplexArgStyleNode getComplexStyle() { + return complexStyle; + } + /** + * {@inheritDoc} + * @draft ICU 49 + * @provisional This API might change or be removed in a future release. + */ + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append('{').append(name); + if (argType != MessagePattern.ArgType.NONE) { + sb.append(',').append(typeName); + if (argType == MessagePattern.ArgType.SIMPLE) { + if (style != null) { + sb.append(',').append(style); + } + } else { + sb.append(',').append(complexStyle.toString()); + } + } + return sb.append('}').toString(); + } + + private ArgNode() { + super(Type.ARG); + } + private static ArgNode createArgNode() { + return new ArgNode(); + } + + private MessagePattern.ArgType argType; + private String name; + private int number = -1; + private String typeName; + private String style; + private ComplexArgStyleNode complexStyle; + } + + /** + * A Node representing details of the argument style of a complex argument. + * (Which is a choice/plural/select argument which selects among nested messages.) + * @draft ICU 49 + * @provisional This API might change or be removed in a future release. + */ + public static class ComplexArgStyleNode extends Node { + /** + * @return the argument type (same as getArgType() on the parent ArgNode) + * @draft ICU 49 + * @provisional This API might change or be removed in a future release. + */ + public MessagePattern.ArgType getArgType() { + return argType; + } + /** + * @return true if this is a plural style with an explicit offset + * @draft ICU 49 + * @provisional This API might change or be removed in a future release. + */ + public boolean hasExplicitOffset() { + return explicitOffset; + } + /** + * @return the plural offset, or 0 if this is not a plural style or + * the offset is explicitly or implicitly 0 + * @draft ICU 49 + * @provisional This API might change or be removed in a future release. + */ + public double getOffset() { + return offset; + } + /** + * @return the list of variants: the nested messages with their selection criteria + * @draft ICU 49 + * @provisional This API might change or be removed in a future release. + */ + public List getVariants() { + return list; + } + /** + * Separates the variants by type. + * Intended for use with plural and select argument styles, + * not useful for choice argument styles. + * + *

Both parameters are used only for output, and are first cleared. + * @param numericVariants Variants with numeric-value selectors (if any) are added here. + * Can be null for a select argument style. + * @param keywordVariants Variants with keyword selectors, except "other", are added here. + * For a plural argument, if this list is empty after the call, then + * all variants except "other" have explicit values + * and PluralRules need not be called. + * @return the "other" variant (the first one if there are several), + * null if none (choice style) + * @draft ICU 49 + * @provisional This API might change or be removed in a future release. + */ + public VariantNode getVariantsByType(List numericVariants, + List keywordVariants) { + if (numericVariants != null) { + numericVariants.clear(); + } + keywordVariants.clear(); + VariantNode other = null; + for (VariantNode variant : list) { + if (variant.isSelectorNumeric()) { + numericVariants.add(variant); + } else if ("other".equals(variant.getSelector())) { + if (other == null) { + // Return the first "other" variant. (MessagePattern allows duplicates.) + other = variant; + } + } else { + keywordVariants.add(variant); + } + } + return other; + } + /** + * {@inheritDoc} + * @draft ICU 49 + * @provisional This API might change or be removed in a future release. + */ + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append('(').append(argType.toString()).append(" style) "); + if (hasExplicitOffset()) { + sb.append("offset:").append(offset).append(' '); + } + return sb.append(list.toString()).toString(); + } + + private ComplexArgStyleNode(MessagePattern.ArgType argType) { + super(); + this.argType = argType; + } + private void addVariant(VariantNode variant) { + list.add(variant); + } + private ComplexArgStyleNode freeze() { + list = Collections.unmodifiableList(list); + return this; + } + + private MessagePattern.ArgType argType; + private double offset; + private boolean explicitOffset; + private List list = new ArrayList(); + } + + /** + * A Node representing a nested message (nested inside an argument) + * with its selection criterium. + * @draft ICU 49 + * @provisional This API might change or be removed in a future release. + */ + public static class VariantNode extends Node { + /** + * Returns the selector string. + * For example: A plural/select keyword ("few"), a plural explicit value ("=1"), + * a choice comparison operator ("#"). + * @return the selector string + * @draft ICU 49 + * @provisional This API might change or be removed in a future release. + */ + public String getSelector() { + return selector; + } + /** + * @return true for choice variants and for plural explicit values + * @draft ICU 49 + * @provisional This API might change or be removed in a future release. + */ + public boolean isSelectorNumeric() { + return numericValue != MessagePattern.NO_NUMERIC_VALUE; + } + /** + * @return the selector's numeric value, or NO_NUMERIC_VALUE if !isSelectorNumeric() + * @draft ICU 49 + * @provisional This API might change or be removed in a future release. + */ + public double getSelectorValue() { + return numericValue; + } + /** + * @return the nested message + * @draft ICU 49 + * @provisional This API might change or be removed in a future release. + */ + public MessageNode getMessage() { + return msgNode; + } + /** + * {@inheritDoc} + * @draft ICU 49 + * @provisional This API might change or be removed in a future release. + */ + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + if (isSelectorNumeric()) { + sb.append(numericValue).append(" (").append(selector).append(") {"); + } else { + sb.append(selector).append(" {"); + } + return sb.append(msgNode.toString()).append('}').toString(); + } + + private VariantNode() { + super(); + } + + private String selector; + private double numericValue = MessagePattern.NO_NUMERIC_VALUE; + private MessageNode msgNode; + } + + private static MessageNode buildMessageNode(MessagePattern pattern, int start, int limit) { + int prevPatternIndex = pattern.getPart(start).getLimit(); + MessageNode node = new MessageNode(); + for (int i = start + 1;; ++i) { + MessagePattern.Part part = pattern.getPart(i); + int patternIndex = part.getIndex(); + if (prevPatternIndex < patternIndex) { + node.addContentsNode( + new TextNode(pattern.getPatternString().substring(prevPatternIndex, + patternIndex))); + } + if (i == limit) { + break; + } + MessagePattern.Part.Type partType = part.getType(); + if (partType == MessagePattern.Part.Type.ARG_START) { + int argLimit = pattern.getLimitPartIndex(i); + node.addContentsNode(buildArgNode(pattern, i, argLimit)); + i = argLimit; + part = pattern.getPart(i); + } else if (partType == MessagePattern.Part.Type.REPLACE_NUMBER) { + node.addContentsNode(MessageContentsNode.createReplaceNumberNode()); + // else: ignore SKIP_SYNTAX and INSERT_CHAR parts. + } + prevPatternIndex = part.getLimit(); + } + return node.freeze(); + } + + private static ArgNode buildArgNode(MessagePattern pattern, int start, int limit) { + ArgNode node = ArgNode.createArgNode(); + MessagePattern.Part part = pattern.getPart(start); + MessagePattern.ArgType argType = node.argType = part.getArgType(); + part = pattern.getPart(++start); // ARG_NAME or ARG_NUMBER + node.name = pattern.getSubstring(part); + if (part.getType() == MessagePattern.Part.Type.ARG_NUMBER) { + node.number = part.getValue(); + } + ++start; + switch(argType) { + case SIMPLE: + // ARG_TYPE + node.typeName = pattern.getSubstring(pattern.getPart(start++)); + if (start < limit) { + // ARG_STYLE + node.style = pattern.getSubstring(pattern.getPart(start)); + } + break; + case CHOICE: + node.typeName = "choice"; + node.complexStyle = buildChoiceStyleNode(pattern, start, limit); + break; + case PLURAL: + node.typeName = "plural"; + node.complexStyle = buildPluralStyleNode(pattern, start, limit); + break; + case SELECT: + node.typeName = "select"; + node.complexStyle = buildSelectStyleNode(pattern, start, limit); + break; + default: + // NONE type, nothing else to do + break; + } + return node; + } + + private static ComplexArgStyleNode buildChoiceStyleNode(MessagePattern pattern, + int start, int limit) { + ComplexArgStyleNode node = new ComplexArgStyleNode(MessagePattern.ArgType.CHOICE); + while (start < limit) { + int valueIndex = start; + MessagePattern.Part part = pattern.getPart(start); + double value = pattern.getNumericValue(part); + start += 2; + int msgLimit = pattern.getLimitPartIndex(start); + VariantNode variant = new VariantNode(); + variant.selector = pattern.getSubstring(pattern.getPart(valueIndex + 1)); + variant.numericValue = value; + variant.msgNode = buildMessageNode(pattern, start, msgLimit); + node.addVariant(variant); + start = msgLimit + 1; + } + return node.freeze(); + } + + private static ComplexArgStyleNode buildPluralStyleNode(MessagePattern pattern, + int start, int limit) { + ComplexArgStyleNode node = new ComplexArgStyleNode(MessagePattern.ArgType.PLURAL); + MessagePattern.Part offset = pattern.getPart(start); + if (offset.getType().hasNumericValue()) { + node.explicitOffset = true; + node.offset = pattern.getNumericValue(offset); + ++start; + } + while (start < limit) { + MessagePattern.Part selector = pattern.getPart(start++); + double value = MessagePattern.NO_NUMERIC_VALUE; + MessagePattern.Part part = pattern.getPart(start); + if (part.getType().hasNumericValue()) { + value = pattern.getNumericValue(part); + ++start; + } + int msgLimit = pattern.getLimitPartIndex(start); + VariantNode variant = new VariantNode(); + variant.selector = pattern.getSubstring(selector); + variant.numericValue = value; + variant.msgNode = buildMessageNode(pattern, start, msgLimit); + node.addVariant(variant); + start = msgLimit + 1; + } + return node.freeze(); + } + + private static ComplexArgStyleNode buildSelectStyleNode(MessagePattern pattern, + int start, int limit) { + ComplexArgStyleNode node = new ComplexArgStyleNode(MessagePattern.ArgType.SELECT); + while (start < limit) { + MessagePattern.Part selector = pattern.getPart(start++); + int msgLimit = pattern.getLimitPartIndex(start); + VariantNode variant = new VariantNode(); + variant.selector = pattern.getSubstring(selector); + variant.msgNode = buildMessageNode(pattern, start, msgLimit); + node.addVariant(variant); + start = msgLimit + 1; + } + return node.freeze(); + } +} diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/MessagePatternUtilTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/MessagePatternUtilTest.java new file mode 100644 index 00000000000..148f24dce1d --- /dev/null +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/MessagePatternUtilTest.java @@ -0,0 +1,508 @@ +/* +******************************************************************************* +* Copyright (C) 2011, 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 com.ibm.icu.text.MessagePattern; +import com.ibm.icu.text.MessagePatternUtil; +import com.ibm.icu.text.MessagePatternUtil.MessageNode; +import com.ibm.icu.text.MessagePatternUtil.MessageContentsNode; +import com.ibm.icu.text.MessagePatternUtil.TextNode; +import com.ibm.icu.text.MessagePatternUtil.ArgNode; +import com.ibm.icu.text.MessagePatternUtil.ComplexArgStyleNode; +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. + */ +public final class MessagePatternUtilTest extends com.ibm.icu.dev.test.TestFmwk { + public static void main(String[] args) throws Exception { + new MessagePatternUtilTest().run(args); + } + + // 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 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 msgContents = msg.getContents(); + boolean ok = assertEquals("different numbers of MessageContentsNode", + contents.size(), msgContents.size()); + if (ok) { + Iterator 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 contents = + new ArrayList(); + } + + /** + * 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 == MessagePattern.ArgType.CHOICE ? "choice" : + argType == MessagePattern.ArgType.PLURAL ? "plural" : "select"); + 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 complexVariants = complexStyle.getVariants(); + ok &= assertEquals("different number of variants", + variants.size(), complexVariants.size()); + if (!ok) { + return ok; + } + Iterator 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 variants = new ArrayList(); + } + + 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. + + public void TestHello() { + // No syntax. + MessageNode msg = MessagePatternUtil.buildMessageNode("Hello!"); + ExpectMessageNode expect = new ExpectMessageNode().expectTextThatContains("Hello"); + expect.checkMatches(msg); + } + + public void TestHelloWithApos() { + // Literal ASCII apostrophe. + MessageNode msg = MessagePatternUtil.buildMessageNode("Hel'lo!"); + ExpectMessageNode expect = new ExpectMessageNode().expectTextThatContains("Hel'lo"); + expect.checkMatches(msg); + } + + 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); + } + + 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); + } + + 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); + } + + 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); + } + + 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); + } + + 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); + } + + 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(); + } + + 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 numericVariants = new ArrayList(); + List keywordVariants = new ArrayList(); + 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)); + } + + 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 keywordVariants = new ArrayList(); + 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)); + } +} diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/TestAll.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/TestAll.java index e8865bd25ff..7d50f03ad72 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/TestAll.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/TestAll.java @@ -1,6 +1,6 @@ /* ******************************************************************************* - * Copyright (c) 2004-2010, International Business Machines + * Copyright (c) 2004-2011, International Business Machines * Corporation and others. All Rights Reserved. * Copyright (C) 2010 , Yahoo! Inc. ******************************************************************************* @@ -122,6 +122,7 @@ public class TestAll extends TestGroup { super(new String[] { "TestMessageFormat", "MessageRegression", + "MessagePatternUtilTest", }); } }