• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

evolvedbinary / elemental / 982

29 Apr 2025 08:34PM UTC coverage: 56.409% (+0.007%) from 56.402%
982

push

circleci

adamretter
[feature] Improve README.md badges

28451 of 55847 branches covered (50.94%)

Branch coverage included in aggregate %.

77468 of 131924 relevant lines covered (58.72%)

0.59 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.3
/exist-core/src/main/java/org/exist/util/CodePointString.java
1
/*
2
 * Elemental
3
 * Copyright (C) 2024, Evolved Binary Ltd
4
 *
5
 * admin@evolvedbinary.com
6
 * https://www.evolvedbinary.com | https://www.elemental.xyz
7
 *
8
 * Use of this software is governed by the Business Source License 1.1
9
 * included in the LICENSE file and at www.mariadb.com/bsl11.
10
 *
11
 * Change Date: 2028-04-27
12
 *
13
 * On the date above, in accordance with the Business Source License, use
14
 * of this software will be governed by the Apache License, Version 2.0.
15
 *
16
 * Additional Use Grant: Production use of the Licensed Work for a permitted
17
 * purpose. A Permitted Purpose is any purpose other than a Competing Use.
18
 * A Competing Use means making the Software available to others in a commercial
19
 * product or service that: substitutes for the Software; substitutes for any
20
 * other product or service we offer using the Software that exists as of the
21
 * date we make the Software available; or offers the same or substantially
22
 * similar functionality as the Software.
23
 */
24
package org.exist.util;
25

26
import net.jcip.annotations.NotThreadSafe;
27

28
import java.util.Arrays;
29

30
/**
31
 * Representation of a Unicode String.
32
 *
33
 * The String is a series of Unicode code-points.
34
 * Each Unicode code-point is an int value.
35
 *
36
 * Note that this is a mutable string implementation!
37
 *
38
 * @author Adam Retter <a href="mailto:adam@evolvedbinary.com">Adam Retter</a>
39
 */
