PageRenderTime 163ms CodeModel.GetById 93ms app.highlight 56ms RepoModel.GetById 1ms app.codeStats 1ms

/jEdit/tags/jedit-4-1-pre5/gnu/regexp/RE.java

#
Java | 1356 lines | 682 code | 165 blank | 509 comment | 373 complexity | 9fbc614c6c42a6a0b950df0a0d8af87e MD5 | raw file
   1/*
   2 *  gnu/regexp/RE.java
   3 *  Copyright (C) 1998-2001 Wes Biggs
   4 *
   5 *  This library is free software; you can redistribute it and/or modify
   6 *  it under the terms of the GNU Lesser General Public License as published
   7 *  by the Free Software Foundation; either version 2.1 of the License, or
   8 *  (at your option) any later version.
   9 *
  10 *  This library is distributed in the hope that it will be useful,
  11 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13 *  GNU Lesser General Public License for more details.
  14 *
  15 *  You should have received a copy of the GNU Lesser General Public License
  16 *  along with this program; if not, write to the Free Software
  17 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  18 */
  19
  20package gnu.regexp;
  21import java.io.InputStream;
  22import java.io.Reader;
  23import java.io.Serializable;
  24import java.util.Locale;
  25import java.util.PropertyResourceBundle;
  26import java.util.ResourceBundle;
  27import java.util.Vector;
  28
  29class IntPair implements Serializable {
  30  public int first, second;
  31}
  32
  33class CharUnit implements Serializable {
  34  public char ch;
  35  public boolean bk;
  36}
  37
  38/**
  39 * RE provides the user interface for compiling and matching regular
  40 * expressions.
  41 * <P>
  42 * A regular expression object (class RE) is compiled by constructing it
  43 * from a String, StringBuffer or character array, with optional 
  44 * compilation flags (below)
  45 * and an optional syntax specification (see RESyntax; if not specified,
  46 * <code>RESyntax.RE_SYNTAX_PERL5</code> is used).
  47 * <P>
  48 * Once compiled, a regular expression object is reusable as well as
  49 * threadsafe: multiple threads can use the RE instance simultaneously
  50 * to match against different input text.
  51 * <P>
  52 * Various methods attempt to match input text against a compiled
  53 * regular expression.  These methods are:
  54 * <LI><code>isMatch</code>: returns true if the input text in its
  55 * entirety matches the regular expression pattern.
  56 * <LI><code>getMatch</code>: returns the first match found in the
  57 * input text, or null if no match is found.
  58 * <LI><code>getAllMatches</code>: returns an array of all
  59 * non-overlapping matches found in the input text.  If no matches are
  60 * found, the array is zero-length.
  61 * <LI><code>substitute</code>: substitute the first occurence of the
  62 * pattern in the input text with a replacement string (which may
  63 * include metacharacters $0-$9, see REMatch.substituteInto).
  64 * <LI><code>substituteAll</code>: same as above, but repeat for each
  65 * match before returning.
  66 * <LI><code>getMatchEnumeration</code>: returns an REMatchEnumeration
  67 * object that allows iteration over the matches (see
  68 * REMatchEnumeration for some reasons why you may want to do this
  69 * instead of using <code>getAllMatches</code>.
  70 * <P>
  71 *
  72 * These methods all have similar argument lists.  The input can be a
  73 * String, a character array, a StringBuffer, a Reader or an
  74 * InputStream of some sort.  Note that when using a Reader or
  75 * InputStream, the stream read position cannot be guaranteed after
  76 * attempting a match (this is not a bug, but a consequence of the way
  77 * regular expressions work).  Using an REMatchEnumeration can
  78 * eliminate most positioning problems.
  79 *
  80 * <P>
  81 *
  82 * The optional index argument specifies the offset from the beginning
  83 * of the text at which the search should start (see the descriptions
  84 * of some of the execution flags for how this can affect positional
  85 * pattern operators).  For a Reader or InputStream, this means an
  86 * offset from the current read position, so subsequent calls with the
  87 * same index argument on a Reader or an InputStream will not
  88 * necessarily access the same position on the stream, whereas
  89 * repeated searches at a given index in a fixed string will return
  90 * consistent results.
  91 *
  92 * <P>
  93 * You can optionally affect the execution environment by using a
  94 * combination of execution flags (constants listed below).
  95 * 
  96 * <P>
  97 * All operations on a regular expression are performed in a
  98 * thread-safe manner.
  99 *
 100 * @author <A HREF="mailto:wes@cacas.org">Wes Biggs</A>
 101 * @version 1.1.5-dev, to be released
 102 */
 103
 104public class RE extends REToken {
 105  // This String will be returned by getVersion()
 106  private static final String VERSION = "1.1.5-dev";
 107
 108  // The localized strings are kept in a separate file
 109  private static ResourceBundle messages = PropertyResourceBundle.getBundle("gnu/regexp/MessagesBundle", Locale.getDefault());
 110
 111  // These are, respectively, the first and last tokens in our linked list
 112  // If there is only one token, firstToken == lastToken
 113  private REToken firstToken, lastToken;
 114
 115  // This is the number of subexpressions in this regular expression,
 116  // with a minimum value of zero.  Returned by getNumSubs()
 117  private int numSubs;
 118
 119    /** Minimum length, in characters, of any possible match. */
 120    private int minimumLength;
 121
 122  /**
 123   * Compilation flag. Do  not  differentiate  case.   Subsequent
 124   * searches  using  this  RE will be case insensitive.
 125   */
 126  public static final int REG_ICASE = 2;
 127
 128  /**
 129   * Compilation flag. The match-any-character operator (dot)
 130   * will match a newline character.  When set this overrides the syntax
 131   * bit RE_DOT_NEWLINE (see RESyntax for details).  This is equivalent to
 132   * the "/s" operator in Perl.
 133   */
 134  public static final int REG_DOT_NEWLINE = 4;
 135
 136  /**
 137   * Compilation flag. Use multiline mode.  In this mode, the ^ and $
 138   * anchors will match based on newlines within the input. This is
 139   * equivalent to the "/m" operator in Perl.
 140   */
 141  public static final int REG_MULTILINE = 8;
 142
 143  /**
 144   * Execution flag.
 145   * The match-beginning operator (^) will not match at the beginning
 146   * of the input string. Useful for matching on a substring when you
 147   * know the context of the input is such that position zero of the
 148   * input to the match test is not actually position zero of the text.
 149   * <P>
 150   * This example demonstrates the results of various ways of matching on
 151   * a substring.
 152   * <P>
 153   * <CODE>
 154   * String s = "food bar fool";<BR>
 155   * RE exp = new RE("^foo.");<BR>
 156   * REMatch m0 = exp.getMatch(s);<BR>
 157   * REMatch m1 = exp.getMatch(s.substring(8));<BR>
 158   * REMatch m2 = exp.getMatch(s.substring(8),0,RE.REG_NOTBOL); <BR>
 159   * REMatch m3 = exp.getMatch(s,8);                            <BR>
 160   * REMatch m4 = exp.getMatch(s,8,RE.REG_ANCHORINDEX);         <BR>
 161   * <P>
 162   * // Results:<BR>
 163   * //  m0.toString(): "food"<BR>
 164   * //  m1.toString(): "fool"<BR>
 165   * //  m2.toString(): null<BR>
 166   * //  m3.toString(): null<BR>
 167   * //  m4.toString(): "fool"<BR>
 168   * </CODE>
 169   */
 170  public static final int REG_NOTBOL = 16;
 171
 172  /**
 173   * Execution flag.
 174   * The match-end operator ($) does not match at the end
 175   * of the input string. Useful for matching on substrings.
 176   */
 177  public static final int REG_NOTEOL = 32;
 178
 179  /**
 180   * Execution flag.
 181   * When a match method is invoked that starts matching at a non-zero
 182   * index into the input, treat the input as if it begins at the index
 183   * given.  The effect of this flag is that the engine does not "see"
 184   * any text in the input before the given index.  This is useful so
 185   * that the match-beginning operator (^) matches not at position 0
 186   * in the input string, but at the position the search started at
 187   * (based on the index input given to the getMatch function).  See
 188   * the example under REG_NOTBOL.  It also affects the use of the \&lt;
 189   * and \b operators.
 190   */
 191  public static final int REG_ANCHORINDEX = 64;
 192
 193  /**
 194   * Execution flag.
 195   * The substitute and substituteAll methods will not attempt to
 196   * interpolate occurrences of $1-$9 in the replacement text with
 197   * the corresponding subexpressions.  For example, you may want to
 198   * replace all matches of "one dollar" with "$1".
 199   */
 200  public static final int REG_NO_INTERPOLATE = 128;
 201
 202  /** Returns a string representing the version of the gnu.regexp package. */
 203  public static final String version() {
 204    return VERSION;
 205  }
 206
 207  // Retrieves a message from the ResourceBundle
 208  static final String getLocalizedMessage(String key) {
 209    return messages.getString(key);
 210  }
 211
 212  /**
 213   * Constructs a regular expression pattern buffer without any compilation
 214   * flags set, and using the default syntax (RESyntax.RE_SYNTAX_PERL5).
 215   *
 216   * @param pattern A regular expression pattern, in the form of a String,
 217   *   StringBuffer or char[].  Other input types will be converted to
 218   *   strings using the toString() method.
 219   * @exception REException The input pattern could not be parsed.
 220   * @exception NullPointerException The pattern was null.
 221   */
 222  public RE(Object pattern) throws REException {
 223    this(pattern,0,RESyntax.RE_SYNTAX_PERL5,0,0);
 224  }
 225
 226  /**
 227   * Constructs a regular expression pattern buffer using the specified
 228   * compilation flags and the default syntax (RESyntax.RE_SYNTAX_PERL5).
 229   *
 230   * @param pattern A regular expression pattern, in the form of a String,
 231   *   StringBuffer, or char[].  Other input types will be converted to
 232   *   strings using the toString() method.
 233   * @param cflags The logical OR of any combination of the compilation flags listed above.
 234   * @exception REException The input pattern could not be parsed.
 235   * @exception NullPointerException The pattern was null.
 236   */
 237  public RE(Object pattern, int cflags) throws REException {
 238    this(pattern,cflags,RESyntax.RE_SYNTAX_PERL5,0,0);
 239  }
 240
 241  /**
 242   * Constructs a regular expression pattern buffer using the specified
 243   * compilation flags and regular expression syntax.
 244   *
 245   * @param pattern A regular expression pattern, in the form of a String,
 246   *   StringBuffer, or char[].  Other input types will be converted to
 247   *   strings using the toString() method.
 248   * @param cflags The logical OR of any combination of the compilation flags listed above.
 249   * @param syntax The type of regular expression syntax to use.
 250   * @exception REException The input pattern could not be parsed.
 251   * @exception NullPointerException The pattern was null.
 252   */
 253  public RE(Object pattern, int cflags, RESyntax syntax) throws REException {
 254    this(pattern,cflags,syntax,0,0);
 255  }
 256
 257  // internal constructor used for alternation
 258  private RE(REToken first, REToken last,int subs, int subIndex, int minLength) {
 259    super(subIndex);
 260    firstToken = first;
 261    lastToken = last;
 262    numSubs = subs;
 263    minimumLength = minLength;
 264    addToken(new RETokenEndSub(subIndex));
 265  }
 266
 267  private RE(Object patternObj, int cflags, RESyntax syntax, int myIndex, int nextSub) throws REException {
 268    super(myIndex); // Subexpression index of this token.
 269    initialize(patternObj, cflags, syntax, myIndex, nextSub);
 270  }
 271
 272    // For use by subclasses
 273    protected RE() { super(0); }
 274
 275    // The meat of construction
 276  protected void initialize(Object patternObj, int cflags, RESyntax syntax, int myIndex, int nextSub) throws REException {
 277      char[] pattern;
 278    if (patternObj instanceof String) {
 279      pattern = ((String) patternObj).toCharArray();
 280    } else if (patternObj instanceof char[]) {
 281      pattern = (char[]) patternObj;
 282    } else if (patternObj instanceof StringBuffer) {
 283      pattern = new char [((StringBuffer) patternObj).length()];
 284      ((StringBuffer) patternObj).getChars(0,pattern.length,pattern,0);
 285    } else {
 286	pattern = patternObj.toString().toCharArray();
 287    }
 288
 289    int pLength = pattern.length;
 290
 291    numSubs = 0; // Number of subexpressions in this token.
 292    Vector branches = null;
 293
 294    // linked list of tokens (sort of -- some closed loops can exist)
 295    firstToken = lastToken = null;
 296
 297    // Precalculate these so we don't pay for the math every time we
 298    // need to access them.
 299    boolean insens = ((cflags & REG_ICASE) > 0);
 300
 301    // Parse pattern into tokens.  Does anyone know if it's more efficient
 302    // to use char[] than a String.charAt()?  I'm assuming so.
 303
 304    // index tracks the position in the char array
 305    int index = 0;
 306
 307    // this will be the current parse character (pattern[index])
 308    CharUnit unit = new CharUnit();
 309
 310    // This is used for {x,y} calculations
 311    IntPair minMax = new IntPair();
 312
 313    // Buffer a token so we can create a TokenRepeated, etc.
 314    REToken currentToken = null;
 315    char ch;
 316
 317    while (index < pLength) {
 318      // read the next character unit (including backslash escapes)
 319      index = getCharUnit(pattern,index,unit);
 320
 321      // ALTERNATION OPERATOR
 322      //  \| or | (if RE_NO_BK_VBAR) or newline (if RE_NEWLINE_ALT)
 323      //  not available if RE_LIMITED_OPS is set
 324
 325      // TODO: the '\n' literal here should be a test against REToken.newline,
 326      // which unfortunately may be more than a single character.
 327      if ( ( (unit.ch == '|' && (syntax.get(RESyntax.RE_NO_BK_VBAR) ^ unit.bk))
 328	     || (syntax.get(RESyntax.RE_NEWLINE_ALT) && (unit.ch == '\n') && !unit.bk) )
 329	   && !syntax.get(RESyntax.RE_LIMITED_OPS)) {
 330	// make everything up to here be a branch. create vector if nec.
 331	addToken(currentToken);
 332	RE theBranch = new RE(firstToken, lastToken, numSubs, subIndex, minimumLength);
 333	minimumLength = 0;
 334	if (branches == null) {
 335	    branches = new Vector();
 336	}
 337	branches.addElement(theBranch);
 338	firstToken = lastToken = currentToken = null;
 339      }
 340      
 341      // INTERVAL OPERATOR:
 342      //  {x} | {x,} | {x,y}  (RE_INTERVALS && RE_NO_BK_BRACES)
 343      //  \{x\} | \{x,\} | \{x,y\} (RE_INTERVALS && !RE_NO_BK_BRACES)
 344      //
 345      // OPEN QUESTION: 
 346      //  what is proper interpretation of '{' at start of string?
 347
 348      else if ((unit.ch == '{') && syntax.get(RESyntax.RE_INTERVALS) && (syntax.get(RESyntax.RE_NO_BK_BRACES) ^ unit.bk)) {
 349	int newIndex = getMinMax(pattern,index,minMax,syntax);
 350        if (newIndex > index) {
 351          if (minMax.first > minMax.second)
 352            throw new REException(getLocalizedMessage("interval.order"),REException.REG_BADRPT,newIndex);
 353          if (currentToken == null)
 354            throw new REException(getLocalizedMessage("repeat.no.token"),REException.REG_BADRPT,newIndex);
 355          if (currentToken instanceof RETokenRepeated) 
 356            throw new REException(getLocalizedMessage("repeat.chained"),REException.REG_BADRPT,newIndex);
 357          if (currentToken instanceof RETokenWordBoundary || currentToken instanceof RETokenWordBoundary)
 358            throw new REException(getLocalizedMessage("repeat.assertion"),REException.REG_BADRPT,newIndex);
 359          if ((currentToken.getMinimumLength() == 0) && (minMax.second == Integer.MAX_VALUE))
 360            throw new REException(getLocalizedMessage("repeat.empty.token"),REException.REG_BADRPT,newIndex);
 361          index = newIndex;
 362          currentToken = setRepeated(currentToken,minMax.first,minMax.second,index); 
 363        }
 364        else {
 365          addToken(currentToken);
 366          currentToken = new RETokenChar(subIndex,unit.ch,insens);
 367        } 
 368      }
 369      
 370      // LIST OPERATOR:
 371      //  [...] | [^...]
 372
 373      else if ((unit.ch == '[') && !unit.bk) {
 374	Vector options = new Vector();
 375	boolean negative = false;
 376	char lastChar = 0;
 377	if (index == pLength) throw new REException(getLocalizedMessage("unmatched.bracket"),REException.REG_EBRACK,index);
 378	
 379	// Check for initial caret, negation
 380	if ((ch = pattern[index]) == '^') {
 381	  negative = true;
 382	  if (++index == pLength) throw new REException(getLocalizedMessage("class.no.end"),REException.REG_EBRACK,index);
 383	  ch = pattern[index];
 384	}
 385
 386	// Check for leading right bracket literal
 387	if (ch == ']') {
 388	  lastChar = ch;
 389	  if (++index == pLength) throw new REException(getLocalizedMessage("class.no.end"),REException.REG_EBRACK,index);
 390	}
 391
 392	while ((ch = pattern[index++]) != ']') {
 393	  if ((ch == '-') && (lastChar != 0)) {
 394	    if (index == pLength) throw new REException(getLocalizedMessage("class.no.end"),REException.REG_EBRACK,index);
 395	    if ((ch = pattern[index]) == ']') {
 396	      options.addElement(new RETokenChar(subIndex,lastChar,insens));
 397	      lastChar = '-';
 398	    } else {
 399	      options.addElement(new RETokenRange(subIndex,lastChar,ch,insens));
 400	      lastChar = 0;
 401	      index++;
 402	    }
 403          } else if ((ch == '\\') && syntax.get(RESyntax.RE_BACKSLASH_ESCAPE_IN_LISTS)) {
 404            if (index == pLength) throw new REException(getLocalizedMessage("class.no.end"),REException.REG_EBRACK,index);
 405	    int posixID = -1;
 406	    boolean negate = false;
 407            char asciiEsc = 0;
 408	    if (("dswDSW".indexOf(pattern[index]) != -1) && syntax.get(RESyntax.RE_CHAR_CLASS_ESC_IN_LISTS)) {
 409	      switch (pattern[index]) {
 410	      case 'D':
 411		negate = true;
 412	      case 'd':
 413		posixID = RETokenPOSIX.DIGIT;
 414		break;
 415	      case 'S':
 416		negate = true;
 417	      case 's':
 418		posixID = RETokenPOSIX.SPACE;
 419		break;
 420	      case 'W':
 421		negate = true;
 422	      case 'w':
 423		posixID = RETokenPOSIX.ALNUM;
 424		break;
 425	      }
 426	    }
 427            else if ("nrt".indexOf(pattern[index]) != -1) {
 428              switch (pattern[index]) {
 429                case 'n':
 430                  asciiEsc = '\n';
 431                  break;
 432                case 't':
 433                  asciiEsc = '\t';
 434                  break;
 435                case 'r':
 436                  asciiEsc = '\r';
 437                  break;
 438              }
 439            }
 440	    if (lastChar != 0) options.addElement(new RETokenChar(subIndex,lastChar,insens));
 441	    
 442	    if (posixID != -1) {
 443	      options.addElement(new RETokenPOSIX(subIndex,posixID,insens,negate));
 444	    } else if (asciiEsc != 0) {
 445	      lastChar = asciiEsc;
 446	    } else {
 447	      lastChar = pattern[index];
 448	    }
 449	    ++index;
 450	  } else if ((ch == '[') && (syntax.get(RESyntax.RE_CHAR_CLASSES)) && (index < pLength) && (pattern[index] == ':')) {
 451	    StringBuffer posixSet = new StringBuffer();
 452	    index = getPosixSet(pattern,index+1,posixSet);
 453	    int posixId = RETokenPOSIX.intValue(posixSet.toString());
 454	    if (posixId != -1)
 455	      options.addElement(new RETokenPOSIX(subIndex,posixId,insens,false));
 456	  } else {
 457	    if (lastChar != 0) options.addElement(new RETokenChar(subIndex,lastChar,insens));
 458	    lastChar = ch;
 459	  }
 460	  if (index == pLength) throw new REException(getLocalizedMessage("class.no.end"),REException.REG_EBRACK,index);
 461	} // while in list
 462	// Out of list, index is one past ']'
 463	    
 464	if (lastChar != 0) options.addElement(new RETokenChar(subIndex,lastChar,insens));
 465	    
 466	// Create a new RETokenOneOf
 467	addToken(currentToken);
 468	options.trimToSize();
 469	currentToken = new RETokenOneOf(subIndex,options,negative);
 470      }
 471
 472      // SUBEXPRESSIONS
 473      //  (...) | \(...\) depending on RE_NO_BK_PARENS
 474
 475      else if ((unit.ch == '(') && (syntax.get(RESyntax.RE_NO_BK_PARENS) ^ unit.bk)) {
 476	boolean pure = false;
 477	boolean comment = false;
 478        boolean lookAhead = false;
 479        boolean negativelh = false;
 480	if ((index+1 < pLength) && (pattern[index] == '?')) {
 481	  switch (pattern[index+1]) {
 482          case '!':
 483            if (syntax.get(RESyntax.RE_LOOKAHEAD)) {
 484              pure = true;
 485              negativelh = true;
 486              lookAhead = true;
 487              index += 2;
 488            }
 489            break;
 490          case '=':
 491            if (syntax.get(RESyntax.RE_LOOKAHEAD)) {
 492              pure = true;
 493              lookAhead = true;
 494              index += 2;
 495            }
 496            break;
 497	  case ':':
 498	    if (syntax.get(RESyntax.RE_PURE_GROUPING)) {
 499	      pure = true;
 500	      index += 2;
 501	    }
 502	    break;
 503	  case '#':
 504	    if (syntax.get(RESyntax.RE_COMMENTS)) {
 505	      comment = true;
 506	    }
 507	    break;
 508          default:
 509            throw new REException(getLocalizedMessage("repeat.no.token"), REException.REG_BADRPT, index);
 510	  }
 511	}
 512
 513	if (index >= pLength) {
 514	    throw new REException(getLocalizedMessage("unmatched.paren"), REException.REG_ESUBREG,index);
 515	}
 516
 517	// find end of subexpression
 518	int endIndex = index;
 519	int nextIndex = index;
 520	int nested = 0;
 521
 522	while ( ((nextIndex = getCharUnit(pattern,endIndex,unit)) > 0)
 523		&& !(nested == 0 && (unit.ch == ')') && (syntax.get(RESyntax.RE_NO_BK_PARENS) ^ unit.bk)) )
 524	  if ((endIndex = nextIndex) >= pLength)
 525	    throw new REException(getLocalizedMessage("subexpr.no.end"),REException.REG_ESUBREG,nextIndex);
 526	  else if (unit.ch == '(' && (syntax.get(RESyntax.RE_NO_BK_PARENS) ^ unit.bk))
 527	    nested++;
 528	  else if (unit.ch == ')' && (syntax.get(RESyntax.RE_NO_BK_PARENS) ^ unit.bk))
 529	    nested--;
 530
 531	// endIndex is now position at a ')','\)' 
 532	// nextIndex is end of string or position after ')' or '\)'
 533
 534	if (comment) index = nextIndex;
 535	else { // not a comment
 536	  // create RE subexpression as token.
 537	  addToken(currentToken);
 538	  if (!pure) {
 539	    numSubs++;
 540	  }
 541
 542	  int useIndex = (pure || lookAhead) ? 0 : nextSub + numSubs;
 543	  currentToken = new RE(String.valueOf(pattern,index,endIndex-index).toCharArray(),cflags,syntax,useIndex,nextSub + numSubs);
 544	  numSubs += ((RE) currentToken).getNumSubs();
 545
 546          if (lookAhead) {
 547	      currentToken = new RETokenLookAhead(currentToken,negativelh);
 548	  }
 549
 550	  index = nextIndex;
 551	} // not a comment
 552      } // subexpression
 553    
 554      // UNMATCHED RIGHT PAREN
 555      // ) or \) throw exception if
 556      // !syntax.get(RESyntax.RE_UNMATCHED_RIGHT_PAREN_ORD)
 557      else if (!syntax.get(RESyntax.RE_UNMATCHED_RIGHT_PAREN_ORD) && ((unit.ch == ')') && (syntax.get(RESyntax.RE_NO_BK_PARENS) ^ unit.bk))) {
 558	throw new REException(getLocalizedMessage("unmatched.paren"),REException.REG_EPAREN,index);
 559      }
 560
 561      // START OF LINE OPERATOR
 562      //  ^
 563
 564      else if ((unit.ch == '^') && !unit.bk) {
 565	addToken(currentToken);
 566	currentToken = null;
 567	addToken(new RETokenStart(subIndex,((cflags & REG_MULTILINE) > 0) ? syntax.getLineSeparator() : null));
 568      }
 569
 570      // END OF LINE OPERATOR
 571      //  $
 572
 573      else if ((unit.ch == '$') && !unit.bk) {
 574	addToken(currentToken);
 575	currentToken = null;
 576	addToken(new RETokenEnd(subIndex,((cflags & REG_MULTILINE) > 0) ? syntax.getLineSeparator() : null));
 577      }
 578
 579      // MATCH-ANY-CHARACTER OPERATOR (except possibly newline and null)
 580      //  .
 581
 582      else if ((unit.ch == '.') && !unit.bk) {
 583	addToken(currentToken);
 584	currentToken = new RETokenAny(subIndex,syntax.get(RESyntax.RE_DOT_NEWLINE) || ((cflags & REG_DOT_NEWLINE) > 0),syntax.get(RESyntax.RE_DOT_NOT_NULL));
 585      }
 586
 587      // ZERO-OR-MORE REPEAT OPERATOR
 588      //  *
 589
 590      else if ((unit.ch == '*') && !unit.bk) {
 591	if (currentToken == null)
 592          throw new REException(getLocalizedMessage("repeat.no.token"),REException.REG_BADRPT,index);
 593	if (currentToken instanceof RETokenRepeated)
 594          throw new REException(getLocalizedMessage("repeat.chained"),REException.REG_BADRPT,index);
 595	if (currentToken instanceof RETokenWordBoundary || currentToken instanceof RETokenWordBoundary)
 596	  throw new REException(getLocalizedMessage("repeat.assertion"),REException.REG_BADRPT,index);
 597	if (currentToken.getMinimumLength() == 0)
 598	  throw new REException(getLocalizedMessage("repeat.empty.token"),REException.REG_BADRPT,index);
 599	currentToken = setRepeated(currentToken,0,Integer.MAX_VALUE,index);
 600      }
 601
 602      // ONE-OR-MORE REPEAT OPERATOR
 603      //  + | \+ depending on RE_BK_PLUS_QM
 604      //  not available if RE_LIMITED_OPS is set
 605
 606      else if ((unit.ch == '+') && !syntax.get(RESyntax.RE_LIMITED_OPS) && (!syntax.get(RESyntax.RE_BK_PLUS_QM) ^ unit.bk)) {
 607	if (currentToken == null)
 608          throw new REException(getLocalizedMessage("repeat.no.token"),REException.REG_BADRPT,index);
 609	if (currentToken instanceof RETokenRepeated)
 610          throw new REException(getLocalizedMessage("repeat.chained"),REException.REG_BADRPT,index);
 611	if (currentToken instanceof RETokenWordBoundary || currentToken instanceof RETokenWordBoundary)
 612	  throw new REException(getLocalizedMessage("repeat.assertion"),REException.REG_BADRPT,index);
 613	if (currentToken.getMinimumLength() == 0)
 614	  throw new REException(getLocalizedMessage("repeat.empty.token"),REException.REG_BADRPT,index);
 615	currentToken = setRepeated(currentToken,1,Integer.MAX_VALUE,index);
 616      }
 617
 618      // ZERO-OR-ONE REPEAT OPERATOR / STINGY MATCHING OPERATOR
 619      //  ? | \? depending on RE_BK_PLUS_QM
 620      //  not available if RE_LIMITED_OPS is set
 621      //  stingy matching if RE_STINGY_OPS is set and it follows a quantifier
 622
 623      else if ((unit.ch == '?') && !syntax.get(RESyntax.RE_LIMITED_OPS) && (!syntax.get(RESyntax.RE_BK_PLUS_QM) ^ unit.bk)) {
 624	if (currentToken == null) throw new REException(getLocalizedMessage("repeat.no.token"),REException.REG_BADRPT,index);
 625
 626	// Check for stingy matching on RETokenRepeated
 627	if (currentToken instanceof RETokenRepeated) {
 628          if (syntax.get(RESyntax.RE_STINGY_OPS) && !((RETokenRepeated)currentToken).isStingy())
 629            ((RETokenRepeated)currentToken).makeStingy();
 630          else
 631            throw new REException(getLocalizedMessage("repeat.chained"),REException.REG_BADRPT,index);
 632        }
 633        else if (currentToken instanceof RETokenWordBoundary || currentToken instanceof RETokenWordBoundary)
 634          throw new REException(getLocalizedMessage("repeat.assertion"),REException.REG_BADRPT,index);
 635	else
 636	  currentToken = setRepeated(currentToken,0,1,index);
 637      }
 638	
 639      // BACKREFERENCE OPERATOR
 640      //  \1 \2 ... \9
 641      // not available if RE_NO_BK_REFS is set
 642
 643      else if (unit.bk && Character.isDigit(unit.ch) && !syntax.get(RESyntax.RE_NO_BK_REFS)) {
 644	addToken(currentToken);
 645	currentToken = new RETokenBackRef(subIndex,Character.digit(unit.ch,10),insens);
 646      }
 647
 648      // START OF STRING OPERATOR
 649      //  \A if RE_STRING_ANCHORS is set
 650      
 651      else if (unit.bk && (unit.ch == 'A') && syntax.get(RESyntax.RE_STRING_ANCHORS)) {
 652	addToken(currentToken);
 653	currentToken = new RETokenStart(subIndex,null);
 654      }
 655
 656      // WORD BREAK OPERATOR
 657      //  \b if ????
 658
 659      else if (unit.bk && (unit.ch == 'b') && syntax.get(RESyntax.RE_STRING_ANCHORS)) {
 660	  addToken(currentToken);
 661	  currentToken = new RETokenWordBoundary(subIndex, RETokenWordBoundary.BEGIN | RETokenWordBoundary.END, false);
 662      } 
 663
 664      // WORD BEGIN OPERATOR 
 665      //  \< if ????
 666      else if (unit.bk && (unit.ch == '<')) {
 667	  addToken(currentToken);
 668	  currentToken = new RETokenWordBoundary(subIndex, RETokenWordBoundary.BEGIN, false);
 669      } 
 670
 671      // WORD END OPERATOR 
 672      //  \> if ????
 673      else if (unit.bk && (unit.ch == '>')) {
 674	  addToken(currentToken);
 675	  currentToken = new RETokenWordBoundary(subIndex, RETokenWordBoundary.END, false);
 676      } 
 677
 678      // NON-WORD BREAK OPERATOR
 679      // \B if ????
 680
 681      else if (unit.bk && (unit.ch == 'B') && syntax.get(RESyntax.RE_STRING_ANCHORS)) {
 682	  addToken(currentToken);
 683	  currentToken = new RETokenWordBoundary(subIndex, RETokenWordBoundary.BEGIN | RETokenWordBoundary.END, true);
 684      } 
 685
 686      
 687      // DIGIT OPERATOR
 688      //  \d if RE_CHAR_CLASS_ESCAPES is set
 689      
 690      else if (unit.bk && (unit.ch == 'd') && syntax.get(RESyntax.RE_CHAR_CLASS_ESCAPES)) {
 691	addToken(currentToken);
 692	currentToken = new RETokenPOSIX(subIndex,RETokenPOSIX.DIGIT,insens,false);
 693      }
 694
 695      // NON-DIGIT OPERATOR
 696      //  \D
 697
 698	else if (unit.bk && (unit.ch == 'D') && syntax.get(RESyntax.RE_CHAR_CLASS_ESCAPES)) {
 699	  addToken(currentToken);
 700	  currentToken = new RETokenPOSIX(subIndex,RETokenPOSIX.DIGIT,insens,true);
 701	}
 702
 703	// NEWLINE ESCAPE
 704        //  \n
 705
 706	else if (unit.bk && (unit.ch == 'n')) {
 707	  addToken(currentToken);
 708	  currentToken = new RETokenChar(subIndex,'\n',false);
 709	}
 710
 711	// RETURN ESCAPE
 712        //  \r
 713
 714	else if (unit.bk && (unit.ch == 'r')) {
 715	  addToken(currentToken);
 716	  currentToken = new RETokenChar(subIndex,'\r',false);
 717	}
 718
 719	// WHITESPACE OPERATOR
 720        //  \s if RE_CHAR_CLASS_ESCAPES is set
 721
 722	else if (unit.bk && (unit.ch == 's') && syntax.get(RESyntax.RE_CHAR_CLASS_ESCAPES)) {
 723	  addToken(currentToken);
 724	  currentToken = new RETokenPOSIX(subIndex,RETokenPOSIX.SPACE,insens,false);
 725	}
 726
 727	// NON-WHITESPACE OPERATOR
 728        //  \S
 729
 730	else if (unit.bk && (unit.ch == 'S') && syntax.get(RESyntax.RE_CHAR_CLASS_ESCAPES)) {
 731	  addToken(currentToken);
 732	  currentToken = new RETokenPOSIX(subIndex,RETokenPOSIX.SPACE,insens,true);
 733	}
 734
 735	// TAB ESCAPE
 736        //  \t
 737
 738	else if (unit.bk && (unit.ch == 't')) {
 739	  addToken(currentToken);
 740	  currentToken = new RETokenChar(subIndex,'\t',false);
 741	}
 742
 743	// ALPHANUMERIC OPERATOR
 744        //  \w
 745
 746	else if (unit.bk && (unit.ch == 'w') && syntax.get(RESyntax.RE_CHAR_CLASS_ESCAPES)) {
 747	  addToken(currentToken);
 748	  currentToken = new RETokenPOSIX(subIndex,RETokenPOSIX.ALNUM,insens,false);
 749	}
 750
 751	// NON-ALPHANUMERIC OPERATOR
 752        //  \W
 753
 754	else if (unit.bk && (unit.ch == 'W') && syntax.get(RESyntax.RE_CHAR_CLASS_ESCAPES)) {
 755	  addToken(currentToken);
 756	  currentToken = new RETokenPOSIX(subIndex,RETokenPOSIX.ALNUM,insens,true);
 757	}
 758
 759	// END OF STRING OPERATOR
 760        //  \Z
 761
 762	else if (unit.bk && (unit.ch == 'Z') && syntax.get(RESyntax.RE_STRING_ANCHORS)) {
 763	  addToken(currentToken);
 764	  currentToken = new RETokenEnd(subIndex,null);
 765	}
 766
 767	// NON-SPECIAL CHARACTER (or escape to make literal)
 768        //  c | \* for example
 769
 770	else {  // not a special character
 771	  addToken(currentToken);
 772	  currentToken = new RETokenChar(subIndex,unit.ch,insens);
 773	} 
 774      } // end while
 775
 776    // Add final buffered token and an EndSub marker
 777    addToken(currentToken);
 778      
 779    if (branches != null) {
 780	branches.addElement(new RE(firstToken,lastToken,numSubs,subIndex,minimumLength));
 781	branches.trimToSize(); // compact the Vector
 782	minimumLength = 0;
 783	firstToken = lastToken = null;
 784	addToken(new RETokenOneOf(subIndex,branches,false));
 785    } 
 786    else addToken(new RETokenEndSub(subIndex));
 787
 788  }
 789
 790  private static int getCharUnit(char[] input, int index, CharUnit unit) throws REException {
 791    unit.ch = input[index++];
 792    if (unit.bk = (unit.ch == '\\'))
 793      if (index < input.length)
 794	unit.ch = input[index++];
 795      else throw new REException(getLocalizedMessage("ends.with.backslash"),REException.REG_ESCAPE,index);
 796    return index;
 797  }
 798
 799  /**
 800   * Checks if the regular expression matches the input in its entirety.
 801   *
 802   * @param input The input text.
 803   */
 804  public boolean isMatch(Object input) {
 805    return isMatch(input,0,0);
 806  }
 807  
 808  /**
 809   * Checks if the input string, starting from index, is an exact match of
 810   * this regular expression.
 811   *
 812   * @param input The input text.
 813   * @param index The offset index at which the search should be begin.
 814   */
 815  public boolean isMatch(Object input,int index) {
 816    return isMatch(input,index,0);
 817  }
 818  
 819
 820  /**
 821   * Checks if the input, starting from index and using the specified
 822   * execution flags, is an exact match of this regular expression.
 823   *
 824   * @param input The input text.
 825   * @param index The offset index at which the search should be begin.
 826   * @param eflags The logical OR of any execution flags above.
 827   */
 828  public boolean isMatch(Object input,int index,int eflags) {
 829    return isMatchImpl(makeCharIndexed(input,index),index,eflags);
 830  }
 831
 832  private boolean isMatchImpl(CharIndexed input, int index, int eflags) {
 833    if (firstToken == null)  // Trivial case
 834      return (input.charAt(0) == CharIndexed.OUT_OF_BOUNDS);
 835    REMatch m = new REMatch(numSubs, index, eflags);
 836    if (firstToken.match(input, m)) {
 837	while (m != null) {
 838	    if (input.charAt(m.index) == CharIndexed.OUT_OF_BOUNDS) {
 839		return true;
 840	    }
 841	    m = m.next;
 842	}
 843    }
 844    return false;
 845  }
 846    
 847  /**
 848   * Returns the maximum number of subexpressions in this regular expression.
 849   * If the expression contains branches, the value returned will be the
 850   * maximum subexpressions in any of the branches.
 851   */
 852  public int getNumSubs() {
 853    return numSubs;
 854  }
 855
 856  // Overrides REToken.setUncle
 857  void setUncle(REToken uncle) {
 858      if (lastToken != null) {
 859	  lastToken.setUncle(uncle);
 860      } else super.setUncle(uncle); // to deal with empty subexpressions
 861  }
 862
 863  // Overrides REToken.chain
 864
 865  boolean chain(REToken next) {
 866    super.chain(next);
 867    setUncle(next);
 868    return true;
 869  }
 870
 871  /**
 872   * Returns the minimum number of characters that could possibly
 873   * constitute a match of this regular expression.
 874   */
 875  public int getMinimumLength() {
 876      return minimumLength;
 877  }
 878
 879  /**
 880   * Returns an array of all matches found in the input.
 881   *
 882   * If the regular expression allows the empty string to match, it will
 883   * substitute matches at all positions except the end of the input.
 884   *
 885   * @param input The input text.
 886   * @return a non-null (but possibly zero-length) array of matches
 887   */
 888  public REMatch[] getAllMatches(Object input) {
 889    return getAllMatches(input,0,0);
 890  }
 891
 892  /**
 893   * Returns an array of all matches found in the input,
 894   * beginning at the specified index position.
 895   *
 896   * If the regular expression allows the empty string to match, it will
 897   * substitute matches at all positions except the end of the input.
 898   *
 899   * @param input The input text.
 900   * @param index The offset index at which the search should be begin.
 901   * @return a non-null (but possibly zero-length) array of matches
 902   */
 903  public REMatch[] getAllMatches(Object input, int index) {
 904    return getAllMatches(input,index,0);
 905  }
 906
 907  /**
 908   * Returns an array of all matches found in the input string,
 909   * beginning at the specified index position and using the specified
 910   * execution flags.
 911   *
 912   * If the regular expression allows the empty string to match, it will
 913   * substitute matches at all positions except the end of the input.
 914   *
 915   * @param input The input text.
 916   * @param index The offset index at which the search should be begin.
 917   * @param eflags The logical OR of any execution flags above.
 918   * @return a non-null (but possibly zero-length) array of matches
 919   */
 920  public REMatch[] getAllMatches(Object input, int index, int eflags) {
 921    return getAllMatchesImpl(makeCharIndexed(input,index),index,eflags);
 922  }
 923
 924  // this has been changed since 1.03 to be non-overlapping matches
 925  private REMatch[] getAllMatchesImpl(CharIndexed input, int index, int eflags) {
 926    Vector all = new Vector();
 927    REMatch m = null;
 928    while ((m = getMatchImpl(input,index,eflags,null)) != null) {
 929      all.addElement(m);
 930      index = m.getEndIndex();
 931      if (m.end[0] == 0) {   // handle pathological case of zero-length match
 932	index++;
 933	input.move(1);
 934      } else {
 935	input.move(m.end[0]);
 936      }
 937      if (!input.isValid()) break;
 938    }
 939    REMatch[] mset = new REMatch[all.size()];
 940    all.copyInto(mset);
 941    return mset;
 942  }
 943  
 944    /* Implements abstract method REToken.match() */
 945    boolean match(CharIndexed input, REMatch mymatch) { 
 946	if (firstToken == null) return next(input, mymatch);
 947
 948	// Note the start of this subexpression
 949	mymatch.start[subIndex] = mymatch.index;
 950
 951	return firstToken.match(input, mymatch);
 952    }
 953  
 954  /**
 955   * Returns the first match found in the input.  If no match is found,
 956   * null is returned.
 957   *
 958   * @param input The input text.
 959   * @return An REMatch instance referencing the match, or null if none.
 960   */
 961  public REMatch getMatch(Object input) {
 962    return getMatch(input,0,0);
 963  }
 964  
 965  /**
 966   * Returns the first match found in the input, beginning
 967   * the search at the specified index.  If no match is found,
 968   * returns null.
 969   *
 970   * @param input The input text.
 971   * @param index The offset within the text to begin looking for a match.
 972   * @return An REMatch instance referencing the match, or null if none.
 973   */
 974  public REMatch getMatch(Object input, int index) {
 975    return getMatch(input,index,0);
 976  }
 977  
 978  /**
 979   * Returns the first match found in the input, beginning
 980   * the search at the specified index, and using the specified
 981   * execution flags.  If no match is found, returns null.
 982   *
 983   * @param input The input text.
 984   * @param index The offset index at which the search should be begin.
 985   * @param eflags The logical OR of any execution flags above.
 986   * @return An REMatch instance referencing the match, or null if none.
 987   */
 988  public REMatch getMatch(Object input, int index, int eflags) {
 989    return getMatch(input,index,eflags,null);
 990  }
 991
 992  /**
 993   * Returns the first match found in the input, beginning the search
 994   * at the specified index, and using the specified execution flags.
 995   * If no match is found, returns null.  If a StringBuffer is
 996   * provided and is non-null, the contents of the input text from the
 997   * index to the beginning of the match (or to the end of the input,
 998   * if there is no match) are appended to the StringBuffer.
 999   *
1000   * @param input The input text.
1001   * @param index The offset index at which the search should be begin.
1002   * @param eflags The logical OR of any execution flags above.
1003   * @param buffer The StringBuffer to save pre-match text in.
1004   * @return An REMatch instance referencing the match, or null if none.  */
1005  public REMatch getMatch(Object input, int index, int eflags, StringBuffer buffer) {
1006    return getMatchImpl(makeCharIndexed(input,index),index,eflags,buffer);
1007  }
1008
1009  REMatch getMatchImpl(CharIndexed input, int anchor, int eflags, StringBuffer buffer) {
1010      // Create a new REMatch to hold results
1011      REMatch mymatch = new REMatch(numSubs, anchor, eflags);
1012      do {
1013	  // Optimization: check if anchor + minimumLength > length
1014	  if (minimumLength == 0 || input.charAt(minimumLength-1) != CharIndexed.OUT_OF_BOUNDS) {
1015	      if (match(input, mymatch)) {
1016		  // Find longest match of them all to observe leftmost longest
1017		  REMatch longest = mymatch;
1018		  while ((mymatch = mymatch.next) != null) {
1019		      if (mymatch.index > longest.index) {
1020			  longest = mymatch;
1021		      }
1022		  }
1023		  
1024		  longest.end[0] = longest.index;
1025		  longest.finish(input);
1026		  return longest;
1027	      }
1028	  }
1029	  mymatch.clear(++anchor);
1030	  // Append character to buffer if needed
1031	  if (buffer != null && input.charAt(0) != CharIndexed.OUT_OF_BOUNDS) {
1032	      buffer.append(input.charAt(0));
1033	  }
1034      } while (input.move(1));
1035      
1036      // Special handling at end of input for e.g. "$"
1037      if (minimumLength == 0) {
1038	  if (match(input, mymatch)) {
1039	      mymatch.finish(input);
1040	      return mymatch;
1041	  }
1042      }
1043
1044      return null;
1045  }
1046
1047  /**
1048   * Returns an REMatchEnumeration that can be used to iterate over the
1049   * matches found in the input text.
1050   *
1051   * @param input The input text.
1052   * @return A non-null REMatchEnumeration instance.
1053   */
1054  public REMatchEnumeration getMatchEnumeration(Object input) {
1055    return getMatchEnumeration(input,0,0);
1056  }
1057
1058
1059  /**
1060   * Returns an REMatchEnumeration that can be used to iterate over the
1061   * matches found in the input text.
1062   *
1063   * @param input The input text.
1064   * @param index The offset index at which the search should be begin.
1065   * @return A non-null REMatchEnumeration instance, with its input cursor
1066   *  set to the index position specified.
1067   */
1068  public REMatchEnumeration getMatchEnumeration(Object input, int index) {
1069    return getMatchEnumeration(input,index,0);
1070  }
1071
1072  /**
1073   * Returns an REMatchEnumeration that can be used to iterate over the
1074   * matches found in the input text.
1075   *
1076   * @param input The input text.
1077   * @param index The offset index at which the search should be begin.
1078   * @param eflags The logical OR of any execution flags above.
1079   * @return A non-null REMatchEnumeration instance, with its input cursor
1080   *  set to the index position specified.
1081   */
1082  public REMatchEnumeration getMatchEnumeration(Object input, int index, int eflags) {
1083    return new REMatchEnumeration(this,makeCharIndexed(input,index),index,eflags);
1084  }
1085
1086
1087  /**
1088   * Substitutes the replacement text for the first match found in the input.
1089   *
1090   * @param input The input text.
1091   * @param replace The replacement text, which may contain $x metacharacters (see REMatch.substituteInto).
1092   * @return A String interpolating the substituted text.
1093   * @see REMatch#substituteInto
1094   */
1095  public String substitute(Object input,String replace) {
1096    return substitute(input,replace,0,0);
1097  }
1098
1099  /**
1100   * Substitutes the replacement text for the first match found in the input
1101   * beginning at the specified index position.  Specifying an index
1102   * effectively causes the regular expression engine to throw away the
1103   * specified number of characters. 
1104   *
1105   * @param input The input text.
1106   * @param replace The replacement text, which may contain $x metacharacters (see REMatch.substituteInto).
1107   * @param index The offset index at which the search should be begin.
1108   * @return A String containing the substring of the input, starting
1109   *   at the index position, and interpolating the substituted text.
1110   * @see REMatch#substituteInto
1111   */
1112  public String substitute(Object input,String replace,int index) {
1113    return substitute(input,replace,index,0);
1114  }
1115
1116  /**
1117   * Substitutes the replacement text for the first match found in the input
1118   * string, beginning at the specified index position and using the
1119   * specified execution flags.
1120   *
1121   * @param input The input text.
1122   * @param replace The replacement text, which may contain $x metacharacters (see REMatch.substituteInto).
1123   * @param index The offset index at which the search should be begin.
1124   * @param eflags The logical OR of any execution flags above.
1125   * @return A String containing the substring of the input, starting
1126   *   at the index position, and interpolating the substituted text.
1127   * @see REMatch#substituteInto
1128   */
1129  public String substitute(Object input,String replace,int index,int eflags) {
1130    return substituteImpl(makeCharIndexed(input,index),replace,index,eflags);
1131  }
1132
1133  private String substituteImpl(CharIndexed input,String replace,int index,int eflags) {
1134    StringBuffer buffer = new StringBuffer();
1135    REMatch m = getMatchImpl(input,index,eflags,buffer);
1136    if (m==null) return buffer.toString();
1137    buffer.append( ((eflags & REG_NO_INTERPOLATE) > 0) ?
1138		   replace : m.substituteInto(replace) );
1139    if (input.move(m.end[0])) {
1140      do {
1141	buffer.append(input.charAt(0));
1142      } while (input.move(1));
1143    }
1144    return buffer.toString();
1145  }
1146  
1147  /**
1148   * Substitutes the replacement text for each non-overlapping match found 
1149   * in the input text.
1150   *
1151   * @param input The input text.
1152   * @param replace The replacement text, which may contain $x metacharacters (see REMatch.substituteInto).
1153   * @return A String interpolating the substituted text.
1154   * @see REMatch#substituteInto
1155   */
1156  public String substituteAll(Object input,String replace) {
1157    return substituteAll(input,replace,0,0);
1158  }
1159
1160  /**
1161   * Substitutes the replacement text for each non-overlapping match found 
1162   * in the input text, starting at the specified index.
1163   *
1164   * If the regular expression allows the empty string to match, it will
1165   * substitute matches at all positions except the end of the input.
1166   *
1167   * @param input The input text.
1168   * @param replace The replacement text, which may contain $x metacharacters (see REMatch.substituteInto).
1169   * @param index The offset index at which the search should be begin.
1170   * @return A String containing the substring of the input, starting
1171   *   at the index position, and interpolating the substituted text.
1172   * @see REMatch#substituteInto
1173   */
1174  public String substituteAll(Object input,String replace,int index) {
1175    return substituteAll(input,replace,index,0);
1176  }
1177 
1178  /**
1179   * Substitutes the replacement text for each non-overlapping match found 
1180   * in the input text, starting at the specified index and using the
1181   * specified execution flags.
1182   *
1183   * @param input The input text.
1184   * @param replace The replacement text, which may contain $x metacharacters (see REMatch.substituteInto).
1185   * @param index The offset index at which the search should be begin.
1186   * @param eflags The logical OR of any execution flags above.
1187   * @return A String containing the substring of the input, starting
1188   *   at the index position, and interpolating the substituted text.
1189   * @see REMatch#substituteInto
1190   */
1191  public String substituteAll(Object input,String replace,int index,int eflags) {
1192    return substituteAllImpl(makeCharIndexed(input,index),replace,index,eflags);
1193  }
1194
1195  private String substituteAllImpl(CharIndexed input,String replace,int index,int eflags) {
1196    StringBuffer buffer = new StringBuffer();
1197    REMatch m;
1198    while ((m = getMatchImpl(input,index,eflags,buffer)) != null) {
1199	buffer.append( ((eflags & REG_NO_INTERPOLATE) > 0) ?
1200		       replace : m.substituteInto(replace) );
1201      index = m.getEndIndex();
1202      if (m.end[0] == 0) {
1203	char ch = input.charAt(0);
1204	if (ch != CharIndexed.OUT_OF_BOUNDS) 
1205	    buffer.append(ch);
1206	input.move(1);
1207      } else {
1208	  input.move(m.end[0]);
1209      }
1210
1211      if (!input.isValid()) break;
1212    }
1213    return buffer.toString();
1214  }
1215  
1216  /* Helper function for constructor */
1217  private void addToken(REToken next) {
1218    if (next == null) return;
1219    minimumLength += next.getMinimumLength();
1220    if (firstToken == null) {
1221	lastToken = firstToken = next;
1222    } else {
1223      // if chain returns false, it "rejected" the token due to
1224      // an optimization, and next was combined with lastToken
1225      if (lastToken.chain(next)) {
1226	  lastToken = next;
1227      }
1228    }
1229  }
1230
1231  private static REToken setRepeated(REToken current, int min, int max, int index) throws REException {
1232    if (current == null) throw new REException(getLocalizedMessage("repeat.no.token"),REException.REG_BADRPT,index);
1233    return new RETokenRepeated(current.subIndex,current,min,max);
1234  }
1235
1236  private static int getPosixSet(char[] pattern,int index,StringBuffer buf) {
1237    // Precondition: pattern[index-1] == ':'
1238    // we will return pos of closing ']'.
1239    int i;
1240    for (i=index; i<(pattern.length-1); i++) {
1241      if ((pattern[i] == ':') && (pattern[i+1] == ']'))
1242	return i+2;
1243      buf.append(pattern[i]);
1244    }
1245    return index; // didn't match up
1246  }
1247
1248  private int getMinMax(char[] input,int index,IntPair minMax,RESyntax syntax) throws REException {
1249    // Precondition: input[index-1] == '{', minMax != null
1250
1251    boolean mustMatch = !syntax.get(RESyntax.RE_NO_BK_BRACES);
1252    int startIndex = index;
1253    if (index == input.length) {
1254      if (mustMatch)
1255        throw new REException(getLocalizedMessage("unmatched.brace"),REException.REG_EBRACE,index);
1256      else
1257        return startIndex;
1258    }
1259    
1260    int min,max=0;
1261    CharUnit unit = new CharUnit();
1262    StringBuffer buf = new StringBuffer();
1263    
1264    // Read string of digits
1265    do {
1266      index = getCharUnit(input,index,unit);
1267      if (Character.isDigit(unit.ch))
1268        buf.append(unit.ch);
1269    } while ((index != input.length) && Character.isDigit(unit.ch));
1270
1271    // Check for {} tomfoolery
1272    if (buf.length() == 0) {
1273      if (mustMatch)
1274        throw new REException(getLocalizedMessage("interval.error"),REException.REG_EBRACE,index);
1275      else
1276        return startIndex;
1277    }
1278
1279    min = Integer.parseInt(buf.toString());
1280	
1281    if ((unit.ch == '}') && (syntax.get(RESyntax.RE_NO_BK_BRACES) ^ unit.bk))
1282      max = min;
1283    else if (index == input.length)
1284      if (mustMatch)
1285        throw new REException(getLocalizedMessage("interval.no.end"),REException.REG_EBRACE,index);
1286      else
1287        return startIndex;
1288    else if ((unit.ch == ',') && !unit.bk) {
1289      buf = new StringBuffer();
1290      // Read string of digits
1291      while (((index = getCharUnit(input,index,unit)) != input.length) && Character.isDigit(unit.ch))
1292	buf.append(unit.ch);
1293
1294      if (!((unit.ch == '}') && (syntax.get(RESyntax.RE_NO_BK_BRACES) ^ unit.bk)))
1295        if (mustMatch)
1296          throw new REException(getLocalizedMessage("interval.error"),REException.REG_EBRACE,index);
1297        else
1298          return startIndex;
1299
1300      // This is the case of {x,}
1301      if (buf.length() == 0) max = Integer.MAX_VALUE;
1302      else max = Integer.parseInt(buf.toString());
1303    } else
1304      if (mustMatch)
1305        throw new REException(getLocalizedMessage("interval.error"),REException.REG_EBRACE,index);
1306      else
1307        return startIndex;
1308
1309    // We know min and max now, and they are valid.
1310
1311    minMax.first = min;
1312    minMax.second = max;
1313
1314    // return the index following the '}'
1315    return index;
1316  }
1317
1318   /**
1319    * Return a human readable form of the compiled regular expression,
1320    * useful for debugging.
1321    */
1322   public String toString() {
1323     StringBuffer sb = new StringBuffer();
1324     dump(sb);
1325     return sb.toString();
1326   }
1327
1328  void dump(StringBuffer os) {
1329    os.append('(');
1330    if (subIndex == 0)
1331      os.append("?:");
1332    if (firstToken != null)
1333      firstToken.dumpAll(os);
1334    os.append(')');
1335  }
1336
1337  // Cast input appropriately or throw exception
1338  private static CharIndexed makeCharIndexed(Object input, int index) {
1339      // We could let a String fall through to final input, but since
1340      // it's the most likely input type, we check it first.
1341    if (input instanceof String)
1342      return new CharIndexedString((String) input,index);
1343    else if (input instanceof char[])
1344      return new CharIndexedCharArray((char[]) input,index);
1345    else if (input instanceof StringBuffer)
1346      return new CharIndexedStringBuffer((StringBuffer) input,index);
1347    else if (input instanceof InputStream)
1348      return new CharIndexedInputStream((InputStream) input,index);
1349    else if (input instanceof Reader)
1350	return new CharIndexedReader((Reader) input, index);
1351    else if (input instanceof CharIndexed)
1352	return (CharIndexed) input; // do we lose index info?
1353    else 
1354	return new CharIndexedString(input.toString(), index);
1355  }
1356}