ICU-8745 merge MessagePatternUtil into trunk, from merge --reintegrate branches/markus/msgnodes from r30506

X-SVN-Rev: 30510
This commit is contained in:
Markus Scherer 2011-08-15 21:09:39 +00:00
parent 3b11db76a4
commit 69c0605df2
4 changed files with 1526 additions and 1 deletions

View File

@ -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 <em>prototype/demo/sample</em> for how we could use the MessagePatternUtil class
* for generating something like JavaScript code for evaluating some
* of the MessageFormat syntax.
*
* <p>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<String> args = new ArrayList<String>();
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<MessagePatternUtil.VariantNode> numericVariants =
new ArrayList<MessagePatternUtil.VariantNode>();
List<MessagePatternUtil.VariantNode> keywordVariants =
new ArrayList<MessagePatternUtil.VariantNode>();
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<MessagePatternUtil.VariantNode> keywordVariants =
new ArrayList<MessagePatternUtil.VariantNode>();
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<MessagePatternUtil.VariantNode> 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<MessagePatternUtil.VariantNode> 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<String> 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,<ph name=\"PERSON\">{$PERSON}</ph>}.}}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}}}}}}");
}
}

View File

@ -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.
*
* <p>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<MessageContentsNode> 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<MessageContentsNode> list = new ArrayList<MessageContentsNode>();
}
/**
* 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<VariantNode> getVariants() {
return list;
}
/**
* Separates the variants by type.
* Intended for use with plural and select argument styles,
* not useful for choice argument styles.
*
* <p>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<VariantNode> numericVariants,
List<VariantNode> 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<VariantNode> list = new ArrayList<VariantNode>();
}
/**
* 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();
}
}

View File

@ -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<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 == 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<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.
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<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));
}
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));
}
}

View File

@ -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",
});
}
}