diff options
author | upstream source tree <ports@midipix.org> | 2015-03-15 20:14:05 -0400 |
---|---|---|
committer | upstream source tree <ports@midipix.org> | 2015-03-15 20:14:05 -0400 |
commit | 554fd8c5195424bdbcabf5de30fdc183aba391bd (patch) | |
tree | 976dc5ab7fddf506dadce60ae936f43f58787092 /libjava/classpath/gnu/java/util/regex/RE.java | |
download | cbb-gcc-4.6.4-554fd8c5195424bdbcabf5de30fdc183aba391bd.tar.bz2 cbb-gcc-4.6.4-554fd8c5195424bdbcabf5de30fdc183aba391bd.tar.xz |
obtained gcc-4.6.4.tar.bz2 from upstream website;upstream
verified gcc-4.6.4.tar.bz2.sig;
imported gcc-4.6.4 source tree from verified upstream tarball.
downloading a git-generated archive based on the 'upstream' tag
should provide you with a source tree that is binary identical
to the one extracted from the above tarball.
if you have obtained the source via the command 'git clone',
however, do note that line-endings of files in your working
directory might differ from line-endings of the respective
files in the upstream repository.
Diffstat (limited to 'libjava/classpath/gnu/java/util/regex/RE.java')
-rw-r--r-- | libjava/classpath/gnu/java/util/regex/RE.java | 2675 |
1 files changed, 2675 insertions, 0 deletions
diff --git a/libjava/classpath/gnu/java/util/regex/RE.java b/libjava/classpath/gnu/java/util/regex/RE.java new file mode 100644 index 000000000..5e9974a49 --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/RE.java @@ -0,0 +1,2675 @@ +/* gnu/regexp/RE.java + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.java.util.regex; + +import gnu.java.lang.CPStringBuilder; + +import java.io.InputStream; +import java.io.Serializable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.PropertyResourceBundle; +import java.util.ResourceBundle; + +/** + * RE provides the user interface for compiling and matching regular + * expressions. + * <P> + * A regular expression object (class RE) is compiled by constructing it + * from a String, StringBuffer or character array, with optional + * compilation flags (below) + * and an optional syntax specification (see RESyntax; if not specified, + * <code>RESyntax.RE_SYNTAX_PERL5</code> is used). + * <P> + * Once compiled, a regular expression object is reusable as well as + * threadsafe: multiple threads can use the RE instance simultaneously + * to match against different input text. + * <P> + * Various methods attempt to match input text against a compiled + * regular expression. These methods are: + * <LI><code>isMatch</code>: returns true if the input text in its + * entirety matches the regular expression pattern. + * <LI><code>getMatch</code>: returns the first match found in the + * input text, or null if no match is found. + * <LI><code>getAllMatches</code>: returns an array of all + * non-overlapping matches found in the input text. If no matches are + * found, the array is zero-length. + * <LI><code>substitute</code>: substitute the first occurence of the + * pattern in the input text with a replacement string (which may + * include metacharacters $0-$9, see REMatch.substituteInto). + * <LI><code>substituteAll</code>: same as above, but repeat for each + * match before returning. + * <LI><code>getMatchEnumeration</code>: returns an REMatchEnumeration + * object that allows iteration over the matches (see + * REMatchEnumeration for some reasons why you may want to do this + * instead of using <code>getAllMatches</code>. + * <P> + * + * These methods all have similar argument lists. The input can be a + * CharIndexed, String, a character array, a StringBuffer, or an + * InputStream of some sort. Note that when using an + * InputStream, the stream read position cannot be guaranteed after + * attempting a match (this is not a bug, but a consequence of the way + * regular expressions work). Using an REMatchEnumeration can + * eliminate most positioning problems. + * + * Although the input object can be of various types, it is recommended + * that it should be a CharIndexed because {@link CharIndexed#getLastMatch()} + * can show the last match found on this input, which helps the expression + * \G work as the end of the previous match. + * + * <P> + * + * The optional index argument specifies the offset from the beginning + * of the text at which the search should start (see the descriptions + * of some of the execution flags for how this can affect positional + * pattern operators). For an InputStream, this means an + * offset from the current read position, so subsequent calls with the + * same index argument on an InputStream will not + * necessarily access the same position on the stream, whereas + * repeated searches at a given index in a fixed string will return + * consistent results. + * + * <P> + * You can optionally affect the execution environment by using a + * combination of execution flags (constants listed below). + * + * <P> + * All operations on a regular expression are performed in a + * thread-safe manner. + * + * @author <A HREF="mailto:wes@cacas.org">Wes Biggs</A> + * @version 1.1.5-dev, to be released + */ + +public class RE extends REToken +{ + + private static final class IntPair implements Serializable + { + public int first, second; + } + + private static final class CharUnit implements Serializable + { + public char ch; + public boolean bk; + } + + // This String will be returned by getVersion() + private static final String VERSION = "1.1.5-dev"; + + // The localized strings are kept in a separate file + // Used by getLocalizedMessage(). + private static ResourceBundle messages; + + // Name of the bundle that contains the localized messages. + private static final String bundle = "gnu/java/util/regex/MessagesBundle"; + + // These are, respectively, the first and last tokens in our linked list + // If there is only one token, firstToken == lastToken + private REToken firstToken, lastToken; + + // This is the number of subexpressions in this regular expression, + // with a minimum value of zero. Returned by getNumSubs() + private int numSubs; + + /** Minimum length, in characters, of any possible match. */ + private int minimumLength; + private int maximumLength; + + /** + * Compilation flag. Do not differentiate case. Subsequent + * searches using this RE will be case insensitive. + */ + public static final int REG_ICASE = 0x02; + + /** + * Compilation flag. The match-any-character operator (dot) + * will match a newline character. When set this overrides the syntax + * bit RE_DOT_NEWLINE (see RESyntax for details). This is equivalent to + * the "/s" operator in Perl. + */ + public static final int REG_DOT_NEWLINE = 0x04; + + /** + * Compilation flag. Use multiline mode. In this mode, the ^ and $ + * anchors will match based on newlines within the input. This is + * equivalent to the "/m" operator in Perl. + */ + public static final int REG_MULTILINE = 0x08; + + /** + * Execution flag. + * The match-beginning operator (^) will not match at the beginning + * of the input string. Useful for matching on a substring when you + * know the context of the input is such that position zero of the + * input to the match test is not actually position zero of the text. + * <P> + * This example demonstrates the results of various ways of matching on + * a substring. + * <P> + * <CODE> + * String s = "food bar fool";<BR> + * RE exp = new RE("^foo.");<BR> + * REMatch m0 = exp.getMatch(s);<BR> + * REMatch m1 = exp.getMatch(s.substring(8));<BR> + * REMatch m2 = exp.getMatch(s.substring(8),0,RE.REG_NOTBOL); <BR> + * REMatch m3 = exp.getMatch(s,8); <BR> + * REMatch m4 = exp.getMatch(s,8,RE.REG_ANCHORINDEX); <BR> + * <P> + * // Results:<BR> + * // m0.toString(): "food"<BR> + * // m1.toString(): "fool"<BR> + * // m2.toString(): null<BR> + * // m3.toString(): null<BR> + * // m4.toString(): "fool"<BR> + * </CODE> + */ + public static final int REG_NOTBOL = 0x10; + + /** + * Execution flag. + * The match-end operator ($) does not match at the end + * of the input string. Useful for matching on substrings. + */ + public static final int REG_NOTEOL = 0x20; + + /** + * Execution flag. + * When a match method is invoked that starts matching at a non-zero + * index into the input, treat the input as if it begins at the index + * given. The effect of this flag is that the engine does not "see" + * any text in the input before the given index. This is useful so + * that the match-beginning operator (^) matches not at position 0 + * in the input string, but at the position the search started at + * (based on the index input given to the getMatch function). See + * the example under REG_NOTBOL. It also affects the use of the \< + * and \b operators. + */ + public static final int REG_ANCHORINDEX = 0x40; + + /** + * Execution flag. + * The substitute and substituteAll methods will not attempt to + * interpolate occurrences of $1-$9 in the replacement text with + * the corresponding subexpressions. For example, you may want to + * replace all matches of "one dollar" with "$1". + */ + public static final int REG_NO_INTERPOLATE = 0x80; + + /** + * Execution flag. + * Try to match the whole input string. An implicit match-end operator + * is added to this regexp. + */ + public static final int REG_TRY_ENTIRE_MATCH = 0x0100; + + /** + * Execution flag. + * The substitute and substituteAll methods will treat the + * character '\' in the replacement as an escape to a literal + * character. In this case "\n", "\$", "\\", "\x40" and "\012" + * will become "n", "$", "\", "x40" and "012" respectively. + * This flag has no effect if REG_NO_INTERPOLATE is set on. + */ + public static final int REG_REPLACE_USE_BACKSLASHESCAPE = 0x0200; + + /** + * Compilation flag. Allow whitespace and comments in pattern. + * This is equivalent to the "/x" operator in Perl. + */ + public static final int REG_X_COMMENTS = 0x0400; + + /** + * Compilation flag. If set, REG_ICASE is effective only for US-ASCII. + */ + public static final int REG_ICASE_USASCII = 0x0800; + + /** + * Execution flag. + * Do not move the position at which the search begins. If not set, + * the starting position will be moved until a match is found. + */ + public static final int REG_FIX_STARTING_POSITION = 0x1000; + + /** Returns a string representing the version of the gnu.regexp package. */ + public static final String version () + { + return VERSION; + } + + // Retrieves a message from the ResourceBundle + static final String getLocalizedMessage (String key) + { + if (messages == null) + messages = + PropertyResourceBundle.getBundle (bundle, Locale.getDefault ()); + return messages.getString (key); + } + + /** + * Constructs a regular expression pattern buffer without any compilation + * flags set, and using the default syntax (RESyntax.RE_SYNTAX_PERL5). + * + * @param pattern A regular expression pattern, in the form of a String, + * StringBuffer or char[]. Other input types will be converted to + * strings using the toString() method. + * @exception REException The input pattern could not be parsed. + * @exception NullPointerException The pattern was null. + */ + public RE (Object pattern) throws REException + { + this (pattern, 0, RESyntax.RE_SYNTAX_PERL5, 0, 0); + } + + /** + * Constructs a regular expression pattern buffer using the specified + * compilation flags and the default syntax (RESyntax.RE_SYNTAX_PERL5). + * + * @param pattern A regular expression pattern, in the form of a String, + * StringBuffer, or char[]. Other input types will be converted to + * strings using the toString() method. + * @param cflags The logical OR of any combination of the compilation flags listed above. + * @exception REException The input pattern could not be parsed. + * @exception NullPointerException The pattern was null. + */ + public RE (Object pattern, int cflags) throws REException + { + this (pattern, cflags, RESyntax.RE_SYNTAX_PERL5, 0, 0); + } + + /** + * Constructs a regular expression pattern buffer using the specified + * compilation flags and regular expression syntax. + * + * @param pattern A regular expression pattern, in the form of a String, + * StringBuffer, or char[]. Other input types will be converted to + * strings using the toString() method. + * @param cflags The logical OR of any combination of the compilation flags listed above. + * @param syntax The type of regular expression syntax to use. + * @exception REException The input pattern could not be parsed. + * @exception NullPointerException The pattern was null. + */ + public RE (Object pattern, int cflags, RESyntax syntax) throws REException + { + this (pattern, cflags, syntax, 0, 0); + } + + // internal constructor used for alternation + private RE (REToken first, REToken last, int subs, int subIndex, + int minLength, int maxLength) + { + super (subIndex); + firstToken = first; + lastToken = last; + numSubs = subs; + minimumLength = minLength; + maximumLength = maxLength; + addToken (new RETokenEndSub (subIndex)); + } + + private RE (Object patternObj, int cflags, RESyntax syntax, int myIndex, + int nextSub) throws REException + { + super (myIndex); // Subexpression index of this token. + initialize (patternObj, cflags, syntax, myIndex, nextSub); + } + + // For use by subclasses + protected RE () + { + super (0); + } + + // The meat of construction + protected void initialize (Object patternObj, int cflags, RESyntax syntax, + int myIndex, int nextSub) throws REException + { + char[] pattern; + if (patternObj instanceof String) + { + pattern = ((String) patternObj).toCharArray (); + } + else if (patternObj instanceof char[]) + { + pattern = (char[]) patternObj; + } + else if (patternObj instanceof StringBuffer) + { + pattern = new char[((StringBuffer) patternObj).length ()]; + ((StringBuffer) patternObj).getChars (0, pattern.length, pattern, 0); + } + else if (patternObj instanceof StringBuilder) + { + pattern = new char[((StringBuilder) patternObj).length ()]; + ((StringBuilder) patternObj).getChars (0, pattern.length, pattern, 0); + } + else if (patternObj instanceof CPStringBuilder) + { + pattern = new char[((CPStringBuilder) patternObj).length ()]; + ((CPStringBuilder) patternObj).getChars (0, pattern.length, pattern, + 0); + } + else + { + pattern = patternObj.toString ().toCharArray (); + } + + int pLength = pattern.length; + + numSubs = 0; // Number of subexpressions in this token. + ArrayList < REToken > branches = null; + + // linked list of tokens (sort of -- some closed loops can exist) + firstToken = lastToken = null; + + // Precalculate these so we don't pay for the math every time we + // need to access them. + boolean insens = ((cflags & REG_ICASE) > 0); + boolean insensUSASCII = ((cflags & REG_ICASE_USASCII) > 0); + + // Parse pattern into tokens. Does anyone know if it's more efficient + // to use char[] than a String.charAt()? I'm assuming so. + + // index tracks the position in the char array + int index = 0; + + // this will be the current parse character (pattern[index]) + CharUnit unit = new CharUnit (); + + // This is used for {x,y} calculations + IntPair minMax = new IntPair (); + + // Buffer a token so we can create a TokenRepeated, etc. + REToken currentToken = null; + boolean quot = false; + + // Saved syntax and flags. + RESyntax savedSyntax = null; + int savedCflags = 0; + boolean flagsSaved = false; + + while (index < pLength) + { + // read the next character unit (including backslash escapes) + index = getCharUnit (pattern, index, unit, quot); + + if (unit.bk) + if (unit.ch == 'Q') + { + quot = true; + continue; + } + else if (unit.ch == 'E') + { + quot = false; + continue; + } + if (quot) + unit.bk = false; + + if (((cflags & REG_X_COMMENTS) > 0) && (!unit.bk) && (!quot)) + { + if (Character.isWhitespace (unit.ch)) + { + continue; + } + if (unit.ch == '#') + { + for (int i = index; i < pLength; i++) + { + if (pattern[i] == '\n') + { + index = i + 1; + continue; + } + else if (pattern[i] == '\r') + { + if (i + 1 < pLength && pattern[i + 1] == '\n') + { + index = i + 2; + } + else + { + index = i + 1; + } + continue; + } + } + index = pLength; + continue; + } + } + + // ALTERNATION OPERATOR + // \| or | (if RE_NO_BK_VBAR) or newline (if RE_NEWLINE_ALT) + // not available if RE_LIMITED_OPS is set + + // TODO: the '\n' literal here should be a test against REToken.newline, + // which unfortunately may be more than a single character. + if (((unit.ch == '|' + && (syntax.get (RESyntax.RE_NO_BK_VBAR) ^ (unit.bk || quot))) + || (syntax.get (RESyntax.RE_NEWLINE_ALT) && (unit.ch == '\n') + && !(unit.bk || quot))) + && !syntax.get (RESyntax.RE_LIMITED_OPS)) + { + // make everything up to here be a branch. create vector if nec. + addToken (currentToken); + RE theBranch = + new RE (firstToken, lastToken, numSubs, subIndex, minimumLength, + maximumLength); + minimumLength = 0; + maximumLength = 0; + if (branches == null) + { + branches = new ArrayList < REToken > (); + } + branches.add (theBranch); + firstToken = lastToken = currentToken = null; + } + + // INTERVAL OPERATOR: + // {x} | {x,} | {x,y} (RE_INTERVALS && RE_NO_BK_BRACES) + // \{x\} | \{x,\} | \{x,y\} (RE_INTERVALS && !RE_NO_BK_BRACES) + // + // OPEN QUESTION: + // what is proper interpretation of '{' at start of string? + // + // This method used to check "repeat.empty.token" to avoid such regexp + // as "(a*){2,}", but now "repeat.empty.token" is allowed. + + else if ((unit.ch == '{') && syntax.get (RESyntax.RE_INTERVALS) + && (syntax. + get (RESyntax.RE_NO_BK_BRACES) ^ (unit.bk || quot))) + { + int newIndex = getMinMax (pattern, index, minMax, syntax); + if (newIndex > index) + { + if (minMax.first > minMax.second) + throw new + REException (getLocalizedMessage ("interval.order"), + REException.REG_BADRPT, newIndex); + if (currentToken == null) + throw new + REException (getLocalizedMessage ("repeat.no.token"), + REException.REG_BADRPT, newIndex); + if (currentToken instanceof RETokenRepeated) + throw new + REException (getLocalizedMessage ("repeat.chained"), + REException.REG_BADRPT, newIndex); + if (currentToken instanceof RETokenWordBoundary + || currentToken instanceof RETokenWordBoundary) + throw new + REException (getLocalizedMessage ("repeat.assertion"), + REException.REG_BADRPT, newIndex); + index = newIndex; + currentToken = + setRepeated (currentToken, minMax.first, minMax.second, + index); + } + else + { + addToken (currentToken); + currentToken = new RETokenChar (subIndex, unit.ch, insens); + if (insensUSASCII) + currentToken.unicodeAware = false; + } + } + + // LIST OPERATOR: + // [...] | [^...] + + else if ((unit.ch == '[') && !(unit.bk || quot)) + { + // Create a new RETokenOneOf + ParseCharClassResult result = + parseCharClass (subIndex, pattern, index, pLength, cflags, + syntax, 0); + addToken (currentToken); + currentToken = result.token; + index = result.index; + } + + // SUBEXPRESSIONS + // (...) | \(...\) depending on RE_NO_BK_PARENS + + else if ((unit.ch == '(') + && (syntax. + get (RESyntax.RE_NO_BK_PARENS) ^ (unit.bk || quot))) + { + boolean pure = false; + boolean comment = false; + boolean lookAhead = false; + boolean lookBehind = false; + boolean independent = false; + boolean negativelh = false; + boolean negativelb = false; + if ((index + 1 < pLength) && (pattern[index] == '?')) + { + switch (pattern[index + 1]) + { + case '!': + if (syntax.get (RESyntax.RE_LOOKAHEAD)) + { + pure = true; + negativelh = true; + lookAhead = true; + index += 2; + } + break; + case '=': + if (syntax.get (RESyntax.RE_LOOKAHEAD)) + { + pure = true; + lookAhead = true; + index += 2; + } + break; + case '<': + // We assume that if the syntax supports look-ahead, + // it also supports look-behind. + if (syntax.get (RESyntax.RE_LOOKAHEAD)) + { + index++; + switch (pattern[index + 1]) + { + case '!': + pure = true; + negativelb = true; + lookBehind = true; + index += 2; + break; + case '=': + pure = true; + lookBehind = true; + index += 2; + } + } + break; + case '>': + // We assume that if the syntax supports look-ahead, + // it also supports independent group. + if (syntax.get (RESyntax.RE_LOOKAHEAD)) + { + pure = true; + independent = true; + index += 2; + } + break; + case 'i': + case 'd': + case 'm': + case 's': + case 'u': + case 'x': + case '-': + if (!syntax.get (RESyntax.RE_EMBEDDED_FLAGS)) + break; + // Set or reset syntax flags. + int flagIndex = index + 1; + int endFlag = -1; + RESyntax newSyntax = new RESyntax (syntax); + int newCflags = cflags; + boolean negate = false; + while (flagIndex < pLength && endFlag < 0) + { + switch (pattern[flagIndex]) + { + case 'i': + if (negate) + newCflags &= ~REG_ICASE; + else + newCflags |= REG_ICASE; + flagIndex++; + break; + case 'd': + if (negate) + newSyntax.setLineSeparator (RESyntax. + DEFAULT_LINE_SEPARATOR); + else + newSyntax.setLineSeparator ("\n"); + flagIndex++; + break; + case 'm': + if (negate) + newCflags &= ~REG_MULTILINE; + else + newCflags |= REG_MULTILINE; + flagIndex++; + break; + case 's': + if (negate) + newCflags &= ~REG_DOT_NEWLINE; + else + newCflags |= REG_DOT_NEWLINE; + flagIndex++; + break; + case 'u': + if (negate) + newCflags |= REG_ICASE_USASCII; + else + newCflags &= ~REG_ICASE_USASCII; + flagIndex++; + break; + case 'x': + if (negate) + newCflags &= ~REG_X_COMMENTS; + else + newCflags |= REG_X_COMMENTS; + flagIndex++; + break; + case '-': + negate = true; + flagIndex++; + break; + case ':': + case ')': + endFlag = pattern[flagIndex]; + break; + default: + throw new + REException (getLocalizedMessage + ("repeat.no.token"), + REException.REG_BADRPT, index); + } + } + if (endFlag == ')') + { + syntax = newSyntax; + cflags = newCflags; + insens = ((cflags & REG_ICASE) > 0); + insensUSASCII = ((cflags & REG_ICASE_USASCII) > 0); + // This can be treated as though it were a comment. + comment = true; + index = flagIndex - 1; + break; + } + if (endFlag == ':') + { + savedSyntax = syntax; + savedCflags = cflags; + flagsSaved = true; + syntax = newSyntax; + cflags = newCflags; + insens = ((cflags & REG_ICASE) > 0); + insensUSASCII = ((cflags & REG_ICASE_USASCII) > 0); + index = flagIndex - 1; + // Fall through to the next case. + } + else + { + throw new + REException (getLocalizedMessage + ("unmatched.paren"), + REException.REG_ESUBREG, index); + } + case ':': + if (syntax.get (RESyntax.RE_PURE_GROUPING)) + { + pure = true; + index += 2; + } + break; + case '#': + if (syntax.get (RESyntax.RE_COMMENTS)) + { + comment = true; + } + break; + default: + throw new + REException (getLocalizedMessage ("repeat.no.token"), + REException.REG_BADRPT, index); + } + } + + if (index >= pLength) + { + throw new + REException (getLocalizedMessage ("unmatched.paren"), + REException.REG_ESUBREG, index); + } + + // find end of subexpression + int endIndex = index; + int nextIndex = index; + int nested = 0; + + while (((nextIndex = + getCharUnit (pattern, endIndex, unit, false)) > 0) + && !(nested == 0 && (unit.ch == ')') + && (syntax. + get (RESyntax.RE_NO_BK_PARENS) ^ (unit.bk + || quot)))) + { + if ((endIndex = nextIndex) >= pLength) + throw new + REException (getLocalizedMessage ("subexpr.no.end"), + REException.REG_ESUBREG, nextIndex); + else + if ((unit.ch == '[') && !(unit.bk || quot)) + { + // I hate to do something similar to the LIST OPERATOR matters + // above, but ... + int listIndex = nextIndex; + if (listIndex < pLength && pattern[listIndex] == '^') + listIndex++; + if (listIndex < pLength && pattern[listIndex] == ']') + listIndex++; + int listEndIndex = -1; + int listNest = 0; + while (listIndex < pLength && listEndIndex < 0) + { + switch (pattern[listIndex++]) + { + case '\\': + listIndex++; + break; + case '[': + // Sun's API document says that regexp like "[a-d[m-p]]" + // is legal. Even something like "[[[^]]]]" is accepted. + listNest++; + if (listIndex < pLength + && pattern[listIndex] == '^') + listIndex++; + if (listIndex < pLength + && pattern[listIndex] == ']') + listIndex++; + break; + case ']': + if (listNest == 0) + listEndIndex = listIndex; + listNest--; + break; + } + } + if (listEndIndex >= 0) + { + nextIndex = listEndIndex; + if ((endIndex = nextIndex) >= pLength) + throw new + REException (getLocalizedMessage ("subexpr.no.end"), + REException.REG_ESUBREG, nextIndex); + else + continue; + } + throw new + REException (getLocalizedMessage ("subexpr.no.end"), + REException.REG_ESUBREG, nextIndex); + } + else if (unit.ch == '(' + && (syntax. + get (RESyntax.RE_NO_BK_PARENS) ^ (unit.bk + || quot))) + nested++; + else if (unit.ch == ')' + && (syntax. + get (RESyntax.RE_NO_BK_PARENS) ^ (unit.bk + || quot))) + nested--; + } + + // endIndex is now position at a ')','\)' + // nextIndex is end of string or position after ')' or '\)' + + if (comment) + index = nextIndex; + else + { // not a comment + // create RE subexpression as token. + addToken (currentToken); + if (!pure) + { + numSubs++; + } + + int useIndex = (pure || lookAhead || lookBehind + || independent) ? 0 : nextSub + numSubs; + currentToken = + new RE (String.valueOf (pattern, index, endIndex - index). + toCharArray (), cflags, syntax, useIndex, + nextSub + numSubs); + numSubs += ((RE) currentToken).getNumSubs (); + + if (lookAhead) + { + currentToken = + new RETokenLookAhead (currentToken, negativelh); + } + else if (lookBehind) + { + currentToken = + new RETokenLookBehind (currentToken, negativelb); + } + else if (independent) + { + currentToken = new RETokenIndependent (currentToken); + } + + index = nextIndex; + if (flagsSaved) + { + syntax = savedSyntax; + cflags = savedCflags; + insens = ((cflags & REG_ICASE) > 0); + insensUSASCII = ((cflags & REG_ICASE_USASCII) > 0); + flagsSaved = false; + } + } // not a comment + } // subexpression + + // UNMATCHED RIGHT PAREN + // ) or \) throw exception if + // !syntax.get(RESyntax.RE_UNMATCHED_RIGHT_PAREN_ORD) + else if (!syntax.get (RESyntax.RE_UNMATCHED_RIGHT_PAREN_ORD) + && ((unit.ch == ')') + && (syntax. + get (RESyntax.RE_NO_BK_PARENS) ^ (unit.bk || quot)))) + { + throw new REException (getLocalizedMessage ("unmatched.paren"), + REException.REG_EPAREN, index); + } + + // START OF LINE OPERATOR + // ^ + + else if ((unit.ch == '^') && !(unit.bk || quot)) + { + addToken (currentToken); + currentToken = null; + RETokenStart token = null; + if ((cflags & REG_MULTILINE) > 0) + { + String sep = syntax.getLineSeparator (); + if (sep == null) + { + token = new RETokenStart (subIndex, null, true); + } + else + { + token = new RETokenStart (subIndex, sep); + } + } + else + { + token = new RETokenStart (subIndex, null); + } + addToken (token); + } + + // END OF LINE OPERATOR + // $ + + else if ((unit.ch == '$') && !(unit.bk || quot)) + { + addToken (currentToken); + currentToken = null; + RETokenEnd token = null; + if ((cflags & REG_MULTILINE) > 0) + { + String sep = syntax.getLineSeparator (); + if (sep == null) + { + token = new RETokenEnd (subIndex, null, true); + } + else + { + token = new RETokenEnd (subIndex, sep); + } + } + else + { + token = new RETokenEnd (subIndex, null); + } + addToken (token); + } + + // MATCH-ANY-CHARACTER OPERATOR (except possibly newline and null) + // . + + else if ((unit.ch == '.') && !(unit.bk || quot)) + { + addToken (currentToken); + currentToken = + new RETokenAny (subIndex, syntax.get (RESyntax.RE_DOT_NEWLINE) + || ((cflags & REG_DOT_NEWLINE) > 0), + syntax.get (RESyntax.RE_DOT_NOT_NULL)); + } + + // ZERO-OR-MORE REPEAT OPERATOR + // * + // + // This method used to check "repeat.empty.token" to avoid such regexp + // as "(a*)*", but now "repeat.empty.token" is allowed. + + else if ((unit.ch == '*') && !(unit.bk || quot)) + { + if (currentToken == null) + throw new REException (getLocalizedMessage ("repeat.no.token"), + REException.REG_BADRPT, index); + if (currentToken instanceof RETokenRepeated) + throw new REException (getLocalizedMessage ("repeat.chained"), + REException.REG_BADRPT, index); + if (currentToken instanceof RETokenWordBoundary + || currentToken instanceof RETokenWordBoundary) + throw new REException (getLocalizedMessage ("repeat.assertion"), + REException.REG_BADRPT, index); + currentToken = + setRepeated (currentToken, 0, Integer.MAX_VALUE, index); + } + + // ONE-OR-MORE REPEAT OPERATOR / POSSESSIVE MATCHING OPERATOR + // + | \+ depending on RE_BK_PLUS_QM + // not available if RE_LIMITED_OPS is set + // + // This method used to check "repeat.empty.token" to avoid such regexp + // as "(a*)+", but now "repeat.empty.token" is allowed. + + else if ((unit.ch == '+') && !syntax.get (RESyntax.RE_LIMITED_OPS) + && (!syntax. + get (RESyntax.RE_BK_PLUS_QM) ^ (unit.bk || quot))) + { + if (currentToken == null) + throw new REException (getLocalizedMessage ("repeat.no.token"), + REException.REG_BADRPT, index); + + // Check for possessive matching on RETokenRepeated + if (currentToken instanceof RETokenRepeated) + { + RETokenRepeated tokenRep = (RETokenRepeated) currentToken; + if (syntax.get (RESyntax.RE_POSSESSIVE_OPS) + && !tokenRep.isPossessive () && !tokenRep.isStingy ()) + tokenRep.makePossessive (); + else + throw new + REException (getLocalizedMessage ("repeat.chained"), + REException.REG_BADRPT, index); + + } + else if (currentToken instanceof RETokenWordBoundary + || currentToken instanceof RETokenWordBoundary) + throw new REException (getLocalizedMessage ("repeat.assertion"), + REException.REG_BADRPT, index); + else + currentToken = + setRepeated (currentToken, 1, Integer.MAX_VALUE, index); + } + + // ZERO-OR-ONE REPEAT OPERATOR / STINGY MATCHING OPERATOR + // ? | \? depending on RE_BK_PLUS_QM + // not available if RE_LIMITED_OPS is set + // stingy matching if RE_STINGY_OPS is set and it follows a quantifier + + else if ((unit.ch == '?') && !syntax.get (RESyntax.RE_LIMITED_OPS) + && (!syntax. + get (RESyntax.RE_BK_PLUS_QM) ^ (unit.bk || quot))) + { + if (currentToken == null) + throw new REException (getLocalizedMessage ("repeat.no.token"), + REException.REG_BADRPT, index); + + // Check for stingy matching on RETokenRepeated + if (currentToken instanceof RETokenRepeated) + { + RETokenRepeated tokenRep = (RETokenRepeated) currentToken; + if (syntax.get (RESyntax.RE_STINGY_OPS) + && !tokenRep.isStingy () && !tokenRep.isPossessive ()) + tokenRep.makeStingy (); + else + throw new + REException (getLocalizedMessage ("repeat.chained"), + REException.REG_BADRPT, index); + } + else if (currentToken instanceof RETokenWordBoundary + || currentToken instanceof RETokenWordBoundary) + throw new REException (getLocalizedMessage ("repeat.assertion"), + REException.REG_BADRPT, index); + else + currentToken = setRepeated (currentToken, 0, 1, index); + } + + // OCTAL CHARACTER + // \0377 + + else if (unit.bk && (unit.ch == '0') + && syntax.get (RESyntax.RE_OCTAL_CHAR)) + { + CharExpression ce = + getCharExpression (pattern, index - 2, pLength, syntax); + if (ce == null) + throw new REException ("invalid octal character", + REException.REG_ESCAPE, index); + index = index - 2 + ce.len; + addToken (currentToken); + currentToken = new RETokenChar (subIndex, ce.ch, insens); + if (insensUSASCII) + currentToken.unicodeAware = false; + } + + // BACKREFERENCE OPERATOR + // \1 \2 ... \9 and \10 \11 \12 ... + // not available if RE_NO_BK_REFS is set + // Perl recognizes \10, \11, and so on only if enough number of + // parentheses have opened before it, otherwise they are treated + // as aliases of \010, \011, ... (octal characters). In case of + // Sun's JDK, octal character expression must always begin with \0. + // We will do as JDK does. But FIXME, take a look at "(a)(b)\29". + // JDK treats \2 as a back reference to the 2nd group because + // there are only two groups. But in our poor implementation, + // we cannot help but treat \29 as a back reference to the 29th group. + + else if (unit.bk && Character.isDigit (unit.ch) + && !syntax.get (RESyntax.RE_NO_BK_REFS)) + { + addToken (currentToken); + int numBegin = index - 1; + int numEnd = pLength; + for (int i = index; i < pLength; i++) + { + if (!Character.isDigit (pattern[i])) + { + numEnd = i; + break; + } + } + int num = parseInt (pattern, numBegin, numEnd - numBegin, 10); + + currentToken = new RETokenBackRef (subIndex, num, insens); + if (insensUSASCII) + currentToken.unicodeAware = false; + index = numEnd; + } + + // START OF STRING OPERATOR + // \A if RE_STRING_ANCHORS is set + + else if (unit.bk && (unit.ch == 'A') + && syntax.get (RESyntax.RE_STRING_ANCHORS)) + { + addToken (currentToken); + currentToken = new RETokenStart (subIndex, null); + } + + // WORD BREAK OPERATOR + // \b if ???? + + else if (unit.bk && (unit.ch == 'b') + && syntax.get (RESyntax.RE_STRING_ANCHORS)) + { + addToken (currentToken); + currentToken = + new RETokenWordBoundary (subIndex, + RETokenWordBoundary. + BEGIN | RETokenWordBoundary.END, + false); + } + + // WORD BEGIN OPERATOR + // \< if ???? + else if (unit.bk && (unit.ch == '<')) + { + addToken (currentToken); + currentToken = + new RETokenWordBoundary (subIndex, RETokenWordBoundary.BEGIN, + false); + } + + // WORD END OPERATOR + // \> if ???? + else if (unit.bk && (unit.ch == '>')) + { + addToken (currentToken); + currentToken = + new RETokenWordBoundary (subIndex, RETokenWordBoundary.END, + false); + } + + // NON-WORD BREAK OPERATOR + // \B if ???? + + else if (unit.bk && (unit.ch == 'B') + && syntax.get (RESyntax.RE_STRING_ANCHORS)) + { + addToken (currentToken); + currentToken = + new RETokenWordBoundary (subIndex, + RETokenWordBoundary. + BEGIN | RETokenWordBoundary.END, true); + } + + + // DIGIT OPERATOR + // \d if RE_CHAR_CLASS_ESCAPES is set + + else if (unit.bk && (unit.ch == 'd') + && syntax.get (RESyntax.RE_CHAR_CLASS_ESCAPES)) + { + addToken (currentToken); + currentToken = + new RETokenPOSIX (subIndex, RETokenPOSIX.DIGIT, insens, false); + if (insensUSASCII) + currentToken.unicodeAware = false; + } + + // NON-DIGIT OPERATOR + // \D + + else if (unit.bk && (unit.ch == 'D') + && syntax.get (RESyntax.RE_CHAR_CLASS_ESCAPES)) + { + addToken (currentToken); + currentToken = + new RETokenPOSIX (subIndex, RETokenPOSIX.DIGIT, insens, true); + if (insensUSASCII) + currentToken.unicodeAware = false; + } + + // NEWLINE ESCAPE + // \n + + else if (unit.bk && (unit.ch == 'n')) + { + addToken (currentToken); + currentToken = new RETokenChar (subIndex, '\n', false); + } + + // RETURN ESCAPE + // \r + + else if (unit.bk && (unit.ch == 'r')) + { + addToken (currentToken); + currentToken = new RETokenChar (subIndex, '\r', false); + } + + // WHITESPACE OPERATOR + // \s if RE_CHAR_CLASS_ESCAPES is set + + else if (unit.bk && (unit.ch == 's') + && syntax.get (RESyntax.RE_CHAR_CLASS_ESCAPES)) + { + addToken (currentToken); + currentToken = + new RETokenPOSIX (subIndex, RETokenPOSIX.SPACE, insens, false); + if (insensUSASCII) + currentToken.unicodeAware = false; + } + + // NON-WHITESPACE OPERATOR + // \S + + else if (unit.bk && (unit.ch == 'S') + && syntax.get (RESyntax.RE_CHAR_CLASS_ESCAPES)) + { + addToken (currentToken); + currentToken = + new RETokenPOSIX (subIndex, RETokenPOSIX.SPACE, insens, true); + if (insensUSASCII) + currentToken.unicodeAware = false; + } + + // TAB ESCAPE + // \t + + else if (unit.bk && (unit.ch == 't')) + { + addToken (currentToken); + currentToken = new RETokenChar (subIndex, '\t', false); + } + + // ALPHANUMERIC OPERATOR + // \w + + else if (unit.bk && (unit.ch == 'w') + && syntax.get (RESyntax.RE_CHAR_CLASS_ESCAPES)) + { + addToken (currentToken); + currentToken = + new RETokenPOSIX (subIndex, RETokenPOSIX.ALNUM, insens, false); + if (insensUSASCII) + currentToken.unicodeAware = false; + } + + // NON-ALPHANUMERIC OPERATOR + // \W + + else if (unit.bk && (unit.ch == 'W') + && syntax.get (RESyntax.RE_CHAR_CLASS_ESCAPES)) + { + addToken (currentToken); + currentToken = + new RETokenPOSIX (subIndex, RETokenPOSIX.ALNUM, insens, true); + if (insensUSASCII) + currentToken.unicodeAware = false; + } + + // END OF STRING OPERATOR + // \Z, \z + + // FIXME: \Z and \z are different in that if the input string + // ends with a line terminator, \Z matches the position before + // the final terminator. This special behavior of \Z is yet + // to be implemented. + + else if (unit.bk && (unit.ch == 'Z' || unit.ch == 'z') && + syntax.get (RESyntax.RE_STRING_ANCHORS)) + { + addToken (currentToken); + currentToken = new RETokenEnd (subIndex, null); + } + + // HEX CHARACTER, UNICODE CHARACTER + // \x1B, \u1234 + + else + if ((unit.bk && (unit.ch == 'x') + && syntax.get (RESyntax.RE_HEX_CHAR)) || (unit.bk + && (unit.ch == 'u') + && syntax. + get (RESyntax. + RE_UNICODE_CHAR))) + { + CharExpression ce = + getCharExpression (pattern, index - 2, pLength, syntax); + if (ce == null) + throw new REException ("invalid hex character", + REException.REG_ESCAPE, index); + index = index - 2 + ce.len; + addToken (currentToken); + currentToken = new RETokenChar (subIndex, ce.ch, insens); + if (insensUSASCII) + currentToken.unicodeAware = false; + } + + // NAMED PROPERTY + // \p{prop}, \P{prop} + + else + if ((unit.bk && (unit.ch == 'p') + && syntax.get (RESyntax.RE_NAMED_PROPERTY)) || (unit.bk + && (unit.ch == + 'P') + && syntax. + get (RESyntax. + RE_NAMED_PROPERTY))) + { + NamedProperty np = getNamedProperty (pattern, index - 2, pLength); + if (np == null) + throw new REException ("invalid escape sequence", + REException.REG_ESCAPE, index); + index = index - 2 + np.len; + addToken (currentToken); + currentToken = + getRETokenNamedProperty (subIndex, np, insens, index); + if (insensUSASCII) + currentToken.unicodeAware = false; + } + + // END OF PREVIOUS MATCH + // \G + + else if (unit.bk && (unit.ch == 'G') && + syntax.get (RESyntax.RE_STRING_ANCHORS)) + { + addToken (currentToken); + currentToken = new RETokenEndOfPreviousMatch (subIndex); + } + + // NON-SPECIAL CHARACTER (or escape to make literal) + // c | \* for example + + else + { // not a special character + addToken (currentToken); + currentToken = new RETokenChar (subIndex, unit.ch, insens); + if (insensUSASCII) + currentToken.unicodeAware = false; + } + } // end while + + // Add final buffered token and an EndSub marker + addToken (currentToken); + + if (branches != null) + { + branches. + add (new + RE (firstToken, lastToken, numSubs, subIndex, minimumLength, + maximumLength)); + branches.trimToSize (); // compact the Vector + minimumLength = 0; + maximumLength = 0; + firstToken = lastToken = null; + addToken (new RETokenOneOf (subIndex, branches, false)); + } + else + addToken (new RETokenEndSub (subIndex)); + + } + + private static class ParseCharClassResult + { + RETokenOneOf token; + int index; + boolean returnAtAndOperator = false; + } + + /** + * Parse [...] or [^...] and make an RETokenOneOf instance. + * @param subIndex subIndex to be given to the created RETokenOneOf instance. + * @param pattern Input array of characters to be parsed. + * @param index Index pointing to the character next to the beginning '['. + * @param pLength Limit of the input array. + * @param cflags Compilation flags used to parse the pattern. + * @param pflags Flags that affect the behavior of this method. + * @param syntax Syntax used to parse the pattern. + */ + private static ParseCharClassResult parseCharClass (int subIndex, + char[]pattern, + int index, int pLength, + int cflags, + RESyntax syntax, + int pflags) throws + REException + { + + boolean insens = ((cflags & REG_ICASE) > 0); + boolean insensUSASCII = ((cflags & REG_ICASE_USASCII) > 0); + final ArrayList < REToken > options = new ArrayList < REToken > (); + ArrayList < Object > addition = new ArrayList < Object > (); + boolean additionAndAppeared = false; + final int RETURN_AT_AND = 0x01; + boolean returnAtAndOperator = ((pflags & RETURN_AT_AND) != 0); + boolean negative = false; + char ch; + + char lastChar = 0; + boolean lastCharIsSet = false; + if (index == pLength) + throw new REException (getLocalizedMessage ("unmatched.bracket"), + REException.REG_EBRACK, index); + + // Check for initial caret, negation + if ((ch = pattern[index]) == '^') + { + negative = true; + if (++index == pLength) + throw new REException (getLocalizedMessage ("class.no.end"), + REException.REG_EBRACK, index); + ch = pattern[index]; + } + + // Check for leading right bracket literal + if (ch == ']') + { + lastChar = ch; + lastCharIsSet = true; + if (++index == pLength) + throw new REException (getLocalizedMessage ("class.no.end"), + REException.REG_EBRACK, index); + } + + while ((ch = pattern[index++]) != ']') + { + if ((ch == '-') && (lastCharIsSet)) + { + if (index == pLength) + throw new REException (getLocalizedMessage ("class.no.end"), + REException.REG_EBRACK, index); + if ((ch = pattern[index]) == ']') + { + RETokenChar t = new RETokenChar (subIndex, lastChar, insens); + if (insensUSASCII) + t.unicodeAware = false; + options.add (t); + lastChar = '-'; + } + else + { + if ((ch == '\\') + && syntax.get (RESyntax.RE_BACKSLASH_ESCAPE_IN_LISTS)) + { + CharExpression ce = + getCharExpression (pattern, index, pLength, syntax); + if (ce == null) + throw new REException ("invalid escape sequence", + REException.REG_ESCAPE, index); + ch = ce.ch; + index = index + ce.len - 1; + } + RETokenRange t = + new RETokenRange (subIndex, lastChar, ch, insens); + if (insensUSASCII) + t.unicodeAware = false; + options.add (t); + lastChar = 0; + lastCharIsSet = false; + index++; + } + } + else if ((ch == '\\') + && syntax.get (RESyntax.RE_BACKSLASH_ESCAPE_IN_LISTS)) + { + if (index == pLength) + throw new REException (getLocalizedMessage ("class.no.end"), + REException.REG_EBRACK, index); + int posixID = -1; + boolean negate = false; + char asciiEsc = 0; + boolean asciiEscIsSet = false; + NamedProperty np = null; + if (("dswDSW".indexOf (pattern[index]) != -1) + && syntax.get (RESyntax.RE_CHAR_CLASS_ESC_IN_LISTS)) + { + switch (pattern[index]) + { + case 'D': + negate = true; + case 'd': + posixID = RETokenPOSIX.DIGIT; + break; + case 'S': + negate = true; + case 's': + posixID = RETokenPOSIX.SPACE; + break; + case 'W': + negate = true; + case 'w': + posixID = RETokenPOSIX.ALNUM; + break; + } + } + if (("pP".indexOf (pattern[index]) != -1) + && syntax.get (RESyntax.RE_NAMED_PROPERTY)) + { + np = getNamedProperty (pattern, index - 1, pLength); + if (np == null) + throw new REException ("invalid escape sequence", + REException.REG_ESCAPE, index); + index = index - 1 + np.len - 1; + } + else + { + CharExpression ce = + getCharExpression (pattern, index - 1, pLength, syntax); + if (ce == null) + throw new REException ("invalid escape sequence", + REException.REG_ESCAPE, index); + asciiEsc = ce.ch; + asciiEscIsSet = true; + index = index - 1 + ce.len - 1; + } + if (lastCharIsSet) + { + RETokenChar t = new RETokenChar (subIndex, lastChar, insens); + if (insensUSASCII) + t.unicodeAware = false; + options.add (t); + } + + if (posixID != -1) + { + RETokenPOSIX t = + new RETokenPOSIX (subIndex, posixID, insens, negate); + if (insensUSASCII) + t.unicodeAware = false; + options.add (t); + } + else if (np != null) + { + RETokenNamedProperty t = + getRETokenNamedProperty (subIndex, np, insens, index); + if (insensUSASCII) + t.unicodeAware = false; + options.add (t); + } + else if (asciiEscIsSet) + { + lastChar = asciiEsc; + lastCharIsSet = true; + } + else + { + lastChar = pattern[index]; + lastCharIsSet = true; + } + ++index; + } + else if ((ch == '[') && (syntax.get (RESyntax.RE_CHAR_CLASSES)) + && (index < pLength) && (pattern[index] == ':')) + { + CPStringBuilder posixSet = new CPStringBuilder (); + index = getPosixSet (pattern, index + 1, posixSet); + int posixId = RETokenPOSIX.intValue (posixSet.toString ()); + if (posixId != -1) + { + RETokenPOSIX t = + new RETokenPOSIX (subIndex, posixId, insens, false); + if (insensUSASCII) + t.unicodeAware = false; + options.add (t); + } + } + else if ((ch == '[') && (syntax.get (RESyntax.RE_NESTED_CHARCLASS))) + { + ParseCharClassResult result = + parseCharClass (subIndex, pattern, index, pLength, cflags, + syntax, 0); + addition.add (result.token); + addition.add ("|"); + index = result.index; + } + else if ((ch == '&') && + (syntax.get (RESyntax.RE_NESTED_CHARCLASS)) && + (index < pLength) && (pattern[index] == '&')) + { + if (returnAtAndOperator) + { + ParseCharClassResult result = new ParseCharClassResult (); + options.trimToSize (); + if (additionAndAppeared) + addition.add ("&"); + if (addition.size () == 0) + addition = null; + result.token = new RETokenOneOf (subIndex, + options, addition, negative); + result.index = index - 1; + result.returnAtAndOperator = true; + return result; + } + // The precedence of the operator "&&" is the lowest. + // So we postpone adding "&" until other elements + // are added. And we insert Boolean.FALSE at the + // beginning of the list of tokens following "&&". + // So, "&&[a-b][k-m]" will be stored in the Vecter + // addition in this order: + // Boolean.FALSE, [a-b], "|", [k-m], "|", "&" + if (additionAndAppeared) + addition.add ("&"); + addition.add (Boolean.FALSE); + additionAndAppeared = true; + + // The part on which "&&" operates may be either + // (1) explicitly enclosed by [] + // or + // (2) not enclosed by [] and terminated by the + // next "&&" or the end of the character list. + // Let the preceding else if block do the case (1). + // We must do something in case of (2). + if ((index + 1 < pLength) && (pattern[index + 1] != '[')) + { + ParseCharClassResult result = + parseCharClass (subIndex, pattern, index + 1, pLength, + cflags, syntax, + RETURN_AT_AND); + addition.add (result.token); + addition.add ("|"); + // If the method returned at the next "&&", it is OK. + // Otherwise we have eaten the mark of the end of this + // character list "]". In this case we must give back + // the end mark. + index = (result.returnAtAndOperator ? + result.index : result.index - 1); + } + } + else + { + if (lastCharIsSet) + { + RETokenChar t = new RETokenChar (subIndex, lastChar, insens); + if (insensUSASCII) + t.unicodeAware = false; + options.add (t); + } + lastChar = ch; + lastCharIsSet = true; + } + if (index == pLength) + throw new REException (getLocalizedMessage ("class.no.end"), + REException.REG_EBRACK, index); + } // while in list + // Out of list, index is one past ']' + + if (lastCharIsSet) + { + RETokenChar t = new RETokenChar (subIndex, lastChar, insens); + if (insensUSASCII) + t.unicodeAware = false; + options.add (t); + } + + ParseCharClassResult result = new ParseCharClassResult (); + // Create a new RETokenOneOf + options.trimToSize (); + if (additionAndAppeared) + addition.add ("&"); + if (addition.size () == 0) + addition = null; + result.token = new RETokenOneOf (subIndex, options, addition, negative); + result.index = index; + return result; + } + + private static int getCharUnit (char[]input, int index, CharUnit unit, + boolean quot) throws REException + { + unit.ch = input[index++]; + unit.bk = (unit.ch == '\\' + && (!quot || index >= input.length || input[index] == 'E')); + if (unit.bk) + if (index < input.length) + unit.ch = input[index++]; + else + throw new REException (getLocalizedMessage ("ends.with.backslash"), + REException.REG_ESCAPE, index); + return index; + } + + private static int parseInt (char[]input, int pos, int len, int radix) + { + int ret = 0; + for (int i = pos; i < pos + len; i++) + { + ret = ret * radix + Character.digit (input[i], radix); + } + return ret; + } + + /** + * This class represents various expressions for a character. + * "a" : 'a' itself. + * "\0123" : Octal char 0123 + * "\x1b" : Hex char 0x1b + * "\u1234" : Unicode char \u1234 + */ + private static class CharExpression + { + /** character represented by this expression */ + char ch; + /** String expression */ + String expr; + /** length of this expression */ + int len; + public String toString () + { + return expr; + } + } + + private static CharExpression getCharExpression (char[]input, int pos, + int lim, RESyntax syntax) + { + CharExpression ce = new CharExpression (); + char c = input[pos]; + if (c == '\\') + { + if (pos + 1 >= lim) + return null; + c = input[pos + 1]; + switch (c) + { + case 't': + ce.ch = '\t'; + ce.len = 2; + break; + case 'n': + ce.ch = '\n'; + ce.len = 2; + break; + case 'r': + ce.ch = '\r'; + ce.len = 2; + break; + case 'x': + case 'u': + if ((c == 'x' && syntax.get (RESyntax.RE_HEX_CHAR)) || + (c == 'u' && syntax.get (RESyntax.RE_UNICODE_CHAR))) + { + int l = 0; + int expectedLength = (c == 'x' ? 2 : 4); + for (int i = pos + 2; i < pos + 2 + expectedLength; i++) + { + if (i >= lim) + break; + if (!((input[i] >= '0' && input[i] <= '9') || + (input[i] >= 'A' && input[i] <= 'F') || + (input[i] >= 'a' && input[i] <= 'f'))) + break; + l++; + } + if (l != expectedLength) + return null; + ce.ch = (char) (parseInt (input, pos + 2, l, 16)); + ce.len = l + 2; + } + else + { + ce.ch = c; + ce.len = 2; + } + break; + case '0': + if (syntax.get (RESyntax.RE_OCTAL_CHAR)) + { + int l = 0; + for (int i = pos + 2; i < pos + 2 + 3; i++) + { + if (i >= lim) + break; + if (input[i] < '0' || input[i] > '7') + break; + l++; + } + if (l == 3 && input[pos + 2] > '3') + l--; + if (l <= 0) + return null; + ce.ch = (char) (parseInt (input, pos + 2, l, 8)); + ce.len = l + 2; + } + else + { + ce.ch = c; + ce.len = 2; + } + break; + default: + ce.ch = c; + ce.len = 2; + break; + } + } + else + { + ce.ch = input[pos]; + ce.len = 1; + } + ce.expr = new String (input, pos, ce.len); + return ce; + } + + /** + * This class represents a substring in a pattern string expressing + * a named property. + * "\pA" : Property named "A" + * "\p{prop}" : Property named "prop" + * "\PA" : Property named "A" (Negated) + * "\P{prop}" : Property named "prop" (Negated) + */ + private static class NamedProperty + { + /** Property name */ + String name; + /** Negated or not */ + boolean negate; + /** length of this expression */ + int len; + } + + private static NamedProperty getNamedProperty (char[]input, int pos, + int lim) + { + NamedProperty np = new NamedProperty (); + char c = input[pos]; + if (c == '\\') + { + if (++pos >= lim) + return null; + c = input[pos++]; + switch (c) + { + case 'p': + np.negate = false; + break; + case 'P': + np.negate = true; + break; + default: + return null; + } + c = input[pos++]; + if (c == '{') + { + int p = -1; + for (int i = pos; i < lim; i++) + { + if (input[i] == '}') + { + p = i; + break; + } + } + if (p < 0) + return null; + int len = p - pos; + np.name = new String (input, pos, len); + np.len = len + 4; + } + else + { + np.name = new String (input, pos - 1, 1); + np.len = 3; + } + return np; + } + else + return null; + } + + private static RETokenNamedProperty getRETokenNamedProperty (int subIndex, + NamedProperty + np, + boolean insens, + int index) + throws REException + { + try + { + return new RETokenNamedProperty (subIndex, np.name, insens, np.negate); + } + catch (REException e) + { + REException ree; + ree = new REException (e.getMessage (), REException.REG_ESCAPE, index); + ree.initCause (e); + throw ree; + } + } + + /** + * Checks if the regular expression matches the input in its entirety. + * + * @param input The input text. + */ + public boolean isMatch (Object input) + { + return isMatch (input, 0, 0); + } + + /** + * Checks if the input string, starting from index, is an exact match of + * this regular expression. + * + * @param input The input text. + * @param index The offset index at which the search should be begin. + */ + public boolean isMatch (Object input, int index) + { + return isMatch (input, index, 0); + } + + + /** + * Checks if the input, starting from index and using the specified + * execution flags, is an exact match of this regular expression. + * + * @param input The input text. + * @param index The offset index at which the search should be begin. + * @param eflags The logical OR of any execution flags above. + */ + public boolean isMatch (Object input, int index, int eflags) + { + return isMatchImpl (makeCharIndexed (input, index), index, eflags); + } + + private boolean isMatchImpl (CharIndexed input, int index, int eflags) + { + if (firstToken == null) // Trivial case + return (input.charAt (0) == CharIndexed.OUT_OF_BOUNDS); + REMatch m = new REMatch (numSubs, index, eflags); + if (firstToken.match (input, m)) + { + if (m != null) + { + if (input.charAt (m.index) == CharIndexed.OUT_OF_BOUNDS) + { + return true; + } + } + } + return false; + } + + /** + * Returns the maximum number of subexpressions in this regular expression. + * If the expression contains branches, the value returned will be the + * maximum subexpressions in any of the branches. + */ + public int getNumSubs () + { + return numSubs; + } + + // Overrides REToken.setUncle + void setUncle (REToken uncle) + { + if (lastToken != null) + { + lastToken.setUncle (uncle); + } + else + super.setUncle (uncle); // to deal with empty subexpressions + } + + // Overrides REToken.chain + + boolean chain (REToken next) + { + super.chain (next); + setUncle (next); + return true; + } + + /** + * Returns the minimum number of characters that could possibly + * constitute a match of this regular expression. + */ + public int getMinimumLength () + { + return minimumLength; + } + + public int getMaximumLength () + { + return maximumLength; + } + + /** + * Returns an array of all matches found in the input. + * + * If the regular expression allows the empty string to match, it will + * substitute matches at all positions except the end of the input. + * + * @param input The input text. + * @return a non-null (but possibly zero-length) array of matches + */ + public REMatch[] getAllMatches (Object input) + { + return getAllMatches (input, 0, 0); + } + + /** + * Returns an array of all matches found in the input, + * beginning at the specified index position. + * + * If the regular expression allows the empty string to match, it will + * substitute matches at all positions except the end of the input. + * + * @param input The input text. + * @param index The offset index at which the search should be begin. + * @return a non-null (but possibly zero-length) array of matches + */ + public REMatch[] getAllMatches (Object input, int index) + { + return getAllMatches (input, index, 0); + } + + /** + * Returns an array of all matches found in the input string, + * beginning at the specified index position and using the specified + * execution flags. + * + * If the regular expression allows the empty string to match, it will + * substitute matches at all positions except the end of the input. + * + * @param input The input text. + * @param index The offset index at which the search should be begin. + * @param eflags The logical OR of any execution flags above. + * @return a non-null (but possibly zero-length) array of matches + */ + public REMatch[] getAllMatches (Object input, int index, int eflags) + { + return getAllMatchesImpl (makeCharIndexed (input, index), index, eflags); + } + + // this has been changed since 1.03 to be non-overlapping matches + private REMatch[] getAllMatchesImpl (CharIndexed input, int index, + int eflags) + { + List < REMatch > all = new ArrayList < REMatch > (); + REMatch m = null; + while ((m = getMatchImpl (input, index, eflags, null)) != null) + { + all.add (m); + index = m.getEndIndex (); + if (m.end[0] == 0) + { // handle pathological case of zero-length match + index++; + input.move (1); + } + else + { + input.move (m.end[0]); + } + if (!input.isValid ()) + break; + } + return all.toArray (new REMatch[all.size ()]); + } + + /* Implements abstract method REToken.match() */ + boolean match (CharIndexed input, REMatch mymatch) + { + input.setHitEnd (mymatch); + if (firstToken == null) + { + return next (input, mymatch); + } + + // Note the start of this subexpression + mymatch.start1[subIndex] = mymatch.index; + + return firstToken.match (input, mymatch); + } + + REMatch findMatch (CharIndexed input, REMatch mymatch) + { + if (mymatch.backtrackStack == null) + mymatch.backtrackStack = new BacktrackStack (); + boolean b = match (input, mymatch); + if (b) + { + return mymatch; + } + return null; + } + + /** + * Returns the first match found in the input. If no match is found, + * null is returned. + * + * @param input The input text. + * @return An REMatch instance referencing the match, or null if none. + */ + public REMatch getMatch (Object input) + { + return getMatch (input, 0, 0); + } + + /** + * Returns the first match found in the input, beginning + * the search at the specified index. If no match is found, + * returns null. + * + * @param input The input text. + * @param index The offset within the text to begin looking for a match. + * @return An REMatch instance referencing the match, or null if none. + */ + public REMatch getMatch (Object input, int index) + { + return getMatch (input, index, 0); + } + + /** + * Returns the first match found in the input, beginning + * the search at the specified index, and using the specified + * execution flags. If no match is found, returns null. + * + * @param input The input text. + * @param index The offset index at which the search should be begin. + * @param eflags The logical OR of any execution flags above. + * @return An REMatch instance referencing the match, or null if none. + */ + public REMatch getMatch (Object input, int index, int eflags) + { + return getMatch (input, index, eflags, null); + } + + /** + * Returns the first match found in the input, beginning the search + * at the specified index, and using the specified execution flags. + * If no match is found, returns null. If a StringBuffer is + * provided and is non-null, the contents of the input text from the + * index to the beginning of the match (or to the end of the input, + * if there is no match) are appended to the StringBuffer. + * + * @param input The input text. + * @param index The offset index at which the search should be begin. + * @param eflags The logical OR of any execution flags above. + * @param buffer The StringBuffer to save pre-match text in. + * @return An REMatch instance referencing the match, or null if none. */ + public REMatch getMatch (Object input, int index, int eflags, + CPStringBuilder buffer) + { + return getMatchImpl (makeCharIndexed (input, index), index, eflags, + buffer); + } + + REMatch getMatchImpl (CharIndexed input, int anchor, int eflags, + CPStringBuilder buffer) + { + boolean tryEntireMatch = ((eflags & REG_TRY_ENTIRE_MATCH) != 0); + boolean doMove = ((eflags & REG_FIX_STARTING_POSITION) == 0); + RE re = (tryEntireMatch ? (RE) this.clone () : this); + if (tryEntireMatch) + { + RETokenEnd reEnd = new RETokenEnd (0, null); + reEnd.setFake (true); + re.chain (reEnd); + } + // Create a new REMatch to hold results + REMatch mymatch = new REMatch (numSubs, anchor, eflags); + do + { + /* The following potimization is commented out because + the matching should be tried even if the length of + input is obviously too short in order that + java.util.regex.Matcher#hitEnd() may work correctly. + // Optimization: check if anchor + minimumLength > length + if (minimumLength == 0 || input.charAt(minimumLength-1) != CharIndexed.OUT_OF_BOUNDS) { + */ + if (re.match (input, mymatch)) + { + REMatch best = mymatch; + // We assume that the match that coms first is the best. + // And the following "The longer, the better" rule has + // been commented out. The longest is not neccesarily + // the best. For example, "a" out of "aaa" is the best + // match for /a+?/. + /* + // Find best match of them all to observe leftmost longest + while ((mymatch = mymatch.next) != null) { + if (mymatch.index > best.index) { + best = mymatch; + } + } + */ + best.end[0] = best.index; + best.finish (input); + input.setLastMatch (best); + return best; + } + /* End of the optimization commented out + } + */ + mymatch.clear (++anchor); + // Append character to buffer if needed + if (buffer != null && input.charAt (0) != CharIndexed.OUT_OF_BOUNDS) + { + buffer.append (input.charAt (0)); + } + // java.util.regex.Matcher#hitEnd() requires that the search should + // be tried at the end of input, so we use move1(1) instead of move(1) + } + while (doMove && input.move1 (1)); + + // Special handling at end of input for e.g. "$" + if (minimumLength == 0) + { + if (match (input, mymatch)) + { + mymatch.finish (input); + return mymatch; + } + } + + return null; + } + + /** + * Returns an REMatchEnumeration that can be used to iterate over the + * matches found in the input text. + * + * @param input The input text. + * @return A non-null REMatchEnumeration instance. + */ + public REMatchEnumeration getMatchEnumeration (Object input) + { + return getMatchEnumeration (input, 0, 0); + } + + + /** + * Returns an REMatchEnumeration that can be used to iterate over the + * matches found in the input text. + * + * @param input The input text. + * @param index The offset index at which the search should be begin. + * @return A non-null REMatchEnumeration instance, with its input cursor + * set to the index position specified. + */ + public REMatchEnumeration getMatchEnumeration (Object input, int index) + { + return getMatchEnumeration (input, index, 0); + } + + /** + * Returns an REMatchEnumeration that can be used to iterate over the + * matches found in the input text. + * + * @param input The input text. + * @param index The offset index at which the search should be begin. + * @param eflags The logical OR of any execution flags above. + * @return A non-null REMatchEnumeration instance, with its input cursor + * set to the index position specified. + */ + public REMatchEnumeration getMatchEnumeration (Object input, int index, + int eflags) + { + return new REMatchEnumeration (this, makeCharIndexed (input, index), + index, eflags); + } + + + /** + * Substitutes the replacement text for the first match found in the input. + * + * @param input The input text. + * @param replace The replacement text, which may contain $x metacharacters (see REMatch.substituteInto). + * @return A String interpolating the substituted text. + * @see REMatch#substituteInto + */ + public String substitute (Object input, String replace) + { + return substitute (input, replace, 0, 0); + } + + /** + * Substitutes the replacement text for the first match found in the input + * beginning at the specified index position. Specifying an index + * effectively causes the regular expression engine to throw away the + * specified number of characters. + * + * @param input The input text. + * @param replace The replacement text, which may contain $x metacharacters (see REMatch.substituteInto). + * @param index The offset index at which the search should be begin. + * @return A String containing the substring of the input, starting + * at the index position, and interpolating the substituted text. + * @see REMatch#substituteInto + */ + public String substitute (Object input, String replace, int index) + { + return substitute (input, replace, index, 0); + } + + /** + * Substitutes the replacement text for the first match found in the input + * string, beginning at the specified index position and using the + * specified execution flags. + * + * @param input The input text. + * @param replace The replacement text, which may contain $x metacharacters (see REMatch.substituteInto). + * @param index The offset index at which the search should be begin. + * @param eflags The logical OR of any execution flags above. + * @return A String containing the substring of the input, starting + * at the index position, and interpolating the substituted text. + * @see REMatch#substituteInto + */ + public String substitute (Object input, String replace, int index, + int eflags) + { + return substituteImpl (makeCharIndexed (input, index), replace, index, + eflags); + } + + private String substituteImpl (CharIndexed input, String replace, int index, + int eflags) + { + CPStringBuilder buffer = new CPStringBuilder (); + REMatch m = getMatchImpl (input, index, eflags, buffer); + if (m == null) + return buffer.toString (); + buffer.append (getReplacement (replace, m, eflags)); + if (input.move (m.end[0])) + { + do + { + buffer.append (input.charAt (0)); + } + while (input.move (1)); + } + return buffer.toString (); + } + + /** + * Substitutes the replacement text for each non-overlapping match found + * in the input text. + * + * @param input The input text. + * @param replace The replacement text, which may contain $x metacharacters (see REMatch.substituteInto). + * @return A String interpolating the substituted text. + * @see REMatch#substituteInto + */ + public String substituteAll (Object input, String replace) + { + return substituteAll (input, replace, 0, 0); + } + + /** + * Substitutes the replacement text for each non-overlapping match found + * in the input text, starting at the specified index. + * + * If the regular expression allows the empty string to match, it will + * substitute matches at all positions except the end of the input. + * + * @param input The input text. + * @param replace The replacement text, which may contain $x metacharacters (see REMatch.substituteInto). + * @param index The offset index at which the search should be begin. + * @return A String containing the substring of the input, starting + * at the index position, and interpolating the substituted text. + * @see REMatch#substituteInto + */ + public String substituteAll (Object input, String replace, int index) + { + return substituteAll (input, replace, index, 0); + } + + /** + * Substitutes the replacement text for each non-overlapping match found + * in the input text, starting at the specified index and using the + * specified execution flags. + * + * @param input The input text. + * @param replace The replacement text, which may contain $x metacharacters (see REMatch.substituteInto). + * @param index The offset index at which the search should be begin. + * @param eflags The logical OR of any execution flags above. + * @return A String containing the substring of the input, starting + * at the index position, and interpolating the substituted text. + * @see REMatch#substituteInto + */ + public String substituteAll (Object input, String replace, int index, + int eflags) + { + return substituteAllImpl (makeCharIndexed (input, index), replace, index, + eflags); + } + + private String substituteAllImpl (CharIndexed input, String replace, + int index, int eflags) + { + CPStringBuilder buffer = new CPStringBuilder (); + REMatch m; + while ((m = getMatchImpl (input, index, eflags, buffer)) != null) + { + buffer.append (getReplacement (replace, m, eflags)); + index = m.getEndIndex (); + if (m.end[0] == 0) + { + char ch = input.charAt (0); + if (ch != CharIndexed.OUT_OF_BOUNDS) + buffer.append (ch); + input.move (1); + } + else + { + input.move (m.end[0]); + } + + if (!input.isValid ()) + break; + } + return buffer.toString (); + } + + public static String getReplacement (String replace, REMatch m, int eflags) + { + if ((eflags & REG_NO_INTERPOLATE) > 0) + return replace; + else + { + if ((eflags & REG_REPLACE_USE_BACKSLASHESCAPE) > 0) + { + CPStringBuilder sb = new CPStringBuilder (); + int l = replace.length (); + for (int i = 0; i < l; i++) + { + char c = replace.charAt (i); + switch (c) + { + case '\\': + i++; + // Let StringIndexOutOfBoundsException be thrown. + sb.append (replace.charAt (i)); + break; + case '$': + int i1 = i + 1; + while (i1 < replace.length () && + Character.isDigit (replace.charAt (i1))) + i1++; + sb.append (m.substituteInto (replace.substring (i, i1))); + i = i1 - 1; + break; + default: + sb.append (c); + } + } + return sb.toString (); + } + else + return m.substituteInto (replace); + } + } + + /* Helper function for constructor */ + private void addToken (REToken next) + { + if (next == null) + return; + minimumLength += next.getMinimumLength (); + int nmax = next.getMaximumLength (); + if (nmax < Integer.MAX_VALUE && maximumLength < Integer.MAX_VALUE) + maximumLength += nmax; + else + maximumLength = Integer.MAX_VALUE; + + if (firstToken == null) + { + lastToken = firstToken = next; + } + else + { + // if chain returns false, it "rejected" the token due to + // an optimization, and next was combined with lastToken + if (lastToken.chain (next)) + { + lastToken = next; + } + } + } + + private static REToken setRepeated (REToken current, int min, int max, + int index) throws REException + { + if (current == null) + throw new REException (getLocalizedMessage ("repeat.no.token"), + REException.REG_BADRPT, index); + return new RETokenRepeated (current.subIndex, current, min, max); + } + + private static int getPosixSet (char[]pattern, int index, + CPStringBuilder buf) + { + // Precondition: pattern[index-1] == ':' + // we will return pos of closing ']'. + int i; + for (i = index; i < (pattern.length - 1); i++) + { + if ((pattern[i] == ':') && (pattern[i + 1] == ']')) + return i + 2; + buf.append (pattern[i]); + } + return index; // didn't match up + } + + private int getMinMax (char[]input, int index, IntPair minMax, + RESyntax syntax) throws REException + { + // Precondition: input[index-1] == '{', minMax != null + + boolean mustMatch = !syntax.get (RESyntax.RE_NO_BK_BRACES); + int startIndex = index; + if (index == input.length) + { + if (mustMatch) + throw new REException (getLocalizedMessage ("unmatched.brace"), + REException.REG_EBRACE, index); + else + return startIndex; + } + + int min, max = 0; + CharUnit unit = new CharUnit (); + CPStringBuilder buf = new CPStringBuilder (); + + // Read string of digits + do + { + index = getCharUnit (input, index, unit, false); + if (Character.isDigit (unit.ch)) + buf.append (unit.ch); + } + while ((index != input.length) && Character.isDigit (unit.ch)); + + // Check for {} tomfoolery + if (buf.length () == 0) + { + if (mustMatch) + throw new REException (getLocalizedMessage ("interval.error"), + REException.REG_EBRACE, index); + else + return startIndex; + } + + min = Integer.parseInt (buf.toString ()); + + if ((unit.ch == '}') && (syntax.get (RESyntax.RE_NO_BK_BRACES) ^ unit.bk)) + max = min; + else if (index == input.length) + if (mustMatch) + throw new REException (getLocalizedMessage ("interval.no.end"), + REException.REG_EBRACE, index); + else + return startIndex; + else + if ((unit.ch == ',') && !unit.bk) + { + buf = new CPStringBuilder (); + // Read string of digits + while (((index = + getCharUnit (input, index, unit, false)) != input.length) + && Character.isDigit (unit.ch)) + buf.append (unit.ch); + + if (! + ((unit.ch == '}') + && (syntax.get (RESyntax.RE_NO_BK_BRACES) ^ unit.bk))) + if (mustMatch) + throw new REException (getLocalizedMessage ("interval.error"), + REException.REG_EBRACE, index); + else + return startIndex; + + // This is the case of {x,} + if (buf.length () == 0) + max = Integer.MAX_VALUE; + else + max = Integer.parseInt (buf.toString ()); + } + else if (mustMatch) + throw new REException (getLocalizedMessage ("interval.error"), + REException.REG_EBRACE, index); + else + return startIndex; + + // We know min and max now, and they are valid. + + minMax.first = min; + minMax.second = max; + + // return the index following the '}' + return index; + } + + /** + * Return a human readable form of the compiled regular expression, + * useful for debugging. + */ + public String toString () + { + CPStringBuilder sb = new CPStringBuilder (); + dump (sb); + return sb.toString (); + } + + void dump (CPStringBuilder os) + { + os.append ("(?#startRE subIndex=" + subIndex + ")"); + if (subIndex == 0) + os.append ("?:"); + if (firstToken != null) + firstToken.dumpAll (os); + if (subIndex == 0) + os.append (")"); + os.append ("(?#endRE subIndex=" + subIndex + ")"); + } + + // Cast input appropriately or throw exception + // This method was originally a private method, but has been made + // public because java.util.regex.Matcher uses this. + public static CharIndexed makeCharIndexed (Object input, int index) + { + // The case where input is already a CharIndexed is supposed + // be the most likely because this is the case with + // java.util.regex.Matcher. + // We could let a String or a CharSequence fall through + // to final input, but since it'a very likely input type, + // we check it first. + if (input instanceof CharIndexed) + { + CharIndexed ci = (CharIndexed) input; + ci.setAnchor (index); + return ci; + } + else if (input instanceof CharSequence) + return new CharIndexedCharSequence ((CharSequence) input, index); + else if (input instanceof String) + return new CharIndexedString ((String) input, index); + else if (input instanceof char[]) + return new CharIndexedCharArray ((char[]) input, index); + else if (input instanceof StringBuffer) + return new CharIndexedStringBuffer ((StringBuffer) input, index); + else if (input instanceof InputStream) + return new CharIndexedInputStream ((InputStream) input, index); + else + return new CharIndexedString (input.toString (), index); + } +} |