001    /*
002     * Sonar, open source software quality management tool.
003     * Copyright (C) 2008-2012 SonarSource
004     * mailto:contact AT sonarsource DOT com
005     *
006     * Sonar is free software; you can redistribute it and/or
007     * modify it under the terms of the GNU Lesser General Public
008     * License as published by the Free Software Foundation; either
009     * version 3 of the License, or (at your option) any later version.
010     *
011     * Sonar is distributed in the hope that it will be useful,
012     * but WITHOUT ANY WARRANTY; without even the implied warranty of
013     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014     * Lesser General Public License for more details.
015     *
016     * You should have received a copy of the GNU Lesser General Public
017     * License along with Sonar; if not, write to the Free Software
018     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
019     */
020    package org.sonar.channel;
021    
022    import java.io.IOException;
023    import java.io.Reader;
024    import java.util.regex.Matcher;
025    
026    /**
027     * The CodeReader class provides some advanced features to read a source code. The most important one is the ability to try consuming the
028     * next characters in the stream according to a regular expression.
029     */
030    public class CodeReader extends CodeBuffer {
031    
032      private Cursor previousCursor;
033    
034      /*
035       * Constructor needed to be backward compatible (before using CodeReaderFilter)
036       */
037      public CodeReader(Reader code) {
038        super(code, new CodeReaderConfiguration());
039      }
040    
041      /*
042       * Constructor needed to be backward compatible (before using CodeReaderFilter)
043       */
044      public CodeReader(String code) {
045        super(code, new CodeReaderConfiguration());
046      }
047    
048      /**
049       * Creates a code reader with specific configuration parameters.
050       * Note that this constructor will read everything from reader and will close it.
051       * 
052       * @param code
053       *          the Reader to read code from
054       * @param configuration
055       *          the configuration parameters
056       */
057      public CodeReader(Reader code, CodeReaderConfiguration configuration) {
058        super(code, configuration);
059      }
060    
061      /**
062       * Creates a code reader with specific configuration parameters.
063       * 
064       * @param code
065       *          the code itself
066       * @param configuration
067       *          the configuration parameters
068       */
069      public CodeReader(String code, CodeReaderConfiguration configuration) {
070        super(code, configuration);
071      }
072    
073      /**
074       * Read and consume the next character
075       * 
076       * @param appendable
077       *          the read character is appended to appendable
078       */
079      public final void pop(Appendable appendable) {
080        try {
081          appendable.append((char) pop());
082        } catch (IOException e) {
083          throw new ChannelException(e.getMessage(), e);
084        }
085      }
086    
087      /**
088       * Read without consuming the next characters
089       * 
090       * @param length
091       *          number of character to read
092       * @return array of characters
093       */
094      public final char[] peek(int length) {
095        char[] result = new char[length];
096        int index = 0;
097        int nextChar = intAt(index);
098        while (nextChar != -1 && index < length) {
099          result[index] = (char) nextChar;
100          nextChar = intAt(++index);
101        }
102        return result;
103      }
104    
105      /**
106       * Read without consuming the next characters until a condition is reached (EndMatcher)
107       * 
108       * @param matcher
109       *          the EndMatcher used to stop the reading
110       * @param appendable
111       *          the read characters is appended to appendable
112       */
113      public final void peekTo(EndMatcher matcher, Appendable appendable) {
114        int index = 0;
115        char nextChar = charAt(index);
116        try {
117          while ( !matcher.match(nextChar) && nextChar != -1) {
118            appendable.append(nextChar);
119            nextChar = charAt(++index);
120          }
121        } catch (IOException e) {
122          throw new ChannelException(e.getMessage(), e);
123        }
124      }
125    
126      /**
127       * @deprecated in 2.2, use {@link #peekTo(EndMatcher matcher, Appendable appendable)} instead
128       */
129      @Deprecated
130      public final String peekTo(EndMatcher matcher) {
131        StringBuilder sb = new StringBuilder();
132        peekTo(matcher, sb);
133        return sb.toString();
134      }
135    
136      /**
137       * @deprecated in 2.2, use {@link #popTo(Matcher matcher, Appendable appendable)} instead
138       */
139      @Deprecated
140      public final void popTo(EndMatcher matcher, Appendable appendable) {
141        previousCursor = getCursor().clone();
142        try {
143          do {
144            appendable.append((char) pop());
145          } while ( !matcher.match(peek()) && peek() != -1);
146        } catch (IOException e) {
147          throw new ChannelException(e.getMessage(), e);
148        }
149      }
150    
151      /**
152       * Read and consume the next characters according to a given regular expression
153       * 
154       * @param matcher
155       *          the regular expression matcher
156       * @param appendable
157       *          the consumed characters are appended to this appendable
158       * @return number of consumed characters or -1 if the next input sequence doesn't match this matcher's pattern
159       */
160      public final int popTo(Matcher matcher, Appendable appendable) {
161        return popTo(matcher, null, appendable);
162      }
163    
164      /**
165       * Read and consume the next characters according to a given regular expression. Moreover the character sequence immediately following the
166       * desired characters must also match a given regular expression.
167       * 
168       * @param matcher
169       *          the Matcher used to try consuming next characters
170       * @param afterMatcher
171       *          the Matcher used to check character sequence immediately following the consumed characters
172       * @param appendable
173       *          the consumed characters are appended to this appendable
174       * @return number of consumed characters or -1 if one of the two Matchers doesn't match
175       */
176      public final int popTo(Matcher matcher, Matcher afterMatcher, Appendable appendable) {
177        try {
178          matcher.reset(this);
179          if (matcher.lookingAt()) {
180            if (afterMatcher != null) {
181              afterMatcher.reset(this);
182              afterMatcher.region(matcher.end(), length());
183              if ( !afterMatcher.lookingAt()) {
184                return -1;
185              }
186            }
187            previousCursor = getCursor().clone();
188            for (int i = 0; i < matcher.end(); i++) {
189              appendable.append((char) pop());
190            }
191            return matcher.end();
192          }
193        } catch (IndexOutOfBoundsException e) {
194          return -1;
195        } catch (IOException e) {
196          throw new ChannelException(e.getMessage(), e);
197        }
198        return -1;
199      }
200    
201      public final Cursor getPreviousCursor() {
202        return previousCursor;
203      }
204    }