001    /* Matcher.java -- Instance of a regular expression applied to a char sequence.
002       Copyright (C) 2002, 2004, 2006 Free Software Foundation, Inc.
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    
039    package java.util.regex;
040    
041    import gnu.java.util.regex.CharIndexed;
042    import gnu.java.util.regex.RE;
043    import gnu.java.util.regex.REMatch;
044    
045    /**
046     * Instance of a regular expression applied to a char sequence.
047     *
048     * @since 1.4
049     */
050    public final class Matcher implements MatchResult
051    {
052      private Pattern pattern;
053      private CharSequence input;
054      // We use CharIndexed as an input object to the getMatch method in order
055      // that /\G/ (the end of the previous match) may work.  The information
056      // of the previous match is stored in the CharIndexed object.
057      private CharIndexed inputCharIndexed;
058      private int position;
059      private int appendPosition;
060      private REMatch match;
061    
062      Matcher(Pattern pattern, CharSequence input)
063      {
064        this.pattern = pattern;
065        this.input = input;
066        this.inputCharIndexed = RE.makeCharIndexed(input, 0);
067      }
068      
069      /**
070       * @param sb The target string buffer
071       * @param replacement The replacement string
072       *
073       * @exception IllegalStateException If no match has yet been attempted,
074       * or if the previous match operation failed
075       * @exception IndexOutOfBoundsException If the replacement string refers
076       * to a capturing group that does not exist in the pattern
077       */
078      public Matcher appendReplacement (StringBuffer sb, String replacement)
079        throws IllegalStateException
080      {
081        assertMatchOp();
082        sb.append(input.subSequence(appendPosition,
083                                    match.getStartIndex()).toString());
084        sb.append(RE.getReplacement(replacement, match,
085            RE.REG_REPLACE_USE_BACKSLASHESCAPE));
086        appendPosition = match.getEndIndex();
087        return this;
088      }
089    
090      /**
091       * @param sb The target string buffer
092       */
093      public StringBuffer appendTail (StringBuffer sb)
094      {
095        sb.append(input.subSequence(appendPosition, input.length()).toString());
096        return sb;
097      }
098     
099      /**
100       * @exception IllegalStateException If no match has yet been attempted,
101       * or if the previous match operation failed
102       */
103      public int end ()
104        throws IllegalStateException
105      {
106        assertMatchOp();
107        return match.getEndIndex();
108      }
109      
110      /**
111       * @param group The index of a capturing group in this matcher's pattern
112       *
113       * @exception IllegalStateException If no match has yet been attempted,
114       * or if the previous match operation failed
115       * @exception IndexOutOfBoundsException If the replacement string refers
116       * to a capturing group that does not exist in the pattern
117       */
118      public int end (int group)
119        throws IllegalStateException
120      {
121        assertMatchOp();
122        return match.getEndIndex(group);
123      }
124     
125      public boolean find ()
126      {
127        boolean first = (match == null);
128        match = pattern.getRE().getMatch(inputCharIndexed, position);
129        if (match != null)
130          {
131            int endIndex = match.getEndIndex();
132            // Are we stuck at the same position?
133            if (!first && endIndex == position)
134              {
135                match = null;
136                // Not at the end of the input yet?
137                if (position < input.length() - 1)
138                  {
139                    position++;
140                    return find(position);
141                  }
142                else
143                  return false;
144              }
145            position = endIndex;
146            return true;
147          }
148        return false;
149      } 
150    
151      /**
152       * @param start The index to start the new pattern matching
153       *
154       * @exception IndexOutOfBoundsException If the replacement string refers
155       * to a capturing group that does not exist in the pattern
156       */
157      public boolean find (int start)
158      {
159        match = pattern.getRE().getMatch(inputCharIndexed, start);
160        if (match != null)
161          {
162            position = match.getEndIndex();
163            return true;
164          }
165        return false;
166      }
167     
168      /**
169       * @exception IllegalStateException If no match has yet been attempted,
170       * or if the previous match operation failed
171       */
172      public String group ()
173      {
174        assertMatchOp();
175        return match.toString();
176      }
177      
178      /**
179       * @param group The index of a capturing group in this matcher's pattern
180       *
181       * @exception IllegalStateException If no match has yet been attempted,
182       * or if the previous match operation failed
183       * @exception IndexOutOfBoundsException If the replacement string refers
184       * to a capturing group that does not exist in the pattern
185       */
186      public String group (int group)
187        throws IllegalStateException
188      {
189        assertMatchOp();
190        return match.toString(group);
191      }
192    
193      /**
194       * @param replacement The replacement string
195       */
196      public String replaceFirst (String replacement)
197      {
198        reset();
199        // Semantics might not quite match
200        return pattern.getRE().substitute(input, replacement, position,
201            RE.REG_REPLACE_USE_BACKSLASHESCAPE);
202      }
203    
204      /**
205       * @param replacement The replacement string
206       */
207      public String replaceAll (String replacement)
208      {
209        reset();
210        return pattern.getRE().substituteAll(input, replacement, position,
211            RE.REG_REPLACE_USE_BACKSLASHESCAPE);
212      }
213      
214      public int groupCount ()
215      {
216        return pattern.getRE().getNumSubs();
217      }
218     
219      public boolean lookingAt ()
220      {
221        match = pattern.getRE().getMatch(inputCharIndexed, 0, RE.REG_FIX_STARTING_POSITION, null);
222        if (match != null)
223          {
224            if (match.getStartIndex() == 0)
225              {
226                position = match.getEndIndex();
227                return true;
228              }
229            match = null;
230          }
231        return false;
232      }
233      
234      /**
235       * Attempts to match the entire input sequence against the pattern. 
236       *
237       * If the match succeeds then more information can be obtained via the
238       * start, end, and group methods.
239       *
240       * @see #start()
241       * @see #end()
242       * @see #group()
243       */
244      public boolean matches ()
245      {
246        match = pattern.getRE().getMatch(inputCharIndexed, 0, RE.REG_TRY_ENTIRE_MATCH|RE.REG_FIX_STARTING_POSITION, null);
247        if (match != null)
248          {
249            if (match.getStartIndex() == 0)
250              {
251                position = match.getEndIndex();
252                if (position == input.length())
253                    return true;
254              }
255            match = null;
256          }
257        return false;
258      }
259      
260      /**
261       * Returns the Pattern that is interpreted by this Matcher
262       */
263      public Pattern pattern ()
264      {
265        return pattern;
266      }
267      
268      public Matcher reset ()
269      {
270        position = 0;
271        match = null;
272        return this;
273      }
274      
275      /**
276       * @param input The new input character sequence
277       */
278      public Matcher reset (CharSequence input)
279      {
280        this.input = input;
281        this.inputCharIndexed = RE.makeCharIndexed(input, 0);
282        return reset();
283      }
284      
285      /**
286       * @returns the index of a capturing group in this matcher's pattern
287       *
288       * @exception IllegalStateException If no match has yet been attempted,
289       * or if the previous match operation failed
290       */
291      public int start ()
292        throws IllegalStateException
293      {
294        assertMatchOp();
295        return match.getStartIndex();
296      }
297    
298      /**
299       * @param group The index of a capturing group in this matcher's pattern
300       *
301       * @exception IllegalStateException If no match has yet been attempted,
302       * or if the previous match operation failed
303       * @exception IndexOutOfBoundsException If the replacement string refers
304       * to a capturing group that does not exist in the pattern
305       */
306      public int start (int group)
307        throws IllegalStateException
308      {
309        assertMatchOp();
310        return match.getStartIndex(group);
311      }
312    
313      /**
314       * @return True if and only if the matcher hit the end of input.
315       */
316      public boolean hitEnd()
317      {
318        return inputCharIndexed.hitEnd();
319      }
320    
321      /**
322       * @return A string expression of this matcher.
323       */
324      public String toString()
325      {
326        StringBuilder sb = new StringBuilder();
327        sb.append(this.getClass().getName())
328          .append("[pattern=").append(pattern.pattern())
329          .append(" region=").append("0").append(",").append(input.length())
330          .append(" lastmatch=").append(match == null ? "" : match.toString())
331          .append("]");
332        return sb.toString();
333      }
334    
335      private void assertMatchOp()
336      {
337        if (match == null) throw new IllegalStateException();
338      }
339    }