1 package org.unicode.cldr.draft; 2 3 /** 4 * Class that permits quick edits; does insertions/deletions much faster, in general, than StringBuilder. This is based 5 * on the fact 6 * that such changes are generally not random; instead they tend to happen more often in either nearby or successive 7 * locations. 8 * The structure of the class keeps 2 buffers; one for the front and one for the end. 9 * 10 * @author markdavis 11 */ 12 // TODO investigate whether keeping the future in reverse order is the right approach in terms of performance. 13 public class GapString implements CharSequence { 14 private static final int GROWTH_INCREMENT = 15; 15 private static final int GROWTH_FACTOR = 3; 16 private char[] buffer = new char[10]; 17 private int pastLength = 0; 18 private int gapLength = 10; 19 GapString()20 public GapString() { 21 } 22 GapString(CharSequence s)23 public GapString(CharSequence s) { 24 insert(0, s); 25 } 26 charAt(int index)27 public char charAt(int index) { 28 try { 29 return buffer[index < pastLength ? index : index + gapLength]; 30 } catch (ArrayIndexOutOfBoundsException e) { 31 throw new StringIndexOutOfBoundsException(index); 32 } 33 } 34 length()35 public int length() { 36 return buffer.length - gapLength; 37 } 38 subSequence(int start, int end)39 public CharSequence subSequence(int start, int end) { 40 // TODO optimize this 41 return toString().subSequence(start, end); 42 } 43 toString()44 public String toString() { 45 StringBuilder b = new StringBuilder(); 46 // check to see whether second argument is length or count 47 b.append(buffer, 0, pastLength); 48 final int futureStart = pastLength + gapLength; 49 b.append(buffer, futureStart, buffer.length - futureStart); 50 return b.toString(); 51 } 52 insert(int index, CharSequence s, int start, int end)53 public GapString insert(int index, CharSequence s, int start, int end) { 54 if (s instanceof String) { 55 return insert(index, (String) s, start, end); 56 } 57 final int gapNeeded = end - start; 58 if (pastLength != index) { 59 shiftTo(index, gapNeeded); 60 } else { 61 final int growthNeeded = gapNeeded - gapLength; 62 if (growthNeeded > 0) { 63 growToLength((buffer.length + growthNeeded) * GROWTH_FACTOR + GROWTH_INCREMENT); 64 } 65 } 66 for (int i = start; i < end; ++i) { 67 buffer[pastLength++] = s.charAt(i); 68 } 69 gapLength -= gapNeeded; 70 return this; 71 } 72 insert(int index, CharSequence s)73 public GapString insert(int index, CharSequence s) { 74 return insert(index, s, 0, s.length()); 75 } 76 insert(int index, String s)77 public GapString insert(int index, String s) { 78 return insert(index, s, 0, s.length()); 79 } 80 insert(int index, String s, int start, int end)81 public GapString insert(int index, String s, int start, int end) { 82 final int gapNeeded = end - start; 83 if (pastLength != index) { 84 shiftTo(index, gapNeeded); 85 } else { 86 final int growthNeeded = gapNeeded - gapLength; 87 if (growthNeeded > 0) { 88 growToLength((buffer.length + growthNeeded) * GROWTH_FACTOR + GROWTH_INCREMENT); 89 } 90 } 91 s.getChars(start, end, buffer, pastLength); 92 pastLength += (end - start); 93 gapLength -= gapNeeded; 94 return this; 95 } 96 insert(int index, char[] s)97 public GapString insert(int index, char[] s) { 98 return insert(index, s, 0, s.length); 99 } 100 insert(int index, char[] s, int start, int end)101 public GapString insert(int index, char[] s, int start, int end) { 102 final int gapNeeded = end - start; 103 if (pastLength != index) { 104 shiftTo(index, gapNeeded); 105 } else { 106 final int growthNeeded = gapNeeded - gapLength; 107 if (growthNeeded > 0) { 108 growToLength((buffer.length + growthNeeded) * GROWTH_FACTOR + GROWTH_INCREMENT); 109 } 110 } 111 System.arraycopy(s, start, buffer, pastLength, gapNeeded); 112 pastLength += (end - start); 113 gapLength -= gapNeeded; 114 return this; 115 } 116 insert(int index, char s)117 public GapString insert(int index, char s) { 118 if (pastLength != index || pastLength < index + 1) { 119 shiftTo(index, 1); 120 } 121 buffer[pastLength++] = s; 122 gapLength -= 1; 123 return this; 124 } 125 append(boolean x)126 public GapString append(boolean x) { 127 return insert(length(), String.valueOf(x)); 128 } 129 append(char x)130 public GapString append(char x) { 131 return insert(length(), x); 132 } 133 append(char[] x)134 public GapString append(char[] x) { 135 return insert(length(), x); 136 } 137 append(char[] x, int start, int end)138 public GapString append(char[] x, int start, int end) { 139 return insert(length(), x, start, end); 140 } 141 append(double x)142 public GapString append(double x) { 143 return insert(length(), String.valueOf(x)); 144 } 145 append(float x)146 public GapString append(float x) { 147 return insert(length(), String.valueOf(x)); 148 } 149 append(int x)150 public GapString append(int x) { 151 return insert(length(), String.valueOf(x)); 152 } 153 append(CharSequence x, int start, int end)154 public GapString append(CharSequence x, int start, int end) { 155 return insert(length(), x, start, end); 156 } 157 append(Object x)158 public GapString append(Object x) { 159 return insert(length(), String.valueOf(x)); 160 } 161 append(long x)162 public GapString append(long x) { 163 return insert(length(), String.valueOf(x)); 164 } 165 appendCodePoint(int x)166 public GapString appendCodePoint(int x) { 167 return insertCodePoint(length(), x); 168 } 169 insertCodePoint(int index, int x)170 private GapString insertCodePoint(int index, int x) { 171 if (x < 0x10000) { 172 return insert(index, (char) x); 173 } 174 return insert(index, Character.toChars(x)); // rare, so inefficiency doesn't matter 175 } 176 append(String s)177 public GapString append(String s) { 178 return insert(buffer.length - gapLength, s, 0, s.length()); 179 } 180 append(CharSequence string)181 public GapString append(CharSequence string) { 182 return insert(buffer.length - gapLength, string); 183 } 184 delete(int start, int end)185 public GapString delete(int start, int end) { 186 // if our deletion includes the gap, we can shortcut this 187 // we just reset the pastLength and gap 188 if (pastLength >= start && pastLength < end) { 189 pastLength = start; 190 } else { 191 // TODO There is a possible optimization, to only move enough to get to one end or the other. 192 // However, I don't know whether it would be worth it or not: have to test. 193 if (pastLength != start) { 194 shiftTo(start, 0); 195 } 196 } 197 gapLength += (end - start); 198 return this; 199 } 200 replace(int start, int end, CharSequence other, int otherStart, int otherEnd)201 public GapString replace(int start, int end, CharSequence other, int otherStart, int otherEnd) { 202 delete(start, end); 203 return insert(start, other, otherStart, otherEnd); 204 } 205 compact()206 public GapString compact() { 207 if (gapLength > 0) { 208 growToLength(buffer.length - gapLength); // remove any gap 209 } 210 return this; 211 } 212 equals(Object other)213 public boolean equals(Object other) { 214 try { 215 return equals((CharSequence) other); 216 } catch (Exception e) { 217 return false; 218 } 219 } 220 equals(CharSequence other)221 public boolean equals(CharSequence other) { 222 final int len = buffer.length - gapLength; 223 if (other.length() != len) { 224 return false; 225 } 226 int i = 0; 227 for (; i < pastLength; ++i) { 228 if (buffer[i] != other.charAt(i)) { 229 return false; 230 } 231 } 232 int j = i; 233 i += gapLength; 234 for (; i < buffer.length; ++i) { 235 if (buffer[i] != other.charAt(j++)) { 236 return false; 237 } 238 } 239 return true; 240 } 241 hashCode()242 public int hashCode() { 243 int result = 0; 244 int i = 0; 245 for (; i < pastLength; ++i) { 246 result = 37 * result + buffer[i]; 247 } 248 i += gapLength; 249 for (; i < buffer.length; ++i) { 250 result = 37 * result + buffer[i]; 251 } 252 return result; 253 } 254 255 // ======== PRIVATES =========== 256 257 /** 258 * This utility function just shifts the gap, so that it starts at newPastLength. The logical contents 259 * of the string are unchanged. 260 */ shiftTo(int newPastLength, int gapNeeded)261 private void shiftTo(int newPastLength, int gapNeeded) { 262 int growth = gapNeeded - gapLength; 263 if (growth > 0) { 264 growToLength((buffer.length + growth) * GROWTH_FACTOR + GROWTH_INCREMENT); 265 } 266 267 int newMinusOldPastLength = newPastLength - pastLength; 268 if (newMinusOldPastLength == 0) { 269 return; 270 } 271 if (newMinusOldPastLength > 0) { 272 System.arraycopy(buffer, pastLength + gapLength, buffer, pastLength, newMinusOldPastLength); 273 } else { 274 System.arraycopy(buffer, newPastLength, buffer, pastLength + gapLength + newMinusOldPastLength, 275 -newMinusOldPastLength); 276 } 277 pastLength = newPastLength; 278 } 279 280 /** 281 * This utility function just grows the gap (and thus the storage). The logical contents 282 * of the string are unchanged. 283 */ growToLength(int neededLength)284 private void growToLength(int neededLength) { 285 char[] temp = new char[neededLength]; 286 System.arraycopy(buffer, 0, temp, 0, pastLength); 287 final int futureStart = pastLength + gapLength; 288 final int futureLength = buffer.length - futureStart; 289 System.arraycopy(buffer, futureStart, temp, neededLength - futureLength, futureLength); 290 gapLength += neededLength - buffer.length; 291 buffer = temp; 292 } 293 294 } 295