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