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) 2014-2016, International Business Machines Corporation and 7 * others. All Rights Reserved. 8 ******************************************************************************* 9 */ 10 package ohos.global.icu.impl; 11 12 import java.io.IOException; 13 import java.text.Format; 14 15 import ohos.global.icu.util.ICUUncheckedIOException; 16 17 /** 18 * Formats simple patterns like "{1} was born in {0}". 19 * Internal version of {@link ohos.global.icu.text.SimpleFormatter} 20 * with only static methods, to avoid wrapper objects. 21 * 22 * <p>This class "compiles" pattern strings into a binary format 23 * and implements formatting etc. based on that. 24 * 25 * <p>Format: 26 * Index 0: One more than the highest argument number. 27 * Followed by zero or more arguments or literal-text segments. 28 * 29 * <p>An argument is stored as its number, less than ARG_NUM_LIMIT. 30 * A literal-text segment is stored as its length (at least 1) offset by ARG_NUM_LIMIT, 31 * followed by that many chars. 32 * @hide exposed on OHOS 33 */ 34 public final class SimpleFormatterImpl { 35 /** 36 * Argument numbers must be smaller than this limit. 37 * Text segment lengths are offset by this much. 38 * This is currently the only unused char value in compiled patterns, 39 * except it is the maximum value of the first unit (max arg +1). 40 */ 41 private static final int ARG_NUM_LIMIT = 0x100; 42 private static final char LEN1_CHAR = (char)(ARG_NUM_LIMIT + 1); 43 private static final char LEN2_CHAR = (char)(ARG_NUM_LIMIT + 2); 44 private static final char LEN3_CHAR = (char)(ARG_NUM_LIMIT + 3); 45 /** 46 * Initial and maximum char/UChar value set for a text segment. 47 * Segment length char values are from ARG_NUM_LIMIT+1 to this value here. 48 * Normally 0xffff, but can be as small as ARG_NUM_LIMIT+1 for testing. 49 */ 50 private static final char SEGMENT_LENGTH_ARGUMENT_CHAR = (char)0xffff; 51 /** 52 * Maximum length of a text segment. Longer segments are split into shorter ones. 53 */ 54 private static final int MAX_SEGMENT_LENGTH = SEGMENT_LENGTH_ARGUMENT_CHAR - ARG_NUM_LIMIT; 55 56 /** "Intern" some common patterns. */ 57 private static final String[][] COMMON_PATTERNS = { 58 { "{0} {1}", "\u0002\u0000" + LEN1_CHAR + " \u0001" }, 59 { "{0} ({1})", "\u0002\u0000" + LEN2_CHAR + " (\u0001" + LEN1_CHAR + ')' }, 60 { "{0}, {1}", "\u0002\u0000" + LEN2_CHAR + ", \u0001" }, 61 { "{0} – {1}", "\u0002\u0000" + LEN3_CHAR + " – \u0001" }, // en dash 62 }; 63 64 /** Use only static methods. */ SimpleFormatterImpl()65 private SimpleFormatterImpl() {} 66 67 /** 68 * Creates a compiled form of the pattern string, for use with appropriate static methods. 69 * The number of arguments checked against the given limits is the 70 * highest argument number plus one, not the number of occurrences of arguments. 71 * 72 * @param pattern The pattern string. 73 * @param sb A StringBuilder instance which may or may not be used. 74 * @param min The pattern must have at least this many arguments. 75 * @param max The pattern must have at most this many arguments. 76 * @return The compiled-pattern string. 77 * @throws IllegalArgumentException for bad argument syntax and too few or too many arguments. 78 */ compileToStringMinMaxArguments( CharSequence pattern, StringBuilder sb, int min, int max)79 public static String compileToStringMinMaxArguments( 80 CharSequence pattern, StringBuilder sb, int min, int max) { 81 // Return some precompiled common two-argument patterns. 82 if (min <= 2 && 2 <= max) { 83 for (String[] pair : COMMON_PATTERNS) { 84 if (pair[0].contentEquals(pattern)) { 85 assert pair[1].charAt(0) == 2; 86 return pair[1]; 87 } 88 } 89 } 90 // Parse consistent with MessagePattern, but 91 // - support only simple numbered arguments 92 // - build a simple binary structure into the result string 93 int patternLength = pattern.length(); 94 sb.ensureCapacity(patternLength); 95 // Reserve the first char for the number of arguments. 96 sb.setLength(1); 97 int textLength = 0; 98 int maxArg = -1; 99 boolean inQuote = false; 100 for (int i = 0; i < patternLength;) { 101 char c = pattern.charAt(i++); 102 if (c == '\'') { 103 if (i < patternLength && (c = pattern.charAt(i)) == '\'') { 104 // double apostrophe, skip the second one 105 ++i; 106 } else if (inQuote) { 107 // skip the quote-ending apostrophe 108 inQuote = false; 109 continue; 110 } else if (c == '{' || c == '}') { 111 // Skip the quote-starting apostrophe, find the end of the quoted literal text. 112 ++i; 113 inQuote = true; 114 } else { 115 // The apostrophe is part of literal text. 116 c = '\''; 117 } 118 } else if (!inQuote && c == '{') { 119 if (textLength > 0) { 120 sb.setCharAt(sb.length() - textLength - 1, (char)(ARG_NUM_LIMIT + textLength)); 121 textLength = 0; 122 } 123 int argNumber; 124 if ((i + 1) < patternLength && 125 0 <= (argNumber = pattern.charAt(i) - '0') && argNumber <= 9 && 126 pattern.charAt(i + 1) == '}') { 127 i += 2; 128 } else { 129 // Multi-digit argument number (no leading zero) or syntax error. 130 // MessagePattern permits PatternProps.skipWhiteSpace(pattern, index) 131 // around the number, but this class does not. 132 int argStart = i - 1; 133 argNumber = -1; 134 if (i < patternLength && '1' <= (c = pattern.charAt(i++)) && c <= '9') { 135 argNumber = c - '0'; 136 while (i < patternLength && '0' <= (c = pattern.charAt(i++)) && c <= '9') { 137 argNumber = argNumber * 10 + (c - '0'); 138 if (argNumber >= ARG_NUM_LIMIT) { 139 break; 140 } 141 } 142 } 143 if (argNumber < 0 || c != '}') { 144 throw new IllegalArgumentException( 145 "Argument syntax error in pattern \"" + pattern + 146 "\" at index " + argStart + 147 ": " + pattern.subSequence(argStart, i)); 148 } 149 } 150 if (argNumber > maxArg) { 151 maxArg = argNumber; 152 } 153 sb.append((char)argNumber); 154 continue; 155 } // else: c is part of literal text 156 // Append c and track the literal-text segment length. 157 if (textLength == 0) { 158 // Reserve a char for the length of a new text segment, preset the maximum length. 159 sb.append(SEGMENT_LENGTH_ARGUMENT_CHAR); 160 } 161 sb.append(c); 162 if (++textLength == MAX_SEGMENT_LENGTH) { 163 textLength = 0; 164 } 165 } 166 if (textLength > 0) { 167 sb.setCharAt(sb.length() - textLength - 1, (char)(ARG_NUM_LIMIT + textLength)); 168 } 169 int argCount = maxArg + 1; 170 if (argCount < min) { 171 throw new IllegalArgumentException( 172 "Fewer than minimum " + min + " arguments in pattern \"" + pattern + "\""); 173 } 174 if (argCount > max) { 175 throw new IllegalArgumentException( 176 "More than maximum " + max + " arguments in pattern \"" + pattern + "\""); 177 } 178 sb.setCharAt(0, (char)argCount); 179 return sb.toString(); 180 } 181 182 /** 183 * @param compiledPattern Compiled form of a pattern string. 184 * @return The max argument number + 1. 185 */ getArgumentLimit(String compiledPattern)186 public static int getArgumentLimit(String compiledPattern) { 187 return compiledPattern.charAt(0); 188 } 189 190 /** 191 * Formats the given values. 192 * 193 * @param compiledPattern Compiled form of a pattern string. 194 */ formatCompiledPattern(String compiledPattern, CharSequence... values)195 public static String formatCompiledPattern(String compiledPattern, CharSequence... values) { 196 return formatAndAppend(compiledPattern, new StringBuilder(), null, values).toString(); 197 } 198 199 /** 200 * Formats the not-compiled pattern with the given values. 201 * Equivalent to compileToStringMinMaxArguments() followed by formatCompiledPattern(). 202 * The number of arguments checked against the given limits is the 203 * highest argument number plus one, not the number of occurrences of arguments. 204 * 205 * @param pattern Not-compiled form of a pattern string. 206 * @param min The pattern must have at least this many arguments. 207 * @param max The pattern must have at most this many arguments. 208 * @return The compiled-pattern string. 209 * @throws IllegalArgumentException for bad argument syntax and too few or too many arguments. 210 */ formatRawPattern(String pattern, int min, int max, CharSequence... values)211 public static String formatRawPattern(String pattern, int min, int max, CharSequence... values) { 212 StringBuilder sb = new StringBuilder(); 213 String compiledPattern = compileToStringMinMaxArguments(pattern, sb, min, max); 214 sb.setLength(0); 215 return formatAndAppend(compiledPattern, sb, null, values).toString(); 216 } 217 218 /** 219 * Formats the given values, appending to the appendTo builder. 220 * 221 * @param compiledPattern Compiled form of a pattern string. 222 * @param appendTo Gets the formatted pattern and values appended. 223 * @param offsets offsets[i] receives the offset of where 224 * values[i] replaced pattern argument {i}. 225 * Can be null, or can be shorter or longer than values. 226 * If there is no {i} in the pattern, then offsets[i] is set to -1. 227 * @param values The argument values. 228 * An argument value must not be the same object as appendTo. 229 * values.length must be at least getArgumentLimit(). 230 * Can be null if getArgumentLimit()==0. 231 * @return appendTo 232 */ formatAndAppend( String compiledPattern, StringBuilder appendTo, int[] offsets, CharSequence... values)233 public static StringBuilder formatAndAppend( 234 String compiledPattern, StringBuilder appendTo, int[] offsets, CharSequence... values) { 235 int valuesLength = values != null ? values.length : 0; 236 if (valuesLength < getArgumentLimit(compiledPattern)) { 237 throw new IllegalArgumentException("Too few values."); 238 } 239 return format(compiledPattern, values, appendTo, null, true, offsets); 240 } 241 242 /** 243 * Formats the given values, replacing the contents of the result builder. 244 * May optimize by actually appending to the result if it is the same object 245 * as the value corresponding to the initial argument in the pattern. 246 * 247 * @param compiledPattern Compiled form of a pattern string. 248 * @param result Gets its contents replaced by the formatted pattern and values. 249 * @param offsets offsets[i] receives the offset of where 250 * values[i] replaced pattern argument {i}. 251 * Can be null, or can be shorter or longer than values. 252 * If there is no {i} in the pattern, then offsets[i] is set to -1. 253 * @param values The argument values. 254 * An argument value may be the same object as result. 255 * values.length must be at least getArgumentLimit(). 256 * @return result 257 */ formatAndReplace( String compiledPattern, StringBuilder result, int[] offsets, CharSequence... values)258 public static StringBuilder formatAndReplace( 259 String compiledPattern, StringBuilder result, int[] offsets, CharSequence... values) { 260 int valuesLength = values != null ? values.length : 0; 261 if (valuesLength < getArgumentLimit(compiledPattern)) { 262 throw new IllegalArgumentException("Too few values."); 263 } 264 265 // If the pattern starts with an argument whose value is the same object 266 // as the result, then we keep the result contents and append to it. 267 // Otherwise we replace its contents. 268 int firstArg = -1; 269 // If any non-initial argument value is the same object as the result, 270 // then we first copy its contents and use that instead while formatting. 271 String resultCopy = null; 272 if (getArgumentLimit(compiledPattern) > 0) { 273 for (int i = 1; i < compiledPattern.length();) { 274 int n = compiledPattern.charAt(i++); 275 if (n < ARG_NUM_LIMIT) { 276 if (values[n] == result) { 277 if (i == 2) { 278 firstArg = n; 279 } else if (resultCopy == null) { 280 resultCopy = result.toString(); 281 } 282 } 283 } else { 284 i += n - ARG_NUM_LIMIT; 285 } 286 } 287 } 288 if (firstArg < 0) { 289 result.setLength(0); 290 } 291 return format(compiledPattern, values, result, resultCopy, false, offsets); 292 } 293 294 /** 295 * Returns the pattern text with none of the arguments. 296 * Like formatting with all-empty string values. 297 * 298 * @param compiledPattern Compiled form of a pattern string. 299 */ getTextWithNoArguments(String compiledPattern)300 public static String getTextWithNoArguments(String compiledPattern) { 301 int capacity = compiledPattern.length() - 1 - getArgumentLimit(compiledPattern); 302 StringBuilder sb = new StringBuilder(capacity); 303 for (int i = 1; i < compiledPattern.length();) { 304 int segmentLength = compiledPattern.charAt(i++) - ARG_NUM_LIMIT; 305 if (segmentLength > 0) { 306 int limit = i + segmentLength; 307 sb.append(compiledPattern, i, limit); 308 i = limit; 309 } 310 } 311 return sb.toString(); 312 } 313 314 /** 315 * Returns the length of the pattern text with none of the arguments. 316 * @param compiledPattern Compiled form of a pattern string. 317 * @param codePoints true to count code points; false to count code units. 318 * @return The number of code points or code units. 319 */ getLength(String compiledPattern, boolean codePoints)320 public static int getLength(String compiledPattern, boolean codePoints) { 321 int result = 0; 322 for (int i = 1; i < compiledPattern.length();) { 323 int segmentLength = compiledPattern.charAt(i++) - ARG_NUM_LIMIT; 324 if (segmentLength > 0) { 325 int limit = i + segmentLength; 326 if (codePoints) { 327 result += Character.codePointCount(compiledPattern, i, limit); 328 } else { 329 result += (limit - i); 330 } 331 i = limit; 332 } 333 } 334 return result; 335 } 336 337 /** 338 * Returns the length in code units of the pattern text up until the first argument. 339 * @param compiledPattern Compiled form of a pattern string. 340 * @return The number of code units. 341 */ getPrefixLength(String compiledPattern)342 public static int getPrefixLength(String compiledPattern) { 343 if (compiledPattern.length() == 1) { 344 return 0; 345 } else if (compiledPattern.charAt(0) == 0) { 346 return compiledPattern.length() - 2; 347 } else if (compiledPattern.charAt(1) <= ARG_NUM_LIMIT) { 348 return 0; 349 } else { 350 return compiledPattern.charAt(1) - ARG_NUM_LIMIT; 351 } 352 } 353 354 /** 355 * Special case for using FormattedStringBuilder with patterns with 0 or 1 argument. 356 * 357 * With 1 argument, treat the current contents of the FormattedStringBuilder between 358 * start and end as the argument {0}. Insert the extra strings from compiledPattern 359 * to surround the argument in the output. 360 * 361 * With 0 arguments, overwrite the entire contents of the FormattedStringBuilder 362 * between start and end. 363 * 364 * @param compiledPattern Compiled form of a pattern string. 365 * @param field Field to use when adding chars to the output. 366 * @param start The start index of the argument already in the output string. 367 * @param end The end index of the argument already in the output string. 368 * @param output Destination for formatted output. 369 * @return Net number of characters added to the formatted string. 370 */ formatPrefixSuffix( String compiledPattern, Format.Field field, int start, int end, FormattedStringBuilder output)371 public static int formatPrefixSuffix( 372 String compiledPattern, 373 Format.Field field, 374 int start, 375 int end, 376 FormattedStringBuilder output) { 377 int argLimit = getArgumentLimit(compiledPattern); 378 if (argLimit == 0) { 379 // No arguments in compiled pattern; overwrite the entire segment with our string. 380 return output.splice(start, end, compiledPattern, 2, compiledPattern.length(), field); 381 } else { 382 assert argLimit == 1; 383 int suffixOffset; 384 int length = 0; 385 if (compiledPattern.charAt(1) != '\u0000') { 386 int prefixLength = compiledPattern.charAt(1) - ARG_NUM_LIMIT; 387 length = output.insert(start, compiledPattern, 2, 2 + prefixLength, field); 388 suffixOffset = 3 + prefixLength; 389 } else { 390 suffixOffset = 2; 391 } 392 if (suffixOffset < compiledPattern.length()) { 393 int suffixLength = compiledPattern.charAt(suffixOffset) - ARG_NUM_LIMIT; 394 length += output.insert(end + length, compiledPattern, 1 + suffixOffset, 395 1 + suffixOffset + suffixLength, field); 396 } 397 return length; 398 } 399 } 400 401 /** Internal iterator interface for maximum efficiency. 402 * 403 * Usage boilerplate: 404 * 405 * <pre> 406 * long state = 0; 407 * while (true) { 408 * state = IterInternal.step(state, compiledPattern, output); 409 * if (state == IterInternal.DONE) { 410 * break; 411 * } 412 * int argIndex = IterInternal.getArgIndex(state); 413 * // Append the string corresponding to argIndex to output 414 * } 415 * </pre> 416 * @hide exposed on OHOS 417 * 418 */ 419 public static class IterInternal { 420 public static final long DONE = -1; 421 step(long state, CharSequence compiledPattern, Appendable output)422 public static long step(long state, CharSequence compiledPattern, Appendable output) { 423 int i = (int) (state >>> 32); 424 assert i < compiledPattern.length(); 425 i++; 426 while (i < compiledPattern.length() && compiledPattern.charAt(i) > ARG_NUM_LIMIT) { 427 int limit = i + compiledPattern.charAt(i) + 1 - ARG_NUM_LIMIT; 428 try { 429 output.append(compiledPattern, i + 1, limit); 430 } catch (IOException e) { 431 throw new ICUUncheckedIOException(e); 432 } 433 i = limit; 434 } 435 if (i == compiledPattern.length()) { 436 return DONE; 437 } 438 return (((long) i) << 32) | compiledPattern.charAt(i); 439 } 440 441 public static int getArgIndex(long state) { 442 return (int) state; 443 } 444 } 445 446 private static StringBuilder format( 447 String compiledPattern, CharSequence[] values, 448 StringBuilder result, String resultCopy, boolean forbidResultAsValue, 449 int[] offsets) { 450 int offsetsLength; 451 if (offsets == null) { 452 offsetsLength = 0; 453 } else { 454 offsetsLength = offsets.length; 455 for (int i = 0; i < offsetsLength; i++) { 456 offsets[i] = -1; 457 } 458 } 459 for (int i = 1; i < compiledPattern.length();) { 460 int n = compiledPattern.charAt(i++); 461 if (n < ARG_NUM_LIMIT) { 462 CharSequence value = values[n]; 463 if (value == result) { 464 if (forbidResultAsValue) { 465 throw new IllegalArgumentException("Value must not be same object as result"); 466 } 467 if (i == 2) { 468 // We are appending to result which is also the first value object. 469 if (n < offsetsLength) { 470 offsets[n] = 0; 471 } 472 } else { 473 if (n < offsetsLength) { 474 offsets[n] = result.length(); 475 } 476 result.append(resultCopy); 477 } 478 } else { 479 if (n < offsetsLength) { 480 offsets[n] = result.length(); 481 } 482 result.append(value); 483 } 484 } else { 485 int limit = i + (n - ARG_NUM_LIMIT); 486 result.append(compiledPattern, i, limit); 487 i = limit; 488 } 489 } 490 return result; 491 } 492 } 493