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