1 /* GENERATED SOURCE. DO NOT MODIFY. */ 2 /* 3 ******************************************************************************* 4 * Copyright (C) 2010-2016, International Business Machines 5 * Corporation and others. All Rights Reserved. 6 ******************************************************************************* 7 * created on: 2010aug21 8 * created by: Markus W. Scherer 9 */ 10 11 package android.icu.text; 12 13 import java.util.ArrayList; 14 import java.util.Locale; 15 16 import android.icu.impl.ICUConfig; 17 import android.icu.impl.PatternProps; 18 import android.icu.util.Freezable; 19 import android.icu.util.ICUCloneNotSupportedException; 20 21 //Note: Minimize ICU dependencies, only use a very small part of the ICU core. 22 //In particular, do not depend on *Format classes. 23 24 /** 25 * Parses and represents ICU MessageFormat patterns. 26 * Also handles patterns for ChoiceFormat, PluralFormat and SelectFormat. 27 * Used in the implementations of those classes as well as in tools 28 * for message validation, translation and format conversion. 29 * <p> 30 * The parser handles all syntax relevant for identifying message arguments. 31 * This includes "complex" arguments whose style strings contain 32 * nested MessageFormat pattern substrings. 33 * For "simple" arguments (with no nested MessageFormat pattern substrings), 34 * the argument style is not parsed any further. 35 * <p> 36 * The parser handles named and numbered message arguments and allows both in one message. 37 * <p> 38 * Once a pattern has been parsed successfully, iterate through the parsed data 39 * with countParts(), getPart() and related methods. 40 * <p> 41 * The data logically represents a parse tree, but is stored and accessed 42 * as a list of "parts" for fast and simple parsing and to minimize object allocations. 43 * Arguments and nested messages are best handled via recursion. 44 * For every _START "part", {@link #getLimitPartIndex(int)} efficiently returns 45 * the index of the corresponding _LIMIT "part". 46 * <p> 47 * List of "parts": 48 * <pre> 49 * message = MSG_START (SKIP_SYNTAX | INSERT_CHAR | REPLACE_NUMBER | argument)* MSG_LIMIT 50 * argument = noneArg | simpleArg | complexArg 51 * complexArg = choiceArg | pluralArg | selectArg 52 * 53 * noneArg = ARG_START.NONE (ARG_NAME | ARG_NUMBER) ARG_LIMIT.NONE 54 * simpleArg = ARG_START.SIMPLE (ARG_NAME | ARG_NUMBER) ARG_TYPE [ARG_STYLE] ARG_LIMIT.SIMPLE 55 * choiceArg = ARG_START.CHOICE (ARG_NAME | ARG_NUMBER) choiceStyle ARG_LIMIT.CHOICE 56 * pluralArg = ARG_START.PLURAL (ARG_NAME | ARG_NUMBER) pluralStyle ARG_LIMIT.PLURAL 57 * selectArg = ARG_START.SELECT (ARG_NAME | ARG_NUMBER) selectStyle ARG_LIMIT.SELECT 58 * 59 * choiceStyle = ((ARG_INT | ARG_DOUBLE) ARG_SELECTOR message)+ 60 * pluralStyle = [ARG_INT | ARG_DOUBLE] (ARG_SELECTOR [ARG_INT | ARG_DOUBLE] message)+ 61 * selectStyle = (ARG_SELECTOR message)+ 62 * </pre> 63 * <ul> 64 * <li>Literal output text is not represented directly by "parts" but accessed 65 * between parts of a message, from one part's getLimit() to the next part's getIndex(). 66 * <li><code>ARG_START.CHOICE</code> stands for an ARG_START Part with ArgType CHOICE. 67 * <li>In the choiceStyle, the ARG_SELECTOR has the '<', the '#' or 68 * the less-than-or-equal-to sign (U+2264). 69 * <li>In the pluralStyle, the first, optional numeric Part has the "offset:" value. 70 * The optional numeric Part between each (ARG_SELECTOR, message) pair 71 * is the value of an explicit-number selector like "=2", 72 * otherwise the selector is a non-numeric identifier. 73 * <li>The REPLACE_NUMBER Part can occur only in an immediate sub-message of the pluralStyle. 74 * </ul> 75 * <p> 76 * This class is not intended for public subclassing. 77 * 78 * @author Markus Scherer 79 */ 80 public final class MessagePattern implements Cloneable, Freezable<MessagePattern> { 81 /** 82 * Mode for when an apostrophe starts quoted literal text for MessageFormat output. 83 * The default is DOUBLE_OPTIONAL unless overridden via ICUConfig 84 * (/com/ibm/icu/ICUConfig.properties). 85 * <p> 86 * A pair of adjacent apostrophes always results in a single apostrophe in the output, 87 * even when the pair is between two single, text-quoting apostrophes. 88 * <p> 89 * The following table shows examples of desired MessageFormat.format() output 90 * with the pattern strings that yield that output. 91 * 92 * <table> 93 * <tr> 94 * <th>Desired output</th> 95 * <th>DOUBLE_OPTIONAL</th> 96 * <th>DOUBLE_REQUIRED</th> 97 * </tr> 98 * <tr> 99 * <td>I see {many}</td> 100 * <td>I see '{many}'</td> 101 * <td>(same)</td> 102 * </tr> 103 * <tr> 104 * <td>I said {'Wow!'}</td> 105 * <td>I said '{''Wow!''}'</td> 106 * <td>(same)</td> 107 * </tr> 108 * <tr> 109 * <td>I don't know</td> 110 * <td>I don't know OR<br> I don''t know</td> 111 * <td>I don''t know</td> 112 * </tr> 113 * </table> 114 */ 115 public enum ApostropheMode { 116 /** 117 * A literal apostrophe is represented by 118 * either a single or a double apostrophe pattern character. 119 * Within a MessageFormat pattern, a single apostrophe only starts quoted literal text 120 * if it immediately precedes a curly brace {}, 121 * or a pipe symbol | if inside a choice format, 122 * or a pound symbol # if inside a plural format. 123 * <p> 124 * This is the default behavior starting with ICU 4.8. 125 */ 126 DOUBLE_OPTIONAL, 127 /** 128 * A literal apostrophe must be represented by 129 * a double apostrophe pattern character. 130 * A single apostrophe always starts quoted literal text. 131 * <p> 132 * This is the behavior of ICU 4.6 and earlier, and of {@link java.text.MessageFormat}. 133 */ 134 DOUBLE_REQUIRED 135 } 136 137 /** 138 * Constructs an empty MessagePattern with default ApostropheMode. 139 */ MessagePattern()140 public MessagePattern() { 141 aposMode=defaultAposMode; 142 } 143 144 /** 145 * Constructs an empty MessagePattern. 146 * @param mode Explicit ApostropheMode. 147 */ MessagePattern(ApostropheMode mode)148 public MessagePattern(ApostropheMode mode) { 149 aposMode=mode; 150 } 151 152 /** 153 * Constructs a MessagePattern with default ApostropheMode and 154 * parses the MessageFormat pattern string. 155 * @param pattern a MessageFormat pattern string 156 * @throws IllegalArgumentException for syntax errors in the pattern string 157 * @throws IndexOutOfBoundsException if certain limits are exceeded 158 * (e.g., argument number too high, argument name too long, etc.) 159 * @throws NumberFormatException if a number could not be parsed 160 */ MessagePattern(String pattern)161 public MessagePattern(String pattern) { 162 aposMode=defaultAposMode; 163 parse(pattern); 164 } 165 166 /** 167 * Parses a MessageFormat pattern string. 168 * @param pattern a MessageFormat pattern string 169 * @return this 170 * @throws IllegalArgumentException for syntax errors in the pattern string 171 * @throws IndexOutOfBoundsException if certain limits are exceeded 172 * (e.g., argument number too high, argument name too long, etc.) 173 * @throws NumberFormatException if a number could not be parsed 174 */ parse(String pattern)175 public MessagePattern parse(String pattern) { 176 preParse(pattern); 177 parseMessage(0, 0, 0, ArgType.NONE); 178 postParse(); 179 return this; 180 } 181 182 /** 183 * Parses a ChoiceFormat pattern string. 184 * @param pattern a ChoiceFormat pattern string 185 * @return this 186 * @throws IllegalArgumentException for syntax errors in the pattern string 187 * @throws IndexOutOfBoundsException if certain limits are exceeded 188 * (e.g., argument number too high, argument name too long, etc.) 189 * @throws NumberFormatException if a number could not be parsed 190 */ parseChoiceStyle(String pattern)191 public MessagePattern parseChoiceStyle(String pattern) { 192 preParse(pattern); 193 parseChoiceStyle(0, 0); 194 postParse(); 195 return this; 196 } 197 198 /** 199 * Parses a PluralFormat pattern string. 200 * @param pattern a PluralFormat pattern string 201 * @return this 202 * @throws IllegalArgumentException for syntax errors in the pattern string 203 * @throws IndexOutOfBoundsException if certain limits are exceeded 204 * (e.g., argument number too high, argument name too long, etc.) 205 * @throws NumberFormatException if a number could not be parsed 206 */ parsePluralStyle(String pattern)207 public MessagePattern parsePluralStyle(String pattern) { 208 preParse(pattern); 209 parsePluralOrSelectStyle(ArgType.PLURAL, 0, 0); 210 postParse(); 211 return this; 212 } 213 214 /** 215 * Parses a SelectFormat pattern string. 216 * @param pattern a SelectFormat pattern string 217 * @return this 218 * @throws IllegalArgumentException for syntax errors in the pattern string 219 * @throws IndexOutOfBoundsException if certain limits are exceeded 220 * (e.g., argument number too high, argument name too long, etc.) 221 * @throws NumberFormatException if a number could not be parsed 222 */ parseSelectStyle(String pattern)223 public MessagePattern parseSelectStyle(String pattern) { 224 preParse(pattern); 225 parsePluralOrSelectStyle(ArgType.SELECT, 0, 0); 226 postParse(); 227 return this; 228 } 229 230 /** 231 * Clears this MessagePattern. 232 * countParts() will return 0. 233 */ clear()234 public void clear() { 235 // Mostly the same as preParse(). 236 if(isFrozen()) { 237 throw new UnsupportedOperationException( 238 "Attempt to clear() a frozen MessagePattern instance."); 239 } 240 msg=null; 241 hasArgNames=hasArgNumbers=false; 242 needsAutoQuoting=false; 243 parts.clear(); 244 if(numericValues!=null) { 245 numericValues.clear(); 246 } 247 } 248 249 /** 250 * Clears this MessagePattern and sets the ApostropheMode. 251 * countParts() will return 0. 252 * @param mode The new ApostropheMode. 253 */ clearPatternAndSetApostropheMode(ApostropheMode mode)254 public void clearPatternAndSetApostropheMode(ApostropheMode mode) { 255 clear(); 256 aposMode=mode; 257 } 258 259 /** 260 * @param other another object to compare with. 261 * @return true if this object is equivalent to the other one. 262 */ 263 @Override equals(Object other)264 public boolean equals(Object other) { 265 if(this==other) { 266 return true; 267 } 268 if(other==null || getClass()!=other.getClass()) { 269 return false; 270 } 271 MessagePattern o=(MessagePattern)other; 272 return 273 aposMode.equals(o.aposMode) && 274 (msg==null ? o.msg==null : msg.equals(o.msg)) && 275 parts.equals(o.parts); 276 // No need to compare numericValues if msg and parts are the same. 277 } 278 279 /** 280 * {@inheritDoc} 281 */ 282 @Override hashCode()283 public int hashCode() { 284 return (aposMode.hashCode()*37+(msg!=null ? msg.hashCode() : 0))*37+parts.hashCode(); 285 } 286 287 /** 288 * @return this instance's ApostropheMode. 289 */ getApostropheMode()290 public ApostropheMode getApostropheMode() { 291 return aposMode; 292 } 293 294 /** 295 * @return true if getApostropheMode() == ApostropheMode.DOUBLE_REQUIRED 296 * @hide draft / provisional / internal are hidden on Android 297 */ jdkAposMode()298 /* package */ boolean jdkAposMode() { 299 return aposMode == ApostropheMode.DOUBLE_REQUIRED; 300 } 301 302 /** 303 * @return the parsed pattern string (null if none was parsed). 304 */ getPatternString()305 public String getPatternString() { 306 return msg; 307 } 308 309 /** 310 * Does the parsed pattern have named arguments like {first_name}? 311 * @return true if the parsed pattern has at least one named argument. 312 */ hasNamedArguments()313 public boolean hasNamedArguments() { 314 return hasArgNames; 315 } 316 317 /** 318 * Does the parsed pattern have numbered arguments like {2}? 319 * @return true if the parsed pattern has at least one numbered argument. 320 */ hasNumberedArguments()321 public boolean hasNumberedArguments() { 322 return hasArgNumbers; 323 } 324 325 /** 326 * {@inheritDoc} 327 */ 328 @Override toString()329 public String toString() { 330 return msg; 331 } 332 333 /** 334 * Validates and parses an argument name or argument number string. 335 * An argument name must be a "pattern identifier", that is, it must contain 336 * no Unicode Pattern_Syntax or Pattern_White_Space characters. 337 * If it only contains ASCII digits, then it must be a small integer with no leading zero. 338 * @param name Input string. 339 * @return >=0 if the name is a valid number, 340 * ARG_NAME_NOT_NUMBER (-1) if it is a "pattern identifier" but not all ASCII digits, 341 * ARG_NAME_NOT_VALID (-2) if it is neither. 342 */ validateArgumentName(String name)343 public static int validateArgumentName(String name) { 344 if(!PatternProps.isIdentifier(name)) { 345 return ARG_NAME_NOT_VALID; 346 } 347 return parseArgNumber(name, 0, name.length()); 348 } 349 350 /** 351 * Return value from {@link #validateArgumentName(String)} for when 352 * the string is a valid "pattern identifier" but not a number. 353 */ 354 public static final int ARG_NAME_NOT_NUMBER=-1; 355 356 /** 357 * Return value from {@link #validateArgumentName(String)} for when 358 * the string is invalid. 359 * It might not be a valid "pattern identifier", 360 * or it have only ASCII digits but there is a leading zero or the number is too large. 361 */ 362 public static final int ARG_NAME_NOT_VALID=-2; 363 364 /** 365 * Returns a version of the parsed pattern string where each ASCII apostrophe 366 * is doubled (escaped) if it is not already, and if it is not interpreted as quoting syntax. 367 * <p> 368 * For example, this turns "I don't '{know}' {gender,select,female{h''er}other{h'im}}." 369 * into "I don''t '{know}' {gender,select,female{h''er}other{h''im}}." 370 * @return the deep-auto-quoted version of the parsed pattern string. 371 * @see MessageFormat#autoQuoteApostrophe(String) 372 */ autoQuoteApostropheDeep()373 public String autoQuoteApostropheDeep() { 374 if(!needsAutoQuoting) { 375 return msg; 376 } 377 StringBuilder modified=null; 378 // Iterate backward so that the insertion indexes do not change. 379 int count=countParts(); 380 for(int i=count; i>0;) { 381 Part part; 382 if((part=getPart(--i)).getType()==Part.Type.INSERT_CHAR) { 383 if(modified==null) { 384 modified=new StringBuilder(msg.length()+10).append(msg); 385 } 386 modified.insert(part.index, (char)part.value); 387 } 388 } 389 if(modified==null) { 390 return msg; 391 } else { 392 return modified.toString(); 393 } 394 } 395 396 /** 397 * Returns the number of "parts" created by parsing the pattern string. 398 * Returns 0 if no pattern has been parsed or clear() was called. 399 * @return the number of pattern parts. 400 */ countParts()401 public int countParts() { 402 return parts.size(); 403 } 404 405 /** 406 * Gets the i-th pattern "part". 407 * @param i The index of the Part data. (0..countParts()-1) 408 * @return the i-th pattern "part". 409 * @throws IndexOutOfBoundsException if i is outside the (0..countParts()-1) range 410 */ getPart(int i)411 public Part getPart(int i) { 412 return parts.get(i); 413 } 414 415 /** 416 * Returns the Part.Type of the i-th pattern "part". 417 * Convenience method for getPart(i).getType(). 418 * @param i The index of the Part data. (0..countParts()-1) 419 * @return The Part.Type of the i-th Part. 420 * @throws IndexOutOfBoundsException if i is outside the (0..countParts()-1) range 421 */ getPartType(int i)422 public Part.Type getPartType(int i) { 423 return parts.get(i).type; 424 } 425 426 /** 427 * Returns the pattern index of the specified pattern "part". 428 * Convenience method for getPart(partIndex).getIndex(). 429 * @param partIndex The index of the Part data. (0..countParts()-1) 430 * @return The pattern index of this Part. 431 * @throws IndexOutOfBoundsException if partIndex is outside the (0..countParts()-1) range 432 */ getPatternIndex(int partIndex)433 public int getPatternIndex(int partIndex) { 434 return parts.get(partIndex).index; 435 } 436 437 /** 438 * Returns the substring of the pattern string indicated by the Part. 439 * Convenience method for getPatternString().substring(part.getIndex(), part.getLimit()). 440 * @param part a part of this MessagePattern. 441 * @return the substring associated with part. 442 */ getSubstring(Part part)443 public String getSubstring(Part part) { 444 int index=part.index; 445 return msg.substring(index, index+part.length); 446 } 447 448 /** 449 * Compares the part's substring with the input string s. 450 * @param part a part of this MessagePattern. 451 * @param s a string. 452 * @return true if getSubstring(part).equals(s). 453 */ partSubstringMatches(Part part, String s)454 public boolean partSubstringMatches(Part part, String s) { 455 return msg.regionMatches(part.index, s, 0, part.length); 456 } 457 458 /** 459 * Returns the numeric value associated with an ARG_INT or ARG_DOUBLE. 460 * @param part a part of this MessagePattern. 461 * @return the part's numeric value, or NO_NUMERIC_VALUE if this is not a numeric part. 462 */ getNumericValue(Part part)463 public double getNumericValue(Part part) { 464 Part.Type type=part.type; 465 if(type==Part.Type.ARG_INT) { 466 return part.value; 467 } else if(type==Part.Type.ARG_DOUBLE) { 468 return numericValues.get(part.value); 469 } else { 470 return NO_NUMERIC_VALUE; 471 } 472 } 473 474 /** 475 * Special value that is returned by getNumericValue(Part) when no 476 * numeric value is defined for a part. 477 * @see #getNumericValue 478 */ 479 public static final double NO_NUMERIC_VALUE=-123456789; 480 481 /** 482 * Returns the "offset:" value of a PluralFormat argument, or 0 if none is specified. 483 * @param pluralStart the index of the first PluralFormat argument style part. (0..countParts()-1) 484 * @return the "offset:" value. 485 * @throws IndexOutOfBoundsException if pluralStart is outside the (0..countParts()-1) range 486 */ getPluralOffset(int pluralStart)487 public double getPluralOffset(int pluralStart) { 488 Part part=parts.get(pluralStart); 489 if(part.type.hasNumericValue()) { 490 return getNumericValue(part); 491 } else { 492 return 0; 493 } 494 } 495 496 /** 497 * Returns the index of the ARG|MSG_LIMIT part corresponding to the ARG|MSG_START at start. 498 * @param start The index of some Part data (0..countParts()-1); 499 * this Part should be of Type ARG_START or MSG_START. 500 * @return The first i>start where getPart(i).getType()==ARG|MSG_LIMIT at the same nesting level, 501 * or start itself if getPartType(msgStart)!=ARG|MSG_START. 502 * @throws IndexOutOfBoundsException if start is outside the (0..countParts()-1) range 503 */ getLimitPartIndex(int start)504 public int getLimitPartIndex(int start) { 505 int limit=parts.get(start).limitPartIndex; 506 if(limit<start) { 507 return start; 508 } 509 return limit; 510 } 511 512 /** 513 * A message pattern "part", representing a pattern parsing event. 514 * There is a part for the start and end of a message or argument, 515 * for quoting and escaping of and with ASCII apostrophes, 516 * and for syntax elements of "complex" arguments. 517 */ 518 public static final class Part { Part(Type t, int i, int l, int v)519 private Part(Type t, int i, int l, int v) { 520 type=t; 521 index=i; 522 length=(char)l; 523 value=(short)v; 524 } 525 526 /** 527 * Returns the type of this part. 528 * @return the part type. 529 */ getType()530 public Type getType() { 531 return type; 532 } 533 534 /** 535 * Returns the pattern string index associated with this Part. 536 * @return this part's pattern string index. 537 */ getIndex()538 public int getIndex() { 539 return index; 540 } 541 542 /** 543 * Returns the length of the pattern substring associated with this Part. 544 * This is 0 for some parts. 545 * @return this part's pattern substring length. 546 */ getLength()547 public int getLength() { 548 return length; 549 } 550 551 /** 552 * Returns the pattern string limit (exclusive-end) index associated with this Part. 553 * Convenience method for getIndex()+getLength(). 554 * @return this part's pattern string limit index, same as getIndex()+getLength(). 555 */ getLimit()556 public int getLimit() { 557 return index+length; 558 } 559 560 /** 561 * Returns a value associated with this part. 562 * See the documentation of each part type for details. 563 * @return the part value. 564 */ getValue()565 public int getValue() { 566 return value; 567 } 568 569 /** 570 * Returns the argument type if this part is of type ARG_START or ARG_LIMIT, 571 * otherwise ArgType.NONE. 572 * @return the argument type for this part. 573 */ getArgType()574 public ArgType getArgType() { 575 Type type=getType(); 576 if(type==Type.ARG_START || type==Type.ARG_LIMIT) { 577 return argTypes[value]; 578 } else { 579 return ArgType.NONE; 580 } 581 } 582 583 /** 584 * Part type constants. 585 */ 586 public enum Type { 587 /** 588 * Start of a message pattern (main or nested). 589 * The length is 0 for the top-level message 590 * and for a choice argument sub-message, otherwise 1 for the '{'. 591 * The value indicates the nesting level, starting with 0 for the main message. 592 * <p> 593 * There is always a later MSG_LIMIT part. 594 */ 595 MSG_START, 596 /** 597 * End of a message pattern (main or nested). 598 * The length is 0 for the top-level message and 599 * the last sub-message of a choice argument, 600 * otherwise 1 for the '}' or (in a choice argument style) the '|'. 601 * The value indicates the nesting level, starting with 0 for the main message. 602 */ 603 MSG_LIMIT, 604 /** 605 * Indicates a substring of the pattern string which is to be skipped when formatting. 606 * For example, an apostrophe that begins or ends quoted text 607 * would be indicated with such a part. 608 * The value is undefined and currently always 0. 609 */ 610 SKIP_SYNTAX, 611 /** 612 * Indicates that a syntax character needs to be inserted for auto-quoting. 613 * The length is 0. 614 * The value is the character code of the insertion character. (U+0027=APOSTROPHE) 615 */ 616 INSERT_CHAR, 617 /** 618 * Indicates a syntactic (non-escaped) # symbol in a plural variant. 619 * When formatting, replace this part's substring with the 620 * (value-offset) for the plural argument value. 621 * The value is undefined and currently always 0. 622 */ 623 REPLACE_NUMBER, 624 /** 625 * Start of an argument. 626 * The length is 1 for the '{'. 627 * The value is the ordinal value of the ArgType. Use getArgType(). 628 * <p> 629 * This part is followed by either an ARG_NUMBER or ARG_NAME, 630 * followed by optional argument sub-parts (see ArgType constants) 631 * and finally an ARG_LIMIT part. 632 */ 633 ARG_START, 634 /** 635 * End of an argument. 636 * The length is 1 for the '}'. 637 * The value is the ordinal value of the ArgType. Use getArgType(). 638 */ 639 ARG_LIMIT, 640 /** 641 * The argument number, provided by the value. 642 */ 643 ARG_NUMBER, 644 /** 645 * The argument name. 646 * The value is undefined and currently always 0. 647 */ 648 ARG_NAME, 649 /** 650 * The argument type. 651 * The value is undefined and currently always 0. 652 */ 653 ARG_TYPE, 654 /** 655 * The argument style text. 656 * The value is undefined and currently always 0. 657 */ 658 ARG_STYLE, 659 /** 660 * A selector substring in a "complex" argument style. 661 * The value is undefined and currently always 0. 662 */ 663 ARG_SELECTOR, 664 /** 665 * An integer value, for example the offset or an explicit selector value 666 * in a PluralFormat style. 667 * The part value is the integer value. 668 */ 669 ARG_INT, 670 /** 671 * A numeric value, for example the offset or an explicit selector value 672 * in a PluralFormat style. 673 * The part value is an index into an internal array of numeric values; 674 * use getNumericValue(). 675 */ 676 ARG_DOUBLE; 677 678 /** 679 * Indicates whether this part has a numeric value. 680 * If so, then that numeric value can be retrieved via {@link MessagePattern#getNumericValue(Part)}. 681 * @return true if this part has a numeric value. 682 */ hasNumericValue()683 public boolean hasNumericValue() { 684 return this==ARG_INT || this==ARG_DOUBLE; 685 } 686 } 687 688 /** 689 * @return a string representation of this part. 690 */ 691 @Override toString()692 public String toString() { 693 String valueString=(type==Type.ARG_START || type==Type.ARG_LIMIT) ? 694 getArgType().name() : Integer.toString(value); 695 return type.name()+"("+valueString+")@"+index; 696 } 697 698 /** 699 * @param other another object to compare with. 700 * @return true if this object is equivalent to the other one. 701 */ 702 @Override equals(Object other)703 public boolean equals(Object other) { 704 if(this==other) { 705 return true; 706 } 707 if(other==null || getClass()!=other.getClass()) { 708 return false; 709 } 710 Part o=(Part)other; 711 return 712 type.equals(o.type) && 713 index==o.index && 714 length==o.length && 715 value==o.value && 716 limitPartIndex==o.limitPartIndex; 717 } 718 719 /** 720 * {@inheritDoc} 721 */ 722 @Override hashCode()723 public int hashCode() { 724 return ((type.hashCode()*37+index)*37+length)*37+value; 725 } 726 727 private static final int MAX_LENGTH=0xffff; 728 private static final int MAX_VALUE=Short.MAX_VALUE; 729 730 // Some fields are not final because they are modified during pattern parsing. 731 // After pattern parsing, the parts are effectively immutable. 732 private final Type type; 733 private final int index; 734 private final char length; 735 private short value; 736 private int limitPartIndex; 737 } 738 739 /** 740 * Argument type constants. 741 * Returned by Part.getArgType() for ARG_START and ARG_LIMIT parts. 742 * 743 * Messages nested inside an argument are each delimited by MSG_START and MSG_LIMIT, 744 * with a nesting level one greater than the surrounding message. 745 */ 746 public enum ArgType { 747 /** 748 * The argument has no specified type. 749 */ 750 NONE, 751 /** 752 * The argument has a "simple" type which is provided by the ARG_TYPE part. 753 * An ARG_STYLE part might follow that. 754 */ 755 SIMPLE, 756 /** 757 * The argument is a ChoiceFormat with one or more 758 * ((ARG_INT | ARG_DOUBLE), ARG_SELECTOR, message) tuples. 759 */ 760 CHOICE, 761 /** 762 * The argument is a cardinal-number PluralFormat with an optional ARG_INT or ARG_DOUBLE offset 763 * (e.g., offset:1) 764 * and one or more (ARG_SELECTOR [explicit-value] message) tuples. 765 * If the selector has an explicit value (e.g., =2), then 766 * that value is provided by the ARG_INT or ARG_DOUBLE part preceding the message. 767 * Otherwise the message immediately follows the ARG_SELECTOR. 768 */ 769 PLURAL, 770 /** 771 * The argument is a SelectFormat with one or more (ARG_SELECTOR, message) pairs. 772 */ 773 SELECT, 774 /** 775 * The argument is an ordinal-number PluralFormat 776 * with the same style parts sequence and semantics as {@link ArgType#PLURAL}. 777 */ 778 SELECTORDINAL; 779 780 /** 781 * @return true if the argument type has a plural style part sequence and semantics, 782 * for example {@link ArgType#PLURAL} and {@link ArgType#SELECTORDINAL}. 783 */ hasPluralStyle()784 public boolean hasPluralStyle() { 785 return this == PLURAL || this == SELECTORDINAL; 786 } 787 } 788 789 /** 790 * Creates and returns a copy of this object. 791 * @return a copy of this object (or itself if frozen). 792 */ 793 @Override clone()794 public Object clone() { 795 if(isFrozen()) { 796 return this; 797 } else { 798 return cloneAsThawed(); 799 } 800 } 801 802 /** 803 * Creates and returns an unfrozen copy of this object. 804 * @return a copy of this object. 805 */ 806 @SuppressWarnings("unchecked") cloneAsThawed()807 public MessagePattern cloneAsThawed() { 808 MessagePattern newMsg; 809 try { 810 newMsg=(MessagePattern)super.clone(); 811 } catch (CloneNotSupportedException e) { 812 throw new ICUCloneNotSupportedException(e); 813 } 814 newMsg.parts=(ArrayList<Part>)parts.clone(); 815 if(numericValues!=null) { 816 newMsg.numericValues=(ArrayList<Double>)numericValues.clone(); 817 } 818 newMsg.frozen=false; 819 return newMsg; 820 } 821 822 /** 823 * Freezes this object, making it immutable and thread-safe. 824 * @return this 825 */ freeze()826 public MessagePattern freeze() { 827 frozen=true; 828 return this; 829 } 830 831 /** 832 * Determines whether this object is frozen (immutable) or not. 833 * @return true if this object is frozen. 834 */ isFrozen()835 public boolean isFrozen() { 836 return frozen; 837 } 838 preParse(String pattern)839 private void preParse(String pattern) { 840 if(isFrozen()) { 841 throw new UnsupportedOperationException( 842 "Attempt to parse("+prefix(pattern)+") on frozen MessagePattern instance."); 843 } 844 msg=pattern; 845 hasArgNames=hasArgNumbers=false; 846 needsAutoQuoting=false; 847 parts.clear(); 848 if(numericValues!=null) { 849 numericValues.clear(); 850 } 851 } 852 postParse()853 private void postParse() { 854 // Nothing to be done currently. 855 } 856 parseMessage(int index, int msgStartLength, int nestingLevel, ArgType parentType)857 private int parseMessage(int index, int msgStartLength, int nestingLevel, ArgType parentType) { 858 if(nestingLevel>Part.MAX_VALUE) { 859 throw new IndexOutOfBoundsException(); 860 } 861 int msgStart=parts.size(); 862 addPart(Part.Type.MSG_START, index, msgStartLength, nestingLevel); 863 index+=msgStartLength; 864 while(index<msg.length()) { 865 char c=msg.charAt(index++); 866 if(c=='\'') { 867 if(index==msg.length()) { 868 // The apostrophe is the last character in the pattern. 869 // Add a Part for auto-quoting. 870 addPart(Part.Type.INSERT_CHAR, index, 0, '\''); // value=char to be inserted 871 needsAutoQuoting=true; 872 } else { 873 c=msg.charAt(index); 874 if(c=='\'') { 875 // double apostrophe, skip the second one 876 addPart(Part.Type.SKIP_SYNTAX, index++, 1, 0); 877 } else if( 878 aposMode==ApostropheMode.DOUBLE_REQUIRED || 879 c=='{' || c=='}' || 880 (parentType==ArgType.CHOICE && c=='|') || 881 (parentType.hasPluralStyle() && c=='#') 882 ) { 883 // skip the quote-starting apostrophe 884 addPart(Part.Type.SKIP_SYNTAX, index-1, 1, 0); 885 // find the end of the quoted literal text 886 for(;;) { 887 index=msg.indexOf('\'', index+1); 888 if(index>=0) { 889 if((index+1)<msg.length() && msg.charAt(index+1)=='\'') { 890 // double apostrophe inside quoted literal text 891 // still encodes a single apostrophe, skip the second one 892 addPart(Part.Type.SKIP_SYNTAX, ++index, 1, 0); 893 } else { 894 // skip the quote-ending apostrophe 895 addPart(Part.Type.SKIP_SYNTAX, index++, 1, 0); 896 break; 897 } 898 } else { 899 // The quoted text reaches to the end of the of the message. 900 index=msg.length(); 901 // Add a Part for auto-quoting. 902 addPart(Part.Type.INSERT_CHAR, index, 0, '\''); // value=char to be inserted 903 needsAutoQuoting=true; 904 break; 905 } 906 } 907 } else { 908 // Interpret the apostrophe as literal text. 909 // Add a Part for auto-quoting. 910 addPart(Part.Type.INSERT_CHAR, index, 0, '\''); // value=char to be inserted 911 needsAutoQuoting=true; 912 } 913 } 914 } else if(parentType.hasPluralStyle() && c=='#') { 915 // The unquoted # in a plural message fragment will be replaced 916 // with the (number-offset). 917 addPart(Part.Type.REPLACE_NUMBER, index-1, 1, 0); 918 } else if(c=='{') { 919 index=parseArg(index-1, 1, nestingLevel); 920 } else if((nestingLevel>0 && c=='}') || (parentType==ArgType.CHOICE && c=='|')) { 921 // Finish the message before the terminator. 922 // In a choice style, report the "}" substring only for the following ARG_LIMIT, 923 // not for this MSG_LIMIT. 924 int limitLength=(parentType==ArgType.CHOICE && c=='}') ? 0 : 1; 925 addLimitPart(msgStart, Part.Type.MSG_LIMIT, index-1, limitLength, nestingLevel); 926 if(parentType==ArgType.CHOICE) { 927 // Let the choice style parser see the '}' or '|'. 928 return index-1; 929 } else { 930 // continue parsing after the '}' 931 return index; 932 } 933 } // else: c is part of literal text 934 } 935 if(nestingLevel>0 && !inTopLevelChoiceMessage(nestingLevel, parentType)) { 936 throw new IllegalArgumentException( 937 "Unmatched '{' braces in message "+prefix()); 938 } 939 addLimitPart(msgStart, Part.Type.MSG_LIMIT, index, 0, nestingLevel); 940 return index; 941 } 942 parseArg(int index, int argStartLength, int nestingLevel)943 private int parseArg(int index, int argStartLength, int nestingLevel) { 944 int argStart=parts.size(); 945 ArgType argType=ArgType.NONE; 946 addPart(Part.Type.ARG_START, index, argStartLength, argType.ordinal()); 947 int nameIndex=index=skipWhiteSpace(index+argStartLength); 948 if(index==msg.length()) { 949 throw new IllegalArgumentException( 950 "Unmatched '{' braces in message "+prefix()); 951 } 952 // parse argument name or number 953 index=skipIdentifier(index); 954 int number=parseArgNumber(nameIndex, index); 955 if(number>=0) { 956 int length=index-nameIndex; 957 if(length>Part.MAX_LENGTH || number>Part.MAX_VALUE) { 958 throw new IndexOutOfBoundsException( 959 "Argument number too large: "+prefix(nameIndex)); 960 } 961 hasArgNumbers=true; 962 addPart(Part.Type.ARG_NUMBER, nameIndex, length, number); 963 } else if(number==ARG_NAME_NOT_NUMBER) { 964 int length=index-nameIndex; 965 if(length>Part.MAX_LENGTH) { 966 throw new IndexOutOfBoundsException( 967 "Argument name too long: "+prefix(nameIndex)); 968 } 969 hasArgNames=true; 970 addPart(Part.Type.ARG_NAME, nameIndex, length, 0); 971 } else { // number<-1 (ARG_NAME_NOT_VALID) 972 throw new IllegalArgumentException("Bad argument syntax: "+prefix(nameIndex)); 973 } 974 index=skipWhiteSpace(index); 975 if(index==msg.length()) { 976 throw new IllegalArgumentException( 977 "Unmatched '{' braces in message "+prefix()); 978 } 979 char c=msg.charAt(index); 980 if(c=='}') { 981 // all done 982 } else if(c!=',') { 983 throw new IllegalArgumentException("Bad argument syntax: "+prefix(nameIndex)); 984 } else /* ',' */ { 985 // parse argument type: case-sensitive a-zA-Z 986 int typeIndex=index=skipWhiteSpace(index+1); 987 while(index<msg.length() && isArgTypeChar(msg.charAt(index))) { 988 ++index; 989 } 990 int length=index-typeIndex; 991 index=skipWhiteSpace(index); 992 if(index==msg.length()) { 993 throw new IllegalArgumentException( 994 "Unmatched '{' braces in message "+prefix()); 995 } 996 if(length==0 || ((c=msg.charAt(index))!=',' && c!='}')) { 997 throw new IllegalArgumentException("Bad argument syntax: "+prefix(nameIndex)); 998 } 999 if(length>Part.MAX_LENGTH) { 1000 throw new IndexOutOfBoundsException( 1001 "Argument type name too long: "+prefix(nameIndex)); 1002 } 1003 argType=ArgType.SIMPLE; 1004 if(length==6) { 1005 // case-insensitive comparisons for complex-type names 1006 if(isChoice(typeIndex)) { 1007 argType=ArgType.CHOICE; 1008 } else if(isPlural(typeIndex)) { 1009 argType=ArgType.PLURAL; 1010 } else if(isSelect(typeIndex)) { 1011 argType=ArgType.SELECT; 1012 } 1013 } else if(length==13) { 1014 if(isSelect(typeIndex) && isOrdinal(typeIndex+6)) { 1015 argType=ArgType.SELECTORDINAL; 1016 } 1017 } 1018 // change the ARG_START type from NONE to argType 1019 parts.get(argStart).value=(short)argType.ordinal(); 1020 if(argType==ArgType.SIMPLE) { 1021 addPart(Part.Type.ARG_TYPE, typeIndex, length, 0); 1022 } 1023 // look for an argument style (pattern) 1024 if(c=='}') { 1025 if(argType!=ArgType.SIMPLE) { 1026 throw new IllegalArgumentException( 1027 "No style field for complex argument: "+prefix(nameIndex)); 1028 } 1029 } else /* ',' */ { 1030 ++index; 1031 if(argType==ArgType.SIMPLE) { 1032 index=parseSimpleStyle(index); 1033 } else if(argType==ArgType.CHOICE) { 1034 index=parseChoiceStyle(index, nestingLevel); 1035 } else { 1036 index=parsePluralOrSelectStyle(argType, index, nestingLevel); 1037 } 1038 } 1039 } 1040 // Argument parsing stopped on the '}'. 1041 addLimitPart(argStart, Part.Type.ARG_LIMIT, index, 1, argType.ordinal()); 1042 return index+1; 1043 } 1044 parseSimpleStyle(int index)1045 private int parseSimpleStyle(int index) { 1046 int start=index; 1047 int nestedBraces=0; 1048 while(index<msg.length()) { 1049 char c=msg.charAt(index++); 1050 if(c=='\'') { 1051 // Treat apostrophe as quoting but include it in the style part. 1052 // Find the end of the quoted literal text. 1053 index=msg.indexOf('\'', index); 1054 if(index<0) { 1055 throw new IllegalArgumentException( 1056 "Quoted literal argument style text reaches to the end of the message: "+ 1057 prefix(start)); 1058 } 1059 // skip the quote-ending apostrophe 1060 ++index; 1061 } else if(c=='{') { 1062 ++nestedBraces; 1063 } else if(c=='}') { 1064 if(nestedBraces>0) { 1065 --nestedBraces; 1066 } else { 1067 int length=--index-start; 1068 if(length>Part.MAX_LENGTH) { 1069 throw new IndexOutOfBoundsException( 1070 "Argument style text too long: "+prefix(start)); 1071 } 1072 addPart(Part.Type.ARG_STYLE, start, length, 0); 1073 return index; 1074 } 1075 } // c is part of literal text 1076 } 1077 throw new IllegalArgumentException( 1078 "Unmatched '{' braces in message "+prefix()); 1079 } 1080 parseChoiceStyle(int index, int nestingLevel)1081 private int parseChoiceStyle(int index, int nestingLevel) { 1082 int start=index; 1083 index=skipWhiteSpace(index); 1084 if(index==msg.length() || msg.charAt(index)=='}') { 1085 throw new IllegalArgumentException( 1086 "Missing choice argument pattern in "+prefix()); 1087 } 1088 for(;;) { 1089 // The choice argument style contains |-separated (number, separator, message) triples. 1090 // Parse the number. 1091 int numberIndex=index; 1092 index=skipDouble(index); 1093 int length=index-numberIndex; 1094 if(length==0) { 1095 throw new IllegalArgumentException("Bad choice pattern syntax: "+prefix(start)); 1096 } 1097 if(length>Part.MAX_LENGTH) { 1098 throw new IndexOutOfBoundsException( 1099 "Choice number too long: "+prefix(numberIndex)); 1100 } 1101 parseDouble(numberIndex, index, true); // adds ARG_INT or ARG_DOUBLE 1102 // Parse the separator. 1103 index=skipWhiteSpace(index); 1104 if(index==msg.length()) { 1105 throw new IllegalArgumentException("Bad choice pattern syntax: "+prefix(start)); 1106 } 1107 char c=msg.charAt(index); 1108 if(!(c=='#' || c=='<' || c=='\u2264')) { // U+2264 is <= 1109 throw new IllegalArgumentException( 1110 "Expected choice separator (#<\u2264) instead of '"+c+ 1111 "' in choice pattern "+prefix(start)); 1112 } 1113 addPart(Part.Type.ARG_SELECTOR, index, 1, 0); 1114 // Parse the message fragment. 1115 index=parseMessage(++index, 0, nestingLevel+1, ArgType.CHOICE); 1116 // parseMessage(..., CHOICE) returns the index of the terminator, or msg.length(). 1117 if(index==msg.length()) { 1118 return index; 1119 } 1120 if(msg.charAt(index)=='}') { 1121 if(!inMessageFormatPattern(nestingLevel)) { 1122 throw new IllegalArgumentException( 1123 "Bad choice pattern syntax: "+prefix(start)); 1124 } 1125 return index; 1126 } // else the terminator is '|' 1127 index=skipWhiteSpace(index+1); 1128 } 1129 } 1130 parsePluralOrSelectStyle(ArgType argType, int index, int nestingLevel)1131 private int parsePluralOrSelectStyle(ArgType argType, int index, int nestingLevel) { 1132 int start=index; 1133 boolean isEmpty=true; 1134 boolean hasOther=false; 1135 for(;;) { 1136 // First, collect the selector looking for a small set of terminators. 1137 // It would be a little faster to consider the syntax of each possible 1138 // token right here, but that makes the code too complicated. 1139 index=skipWhiteSpace(index); 1140 boolean eos=index==msg.length(); 1141 if(eos || msg.charAt(index)=='}') { 1142 if(eos==inMessageFormatPattern(nestingLevel)) { 1143 throw new IllegalArgumentException( 1144 "Bad "+ 1145 argType.toString().toLowerCase(Locale.ENGLISH)+ 1146 " pattern syntax: "+prefix(start)); 1147 } 1148 if(!hasOther) { 1149 throw new IllegalArgumentException( 1150 "Missing 'other' keyword in "+ 1151 argType.toString().toLowerCase(Locale.ENGLISH)+ 1152 " pattern in "+prefix()); 1153 } 1154 return index; 1155 } 1156 int selectorIndex=index; 1157 if(argType.hasPluralStyle() && msg.charAt(selectorIndex)=='=') { 1158 // explicit-value plural selector: =double 1159 index=skipDouble(index+1); 1160 int length=index-selectorIndex; 1161 if(length==1) { 1162 throw new IllegalArgumentException( 1163 "Bad "+ 1164 argType.toString().toLowerCase(Locale.ENGLISH)+ 1165 " pattern syntax: "+prefix(start)); 1166 } 1167 if(length>Part.MAX_LENGTH) { 1168 throw new IndexOutOfBoundsException( 1169 "Argument selector too long: "+prefix(selectorIndex)); 1170 } 1171 addPart(Part.Type.ARG_SELECTOR, selectorIndex, length, 0); 1172 parseDouble(selectorIndex+1, index, false); // adds ARG_INT or ARG_DOUBLE 1173 } else { 1174 index=skipIdentifier(index); 1175 int length=index-selectorIndex; 1176 if(length==0) { 1177 throw new IllegalArgumentException( 1178 "Bad "+ 1179 argType.toString().toLowerCase(Locale.ENGLISH)+ 1180 " pattern syntax: "+prefix(start)); 1181 } 1182 // Note: The ':' in "offset:" is just beyond the skipIdentifier() range. 1183 if( argType.hasPluralStyle() && length==6 && index<msg.length() && 1184 msg.regionMatches(selectorIndex, "offset:", 0, 7) 1185 ) { 1186 // plural offset, not a selector 1187 if(!isEmpty) { 1188 throw new IllegalArgumentException( 1189 "Plural argument 'offset:' (if present) must precede key-message pairs: "+ 1190 prefix(start)); 1191 } 1192 // allow whitespace between offset: and its value 1193 int valueIndex=skipWhiteSpace(index+1); // The ':' is at index. 1194 index=skipDouble(valueIndex); 1195 if(index==valueIndex) { 1196 throw new IllegalArgumentException( 1197 "Missing value for plural 'offset:' "+prefix(start)); 1198 } 1199 if((index-valueIndex)>Part.MAX_LENGTH) { 1200 throw new IndexOutOfBoundsException( 1201 "Plural offset value too long: "+prefix(valueIndex)); 1202 } 1203 parseDouble(valueIndex, index, false); // adds ARG_INT or ARG_DOUBLE 1204 isEmpty=false; 1205 continue; // no message fragment after the offset 1206 } else { 1207 // normal selector word 1208 if(length>Part.MAX_LENGTH) { 1209 throw new IndexOutOfBoundsException( 1210 "Argument selector too long: "+prefix(selectorIndex)); 1211 } 1212 addPart(Part.Type.ARG_SELECTOR, selectorIndex, length, 0); 1213 if(msg.regionMatches(selectorIndex, "other", 0, length)) { 1214 hasOther=true; 1215 } 1216 } 1217 } 1218 1219 // parse the message fragment following the selector 1220 index=skipWhiteSpace(index); 1221 if(index==msg.length() || msg.charAt(index)!='{') { 1222 throw new IllegalArgumentException( 1223 "No message fragment after "+ 1224 argType.toString().toLowerCase(Locale.ENGLISH)+ 1225 " selector: "+prefix(selectorIndex)); 1226 } 1227 index=parseMessage(index, 1, nestingLevel+1, argType); 1228 isEmpty=false; 1229 } 1230 } 1231 1232 /** 1233 * Validates and parses an argument name or argument number string. 1234 * This internal method assumes that the input substring is a "pattern identifier". 1235 * @return >=0 if the name is a valid number, 1236 * ARG_NAME_NOT_NUMBER (-1) if it is a "pattern identifier" but not all ASCII digits, 1237 * ARG_NAME_NOT_VALID (-2) if it is neither. 1238 * @see #validateArgumentName(String) 1239 */ parseArgNumber(CharSequence s, int start, int limit)1240 private static int parseArgNumber(CharSequence s, int start, int limit) { 1241 // If the identifier contains only ASCII digits, then it is an argument _number_ 1242 // and must not have leading zeros (except "0" itself). 1243 // Otherwise it is an argument _name_. 1244 if(start>=limit) { 1245 return ARG_NAME_NOT_VALID; 1246 } 1247 int number; 1248 // Defer numeric errors until we know there are only digits. 1249 boolean badNumber; 1250 char c=s.charAt(start++); 1251 if(c=='0') { 1252 if(start==limit) { 1253 return 0; 1254 } else { 1255 number=0; 1256 badNumber=true; // leading zero 1257 } 1258 } else if('1'<=c && c<='9') { 1259 number=c-'0'; 1260 badNumber=false; 1261 } else { 1262 return ARG_NAME_NOT_NUMBER; 1263 } 1264 while(start<limit) { 1265 c=s.charAt(start++); 1266 if('0'<=c && c<='9') { 1267 if(number>=Integer.MAX_VALUE/10) { 1268 badNumber=true; // overflow 1269 } 1270 number=number*10+(c-'0'); 1271 } else { 1272 return ARG_NAME_NOT_NUMBER; 1273 } 1274 } 1275 // There are only ASCII digits. 1276 if(badNumber) { 1277 return ARG_NAME_NOT_VALID; 1278 } else { 1279 return number; 1280 } 1281 } 1282 parseArgNumber(int start, int limit)1283 private int parseArgNumber(int start, int limit) { 1284 return parseArgNumber(msg, start, limit); 1285 } 1286 1287 /** 1288 * Parses a number from the specified message substring. 1289 * @param start start index into the message string 1290 * @param limit limit index into the message string, must be start<limit 1291 * @param allowInfinity true if U+221E is allowed (for ChoiceFormat) 1292 */ parseDouble(int start, int limit, boolean allowInfinity)1293 private void parseDouble(int start, int limit, boolean allowInfinity) { 1294 assert start<limit; 1295 // fake loop for easy exit and single throw statement 1296 for(;;) { 1297 // fast path for small integers and infinity 1298 int value=0; 1299 int isNegative=0; // not boolean so that we can easily add it to value 1300 int index=start; 1301 char c=msg.charAt(index++); 1302 if(c=='-') { 1303 isNegative=1; 1304 if(index==limit) { 1305 break; // no number 1306 } 1307 c=msg.charAt(index++); 1308 } else if(c=='+') { 1309 if(index==limit) { 1310 break; // no number 1311 } 1312 c=msg.charAt(index++); 1313 } 1314 if(c==0x221e) { // infinity 1315 if(allowInfinity && index==limit) { 1316 addArgDoublePart( 1317 isNegative!=0 ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY, 1318 start, limit-start); 1319 return; 1320 } else { 1321 break; 1322 } 1323 } 1324 // try to parse the number as a small integer but fall back to a double 1325 while('0'<=c && c<='9') { 1326 value=value*10+(c-'0'); 1327 if(value>(Part.MAX_VALUE+isNegative)) { 1328 break; // not a small-enough integer 1329 } 1330 if(index==limit) { 1331 addPart(Part.Type.ARG_INT, start, limit-start, isNegative!=0 ? -value : value); 1332 return; 1333 } 1334 c=msg.charAt(index++); 1335 } 1336 // Let Double.parseDouble() throw a NumberFormatException. 1337 double numericValue=Double.parseDouble(msg.substring(start, limit)); 1338 addArgDoublePart(numericValue, start, limit-start); 1339 return; 1340 } 1341 throw new NumberFormatException( 1342 "Bad syntax for numeric value: "+msg.substring(start, limit)); 1343 } 1344 1345 /** 1346 * Appends the s[start, limit[ substring to sb, but with only half of the apostrophes 1347 * according to JDK pattern behavior. 1348 * @hide draft / provisional / internal are hidden on Android 1349 */ 1350 /* package */ static void appendReducedApostrophes(String s, int start, int limit, 1351 StringBuilder sb) { 1352 int doubleApos=-1; 1353 for(;;) { 1354 int i=s.indexOf('\'', start); 1355 if(i<0 || i>=limit) { 1356 sb.append(s, start, limit); 1357 break; 1358 } 1359 if(i==doubleApos) { 1360 // Double apostrophe at start-1 and start==i, append one. 1361 sb.append('\''); 1362 ++start; 1363 doubleApos=-1; 1364 } else { 1365 // Append text between apostrophes and skip this one. 1366 sb.append(s, start, i); 1367 doubleApos=start=i+1; 1368 } 1369 } 1370 } 1371 1372 private int skipWhiteSpace(int index) { 1373 return PatternProps.skipWhiteSpace(msg, index); 1374 } 1375 1376 private int skipIdentifier(int index) { 1377 return PatternProps.skipIdentifier(msg, index); 1378 } 1379 1380 /** 1381 * Skips a sequence of characters that could occur in a double value. 1382 * Does not fully parse or validate the value. 1383 */ 1384 private int skipDouble(int index) { 1385 while(index<msg.length()) { 1386 char c=msg.charAt(index); 1387 // U+221E: Allow the infinity symbol, for ChoiceFormat patterns. 1388 if((c<'0' && "+-.".indexOf(c)<0) || (c>'9' && c!='e' && c!='E' && c!=0x221e)) { 1389 break; 1390 } 1391 ++index; 1392 } 1393 return index; 1394 } 1395 1396 private static boolean isArgTypeChar(int c) { 1397 return ('a'<=c && c<='z') || ('A'<=c && c<='Z'); 1398 } 1399 1400 private boolean isChoice(int index) { 1401 char c; 1402 return 1403 ((c=msg.charAt(index++))=='c' || c=='C') && 1404 ((c=msg.charAt(index++))=='h' || c=='H') && 1405 ((c=msg.charAt(index++))=='o' || c=='O') && 1406 ((c=msg.charAt(index++))=='i' || c=='I') && 1407 ((c=msg.charAt(index++))=='c' || c=='C') && 1408 ((c=msg.charAt(index))=='e' || c=='E'); 1409 } 1410 1411 private boolean isPlural(int index) { 1412 char c; 1413 return 1414 ((c=msg.charAt(index++))=='p' || c=='P') && 1415 ((c=msg.charAt(index++))=='l' || c=='L') && 1416 ((c=msg.charAt(index++))=='u' || c=='U') && 1417 ((c=msg.charAt(index++))=='r' || c=='R') && 1418 ((c=msg.charAt(index++))=='a' || c=='A') && 1419 ((c=msg.charAt(index))=='l' || c=='L'); 1420 } 1421 1422 private boolean isSelect(int index) { 1423 char c; 1424 return 1425 ((c=msg.charAt(index++))=='s' || c=='S') && 1426 ((c=msg.charAt(index++))=='e' || c=='E') && 1427 ((c=msg.charAt(index++))=='l' || c=='L') && 1428 ((c=msg.charAt(index++))=='e' || c=='E') && 1429 ((c=msg.charAt(index++))=='c' || c=='C') && 1430 ((c=msg.charAt(index))=='t' || c=='T'); 1431 } 1432 1433 private boolean isOrdinal(int index) { 1434 char c; 1435 return 1436 ((c=msg.charAt(index++))=='o' || c=='O') && 1437 ((c=msg.charAt(index++))=='r' || c=='R') && 1438 ((c=msg.charAt(index++))=='d' || c=='D') && 1439 ((c=msg.charAt(index++))=='i' || c=='I') && 1440 ((c=msg.charAt(index++))=='n' || c=='N') && 1441 ((c=msg.charAt(index++))=='a' || c=='A') && 1442 ((c=msg.charAt(index))=='l' || c=='L'); 1443 } 1444 1445 /** 1446 * @return true if we are inside a MessageFormat (sub-)pattern, 1447 * as opposed to inside a top-level choice/plural/select pattern. 1448 */ 1449 private boolean inMessageFormatPattern(int nestingLevel) { 1450 return nestingLevel>0 || parts.get(0).type==Part.Type.MSG_START; 1451 } 1452 1453 /** 1454 * @return true if we are in a MessageFormat sub-pattern 1455 * of a top-level ChoiceFormat pattern. 1456 */ 1457 private boolean inTopLevelChoiceMessage(int nestingLevel, ArgType parentType) { 1458 return 1459 nestingLevel==1 && 1460 parentType==ArgType.CHOICE && 1461 parts.get(0).type!=Part.Type.MSG_START; 1462 } 1463 1464 private void addPart(Part.Type type, int index, int length, int value) { 1465 parts.add(new Part(type, index, length, value)); 1466 } 1467 1468 private void addLimitPart(int start, Part.Type type, int index, int length, int value) { 1469 parts.get(start).limitPartIndex=parts.size(); 1470 addPart(type, index, length, value); 1471 } 1472 1473 private void addArgDoublePart(double numericValue, int start, int length) { 1474 int numericIndex; 1475 if(numericValues==null) { 1476 numericValues=new ArrayList<Double>(); 1477 numericIndex=0; 1478 } else { 1479 numericIndex=numericValues.size(); 1480 if(numericIndex>Part.MAX_VALUE) { 1481 throw new IndexOutOfBoundsException("Too many numeric values"); 1482 } 1483 } 1484 numericValues.add(numericValue); 1485 addPart(Part.Type.ARG_DOUBLE, start, length, numericIndex); 1486 } 1487 1488 private static final int MAX_PREFIX_LENGTH=24; 1489 1490 /** 1491 * Returns a prefix of s.substring(start). Used for Exception messages. 1492 * @param s 1493 * @param start start index in s 1494 * @return s.substring(start) or a prefix of that 1495 */ 1496 private static String prefix(String s, int start) { 1497 StringBuilder prefix=new StringBuilder(MAX_PREFIX_LENGTH+20); 1498 if(start==0) { 1499 prefix.append("\""); 1500 } else { 1501 prefix.append("[at pattern index ").append(start).append("] \""); 1502 } 1503 int substringLength=s.length()-start; 1504 if(substringLength<=MAX_PREFIX_LENGTH) { 1505 prefix.append(start==0 ? s : s.substring(start)); 1506 } else { 1507 int limit=start+MAX_PREFIX_LENGTH-4; 1508 if(Character.isHighSurrogate(s.charAt(limit-1))) { 1509 // remove lead surrogate from the end of the prefix 1510 --limit; 1511 } 1512 prefix.append(s, start, limit).append(" ..."); 1513 } 1514 return prefix.append("\"").toString(); 1515 } 1516 1517 private static String prefix(String s) { 1518 return prefix(s, 0); 1519 } 1520 1521 private String prefix(int start) { 1522 return prefix(msg, start); 1523 } 1524 1525 private String prefix() { 1526 return prefix(msg, 0); 1527 } 1528 1529 private ApostropheMode aposMode; 1530 private String msg; 1531 private ArrayList<Part> parts=new ArrayList<Part>(); 1532 private ArrayList<Double> numericValues; 1533 private boolean hasArgNames; 1534 private boolean hasArgNumbers; 1535 private boolean needsAutoQuoting; 1536 private volatile boolean frozen; 1537 1538 private static final ApostropheMode defaultAposMode= 1539 ApostropheMode.valueOf( 1540 ICUConfig.get("android.icu.text.MessagePattern.ApostropheMode", "DOUBLE_OPTIONAL")); 1541 1542 private static final ArgType[] argTypes=ArgType.values(); 1543 } 1544