/* * Copyright (c) 2005, 2011, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ /* ******************************************************************************* * (C) Copyright IBM Corp. and others, 1996-2009 - All Rights Reserved * * * * The original version of this source code and documentation is copyrighted * * and owned by IBM, These materials are provided under terms of a License * * Agreement between IBM and Sun. This technology is protected by multiple * * US and International patents. This notice and attribution to IBM may not * * to removed. * ******************************************************************************* */ package sun.text.normalizer; import java.text.ParsePosition; import java.util.Iterator; import java.util.TreeSet; /** * 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. * *
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 UCharacterProperty.isRuleWhiteSpace(), 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 * * http://www.icu-project.org/userguide/unicodeSet.html. * 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. * *
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 {@literal '&'} operator or the asymmetric * set difference may be taken using the '-' operator, for example, * "{@code [[:L:]&[\\u0000-\\u0FFF]]}" indicates the set of all Unicode letters * with values less than 4096. Operators ({@literal '&'} 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 * |
Warning: you cannot add an empty string ("") to a UnicodeSet.
* *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 :=
any character for which * *Character.digit(c, 16)
* returns a non-negative result* ** 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, use UnicodeSetIterator class.
*
* @author Alan Liu
* @stable ICU 2.0
* @see UnicodeSetIterator
*/
@SuppressWarnings("deprecation")
public class UnicodeSet implements UnicodeMatcher {
private static final int LOW = 0x000000; // LOW <= all valid values. ZERO for codepoints
private static final int HIGH = 0x110000; // HIGH > all valid values. 10000 for code units.
// 110000 for codepoints
/**
* Minimum value that can be stored in a UnicodeSet.
* @stable ICU 2.0
*/
public static final int MIN_VALUE = LOW;
/**
* Maximum value that can be stored in a UnicodeSet.
* @stable ICU 2.0
*/
public static final int MAX_VALUE = HIGH - 1;
private int len; // length used; list may be longer to minimize reallocs
private int[] list; // MUST be terminated with HIGH
private int[] rangeList; // internal buffer
private int[] buffer; // internal buffer
// NOTE: normally the field should be of type SortedSet; but that is missing a public clone!!
// is not private so that UnicodeSetIterator can get access
TreeSetother
.
* @param other a UnicodeSet
whose value will be
* copied to this object
* @stable ICU 2.0
*/
@SuppressWarnings("unchecked") // Casting result of clone of a collection
public UnicodeSet set(UnicodeSet other) {
list = other.list.clone();
len = other.len;
pat = other.pat;
strings = (TreeSet)other.strings.clone();
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.
* @stable ICU 2.0
*/
public final UnicodeSet applyPattern(String pattern) {
return applyPattern(pattern, null, null, IGNORE_SPACE);
}
/**
* Append the toPattern()
representation of a
* string to the given StringBuffer
.
*/
private static void _appendToPat(StringBuffer buf, String s, boolean escapeUnprintable) {
for (int i = 0; i < s.length(); i += UTF16.getCharCount(i)) {
_appendToPat(buf, UTF16.charAt(s, i), escapeUnprintable);
}
}
/**
* Append the toPattern()
representation of a
* character to the given StringBuffer
.
*/
private static void _appendToPat(StringBuffer buf, int c, boolean escapeUnprintable) {
if (escapeUnprintable && Utility.isUnprintable(c)) {
// Use hex escape notation (
Warning: you cannot add an empty string ("") to a UnicodeSet.
* @param s the source string
* @return this object, for chaining
* @stable ICU 2.0
*/
public final UnicodeSet add(String s) {
int cp = getSingleCP(s);
if (cp < 0) {
strings.add(s);
pat = null;
} else {
add_unchecked(cp, cp);
}
return this;
}
/**
* @return a code point IF the string consists of a single one.
* otherwise returns -1.
* @param string to test
*/
private static int getSingleCP(String s) {
if (s.length() < 1) {
throw new IllegalArgumentException("Can't use zero-length strings in UnicodeSet");
}
if (s.length() > 2) return -1;
if (s.length() == 1) return s.charAt(0);
// at this point, len = 2
int cp = UTF16.charAt(s, 0);
if (cp > 0xFFFF) { // is surrogate pair
return cp;
}
return -1;
}
/**
* 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 {@code end > start}
* then an empty range is complemented, 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.
* @stable ICU 2.0
*/
public UnicodeSet complement(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) {
xor(range(start, end), 2, 0);
}
pat = null;
return this;
}
/**
* This is equivalent to
* complement(MIN_VALUE, MAX_VALUE)
.
* @stable ICU 2.0
*/
public UnicodeSet complement() {
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;
}
/**
* 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
* @stable ICU 2.0
*/
public boolean contains(int c) {
if (c < MIN_VALUE || c > MAX_VALUE) {
throw new IllegalArgumentException("Invalid code point U+" + Utility.hex(c, 6));
}
/*
// 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;
}
}
}
/**
* Adds all of the elements in the specified set to this set if
* they're not already present. This operation effectively
* modifies this set so that its value is the union of the two
* sets. The behavior of this operation is unspecified if the specified
* collection is modified while the operation is in progress.
*
* @param c set whose elements are to be added to this set.
* @stable ICU 2.0
*/
public UnicodeSet addAll(UnicodeSet c) {
add(c.list, c.len, 0);
strings.addAll(c.strings);
return this;
}
/**
* Retains only the elements in this set that are contained in the
* specified set. In other words, removes from this set all of
* its elements that are not contained in the specified set. This
* operation effectively modifies this set so that its value is
* the intersection of the two sets.
*
* @param c set that defines which elements this set will retain.
* @stable ICU 2.0
*/
public UnicodeSet retainAll(UnicodeSet c) {
retain(c.list, c.len, 0);
strings.retainAll(c.strings);
return this;
}
/**
* Removes from this set all of its elements that are contained in the
* specified set. This operation effectively modifies this
* set so that its value is the asymmetric set difference of
* the two sets.
*
* @param c set that defines which elements will be removed from
* this set.
* @stable ICU 2.0
*/
public UnicodeSet removeAll(UnicodeSet c) {
retain(c.list, c.len, 2);
strings.removeAll(c.strings);
return this;
}
/**
* Removes all of the elements from this set. This set will be
* empty after this call returns.
* @stable ICU 2.0
*/
public UnicodeSet clear() {
list[0] = HIGH;
len = 1;
pat = null;
strings.clear();
return this;
}
/**
* Iteration method that returns the number of ranges contained in
* this set.
* @see #getRangeStart
* @see #getRangeEnd
* @stable ICU 2.0
*/
public int getRangeCount() {
return len/2;
}
/**
* Iteration method that returns the first character in the
* specified range of this set.
* @exception ArrayIndexOutOfBoundsException if index is outside
* the range 0..getRangeCount()-1
* @see #getRangeCount
* @see #getRangeEnd
* @stable ICU 2.0
*/
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
* @stable ICU 2.0
*/
public int getRangeEnd(int index) {
return (list[index*2 + 1] - 1);
}
//----------------------------------------------------------------
// 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.
*/
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);
}
StringBuffer rebuiltPat = new StringBuffer();
RuleCharacterIterator chars =
new RuleCharacterIterator(pattern, symbols, pos);
applyPattern(chars, symbols, rebuiltPat, options);
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 = Utility.skipWhitespace(pattern, i);
}
if (i != pattern.length()) {
throw new IllegalArgumentException("Parse of \"" + pattern +
"\" failed at " + i);
}
}
return this;
}
/**
* 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 of zero or more of the following:
* IGNORE_SPACE, CASE.
*/
void applyPattern(RuleCharacterIterator chars, SymbolTable symbols,
StringBuffer rebuiltPat, int options) {
// 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;
}
StringBuffer patBuf = new StringBuffer(), buf = null;
boolean usePat = false;
UnicodeSet scratch = null;
Object backup = null;
// mode: 0=before [, 1=between [...], 2=after ]
// lastItem: 0=none, 1=char, 2=set
int lastItem = 0, lastChar = 0, mode = 0;
char op = 0;
boolean invert = false;
clear();
while (mode != 2 && !chars.atEnd()) {
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 = 0;
if (resemblesPropertyPattern(chars, opts)) {
setMode = 2;
}
// -------- 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 == 1) {
chars.setPos(backup); // backup
setMode = 1;
} else {
// Handle opening '[' delimiter
mode = 1;
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 = 3;
} 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 != 0) {
if (lastItem == 1) {
if (op != 0) {
syntaxError(chars, "Char expected after operator");
}
add_unchecked(lastChar, lastChar);
_appendToPat(patBuf, lastChar, false);
lastItem = op = 0;
}
if (op == '-' || op == '&') {
patBuf.append(op);
}
if (nested == null) {
if (scratch == null) scratch = new UnicodeSet();
nested = scratch;
}
switch (setMode) {
case 1:
nested.applyPattern(chars, symbols, patBuf, options);
break;
case 2:
chars.skipIgnored(opts);
nested.applyPropertyPattern(chars, patBuf, symbols);
break;
case 3: // `nested' already parsed
nested._toPattern(patBuf, false);
break;
}
usePat = true;
if (mode == 0) {
// Entire pattern is a category; leave parse loop
set(nested);
mode = 2;
break;
}
switch (op) {
case '-':
removeAll(nested);
break;
case '&':
retainAll(nested);
break;
case 0:
addAll(nested);
break;
}
op = 0;
lastItem = 2;
continue;
}
if (mode == 0) {
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 == 1) {
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 = 2;
continue;
case '-':
if (op == 0) {
if (lastItem != 0) {
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 = 2;
continue;
}
}
}
syntaxError(chars, "'-' not after char or set");
break;
case '&':
if (lastItem == 2 && op == 0) {
op = (char) c;
continue;
}
syntaxError(chars, "'&' not after set");
break;
case '^':
syntaxError(chars, "'^' not after '['");
break;
case '{':
if (op != 0) {
syntaxError(chars, "Missing operand after operator");
}
if (lastItem == 1) {
add_unchecked(lastChar, lastChar);
_appendToPat(patBuf, lastChar, false);
}
lastItem = 0;
if (buf == null) {
buf = new StringBuffer();
} else {
buf.setLength(0);
}
boolean ok = false;
while (!chars.atEnd()) {
c = chars.next(opts);
literal = chars.isEscaped();
if (c == '}' && !literal) {
ok = true;
break;
}
UTF16.append(buf, c);
}
if (buf.length() < 1 || !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
add(buf.toString());
patBuf.append('{');
_appendToPat(patBuf, buf.toString(), 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 == 1) {
add_unchecked(lastChar, lastChar);
_appendToPat(patBuf, lastChar, false);
}
add_unchecked(UnicodeMatcher.ETHER);
usePat = true;
patBuf.append(SymbolTable.SYMBOL_REF).append(']');
mode = 2;
continue;
}
syntaxError(chars, "Unquoted '$'");
break;
default:
break;
}
}
// -------- Parse literal characters. This includes both
// escaped chars ("\u4E01") and non-syntax characters
// ("a").
switch (lastItem) {
case 0:
lastItem = 1;
lastChar = c;
break;
case 1:
if (op == '-') {
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 = op = 0;
} else {
add_unchecked(lastChar, lastChar);
_appendToPat(patBuf, lastChar, false);
lastChar = c;
}
break;
case 2:
if (op != 0) {
syntaxError(chars, "Set expected after operator");
}
lastChar = c;
lastItem = 1;
break;
}
}
if (mode != 2) {
syntaxError(chars, "Missing ']'");
}
chars.skipIgnored(opts);
if (invert) {
complement();
}
// Use the rebuilt pattern (pat) only if necessary. Prefer the
// generated pattern.
if (usePat) {
rebuiltPat.append(patBuf.toString());
} else {
_generatePattern(rebuiltPat, false, true);
}
}
private static void syntaxError(RuleCharacterIterator chars, String msg) {
throw new IllegalArgumentException("Error: " + msg + " at \"" +
Utility.escape(chars.toString()) +
'"');
}
//----------------------------------------------------------------
// Implementation: Utility methods
//----------------------------------------------------------------
private void ensureCapacity(int newLen) {
if (newLen <= list.length) return;
int[] temp = new int[newLen + GROW_EXTRA];
System.arraycopy(list, 0, temp, 0, len);
list = temp;
}
private void ensureBufferCapacity(int newLen) {
if (buffer != null && newLen <= buffer.length) return;
buffer = new int[newLen + GROW_EXTRA];
}
/**
* Assumes start <= end.
*/
private int[] range(int start, int end) {
if (rangeList == null) {
rangeList = new int[] { start, end+1, HIGH };
} else {
rangeList[0] = start;
rangeList[1] = end+1;
}
return rangeList;
}
//----------------------------------------------------------------
// Implementation: Fundamental operations
//----------------------------------------------------------------
// polarity = 0, 3 is normal: x xor y
// polarity = 1, 2: x xor ~y == x === y
private UnicodeSet xor(int[] other, int otherLen, int polarity) {
ensureBufferCapacity(len + otherLen);
int i = 0, j = 0, k = 0;
int a = list[i++];
int b;
if (polarity == 1 || polarity == 2) {
b = LOW;
if (other[j] == LOW) { // skip base if already LOW
++j;
b = other[j];
}
} else {
b = other[j++];
}
// simplest of all the routines
// sort the values, discarding identicals!
while (true) {
if (a < b) {
buffer[k++] = a;
a = list[i++];
} else if (b < a) {
buffer[k++] = b;
b = other[j++];
} else if (a != HIGH) { // at this point, a == b
// discard both values!
a = list[i++];
b = other[j++];
} else { // DONE!
buffer[k++] = HIGH;
len = k;
break;
}
}
// swap list and buffer
int[] temp = list;
list = buffer;
buffer = temp;
pat = null;
return this;
}
// polarity = 0 is normal: x union y
// polarity = 2: x union ~y
// polarity = 1: ~x union y
// polarity = 3: ~x union ~y
private UnicodeSet add(int[] other, int otherLen, int polarity) {
ensureBufferCapacity(len + otherLen);
int i = 0, j = 0, k = 0;
int a = list[i++];
int b = other[j++];
// change from xor is that we have to check overlapping pairs
// polarity bit 1 means a is second, bit 2 means b is.
main:
while (true) {
switch (polarity) {
case 0: // both first; take lower if unequal
if (a < b) { // take a
// Back up over overlapping ranges in buffer[]
if (k > 0 && a <= buffer[k-1]) {
// Pick latter end value in buffer[] vs. list[]
a = max(list[i], buffer[--k]);
} else {
// No overlap
buffer[k++] = a;
a = list[i];
}
i++; // Common if/else code factored out
polarity ^= 1;
} else if (b < a) { // take b
if (k > 0 && b <= buffer[k-1]) {
b = max(other[j], buffer[--k]);
} else {
buffer[k++] = b;
b = other[j];
}
j++;
polarity ^= 2;
} else { // a == b, take a, drop b
if (a == HIGH) break main;
// This is symmetrical; it doesn't matter if
// we backtrack with a or b. - liu
if (k > 0 && a <= buffer[k-1]) {
a = max(list[i], buffer[--k]);
} else {
// No overlap
buffer[k++] = a;
a = list[i];
}
i++;
polarity ^= 1;
b = other[j++]; polarity ^= 2;
}
break;
case 3: // both second; take higher if unequal, and drop other
if (b <= a) { // take a
if (a == HIGH) break main;
buffer[k++] = a;
} else { // take b
if (b == HIGH) break main;
buffer[k++] = b;
}
a = list[i++]; polarity ^= 1; // factored common code
b = other[j++]; polarity ^= 2;
break;
case 1: // a second, b first; if b < a, overlap
if (a < b) { // no overlap, take a
buffer[k++] = a; a = list[i++]; polarity ^= 1;
} else if (b < a) { // OVERLAP, drop b
b = other[j++]; polarity ^= 2;
} else { // a == b, drop both!
if (a == HIGH) break main;
a = list[i++]; polarity ^= 1;
b = other[j++]; polarity ^= 2;
}
break;
case 2: // a first, b second; if a < b, overlap
if (b < a) { // no overlap, take b
buffer[k++] = b; b = other[j++]; polarity ^= 2;
} else if (a < b) { // OVERLAP, drop a
a = list[i++]; polarity ^= 1;
} else { // a == b, drop both!
if (a == HIGH) break main;
a = list[i++]; polarity ^= 1;
b = other[j++]; polarity ^= 2;
}
break;
}
}
buffer[k++] = HIGH; // terminate
len = k;
// swap list and buffer
int[] temp = list;
list = buffer;
buffer = temp;
pat = null;
return this;
}
// polarity = 0 is normal: x intersect y
// polarity = 2: x intersect ~y == set-minus
// polarity = 1: ~x intersect y
// polarity = 3: ~x intersect ~y
private UnicodeSet retain(int[] other, int otherLen, int polarity) {
ensureBufferCapacity(len + otherLen);
int i = 0, j = 0, k = 0;
int a = list[i++];
int b = other[j++];
// change from xor is that we have to check overlapping pairs
// polarity bit 1 means a is second, bit 2 means b is.
main:
while (true) {
switch (polarity) {
case 0: // both first; drop the smaller
if (a < b) { // drop a
a = list[i++]; polarity ^= 1;
} else if (b < a) { // drop b
b = other[j++]; polarity ^= 2;
} else { // a == b, take one, drop other
if (a == HIGH) break main;
buffer[k++] = a; a = list[i++]; polarity ^= 1;
b = other[j++]; polarity ^= 2;
}
break;
case 3: // both second; take lower if unequal
if (a < b) { // take a
buffer[k++] = a; a = list[i++]; polarity ^= 1;
} else if (b < a) { // take b
buffer[k++] = b; b = other[j++]; polarity ^= 2;
} else { // a == b, take one, drop other
if (a == HIGH) break main;
buffer[k++] = a; a = list[i++]; polarity ^= 1;
b = other[j++]; polarity ^= 2;
}
break;
case 1: // a second, b first;
if (a < b) { // NO OVERLAP, drop a
a = list[i++]; polarity ^= 1;
} else if (b < a) { // OVERLAP, take b
buffer[k++] = b; b = other[j++]; polarity ^= 2;
} else { // a == b, drop both!
if (a == HIGH) break main;
a = list[i++]; polarity ^= 1;
b = other[j++]; polarity ^= 2;
}
break;
case 2: // a first, b second; if a < b, overlap
if (b < a) { // no overlap, drop b
b = other[j++]; polarity ^= 2;
} else if (a < b) { // OVERLAP, take a
buffer[k++] = a; a = list[i++]; polarity ^= 1;
} else { // a == b, drop both!
if (a == HIGH) break main;
a = list[i++]; polarity ^= 1;
b = other[j++]; polarity ^= 2;
}
break;
}
}
buffer[k++] = HIGH; // terminate
len = k;
// swap list and buffer
int[] temp = list;
list = buffer;
buffer = temp;
pat = null;
return this;
}
private static final int max(int a, int b) {
return (a > b) ? a : b;
}
//----------------------------------------------------------------
// Generic filter-based scanning code
//----------------------------------------------------------------
private static interface Filter {
boolean contains(int codePoint);
}
// VersionInfo for unassigned characters
static final VersionInfo NO_VERSION = VersionInfo.getInstance(0, 0, 0, 0);
private static class VersionFilter implements Filter {
VersionInfo version;
VersionFilter(VersionInfo version) { this.version = version; }
public boolean contains(int ch) {
VersionInfo v = UCharacter.getAge(ch);
// Reference comparison ok; VersionInfo caches and reuses
// unique objects.
return v != NO_VERSION &&
v.compareTo(version) <= 0;
}
}
private static synchronized UnicodeSet getInclusions(int src) {
if (INCLUSIONS == null) {
INCLUSIONS = new UnicodeSet[UCharacterProperty.SRC_COUNT];
}
if(INCLUSIONS[src] == null) {
UnicodeSet incl = new UnicodeSet();
switch(src) {
case UCharacterProperty.SRC_PROPSVEC:
UCharacterProperty.getInstance().upropsvec_addPropertyStarts(incl);
break;
default:
throw new IllegalStateException("UnicodeSet.getInclusions(unknown src "+src+")");
}
INCLUSIONS[src] = incl;
}
return INCLUSIONS[src];
}
/**
* Generic filter-based scanning code for UCD property UnicodeSets.
*/
private UnicodeSet applyFilter(Filter filter, int src) {
// Walk through all Unicode characters, noting the start
// and end of each range for which filter.contain(c) is
// true. Add each range to a set.
//
// To improve performance, use the INCLUSIONS set, which
// encodes information about character ranges that are known
// to have identical properties, such as the CJK Ideographs
// from U+4E00 to U+9FA5. INCLUSIONS contains all characters
// except the first characters of such ranges.
//
// TODO Where possible, instead of scanning over code points,
// use internal property data to initialize UnicodeSets for
// those properties. Scanning code points is slow.
clear();
int startHasProperty = -1;
UnicodeSet inclusions = getInclusions(src);
int limitRange = inclusions.getRangeCount();
for (int j=0; j