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