PageRenderTime 134ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/jEdit/tags/jedit-4-2-pre14/gnu/regexp/RE.java

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