• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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