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