~ubuntu-branches/ubuntu/vivid/icu4j-4.4/vivid

« back to all changes in this revision

Viewing changes to main/classes/core/src/com/ibm/icu/impl/UnicodeSetStringSpan.java

  • Committer: Bazaar Package Importer
  • Author(s): Niels Thykier
  • Date: 2011-08-02 15:50:33 UTC
  • Revision ID: james.westby@ubuntu.com-20110802155033-itjzsl21y2lqdonn
Tags: upstream-4.4.2
ImportĀ upstreamĀ versionĀ 4.4.2

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 ******************************************************************************
 
3
 *
 
4
 *   Copyright (C) 2009-2010, International Business Machines
 
5
 *   Corporation and others.  All Rights Reserved.
 
6
 *
 
7
 ******************************************************************************
 
8
 */
 
9
 
 
10
package com.ibm.icu.impl;
 
11
 
 
12
import java.util.ArrayList;
 
13
 
 
14
import com.ibm.icu.text.UnicodeSet;
 
15
import com.ibm.icu.text.UnicodeSet.SpanCondition;
 
16
 
 
17
/*
 
18
 * Implement span() etc. for a set with strings.
 
19
 * Avoid recursion because of its exponential complexity.
 
20
 * Instead, try multiple paths at once and track them with an IndexList.
 
21
 */
 
