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