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 */ 10package android.icu.impl; 11 12/** 13 * Formats simple patterns like "{1} was born in {0}". 14 * Internal version of {@link android.icu.text.SimpleFormatter} 15 * with only static methods, to avoid wrapper objects. 16 * 17 * <p>This class "compiles" pattern strings into a binary format 18 * and implements formatting etc. based on that. 19 * 20 * <p>Format: 21 * Index 0: One more than the highest argument number. 22 * Followed by zero or more arguments or literal-text segments. 23 * 24 * <p>An argument is stored as its number, less than ARG_NUM_LIMIT. 25 * A literal-text segment is stored as its length (at least 1) offset by ARG_NUM_LIMIT, 26 * followed by that many chars. 27 * @hide Only a subset of ICU is exposed in Android 28 */ 29public final class SimpleFormatterImpl { 30 /** 31 * Argument numbers must be smaller than this limit. 32 * Text segment lengths are offset by this much. 33 * This is currently the only unused char value in compiled patterns, 34 * except it is the maximum value of the first unit (max arg +1). 35 */ 36 private static final int ARG_NUM_LIMIT = 0x100; 37 private static final char LEN1_CHAR = (char)(ARG_NUM_LIMIT + 1); 38 private static final char LEN2_CHAR = (char)(ARG_NUM_LIMIT + 2); 39 private static final char LEN3_CHAR = (char)(ARG_NUM_LIMIT + 3); 40 /** 41 * Initial and maximum char/UChar value set for a text segment. 42 * Segment length char values are from ARG_NUM_LIMIT+1 to this value here. 43 * Normally 0xffff, but can be as small as ARG_NUM_LIMIT+1 for testing. 44 */ 45 private static final char SEGMENT_LENGTH_ARGUMENT_CHAR = (char)0xffff; 46 /** 47 * Maximum length of a text segment. Longer segments are split into shorter ones. 48 */ 49 private static final int MAX_SEGMENT_LENGTH = SEGMENT_LENGTH_ARGUMENT_CHAR - ARG_NUM_LIMIT; 50 51 /** "Intern" some common patterns. */ 52 private static final String[][] COMMON_PATTERNS = { 53 { "{0} {1}", "\u0002\u0000" + LEN1_CHAR + " \u0001" }, 54 { "{0} ({1})", "\u0002\u0000" + LEN2_CHAR + " (\u0001" + LEN1_CHAR + ')' }, 55 { "{0}, {1}", "\u0002\u0000" + LEN2_CHAR + ", \u0001" }, 56 { "{0} – {1}", "\u0002\u0000" + LEN3_CHAR + " – \u0001" }, // en dash 57 }; 58 59 /** Use only static methods. */ 60 private SimpleFormatterImpl() {} 61 62 /** 63 * Creates a compiled form of the pattern string, for use with appropriate static methods. 64 * The number of arguments checked against the given limits is the 65 * highest argument number plus one, not the number of occurrences of arguments. 66 * 67 * @param pattern The pattern string. 68 * @param min The pattern must have at least this many arguments. 69 * @param max The pattern must have at most this many arguments. 70 * @return The compiled-pattern string. 71 * @throws IllegalArgumentException for bad argument syntax and too few or too many arguments. 72 */ 73 public static String compileToStringMinMaxArguments( 74 CharSequence pattern, StringBuilder sb, int min, int max) { 75 // Return some precompiled common two-argument patterns. 76 if (min <= 2 && 2 <= max) { 77 for (String[] pair : COMMON_PATTERNS) { 78 if (pair[0].contentEquals(pattern)) { 79 assert pair[1].charAt(0) == 2; 80 return pair[1]; 81 } 82 } 83 } 84 // Parse consistent with MessagePattern, but 85 // - support only simple numbered arguments 86 // - build a simple binary structure into the result string 87 int patternLength = pattern.length(); 88 sb.ensureCapacity(patternLength); 89 // Reserve the first char for the number of arguments. 90 sb.setLength(1); 91 int textLength = 0; 92 int maxArg = -1; 93 boolean inQuote = false; 94 for (int i = 0; i < patternLength;) { 95 char c = pattern.charAt(i++); 96 if (c == '\'') { 97 if (i < patternLength && (c = pattern.charAt(i)) == '\'') { 98 // double apostrophe, skip the second one 99 ++i; 100 } else if (inQuote) { 101 // skip the quote-ending apostrophe 102 inQuote = false; 103 continue; 104 } else if (c == '{' || c == '}') { 105 // Skip the quote-starting apostrophe, find the end of the quoted literal text. 106 ++i; 107 inQuote = true; 108 } else { 109 // The apostrophe is part of literal text. 110 c = '\''; 111 } 112 } else if (!inQuote && c == '{') { 113 if (textLength > 0) { 114 sb.setCharAt(sb.length() - textLength - 1, (char)(ARG_NUM_LIMIT + textLength)); 115 textLength = 0; 116 } 117 int argNumber; 118 if ((i + 1) < patternLength && 119 0 <= (argNumber = pattern.charAt(i) - '0') && argNumber <= 9 && 120 pattern.charAt(i + 1) == '}') { 121 i += 2; 122 } else { 123 // Multi-digit argument number (no leading zero) or syntax error. 124 // MessagePattern permits PatternProps.skipWhiteSpace(pattern, index) 125 // around the number, but this class does not. 126 int argStart = i - 1; 127 argNumber = -1; 128 if (i < patternLength && '1' <= (c = pattern.charAt(i++)) && c <= '9') { 129 argNumber = c - '0'; 130 while (i < patternLength && '0' <= (c = pattern.charAt(i++)) && c <= '9') { 131 argNumber = argNumber * 10 + (c - '0'); 132 if (argNumber >= ARG_NUM_LIMIT) { 133 break; 134 } 135 } 136 } 137 if (argNumber < 0 || c != '}') { 138 throw new IllegalArgumentException( 139 "Argument syntax error in pattern \"" + pattern + 140 "\" at index " + argStart + 141 ": " + pattern.subSequence(argStart, i)); 142 } 143 } 144 if (argNumber > maxArg) { 145 maxArg = argNumber; 146 } 147 sb.append((char)argNumber); 148 continue; 149 } // else: c is part of literal text 150 // Append c and track the literal-text segment length. 151 if (textLength == 0) { 152 // Reserve a char for the length of a new text segment, preset the maximum length. 153 sb.append(SEGMENT_LENGTH_ARGUMENT_CHAR); 154 } 155 sb.append(c); 156 if (++textLength == MAX_SEGMENT_LENGTH) { 157 textLength = 0; 158 } 159 } 160 if (textLength > 0) { 161 sb.setCharAt(sb.length() - textLength - 1, (char)(ARG_NUM_LIMIT + textLength)); 162 } 163 int argCount = maxArg + 1; 164 if (argCount < min) { 165 throw new IllegalArgumentException( 166 "Fewer than minimum " + min + " arguments in pattern \"" + pattern + "\""); 167 } 168 if (argCount > max) { 169 throw new IllegalArgumentException( 170 "More than maximum " + max + " arguments in pattern \"" + pattern + "\""); 171 } 172 sb.setCharAt(0, (char)argCount); 173 return sb.toString(); 174 } 175 176 /** 177 * @param compiledPattern Compiled form of a pattern string. 178 * @return The max argument number + 1. 179 */ 180 public static int getArgumentLimit(String compiledPattern) { 181 return compiledPattern.charAt(0); 182 } 183 184 /** 185 * Formats the given values. 186 * 187 * @param compiledPattern Compiled form of a pattern string. 188 */ 189 public static String formatCompiledPattern(String compiledPattern, CharSequence... values) { 190 return formatAndAppend(compiledPattern, new StringBuilder(), null, values).toString(); 191 } 192 193 /** 194 * Formats the not-compiled pattern with the given values. 195 * Equivalent to compileToStringMinMaxArguments() followed by formatCompiledPattern(). 196 * The number of arguments checked against the given limits is the 197 * highest argument number plus one, not the number of occurrences of arguments. 198 * 199 * @param pattern Not-compiled form of a pattern string. 200 * @param min The pattern must have at least this many arguments. 201 * @param max The pattern must have at most this many arguments. 202 * @return The compiled-pattern string. 203 * @throws IllegalArgumentException for bad argument syntax and too few or too many arguments. 204 */ 205 public static String formatRawPattern(String pattern, int min, int max, CharSequence... values) { 206 StringBuilder sb = new StringBuilder(); 207 String compiledPattern = compileToStringMinMaxArguments(pattern, sb, min, max); 208 sb.setLength(0); 209 return formatAndAppend(compiledPattern, sb, null, values).toString(); 210 } 211 212 /** 213 * Formats the given values, appending to the appendTo builder. 214 * 215 * @param compiledPattern Compiled form of a pattern string. 216 * @param appendTo Gets the formatted pattern and values appended. 217 * @param offsets offsets[i] receives the offset of where 218 * values[i] replaced pattern argument {i}. 219 * Can be null, or can be shorter or longer than values. 220 * If there is no {i} in the pattern, then offsets[i] is set to -1. 221 * @param values The argument values. 222 * An argument value must not be the same object as appendTo. 223 * values.length must be at least getArgumentLimit(). 224 * Can be null if getArgumentLimit()==0. 225 * @return appendTo 226 */ 227 public static StringBuilder formatAndAppend( 228 String compiledPattern, StringBuilder appendTo, int[] offsets, CharSequence... values) { 229 int valuesLength = values != null ? values.length : 0; 230 if (valuesLength < getArgumentLimit(compiledPattern)) { 231 throw new IllegalArgumentException("Too few values."); 232 } 233 return format(compiledPattern, values, appendTo, null, true, offsets); 234 } 235 236 /** 237 * Formats the given values, replacing the contents of the result builder. 238 * May optimize by actually appending to the result if it is the same object 239 * as the value corresponding to the initial argument in the pattern. 240 * 241 * @param compiledPattern Compiled form of a pattern string. 242 * @param result Gets its contents replaced by the formatted pattern and values. 243 * @param offsets offsets[i] receives the offset of where 244 * values[i] replaced pattern argument {i}. 245 * Can be null, or can be shorter or longer than values. 246 * If there is no {i} in the pattern, then offsets[i] is set to -1. 247 * @param values The argument values. 248 * An argument value may be the same object as result. 249 * values.length must be at least getArgumentLimit(). 250 * @return result 251 */ 252 public static StringBuilder formatAndReplace( 253 String compiledPattern, StringBuilder result, int[] offsets, CharSequence... values) { 254 int valuesLength = values != null ? values.length : 0; 255 if (valuesLength < getArgumentLimit(compiledPattern)) { 256 throw new IllegalArgumentException("Too few values."); 257 } 258 259 // If the pattern starts with an argument whose value is the same object 260 // as the result, then we keep the result contents and append to it. 261 // Otherwise we replace its contents. 262 int firstArg = -1; 263 // If any non-initial argument value is the same object as the result, 264 // then we first copy its contents and use that instead while formatting. 265 String resultCopy = null; 266 if (getArgumentLimit(compiledPattern) > 0) { 267 for (int i = 1; i < compiledPattern.length();) { 268 int n = compiledPattern.charAt(i++); 269 if (n < ARG_NUM_LIMIT) { 270 if (values[n] == result) { 271 if (i == 2) { 272 firstArg = n; 273 } else if (resultCopy == null) { 274 resultCopy = result.toString(); 275 } 276 } 277 } else { 278 i += n - ARG_NUM_LIMIT; 279 } 280 } 281 } 282 if (firstArg < 0) { 283 result.setLength(0); 284 } 285 return format(compiledPattern, values, result, resultCopy, false, offsets); 286 } 287 288 /** 289 * Returns the pattern text with none of the arguments. 290 * Like formatting with all-empty string values. 291 * 292 * @param compiledPattern Compiled form of a pattern string. 293 */ 294 public static String getTextWithNoArguments(String compiledPattern) { 295 int capacity = compiledPattern.length() - 1 - getArgumentLimit(compiledPattern); 296 StringBuilder sb = new StringBuilder(capacity); 297 for (int i = 1; i < compiledPattern.length();) { 298 int segmentLength = compiledPattern.charAt(i++) - ARG_NUM_LIMIT; 299 if (segmentLength > 0) { 300 int limit = i + segmentLength; 301 sb.append(compiledPattern, i, limit); 302 i = limit; 303 } 304 } 305 return sb.toString(); 306 } 307 308 private static StringBuilder format( 309 String compiledPattern, CharSequence[] values, 310 StringBuilder result, String resultCopy, boolean forbidResultAsValue, 311 int[] offsets) { 312 int offsetsLength; 313 if (offsets == null) { 314 offsetsLength = 0; 315 } else { 316 offsetsLength = offsets.length; 317 for (int i = 0; i < offsetsLength; i++) { 318 offsets[i] = -1; 319 } 320 } 321 for (int i = 1; i < compiledPattern.length();) { 322 int n = compiledPattern.charAt(i++); 323 if (n < ARG_NUM_LIMIT) { 324 CharSequence value = values[n]; 325 if (value == result) { 326 if (forbidResultAsValue) { 327 throw new IllegalArgumentException("Value must not be same object as result"); 328 } 329 if (i == 2) { 330 // We are appending to result which is also the first value object. 331 if (n < offsetsLength) { 332 offsets[n] = 0; 333 } 334 } else { 335 if (n < offsetsLength) { 336 offsets[n] = result.length(); 337 } 338 result.append(resultCopy); 339 } 340 } else { 341 if (n < offsetsLength) { 342 offsets[n] = result.length(); 343 } 344 result.append(value); 345 } 346 } else { 347 int limit = i + (n - ARG_NUM_LIMIT); 348 result.append(compiledPattern, i, limit); 349 i = limit; 350 } 351 } 352 return result; 353 } 354} 355