22
public class UnicodeSetStringSpan {
 
23
 
 
24
    /*
 
25
     * Which span() variant will be used? The object is either built for one variant and used once, or built for all and
 
26
     * may be used many times.
 
27
     */
 
28
    public static final int FWD           = 0x20;
 
29
    public static final int BACK          = 0x10;
 
30
    public static final int UTF16         = 8;
 
31
    public static final int CONTAINED     = 2;
 
32
    public static final int NOT_CONTAINED = 1;
 
33
 
 
34
    public static final int ALL = 0x3f;
 
35
 
 
36
    public static final int FWD_UTF16_CONTAINED      = FWD  | UTF16 |     CONTAINED;
 
37
    public static final int FWD_UTF16_NOT_CONTAINED  = FWD  | UTF16 | NOT_CONTAINED;
 
38
    public static final int BACK_UTF16_CONTAINED     = BACK | UTF16 |     CONTAINED;
 
39
    public static final int BACK_UTF16_NOT_CONTAINED = BACK | UTF16 | NOT_CONTAINED;
 
40
 
 
41
    // Special spanLength short values. (since Java has not unsigned byte type)
 
42
    // All code points in the string are contained in the parent set.
 
43
    static final short ALL_CP_CONTAINED = 0xff;
 
44
    // The spanLength is >=0xfe.
 
45
    static final short LONG_SPAN = ALL_CP_CONTAINED - 1;
 
46
 
 
47
    // Set for span(). Same as parent but without strings.
 
48
    private UnicodeSet spanSet;
 
49
 
 
50
    // Set for span(not contained).
 
51
    // Same as spanSet, plus characters that start or end strings.
 
52
    private UnicodeSet spanNotSet;
 
53
 
 
54
    // The strings of the parent set.
 
55
    private ArrayList<String> strings;
 
56
 
 
57
    // the lengths of span(), spanBack() etc. for each string.
 
58
    private short[] spanLengths;
 
59
 
 
60
    // Maximum lengths of relevant strings.
 
61
    private int maxLength16;
 
62
 
 
63
    // Set up for all variants of span()?
 
64
    private boolean all;
 
65
 
 
66
    // Span helper
 
67
    private OffsetList offsets;
 
68
 
 
69
    // Construct for all variants of span(), or only for any one variant.
 
70
    // Initialize as little as possible, for single use.
 
71
    public UnicodeSetStringSpan(final UnicodeSet set, final ArrayList<String> setStrings, int which) {
 
72
        spanSet = new UnicodeSet(0, 0x10ffff);
 
73
        strings = setStrings;
 
74
        all = (which == ALL);
 
75
        spanSet.retainAll(set);
 
76
        if (0 != (which & NOT_CONTAINED)) {
 
77
            // Default to the same sets.
 
78
            // addToSpanNotSet() will create a separate set if necessary.
 
79
            spanNotSet = spanSet;
 
80
        }
 
81
        offsets = new OffsetList();
 
82
 
 
83
        // Determine if the strings even need to be taken into account at all for span() etc.
 
84
        // If any string is relevant, then all strings need to be used for
 
85
        // span(longest match) but only the relevant ones for span(while contained).
 
86
        // TODO: Possible optimization: Distinguish CONTAINED vs. LONGEST_MATCH
 
87
        // and do not store UTF-8 strings if !thisRelevant and CONTAINED.
 
88
        // (Only store irrelevant UTF-8 strings for LONGEST_MATCH where they are relevant after all.)
 
89
        // Also count the lengths of the UTF-8 versions of the strings for memory allocation.
 
90
        int stringsLength = strings.size();
 
91
 
 
92
        int i, spanLength;
 
93
        boolean someRelevant = false;
 
94
        for (i = 0; i < stringsLength; ++i) {
 
95
            String string = strings.get(i);
 
96
            int length16 = string.length();
 
97
            spanLength = spanSet.span(string, SpanCondition.CONTAINED);
 
98
            if (spanLength < length16) { // Relevant string.
 
99
                someRelevant = true;
 
100
            }
 
101
            if ((0 != (which & UTF16)) && length16 > maxLength16) {
 
102
                maxLength16 = length16;
 
103
            }
 
104
        }
 
105
        if (!someRelevant) {
 
106
            maxLength16 = 0;
 
107
            return;
 
108
        }
 
109
 
 
110
        // Freeze after checking for the need to use strings at all because freezing
 
111
        // a set takes some time and memory which are wasted if there are no relevant strings.
 
112
        if (all) {
 
113
            spanSet.freeze();
 
114
        }
 
115
 
 
116
        int spanBackLengthsOffset;
 
117
 
 
118
        // Allocate a block of meta data.
 
119
        int allocSize;
 
120
        if (all) {
 
121
            // 2 sets of span lengths
 
122
            allocSize = stringsLength * (2);
 
123
        } else {
 
124
            allocSize = stringsLength; // One set of span lengths.
 
125
        }
 
126
        spanLengths = new short[allocSize];
 
127
 
 
128
        if (all) {
 
129
            // Store span lengths for all span() variants.
 
130
            spanBackLengthsOffset = stringsLength;
 
131
        } else {
 
132
            // Store span lengths for only one span() variant.
 
133
            spanBackLengthsOffset = 0;
 
134
        }
 
135
 
 
136
        // Set the meta data and spanNotSet and write the UTF-8 strings.
 
137
 
 
138
        for (i = 0; i < stringsLength; ++i) {
 
139
            String string = strings.get(i);
 
140
            int length16 = string.length();
 
141
            spanLength = spanSet.span(string, SpanCondition.CONTAINED);
 
142
            if (spanLength < length16) { // Relevant string.
 
143
                if (0 != (which & UTF16)) {
 
144
                    if (0 != (which & CONTAINED)) {
 
145
                        if (0 != (which & FWD)) {
 
146
                            spanLengths[i] = makeSpanLengthByte(spanLength);
 
147
                        }
 
148
                        if (0 != (which & BACK)) {
 
149
                            spanLength = length16
 
150
                                    - spanSet.spanBack(string, length16, SpanCondition.CONTAINED);
 
151
                            spanLengths[spanBackLengthsOffset + i] = makeSpanLengthByte(spanLength);
 
152
                        }
 
153
                    } else /* not CONTAINED, not all, but NOT_CONTAINED */{
 
154
                        spanLengths[i] = spanLengths[spanBackLengthsOffset + i] = 0; // Only store a relevant/irrelevant
 
155
                                                                                     // flag.
 
156
                    }
 
157
                }
 
158
                if (0 != (which & NOT_CONTAINED)) {
 
159
                    // Add string start and end code points to the spanNotSet so that
 
160
                    // a span(while not contained) stops before any string.
 
161
                    int c;
 
162
                    if (0 != (which & FWD)) {
 
163
                        c = string.codePointAt(0);
 
164
                        addToSpanNotSet(c);
 
165
                    }
 
166
                    if (0 != (which & BACK)) {
 
167
                        c = string.codePointBefore(length16);
 
168
                        addToSpanNotSet(c);
 
169
                    }
 
170
                }
 
171
            } else { // Irrelevant string.
 
172
                if (all) {
 
173
                    spanLengths[i] = spanLengths[spanBackLengthsOffset + i] = ALL_CP_CONTAINED;
 
174
                } else {
 
175
                    // All spanXYZLengths pointers contain the same address.
 
176
                    spanLengths[i] = ALL_CP_CONTAINED;
 
177
                }
 
178
            }
 
179
        }
 
180
 
 
181
        // Finish.
 
182
        if (all) {
 
183
            spanNotSet.freeze();
 
184
        }
 
185
    }
 
186
 
 
187
    /**
 
188
     * Constructs a copy of an existing UnicodeSetStringSpan.
 
189
     * Assumes which==ALL for a frozen set.
 
190
     */
 
191
    public UnicodeSetStringSpan(final UnicodeSetStringSpan otherStringSpan, final ArrayList<String> newParentSetStrings) {
 
192
        spanSet = otherStringSpan.spanSet;
 
193
        strings = newParentSetStrings;
 
194
        maxLength16 = otherStringSpan.maxLength16;
 
195
        all = true;
 
196
        if (otherStringSpan.spanNotSet == otherStringSpan.spanSet) {
 
197
            spanNotSet = spanSet;
 
198
        } else {
 
199
            spanNotSet = (UnicodeSet) otherStringSpan.spanNotSet.clone();
 
200
        }
 
201
        offsets = new OffsetList();
 
202
 
 
203
        spanLengths = otherStringSpan.spanLengths.clone();
 
204
    }
 
205
 
 
206
    /*
 
207
     * Do the strings need to be checked in span() etc.?
 
208
     * 
 
209
     * @return TRUE if strings need to be checked (call span() here), FALSE if not (use a BMPSet for best performance).
 
210
     */
 
211
    public boolean needsStringSpanUTF16() {
 
212
        return (maxLength16 != 0);
 
213
    }
 
214
 
 
215
    // For fast UnicodeSet::contains(c).
 
216
    public boolean contains(int c) {
 
217
        return spanSet.contains(c);
 
218
    }
 
219
 
 
220
    // Add a starting or ending string character to the spanNotSet
 
221
    // so that a character span ends before any string.
 
222
    private void addToSpanNotSet(int c) {
 
223
        if (spanNotSet == null || spanNotSet == spanSet) {
 
224
            if (spanSet.contains(c)) {
 
225
                return; // Nothing to do.
 
226
            }
 
227
            spanNotSet = spanSet.cloneAsThawed();
 
228
        }
 
229
        spanNotSet.add(c);
 
230
    }
 
231
 
 
232
    /*
 
233
     * Note: In span() when spanLength==0 (after a string match, or at the beginning after an empty code point span) and
 
234
     * in spanNot() and spanNotUTF8(), string matching could use a binary search because all string matches are done
 
235
     * from the same start index.
 
236
     * 
 
237
     * For UTF-8, this would require a comparison function that returns UTF-16 order.
 
238
     * 
 
239
     * This optimization should not be necessary for normal UnicodeSets because most sets have no strings, and most sets
 
240
     * with strings have very few very short strings. For cases with many strings, it might be better to use a different
 
241
     * API and implementation with a DFA (state machine).
 
242
     */
 
243
 
 
244
    /*
 
245
     * Algorithm for span(SpanCondition.CONTAINED)
 
246
     * 
 
247
     * Theoretical algorithm: - Iterate through the string, and at each code point boundary: + If the code point there
 
248
     * is in the set, then remember to continue after it. + If a set string matches at the current position, then
 
249
     * remember to continue after it. + Either recursively span for each code point or string match, or recursively span
 
250
     * for all but the shortest one and iteratively continue the span with the shortest local match. + Remember the
 
251
     * longest recursive span (the farthest end point). + If there is no match at the current position, neither for the
 
252
     * code point there nor for any set string, then stop and return the longest recursive span length.
 
253
     * 
 
254
     * Optimized implementation:
 
255
     * 
 
256
     * (We assume that most sets will have very few very short strings. A span using a string-less set is extremely
 
257
     * fast.)
 
258
     * 
 
259
     * Create and cache a spanSet which contains all of the single code points of the original set but none of its
 
260
     * strings.
 
261
     * 
 
262
     * - Start with spanLength=spanSet.span(SpanCondition.CONTAINED). - Loop: + Try to match each set
 
263
     * string at the end of the spanLength. ~ Set strings that start with set-contained code points must be matched with
 
264
     * a partial overlap because the recursive algorithm would have tried to match them at every position. ~ Set strings
 
265
     * that entirely consist of set-contained code points are irrelevant for span(SpanCondition.CONTAINED)
 
266
     * because the recursive algorithm would continue after them anyway and find the longest recursive match from their
 
267
     * end. ~ Rather than recursing, note each end point of a set string match. + If no set string matched after
 
268
     * spanSet.span(), then return with where the spanSet.span() ended. + If at least one set string matched after
 
269
     * spanSet.span(), then pop the shortest string match end point and continue the loop, trying to match all set
 
270
     * strings from there. + If at least one more set string matched after a previous string match, then test if the
 
271
     * code point after the previous string match is also contained in the set. Continue the loop with the shortest end
 
272
     * point of either this code point or a matching set string. + If no more set string matched after a previous string
 
273
     * match, then try another spanLength=spanSet.span(SpanCondition.CONTAINED). Stop if spanLength==0,
 
274
     * otherwise continue the loop.
 
275
     * 
 
276
     * By noting each end point of a set string match, the function visits each string position at most once and
 
277
     * finishes in linear time.
 
278
     * 
 
279
     * The recursive algorithm may visit the same string position many times if multiple paths lead to it and finishes
 
280
     * in exponential time.
 
281
     */
 
282
 
 
283
    /*
 
284
     * Algorithm for span(SIMPLE)
 
285
     * 
 
286
     * Theoretical algorithm: - Iterate through the string, and at each code point boundary: + If the code point there
 
287
     * is in the set, then remember to continue after it. + If a set string matches at the current position, then
 
288
     * remember to continue after it. + Continue from the farthest match position and ignore all others. + If there is
 
289
     * no match at the current position, then stop and return the current position.
 
290
     * 
 
291
     * Optimized implementation:
 
292
     * 
 
293
     * (Same assumption and spanSet as above.)
 
294
     * 
 
295
     * - Start with spanLength=spanSet.span(SpanCondition.CONTAINED). - Loop: + Try to match each set
 
296
     * string at the end of the spanLength. ~ Set strings that start with set-contained code points must be matched with
 
297
     * a partial overlap because the standard algorithm would have tried to match them earlier. ~ Set strings that
 
298
     * entirely consist of set-contained code points must be matched with a full overlap because the longest-match
 
299
     * algorithm would hide set string matches that end earlier. Such set strings need not be matched earlier inside the
 
300
     * code point span because the standard algorithm would then have continued after the set string match anyway. ~
 
301
     * Remember the longest set string match (farthest end point) from the earliest starting point. + If no set string
 
302
     * matched after spanSet.span(), then return with where the spanSet.span() ended. + If at least one set string
 
303
     * matched, then continue the loop after the longest match from the earliest position. + If no more set string
 
304
     * matched after a previous string match, then try another
 
305
     * spanLength=spanSet.span(SpanCondition.CONTAINED). Stop if spanLength==0, otherwise continue the
 
306
     * loop.
 
307
     */
 
308
    /**
 
309
     * Span a string.
 
310
     * 
 
311
     * @param s The string to be spanned
 
312
     * @param start The start index that the span begins
 
313
     * @param spanCondition The span condition
 
314
     * @return the length of the span
 
315
     * @draft ICU 4.4
 
316
     */
 
317
    public synchronized int span(CharSequence s, int start, int length, SpanCondition spanCondition) {
 
318
        if (spanCondition == SpanCondition.NOT_CONTAINED) {
 
319
            return spanNot(s, start, length);
 
320
        }
 
321
        int spanLength = spanSet.span(s.subSequence(start, start + length), SpanCondition.CONTAINED);
 
322
        if (spanLength == length) {
 
323
            return length;
 
324
        }
 
325
 
 
326
        // Consider strings; they may overlap with the span.
 
327
        int initSize = 0;
 
328
        if (spanCondition == SpanCondition.CONTAINED) {
 
329
            // Use offset list to try all possibilities.
 
330
            initSize = maxLength16;
 
331
        }
 
332
        offsets.setMaxLength(initSize);
 
333
        int pos = start + spanLength, rest = length - spanLength;
 
334
        int i, stringsLength = strings.size();
 
335
        for (;;) {
 
336
            if (spanCondition == SpanCondition.CONTAINED) {
 
337
                for (i = 0; i < stringsLength; ++i) {
 
338
                    int overlap = spanLengths[i];
 
339
                    if (overlap == ALL_CP_CONTAINED) {
 
340
                        continue; // Irrelevant string.
 
341
                    }
 
342
                    String string = strings.get(i);
 
343
 
 
344
                    int length16 = string.length();
 
345
 
 
346
                    // Try to match this string at pos-overlap..pos.
 
347
                    if (overlap >= LONG_SPAN) {
 
348
                        overlap = length16;
 
349
                        // While contained: No point matching fully inside the code point span.
 
350
                        overlap = string.offsetByCodePoints(overlap, -1); // Length of the string minus the last code
 
351
                                                                          // point.
 
352
                    }
 
353
                    if (overlap > spanLength) {
 
354
                        overlap = spanLength;
 
355
                    }
 
356
                    int inc = length16 - overlap; // Keep overlap+inc==length16.
 
357
                    for (;;) {
 
358
                        if (inc > rest) {
 
359
                            break;
 
360
                        }
 
361
                        // Try to match if the increment is not listed already.
 
362
                        if (!offsets.containsOffset(inc) && matches16CPB(s, pos - overlap, length, string, length16)) {
 
363
                            if (inc == rest) {
 
364
                                return length; // Reached the end of the string.
 
365
                            }
 
366
                            offsets.addOffset(inc);
 
367
                        }
 
368
                        if (overlap == 0) {
 
369
                            break;
 
370
                        }
 
371
                        --overlap;
 
372
                        ++inc;
 
373
                    }
 
374
                }
 
375
            } else /* SIMPLE */{
 
376
                int maxInc = 0, maxOverlap = 0;
 
377
                for (i = 0; i < stringsLength; ++i) {
 
378
                    int overlap = spanLengths[i];
 
379
                    // For longest match, we do need to try to match even an all-contained string
 
380
                    // to find the match from the earliest start.
 
381
 
 
382
                    String string = strings.get(i);
 
383
 
 
384
                    int length16 = string.length();
 
385
 
 
386
                    // Try to match this string at pos-overlap..pos.
 
387
                    if (overlap >= LONG_SPAN) {
 
388
                        overlap = length16;
 
389
                        // Longest match: Need to match fully inside the code point span
 
390
                        // to find the match from the earliest start.
 
391
                    }
 
392
                    if (overlap > spanLength) {
 
393
                        overlap = spanLength;
 
394
                    }
 
395
                    int inc = length16 - overlap; // Keep overlap+inc==length16.
 
396
                    for (;;) {
 
397
                        if (inc > rest || overlap < maxOverlap) {
 
398
                            break;
 
399
                        }
 
400
                        // Try to match if the string is longer or starts earlier.
 
401
                        if ((overlap > maxOverlap || /* redundant overlap==maxOverlap && */inc > maxInc)
 
402
                                && matches16CPB(s, pos - overlap, length, string, length16)) {
 
403
                            maxInc = inc; // Longest match from earliest start.
 
404
                            maxOverlap = overlap;
 
405
                            break;
 
406
                        }
 
407
                        --overlap;
 
408
                        ++inc;
 
409
                    }
 
410
                }
 
411
 
 
412
                if (maxInc != 0 || maxOverlap != 0) {
 
413
                    // Longest-match algorithm, and there was a string match.
 
414
                    // Simply continue after it.
 
415
                    pos += maxInc;
 
416
                    rest -= maxInc;
 
417
                    if (rest == 0) {
 
418
                        return length; // Reached the end of the string.
 
419
                    }
 
420
                    spanLength = 0; // Match strings from after a string match.
 
421
                    continue;
 
422
                }
 
423
            }
 
424
            // Finished trying to match all strings at pos.
 
425
 
 
426
            if (spanLength != 0 || pos == 0) {
 
427
                // The position is after an unlimited code point span (spanLength!=0),
 
428
                // not after a string match.
 
429
                // The only position where spanLength==0 after a span is pos==0.
 
430
                // Otherwise, an unlimited code point span is only tried again when no
 
431
                // strings match, and if such a non-initial span fails we stop.
 
432
                if (offsets.isEmpty()) {
 
433
                    return pos - start; // No strings matched after a span.
 
434
                }
 
435
                // Match strings from after the next string match.
 
436
            } else {
 
437
                // The position is after a string match (or a single code point).
 
438
                if (offsets.isEmpty()) {
 
439
                    // No more strings matched after a previous string match.
 
440
                    // Try another code point span from after the last string match.
 
441
                    spanLength = spanSet.span(s.subSequence(pos, pos + rest), SpanCondition.CONTAINED);
 
442
                    if (spanLength == rest || // Reached the end of the string, or
 
443
                            spanLength == 0 // neither strings nor span progressed.
 
444
                    ) {
 
445
                        return pos + spanLength - start;
 
446
                    }
 
447
                    pos += spanLength;
 
448
                    rest -= spanLength;
 
449
                    continue; // spanLength>0: Match strings from after a span.
 
450
                } else {
 
451
                    // Try to match only one code point from after a string match if some
 
452
                    // string matched beyond it, so that we try all possible positions
 
453
                    // and don't overshoot.
 
454
                    spanLength = spanOne(spanSet, s, pos, rest);
 
455
                    if (spanLength > 0) {
 
456
                        if (spanLength == rest) {
 
457
                            return length; // Reached the end of the string.
 
458
                        }
 
459
                        // Match strings after this code point.
 
460
                        // There cannot be any increments below it because UnicodeSet strings
 
461
                        // contain multiple code points.
 
462
                        pos += spanLength;
 
463
                        rest -= spanLength;
 
464
                        offsets.shift(spanLength);
 
465
                        spanLength = 0;
 
466
                        continue; // Match strings from after a single code point.
 
467
                    }
 
468
                    // Match strings from after the next string match.
 
469
                }
 
470
            }
 
471
            int minOffset = offsets.popMinimum();
 
472
            pos += minOffset;
 
473
            rest -= minOffset;
 
474
            spanLength = 0; // Match strings from after a string match.
 
475
        }
 
476
    }
 
477
 
 
478
    /**
 
479
     * Span a string backwards.
 
480
     * 
 
481
     * @param s The string to be spanned
 
482
     * @param spanCondition The span condition
 
483
     * @return The string index which starts the span (i.e. inclusive).
 
484
     * @draft ICU 4.4
 
485
     */
 
486
    public synchronized int spanBack(CharSequence s, int length, SpanCondition spanCondition) {
 
487
        if (spanCondition == SpanCondition.NOT_CONTAINED) {
 
488
            return spanNotBack(s, length);
 
489
        }
 
490
        int pos = spanSet.spanBack(s, length, SpanCondition.CONTAINED);
 
491
        if (pos == 0) {
 
492
            return 0;
 
493
        }
 
494
        int spanLength = length - pos;
 
495
 
 
496
        // Consider strings; they may overlap with the span.
 
497
        int initSize = 0;
 
498
        if (spanCondition == SpanCondition.CONTAINED) {
 
499
            // Use offset list to try all possibilities.
 
500
            initSize = maxLength16;
 
501
        }
 
502
        offsets.setMaxLength(initSize);
 
503
        int i, stringsLength = strings.size();
 
504
        int spanBackLengthsOffset = 0;
 
505
        if (all) {
 
506
            spanBackLengthsOffset = stringsLength;
 
507
        }
 
508
        for (;;) {
 
509
            if (spanCondition == SpanCondition.CONTAINED) {
 
510
                for (i = 0; i < stringsLength; ++i) {
 
511
                    int overlap = spanLengths[spanBackLengthsOffset + i];
 
512
                    if (overlap == ALL_CP_CONTAINED) {
 
513
                        continue; // Irrelevant string.
 
514
                    }
 
515
                    String string = strings.get(i);
 
516
 
 
517
                    int length16 = string.length();
 
518
 
 
519
                    // Try to match this string at pos-(length16-overlap)..pos-length16.
 
520
                    if (overlap >= LONG_SPAN) {
 
521
                        overlap = length16;
 
522
                        // While contained: No point matching fully inside the code point span.
 
523
                        int len1 = 0;
 
524
                        len1 = string.offsetByCodePoints(0, 1);
 
525
                        overlap -= len1; // Length of the string minus the first code point.
 
526
                    }
 
527
                    if (overlap > spanLength) {
 
528
                        overlap = spanLength;
 
529
                    }
 
530
                    int dec = length16 - overlap; // Keep dec+overlap==length16.
 
531
                    for (;;) {
 
532
                        if (dec > pos) {
 
533
                            break;
 
534
                        }
 
535
                        // Try to match if the decrement is not listed already.
 
536
                        if (!offsets.containsOffset(dec) && matches16CPB(s, pos - dec, length, string, length16)) {
 
537
                            if (dec == pos) {
 
538
                                return 0; // Reached the start of the string.
 
539
                            }
 
540
                            offsets.addOffset(dec);
 
541
                        }
 
542
                        if (overlap == 0) {
 
543
                            break;
 
544
                        }
 
545
                        --overlap;
 
546
                        ++dec;
 
547
                    }
 
548
                }
 
549
            } else /* SIMPLE */{
 
550
                int maxDec = 0, maxOverlap = 0;
 
551
                for (i = 0; i < stringsLength; ++i) {
 
552
                    int overlap = spanLengths[spanBackLengthsOffset + i];
 
553
                    // For longest match, we do need to try to match even an all-contained string
 
554
                    // to find the match from the latest end.
 
555
 
 
556
                    String string = strings.get(i);
 
557
 
 
558
                    int length16 = string.length();
 
559
 
 
560
                    // Try to match this string at pos-(length16-overlap)..pos-length16.
 
561
                    if (overlap >= LONG_SPAN) {
 
562
                        overlap = length16;
 
563
                        // Longest match: Need to match fully inside the code point span
 
564
                        // to find the match from the latest end.
 
565
                    }
 
566
                    if (overlap > spanLength) {
 
567
                        overlap = spanLength;
 
568
                    }
 
569
                    int dec = length16 - overlap; // Keep dec+overlap==length16.
 
570
                    for (;;) {
 
571
                        if (dec > pos || overlap < maxOverlap) {
 
572
                            break;
 
573
                        }
 
574
                        // Try to match if the string is longer or ends later.
 
575
                        if ((overlap > maxOverlap || /* redundant overlap==maxOverlap && */dec > maxDec)
 
576
                                && matches16CPB(s, pos - dec, length, string, length16)) {
 
577
                            maxDec = dec; // Longest match from latest end.
 
578
                            maxOverlap = overlap;
 
579
                            break;
 
580
                        }
 
581
                        --overlap;
 
582
                        ++dec;
 
583
                    }
 
584
                }
 
585
 
 
586
                if (maxDec != 0 || maxOverlap != 0) {
 
587
                    // Longest-match algorithm, and there was a string match.
 
588
                    // Simply continue before it.
 
589
                    pos -= maxDec;
 
590
                    if (pos == 0) {
 
591
                        return 0; // Reached the start of the string.
 
592
                    }
 
593
                    spanLength = 0; // Match strings from before a string match.
 
594
                    continue;
 
595
                }
 
596
            }
 
597
            // Finished trying to match all strings at pos.
 
598
 
 
599
            if (spanLength != 0 || pos == length) {
 
600
                // The position is before an unlimited code point span (spanLength!=0),
 
601
                // not before a string match.
 
602
                // The only position where spanLength==0 before a span is pos==length.
 
603
                // Otherwise, an unlimited code point span is only tried again when no
 
604
                // strings match, and if such a non-initial span fails we stop.
 
605
                if (offsets.isEmpty()) {
 
606
                    return pos; // No strings matched before a span.
 
607
                }
 
608
                // Match strings from before the next string match.
 
609
            } else {
 
610
                // The position is before a string match (or a single code point).
 
611
                if (offsets.isEmpty()) {
 
612
                    // No more strings matched before a previous string match.
 
613
                    // Try another code point span from before the last string match.
 
614
                    int oldPos = pos;
 
615
                    pos = spanSet.spanBack(s, oldPos, SpanCondition.CONTAINED);
 
616
                    spanLength = oldPos - pos;
 
617
                    if (pos == 0 || // Reached the start of the string, or
 
618
                            spanLength == 0 // neither strings nor span progressed.
 
619
                    ) {
 
620
                        return pos;
 
621
                    }
 
622
                    continue; // spanLength>0: Match strings from before a span.
 
623
                } else {
 
624
                    // Try to match only one code point from before a string match if some
 
625
                    // string matched beyond it, so that we try all possible positions
 
626
                    // and don't overshoot.
 
627
                    spanLength = spanOneBack(spanSet, s, pos);
 
628
                    if (spanLength > 0) {
 
629
                        if (spanLength == pos) {
 
630
                            return 0; // Reached the start of the string.
 
631
                        }
 
632
                        // Match strings before this code point.
 
633
                        // There cannot be any decrements below it because UnicodeSet strings
 
634
                        // contain multiple code points.
 
635
                        pos -= spanLength;
 
636
                        offsets.shift(spanLength);
 
637
                        spanLength = 0;
 
638
                        continue; // Match strings from before a single code point.
 
639
                    }
 
640
                    // Match strings from before the next string match.
 
641
                }
 
642
            }
 
643
            pos -= offsets.popMinimum();
 
644
            spanLength = 0; // Match strings from before a string match.
 
645
        }
 
646
    }
 
647
 
 
648
    /*
 
649
     * Algorithm for spanNot()==span(SpanCondition.NOT_CONTAINED)
 
650
     * 
 
651
     * Theoretical algorithm: - Iterate through the string, and at each code point boundary: + If the code point there
 
652
     * is in the set, then return with the current position. + If a set string matches at the current position, then
 
653
     * return with the current position.
 
654
     * 
 
655
     * Optimized implementation:
 
656
     * 
 
657
     * (Same assumption as for span() above.)
 
658
     * 
 
659
     * Create and cache a spanNotSet which contains all of the single code points of the original set but none of its
 
660
     * strings. For each set string add its initial code point to the spanNotSet. (Also add its final code point for
 
661
     * spanNotBack().)
 
662
     * 
 
663
     * - Loop:
 
664
     *   + Do spanLength=spanNotSet.span(SpanCondition.NOT_CONTAINED).
 
665
     *   + If the current code point is in the original set, then return the current position.
 
666
     *   + If any set string matches at the current position, then return the current position.
 
667
     *   + If there is no match at the current position, neither for the code point
 
668
     * there nor for any set string, then skip this code point and continue the loop. This happens for
 
669
     * set-string-initial code points that were added to spanNotSet when there is not actually a match for such a set
 
670
     * string.
 
671
     *
 
672
     * @return the length of the span
 
673
     */
 
674
    private int spanNot(CharSequence s, int start, int length) {
 
675
        int pos = start, rest = length;
 
676
        int i, stringsLength = strings.size();
 
677
        do {
 
678
            // Span until we find a code point from the set,
 
679
            // or a code point that starts or ends some string.
 
680
            i = spanNotSet.span(s.subSequence(pos, pos + rest), SpanCondition.NOT_CONTAINED);
 
681
            if (i == rest) {
 
682
                return length; // Reached the end of the string.
 
683
            }
 
684
            pos += i;
 
685
            rest -= i;
 
686
 
 
687
            // Check whether the current code point is in the original set,
 
688
            // without the string starts and ends.
 
689
            int cpLength = spanOne(spanSet, s, pos, rest);
 
690
            if (cpLength > 0) {
 
691
                return pos - start; // There is a set element at pos.
 
692
            }
 
693
 
 
694
            // Try to match the strings at pos.
 
695
            for (i = 0; i < stringsLength; ++i) {
 
696
                if (spanLengths[i] == ALL_CP_CONTAINED) {
 
697
                    continue; // Irrelevant string.
 
698
                }
 
699
                String string = strings.get(i);
 
700
 
 
701
                int length16 = string.length();
 
702
                if (length16 <= rest && matches16CPB(s, pos, length, string, length16)) {
 
703
                    return pos - start; // There is a set element at pos.
 
704
                }
 
705
            }
 
706
 
 
707
            // The span(while not contained) ended on a string start/end which is
 
708
            // not in the original set. Skip this code point and continue.
 
709
            // cpLength<0
 
710
            pos -= cpLength;
 
711
            rest += cpLength;
 
712
        } while (rest != 0);
 
713
        return length; // Reached the end of the string.
 
714
    }
 
715
 
 
716
    private int spanNotBack(CharSequence s, int length) {
 
717
        int pos = length;
 
718
        int i, stringsLength = strings.size();
 
719
        do {
 
720
            // Span until we find a code point from the set,
 
721
            // or a code point that starts or ends some string.
 
722
            pos = spanNotSet.spanBack(s, pos, SpanCondition.NOT_CONTAINED);
 
723
            if (pos == 0) {
 
724
                return 0; // Reached the start of the string.
 
725
            }
 
726
 
 
727
            // Check whether the current code point is in the original set,
 
728
            // without the string starts and ends.
 
729
            int cpLength = spanOneBack(spanSet, s, pos);
 
730
            if (cpLength > 0) {
 
731
                return pos; // There is a set element at pos.
 
732
            }
 
733
 
 
734
            // Try to match the strings at pos.
 
735
            for (i = 0; i < stringsLength; ++i) {
 
736
                // Use spanLengths rather than a spanLengths pointer because
 
737
                // it is easier and we only need to know whether the string is irrelevant
 
738
                // which is the same in either array.
 
739
                if (spanLengths[i] == ALL_CP_CONTAINED) {
 
740
                    continue; // Irrelevant string.
 
741
                }
 
742
                String string = strings.get(i);
 
743
 
 
744
                int length16 = string.length();
 
745
                if (length16 <= pos && matches16CPB(s, pos - length16, length, string, length16)) {
 
746
                    return pos; // There is a set element at pos.
 
747
                }
 
748
            }
 
749
 
 
750
            // The span(while not contained) ended on a string start/end which is
 
751
            // not in the original set. Skip this code point and continue.
 
752
            // cpLength<0
 
753
            pos += cpLength;
 
754
        } while (pos != 0);
 
755
        return 0; // Reached the start of the string.
 
756
    }
 
757
 
 
758
    static short makeSpanLengthByte(int spanLength) {
 
759
        // 0xfe==UnicodeSetStringSpan::LONG_SPAN
 
760
        return spanLength < LONG_SPAN ? (short) spanLength : LONG_SPAN;
 
761
    }
 
762
 
 
763
    // Compare strings without any argument checks. Requires length>0.
 
764
    private static boolean matches16(CharSequence s, int start, final String t, int length) {
 
765
        int end = start + length;
 
766
        while (length-- > 0) {
 
767
            if (s.charAt(--end) != t.charAt(length)) {
 
768
                return false;
 
769
            }
 
770
        }
 
771
        return true;
 
772
    }
 
773
 
 
774
    /**
 
775
     * Compare 16-bit Unicode strings (which may be malformed UTF-16)
 
776
     * at code point boundaries.
 
777
     * That is, each edge of a match must not be in the middle of a surrogate pair.
 
778
     * @param start   The start index of s.
 
779
     * @param slength The length of s from start.
 
780
     * @param tlength The length of t.
 
781
     */
 
782
    static boolean matches16CPB(CharSequence s, int start, int slength, final String t, int tlength) {
 
783
        return !(0 < start && com.ibm.icu.text.UTF16.isLeadSurrogate (s.charAt(start - 1)) &&
 
784
                              com.ibm.icu.text.UTF16.isTrailSurrogate(s.charAt(start + 0)))
 
785
                && !(tlength < slength && com.ibm.icu.text.UTF16.isLeadSurrogate (s.charAt(start + tlength - 1)) &&
 
786
                                       com.ibm.icu.text.UTF16.isTrailSurrogate(s.charAt(start + tlength)))
 
787
                && matches16(s, start, t, tlength);
 
788
    }
 
789
 
 
790
    // Does the set contain the next code point?
 
791
    // If so, return its length; otherwise return its negative length.
 
792
    static int spanOne(final UnicodeSet set, CharSequence s, int start, int length) {
 
793
        char c = s.charAt(start);
 
794
        if (c >= 0xd800 && c <= 0xdbff && length >= 2) {
 
795
            char c2 = s.charAt(start + 1);
 
796
            if (com.ibm.icu.text.UTF16.isTrailSurrogate(c2)) {
 
797
                int supplementary = UCharacterProperty.getRawSupplementary(c, c2);
 
798
                return set.contains(supplementary) ? 2 : -2;
 
799
            }
 
800
        }
 
801
        return set.contains(c) ? 1 : -1;
 
802
    }
 
803
 
 
804
    static int spanOneBack(final UnicodeSet set, CharSequence s, int length) {
 
805
        char c = s.charAt(length - 1);
 
806
        if (c >= 0xdc00 && c <= 0xdfff && length >= 2) {
 
807
            char c2 = s.charAt(length - 2);
 
808
            if (com.ibm.icu.text.UTF16.isLeadSurrogate(c2)) {
 
809
                int supplementary = UCharacterProperty.getRawSupplementary(c2, c);
 
810
                return set.contains(supplementary) ? 2 : -2;
 
811
            }
 
812
        }
 
813
        return set.contains(c) ? 1 : -1;
 
814
    }
 
815
 
 
816
 
 
817
    /*
 
818
     * Helper class for UnicodeSetStringSpan.
 
819
     *
 
820
     * List of offsets from the current position from where to try matching a code point or a string. Store offsets rather
 
821
     * than indexes to simplify the code and use the same list for both increments (in span()) and decrements (in
 
822
     * spanBack()).
 
823
     * 
 
824
     * Assumption: The maximum offset is limited, and the offsets that are stored at any one time are relatively dense, that
 
825
     * is, there are normally no gaps of hundreds or thousands of offset values.
 
826
     * 
 
827
     * The implementation uses a circular buffer of byte flags, each indicating whether the corresponding offset is in the
 
828
     * list. This avoids inserting into a sorted list of offsets (or absolute indexes) and physically moving part of the
 
829
     * list.
 
830
     * 
 
831
     * Note: In principle, the caller should setMaxLength() to the maximum of the max string length and U16_LENGTH/U8_LENGTH
 
832
     * to account for "long" single code points.
 
833
     * 
 
834
     * Note: If maxLength were guaranteed to be no more than 32 or 64, the list could be stored as bit flags in a single
 
835
     * integer. Rather than handling a circular buffer with a start list index, the integer would simply be shifted when
 
836
     * lower offsets are removed. UnicodeSet does not have a limit on the lengths of strings.
 
837
     */
 
838
    static class OffsetList {
 
839
        private boolean[] list;
 
840
        private int length;
 
841
        private int start;
 
842
 
 
843
        public OffsetList() {
 
844
            list = new boolean[16];  // default size
 
845
        }
 
846
 
 
847
        public void setMaxLength(int maxLength) {
 
848
            if (maxLength > list.length) {
 
849
                list = new boolean[maxLength];
 
850
            }
 
851
            clear();
 
852
        }
 
853
 
 
854
        public void clear() {
 
855
            for (int i = list.length; i-- > 0;) {
 
856
                list[i] = false;
 
857
            }
 
858
            start = length = 0;
 
859
        }
 
860
 
 
861
        public boolean isEmpty() {
 
862
            return (length == 0);
 
863
        }
 
864
 
 
865
        // Reduce all stored offsets by delta, used when the current position
 
866
        // moves by delta.
 
867
        // There must not be any offsets lower than delta.
 
868
        // If there is an offset equal to delta, it is removed.
 
869
        // delta=[1..maxLength]
 
870
        public void shift(int delta) {
 
871
            int i = start + delta;
 
872
            if (i >= list.length) {
 
873
                i -= list.length;
 
874
            }
 
875
            if (list[i]) {
 
876
                list[i] = false;
 
877
                --length;
 
878
            }
 
879
            start = i;
 
880
        }
 
881
 
 
882
        // Add an offset. The list must not contain it yet.
 
883
        // offset=[1..maxLength]
 
884
        public void addOffset(int offset) {
 
885
            int i = start + offset;
 
886
            if (i >= list.length) {
 
887
                i -= list.length;
 
888
            }
 
889
            list[i] = true;
 
890
            ++length;
 
891
        }
 
892
 
 
893
        // offset=[1..maxLength]
 
894
        public boolean containsOffset(int offset) {
 
895
            int i = start + offset;
 
896
            if (i >= list.length) {
 
897
                i -= list.length;
 
898
            }
 
899
            return list[i];
 
900
        }
 
901
 
 
902
        // Find the lowest stored offset from a non-empty list, remove it,
 
903
        // and reduce all other offsets by this minimum.
 
904
        // Returns [1..maxLength].
 
905
        public int popMinimum() {
 
906
            // Look for the next offset in list[start+1..list.length-1].
 
907
            int i = start, result;
 
908
            while (++i < list.length) {
 
909
                if (list[i]) {
 
910
                    list[i] = false;
 
911
                    --length;
 
912
                    result = i - start;
 
913
                    start = i;
 
914
                    return result;
 
915
                }
 
916
            }
 
917
            // i==list.length
 
918
 
 
919
            // Wrap around and look for the next offset in list[0..start].
 
920
            // Since the list is not empty, there will be one.
 
921
            result = list.length - start;
 
922
            i = 0;
 
923
            while (!list[i]) {
 
924
                ++i;
 
925
            }
 
926
            list[i] = false;
 
927
            --length;
 
928
            start = i;
 
929
            return result += i;
 
930
        }
 
931
    }
 
932
}