SimplePatternFormatter.java revision 7935b1839a081ed19ae0d33029ad3c09632a2caa
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;
417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private SimplePatternFormatter(String pattern, PlaceholdersBuilder builder) {
437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        this.patternWithoutPlaceholders = pattern;
447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        this.placeholderIdsOrderedByOffset =
457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                builder.getPlaceholderIdsOrderedByOffset();
467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        this.placeholderCount = builder.getPlaceholderCount();
477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Compiles a string.
517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param pattern The string.
527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return the new SimplePatternFormatter object.
537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public static SimplePatternFormatter compile(CharSequence pattern) {
557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        PlaceholdersBuilder placeholdersBuilder = new PlaceholdersBuilder();
567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        PlaceholderIdBuilder idBuilder =  new PlaceholderIdBuilder();
577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        StringBuilder newPattern = new StringBuilder();
587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        State state = State.INIT;
597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (int i = 0; i < pattern.length(); i++) {
607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            char ch = pattern.charAt(i);
617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            switch (state) {
627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            case INIT:
637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (ch == 0x27) {
647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    state = State.APOSTROPHE;
657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                } else if (ch == '{') {
667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    state = State.PLACEHOLDER;
677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    idBuilder.reset();
687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                } else {
697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    newPattern.append(ch);
707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                break;
727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            case APOSTROPHE:
737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (ch == 0x27) {
747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    newPattern.append("'");
757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                } else if (ch == '{') {
767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    newPattern.append("{");
777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                } else {
787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    newPattern.append("'");
797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    newPattern.append(ch);
807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                state = State.INIT;
827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                break;
837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            case PLACEHOLDER:
847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (ch >= '0' && ch <= '9') {
857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    idBuilder.add(ch);
867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                } else if (ch == '}' && idBuilder.isValid()) {
877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    placeholdersBuilder.add(idBuilder.getId(), newPattern.length());
887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    state = State.INIT;
897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                } else {
907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    newPattern.append('{');
917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    idBuilder.appendTo(newPattern);
927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    newPattern.append(ch);
937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    state = State.INIT;
947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                break;
967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            default:
977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                throw new IllegalStateException();
987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
1007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        switch (state) {
1017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        case INIT:
1027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            break;
1037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        case APOSTROPHE:
1047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            newPattern.append("'");
1057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            break;
1067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        case PLACEHOLDER:
1077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            newPattern.append('{');
1087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            idBuilder.appendTo(newPattern);
1097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            break;
1107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        default:
1117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            throw new IllegalStateException();
1127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
1137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return new SimplePatternFormatter(newPattern.toString(), placeholdersBuilder);
1147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
1167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
1187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Returns the max placeholder ID + 1.
1197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
1207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public int getPlaceholderCount() {
1217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return placeholderCount;
1227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
1237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
1257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Returns true if this instance starts with placeholder with given id.
1267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
1277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public boolean startsWithPlaceholder(int id) {
1287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (placeholderIdsOrderedByOffset.length == 0) {
1297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return false;
1307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
1317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return (placeholderIdsOrderedByOffset[0] == 0 && placeholderIdsOrderedByOffset[1] == id);
1327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
1337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
1357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Formats the given values.
1367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
1377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public String format(CharSequence... values) {
1387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return format(new StringBuilder(), null, values).toString();
1397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
1407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
1427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Formats the given values.
1437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
1447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param appendTo the result appended here. Optimization: If the pattern this object
1457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * represents starts with a placeholder AND appendTo references the value of that same
1467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * placeholder (corresponding values parameter must also be a StringBuilder), then that
1477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * placeholder value is not copied to appendTo (Its already there). If the value of the
1487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * starting placeholder is very large, this optimization can offer huge savings.
1497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param offsets position of first value in appendTo stored in offsets[0];
1507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *   second in offsets[1]; third in offsets[2] etc. An offset of -1 means that the
1517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *   corresponding value is not in appendTo. offsets.length and values.length may
1527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *   differ. If caller is not interested in offsets, caller may pass null here.
1537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param values the values
1547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return appendTo
1557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
1567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public StringBuilder format(
1577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            StringBuilder appendTo, int[] offsets, CharSequence... values) {
1587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (values.length < placeholderCount) {
1597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            throw new IllegalArgumentException("Too few values.");
1607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
1617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        int offsetLen = offsets == null ? 0 : offsets.length;
1627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (int i = 0; i < offsetLen; i++) {
1637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            offsets[i] = -1;
1647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
1657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (placeholderIdsOrderedByOffset.length == 0) {
1667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            appendTo.append(patternWithoutPlaceholders);
1677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return appendTo;
1687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
1697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (placeholderIdsOrderedByOffset[0] > 0 ||
1707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                appendTo != values[placeholderIdsOrderedByOffset[1]]) {
1717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            appendTo.append(
1727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    patternWithoutPlaceholders,
1737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    0,
1747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    placeholderIdsOrderedByOffset[0]);
1757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            setPlaceholderOffset(
1767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    placeholderIdsOrderedByOffset[1],
1777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    appendTo.length(),
1787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    offsets,
1797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    offsetLen);
1807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            appendTo.append(values[placeholderIdsOrderedByOffset[1]]);
1817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        } else {
1827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            setPlaceholderOffset(
1837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    placeholderIdsOrderedByOffset[1],
1847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    0,
1857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    offsets,
1867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    offsetLen);
1877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
1887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (int i = 2; i < placeholderIdsOrderedByOffset.length; i += 2) {
1897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            appendTo.append(
1907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    patternWithoutPlaceholders,
1917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    placeholderIdsOrderedByOffset[i - 2],
1927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    placeholderIdsOrderedByOffset[i]);
1937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            setPlaceholderOffset(
1947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    placeholderIdsOrderedByOffset[i + 1],
1957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    appendTo.length(),
1967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    offsets,
1977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    offsetLen);
1987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            appendTo.append(values[placeholderIdsOrderedByOffset[i + 1]]);
1997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
2007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        appendTo.append(
2017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                patternWithoutPlaceholders,
2027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                placeholderIdsOrderedByOffset[placeholderIdsOrderedByOffset.length - 2],
2037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                patternWithoutPlaceholders.length());
2047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return appendTo;
2057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
2067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
2087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Formats this object using values {0}, {1} etc. Note that this is
2097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * not the same as the original pattern string used to build this object.
2107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
2117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    @Override
2127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public String toString() {
2137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String[] values = new String[this.getPlaceholderCount()];
2147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (int i = 0; i < values.length; i++) {
2157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            values[i] = String.format("{%d}", i);
2167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
2177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return format(new StringBuilder(), null, values).toString();
2187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
2197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static void setPlaceholderOffset(
2217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int placeholderId, int offset, int[] offsets, int offsetLen) {
2227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (placeholderId < offsetLen) {
2237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            offsets[placeholderId] = offset;
2247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
2257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
2267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static enum State {
2287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        INIT,
2297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        APOSTROPHE,
2307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        PLACEHOLDER,
2317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
2327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static class PlaceholderIdBuilder {
2347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        private int id = 0;
2357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        private int idLen = 0;
2367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public void reset() {
2387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            id = 0;
2397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            idLen = 0;
2407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
2417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public int getId() {
2437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert           return id;
2447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
2457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public void appendTo(StringBuilder appendTo) {
2477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (idLen > 0) {
2487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                appendTo.append(id);
2497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
2507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
2517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public boolean isValid() {
2537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert           return idLen > 0;
2547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
2557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public void add(char ch) {
2577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            id = id * 10 + ch - '0';
2587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            idLen++;
2597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
2607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
2617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static class PlaceholdersBuilder {
2637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        private List<Integer> placeholderIdsOrderedByOffset = new ArrayList<Integer>();
2647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        private int placeholderCount = 0;
2657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public void add(int placeholderId, int offset) {
2677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            placeholderIdsOrderedByOffset.add(offset);
2687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            placeholderIdsOrderedByOffset.add(placeholderId);
2697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (placeholderId >= placeholderCount) {
2707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                placeholderCount = placeholderId + 1;
2717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
2727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
2737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public int getPlaceholderCount() {
2757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return placeholderCount;
2767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
2777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public int[] getPlaceholderIdsOrderedByOffset() {
2797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int[] result = new int[placeholderIdsOrderedByOffset.size()];
2807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            for (int i = 0; i < result.length; i++) {
2817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                result[i] = placeholderIdsOrderedByOffset.get(i).intValue();
2827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
2837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return result;
2847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
2857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
2867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
2887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Returns this pattern with none of the placeholders.
2897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
2907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public String getPatternWithNoPlaceholders() {
2917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return patternWithoutPlaceholders;
2927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
2937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert}
294