2
*******************************************************************************
3
* Copyright (C) 2009-2010, International Business Machines Corporation and *
4
* others. All Rights Reserved. *
5
*******************************************************************************
7
package com.ibm.icu.dev.test.lang;
9
import com.ibm.icu.dev.test.TestFmwk;
10
import com.ibm.icu.impl.Utility;
11
import com.ibm.icu.text.UTF16;
12
import com.ibm.icu.text.UnicodeSet;
13
import com.ibm.icu.text.UnicodeSetIterator;
14
import com.ibm.icu.text.UnicodeSet.SpanCondition;
18
* @summary General test of UnicodeSet string span.
20
public class UnicodeSetStringSpanTest extends TestFmwk {
22
public static void main(String[] args) throws Exception {
23
new UnicodeSetStringSpanTest().run(args);
26
// Simple test first, easier to debug.
27
public void TestSimpleStringSpan() {
28
String pattern = "[a{ab}{bc}]";
29
String string = "abc";
30
UnicodeSet set = new UnicodeSet(pattern);
32
int pos = set.spanBack(string, 3, SpanCondition.SIMPLE);
34
errln(String.format("FAIL: UnicodeSet(%s).spanBack(%s) returns the wrong value pos %d (!= 1)",
35
set.toString(), string, pos));
37
pos = set.span(string, SpanCondition.SIMPLE);
39
errln(String.format("FAIL: UnicodeSet(%s).span(%s) returns the wrong value pos %d (!= 3)",
40
set.toString(), string, pos));
42
pos = set.span(string, 1, SpanCondition.SIMPLE);
44
errln(String.format("FAIL: UnicodeSet(%s).span(%s) returns the wrong value pos %d (!= 3)",
45
set.toString(), string, pos));
49
// test our slow implementation
50
public void TestSimpleStringSpanSlow() {
51
String pattern = "[a{ab}{bc}]";
52
String string = "abc";
53
UnicodeSet uset = new UnicodeSet(pattern);
55
UnicodeSetWithStrings set = new UnicodeSetWithStrings(uset);
57
int length = containsSpanBackUTF16(set, string, 3, SpanCondition.SIMPLE);
59
errln(String.format("FAIL: UnicodeSet(%s) containsSpanBackUTF16(%s) returns the wrong value length %d (!= 1)",
60
set.toString(), string, length));
62
length = containsSpanUTF16(set, string, SpanCondition.SIMPLE);
64
errln(String.format("FAIL: UnicodeSet(%s) containsSpanUTF16(%s) returns the wrong value length %d (!= 3)",
65
set.toString(), string, length));
67
length = containsSpanUTF16(set, string.substring(1), SpanCondition.SIMPLE);
69
errln(String.format("FAIL: UnicodeSet(%s) containsSpanUTF16(%s) returns the wrong value length %d (!= 2)",
70
set.toString(), string, length));
74
// Test select patterns and strings, and test SIMPLE.
75
public void TestSimpleStringSpanAndFreeze() {
76
String pattern = "[x{xy}{xya}{axy}{ax}]";
77
final String string = "xx"
78
+ "xyaxyaxyaxyaxyaxyaxyaxyaxyaxyaxyaxyaxyaxyaxyaxyaxyaxyaxyaxyaxya" + "xx"
79
+ "xyaxyaxyaxyaxyaxyaxyaxyaxyaxyaxyaxyaxyaxyaxyaxyaxyaxyaxyaxyaxya" + "xx"
80
+ "xyaxyaxyaxyaxyaxyaxyaxyaxyaxyaxyaxyaxyaxyaxyaxyaxyaxyaxyaxyaxy" + "aaaa";
82
UnicodeSet set = new UnicodeSet(pattern);
83
if (set.containsAll(string)) {
84
errln("FAIL: UnicodeSet(" + pattern + ").containsAll(" + string + ") should be FALSE");
87
// Remove trailing "aaaa".
88
String string16 = string.substring(0, string.length() - 4);
89
if (!set.containsAll(string16)) {
90
errln("FAIL: UnicodeSet(" + pattern + ").containsAll(" + string + "[:-4]) should be TRUE");
93
String s16 = "byayaxya";
94
if ( set.span(s16.substring(0, 8), SpanCondition.NOT_CONTAINED) != 4
95
|| set.span(s16.substring(0, 7), SpanCondition.NOT_CONTAINED) != 4
96
|| set.span(s16.substring(0, 6), SpanCondition.NOT_CONTAINED) != 4
97
|| set.span(s16.substring(0, 5), SpanCondition.NOT_CONTAINED) != 5
98
|| set.span(s16.substring(0, 4), SpanCondition.NOT_CONTAINED) != 4
99
|| set.span(s16.substring(0, 3), SpanCondition.NOT_CONTAINED) != 3) {
100
errln("FAIL: UnicodeSet(" + pattern + ").span(while not) returns the wrong value");
103
pattern = "[a{ab}{abc}{cd}]";
104
set.applyPattern(pattern);
105
s16 = "acdabcdabccd";
106
if ( set.span(s16.substring(0, 12), SpanCondition.CONTAINED) != 12
107
|| set.span(s16.substring(0, 12), SpanCondition.SIMPLE) != 6
108
|| set.span(s16.substring(7), SpanCondition.SIMPLE) != 5) {
109
errln("FAIL: UnicodeSet(" + pattern + ").span(while longest match) returns the wrong value");
112
if ( set.span(s16.substring(0, 12), SpanCondition.CONTAINED) != 12
113
|| set.span(s16.substring(0, 12), SpanCondition.SIMPLE) != 6
114
|| set.span(s16.substring(7), SpanCondition.SIMPLE) != 5) {
115
errln("FAIL: UnicodeSet(" + pattern + ").span(while longest match) returns the wrong value");
118
pattern = "[d{cd}{bcd}{ab}]";
119
set = (UnicodeSet)set.cloneAsThawed();
120
set.applyPattern(pattern).freeze();
121
s16 = "abbcdabcdabd";
122
if ( set.spanBack(s16, 12, SpanCondition.CONTAINED) != 0
123
|| set.spanBack(s16, 12, SpanCondition.SIMPLE) != 6
124
|| set.spanBack(s16, 5, SpanCondition.SIMPLE) != 0) {
125
errln("FAIL: UnicodeSet(" + pattern + ").spanBack(while longest match) returns the wrong value");
129
// more complex test. --------------------------------------------------------
131
// Make the strings in a UnicodeSet easily accessible.
132
static class UnicodeSetWithStrings {
134
private UnicodeSet set;
136
private String strings[];
137
private int stringsLength;
138
private boolean hasSurrogates;
140
public UnicodeSetWithStrings(final UnicodeSet normalSet) {
143
hasSurrogates = false;
144
strings = new String[20];
145
int size = set.size();
146
if (size > 0 && set.charAt(size - 1) < 0) {
147
// If a set's last element is not a code point, then it must contain strings.
148
// Iterate over the set, skip all code point ranges, and cache the strings.
149
UnicodeSetIterator iter = new UnicodeSetIterator(set);
150
while (iter.nextRange() && stringsLength < strings.length) {
151
if (iter.codepoint == UnicodeSetIterator.IS_STRING) {
152
// Store the pointer to the set's string element
153
// which we happen to know is a stable pointer.
154
strings[stringsLength] = iter.getString();
161
public final UnicodeSet getSet() {
165
public boolean hasStrings() {
166
return (stringsLength > 0);
169
public boolean hasStringsWithSurrogates() {
170
return hasSurrogates;
175
static class UnicodeSetWithStringsIterator {
177
private UnicodeSetWithStrings fSet;
178
private int nextStringIndex;
180
public UnicodeSetWithStringsIterator(final UnicodeSetWithStrings set) {
185
public void reset() {
189
public final String nextString() {
190
if (nextStringIndex < fSet.stringsLength) {
191
return fSet.strings[nextStringIndex++];
199
// Compare 16-bit Unicode strings (which may be malformed UTF-16)
200
// at code point boundaries.
201
// That is, each edge of a match must not be in the middle of a surrogate pair.
202
static boolean matches16CPB(final String s, int start, int limit, final String t) {
204
int length = t.length();
205
return t.equals(s.substring(start, start + length))
206
&& !(0 < start && UTF16.isLeadSurrogate (s.charAt(start - 1)) &&
207
UTF16.isTrailSurrogate(s.charAt(start)))
208
&& !(length < limit && UTF16.isLeadSurrogate (s.charAt(start + length - 1)) &&
209
UTF16.isTrailSurrogate(s.charAt(start + length)));
212
// Implement span() with contains() for comparison.
213
static int containsSpanUTF16(final UnicodeSetWithStrings set, final String s,
214
SpanCondition spanCondition) {
215
final UnicodeSet realSet = set.getSet();
216
int length = s.length();
217
if (!set.hasStrings()) {
218
boolean spanContained = false;
219
if (spanCondition != SpanCondition.NOT_CONTAINED) {
220
spanContained = true; // Pin to 0/1 values.
225
while ((prev = start) < length) {
226
c = s.codePointAt(start);
227
start = s.offsetByCodePoints(start, 1);
228
if (realSet.contains(c) != spanContained) {
233
} else if (spanCondition == SpanCondition.NOT_CONTAINED) {
234
UnicodeSetWithStringsIterator iter = new UnicodeSetWithStringsIterator(set);
237
for (start = next = 0; start < length;) {
238
c = s.codePointAt(next);
239
next = s.offsetByCodePoints(next, 1);
240
if (realSet.contains(c)) {
245
while ((str = iter.nextString()) != null) {
246
if (str.length() <= (length - start) && matches16CPB(s, start, length, str)) {
247
// spanNeedsStrings=true;
254
} else /* CONTAINED or SIMPLE */{
255
UnicodeSetWithStringsIterator iter = new UnicodeSetWithStringsIterator(set);
257
int start, next, maxSpanLimit = 0;
258
for (start = next = 0; start < length;) {
259
c = s.codePointAt(next);
260
next = s.offsetByCodePoints(next, 1);
261
if (!realSet.contains(c)) {
262
next = start; // Do not span this single, not-contained code point.
266
while ((str = iter.nextString()) != null) {
267
if (str.length() <= (length - start) && matches16CPB(s, start, length, str)) {
268
// spanNeedsStrings=true;
269
int matchLimit = start + str.length();
270
if (matchLimit == length) {
273
if (spanCondition == SpanCondition.CONTAINED) {
274
// Iterate for the shortest match at each position.
275
// Recurse for each but the shortest match.
277
next = matchLimit; // First match from start.
279
if (matchLimit < next) {
280
// Remember shortest match from start for iteration.
285
// Recurse for non-shortest match from start.
286
int spanLength = containsSpanUTF16(set, s.substring(matchLimit),
287
SpanCondition.CONTAINED);
288
if ((matchLimit + spanLength) > maxSpanLimit) {
289
maxSpanLimit = matchLimit + spanLength;
290
if (maxSpanLimit == length) {
295
} else /* spanCondition==SIMPLE */{
296
if (matchLimit > next) {
297
// Remember longest match from start.
304
break; // No match from start.
308
if (start > maxSpanLimit) {
316
static int containsSpanBackUTF16(final UnicodeSetWithStrings set, final String s, int length,
317
SpanCondition spanCondition) {
321
final UnicodeSet realSet = set.getSet();
322
if (!set.hasStrings()) {
323
boolean spanContained = false;
324
if (spanCondition != SpanCondition.NOT_CONTAINED) {
325
spanContained = true; // Pin to 0/1 values.
331
c = s.codePointBefore(prev);
332
if (realSet.contains(c) != spanContained) {
335
prev = s.offsetByCodePoints(prev, -1);
338
} else if (spanCondition == SpanCondition.NOT_CONTAINED) {
339
UnicodeSetWithStringsIterator iter = new UnicodeSetWithStringsIterator(set);
341
int prev = length, length0 = length;
343
c = s.codePointBefore(prev);
344
if (realSet.contains(c)) {
349
while ((str = iter.nextString()) != null) {
350
if (str.length() <= prev && matches16CPB(s, prev - str.length(), length0, str)) {
351
// spanNeedsStrings=true;
355
prev = s.offsetByCodePoints(prev, -1);
358
} else /* SpanCondition.CONTAINED or SIMPLE */{
359
UnicodeSetWithStringsIterator iter = new UnicodeSetWithStringsIterator(set);
361
int prev = length, minSpanStart = length, length0 = length;
363
c = s.codePointBefore(length);
364
length = s.offsetByCodePoints(length, -1);
365
if (!realSet.contains(c)) {
366
length = prev; // Do not span this single, not-contained code point.
370
while ((str = iter.nextString()) != null) {
371
if (str.length() <= prev && matches16CPB(s, prev - str.length(), length0, str)) {
372
// spanNeedsStrings=true;
373
int matchStart = prev - str.length();
374
if (matchStart == 0) {
377
if (spanCondition == SpanCondition.CONTAINED) {
378
// Iterate for the shortest match at each position.
379
// Recurse for each but the shortest match.
380
if (length == prev) {
381
length = matchStart; // First match from prev.
383
if (matchStart > length) {
384
// Remember shortest match from prev for iteration.
389
// Recurse for non-shortest match from prev.
390
int spanStart = containsSpanBackUTF16(set, s, matchStart,
391
SpanCondition.CONTAINED);
392
if (spanStart < minSpanStart) {
393
minSpanStart = spanStart;
394
if (minSpanStart == 0) {
399
} else /* spanCondition==SIMPLE */{
400
if (matchStart < length) {
401
// Remember longest match from prev.
407
if (length == prev) {
408
break; // No match from prev.
410
} while ((prev = length) > 0);
411
if (prev < minSpanStart) {
419
// spans to be performed and compared
420
static final int SPAN_UTF16 = 1;
421
static final int SPAN_UTF8 = 2;
422
static final int SPAN_UTFS = 3;
424
static final int SPAN_SET = 4;
425
static final int SPAN_COMPLEMENT = 8;
426
static final int SPAN_POLARITY = 0xc;
428
static final int SPAN_FWD = 0x10;
429
static final int SPAN_BACK = 0x20;
430
static final int SPAN_DIRS = 0x30;
432
static final int SPAN_CONTAINED = 0x100;
433
static final int SPAN_SIMPLE = 0x200;
434
static final int SPAN_CONDITION = 0x300;
436
static final int SPAN_ALL = 0x33f;
438
static SpanCondition invertSpanCondition(SpanCondition spanCondition, SpanCondition contained) {
439
return spanCondition == SpanCondition.NOT_CONTAINED ? contained
440
: SpanCondition.NOT_CONTAINED;
444
* Count spans on a string with the method according to type and set the span limits. The set may be the complement
445
* of the original. When using spanBack() and comparing with span(), use a span condition for the first spanBack()
446
* according to the expected number of spans. Sets typeName to an empty string if there is no such type. Returns -1
447
* if the span option is filtered out.
449
static int getSpans(final UnicodeSetWithStrings set, boolean isComplement, final String s,
450
int whichSpans, int type, String[] typeName, int limits[], int limitsCapacity,
452
final UnicodeSet realSet = set.getSet();
454
SpanCondition spanCondition, firstSpanCondition, contained;
457
int length = s.length();
458
if (type < 0 || 7 < type) {
463
final String typeNames16[] = {
473
typeName[0] = typeNames16[type];
475
// filter span options
478
if ((whichSpans & SPAN_FWD) == 0) {
484
if ((whichSpans & SPAN_BACK) == 0) {
489
if ((type & 1) == 0) {
490
// use SpanCondition.CONTAINED
491
if ((whichSpans & SPAN_CONTAINED) == 0) {
494
contained = SpanCondition.CONTAINED;
497
if ((whichSpans & SPAN_SIMPLE) == 0) {
500
contained = SpanCondition.SIMPLE;
503
// Default first span condition for going forward with an uncomplemented set.
504
spanCondition = SpanCondition.NOT_CONTAINED;
506
spanCondition = invertSpanCondition(spanCondition, contained);
509
// First span condition for span(), used to terminate the spanBack() iteration.
510
firstSpanCondition = spanCondition;
512
// spanBack(): Its initial span condition is span()'s last span condition,
513
// which is the opposite of span()'s first span condition
514
// if we expect an even number of spans.
515
// (The loop inverts spanCondition (expectCount-1) times
516
// before the expectCount'th span() call.)
517
// If we do not compare forward and backward directions, then we do not have an
518
// expectCount and just start with firstSpanCondition.
519
if (!isForward && (whichSpans & SPAN_FWD) != 0 && (expectCount & 1) == 0) {
520
spanCondition = invertSpanCondition(spanCondition, contained);
529
start += containsSpanUTF16(set, s.substring(start), spanCondition);
530
if (count < limitsCapacity) {
531
limits[count] = start;
534
if (start >= length) {
537
spanCondition = invertSpanCondition(spanCondition, contained);
544
start = realSet.span(s, start, spanCondition);
545
if (count < limitsCapacity) {
546
limits[count] = start;
549
if (start >= length) {
552
spanCondition = invertSpanCondition(spanCondition, contained);
559
if (count <= limitsCapacity) {
560
limits[limitsCapacity - count] = length;
562
length = containsSpanBackUTF16(set, s, length, spanCondition);
563
if (length == 0 && spanCondition == firstSpanCondition) {
566
spanCondition = invertSpanCondition(spanCondition, contained);
568
if (count < limitsCapacity) {
569
for (i = count; i-- > 0;) {
570
limits[i] = limits[limitsCapacity - count + i];
578
if (count <= limitsCapacity) {
579
limits[limitsCapacity - count] = length >= 0 ? length : s.length();
581
length = realSet.spanBack(s, length, spanCondition);
582
if (length == 0 && spanCondition == firstSpanCondition) {
585
spanCondition = invertSpanCondition(spanCondition, contained);
587
if (count < limitsCapacity) {
588
for (i = count; i-- > 0;) {
589
limits[i] = limits[limitsCapacity - count + i];
601
// sets to be tested; odd index=isComplement
602
static final int SLOW = 0;
603
static final int SLOW_NOT = 1;
604
static final int FAST = 2;
605
static final int FAST_NOT = 3;
606
static final int SET_COUNT = 4;
608
static final String setNames[] = { "slow", "slow.not", "fast", "fast.not" };
611
* Verify that we get the same results whether we look at text with contains(), span() or spanBack(), using unfrozen
612
* or frozen versions of the set, and using the set or its complement (switching the spanConditions accordingly).
613
* The latter verifies that set.span(spanCondition) == set.complement().span(!spanCondition).
615
* The expectLimits[] are either provided by the caller (with expectCount>=0) or returned to the caller (with an
616
* input expectCount<0).
618
void verifySpan(final UnicodeSetWithStrings sets[], final String s, int whichSpans,
619
int expectLimits[], int expectCount, // TODO
620
final String testName, int index) {
621
int[] limits = new int[500];
624
String[] typeName = new String[1];
627
for (i = 0; i < SET_COUNT; ++i) {
629
// Even-numbered sets are original, uncomplemented sets.
630
if ((whichSpans & SPAN_SET) == 0) {
634
// Odd-numbered sets are complemented.
635
if ((whichSpans & SPAN_COMPLEMENT) == 0) {
639
for (type = 0;; ++type) {
640
limitsCount = getSpans(sets[i], (0 != (i & 1)), s, whichSpans, type, typeName, limits,
641
limits.length, expectCount);
642
if (typeName[0] == null) {
643
break; // All types tried.
645
if (limitsCount < 0) {
646
continue; // Span option filtered out.
648
if (expectCount < 0) {
649
expectCount = limitsCount;
650
if (limitsCount > limits.length) {
651
errln(String.format("FAIL: %s[0x%x].%s.%s span count=%d > %d capacity - too many spans",
652
testName, index, setNames[i], typeName[0], limitsCount, limits.length));
655
for (j = limitsCount; j-- > 0;) {
656
expectLimits[j] = limits[j];
658
} else if (limitsCount != expectCount) {
659
errln(String.format("FAIL: %s[0x%x].%s.%s span count=%d != %d", testName, index, setNames[i],
660
typeName[0], limitsCount, expectCount));
662
for (j = 0; j < limitsCount; ++j) {
663
if (limits[j] != expectLimits[j]) {
664
errln(String.format("FAIL: %s[0x%x].%s.%s span count=%d limits[%d]=%d != %d", testName,
665
index, setNames[i], typeName[0], limitsCount, j, limits[j], expectLimits[j]));
673
// Compare span() with containsAll()/containsNone(),
674
// but only if we have expectLimits[] from the uncomplemented set.
675
if ((whichSpans & SPAN_SET) != 0) {
676
final String s16 = s;
678
int prev = 0, limit, len;
679
for (i = 0; i < expectCount; ++i) {
680
limit = expectLimits[i];
683
string = s16.substring(prev, prev + len); // read-only alias
685
if (!sets[SLOW].getSet().containsAll(string)) {
686
errln(String.format("FAIL: %s[0x%x].%s.containsAll(%d..%d)==false contradicts span()",
687
testName, index, setNames[SLOW], prev, limit));
690
if (!sets[FAST].getSet().containsAll(string)) {
691
errln(String.format("FAIL: %s[0x%x].%s.containsAll(%d..%d)==false contradicts span()",
692
testName, index, setNames[FAST], prev, limit));
696
if (!sets[SLOW].getSet().containsNone(string)) {
697
errln(String.format("FAIL: %s[0x%x].%s.containsNone(%d..%d)==false contradicts span()",
698
testName, index, setNames[SLOW], prev, limit));
701
if (!sets[FAST].getSet().containsNone(string)) {
702
errln(String.format("FAIL: %s[0x%x].%s.containsNone(%d..%d)==false contradicts span()",
703
testName, index, setNames[FAST], prev, limit));
713
// Specifically test either UTF-16 or UTF-8.
714
void verifySpan(final UnicodeSetWithStrings sets[], final String s, int whichSpans,
715
final String testName, int index) {
716
int[] expectLimits = new int[500];
717
int expectCount = -1;
718
verifySpan(sets, s, whichSpans, expectLimits, expectCount, testName, index);
721
// Test both UTF-16 and UTF-8 versions of span() etc. on the same sets and text,
722
// unless either UTF is turned off in whichSpans.
723
// Testing UTF-16 and UTF-8 together requires that surrogate code points
724
// have the same contains(c) value as U+FFFD.
725
void verifySpanBothUTFs(final UnicodeSetWithStrings sets[], final String s16, int whichSpans,
726
final String testName, int index) {
727
int[] expectLimits = new int[500];
730
expectCount = -1; // Get expectLimits[] from verifySpan().
732
if ((whichSpans & SPAN_UTF16) != 0) {
733
verifySpan(sets, s16, whichSpans, expectLimits, expectCount, testName, index);
737
static int nextCodePoint(int c) {
738
// Skip some large and boring ranges.
759
// Verify that all implementations represent the same set.
760
void verifySpanContents(final UnicodeSetWithStrings sets[], int whichSpans, final String testName) {
761
StringBuffer s = new StringBuffer();
764
for (first = c = 0;; c = nextCodePoint(c)) {
765
if (c > 0x10ffff || s.length() > 1024) {
766
localWhichSpans = whichSpans;
767
verifySpanBothUTFs(sets, s.toString(), localWhichSpans, testName, first);
771
s.delete(0, s.length());
778
// Test with a particular, interesting string.
779
// Specify length and try NUL-termination.
780
static final char interestingStringChars[] = { 0x61, 0x62, 0x20, // Latin, space
781
0x3b1, 0x3b2, 0x3b3, // Greek
782
0xd900, // lead surrogate
783
0x3000, 0x30ab, 0x30ad, // wide space, Katakana
784
0xdc05, // trail surrogate
785
0xa0, 0xac00, 0xd7a3, // nbsp, Hangul
786
0xd900, 0xdc05, // unassigned supplementary
787
0xd840, 0xdfff, 0xd860, 0xdffe, // Han supplementary
788
0xd7a4, 0xdc05, 0xd900, 0x2028 // unassigned, surrogates in wrong order, LS
790
static String interestingString = new String(interestingStringChars);
791
static final String unicodeSet1 = "[[[:ID_Continue:]-[\\u30ab\\u30ad]]{\\u3000\\u30ab}{\\u3000\\u30ab\\u30ad}]";
793
public void TestInterestingStringSpan() {
794
UnicodeSet uset = new UnicodeSet(Utility.unescape(unicodeSet1));
795
SpanCondition spanCondition = SpanCondition.NOT_CONTAINED;
800
boolean contains = uset.contains(c);
801
if (false != contains) {
802
errln(String.format("FAIL: UnicodeSet(unicodeSet1).contains(%d) = true (expect false)",
806
UnicodeSetWithStrings set = new UnicodeSetWithStrings(uset);
807
int len = containsSpanUTF16(set, interestingString.substring(start), spanCondition);
809
errln(String.format("FAIL: containsSpanUTF16(unicodeSet1, \"%s(%d)\") = %d (expect %d)",
810
interestingString, start, len, expect));
813
len = uset.span(interestingString, start, spanCondition) - start;
815
errln(String.format("FAIL: UnicodeSet(unicodeSet1).span(\"%s\", %d) = %d (expect %d)",
816
interestingString, start, len, expect));
820
void verifySpanUTF16String(final UnicodeSetWithStrings sets[], int whichSpans, final String testName) {
821
if ((whichSpans & SPAN_UTF16) == 0) {
824
verifySpan(sets, interestingString, (whichSpans & ~SPAN_UTF8), testName, 1);
827
// Take a set of span options and multiply them so that
828
// each portion only has one of the options a, b and c.
829
// If b==0, then the set of options is just modified with mask and a.
830
// If b!=0 and c==0, then the set of options is just modified with mask, a and b.
831
static int addAlternative(int whichSpans[], int whichSpansCount, int mask, int a, int b, int c) {
835
for (i = 0; i < whichSpansCount; ++i) {
836
s = whichSpans[i] & mask;
837
whichSpans[i] = s | a;
839
whichSpans[whichSpansCount + i] = s | b;
841
whichSpans[2 * whichSpansCount + i] = s | c;
845
return b == 0 ? whichSpansCount : c == 0 ? 2 * whichSpansCount : 3 * whichSpansCount;
848
// They are not representable in UTF-8, and a leading trail surrogate
849
// and a trailing lead surrogate must not match in the middle of a proper surrogate pair.
850
// U+20001 == \\uD840\\uDC01
851
// U+20400 == \\uD841\\uDC00
852
static final String patternWithUnpairedSurrogate =
853
"[a\\U00020001\\U00020400{ab}{b\\uD840}{\\uDC00a}]";
854
static final String stringWithUnpairedSurrogate =
855
"aaab\\U00020001ba\\U00020400aba\\uD840ab\\uD840\\U00020000b\\U00020000a\\U00020000\\uDC00a\\uDC00babbb";
857
static final String _63_a = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
858
static final String _64_a = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
859
static final String _63_b = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
860
static final String _64_b = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
861
static final String longPattern =
862
"[a{" + _64_a + _64_a + _64_a + _64_a + "b}" + "{a" + _64_b + _64_b + _64_b + _64_b + "}]";
864
public void TestStringWithUnpairedSurrogateSpan() {
865
String string = Utility.unescape(stringWithUnpairedSurrogate);
866
UnicodeSet uset = new UnicodeSet(Utility.unescape(patternWithUnpairedSurrogate));
867
SpanCondition spanCondition = SpanCondition.NOT_CONTAINED;
871
UnicodeSetWithStrings set = new UnicodeSetWithStrings(uset);
872
int len = containsSpanUTF16(set, string.substring(start), spanCondition);
874
errln(String.format("FAIL: containsSpanUTF16(patternWithUnpairedSurrogate, \"%s(%d)\") = %d (expect %d)",
875
string, start, len, expect));
878
len = uset.span(string, start, spanCondition) - start;
880
errln(String.format("FAIL: UnicodeSet(patternWithUnpairedSurrogate).span(\"%s\", %d) = %d (expect %d)",
881
string, start, len, expect));
885
public void TestSpan() {
886
// "[...]" is a UnicodeSet pattern.
887
// "*" performs tests on all Unicode code points and on a selection of
888
// malformed UTF-8/16 strings.
889
// "-options" limits the scope of testing for the current set.
890
// By default, the test verifies that equivalent boundaries are found
891
// for UTF-16 and UTF-8, going forward and backward,
892
// alternating NOT_CONTAINED with
893
// either CONTAINED or SIMPLE.
894
// Single-character options:
895
// 8 -- UTF-16 and UTF-8 boundaries may differ.
896
// Cause: contains(U+FFFD) is inconsistent with contains(some surrogates),
897
// or the set contains strings with unpaired surrogates
898
// which do not translate to valid UTF-8.
899
// c -- set.span() and set.complement().span() boundaries may differ.
900
// Cause: Set strings are not complemented.
901
// b -- span() and spanBack() boundaries may differ.
902
// Cause: Strings in the set overlap, and spanBack(CONTAINED)
903
// and spanBack(SIMPLE) are defined to
904
// match with non-overlapping substrings.
905
// For example, with a set containing "ab" and "ba",
906
// span() of "aba" yields boundaries { 0, 2, 3 }
907
// because the initial "ab" matches from 0 to 2,
908
// while spanBack() yields boundaries { 0, 1, 3 }
909
// because the final "ba" matches from 1 to 3.
910
// l -- CONTAINED and SIMPLE boundaries may differ.
911
// Cause: Strings in the set overlap, and a longer match may
912
// require a sequence including non-longest substrings.
913
// For example, with a set containing "ab", "abc" and "cd",
914
// span(contained) of "abcd" spans the entire string
915
// but span(longest match) only spans the first 3 characters.
916
// Each "-options" first resets all options and then applies the specified options.
917
// A "-" without options resets the options.
918
// The options are also reset for each new set.
919
// Other strings will be spanned.
920
final String testdata[] = {
927
"[\\u0000-\\U0010FFFF]",
929
"[\\u0000\\u0080\\u0800\\U00010000]",
931
"[\\u007F\\u07FF\\uFFFF\\U0010FFFF]",
936
"[[[:ID_Continue:]-[\\u30ab\\u30ad]]{\\u30ab\\u30ad}{\\u3000\\u30ab\\u30ad}]",
940
// Overlapping strings cause overlapping attempts to match.
941
"[x{xy}{xya}{axy}{ax}]",
944
// More repetitions of "xya" would take too long with the recursive
945
// reference implementation.
946
// containsAll()=false
948
"xx" + "xyaxyaxyaxya" + // set.complement().span(longest match) will stop here.
949
"xx" + // set.complement().span(contained) will stop between the two 'x'es.
950
"xyaxyaxyaxya" + "xx" + "xyaxyaxyaxya" + // span() ends here.
953
// containsAll()=true
955
"xx" + "xyaxyaxyaxya" + "xx" + "xyaxyaxyaxya" + "xx" + "xyaxyaxyaxy",
959
"byayaxya", // span() -> { 4, 7, 8 } spanBack() -> { 5, 8 }
961
"byayaxy", // span() -> { 4, 7 } complement.span() -> { 7 }
962
"byayax", // span() -> { 4, 6 } complement.span() -> { 6 }
964
"byaya", // span() -> { 5 }
965
"byay", // span() -> { 4 }
966
"bya", // span() -> { 3 }
968
// span(longest match) will not span the whole string.
978
// spanBack(longest match) will not span the whole string.
987
// Test with non-ASCII set strings - test proper handling of surrogate pairs
988
// and UTF-8 trail bytes.
989
// Copies of above test sets and strings, but transliterated to have
990
// different code points with similar trail units.
992
// Unicode: 042B 30AB 200AB 204AB
993
// UTF-16: 042B 30AB D840 DCAB D841 DCAB
994
// UTF-8: D0 AB E3 82 AB F0 A0 82 AB F0 A0 92 AB
995
"[\\u042B{\\u042B\\u30AB}{\\u042B\\u30AB\\U000200AB}{\\U000200AB\\U000204AB}]",
997
"\\u042B\\U000200AB\\U000204AB\\u042B\\u30AB\\U000200AB\\U000204AB\\u042B\\u30AB\\U000200AB\\U000200AB\\U000204AB",
999
"[\\U000204AB{\\U000200AB\\U000204AB}{\\u30AB\\U000200AB\\U000204AB}{\\u042B\\u30AB}]",
1001
"\\u042B\\u30AB\\u30AB\\U000200AB\\U000204AB\\u042B\\u30AB\\U000200AB\\U000204AB\\u042B\\u30AB\\U000204AB",
1003
// Stress bookkeeping and recursion.
1004
// The following strings are barely doable with the recursive
1005
// reference implementation.
1006
// The not-contained character at the end prevents an early exit from the span().
1010
"bbbbbbbbbbbbbbbbbbbbbbbb-",
1011
// On complement sets, span() and spanBack() get different results
1012
// because b is not in the complement set and there is an odd number of b's
1013
// in the test string.
1015
"bbbbbbbbbbbbbbbbbbbbbbbbb-",
1017
// Test with set strings with an initial or final code point span
1021
_64_a + _64_a + _64_a + _63_a + "b",
1022
_64_a + _64_a + _64_a + _64_a + "b",
1023
_64_a + _64_a + _64_a + _64_a + "aaaabbbb",
1024
"a" + _64_b + _64_b + _64_b + _63_b,
1025
"a" + _64_b + _64_b + _64_b + _64_b,
1026
"aaaabbbb" + _64_b + _64_b + _64_b + _64_b,
1028
// Test with strings containing unpaired surrogates.
1029
patternWithUnpairedSurrogate, "-8cl",
1030
stringWithUnpairedSurrogate };
1032
int whichSpansCount = 1;
1033
int[] whichSpans = new int[96];
1034
for (i = whichSpans.length; i-- > 0;) {
1035
whichSpans[i] = SPAN_ALL;
1038
UnicodeSet[] sets = new UnicodeSet[SET_COUNT];
1039
UnicodeSetWithStrings[] sets_with_str = new UnicodeSetWithStrings[SET_COUNT];
1041
String testName = null;
1042
String testNameLimit;
1044
for (i = 0; i < testdata.length; ++i) {
1045
final String s = testdata[i];
1046
if (s.charAt(0) == '[') {
1047
// Create new test sets from this pattern.
1048
for (j = 0; j < SET_COUNT; ++j) {
1049
sets_with_str[j] = null;
1052
sets[SLOW] = new UnicodeSet(Utility.unescape(s));
1053
sets[SLOW_NOT] = new UnicodeSet(sets[SLOW]);
1054
sets[SLOW_NOT].complement();
1055
// Intermediate set: Test cloning of a frozen set.
1056
UnicodeSet fast = new UnicodeSet(sets[SLOW]);
1058
sets[FAST] = (UnicodeSet) fast.clone();
1060
UnicodeSet fastNot = new UnicodeSet(sets[SLOW_NOT]);
1062
sets[FAST_NOT] = (UnicodeSet) fastNot.clone();
1065
for (j = 0; j < SET_COUNT; ++j) {
1066
sets_with_str[j] = new UnicodeSetWithStrings(sets[j]);
1070
whichSpans[0] = SPAN_ALL;
1071
whichSpansCount = 1;
1072
} else if (s.charAt(0) == '-') {
1073
whichSpans[0] = SPAN_ALL;
1074
whichSpansCount = 1;
1076
for (j = 1; j < s.length(); j++) {
1077
switch (s.charAt(j)) {
1079
whichSpansCount = addAlternative(whichSpans, whichSpansCount, ~SPAN_POLARITY, SPAN_SET,
1080
SPAN_COMPLEMENT, 0);
1083
whichSpansCount = addAlternative(whichSpans, whichSpansCount, ~SPAN_DIRS, SPAN_FWD, SPAN_BACK,
1087
// test CONTAINED FWD & BACK, and separately
1088
// SIMPLE only FWD, and separately
1090
whichSpansCount = addAlternative(whichSpans, whichSpansCount, ~(SPAN_DIRS | SPAN_CONDITION),
1091
SPAN_DIRS | SPAN_CONTAINED, SPAN_FWD | SPAN_SIMPLE, SPAN_BACK | SPAN_SIMPLE);
1094
whichSpansCount = addAlternative(whichSpans, whichSpansCount, ~SPAN_UTFS, SPAN_UTF16,
1098
errln(String.format("FAIL: unrecognized span set option in \"%s\"", testdata[i]));
1102
} else if (s.equals("*")) {
1103
testNameLimit = "bad_string";
1104
for (j = 0; j < whichSpansCount; ++j) {
1105
if (whichSpansCount > 1) {
1106
testNameLimit += String.format("%%0x%3x", whichSpans[j]);
1108
verifySpanUTF16String(sets_with_str, whichSpans[j], testName);
1111
testNameLimit = "contents";
1112
for (j = 0; j < whichSpansCount; ++j) {
1113
if (whichSpansCount > 1) {
1114
testNameLimit += String.format("%%0x%3x", whichSpans[j]);
1116
verifySpanContents(sets_with_str, whichSpans[j], testName);
1119
String string = Utility.unescape(s);
1120
testNameLimit = "test_string";
1121
for (j = 0; j < whichSpansCount; ++j) {
1122
if (whichSpansCount > 1) {
1123
testNameLimit += String.format("%%0x%3x", whichSpans[j]);
1125
verifySpanBothUTFs(sets_with_str, string, whichSpans[j], testName, i);