SimplePatternFormatter.java revision 82027afe36d2dbe419417f025716dc57c89ee0a4
17935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert/*
27935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *******************************************************************************
382027afe36d2dbe419417f025716dc57c89ee0a4Markus Scherer * Copyright (C) 2014-2015, International Business Machines Corporation and
482027afe36d2dbe419417f025716dc57c89ee0a4Markus Scherer * 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) {
5882027afe36d2dbe419417f025716dc57c89ee0a4Markus Scherer        return compileMinMaxPlaceholders(pattern, 0, Integer.MAX_VALUE);
5982027afe36d2dbe419417f025716dc57c89ee0a4Markus Scherer    }
6082027afe36d2dbe419417f025716dc57c89ee0a4Markus Scherer
6182027afe36d2dbe419417f025716dc57c89ee0a4Markus Scherer    /**
6282027afe36d2dbe419417f025716dc57c89ee0a4Markus Scherer     * Compiles a string.
6382027afe36d2dbe419417f025716dc57c89ee0a4Markus Scherer     * @param pattern The string.
6482027afe36d2dbe419417f025716dc57c89ee0a4Markus Scherer     * @param min The pattern must have at least this many placeholders.
6582027afe36d2dbe419417f025716dc57c89ee0a4Markus Scherer     * @param max The pattern must have at most this many placeholders.
6682027afe36d2dbe419417f025716dc57c89ee0a4Markus Scherer     * @return the new SimplePatternFormatter object.
6782027afe36d2dbe419417f025716dc57c89ee0a4Markus Scherer     */
6882027afe36d2dbe419417f025716dc57c89ee0a4Markus Scherer    public static SimplePatternFormatter compileMinMaxPlaceholders(String pattern, int min, int max) {
697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        PlaceholdersBuilder placeholdersBuilder = new PlaceholdersBuilder();
707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        PlaceholderIdBuilder idBuilder =  new PlaceholderIdBuilder();
717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        StringBuilder newPattern = new StringBuilder();
727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        State state = State.INIT;
737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (int i = 0; i < pattern.length(); i++) {
747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            char ch = pattern.charAt(i);
757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            switch (state) {
767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            case INIT:
777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (ch == 0x27) {
787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    state = State.APOSTROPHE;
797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                } else if (ch == '{') {
807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    state = State.PLACEHOLDER;
817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    idBuilder.reset();
827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                } else {
837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    newPattern.append(ch);
847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                break;
867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            case APOSTROPHE:
877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (ch == 0x27) {
887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    newPattern.append("'");
897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                } else if (ch == '{') {
907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    newPattern.append("{");
917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                } else {
927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    newPattern.append("'");
937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    newPattern.append(ch);
947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                state = State.INIT;
967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                break;
977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            case PLACEHOLDER:
987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (ch >= '0' && ch <= '9') {
997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    idBuilder.add(ch);
1007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                } else if (ch == '}' && idBuilder.isValid()) {
1017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    placeholdersBuilder.add(idBuilder.getId(), newPattern.length());
1027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    state = State.INIT;
1037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                } else {
1047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    newPattern.append('{');
1057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    idBuilder.appendTo(newPattern);
1067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    newPattern.append(ch);
1077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    state = State.INIT;
1087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
1097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                break;
1107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            default:
1117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                throw new IllegalStateException();
1127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
1137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
1147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        switch (state) {
1157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        case INIT:
1167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            break;
1177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        case APOSTROPHE:
1187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            newPattern.append("'");
1197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            break;
1207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        case PLACEHOLDER:
1217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            newPattern.append('{');
1227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            idBuilder.appendTo(newPattern);
1237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            break;
1247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        default:
1257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            throw new IllegalStateException();
1267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
12782027afe36d2dbe419417f025716dc57c89ee0a4Markus Scherer        if (placeholdersBuilder.getPlaceholderCount() < min) {
12882027afe36d2dbe419417f025716dc57c89ee0a4Markus Scherer            throw new IllegalArgumentException(
12982027afe36d2dbe419417f025716dc57c89ee0a4Markus Scherer                    "Fewer than minimum " + min + " placeholders in pattern \"" + pattern + "\"");
13082027afe36d2dbe419417f025716dc57c89ee0a4Markus Scherer        }
13182027afe36d2dbe419417f025716dc57c89ee0a4Markus Scherer        if (placeholdersBuilder.getPlaceholderCount() > max) {
13282027afe36d2dbe419417f025716dc57c89ee0a4Markus Scherer            throw new IllegalArgumentException(
13382027afe36d2dbe419417f025716dc57c89ee0a4Markus Scherer                    "More than maximum " + max + " placeholders in pattern \"" + pattern + "\"");
13482027afe36d2dbe419417f025716dc57c89ee0a4Markus Scherer        }
1357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return new SimplePatternFormatter(newPattern.toString(), placeholdersBuilder);
1367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
1377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
1397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Returns the max placeholder ID + 1.
1407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
1417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public int getPlaceholderCount() {
1427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return placeholderCount;
1437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
1447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
1467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Formats the given values.
1477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
1487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public String format(CharSequence... values) {
149f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        return formatAndAppend(new StringBuilder(), null, values).toString();
1507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
1517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
1537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Formats the given values.
1547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
155f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     * @param appendTo the result appended here.
1567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param offsets position of first value in appendTo stored in offsets[0];
1577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *   second in offsets[1]; third in offsets[2] etc. An offset of -1 means that the
1587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *   corresponding value is not in appendTo. offsets.length and values.length may
159f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     *   differ. If offsets.length < values.length then only the first offsets are written out;
160f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     *   If offsets.length > values.length then the extra offsets get -1.
161f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     *   If caller is not interested in offsets, caller may pass null here.
162f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     * @param values the placeholder values. A placeholder value may not be the same object as
163f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     *   appendTo.
1647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return appendTo
1657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
166f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert    public StringBuilder formatAndAppend(
1677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            StringBuilder appendTo, int[] offsets, CharSequence... values) {
1687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (values.length < placeholderCount) {
1697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            throw new IllegalArgumentException("Too few values.");
1707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
171f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        PlaceholderValues placeholderValues = new PlaceholderValues(values);
172f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        if (placeholderValues.isAppendToInAnyIndexExcept(appendTo, -1)) {
173f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            throw new IllegalArgumentException("Parameter values cannot be the same as appendTo.");
174f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        }
175f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        formatReturningOffsetLength(appendTo, offsets, placeholderValues);
176f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        return appendTo;
177f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert    }
178f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert
179f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert    /**
180f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     * Formats the given values.
181f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     *
182f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     * @param result The result is stored here overwriting any previously stored value.
183f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     * @param offsets position of first value in result stored in offsets[0];
184f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     *   second in offsets[1]; third in offsets[2] etc. An offset of -1 means that the
185f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     *   corresponding value is not in result. offsets.length and values.length may
186f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     *   differ. If offsets.length < values.length then only the first offsets are written out;
187f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     *   If offsets.length > values.length then the extra offsets get -1.
188f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     *   If caller is not interested in offsets, caller may pass null here.
189f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     * @param values the placeholder values. A placeholder value may be result itself in which case
190f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     *   The previous value of result is used.
191f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     * @return result
192f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     */
193f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert    public StringBuilder formatAndReplace(
194f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            StringBuilder result, int[] offsets, CharSequence... values) {
195f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        if (values.length < placeholderCount) {
196f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            throw new IllegalArgumentException("Too few values.");
197f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        }
198f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        PlaceholderValues placeholderValues = new PlaceholderValues(values);
199f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        int placeholderAtStart = getUniquePlaceholderAtStart();
200f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert
201f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        // If patterns starts with a placeholder and the value for that placeholder
202f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        // is result, then we can may be able optimize by just appending to result.
203f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        if (placeholderAtStart >= 0 && values[placeholderAtStart] == result) {
204f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert
205f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            // If result is the value for other placeholders, call off optimization.
206f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            if (placeholderValues.isAppendToInAnyIndexExcept(result, placeholderAtStart)) {
207f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert                placeholderValues.snapshotAppendTo(result);
208f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert                result.setLength(0);
209f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert                formatReturningOffsetLength(result, offsets, placeholderValues);
210f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert                return result;
211f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            }
212f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert
213f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            // Otherwise we can optimize
214f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            int offsetLength = formatReturningOffsetLength(result, offsets, placeholderValues);
215f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert
216f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            // We have to make the offset for the placeholderAtStart placeholder be 0.
217f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            // Otherwise it would be the length of the previous value of result.
218f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            if (offsetLength > placeholderAtStart) {
219f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert                offsets[placeholderAtStart] = 0;
220f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            }
221f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            return result;
222f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        }
223f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        if (placeholderValues.isAppendToInAnyIndexExcept(result, -1)) {
224f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            placeholderValues.snapshotAppendTo(result);
225f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        }
226f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        result.setLength(0);
227f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        formatReturningOffsetLength(result, offsets, placeholderValues);
228f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        return result;
229f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert    }
230f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert
231f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert    /**
232f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     * Formats this object using values {0}, {1} etc. Note that this is
233f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     * not the same as the original pattern string used to build this object.
234f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     */
235f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert    @Override
236f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert    public String toString() {
237f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        String[] values = new String[this.getPlaceholderCount()];
238f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        for (int i = 0; i < values.length; i++) {
239f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            values[i] = String.format("{%d}", i);
240f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        }
241f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        return formatAndAppend(new StringBuilder(), null, values).toString();
242f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert    }
243f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert
244f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert    /**
245f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     * Returns this pattern with none of the placeholders.
246f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     */
247f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert    public String getPatternWithNoPlaceholders() {
248f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        return patternWithoutPlaceholders;
249f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert    }
250f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert
251f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert    /**
252f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     * Just like format, but uses placeholder values exactly as they are.
253f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     * A placeholder value that is the same object as appendTo is treated
254f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     * as the empty string. In addition, returns the length of the offsets
255f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     * array. Returns 0 if offsets is null.
256f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     */
257f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert    private int formatReturningOffsetLength(
258f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            StringBuilder appendTo,
259f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            int[] offsets,
260f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            PlaceholderValues values) {
2617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        int offsetLen = offsets == null ? 0 : offsets.length;
2627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (int i = 0; i < offsetLen; i++) {
2637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            offsets[i] = -1;
2647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
2657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (placeholderIdsOrderedByOffset.length == 0) {
2667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            appendTo.append(patternWithoutPlaceholders);
267f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            return offsetLen;
2687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
269f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        appendTo.append(
270f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert                patternWithoutPlaceholders,
271f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert                0,
272f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert                placeholderIdsOrderedByOffset[0]);
273f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        setPlaceholderOffset(
274f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert                placeholderIdsOrderedByOffset[1],
275f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert                appendTo.length(),
276f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert                offsets,
277f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert                offsetLen);
278f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        CharSequence placeholderValue = values.get(placeholderIdsOrderedByOffset[1]);
279f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        if (placeholderValue != appendTo) {
280f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            appendTo.append(placeholderValue);
2817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
2827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (int i = 2; i < placeholderIdsOrderedByOffset.length; i += 2) {
2837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            appendTo.append(
2847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    patternWithoutPlaceholders,
2857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    placeholderIdsOrderedByOffset[i - 2],
2867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    placeholderIdsOrderedByOffset[i]);
2877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            setPlaceholderOffset(
2887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    placeholderIdsOrderedByOffset[i + 1],
2897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    appendTo.length(),
2907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    offsets,
2917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    offsetLen);
292f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            placeholderValue = values.get(placeholderIdsOrderedByOffset[i + 1]);
293f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            if (placeholderValue != appendTo) {
294f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert                appendTo.append(placeholderValue);
295f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            }
2967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
2977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        appendTo.append(
2987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                patternWithoutPlaceholders,
2997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                placeholderIdsOrderedByOffset[placeholderIdsOrderedByOffset.length - 2],
3007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                patternWithoutPlaceholders.length());
301f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        return offsetLen;
3027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
3037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
304f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert
3057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
306f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     * Returns the placeholder at the beginning of this pattern (e.g 3 for placeholder {3}).
307f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     * Returns -1 if the beginning of pattern is text or if the placeholder at beginning
308f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     * of this pattern is used again elsewhere in pattern.
3097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
310f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert    private int getUniquePlaceholderAtStart() {
311f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        if (placeholderIdsOrderedByOffset.length == 0
312f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert                || firstPlaceholderReused || placeholderIdsOrderedByOffset[0] != 0) {
313f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            return -1;
3147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
315f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        return placeholderIdsOrderedByOffset[1];
3167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
3177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static void setPlaceholderOffset(
3197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int placeholderId, int offset, int[] offsets, int offsetLen) {
3207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (placeholderId < offsetLen) {
3217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            offsets[placeholderId] = offset;
3227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
3237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
3247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static enum State {
3267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        INIT,
3277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        APOSTROPHE,
3287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        PLACEHOLDER,
3297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
3307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static class PlaceholderIdBuilder {
3327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        private int id = 0;
3337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        private int idLen = 0;
3347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public void reset() {
3367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            id = 0;
3377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            idLen = 0;
3387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
3397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public int getId() {
3417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert           return id;
3427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
3437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public void appendTo(StringBuilder appendTo) {
3457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (idLen > 0) {
3467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                appendTo.append(id);
3477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
3487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
3497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public boolean isValid() {
3517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert           return idLen > 0;
3527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
3537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public void add(char ch) {
3557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            id = id * 10 + ch - '0';
3567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            idLen++;
3577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
3587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
3597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static class PlaceholdersBuilder {
3617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        private List<Integer> placeholderIdsOrderedByOffset = new ArrayList<Integer>();
3627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        private int placeholderCount = 0;
363f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        private boolean firstPlaceholderReused = false;
3647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public void add(int placeholderId, int offset) {
3667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            placeholderIdsOrderedByOffset.add(offset);
3677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            placeholderIdsOrderedByOffset.add(placeholderId);
3687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (placeholderId >= placeholderCount) {
3697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                placeholderCount = placeholderId + 1;
3707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
371f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            int len = placeholderIdsOrderedByOffset.size();
372f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            if (len > 2
373f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert                    && placeholderIdsOrderedByOffset.get(len - 1)
374f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert                            .equals(placeholderIdsOrderedByOffset.get(1))) {
375f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert                firstPlaceholderReused = true;
376f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            }
3777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
3787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public int getPlaceholderCount() {
3807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return placeholderCount;
3817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
3827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public int[] getPlaceholderIdsOrderedByOffset() {
3847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int[] result = new int[placeholderIdsOrderedByOffset.size()];
3857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            for (int i = 0; i < result.length; i++) {
3867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                result[i] = placeholderIdsOrderedByOffset.get(i).intValue();
3877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
3887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return result;
3897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
390f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert
391f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        public boolean getFirstPlaceholderReused() {
392f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            return firstPlaceholderReused;
393f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        }
3947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
395f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert
3967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
397f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert     * Represents placeholder values.
3987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
399f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert    private static class PlaceholderValues {
400f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        private final CharSequence[] values;
401f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        private CharSequence appendTo;
402f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        private String appendToCopy;
403f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert
404f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        public PlaceholderValues(CharSequence ...values) {
405f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            this.values = values;
406f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            this.appendTo = null;
407f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            this.appendToCopy = null;
408f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        }
409f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert
410f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        /**
411f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert         * Returns true if appendTo value is at any index besides exceptIndex.
412f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert         */
413f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        public boolean isAppendToInAnyIndexExcept(CharSequence appendTo, int exceptIndex) {
414f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            for (int i = 0; i < values.length; ++i) {
415f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert                if (i != exceptIndex && values[i] == appendTo) {
416f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert                    return true;
417f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert                }
418f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            }
419f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            return false;
420f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        }
421f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert
422f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        /**
423f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert         * For each appendTo value, stores the snapshot of it in its place.
424f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert         */
425f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        public void snapshotAppendTo(CharSequence appendTo) {
426f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            this.appendTo = appendTo;
427f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            this.appendToCopy = appendTo.toString();
428f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        }
429f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert
430f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        /**
431f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert         *  Return placeholder at given index.
432f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert         */
433f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        public CharSequence get(int index) {
434f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            if (appendTo == null || appendTo != values[index]) {
435f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert                return values[index];
436f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            }
437f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            return appendToCopy;
438f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert        }
4397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
440f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert
4417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert}
442