40
@NotThreadSafe
41
public class CodePointString {
42
    private int[] codePoints;
43

44
    // TODO(AR) change resizing of codePoints so it isn't linear
45

46
    /**
47
     * Construct a Code Point String from a java.lang.String.
48
     *
49
     * @param string the Java String
50
     */
51
    public CodePointString(final String string) {
1✔
52
        this.codePoints = string.codePoints().toArray();
1✔
53
    }
1✔
54

55
    /**
56
     * Copy constructor.
57
     *
58
     * @param other the other Code Point String
59
     */
60
    public CodePointString(final CodePointString other) {
×
61
        this.codePoints = Arrays.copyOf(other.codePoints, other.codePoints.length);
×
62
    }
×
63

64
    public CodePointString() {
1✔
65
        this.codePoints = new int[0];
1✔
66
    }
1✔
67

68
    /**
69
     * Make a copy of this Code Point string.
70
     *
71
     * @return a copy of this Code Point String.
72
     */
73
    public CodePointString copy() {
74
        return new CodePointString(this);
×
75
    }
76

77
    /**
78
     * Replace the first instance of <code>oldCodePoint</code> with <code>newCodePoint</code>.
79
     *
80
     * @param oldCodePoint The code point to replace
81
     * @param newCodePoint The replacement code point
82
     *
83
     * @return this
84
     */
85
    public CodePointString replaceFirst(final int oldCodePoint, final int newCodePoint) {
86
        for (int i = 0; i < codePoints.length; i++) {
1✔
87
            if (codePoints[i] == oldCodePoint) {
1✔
88
                codePoints[i] = newCodePoint;
1✔
89
                break;
1✔
90
            }
91
        }
92
        return this;
1✔
93
    }
94

95
    /**
96
     * Replace all instances of <code>oldCodePoint</code> with <code>newCodePoint</code>.
97
     *
98
     * @param oldCodePoint The code point to replace all instances of
99
     * @param newCodePoint The replacement code point
100
     *
101
     * @return this
102
     */
103
    public CodePointString replaceAll(final int oldCodePoint, final int newCodePoint) {
104
        for (int i = 0; i < codePoints.length; i++) {
1✔
105
            if (codePoints[i] == oldCodePoint) {
1✔
106
                codePoints[i] = newCodePoint;
1✔
107
            }
108
        }
109
        return this;
1✔
110
    }
111

112
    /**
113
     * Find the index of a code point.
114
     *
115
     * @param codePoint The code point to find
116
     *
117
     * @return the index of the code point in the
118
     *     string, or -1 if it is not found
119
     */
120
    public int indexOf(final int codePoint) {
121
        for (int i = 0; i < codePoints.length; i++) {
1✔
122
            if (codePoints[i] == codePoint) {
1✔
123
                return i;
1✔
124
            }
125
        }
126

127
        return -1;
1✔
128
    }
129

130
    /**
131
     * Determines if this string contains a code point.
132
     *
133
     * @param codePoint The code point to find
134
     *
135
     * @return true if the code point is found, false otherwise
136
     */
137
    public boolean contains(final int codePoint) {
138
        return indexOf(codePoint) >= 0;
1✔
139
    }
140

141
    /**
142
     * Append a code point to this string.
143
     *
144
     * @param codePoint the code point to append.
145
     * @return this
146
     */
147
    public CodePointString append(final int codePoint) {
148
        this.codePoints = Arrays.copyOf(codePoints, codePoints.length + 1);
1✔
149
        this.codePoints[codePoints.length - 1] = codePoint;
1✔
150
        return this;
1✔
151
    }
152

153
    /**
154
     * Append a code point string to this string.
155
     *
156
     * @param other the code point string to append.
157
     * @return this
158
     */
159
    public CodePointString append(final CodePointString other) {
160
        final int len = codePoints.length;
1✔
161
        this.codePoints = Arrays.copyOf(codePoints, len + other.length());
1✔
162
        System.arraycopy(other.codePoints, 0, codePoints, len, other.length());
1✔
163
        return this;
1✔
164
    }
165

166
    /**
167
     * Left trim this string.
168
     *
169
     * Removes n code points from the start of this string.
170
     *
171
     * @param codePoint the code point to trim starting from index 0
172
     *
173
     * @return this
174
     */
175
    public CodePointString leftTrim(final int codePoint) {
176
        if (codePoints.length > 0) {
1✔
177
            int i = 0;
1✔
178
            for (; i < codePoints.length && codePoints[i] == codePoint; i++) {
1!
179
            }
180

181
            if (i > 0) {
1✔
182
                this.codePoints = Arrays.copyOfRange(codePoints, i, codePoints.length);
1✔
183
            }
184
        }
185
        return this;
1✔
186
    }
187

188
    /**
189
     * Right trim this string.
190
     *
191
     * Removes n code points from the end of this string.
192
     *
193
     * @param codePoint the code point to trim starting from index {@link #length()} - 1
194
     *
195
     * @return this
196
     */
197
    public CodePointString rightTrim(final int codePoint) {
198
        if (codePoints.length > 0) {
1✔
199
            int i = codePoints.length - 1;
1✔
200
            for (; i >= 0 && codePoints[i] == codePoint; i--) {
1!
201
            }
202
            this.codePoints = Arrays.copyOfRange(codePoints, 0, i + 1);
1✔
203
        }
204
        return this;
1✔
205
    }
206

207
    /**
208
     * Transform a region of code points within the string
209
     *
210
     * Replaces any code point <code>c</code> between <code>fromOldCodePoint</code> (inclusive) to
211
     * <code>toOldCodePoint</code> (inclusive), with <code>fromNewCodePoint + (c - fromOldCodePoint)</code>.
212
     *
213
     * @param fromOldCodePoint the starting code point of the region to transform
214
     * @param toOldCodePoint the ending code point of the region to transform
215
     * @param fromNewCodePoint the new code point for the transformation
216
     *
217
     * @return this
218
     */
219
    public CodePointString transform(final int fromOldCodePoint, final int toOldCodePoint, final int fromNewCodePoint) {
220
        for (int i = 0; i < codePoints.length; i++) {
1✔
221
            final int c = codePoints[i];
1✔
222
            if (c >= fromOldCodePoint && c <= toOldCodePoint) {
1✔
223
                codePoints[i] = fromNewCodePoint + (c - fromOldCodePoint);
1✔
224
            }
225
        }
226
        return this;
1✔
227
    }
228

229
    /**
230
     * Pads the left of the string with <code>len</code> <code>codePoint</code>(s).
231
     *
232
     * @param codePoint the code point to use for the padding
233
     * @param len the length of the padding
234
     *
235
     * @return this
236
     */
237
    public CodePointString leftPad(final int codePoint, final int len) {
238
        if (len > 0) {
1✔
239
            final int[] newCodePoints = new int[codePoints.length + len];
1✔
240
            Arrays.fill(newCodePoints, 0, len, codePoint);
1✔
241
            System.arraycopy(codePoints, 0, newCodePoints, len, codePoints.length);
1✔
242
            this.codePoints = newCodePoints;
1✔
243
        }
244
        return this;
1✔
245
    }
246

247
    /**
248
     * Pads the right of the string with <code>len</code> <code>codePoint</code>(s).
249
     *
250
     * @param codePoint the code point to use for the padding
251
     * @param len the length of the padding
252
     *
253
     * @return this
254
     */
255
    public CodePointString rightPad(final int codePoint, final int len) {
256
        if (len > 0) {
1✔
257
            final int origLen = codePoints.length;
1✔
258
            final int newLen = codePoints.length + len;
1✔
259
            this.codePoints = Arrays.copyOf(codePoints, newLen);
1✔
260
            Arrays.fill(this.codePoints, origLen, newLen, codePoint);
1✔
261
        }
262
        return this;
1✔
263
    }
264

265
    /**
266
     * Insert a code point into the string.
267
     *
268
     * @param index the offset at which to insert the code point
269
     * @param codePoint the code point to insert
270
     *
271
     * @return this
272
     *
273
     * @throws IndexOutOfBoundsException if <code>index &lt; 0 || index &gt; getLength()</code>
274
     */
275
    public CodePointString insert(final int index, final int codePoint) {
276
        if (index < 0 || index > codePoints.length) {
1✔
277
            throw new IndexOutOfBoundsException();
1✔
278
        }
279

280
        final int[] newCodePoints = new int[codePoints.length + 1];
1✔
281
        System.arraycopy(codePoints, 0, newCodePoints, 0, index);
1✔
282
        newCodePoints[index] = codePoint;
1✔
283
        System.arraycopy(codePoints, index, newCodePoints, index + 1, codePoints.length - index);
1✔
284
        this.codePoints = newCodePoints;
1✔
285

286
        return this;
1✔
287
    }
288

289
    /**
290
     * Insert a code point into the string at one or more offsets.
291
     *
292
     * Note that this is NOT the same as calling {@link #insert(int, int)}
293
     * multiple times, as the <code>offsets</code> refer to the positions
294
     * in the string before the first insert is made.
295
     *
296
     * @param indexes the offsets at which to insert the code point
297
     * @param codePoint the code point to insert
298
     *
299
     * @return this
300
     *
301
     * @throws IndexOutOfBoundsException if <code>indexes[i] &lt; 0 || indexes[i] &gt; getLength()</code>
302
     */
303
    public CodePointString insert(final int[] indexes, final int codePoint) {
304
        // first sort the indexes into ascending order
305
        Arrays.sort(indexes);
1✔
306

307
        // only codePoints.length >= offsets > 0
308
        for (final int index : indexes) {
1✔
309
            if (index < 0 || index > codePoints.length) {
1✔
310
                throw new IndexOutOfBoundsException();
1✔
311
            }
312
        }
313

314
        final int[] newCodePoints = Arrays.copyOf(codePoints, codePoints.length + indexes.length);
1✔
315
        for (int i = 0; i < indexes.length; i++) {
1✔
316
            final int index = indexes[i] + i;
1✔
317
            // shift to right
318
            if (newCodePoints.length > 1) {
1✔
319
                System.arraycopy(newCodePoints, index, newCodePoints, index + 1, newCodePoints.length - index - 1);
1✔
320
            }
321
            // insert codepoint
322
            newCodePoints[index] = codePoint;
1✔
323
        }
324

325
        this.codePoints = newCodePoints;
1✔
326
        return this;
1✔
327
    }
328

329
    /**
330
     * Remove the first instance of a code point from the string
331
     *
332
     * @param codePoint the code point to remove
333
     *
334
     * @return this
335
     */
336
    public CodePointString removeFirst(final int codePoint) {
337
        int idx = -1;
1✔
338
        for (int i = 0; i < codePoints.length; i++) {
1✔
339
            if (codePoints[i] == codePoint) {
1✔
340
                idx = i;
1✔
341
                break;
1✔
342
            }
343
        }
344

345
        if (idx > -1) {
1✔
346
            final int[] newCodePoints = new int[codePoints.length - 1];
1✔
347

348
            if (newCodePoints.length > 0) {
1✔
349
                System.arraycopy(codePoints, 0, newCodePoints, 0, idx);
1✔
350
                if (idx + 1 < codePoints.length) {
1✔
351
                    System.arraycopy(codePoints, idx + 1, newCodePoints, idx, newCodePoints.length - idx);
1✔
352
                }
353
            }
354

355
            this.codePoints = newCodePoints;
1✔
356
        }
357
        return this;
1✔
358
    }
359

360
    /**
361
     * Return the number of code points in the string.
362
     *
363
     * @return the number of code points in the string
364
     */
365
    public int length() {
366
        return codePoints.length;
1✔
367
    }
368

369
    /**
370
     * Gets a code point from the string.
371
     *
372
     * @param index the offset within the string
373
     *
374
     * @return the code point
375
     *
376
     * @throws IndexOutOfBoundsException if the index is outside the bounds of the string
377
     */
378
    public int codePointAt(final int index) {
379
        return codePoints[index];
1✔
380
    }
381

382
    @Override
383
    public String toString() {
384
        final StringBuilder builder = new StringBuilder(codePoints.length);
1✔
385
        for (final int codePoint : codePoints) {
1✔
386
            builder.appendCodePoint(codePoint);
1✔
387
        }
388
        return builder.toString();
1✔
389
    }
390
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc