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