1 /* GENERATED SOURCE. DO NOT MODIFY. */ 2 // © 2016 and later: Unicode, Inc. and others. 3 // License & terms of use: http://www.unicode.org/copyright.html 4 /* 5 ******************************************************************************* 6 * Copyright (C) 2011-2014, International Business Machines 7 * Corporation and others. All Rights Reserved. 8 ******************************************************************************* 9 * created on: 2011jul14 10 * created by: Markus W. Scherer 11 */ 12 13 package android.icu.text; 14 15 import java.util.ArrayList; 16 import java.util.Collections; 17 import java.util.List; 18 19 /** 20 * Utilities for working with a MessagePattern. 21 * Intended for use in tools when convenience is more important than 22 * minimizing runtime and object creations. 23 * 24 * <p>This class only has static methods. 25 * Each of the nested classes is immutable and thread-safe. 26 * 27 * <p>This class and its nested classes are not intended for public subclassing. 28 * @author Markus Scherer 29 * @hide Only a subset of ICU is exposed in Android 30 */ 31 public final class MessagePatternUtil { 32 33 // Private constructor preventing object instantiation MessagePatternUtil()34 private MessagePatternUtil() { 35 } 36 37 /** 38 * Factory method, builds and returns a MessageNode from a MessageFormat pattern string. 39 * @param patternString a MessageFormat pattern string 40 * @return a MessageNode or a ComplexArgStyleNode 41 * @throws IllegalArgumentException if the MessagePattern is empty 42 * or does not represent a MessageFormat pattern 43 */ buildMessageNode(String patternString)44 public static MessageNode buildMessageNode(String patternString) { 45 return buildMessageNode(new MessagePattern(patternString)); 46 } 47 48 /** 49 * Factory method, builds and returns a MessageNode from a MessagePattern. 50 * @param pattern a parsed MessageFormat pattern string 51 * @return a MessageNode or a ComplexArgStyleNode 52 * @throws IllegalArgumentException if the MessagePattern is empty 53 * or does not represent a MessageFormat pattern 54 */ buildMessageNode(MessagePattern pattern)55 public static MessageNode buildMessageNode(MessagePattern pattern) { 56 int limit = pattern.countParts() - 1; 57 if (limit < 0) { 58 throw new IllegalArgumentException("The MessagePattern is empty"); 59 } else if (pattern.getPartType(0) != MessagePattern.Part.Type.MSG_START) { 60 throw new IllegalArgumentException( 61 "The MessagePattern does not represent a MessageFormat pattern"); 62 } 63 return buildMessageNode(pattern, 0, limit); 64 } 65 66 /** 67 * Common base class for all elements in a tree of nodes 68 * returned by {@link MessagePatternUtil#buildMessageNode(MessagePattern)}. 69 * This class and all subclasses are immutable and thread-safe. 70 * @hide Only a subset of ICU is exposed in Android 71 */ 72 public static class Node { Node()73 private Node() {} 74 } 75 76 /** 77 * A Node representing a parsed MessageFormat pattern string. 78 * @hide Only a subset of ICU is exposed in Android 79 */ 80 public static class MessageNode extends Node { 81 /** 82 * @return the list of MessageContentsNode nodes that this message contains 83 */ getContents()84 public List<MessageContentsNode> getContents() { 85 return list; 86 } 87 /** 88 * {@inheritDoc} 89 */ 90 @Override toString()91 public String toString() { 92 return list.toString(); 93 } 94 MessageNode()95 private MessageNode() { 96 super(); 97 } addContentsNode(MessageContentsNode node)98 private void addContentsNode(MessageContentsNode node) { 99 if (node instanceof TextNode && !list.isEmpty()) { 100 // Coalesce adjacent text nodes. 101 MessageContentsNode lastNode = list.get(list.size() - 1); 102 if (lastNode instanceof TextNode) { 103 TextNode textNode = (TextNode)lastNode; 104 textNode.text = textNode.text + ((TextNode)node).text; 105 return; 106 } 107 } 108 list.add(node); 109 } freeze()110 private MessageNode freeze() { 111 list = Collections.unmodifiableList(list); 112 return this; 113 } 114 115 private volatile List<MessageContentsNode> list = new ArrayList<MessageContentsNode>(); 116 } 117 118 /** 119 * A piece of MessageNode contents. 120 * Use getType() to determine the type and the actual Node subclass. 121 * @hide Only a subset of ICU is exposed in Android 122 */ 123 public static class MessageContentsNode extends Node { 124 /** 125 * The type of a piece of MessageNode contents. 126 * @hide Only a subset of ICU is exposed in Android 127 */ 128 public enum Type { 129 /** 130 * This is a TextNode containing literal text (downcast and call getText()). 131 */ 132 TEXT, 133 /** 134 * This is an ArgNode representing a message argument 135 * (downcast and use specific methods). 136 */ 137 ARG, 138 /** 139 * This Node represents a place in a plural argument's variant where 140 * the formatted (plural-offset) value is to be put. 141 */ 142 REPLACE_NUMBER 143 } 144 /** 145 * Returns the type of this piece of MessageNode contents. 146 */ getType()147 public Type getType() { 148 return type; 149 } 150 /** 151 * {@inheritDoc} 152 */ 153 @Override toString()154 public String toString() { 155 // Note: There is no specific subclass for REPLACE_NUMBER 156 // because it would not provide any additional API. 157 // Therefore we have a little bit of REPLACE_NUMBER-specific code 158 // here in the contents-node base class. 159 return "{REPLACE_NUMBER}"; 160 } 161 MessageContentsNode(Type type)162 private MessageContentsNode(Type type) { 163 super(); 164 this.type = type; 165 } createReplaceNumberNode()166 private static MessageContentsNode createReplaceNumberNode() { 167 return new MessageContentsNode(Type.REPLACE_NUMBER); 168 } 169 170 private Type type; 171 } 172 173 /** 174 * Literal text, a piece of MessageNode contents. 175 * @hide Only a subset of ICU is exposed in Android 176 */ 177 public static class TextNode extends MessageContentsNode { 178 /** 179 * @return the literal text at this point in the message 180 */ getText()181 public String getText() { 182 return text; 183 } 184 /** 185 * {@inheritDoc} 186 */ 187 @Override toString()188 public String toString() { 189 return "«" + text + "»"; 190 } 191 TextNode(String text)192 private TextNode(String text) { 193 super(Type.TEXT); 194 this.text = text; 195 } 196 197 private String text; 198 } 199 200 /** 201 * A piece of MessageNode contents representing a message argument and its details. 202 * @hide Only a subset of ICU is exposed in Android 203 */ 204 public static class ArgNode extends MessageContentsNode { 205 /** 206 * @return the argument type 207 */ getArgType()208 public MessagePattern.ArgType getArgType() { 209 return argType; 210 } 211 /** 212 * @return the argument name string (the decimal-digit string if the argument has a number) 213 */ getName()214 public String getName() { 215 return name; 216 } 217 /** 218 * @return the argument number, or -1 if none (for a named argument) 219 */ getNumber()220 public int getNumber() { 221 return number; 222 } 223 /** 224 * @return the argument type string, or null if none was specified 225 */ getTypeName()226 public String getTypeName() { 227 return typeName; 228 } 229 /** 230 * @return the simple-argument style string, 231 * or null if no style is specified and for other argument types 232 */ getSimpleStyle()233 public String getSimpleStyle() { 234 return style; 235 } 236 /** 237 * @return the complex-argument-style object, 238 * or null if the argument type is NONE_ARG or SIMPLE_ARG 239 */ getComplexStyle()240 public ComplexArgStyleNode getComplexStyle() { 241 return complexStyle; 242 } 243 /** 244 * {@inheritDoc} 245 */ 246 @Override toString()247 public String toString() { 248 StringBuilder sb = new StringBuilder(); 249 sb.append('{').append(name); 250 if (argType != MessagePattern.ArgType.NONE) { 251 sb.append(',').append(typeName); 252 if (argType == MessagePattern.ArgType.SIMPLE) { 253 if (style != null) { 254 sb.append(',').append(style); 255 } 256 } else { 257 sb.append(',').append(complexStyle.toString()); 258 } 259 } 260 return sb.append('}').toString(); 261 } 262 ArgNode()263 private ArgNode() { 264 super(Type.ARG); 265 } createArgNode()266 private static ArgNode createArgNode() { 267 return new ArgNode(); 268 } 269 270 private MessagePattern.ArgType argType; 271 private String name; 272 private int number = -1; 273 private String typeName; 274 private String style; 275 private ComplexArgStyleNode complexStyle; 276 } 277 278 /** 279 * A Node representing details of the argument style of a complex argument. 280 * (Which is a choice/plural/select argument which selects among nested messages.) 281 * @hide Only a subset of ICU is exposed in Android 282 */ 283 public static class ComplexArgStyleNode extends Node { 284 /** 285 * @return the argument type (same as getArgType() on the parent ArgNode) 286 */ getArgType()287 public MessagePattern.ArgType getArgType() { 288 return argType; 289 } 290 /** 291 * @return true if this is a plural style with an explicit offset 292 */ hasExplicitOffset()293 public boolean hasExplicitOffset() { 294 return explicitOffset; 295 } 296 /** 297 * @return the plural offset, or 0 if this is not a plural style or 298 * the offset is explicitly or implicitly 0 299 */ getOffset()300 public double getOffset() { 301 return offset; 302 } 303 /** 304 * @return the list of variants: the nested messages with their selection criteria 305 */ getVariants()306 public List<VariantNode> getVariants() { 307 return list; 308 } 309 /** 310 * Separates the variants by type. 311 * Intended for use with plural and select argument styles, 312 * not useful for choice argument styles. 313 * 314 * <p>Both parameters are used only for output, and are first cleared. 315 * @param numericVariants Variants with numeric-value selectors (if any) are added here. 316 * Can be null for a select argument style. 317 * @param keywordVariants Variants with keyword selectors, except "other", are added here. 318 * For a plural argument, if this list is empty after the call, then 319 * all variants except "other" have explicit values 320 * and PluralRules need not be called. 321 * @return the "other" variant (the first one if there are several), 322 * null if none (choice style) 323 */ getVariantsByType(List<VariantNode> numericVariants, List<VariantNode> keywordVariants)324 public VariantNode getVariantsByType(List<VariantNode> numericVariants, 325 List<VariantNode> keywordVariants) { 326 if (numericVariants != null) { 327 numericVariants.clear(); 328 } 329 keywordVariants.clear(); 330 VariantNode other = null; 331 for (VariantNode variant : list) { 332 if (variant.isSelectorNumeric()) { 333 numericVariants.add(variant); 334 } else if ("other".equals(variant.getSelector())) { 335 if (other == null) { 336 // Return the first "other" variant. (MessagePattern allows duplicates.) 337 other = variant; 338 } 339 } else { 340 keywordVariants.add(variant); 341 } 342 } 343 return other; 344 } 345 /** 346 * {@inheritDoc} 347 */ 348 @Override toString()349 public String toString() { 350 StringBuilder sb = new StringBuilder(); 351 sb.append('(').append(argType.toString()).append(" style) "); 352 if (hasExplicitOffset()) { 353 sb.append("offset:").append(offset).append(' '); 354 } 355 return sb.append(list.toString()).toString(); 356 } 357 ComplexArgStyleNode(MessagePattern.ArgType argType)358 private ComplexArgStyleNode(MessagePattern.ArgType argType) { 359 super(); 360 this.argType = argType; 361 } addVariant(VariantNode variant)362 private void addVariant(VariantNode variant) { 363 list.add(variant); 364 } freeze()365 private ComplexArgStyleNode freeze() { 366 list = Collections.unmodifiableList(list); 367 return this; 368 } 369 370 private MessagePattern.ArgType argType; 371 private double offset; 372 private boolean explicitOffset; 373 private volatile List<VariantNode> list = new ArrayList<VariantNode>(); 374 } 375 376 /** 377 * A Node representing a nested message (nested inside an argument) 378 * with its selection criterion. 379 * @hide Only a subset of ICU is exposed in Android 380 */ 381 public static class VariantNode extends Node { 382 /** 383 * Returns the selector string. 384 * For example: A plural/select keyword ("few"), a plural explicit value ("=1"), 385 * a choice comparison operator ("#"). 386 * @return the selector string 387 */ getSelector()388 public String getSelector() { 389 return selector; 390 } 391 /** 392 * @return true for choice variants and for plural explicit values 393 */ isSelectorNumeric()394 public boolean isSelectorNumeric() { 395 return numericValue != MessagePattern.NO_NUMERIC_VALUE; 396 } 397 /** 398 * @return the selector's numeric value, or NO_NUMERIC_VALUE if !isSelectorNumeric() 399 */ getSelectorValue()400 public double getSelectorValue() { 401 return numericValue; 402 } 403 /** 404 * @return the nested message 405 */ getMessage()406 public MessageNode getMessage() { 407 return msgNode; 408 } 409 /** 410 * {@inheritDoc} 411 */ 412 @Override toString()413 public String toString() { 414 StringBuilder sb = new StringBuilder(); 415 if (isSelectorNumeric()) { 416 sb.append(numericValue).append(" (").append(selector).append(") {"); 417 } else { 418 sb.append(selector).append(" {"); 419 } 420 return sb.append(msgNode.toString()).append('}').toString(); 421 } 422 VariantNode()423 private VariantNode() { 424 super(); 425 } 426 427 private String selector; 428 private double numericValue = MessagePattern.NO_NUMERIC_VALUE; 429 private MessageNode msgNode; 430 } 431 buildMessageNode(MessagePattern pattern, int start, int limit)432 private static MessageNode buildMessageNode(MessagePattern pattern, int start, int limit) { 433 int prevPatternIndex = pattern.getPart(start).getLimit(); 434 MessageNode node = new MessageNode(); 435 for (int i = start + 1;; ++i) { 436 MessagePattern.Part part = pattern.getPart(i); 437 int patternIndex = part.getIndex(); 438 if (prevPatternIndex < patternIndex) { 439 node.addContentsNode( 440 new TextNode(pattern.getPatternString().substring(prevPatternIndex, 441 patternIndex))); 442 } 443 if (i == limit) { 444 break; 445 } 446 MessagePattern.Part.Type partType = part.getType(); 447 if (partType == MessagePattern.Part.Type.ARG_START) { 448 int argLimit = pattern.getLimitPartIndex(i); 449 node.addContentsNode(buildArgNode(pattern, i, argLimit)); 450 i = argLimit; 451 part = pattern.getPart(i); 452 } else if (partType == MessagePattern.Part.Type.REPLACE_NUMBER) { 453 node.addContentsNode(MessageContentsNode.createReplaceNumberNode()); 454 // else: ignore SKIP_SYNTAX and INSERT_CHAR parts. 455 } 456 prevPatternIndex = part.getLimit(); 457 } 458 return node.freeze(); 459 } 460 buildArgNode(MessagePattern pattern, int start, int limit)461 private static ArgNode buildArgNode(MessagePattern pattern, int start, int limit) { 462 ArgNode node = ArgNode.createArgNode(); 463 MessagePattern.Part part = pattern.getPart(start); 464 MessagePattern.ArgType argType = node.argType = part.getArgType(); 465 part = pattern.getPart(++start); // ARG_NAME or ARG_NUMBER 466 node.name = pattern.getSubstring(part); 467 if (part.getType() == MessagePattern.Part.Type.ARG_NUMBER) { 468 node.number = part.getValue(); 469 } 470 ++start; 471 switch(argType) { 472 case SIMPLE: 473 // ARG_TYPE 474 node.typeName = pattern.getSubstring(pattern.getPart(start++)); 475 if (start < limit) { 476 // ARG_STYLE 477 node.style = pattern.getSubstring(pattern.getPart(start)); 478 } 479 break; 480 case CHOICE: 481 node.typeName = "choice"; 482 node.complexStyle = buildChoiceStyleNode(pattern, start, limit); 483 break; 484 case PLURAL: 485 node.typeName = "plural"; 486 node.complexStyle = buildPluralStyleNode(pattern, start, limit, argType); 487 break; 488 case SELECT: 489 node.typeName = "select"; 490 node.complexStyle = buildSelectStyleNode(pattern, start, limit); 491 break; 492 case SELECTORDINAL: 493 node.typeName = "selectordinal"; 494 node.complexStyle = buildPluralStyleNode(pattern, start, limit, argType); 495 break; 496 default: 497 // NONE type, nothing else to do 498 break; 499 } 500 return node; 501 } 502 buildChoiceStyleNode(MessagePattern pattern, int start, int limit)503 private static ComplexArgStyleNode buildChoiceStyleNode(MessagePattern pattern, 504 int start, int limit) { 505 ComplexArgStyleNode node = new ComplexArgStyleNode(MessagePattern.ArgType.CHOICE); 506 while (start < limit) { 507 int valueIndex = start; 508 MessagePattern.Part part = pattern.getPart(start); 509 double value = pattern.getNumericValue(part); 510 start += 2; 511 int msgLimit = pattern.getLimitPartIndex(start); 512 VariantNode variant = new VariantNode(); 513 variant.selector = pattern.getSubstring(pattern.getPart(valueIndex + 1)); 514 variant.numericValue = value; 515 variant.msgNode = buildMessageNode(pattern, start, msgLimit); 516 node.addVariant(variant); 517 start = msgLimit + 1; 518 } 519 return node.freeze(); 520 } 521 buildPluralStyleNode(MessagePattern pattern, int start, int limit, MessagePattern.ArgType argType)522 private static ComplexArgStyleNode buildPluralStyleNode(MessagePattern pattern, 523 int start, int limit, 524 MessagePattern.ArgType argType) { 525 ComplexArgStyleNode node = new ComplexArgStyleNode(argType); 526 MessagePattern.Part offset = pattern.getPart(start); 527 if (offset.getType().hasNumericValue()) { 528 node.explicitOffset = true; 529 node.offset = pattern.getNumericValue(offset); 530 ++start; 531 } 532 while (start < limit) { 533 MessagePattern.Part selector = pattern.getPart(start++); 534 double value = MessagePattern.NO_NUMERIC_VALUE; 535 MessagePattern.Part part = pattern.getPart(start); 536 if (part.getType().hasNumericValue()) { 537 value = pattern.getNumericValue(part); 538 ++start; 539 } 540 int msgLimit = pattern.getLimitPartIndex(start); 541 VariantNode variant = new VariantNode(); 542 variant.selector = pattern.getSubstring(selector); 543 variant.numericValue = value; 544 variant.msgNode = buildMessageNode(pattern, start, msgLimit); 545 node.addVariant(variant); 546 start = msgLimit + 1; 547 } 548 return node.freeze(); 549 } 550 buildSelectStyleNode(MessagePattern pattern, int start, int limit)551 private static ComplexArgStyleNode buildSelectStyleNode(MessagePattern pattern, 552 int start, int limit) { 553 ComplexArgStyleNode node = new ComplexArgStyleNode(MessagePattern.ArgType.SELECT); 554 while (start < limit) { 555 MessagePattern.Part selector = pattern.getPart(start++); 556 int msgLimit = pattern.getLimitPartIndex(start); 557 VariantNode variant = new VariantNode(); 558 variant.selector = pattern.getSubstring(selector); 559 variant.msgNode = buildMessageNode(pattern, start, msgLimit); 560 node.addVariant(variant); 561 start = msgLimit + 1; 562 } 563 return node.freeze(); 564 } 565 } 566