/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package groovy.io; import java.io.BufferedReader; import java.io.IOException; import java.io.Reader; import java.nio.CharBuffer; /** * The <code>LineColumnReader</code> is an extension to <code>BufferedReader</code> * that keeps track of the line and column information of where the cursor is. * * @author Guillaume Laforge * @since 1.8.0 */ public class LineColumnReader extends BufferedReader { /** * The current line position */ private long line = 1; /** * The current column position */ private long column = 1; /** * The latest marked line position */ private long lineMark = 1; /** * The latest marked line position */ private long columnMark = 1; private boolean newLineWasRead = false; /** * Constructor wrapping a <code>Reader</code> * (<code>FileReader</code>, <code>FileReader</code>, <code>InputStreamReader</code>, etc.) * * @param reader the reader to wrap */ public LineColumnReader(Reader reader) { super(reader); } /** * Marks the present position in the stream. Subsequent calls to reset() will attempt to reposition the stream to this point. * * @param readAheadLimit Limit on the number of characters that may be read while still preserving the mark. * An attempt to reset the stream after reading characters up to this limit or beyond may fail. * A limit value larger than the size of the input buffer will cause a new buffer to be allocated whose size is no smaller than limit. * Therefore large values should be used with care. */ @Override public void mark(int readAheadLimit) throws IOException { lineMark = line; columnMark = column; super.mark(readAheadLimit); } /** * Resets the stream to the most recent mark. */ @Override public void reset() throws IOException { line = lineMark; column = columnMark; super.reset(); } /** * Reads a single character. * * @return The character read, as an integer in the range 0 to 65535 (0x00-0xffff), * or -1 if the end of the stream has been reached */ @Override public int read() throws IOException { if (newLineWasRead) { line += 1; column = 1; newLineWasRead = false; } int charRead = super.read(); if (charRead > -1) { char c = (char)charRead; // found a \r or \n, like on Mac or Unix // could also be Windows' \r\n if (c == '\r' || c == '\n') { newLineWasRead = true; if (c == '\r') { mark(1); c = (char)super.read(); // check if we have \r\n like on Windows // if it's not \r\n we reset, otherwise, the \n is just consummed if (c != '\n') { reset(); } } } else { column += 1; } } return charRead; } /** * Reads characters into a portion of an array. * * @param chars Destination array of char * @param startOffset Offset at which to start storing characters * @param length Maximum number of characters to read * @return an exception if an error occurs */ @Override public int read(char[] chars, int startOffset, int length) throws IOException { for (int i = startOffset; i <= startOffset + length; i++) { int readInt = read(); if (readInt == -1) return i - startOffset; chars[i] = (char)readInt; } return length; } /** * Reads a line of text. A line is considered to be terminated by any one of a line feed ('\n'), * a carriage return ('\r'), or a carriage return followed immediately by a linefeed. * * @return A String containing the contents of the line, not including any line-termination characters, * or null if the end of the stream has been reached */ @Override public String readLine() throws IOException { StringBuilder result = new StringBuilder(); for (;;) { int intRead = read(); if (intRead == -1) { return result.length() == 0 ? null : result.toString(); } char c = (char)intRead; if (c == '\n' || c == '\r') break; result.append(c); } return result.toString(); } /** * Skips characters. * * @param toSkip the number of characters to skip * @return The number of characters actually skipped */ @Override public long skip(long toSkip) throws IOException { for (long i = 0; i < toSkip; i++) { int intRead = read(); if (intRead == -1) return i; } return toSkip; } /** * Reads characters into an array. * This method will block until some input is available, an I/O error occurs, * or the end of the stream is reached. * * @param chars Destination buffer * @return The number of characters read, or -1 if the end of the stream has been reached */ @Override public int read(char[] chars) throws IOException { return read(chars, 0, chars.length - 1); } /** * Not implemented. * * @param buffer Destination buffer * @return The number of characters read, or -1 if the end of the stream has been reached * @throws UnsupportedOperationException as the method is not implemented */ @Override public int read(CharBuffer buffer) { throw new UnsupportedOperationException("read(CharBuffer) not yet implemented"); } /** * Closes the stream and releases any system resources associated with it. * Once the stream has been closed, further read(), ready(), mark(), reset(), or skip() invocations * will throw an IOException. Closing a previously closed stream has no effect. */ @Override public void close() throws IOException { super.close(); } public long getColumn() { return column; } public void setColumn(long column) { this.column = column; } public long getColumnMark() { return columnMark; } public void setColumnMark(long columnMark) { this.columnMark = columnMark; } public long getLine() { return line; } public void setLine(long line) { this.line = line; } public long getLineMark() { return lineMark; } public void setLineMark(long lineMark) { this.lineMark = lineMark; } }