17935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert/*
27935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *******************************************************************************
37935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * Copyright (C) 2014, International Business Machines Corporation and         *
47935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * others. All Rights Reserved.                                                *
57935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *******************************************************************************
67935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert */
77935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertpackage com.ibm.icu.impl;
87935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
97935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.ArrayList;
107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.List;
117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert/**
137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * Compiled version of a pattern such as "{1} was born in {0}".
147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * <p>
157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * Using SimplePatternFormatter objects is both faster and safer than adhoc replacement
167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * such as <code>pattern.replace("{0}", "Colorado").replace("{1} "Fred");</code>.
177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * They are faster because they are precompiled; they are safer because they
187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * account for curly braces escaped by apostrophe (').
197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *
207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * Placeholders are of the form \{[0-9]+\}. If a curly brace is preceded
217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * by a single quote, it becomes a curly brace instead of the start of a
227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * placeholder. Two single quotes resolve to one single quote.
237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * <p>
247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * SimplePatternFormatter objects are immutable and can be safely cached like strings.
257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * <p>
267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * Example:
277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * <pre>
287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * SimplePatternFormatter fmt = SimplePatternFormatter.compile("{1} '{born} in {0}");
297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *
307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * // Output: "paul {born} in england"
317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * System.out.println(fmt.format("england", "paul"));
327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * </pre>
337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert */
347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertpublic class SimplePatternFormatter {
357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private final String patternWithoutPlaceholders;
367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private final int placeholderCount;
377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // [0] first offset; [1] first placeholderId; [2] second offset;
397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // [3] second placeholderId etc.
407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private final int[] placeholderIdsOrderedByOffset;
41f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert
42f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert    private final boolean firstPlaceholderReused;
437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private SimplePatternFormatter(String pattern, PlaceholdersBuilder builder) {
457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        this.patternWithoutPlaceholders = pattern;
467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        this.placeholderIdsOrderedByOffset =
477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                builder.getPlaceholderIdsOrderedByOffset();
487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        this.placeholderCount = builder.getPlaceholderCount();
49f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        this.firstPlaceholderReused = builder.getFirstPlaceholderReused();
507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Compiles a string.
547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param pattern The string.
557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return the new SimplePatternFormatter object.
567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
57f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert    public static SimplePatternFormatter compile(String pattern) {
587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        PlaceholdersBuilder placeholdersBuilder = new PlaceholdersBuilder();
597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        PlaceholderIdBuilder idBuilder =  new PlaceholderIdBuilder();
607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        StringBuilder newPattern = new StringBuilder();
617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        State state = State.INIT;
627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (int i = 0; i < pattern.length(); i++) {
637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            char ch = pattern.charAt(i);
647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            switch (state) {
657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            case INIT:
667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (ch == 0x27) {
677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    state = State.APOSTROPHE;
687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                } else if (ch == '{') {
697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    state = State.PLACEHOLDER;
707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    idBuilder.reset();
717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                } else {
727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    newPattern.append(ch);
737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                break;
757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            case APOSTROPHE:
767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (ch == 0x27) {
777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    newPattern.append("'");
787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                } else if (ch == '{') {
797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    newPattern.append("{");
807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                } else {
817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    newPattern.append("'");
827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    newPattern.append(ch);
837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                state = State.INIT;
857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                break;
867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            case PLACEHOLDER:
877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (ch >= '0' && ch <= '9') {
887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    idBuilder.add(ch);
897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                } else if (ch == '}' && idBuilder.isValid()) {
907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    placeholdersBuilder.add(idBuilder.getId(), newPattern.length());
917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    state = State.INIT;
927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                } else {
937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    newPattern.append('{');
947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    idBuilder.appendTo(newPattern);
957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    newPattern.append(ch);
967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    state = State.INIT;
977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                break;
997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            default:
1007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                throw new IllegalStateException();
1017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
1027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
1037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        switch (state) {
1047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        case INIT:
1057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            break;
1067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        case APOSTROPHE:
1077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            newPattern.append("'");
1087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            break;
1097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        case PLACEHOLDER:
1107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            newPattern.append('{');
1117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            idBuilder.appendTo(newPattern);
1127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            break;
1137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        default:
1147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            throw new IllegalStateException();
1157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
1167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return new SimplePatternFormatter(newPattern.toString(), placeholdersBuilder);
1177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
1197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
1217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Returns the max placeholder ID + 1.
1227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
1237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public int getPlaceholderCount() {
1247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return placeholderCount;
1257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
1267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
1287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Formats the given values.
1297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
1307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public String format(CharSequence... values) {
131f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        return formatAndAppend(new StringBuilder(), null, values).toString();
1327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
1337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
1357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Formats the given values.
1367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
137f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     * @param appendTo the result appended here.
1387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param offsets position of first value in appendTo stored in offsets[0];
1397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *   second in offsets[1]; third in offsets[2] etc. An offset of -1 means that the
1407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *   corresponding value is not in appendTo. offsets.length and values.length may
141f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     *   differ. If offsets.length < values.length then only the first offsets are written out;
142f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     *   If offsets.length > values.length then the extra offsets get -1.
143f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     *   If caller is not interested in offsets, caller may pass null here.
144f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     * @param values the placeholder values. A placeholder value may not be the same object as
145f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     *   appendTo.
1467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return appendTo
1477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
148f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert    public StringBuilder formatAndAppend(
1497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            StringBuilder appendTo, int[] offsets, CharSequence... values) {
1507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (values.length < placeholderCount) {
1517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            throw new IllegalArgumentException("Too few values.");
1527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
153f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        PlaceholderValues placeholderValues = new PlaceholderValues(values);
154f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        if (placeholderValues.isAppendToInAnyIndexExcept(appendTo, -1)) {
155f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            throw new IllegalArgumentException("Parameter values cannot be the same as appendTo.");
156f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        }
157f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        formatReturningOffsetLength(appendTo, offsets, placeholderValues);
158f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        return appendTo;
159f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert    }
160f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert
161f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert    /**
162f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     * Formats the given values.
163f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     *
164f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     * @param result The result is stored here overwriting any previously stored value.
165f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     * @param offsets position of first value in result stored in offsets[0];
166f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     *   second in offsets[1]; third in offsets[2] etc. An offset of -1 means that the
167f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     *   corresponding value is not in result. offsets.length and values.length may
168f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     *   differ. If offsets.length < values.length then only the first offsets are written out;
169f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     *   If offsets.length > values.length then the extra offsets get -1.
170f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     *   If caller is not interested in offsets, caller may pass null here.
171f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     * @param values the placeholder values. A placeholder value may be result itself in which case
172f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     *   The previous value of result is used.
173f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     * @return result
174f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     */
175f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert    public StringBuilder formatAndReplace(
176f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            StringBuilder result, int[] offsets, CharSequence... values) {
177f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        if (values.length < placeholderCount) {
178f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            throw new IllegalArgumentException("Too few values.");
179f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        }
180f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        PlaceholderValues placeholderValues = new PlaceholderValues(values);
181f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        int placeholderAtStart = getUniquePlaceholderAtStart();
182f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert
183f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        // If patterns starts with a placeholder and the value for that placeholder
184f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        // is result, then we can may be able optimize by just appending to result.
185f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        if (placeholderAtStart >= 0 && values[placeholderAtStart] == result) {
186f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert
187f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            // If result is the value for other placeholders, call off optimization.
188f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            if (placeholderValues.isAppendToInAnyIndexExcept(result, placeholderAtStart)) {
189f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert                placeholderValues.snapshotAppendTo(result);
190f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert                result.setLength(0);
191f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert                formatReturningOffsetLength(result, offsets, placeholderValues);
192f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert                return result;
193f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            }
194f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert
195f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            // Otherwise we can optimize
196f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            int offsetLength = formatReturningOffsetLength(result, offsets, placeholderValues);
197f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert
198f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            // We have to make the offset for the placeholderAtStart placeholder be 0.
199f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            // Otherwise it would be the length of the previous value of result.
200f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            if (offsetLength > placeholderAtStart) {
201f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert                offsets[placeholderAtStart] = 0;
202f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            }
203f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            return result;
204f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        }
205f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        if (placeholderValues.isAppendToInAnyIndexExcept(result, -1)) {
206f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            placeholderValues.snapshotAppendTo(result);
207f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        }
208f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        result.setLength(0);
209f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        formatReturningOffsetLength(result, offsets, placeholderValues);
210f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        return result;
211f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert    }
212f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert
213f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert    /**
214f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     * Formats this object using values {0}, {1} etc. Note that this is
215f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     * not the same as the original pattern string used to build this object.
216f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     */
217f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert    @Override
218f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert    public String toString() {
219f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        String[] values = new String[this.getPlaceholderCount()];
220f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        for (int i = 0; i < values.length; i++) {
221f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            values[i] = String.format("{%d}", i);
222f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        }
223f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        return formatAndAppend(new StringBuilder(), null, values).toString();
224f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert    }
225f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert
226f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert    /**
227f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     * Returns this pattern with none of the placeholders.
228f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     */
229f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert    public String getPatternWithNoPlaceholders() {
230f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        return patternWithoutPlaceholders;
231f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert    }
232f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert
233f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert    /**
234f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     * Just like format, but uses placeholder values exactly as they are.
235f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     * A placeholder value that is the same object as appendTo is treated
236f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     * as the empty string. In addition, returns the length of the offsets
237f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     * array. Returns 0 if offsets is null.
238f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     */
239f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert    private int formatReturningOffsetLength(
240f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            StringBuilder appendTo,
241f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            int[] offsets,
242f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            PlaceholderValues values) {
2437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        int offsetLen = offsets == null ? 0 : offsets.length;
2447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (int i = 0; i < offsetLen; i++) {
2457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            offsets[i] = -1;
2467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
2477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (placeholderIdsOrderedByOffset.length == 0) {
2487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            appendTo.append(patternWithoutPlaceholders);
249f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            return offsetLen;
2507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
251f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        appendTo.append(
252f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert                patternWithoutPlaceholders,
253f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert                0,
254f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert                placeholderIdsOrderedByOffset[0]);
255f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        setPlaceholderOffset(
256f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert                placeholderIdsOrderedByOffset[1],
257f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert                appendTo.length(),
258f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert                offsets,
259f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert                offsetLen);
260f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        CharSequence placeholderValue = values.get(placeholderIdsOrderedByOffset[1]);
261f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        if (placeholderValue != appendTo) {
262f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            appendTo.append(placeholderValue);
2637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
2647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (int i = 2; i < placeholderIdsOrderedByOffset.length; i += 2) {
2657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            appendTo.append(
2667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    patternWithoutPlaceholders,
2677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    placeholderIdsOrderedByOffset[i - 2],
2687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    placeholderIdsOrderedByOffset[i]);
2697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            setPlaceholderOffset(
2707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    placeholderIdsOrderedByOffset[i + 1],
2717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    appendTo.length(),
2727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    offsets,
2737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    offsetLen);
274f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            placeholderValue = values.get(placeholderIdsOrderedByOffset[i + 1]);
275f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            if (placeholderValue != appendTo) {
276f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert                appendTo.append(placeholderValue);
277f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            }
2787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
2797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        appendTo.append(
2807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                patternWithoutPlaceholders,
2817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                placeholderIdsOrderedByOffset[placeholderIdsOrderedByOffset.length - 2],
2827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                patternWithoutPlaceholders.length());
283f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        return offsetLen;
2847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
2857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
286f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert
2877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
288f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     * Returns the placeholder at the beginning of this pattern (e.g 3 for placeholder {3}).
289f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     * Returns -1 if the beginning of pattern is text or if the placeholder at beginning
290f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     * of this pattern is used again elsewhere in pattern.
2917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
292f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert    private int getUniquePlaceholderAtStart() {
293f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        if (placeholderIdsOrderedByOffset.length == 0
294f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert                || firstPlaceholderReused || placeholderIdsOrderedByOffset[0] != 0) {
295f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            return -1;
2967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
297f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        return placeholderIdsOrderedByOffset[1];
2987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
2997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static void setPlaceholderOffset(
3017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int placeholderId, int offset, int[] offsets, int offsetLen) {
3027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (placeholderId < offsetLen) {
3037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            offsets[placeholderId] = offset;
3047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
3057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
3067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static enum State {
3087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        INIT,
3097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        APOSTROPHE,
3107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        PLACEHOLDER,
3117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
3127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static class PlaceholderIdBuilder {
3147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        private int id = 0;
3157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        private int idLen = 0;
3167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public void reset() {
3187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            id = 0;
3197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            idLen = 0;
3207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
3217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public int getId() {
3237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert           return id;
3247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
3257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public void appendTo(StringBuilder appendTo) {
3277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (idLen > 0) {
3287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                appendTo.append(id);
3297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
3307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
3317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public boolean isValid() {
3337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert           return idLen > 0;
3347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
3357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public void add(char ch) {
3377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            id = id * 10 + ch - '0';
3387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            idLen++;
3397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
3407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
3417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static class PlaceholdersBuilder {
3437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        private List<Integer> placeholderIdsOrderedByOffset = new ArrayList<Integer>();
3447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        private int placeholderCount = 0;
345f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        private boolean firstPlaceholderReused = false;
3467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public void add(int placeholderId, int offset) {
3487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            placeholderIdsOrderedByOffset.add(offset);
3497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            placeholderIdsOrderedByOffset.add(placeholderId);
3507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (placeholderId >= placeholderCount) {
3517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                placeholderCount = placeholderId + 1;
3527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
353f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            int len = placeholderIdsOrderedByOffset.size();
354f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            if (len > 2
355f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert                    && placeholderIdsOrderedByOffset.get(len - 1)
356f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert                            .equals(placeholderIdsOrderedByOffset.get(1))) {
357f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert                firstPlaceholderReused = true;
358f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            }
3597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
3607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public int getPlaceholderCount() {
3627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return placeholderCount;
3637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
3647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public int[] getPlaceholderIdsOrderedByOffset() {
3667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int[] result = new int[placeholderIdsOrderedByOffset.size()];
3677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            for (int i = 0; i < result.length; i++) {
3687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                result[i] = placeholderIdsOrderedByOffset.get(i).intValue();
3697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
3707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return result;
3717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
372f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert
373f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        public boolean getFirstPlaceholderReused() {
374f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            return firstPlaceholderReused;
375f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        }
3767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
377f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert
3787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
379f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     * Represents placeholder values.
3807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
381f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert    private static class PlaceholderValues {
382f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        private final CharSequence[] values;
383f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        private CharSequence appendTo;
384f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        private String appendToCopy;
385f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert
386f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        public PlaceholderValues(CharSequence ...values) {
387f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            this.values = values;
388f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            this.appendTo = null;
389f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            this.appendToCopy = null;
390f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        }
391f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert
392f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        /**
393f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert         * Returns true if appendTo value is at any index besides exceptIndex.
394f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert         */
395f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        public boolean isAppendToInAnyIndexExcept(CharSequence appendTo, int exceptIndex) {
396f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            for (int i = 0; i < values.length; ++i) {
397f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert                if (i != exceptIndex && values[i] == appendTo) {
398f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert                    return true;
399f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert                }
400f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            }
401f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            return false;
402f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        }
403f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert
404f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        /**
405f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert         * For each appendTo value, stores the snapshot of it in its place.
406f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert         */
407f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        public void snapshotAppendTo(CharSequence appendTo) {
408f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            this.appendTo = appendTo;
409f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            this.appendToCopy = appendTo.toString();
410f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        }
411f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert
412f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        /**
413f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert         *  Return placeholder at given index.
414f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert         */
415f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        public CharSequence get(int index) {
416f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            if (appendTo == null || appendTo != values[index]) {
417f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert                return values[index];
418f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            }
419f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            return appendToCopy;
420f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        }
4217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
422f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert
4237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert}
424