/* GENERATED SOURCE. DO NOT MODIFY. */ // © 2016 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html /* ******************************************************************************* * Copyright (C) 1996-2016, International Business Machines Corporation and * others. All Rights Reserved. ******************************************************************************* */ package android.icu.text; import java.io.IOException; import java.text.ParsePosition; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.SortedSet; import java.util.TreeSet; import android.icu.impl.BMPSet; import android.icu.impl.CharacterPropertiesImpl; import android.icu.impl.PatternProps; import android.icu.impl.RuleCharacterIterator; import android.icu.impl.SortedSetRelation; import android.icu.impl.StringRange; import android.icu.impl.UCaseProps; import android.icu.impl.UCharacterProperty; import android.icu.impl.UPropertyAliases; import android.icu.impl.UnicodeSetStringSpan; import android.icu.impl.Utility; import android.icu.lang.CharSequences; import android.icu.lang.CharacterProperties; import android.icu.lang.UCharacter; import android.icu.lang.UProperty; import android.icu.lang.UScript; import android.icu.util.Freezable; import android.icu.util.ICUUncheckedIOException; import android.icu.util.OutputInt; import android.icu.util.ULocale; import android.icu.util.VersionInfo; /** * A mutable set of Unicode characters and multicharacter strings. * Objects of this class represent character classes used * in regular expressions. A character specifies a subset of Unicode * code points. Legal code points are U+0000 to U+10FFFF, inclusive. * * Note: method freeze() will not only make the set immutable, but * also makes important methods much higher performance: * contains(c), containsNone(...), span(...), spanBack(...) etc. * After the object is frozen, any subsequent call that wants to change * the object will throw UnsupportedOperationException. * *
The UnicodeSet class is not designed to be subclassed. * *
UnicodeSet
supports two APIs. The first is the
* operand API that allows the caller to modify the value of
* a UnicodeSet
object. It conforms to Java 2's
* java.util.Set
interface, although
* UnicodeSet
does not actually implement that
* interface. All methods of Set
are supported, with the
* modification that they take a character range or single character
* instead of an Object
, and they take a
* UnicodeSet
instead of a Collection
. The
* operand API may be thought of in terms of boolean logic: a boolean
* OR is implemented by add
, a boolean AND is implemented
* by retain
, a boolean XOR is implemented by
* complement
taking an argument, and a boolean NOT is
* implemented by complement
with no argument. In terms
* of traditional set theory function names, add
is a
* union, retain
is an intersection, remove
* is an asymmetric difference, and complement
with no
* argument is a set complement with respect to the superset range
* MIN_VALUE-MAX_VALUE
*
*
The second API is the
* applyPattern()
/toPattern()
API from the
* java.text.Format
-derived classes. Unlike the
* methods that add characters, add categories, and control the logic
* of the set, the method applyPattern()
sets all
* attributes of a UnicodeSet
at once, based on a
* string pattern.
*
*
Pattern syntax
* * Patterns are accepted by the constructors and the *applyPattern()
methods and returned by the
* toPattern()
method. These patterns follow a syntax
* similar to that employed by version 8 regular expression character
* classes. Here are some simple examples:
*
* ** * Any character may be preceded by a backslash in order to remove any special * meaning. White space characters, as defined by the Unicode Pattern_White_Space property, are * ignored, unless they are escaped. * **
** * []
No characters ** * [a]
The character 'a' ** ** [ae]
The characters 'a' and 'e' ** ** [a-e]
The characters 'a' through 'e' inclusive, in Unicode code * point order ** ** [\\u4E01]
The character U+4E01 ** ** [a{ab}{ac}]
The character 'a' and the multicharacter strings "ab" and * "ac" ** ** [\p{Lu}]
All characters in the general category Uppercase Letter *
Property patterns specify a set of characters having a certain * property as defined by the Unicode standard. Both the POSIX-like * "[:Lu:]" and the Perl-like syntax "\p{Lu}" are recognized. For a * complete list of supported property patterns, see the User's Guide * for UnicodeSet at * * https://unicode-org.github.io/icu/userguide/strings/unicodeset. * Actual determination of property data is defined by the underlying * Unicode database as implemented by UCharacter. * *
Patterns specify individual characters, ranges of characters, and * Unicode property sets. When elements are concatenated, they * specify their union. To complement a set, place a '^' immediately * after the opening '['. Property patterns are inverted by modifying * their delimiters; "[:^foo]" and "\P{foo}". In any other location, * '^' has no special meaning. * *
Since ICU 70, "[^...]", "[:^foo]", "\P{foo}", and "[:binaryProperty=No:]" * perform a “code point complement” (all code points minus the original set), * removing all multicharacter strings, * equivalent to .{@link #complement()}.{@link #removeAllStrings()} . * The {@link #complement()} API function continues to perform a * symmetric difference with all code points and thus retains all multicharacter strings. * *
Ranges are indicated by placing two a '-' between two * characters, as in "a-z". This specifies the range of all * characters from the left to the right, in Unicode order. If the * left character is greater than or equal to the * right character it is a syntax error. If a '-' occurs as the first * character after the opening '[' or '[^', or if it occurs as the * last character before the closing ']', then it is taken as a * literal. Thus "[a\\-b]", "[-ab]", and "[ab-]" all indicate the same * set of three characters, 'a', 'b', and '-'. * *
Sets may be intersected using the '&' operator or the asymmetric * set difference may be taken using the '-' operator, for example, * "[[:L:]&[\\u0000-\\u0FFF]]" indicates the set of all Unicode letters * with values less than 4096. Operators ('&' and '|') have equal * precedence and bind left-to-right. Thus * "[[:L:]-[a-z]-[\\u0100-\\u01FF]]" is equivalent to * "[[[:L:]-[a-z]]-[\\u0100-\\u01FF]]". This only really matters for * difference; intersection is commutative. * *
[a] | The set containing 'a' * |
[a-z] | The set containing 'a' * through 'z' and all letters in between, in Unicode order * |
[^a-z] | The set containing * all characters but 'a' through 'z', * that is, U+0000 through 'a'-1 and 'z'+1 through U+10FFFF * |
[[pat1][pat2]]
* | The union of sets specified by pat1 and pat2 * |
[[pat1]&[pat2]]
* | The intersection of sets specified by pat1 and pat2 * |
[[pat1]-[pat2]]
* | The asymmetric difference of sets specified by pat1 and * pat2 * |
[:Lu:] or \p{Lu}
* | The set of characters having the specified * Unicode property; in * this case, Unicode uppercase letters * |
[:^Lu:] or \P{Lu}
* | The set of characters not having the given * Unicode property * |
Formal syntax
* ****
** ** pattern :=
* ('[' '^'? item* ']') | * property
* ** item :=
* char | (char '-' char) | pattern-expr
** ** pattern-expr :=
* pattern | pattern-expr pattern | * pattern-expr op pattern
** ** op :=
* '&' | '-'
** ** special :=
* '[' | ']' | '-'
** ** char :=
any character that is not *special
any character
* | ('\\')
* | ('\u' hex hex hex hex)
** ** hex :=
* '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' |
* 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'a' | 'b' | 'c' | 'd' | 'e' | 'f'* ** property :=
a Unicode property set pattern *
**
** *Legend: **
** ** a := b
* * a
may be replaced byb
* ** a?
* zero or one instance of *a
** ** a*
* one or more instances of *a
** ** a | b
* either *a
orb
** ** 'a'
* the literal string between the quotes *
To iterate over contents of UnicodeSet, the following are available: *
To replace, count elements, or delete spans, see {@link android.icu.text.UnicodeSetSpanner UnicodeSetSpanner}.
*
* @author Alan Liu
* @see UnicodeSetIterator
* @see UnicodeSetSpanner
*/
public class UnicodeSet extends UnicodeFilter implements Iterable Note: This performs a symmetric difference with all code points
* and thus retains all multicharacter strings.
* In order to achieve a “code point complement” (all code points minus this set),
* the easiest is to .{@link #complement()}.{@link #removeAllStrings()} .
*/
public UnicodeSet complement() {
checkFrozen();
if (list[0] == LOW) {
System.arraycopy(list, 1, list, 0, len-1);
--len;
} else {
ensureCapacity(len+1);
System.arraycopy(list, 0, list, 1, len);
list[0] = LOW;
++len;
}
pat = null;
return this;
}
/**
* Complement the specified string in this set.
* The set will not contain the specified string once the call
* returns.
*
* @param s the string to complement
* @return this object, for chaining
*/
public final UnicodeSet complement(CharSequence s) {
checkFrozen();
int cp = getSingleCP(s);
if (cp < 0) {
String s2 = s.toString();
if (strings.contains(s2)) {
strings.remove(s2);
} else {
addString(s2);
}
pat = null;
} else {
complement(cp, cp);
}
return this;
}
/**
* Returns true if this set contains the given character.
* @param c character to be checked for containment
* @return true if the test condition is met
*/
@Override
public boolean contains(int c) {
if (c < MIN_VALUE || c > MAX_VALUE) {
throw new IllegalArgumentException("Invalid code point U+" + Utility.hex(c, 6));
}
if (bmpSet != null) {
return bmpSet.contains(c);
}
if (stringSpan != null) {
return stringSpan.contains(c);
}
/*
// Set i to the index of the start item greater than ch
// We know we will terminate without length test!
int i = -1;
while (true) {
if (c < list[++i]) break;
}
*/
int i = findCodePoint(c);
return ((i & 1) != 0); // return true if odd
}
/**
* Returns the smallest value i such that c < list[i]. Caller
* must ensure that c is a legal value or this method will enter
* an infinite loop. This method performs a binary search.
* @param c a character in the range MIN_VALUE..MAX_VALUE
* inclusive
* @return the smallest integer i in the range 0..len-1,
* inclusive, such that c < list[i]
*/
private final int findCodePoint(int c) {
/* Examples:
findCodePoint(c)
set list[] c=0 1 3 4 7 8
=== ============== ===========
[] [110000] 0 0 0 0 0 0
[\u0000-\u0003] [0, 4, 110000] 1 1 1 2 2 2
[\u0004-\u0007] [4, 8, 110000] 0 0 0 1 1 2
[:all:] [0, 110000] 1 1 1 1 1 1
*/
// Return the smallest i such that c < list[i]. Assume
// list[len - 1] == HIGH and that c is legal (0..HIGH-1).
if (c < list[0]) return 0;
// High runner test. c is often after the last range, so an
// initial check for this condition pays off.
if (len >= 2 && c >= list[len-2]) return len-1;
int lo = 0;
int hi = len - 1;
// invariant: c >= list[lo]
// invariant: c < list[hi]
for (;;) {
int i = (lo + hi) >>> 1;
if (i == lo) return hi;
if (c < list[i]) {
hi = i;
} else {
lo = i;
}
}
}
// //----------------------------------------------------------------
// // Unrolled binary search
// //----------------------------------------------------------------
//
// private int validLen = -1; // validated value of len
// private int topOfLow;
// private int topOfHigh;
// private int power;
// private int deltaStart;
//
// private void validate() {
// if (len <= 1) {
// throw new IllegalArgumentException("list.len==" + len + "; must be >1");
// }
//
// // find greatest power of 2 less than or equal to len
// for (power = exp2.length-1; power > 0 && exp2[power] > len; power--) {}
//
// // assert(exp2[power] <= len);
//
// // determine the starting points
// topOfLow = exp2[power] - 1;
// topOfHigh = len - 1;
// deltaStart = exp2[power-1];
// validLen = len;
// }
//
// private static final int exp2[] = {
// 0x1, 0x2, 0x4, 0x8,
// 0x10, 0x20, 0x40, 0x80,
// 0x100, 0x200, 0x400, 0x800,
// 0x1000, 0x2000, 0x4000, 0x8000,
// 0x10000, 0x20000, 0x40000, 0x80000,
// 0x100000, 0x200000, 0x400000, 0x800000,
// 0x1000000, 0x2000000, 0x4000000, 0x8000000,
// 0x10000000, 0x20000000 // , 0x40000000 // no unsigned int in Java
// };
//
// /**
// * Unrolled lowest index GT.
// */
// private final int leastIndexGT(int searchValue) {
//
// if (len != validLen) {
// if (len == 1) return 0;
// validate();
// }
// int temp;
//
// // set up initial range to search. Each subrange is a power of two in length
// int high = searchValue < list[topOfLow] ? topOfLow : topOfHigh;
//
// // Completely unrolled binary search, folhighing "Programming Pearls"
// // Each case deliberately falls through to the next
// // Logically, list[-1] < all_search_values && list[count] > all_search_values
// // although the values -1 and count are never actually touched.
//
// // The bounds at each point are low & high,
// // where low == high - delta*2
// // so high - delta is the midpoint
//
// // The invariant AFTER each line is that list[low] < searchValue <= list[high]
//
// switch (power) {
// //case 31: if (searchValue < list[temp = high-0x40000000]) high = temp; // no unsigned int in Java
// case 30: if (searchValue < list[temp = high-0x20000000]) high = temp;
// case 29: if (searchValue < list[temp = high-0x10000000]) high = temp;
//
// case 28: if (searchValue < list[temp = high- 0x8000000]) high = temp;
// case 27: if (searchValue < list[temp = high- 0x4000000]) high = temp;
// case 26: if (searchValue < list[temp = high- 0x2000000]) high = temp;
// case 25: if (searchValue < list[temp = high- 0x1000000]) high = temp;
//
// case 24: if (searchValue < list[temp = high- 0x800000]) high = temp;
// case 23: if (searchValue < list[temp = high- 0x400000]) high = temp;
// case 22: if (searchValue < list[temp = high- 0x200000]) high = temp;
// case 21: if (searchValue < list[temp = high- 0x100000]) high = temp;
//
// case 20: if (searchValue < list[temp = high- 0x80000]) high = temp;
// case 19: if (searchValue < list[temp = high- 0x40000]) high = temp;
// case 18: if (searchValue < list[temp = high- 0x20000]) high = temp;
// case 17: if (searchValue < list[temp = high- 0x10000]) high = temp;
//
// case 16: if (searchValue < list[temp = high- 0x8000]) high = temp;
// case 15: if (searchValue < list[temp = high- 0x4000]) high = temp;
// case 14: if (searchValue < list[temp = high- 0x2000]) high = temp;
// case 13: if (searchValue < list[temp = high- 0x1000]) high = temp;
//
// case 12: if (searchValue < list[temp = high- 0x800]) high = temp;
// case 11: if (searchValue < list[temp = high- 0x400]) high = temp;
// case 10: if (searchValue < list[temp = high- 0x200]) high = temp;
// case 9: if (searchValue < list[temp = high- 0x100]) high = temp;
//
// case 8: if (searchValue < list[temp = high- 0x80]) high = temp;
// case 7: if (searchValue < list[temp = high- 0x40]) high = temp;
// case 6: if (searchValue < list[temp = high- 0x20]) high = temp;
// case 5: if (searchValue < list[temp = high- 0x10]) high = temp;
//
// case 4: if (searchValue < list[temp = high- 0x8]) high = temp;
// case 3: if (searchValue < list[temp = high- 0x4]) high = temp;
// case 2: if (searchValue < list[temp = high- 0x2]) high = temp;
// case 1: if (searchValue < list[temp = high- 0x1]) high = temp;
// }
//
// return high;
// }
//
// // For debugging only
// public int len() {
// return len;
// }
//
// //----------------------------------------------------------------
// //----------------------------------------------------------------
/**
* Returns true if this set contains every character
* of the given range.
* @param start first character, inclusive, of the range
* @param end last character, inclusive, of the range
* @return true if the test condition is met
*/
public boolean contains(int start, int end) {
if (start < MIN_VALUE || start > MAX_VALUE) {
throw new IllegalArgumentException("Invalid code point U+" + Utility.hex(start, 6));
}
if (end < MIN_VALUE || end > MAX_VALUE) {
throw new IllegalArgumentException("Invalid code point U+" + Utility.hex(end, 6));
}
//int i = -1;
//while (true) {
// if (start < list[++i]) break;
//}
int i = findCodePoint(start);
return ((i & 1) != 0 && end < list[i]);
}
/**
* Returns true if this set contains the given
* multicharacter string.
* @param s string to be checked for containment
* @return true if this set contains the specified string
*/
public final boolean contains(CharSequence s) {
int cp = getSingleCP(s);
if (cp < 0) {
return strings.contains(s.toString());
} else {
return contains(cp);
}
}
/**
* Returns true if this set contains all the characters and strings
* of the given set.
* @param b set to be checked for containment
* @return true if the test condition is met
*/
public boolean containsAll(UnicodeSet b) {
// The specified set is a subset if all of its pairs are contained in
// this set. This implementation accesses the lists directly for speed.
// TODO: this could be faster if size() were cached. But that would affect building speed
// so it needs investigation.
int[] listB = b.list;
boolean needA = true;
boolean needB = true;
int aPtr = 0;
int bPtr = 0;
int aLen = len - 1;
int bLen = b.len - 1;
int startA = 0, startB = 0, limitA = 0, limitB = 0;
while (true) {
// double iterations are such a pain...
if (needA) {
if (aPtr >= aLen) {
// ran out of A. If B is also exhausted, then break;
if (needB && bPtr >= bLen) {
break;
}
return false;
}
startA = list[aPtr++];
limitA = list[aPtr++];
}
if (needB) {
if (bPtr >= bLen) {
// ran out of B. Since we got this far, we have an A and we are ok so far
break;
}
startB = listB[bPtr++];
limitB = listB[bPtr++];
}
// if B doesn't overlap and is greater than A, get new A
if (startB >= limitA) {
needA = true;
needB = false;
continue;
}
// if B is wholy contained in A, then get a new B
if (startB >= startA && limitB <= limitA) {
needA = false;
needB = true;
continue;
}
// all other combinations mean we fail
return false;
}
if (!strings.containsAll(b.strings)) return false;
return true;
}
// /**
// * Returns true if this set contains all the characters and strings
// * of the given set.
// * @param c set to be checked for containment
// * @return true if the test condition is met
// * @stable ICU 2.0
// */
// public boolean containsAllOld(UnicodeSet c) {
// // The specified set is a subset if all of its pairs are contained in
// // this set. It's possible to code this more efficiently in terms of
// // direct manipulation of the inversion lists if the need arises.
// int n = c.getRangeCount();
// for (int i=0; i This value is an options bit set value for some
* constructors, applyPattern(), and closeOver().
* It can be ORed together with other, unrelated options.
*
* The resulting set is a superset of the input for the code points but
* not for the strings.
* It performs a case mapping closure of the code points and adds
* full case folding strings for the code points, and reduces strings of
* the original set to their full case folding equivalents.
*
* This is designed for case-insensitive matches, for example
* in regular expressions. The full code point case closure allows checking of
* an input character directly against the closure set.
* Strings are matched by comparing the case-folded form from the closure
* set with an incremental case folding of the string in question.
*
* The closure set will also contain single code points if the original
* set contained case-equivalent strings (like U+00DF for "ss" or "Ss" etc.).
* This is not necessary (that is, redundant) for the above matching method
* but results in the same closure sets regardless of whether the original
* set contained the code point or a string.
*/
public static final int CASE_INSENSITIVE = 2;
/**
* Adds all case mappings for each element in the set.
* This adds the full lower-, title-, and uppercase mappings as well as the full case folding
* of each existing element in the set.
*
* This value is an options bit set value for some
* constructors, applyPattern(), and closeOver().
* It can be ORed together with other, unrelated options.
*
* Unlike the “case insensitive” options, this does not perform a closure.
* For example, it does not add 'ſ' (U+017F long s) for 's',
* 'K' (U+212A Kelvin sign) for 'k', or replace set strings by their case-folded versions.
*/
public static final int ADD_CASE_MAPPINGS = 4;
/**
* Enable case insensitive matching.
* Same as {@link #CASE_INSENSITIVE} but using only Simple_Case_Folding (scf) mappings,
* which map each code point to one code point,
* not full Case_Folding (cf) mappings, which map some code points to multiple code points.
*
* This is designed for case-insensitive matches, for example in certain
* regular expression implementations where only Simple_Case_Folding mappings are used,
* such as in ECMAScript (JavaScript) regular expressions.
*
* This value is an options bit set value for some
* constructors, applyPattern(), and closeOver().
* It can be ORed together with other, unrelated options.
*
* @hide unsupported on Android
*/
public static final int SIMPLE_CASE_INSENSITIVE = 6;
private static final int CASE_MASK = CASE_INSENSITIVE | ADD_CASE_MAPPINGS;
// add the result of a full case mapping to the set
// use str as a temporary string to avoid constructing one
private static final void addCaseMapping(UnicodeSet set, int result, StringBuilder full) {
if(result >= 0) {
if(result > UCaseProps.MAX_STRING_LENGTH) {
// add a single-code point case mapping
set.add(result);
} else {
// add a string case mapping from full with length result
set.add(full.toString());
full.setLength(0);
}
}
// result < 0: the code point mapped to itself, no need to add it
// see UCaseProps
}
/** For case closure on a large set, look only at code points with relevant properties. */
UnicodeSet maybeOnlyCaseSensitive(UnicodeSet src) {
if (src.size() < 30) {
return src;
}
// Return the intersection of the src code points with Case_Sensitive ones.
UnicodeSet sensitive = CharacterProperties.getBinaryPropertySet(UProperty.CASE_SENSITIVE);
// Start by cloning the "smaller" set. Try not to copy the strings, if there are any in src.
if (src.hasStrings() || src.getRangeCount() > sensitive.getRangeCount()) {
return sensitive.cloneAsThawed().retainAll(src);
} else {
return ((UnicodeSet) src.clone()).retainAll(sensitive);
}
}
// Per-character scf = Simple_Case_Folding of a string.
// (Normally when we case-fold a string we use full case foldings.)
private static final boolean scfString(CharSequence s, StringBuilder scf) {
int length = s.length();
// Loop while not needing modification.
for (int i = 0; i < length;) {
int c = Character.codePointAt(s, i);
int scfChar = UCharacter.foldCase(c, UCharacter.FOLD_CASE_DEFAULT);
if (scfChar != c) {
// Copy the characters before c.
scf.setLength(0);
scf.append(s, 0, i);
// Loop over the rest of the string and keep case-folding.
for (;;) {
scf.appendCodePoint(scfChar);
i += Character.charCount(c);
if (i == length) {
return true;
}
c = Character.codePointAt(s, i);
scfChar = UCharacter.foldCase(c, UCharacter.FOLD_CASE_DEFAULT);
}
}
i += Character.charCount(c);
}
return false;
}
/**
* Close this set over the given attribute. For the attribute
* {@link #CASE_INSENSITIVE}, the result is to modify this set so that:
*
* Example: [aq\u00DF{Bc}{bC}{Fi}] => [aAqQ\u00DF\uFB01{ss}{bc}{fi}]
*
* (Here foldCase(x) refers to the operation
* UCharacter.foldCase(x, true), and a == b actually denotes
* a.equals(b), not pointer comparison.)
*
* @param attribute bitmask for attributes to close over.
* Valid options:
* At most one of {@link #CASE_INSENSITIVE}, {@link #ADD_CASE_MAPPINGS},
* {@link #SIMPLE_CASE_INSENSITIVE}. These case options are mutually exclusive.
* Unrelated options bits are ignored.
* @return a reference to this set.
*/
public UnicodeSet closeOver(int attribute) {
checkFrozen();
switch (attribute & CASE_MASK) {
case 0:
break;
case CASE_INSENSITIVE:
closeOverCaseInsensitive(/* simple= */ false);
break;
case ADD_CASE_MAPPINGS:
closeOverAddCaseMappings();
break;
case SIMPLE_CASE_INSENSITIVE:
closeOverCaseInsensitive(/* simple= */ true);
break;
default:
// bad option (unreachable)
break;
}
return this;
}
private void closeOverCaseInsensitive(boolean simple) {
UCaseProps csp = UCaseProps.INSTANCE;
// Start with input set to guarantee inclusion.
UnicodeSet foldSet = new UnicodeSet(this);
// Full case mappings closure:
// Remove strings because the strings will actually be reduced (folded);
// therefore, start with no strings and add only those needed.
// Do this before processing code points, because they may add strings.
if (!simple && foldSet.hasStrings()) {
foldSet.strings.clear();
}
UnicodeSet codePoints = maybeOnlyCaseSensitive(this);
// Iterate over the ranges of single code points. Nested loop for each code point.
int n = codePoints.getRangeCount();
for (int i=0; i To replace, count elements, or delete spans, see {@link android.icu.text.UnicodeSetSpanner UnicodeSetSpanner}.
* @param s The string to be spanned
* @param spanCondition The span condition
* @return the length of the span
*/
public int span(CharSequence s, SpanCondition spanCondition) {
return span(s, 0, spanCondition);
}
/**
* Span a string using this UnicodeSet.
* If the start index is less than 0, span will start from 0.
* If the start index is greater than the string length, span returns the string length.
* To replace, count elements, or delete spans, see {@link android.icu.text.UnicodeSetSpanner UnicodeSetSpanner}.
* @param s The string to be spanned
* @param start The start index that the span begins
* @param spanCondition The span condition
* @return the string index which ends the span (i.e. exclusive)
*/
public int span(CharSequence s, int start, SpanCondition spanCondition) {
int end = s.length();
if (start < 0) {
start = 0;
} else if (start >= end) {
return end;
}
if (bmpSet != null) {
// Frozen set without strings, or no string is relevant for span().
return bmpSet.span(s, start, spanCondition, null);
}
if (stringSpan != null) {
return stringSpan.span(s, start, spanCondition);
} else if (hasStrings()) {
int which = spanCondition == SpanCondition.NOT_CONTAINED ? UnicodeSetStringSpan.FWD_UTF16_NOT_CONTAINED
: UnicodeSetStringSpan.FWD_UTF16_CONTAINED;
UnicodeSetStringSpan strSpan = new UnicodeSetStringSpan(this, new ArrayList<>(strings), which);
if (strSpan.needsStringSpanUTF16()) {
return strSpan.span(s, start, spanCondition);
}
}
return spanCodePointsAndCount(s, start, spanCondition, null);
}
/**
* Same as span() but also counts the smallest number of set elements on any path across the span.
* To replace, count elements, or delete spans, see {@link android.icu.text.UnicodeSetSpanner UnicodeSetSpanner}.
* @param outCount An output-only object (must not be null) for returning the count.
* @return the limit (exclusive end) of the span
* @deprecated This API is ICU internal only.
* @hide original deprecated declaration
* @hide draft / provisional / internal are hidden on Android
*/
@Deprecated
public int spanAndCount(CharSequence s, int start, SpanCondition spanCondition, OutputInt outCount) {
if (outCount == null) {
throw new IllegalArgumentException("outCount must not be null");
}
int end = s.length();
if (start < 0) {
start = 0;
} else if (start >= end) {
return end;
}
if (stringSpan != null) {
// We might also have bmpSet != null,
// but fully-contained strings are relevant for counting elements.
return stringSpan.spanAndCount(s, start, spanCondition, outCount);
} else if (bmpSet != null) {
return bmpSet.span(s, start, spanCondition, outCount);
} else if (hasStrings()) {
int which = spanCondition == SpanCondition.NOT_CONTAINED ? UnicodeSetStringSpan.FWD_UTF16_NOT_CONTAINED
: UnicodeSetStringSpan.FWD_UTF16_CONTAINED;
which |= UnicodeSetStringSpan.WITH_COUNT;
UnicodeSetStringSpan strSpan = new UnicodeSetStringSpan(this, new ArrayList<>(strings), which);
return strSpan.spanAndCount(s, start, spanCondition, outCount);
}
return spanCodePointsAndCount(s, start, spanCondition, outCount);
}
private int spanCodePointsAndCount(CharSequence s, int start,
SpanCondition spanCondition, OutputInt outCount) {
// Pin to 0/1 values.
boolean spanContained = (spanCondition != SpanCondition.NOT_CONTAINED);
int c;
int next = start;
int length = s.length();
int count = 0;
do {
c = Character.codePointAt(s, next);
if (spanContained != contains(c)) {
break;
}
++count;
next += Character.charCount(c);
} while (next < length);
if (outCount != null) { outCount.value = count; }
return next;
}
/**
* Span a string backwards (from the end) using this UnicodeSet.
* To replace, count elements, or delete spans, see {@link android.icu.text.UnicodeSetSpanner UnicodeSetSpanner}.
* @param s The string to be spanned
* @param spanCondition The span condition
* @return The string index which starts the span (i.e. inclusive).
*/
public int spanBack(CharSequence s, SpanCondition spanCondition) {
return spanBack(s, s.length(), spanCondition);
}
/**
* Span a string backwards (from the fromIndex) using this UnicodeSet.
* If the fromIndex is less than 0, spanBack will return 0.
* If fromIndex is greater than the string length, spanBack will start from the string length.
* To replace, count elements, or delete spans, see {@link android.icu.text.UnicodeSetSpanner UnicodeSetSpanner}.
* @param s The string to be spanned
* @param fromIndex The index of the char (exclusive) that the string should be spanned backwards
* @param spanCondition The span condition
* @return The string index which starts the span (i.e. inclusive).
*/
public int spanBack(CharSequence s, int fromIndex, SpanCondition spanCondition) {
if (fromIndex <= 0) {
return 0;
}
if (fromIndex > s.length()) {
fromIndex = s.length();
}
if (bmpSet != null) {
// Frozen set without strings, or no string is relevant for spanBack().
return bmpSet.spanBack(s, fromIndex, spanCondition);
}
if (stringSpan != null) {
return stringSpan.spanBack(s, fromIndex, spanCondition);
} else if (hasStrings()) {
int which = (spanCondition == SpanCondition.NOT_CONTAINED)
? UnicodeSetStringSpan.BACK_UTF16_NOT_CONTAINED
: UnicodeSetStringSpan.BACK_UTF16_CONTAINED;
UnicodeSetStringSpan strSpan = new UnicodeSetStringSpan(this, new ArrayList<>(strings), which);
if (strSpan.needsStringSpanUTF16()) {
return strSpan.spanBack(s, fromIndex, spanCondition);
}
}
// Pin to 0/1 values.
boolean spanContained = (spanCondition != SpanCondition.NOT_CONTAINED);
int c;
int prev = fromIndex;
do {
c = Character.codePointBefore(s, prev);
if (spanContained != contains(c)) {
break;
}
prev -= Character.charCount(c);
} while (prev > 0);
return prev;
}
/**
* Clone a thawed version of this class, according to the Freezable interface.
* @return the clone, not frozen
*/
@Override
public UnicodeSet cloneAsThawed() {
UnicodeSet result = new UnicodeSet(this);
assert !result.isFrozen();
return result;
}
// internal function
private void checkFrozen() {
if (isFrozen()) {
throw new UnsupportedOperationException("Attempt to modify frozen object");
}
}
// ************************
// Additional methods for integration with Generics and Collections
// ************************
/**
* A struct-like class used for iteration through ranges, for faster iteration than by String.
* Read about the restrictions on usage in {@link UnicodeSet#ranges()}.
*/
public static class EntryRange {
/**
* The starting code point of the range.
*/
public int codepoint;
/**
* The ending code point of the range
*/
public int codepointEnd;
EntryRange() {
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
StringBuilder b = new StringBuilder();
return (
codepoint == codepointEnd ? _appendToPat(b, codepoint, false)
: _appendToPat(_appendToPat(b, codepoint, false).append('-'), codepointEnd, false))
.toString();
}
}
/**
* Provide for faster iteration than by String. Returns an Iterable/Iterator over ranges of code points.
* The UnicodeSet must not be altered during the iteration.
* The EntryRange instance is the same each time; the contents are just reset.
*
* Warning: To iterate over the full contents, you have to also iterate over the strings.
*
* Warning: For speed, UnicodeSet iteration does not check for concurrent modification.
* Do not alter the UnicodeSet while iterating.
*
* Warning: For speed, UnicodeSet iteration does not check for concurrent modification.
* Do not alter the UnicodeSet while iterating.
* @see java.util.Set#iterator()
*/
@Override
public Iterator
* The functionality is straightforward for sets with only single code points, without strings (which is the common
* case):
*
* Note: Unpaired surrogates are treated like surrogate code points. Similarly, set strings match only on code point
* boundaries, never in the middle of a surrogate pair.
*/
public enum SpanCondition {
/**
* Continues a span() while there is no set element at the current position.
* Increments by one code point at a time.
* Stops before the first set element (character or string).
* (For code points only, this is like while contains(current)==false).
*
* When span() returns, the substring between where it started and the position it returned consists only of
* characters that are not in the set, and none of its strings overlap with the span.
*/
NOT_CONTAINED,
/**
* Spans the longest substring that is a concatenation of set elements (characters or strings).
* (For characters only, this is like while contains(current)==true).
*
* When span() returns, the substring between where it started and the position it returned consists only of set
* elements (characters or strings) that are in the set.
*
* If a set contains strings, then the span will be the longest substring for which there
* exists at least one non-overlapping concatenation of set elements (characters or strings).
* This is equivalent to a POSIX regular expression for
* When span() returns, the substring between where it started and the position it returned consists only of set
* elements (characters or strings) that are in the set.
*
* If a set only contains single characters, then this is the same as CONTAINED.
*
* If a set contains strings, then the span will be the longest substring with a match at each position with the
* longest single set element (character or string).
*
* Use this span condition together with other longest-match algorithms, such as ICU converters
* (ucnv_getUnicodeSet()).
*/
SIMPLE,
/**
* One more than the last span condition.
*/
CONDITION_COUNT
}
/**
* Get the default symbol table. Null means ordinary processing. For internal use only.
* @return the symbol table
* @deprecated This API is ICU internal only.
* @hide original deprecated declaration
* @hide draft / provisional / internal are hidden on Android
*/
@Deprecated
public static XSymbolTable getDefaultXSymbolTable() {
return XSYMBOL_TABLE;
}
/**
* Set the default symbol table. Null means ordinary processing. For internal use only. Will affect all subsequent parsing
* of UnicodeSets.
*
* WARNING: If this function is used with a UnicodeProperty, and the
* Unassigned characters (gc=Cn) are different than in ICU, you MUST call
* {@code UnicodeProperty.ResetCacheProperties} afterwards. If you then call {@code UnicodeSet.setDefaultXSymbolTable}
* with null to clear the value, you MUST also call {@code UnicodeProperty.ResetCacheProperties}.
*
* @param xSymbolTable the new default symbol table.
* @deprecated This API is ICU internal only.
* @hide original deprecated declaration
* @hide draft / provisional / internal are hidden on Android
*/
@Deprecated
public static void setDefaultXSymbolTable(XSymbolTable xSymbolTable) {
// If the properties override inclusions, these have to be regenerated.
// TODO: Check if the Unicode Tools or Unicode Utilities really need this.
CharacterPropertiesImpl.clear();
XSYMBOL_TABLE = xSymbolTable;
}
}
//eof
end >
* start
then an empty set is created.
*
* @param start first character, inclusive, of range
* @param end last character, inclusive, of range
*/
public UnicodeSet(int start, int end) {
this();
add(start, end);
}
/**
* Quickly constructs a set from a set of ranges <s0, e0, s1, e1, s2, e2, ..., sn, en>.
* There must be an even number of integers, and they must be all greater than zero,
* all less than or equal to Character.MAX_CODE_POINT.
* In each pair (..., si, ei, ...) it must be true that si <= ei
* Between adjacent pairs (...ei, sj...), it must be true that ei+1 < sj
* @param pairs pairs of character representing ranges
*/
public UnicodeSet(int... pairs) {
if ((pairs.length & 1) != 0) {
throw new IllegalArgumentException("Must have even number of integers");
}
list = new int[pairs.length + 1]; // don't allocate extra space, because it is likely that this is a fixed set.
len = list.length;
int last = -1; // used to ensure that the results are monotonically increasing.
int i = 0;
while (i < pairs.length) {
int start = pairs[i];
if (last >= start) {
throw new IllegalArgumentException("Must be monotonically increasing.");
}
list[i++] = start;
int limit = pairs[i] + 1;
if (start >= limit) {
throw new IllegalArgumentException("Must be monotonically increasing.");
}
list[i++] = last = limit;
}
list[i] = HIGH; // terminate
}
/**
* Constructs a set from the given pattern. See the class description
* for the syntax of the pattern language. Whitespace is ignored.
* @param pattern a string specifying what characters are in the set
* @exception java.lang.IllegalArgumentException if the pattern contains
* a syntax error.
*/
public UnicodeSet(String pattern) {
this();
applyPattern(pattern, null, null, IGNORE_SPACE);
}
/**
* Constructs a set from the given pattern. See the class description
* for the syntax of the pattern language.
* @param pattern a string specifying what characters are in the set
* @param ignoreWhitespace if true, ignore Unicode Pattern_White_Space characters
* @exception java.lang.IllegalArgumentException if the pattern contains
* a syntax error.
*/
public UnicodeSet(String pattern, boolean ignoreWhitespace) {
this();
applyPattern(pattern, null, null, ignoreWhitespace ? IGNORE_SPACE : 0);
}
/**
* Constructs a set from the given pattern. See the class description
* for the syntax of the pattern language.
* @param pattern a string specifying what characters are in the set
* @param options a bitmask indicating which options to apply.
* Valid options are {@link #IGNORE_SPACE} and
* at most one of {@link #CASE_INSENSITIVE}, {@link #ADD_CASE_MAPPINGS},
* {@link #SIMPLE_CASE_INSENSITIVE}. These case options are mutually exclusive.
* @exception java.lang.IllegalArgumentException if the pattern contains
* a syntax error.
*/
public UnicodeSet(String pattern, int options) {
this();
applyPattern(pattern, null, null, options);
}
/**
* Constructs a set from the given pattern. See the class description
* for the syntax of the pattern language.
* @param pattern a string specifying what characters are in the set
* @param pos on input, the position in pattern at which to start parsing.
* On output, the position after the last character parsed.
* @param symbols a symbol table mapping variables to char[] arrays
* and chars to UnicodeSets
* @exception java.lang.IllegalArgumentException if the pattern
* contains a syntax error.
*/
public UnicodeSet(String pattern, ParsePosition pos, SymbolTable symbols) {
this();
applyPattern(pattern, pos, symbols, IGNORE_SPACE);
}
/**
* Constructs a set from the given pattern. See the class description
* for the syntax of the pattern language.
* @param pattern a string specifying what characters are in the set
* @param pos on input, the position in pattern at which to start parsing.
* On output, the position after the last character parsed.
* @param symbols a symbol table mapping variables to char[] arrays
* and chars to UnicodeSets
* @param options a bitmask indicating which options to apply.
* Valid options are {@link #IGNORE_SPACE} and
* at most one of {@link #CASE_INSENSITIVE}, {@link #ADD_CASE_MAPPINGS},
* {@link #SIMPLE_CASE_INSENSITIVE}. These case options are mutually exclusive.
* @exception java.lang.IllegalArgumentException if the pattern
* contains a syntax error.
*/
public UnicodeSet(String pattern, ParsePosition pos, SymbolTable symbols, int options) {
this();
applyPattern(pattern, pos, symbols, options);
}
/**
* Return a new set that is equivalent to this one.
*/
@Override
public Object clone() {
if (isFrozen()) {
return this;
}
return new UnicodeSet(this);
}
/**
* Make this object represent the range start - end
.
* If start > end
then this object is set to an empty range.
*
* @param start first character in the set, inclusive
* @param end last character in the set, inclusive
*/
public UnicodeSet set(int start, int end) {
checkFrozen();
clear();
complement(start, end);
return this;
}
/**
* Make this object represent the same set as other
.
* @param other a UnicodeSet
whose value will be
* copied to this object
*/
public UnicodeSet set(UnicodeSet other) {
checkFrozen();
list = Arrays.copyOf(other.list, other.len);
len = other.len;
pat = other.pat;
if (other.hasStrings()) {
strings = new TreeSet<>(other.strings);
} else {
strings = EMPTY_STRINGS;
}
return this;
}
/**
* Modifies this set to represent the set specified by the given pattern.
* See the class description for the syntax of the pattern language.
* Whitespace is ignored.
* @param pattern a string specifying what characters are in the set
* @exception java.lang.IllegalArgumentException if the pattern
* contains a syntax error.
*/
public final UnicodeSet applyPattern(String pattern) {
checkFrozen();
return applyPattern(pattern, null, null, IGNORE_SPACE);
}
/**
* Modifies this set to represent the set specified by the given pattern,
* optionally ignoring whitespace.
* See the class description for the syntax of the pattern language.
* @param pattern a string specifying what characters are in the set
* @param ignoreWhitespace if true then Unicode Pattern_White_Space characters are ignored
* @exception java.lang.IllegalArgumentException if the pattern
* contains a syntax error.
*/
public UnicodeSet applyPattern(String pattern, boolean ignoreWhitespace) {
checkFrozen();
return applyPattern(pattern, null, null, ignoreWhitespace ? IGNORE_SPACE : 0);
}
/**
* Modifies this set to represent the set specified by the given pattern,
* optionally ignoring whitespace.
* See the class description for the syntax of the pattern language.
* @param pattern a string specifying what characters are in the set
* @param options a bitmask indicating which options to apply.
* Valid options are {@link #IGNORE_SPACE} and
* at most one of {@link #CASE_INSENSITIVE}, {@link #ADD_CASE_MAPPINGS},
* {@link #SIMPLE_CASE_INSENSITIVE}. These case options are mutually exclusive.
* @exception java.lang.IllegalArgumentException if the pattern
* contains a syntax error.
*/
public UnicodeSet applyPattern(String pattern, int options) {
checkFrozen();
return applyPattern(pattern, null, null, options);
}
/**
* Return true if the given position, in the given pattern, appears
* to be the start of a UnicodeSet pattern.
* @hide unsupported on Android
*/
public static boolean resemblesPattern(String pattern, int pos) {
return ((pos+1) < pattern.length() &&
pattern.charAt(pos) == '[') ||
resemblesPropertyPattern(pattern, pos);
}
/**
* TODO: create Appendable version of UTF16.append(buf, c),
* maybe in new class Appendables?
* @throws IOException
*/
private static void appendCodePoint(Appendable app, int c) {
assert 0 <= c && c <= 0x10ffff;
try {
if (c <= 0xffff) {
app.append((char) c);
} else {
app.append(UTF16.getLeadSurrogate(c)).append(UTF16.getTrailSurrogate(c));
}
} catch (IOException e) {
throw new ICUUncheckedIOException(e);
}
}
/**
* TODO: create class Appendables?
* @throws IOException
*/
private static void append(Appendable app, CharSequence s) {
try {
app.append(s);
} catch (IOException e) {
throw new ICUUncheckedIOException(e);
}
}
/**
* Append the toPattern()
representation of a
* string to the given Appendable
.
*/
private static toPattern()
representation of a
* character to the given Appendable
.
*/
private static charAt()
.
* @return an index from 0..size()-1, or -1
*/
public int indexOf(int c) {
if (c < MIN_VALUE || c > MAX_VALUE) {
throw new IllegalArgumentException("Invalid code point U+" + Utility.hex(c, 6));
}
int i = 0;
int n = 0;
for (;;) {
int start = list[i++];
if (c < start) {
return -1;
}
int limit = list[i++];
if (c < limit) {
return n + c - start;
}
n += limit - start;
}
}
/**
* Returns the character at the given index within this set, where
* the set is ordered by ascending code point. If the index is
* out of range, return -1. The inverse of this method is
* indexOf()
.
* @param index an index from 0..size()-1
* @return the character at the given index, or -1.
*/
public int charAt(int index) {
if (index >= 0) {
// len2 is the largest even integer <= len, that is, it is len
// for even values and len-1 for odd values. With odd values
// the last entry is UNICODESET_HIGH.
int len2 = len & ~1;
for (int i=0; i < len2;) {
int start = list[i++];
int count = list[i++] - start;
if (index < count) {
return start + index;
}
index -= count;
}
}
return -1;
}
/**
* Adds the specified range to this set if it is not already
* present. If this set already contains the specified range,
* the call leaves this set unchanged. If start > end
* then an empty range is added, leaving the set unchanged.
*
* @param start first character, inclusive, of range to be added
* to this set.
* @param end last character, inclusive, of range to be added
* to this set.
*/
public UnicodeSet add(int start, int end) {
checkFrozen();
return add_unchecked(start, end);
}
/**
* Adds all characters in range (uses preferred naming convention).
* @param start The index of where to start on adding all characters.
* @param end The index of where to end on adding all characters.
* @return a reference to this object
*/
public UnicodeSet addAll(int start, int end) {
checkFrozen();
return add_unchecked(start, end);
}
// for internal use, after checkFrozen has been called
private UnicodeSet add_unchecked(int start, int end) {
if (start < MIN_VALUE || start > MAX_VALUE) {
throw new IllegalArgumentException("Invalid code point U+" + Utility.hex(start, 6));
}
if (end < MIN_VALUE || end > MAX_VALUE) {
throw new IllegalArgumentException("Invalid code point U+" + Utility.hex(end, 6));
}
if (start < end) {
int limit = end + 1;
// Fast path for adding a new range after the last one.
// Odd list length: [..., lastStart, lastLimit, HIGH]
if ((len & 1) != 0) {
// If the list is empty, set lastLimit low enough to not be adjacent to 0.
int lastLimit = len == 1 ? -2 : list[len - 2];
if (lastLimit <= start) {
checkFrozen();
if (lastLimit == start) {
// Extend the last range.
list[len - 2] = limit;
if (limit == HIGH) {
--len;
}
} else {
list[len - 1] = start;
if (limit < HIGH) {
ensureCapacity(len + 2);
list[len++] = limit;
list[len++] = HIGH;
} else { // limit == HIGH
ensureCapacity(len + 1);
list[len++] = HIGH;
}
}
pat = null;
return this;
}
}
// This is slow. Could be much faster using findCodePoint(start)
// and modifying the list, dealing with adjacent & overlapping ranges.
add(range(start, end), 2, 0);
} else if (start == end) {
add(start);
}
return this;
}
// /**
// * Format out the inversion list as a string, for debugging. Uncomment when
// * needed.
// */
// public final String dump() {
// StringBuffer buf = new StringBuffer("[");
// for (int i=0; istart > end
then an empty range is
* retained, leaving the set empty.
*
* @param start first character, inclusive, of range
* @param end last character, inclusive, of range
*/
public UnicodeSet retain(int start, int end) {
checkFrozen();
if (start < MIN_VALUE || start > MAX_VALUE) {
throw new IllegalArgumentException("Invalid code point U+" + Utility.hex(start, 6));
}
if (end < MIN_VALUE || end > MAX_VALUE) {
throw new IllegalArgumentException("Invalid code point U+" + Utility.hex(end, 6));
}
if (start <= end) {
retain(range(start, end), 2, 0);
} else {
clear();
}
return this;
}
/**
* Retain the specified character from this set if it is present.
* Upon return this set will be empty if it did not contain c, or
* will only contain c if it did contain c.
* @param c the character to be retained
* @return this object, for chaining
*/
public final UnicodeSet retain(int c) {
return retain(c, c);
}
/**
* Retain the specified string in this set if it is present.
* Upon return this set will be empty if it did not contain s, or
* will only contain s if it did contain s.
* @param cs the string to be retained
* @return this object, for chaining
*/
public final UnicodeSet retain(CharSequence cs) {
int cp = getSingleCP(cs);
if (cp < 0) {
checkFrozen();
String s = cs.toString();
boolean isIn = strings.contains(s);
// Check for getRangeCount() first to avoid somewhat-expensive size()
// when there are single code points.
if (isIn && getRangeCount() == 0 && size() == 1) {
return this;
}
clear();
if (isIn) {
addString(s);
}
pat = null;
} else {
retain(cp, cp);
}
return this;
}
/**
* Removes the specified range from this set if it is present.
* The set will not contain the specified range once the call
* returns. If start > end
then an empty range is
* removed, leaving the set unchanged.
*
* @param start first character, inclusive, of range to be removed
* from this set.
* @param end last character, inclusive, of range to be removed
* from this set.
*/
public UnicodeSet remove(int start, int end) {
checkFrozen();
if (start < MIN_VALUE || start > MAX_VALUE) {
throw new IllegalArgumentException("Invalid code point U+" + Utility.hex(start, 6));
}
if (end < MIN_VALUE || end > MAX_VALUE) {
throw new IllegalArgumentException("Invalid code point U+" + Utility.hex(end, 6));
}
if (start <= end) {
retain(range(start, end), 2, 2);
}
return this;
}
/**
* Removes the specified character from this set if it is present.
* The set will not contain the specified character once the call
* returns.
* @param c the character to be removed
* @return this object, for chaining
*/
public final UnicodeSet remove(int c) {
return remove(c, c);
}
/**
* Removes the specified string from this set if it is present.
* The set will not contain the specified string once the call
* returns.
* @param s the string to be removed
* @return this object, for chaining
*/
public final UnicodeSet remove(CharSequence s) {
int cp = getSingleCP(s);
if (cp < 0) {
checkFrozen();
String str = s.toString();
if (strings.contains(str)) {
strings.remove(str);
pat = null;
}
} else {
remove(cp, cp);
}
return this;
}
/**
* Complements the specified range in this set. Any character in
* the range will be removed if it is in this set, or will be
* added if it is not in this set. If start > end
* then an empty range is complemented, leaving the set unchanged.
*
* @param start first character, inclusive, of range
* @param end last character, inclusive, of range
*/
public UnicodeSet complement(int start, int end) {
checkFrozen();
if (start < MIN_VALUE || start > MAX_VALUE) {
throw new IllegalArgumentException("Invalid code point U+" + Utility.hex(start, 6));
}
if (end < MIN_VALUE || end > MAX_VALUE) {
throw new IllegalArgumentException("Invalid code point U+" + Utility.hex(end, 6));
}
if (start <= end) {
xor(range(start, end), 2, 0);
}
pat = null;
return this;
}
/**
* Complements the specified character in this set. The character
* will be removed if it is in this set, or will be added if it is
* not in this set.
*/
public final UnicodeSet complement(int c) {
return complement(c, c);
}
/**
* This is equivalent to
* complement(MIN_VALUE, MAX_VALUE)
.
*
*
* containsAll is false for each of: "acb", "bcda", "bcx"
* @param s string containing characters to be checked for containment
* @return true if the test condition is met
*/
public boolean containsAll(String s) {
int cp;
for (int i = 0; i < s.length(); i += UTF16.getCharCount(cp)) {
cp = UTF16.charAt(s, i);
if (!contains(cp)) {
if (!hasStrings()) {
return false;
}
return containsAll(s, 0);
}
}
return true;
}
/**
* Recursive routine called if we fail to find a match in containsAll, and there are strings
* @param s source string
* @param i point to match to the end on
* @return true if ok
*/
private boolean containsAll(String s, int i) {
if (i >= s.length()) {
return true;
}
int cp= UTF16.charAt(s, i);
if (contains(cp) && containsAll(s, i+UTF16.getCharCount(cp))) {
return true;
}
for (String setStr : strings) {
if (!setStr.isEmpty() && // skip the empty string
s.startsWith(setStr, i) && containsAll(s, i+setStr.length())) {
return true;
}
}
return false;
}
/**
* Get the Regex equivalent for this UnicodeSet
* @return regex pattern equivalent to this UnicodeSet
* @deprecated This API is ICU internal only.
* @hide original deprecated declaration
* @hide draft / provisional / internal are hidden on Android
*/
@Deprecated
public String getRegexEquivalent() {
if (!hasStrings()) {
return toString();
}
StringBuilder result = new StringBuilder("(?:");
appendNewPattern(result, true, false);
for (String s : strings) {
result.append('|');
_appendToPat(result, s, true);
}
return result.append(")").toString();
}
/**
* Returns true if this set contains none of the characters
* of the given range.
* @param start first character, inclusive, of the range
* @param end last character, inclusive, of the range
* @return true if the test condition is met
*/
public boolean containsNone(int start, int end) {
if (start < MIN_VALUE || start > MAX_VALUE) {
throw new IllegalArgumentException("Invalid code point U+" + Utility.hex(start, 6));
}
if (end < MIN_VALUE || end > MAX_VALUE) {
throw new IllegalArgumentException("Invalid code point U+" + Utility.hex(end, 6));
}
int i = -1;
while (true) {
if (start < list[++i]) break;
}
return ((i & 1) == 0 && end < list[i]);
}
/**
* Returns true if none of the characters or strings in this UnicodeSet appears in the string.
* For example, for the Unicode set [a{bc}{cd}]
* containsNone is true for: "xy", "cb"
* containsNone is false for: "a", "bc", "bcd"
* @param b set to be checked for containment
* @return true if the test condition is met
*/
public boolean containsNone(UnicodeSet b) {
// The specified set is a subset if some of its pairs overlap with some of this set's pairs.
// This implementation accesses the lists directly for speed.
int[] listB = b.list;
boolean needA = true;
boolean needB = true;
int aPtr = 0;
int bPtr = 0;
int aLen = len - 1;
int bLen = b.len - 1;
int startA = 0, startB = 0, limitA = 0, limitB = 0;
while (true) {
// double iterations are such a pain...
if (needA) {
if (aPtr >= aLen) {
// ran out of A: break so we test strings
break;
}
startA = list[aPtr++];
limitA = list[aPtr++];
}
if (needB) {
if (bPtr >= bLen) {
// ran out of B: break so we test strings
break;
}
startB = listB[bPtr++];
limitB = listB[bPtr++];
}
// if B is higher than any part of A, get new A
if (startB >= limitA) {
needA = true;
needB = false;
continue;
}
// if A is higher than any part of B, get new B
if (startA >= limitB) {
needA = false;
needB = true;
continue;
}
// all other combinations mean we fail
return false;
}
if (!SortedSetRelation.hasRelation(strings, SortedSetRelation.DISJOINT, b.strings)) return false;
return true;
}
// /**
// * Returns true if none of the characters or strings in this UnicodeSet appears in the string.
// * For example, for the Unicode set [a{bc}{cd}]
// * containsNone is true for: "xy", "cb"
// * containsNone is false for: "a", "bc", "bcd"
// * @param c set to be checked for containment
// * @return true if the test condition is met
// * @stable ICU 2.0
// */
// public boolean containsNoneOld(UnicodeSet c) {
// // The specified set is a subset if all of its pairs are contained in
// // this set. It's possible to code this more efficiently in terms of
// // direct manipulation of the inversion lists if the need arises.
// int n = c.getRangeCount();
// for (int i=0; i0..getRangeCount()-1
* @see #getRangeCount
* @see #getRangeEnd
*/
public int getRangeStart(int index) {
return list[index*2];
}
/**
* Iteration method that returns the last character in the
* specified range of this set.
* @exception ArrayIndexOutOfBoundsException if index is outside
* the range 0..getRangeCount()-1
* @see #getRangeStart
* @see #getRangeEnd
*/
public int getRangeEnd(int index) {
return (list[index*2 + 1] - 1);
}
/**
* Reallocate this objects internal structures to take up the least
* possible space, without changing this object's value.
*/
public UnicodeSet compact() {
checkFrozen();
if ((len + 7) < list.length) {
// If we have more than a little unused capacity, shrink it to len.
list = Arrays.copyOf(list, len);
}
rangeList = null;
buffer = null;
if (strings != EMPTY_STRINGS && strings.isEmpty()) {
strings = EMPTY_STRINGS;
}
return this;
}
/**
* Compares the specified object with this set for equality. Returns
* true if the specified object is also a set, the two sets
* have the same size, and every member of the specified set is
* contained in this set (or equivalently, every member of this set is
* contained in the specified set).
*
* @param o Object to be compared for equality with this set.
* @return true if the specified Object is equal to this set.
*/
@Override
public boolean equals(Object o) {
if (o == null) {
return false;
}
if (this == o) {
return true;
}
try {
UnicodeSet that = (UnicodeSet) o;
if (len != that.len) return false;
for (int i = 0; i < len; ++i) {
if (list[i] != that.list[i]) return false;
}
if (!strings.equals(that.strings)) return false;
} catch (Exception e) {
return false;
}
return true;
}
/**
* Returns the hash code value for this set.
*
* @return the hash code value for this set.
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
int result = len;
for (int i = 0; i < len; ++i) {
result *= 1000003;
result += list[i];
}
return result;
}
/**
* Return a programmer-readable string representation of this object.
*/
@Override
public String toString() {
return toPattern(true);
}
//----------------------------------------------------------------
// Implementation: Pattern parsing
//----------------------------------------------------------------
/**
* Parses the given pattern, starting at the given position. The character
* at pattern.charAt(pos.getIndex()) must be '[', or the parse fails.
* Parsing continues until the corresponding closing ']'. If a syntax error
* is encountered between the opening and closing brace, the parse fails.
* Upon return from a successful parse, the ParsePosition is updated to
* point to the character following the closing ']', and an inversion
* list for the parsed pattern is returned. This method
* calls itself recursively to parse embedded subpatterns.
*
* @param pattern the string containing the pattern to be parsed. The
* portion of the string from pos.getIndex(), which must be a '[', to the
* corresponding closing ']', is parsed.
* @param pos upon entry, the position at which to being parsing. The
* character at pattern.charAt(pos.getIndex()) must be a '['. Upon return
* from a successful parse, pos.getIndex() is either the character after the
* closing ']' of the parsed pattern, or pattern.length() if the closing ']'
* is the last character of the pattern string.
* @return an inversion list for the parsed substring
* of pattern
* @exception java.lang.IllegalArgumentException if the parse fails.
* @deprecated This API is ICU internal only.
* @hide original deprecated declaration
* @hide draft / provisional / internal are hidden on Android
*/
@Deprecated
public UnicodeSet applyPattern(String pattern,
ParsePosition pos,
SymbolTable symbols,
int options) {
// Need to build the pattern in a temporary string because
// _applyPattern calls add() etc., which set pat to empty.
boolean parsePositionWasNull = pos == null;
if (parsePositionWasNull) {
pos = new ParsePosition(0);
}
StringBuilder rebuiltPat = new StringBuilder();
RuleCharacterIterator chars =
new RuleCharacterIterator(pattern, symbols, pos);
applyPattern(chars, symbols, rebuiltPat, options, 0);
if (chars.inVariable()) {
syntaxError(chars, "Extra chars in variable value");
}
pat = rebuiltPat.toString();
if (parsePositionWasNull) {
int i = pos.getIndex();
// Skip over trailing whitespace
if ((options & IGNORE_SPACE) != 0) {
i = PatternProps.skipWhiteSpace(pattern, i);
}
if (i != pattern.length()) {
throw new IllegalArgumentException("Parse of \"" + pattern +
"\" failed at " + i);
}
}
return this;
}
// Add constants to make the applyPattern() code easier to follow.
private static final int LAST0_START = 0,
LAST1_RANGE = 1,
LAST2_SET = 2;
private static final int MODE0_NONE = 0,
MODE1_INBRACKET = 1,
MODE2_OUTBRACKET = 2;
private static final int SETMODE0_NONE = 0,
SETMODE1_UNICODESET = 1,
SETMODE2_PROPERTYPAT = 2,
SETMODE3_PREPARSED = 3;
private static final int MAX_DEPTH = 100;
/**
* Parse the pattern from the given RuleCharacterIterator. The
* iterator is advanced over the parsed pattern.
* @param chars iterator over the pattern characters. Upon return
* it will be advanced to the first character after the parsed
* pattern, or the end of the iteration if all characters are
* parsed.
* @param symbols symbol table to use to parse and dereference
* variables, or null if none.
* @param rebuiltPat the pattern that was parsed, rebuilt or
* copied from the input pattern, as appropriate.
* @param options a bit mask.
* Valid options are {@link #IGNORE_SPACE} and
* at most one of {@link #CASE_INSENSITIVE}, {@link #ADD_CASE_MAPPINGS},
* {@link #SIMPLE_CASE_INSENSITIVE}. These case options are mutually exclusive.
*/
private void applyPattern(RuleCharacterIterator chars, SymbolTable symbols,
Appendable rebuiltPat, int options, int depth) {
if (depth > MAX_DEPTH) {
syntaxError(chars, "Pattern nested too deeply");
}
// Syntax characters: [ ] ^ - & { }
// Recognized special forms for chars, sets: c-c s-s s&s
int opts = RuleCharacterIterator.PARSE_VARIABLES |
RuleCharacterIterator.PARSE_ESCAPES;
if ((options & IGNORE_SPACE) != 0) {
opts |= RuleCharacterIterator.SKIP_WHITESPACE;
}
StringBuilder patBuf = new StringBuilder(), buf = null;
boolean usePat = false;
UnicodeSet scratch = null;
RuleCharacterIterator.Position backup = null;
// mode: 0=before [, 1=between [...], 2=after ]
// lastItem: 0=none, 1=char, 2=set
int lastItem = LAST0_START, lastChar = 0, mode = MODE0_NONE;
char op = 0;
boolean invert = false;
clear();
String lastString = null;
while (mode != MODE2_OUTBRACKET && !chars.atEnd()) {
//Eclipse stated the following is "dead code"
/*
if (false) {
// Debugging assertion
if (!((lastItem == 0 && op == 0) ||
(lastItem == 1 && (op == 0 || op == '-')) ||
(lastItem == 2 && (op == 0 || op == '-' || op == '&')))) {
throw new IllegalArgumentException();
}
}*/
int c = 0;
boolean literal = false;
UnicodeSet nested = null;
// -------- Check for property pattern
// setMode: 0=none, 1=unicodeset, 2=propertypat, 3=preparsed
int setMode = SETMODE0_NONE;
if (resemblesPropertyPattern(chars, opts)) {
setMode = SETMODE2_PROPERTYPAT;
}
// -------- Parse '[' of opening delimiter OR nested set.
// If there is a nested set, use `setMode' to define how
// the set should be parsed. If the '[' is part of the
// opening delimiter for this pattern, parse special
// strings "[", "[^", "[-", and "[^-". Check for stand-in
// characters representing a nested set in the symbol
// table.
else {
// Prepare to backup if necessary
backup = chars.getPos(backup);
c = chars.next(opts);
literal = chars.isEscaped();
if (c == '[' && !literal) {
if (mode == MODE1_INBRACKET) {
chars.setPos(backup); // backup
setMode = SETMODE1_UNICODESET;
} else {
// Handle opening '[' delimiter
mode = MODE1_INBRACKET;
patBuf.append('[');
backup = chars.getPos(backup); // prepare to backup
c = chars.next(opts);
literal = chars.isEscaped();
if (c == '^' && !literal) {
invert = true;
patBuf.append('^');
backup = chars.getPos(backup); // prepare to backup
c = chars.next(opts);
literal = chars.isEscaped();
}
// Fall through to handle special leading '-';
// otherwise restart loop for nested [], \p{}, etc.
if (c == '-') {
literal = true;
// Fall through to handle literal '-' below
} else {
chars.setPos(backup); // backup
continue;
}
}
} else if (symbols != null) {
UnicodeMatcher m = symbols.lookupMatcher(c); // may be null
if (m != null) {
try {
nested = (UnicodeSet) m;
setMode = SETMODE3_PREPARSED;
} catch (ClassCastException e) {
syntaxError(chars, "Syntax error");
}
}
}
}
// -------- Handle a nested set. This either is inline in
// the pattern or represented by a stand-in that has
// previously been parsed and was looked up in the symbol
// table.
if (setMode != SETMODE0_NONE) {
if (lastItem == LAST1_RANGE) {
if (op != 0) {
syntaxError(chars, "Char expected after operator");
}
add_unchecked(lastChar, lastChar);
_appendToPat(patBuf, lastChar, false);
lastItem = LAST0_START;
op = 0;
}
if (op == '-' || op == '&') {
patBuf.append(op);
}
if (nested == null) {
if (scratch == null) scratch = new UnicodeSet();
nested = scratch;
}
switch (setMode) {
case SETMODE1_UNICODESET:
nested.applyPattern(chars, symbols, patBuf, options, depth + 1);
break;
case SETMODE2_PROPERTYPAT:
chars.skipIgnored(opts);
nested.applyPropertyPattern(chars, patBuf, symbols);
break;
case SETMODE3_PREPARSED: // `nested' already parsed
nested._toPattern(patBuf, false);
break;
}
usePat = true;
if (mode == MODE0_NONE) {
// Entire pattern is a category; leave parse loop
set(nested);
mode = MODE2_OUTBRACKET;
break;
}
switch (op) {
case '-':
removeAll(nested);
break;
case '&':
retainAll(nested);
break;
case 0:
addAll(nested);
break;
}
op = 0;
lastItem = LAST2_SET;
continue;
}
if (mode == MODE0_NONE) {
syntaxError(chars, "Missing '['");
}
// -------- Parse special (syntax) characters. If the
// current character is not special, or if it is escaped,
// then fall through and handle it below.
if (!literal) {
switch (c) {
case ']':
if (lastItem == LAST1_RANGE) {
add_unchecked(lastChar, lastChar);
_appendToPat(patBuf, lastChar, false);
}
// Treat final trailing '-' as a literal
if (op == '-') {
add_unchecked(op, op);
patBuf.append(op);
} else if (op == '&') {
syntaxError(chars, "Trailing '&'");
}
patBuf.append(']');
mode = MODE2_OUTBRACKET;
continue;
case '-':
if (op == 0) {
if (lastItem != LAST0_START) {
op = (char) c;
continue;
} else if (lastString != null) {
op = (char) c;
continue;
} else {
// Treat final trailing '-' as a literal
add_unchecked(c, c);
c = chars.next(opts);
literal = chars.isEscaped();
if (c == ']' && !literal) {
patBuf.append("-]");
mode = MODE2_OUTBRACKET;
continue;
}
}
}
syntaxError(chars, "'-' not after char, string, or set");
break;
case '&':
if (lastItem == LAST2_SET && op == 0) {
op = (char) c;
continue;
}
syntaxError(chars, "'&' not after set");
break;
case '^':
syntaxError(chars, "'^' not after '['");
break;
case '{':
if (op != 0 && op != '-') {
syntaxError(chars, "Missing operand after operator");
}
if (lastItem == LAST1_RANGE) {
add_unchecked(lastChar, lastChar);
_appendToPat(patBuf, lastChar, false);
}
lastItem = LAST0_START;
if (buf == null) {
buf = new StringBuilder();
} else {
buf.setLength(0);
}
boolean ok = false;
while (!chars.atEnd()) {
c = chars.next(opts);
literal = chars.isEscaped();
if (c == '}' && !literal) {
ok = true;
break;
}
appendCodePoint(buf, c);
}
if (!ok) {
syntaxError(chars, "Invalid multicharacter string");
}
// We have new string. Add it to set and continue;
// we don't need to drop through to the further
// processing
String curString = buf.toString();
if (op == '-') {
int lastSingle = CharSequences.getSingleCodePoint(lastString == null ? "" : lastString);
int curSingle = CharSequences.getSingleCodePoint(curString);
if (lastSingle != Integer.MAX_VALUE && curSingle != Integer.MAX_VALUE) {
add(lastSingle,curSingle);
} else {
if (strings == EMPTY_STRINGS) {
strings = new TreeSet<>();
}
try {
StringRange.expand(lastString, curString, true, strings);
} catch (Exception e) {
syntaxError(chars, e.getMessage());
}
}
lastString = null;
op = 0;
} else {
add(curString);
lastString = curString;
}
patBuf.append('{');
_appendToPat(patBuf, curString, false);
patBuf.append('}');
continue;
case SymbolTable.SYMBOL_REF:
// symbols nosymbols
// [a-$] error error (ambiguous)
// [a$] anchor anchor
// [a-$x] var "x"* literal '$'
// [a-$.] error literal '$'
// *We won't get here in the case of var "x"
backup = chars.getPos(backup);
c = chars.next(opts);
literal = chars.isEscaped();
boolean anchor = (c == ']' && !literal);
if (symbols == null && !anchor) {
c = SymbolTable.SYMBOL_REF;
chars.setPos(backup);
break; // literal '$'
}
if (anchor && op == 0) {
if (lastItem == LAST1_RANGE) {
add_unchecked(lastChar, lastChar);
_appendToPat(patBuf, lastChar, false);
}
add_unchecked(UnicodeMatcher.ETHER);
usePat = true;
patBuf.append(SymbolTable.SYMBOL_REF).append(']');
mode = MODE2_OUTBRACKET;
continue;
}
syntaxError(chars, "Unquoted '$'");
break;
default:
break;
}
}
// -------- Parse literal characters. This includes both
// escaped chars ("\u4E01") and non-syntax characters
// ("a").
switch (lastItem) {
case LAST0_START:
if (op == '-' && lastString != null) {
syntaxError(chars, "Invalid range");
}
lastItem = LAST1_RANGE;
lastChar = c;
lastString = null;
break;
case LAST1_RANGE:
if (op == '-') {
if (lastString != null) {
syntaxError(chars, "Invalid range");
}
if (lastChar >= c) {
// Don't allow redundant (a-a) or empty (b-a) ranges;
// these are most likely typos.
syntaxError(chars, "Invalid range");
}
add_unchecked(lastChar, c);
_appendToPat(patBuf, lastChar, false);
patBuf.append(op);
_appendToPat(patBuf, c, false);
lastItem = LAST0_START;
op = 0;
} else {
add_unchecked(lastChar, lastChar);
_appendToPat(patBuf, lastChar, false);
lastChar = c;
}
break;
case LAST2_SET:
if (op != 0) {
syntaxError(chars, "Set expected after operator");
}
lastChar = c;
lastItem = LAST1_RANGE;
break;
}
}
if (mode != MODE2_OUTBRACKET) {
syntaxError(chars, "Missing ']'");
}
chars.skipIgnored(opts);
/**
* Handle global flags (invert, case insensitivity). If this
* pattern should be compiled case-insensitive, then we need
* to close over case BEFORE COMPLEMENTING. This makes
* patterns like /[^abc]/i work.
*/
if ((options & CASE_MASK) != 0) {
closeOver(options);
}
if (invert) {
complement().removeAllStrings(); // code point complement
}
// Use the rebuilt pattern (pat) only if necessary. Prefer the
// generated pattern.
if (usePat) {
append(rebuiltPat, patBuf.toString());
} else {
appendNewPattern(rebuiltPat, false, true);
}
}
private static void syntaxError(RuleCharacterIterator chars, String msg) {
throw new IllegalArgumentException("Error: " + msg + " at \"" +
Utility.escape(chars.toString()) +
'"');
}
/**
* Add the contents of the UnicodeSet (as strings) into a collection.
* @param target collection to add into
*/
public
*
*
*
* // Sample code
* for (EntryRange range : us1.ranges()) {
* // do something with code points between range.codepoint and range.codepointEnd;
* }
* for (String s : us1.strings()) {
* // do something with each string;
* }
*
*/
public Iterable
* for (String key : myUnicodeSet.strings()) {
* doSomethingWith(key);
* }
*
*/
public Collection
*
* When a set contains multi-code point strings, then these statements may not be true, depending on the strings in
* the set (for example, whether they overlap with each other) and the string that is processed. For a set with
* strings:
*
*
* Note: If it is important to get the same boundaries whether iterating forward or backward through a string, then
* either only span() should be used and the boundaries cached for backward operation, or an ICU BreakIterator could
* be used.
* (OR of each set element)*
.
* (Java/ICU/Perl regex stops at the first match of an OR.)
*/
CONTAINED,
/**
* Continues a span() while there is a set element at the current position.
* Increments by the longest matching element at each position.
* (For characters only, this is like while contains(current)==true).
*