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 */
020package org.sonar.channel;
021
022import java.io.IOException;
023import java.io.Reader;
024import 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 */
030public 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}