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