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