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