package org.cdlib.xtf.util;
/**
* Copyright (c) 2004, Regents of the University of California
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* - Neither the name of the University of California nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
import java.io.IOException;
import java.io.RandomAccessFile;
/**
* Provides a line-based interface for reading a file. Has the unusual ability
* to read forward <i>or backward</i>.
*
* @author Martin Haye
*/
@SuppressWarnings("cast")
public class LineReader {
/** Size of our internal buffer */
private static final int blockSize = 1024;
/** Internal buffer of file data */
private byte[] block = new byte[blockSize];
/** Overall length of the file we're reading */
private int length;
/** Current position within the file */
private int pos = -1;
/** Starting position of the block buffer within the file */
private int blockStart = -1;
/** Ending position of the block buffer within the file */
private int blockEnd;
/** File position of the start of the last line read */
private int linePos;
/** Actual disk file we're reading */
private RandomAccessFile file;
/** Used to accumulate lines */
private StringBuffer buf = new StringBuffer(500);
/**
* Default constructor
*
* @param filePath Path of the file to read
*/
public LineReader(String filePath)
throws IOException
{
file = new RandomAccessFile(filePath, "r");
length = (int)file.length();
seek(0);
} // constructor
/**
* Read a block of data starting a the given position.
*/
private void readBlock(int startPos)
throws IOException
{
file.seek(startPos);
int nRead = file.read(block);
blockStart = startPos;
blockEnd = Math.min(length, startPos + nRead);
} // readBlock()
/**
* Tells how long the file is, in bytes.
*/
public final int length()
throws IOException
{
return length;
}
/**
* Reposition the file pointer at the beginning of the line containing the
* specified byte position.
*/
public void seek(int toPos)
throws IOException
{
pos = toPos;
int newStart = pos - (pos % blockSize);
if (newStart != blockStart)
readBlock(newStart);
// Adjust so we're on an even line boundary.
char c = prevChar();
if (c != '\n' && c != '\r')
prevLine();
else
nextChar();
} // seek()
/**
* Get the next character in the input file, and increment the position.
*/
private char nextChar()
throws IOException
{
// If at the end of this block, read another one.
if (pos == blockEnd)
readBlock(blockEnd);
assert pos >= blockStart && pos < blockEnd;
// Get the character
return (char)(((int)block[(pos++) - blockStart]) & 0xff);
} // nextChar()
/**
* Get the previous character in the input file, and decrement the position.
*/
private char prevChar()
throws IOException
{
if (pos == 0)
return 0;
// If at the start of this block, read the prior one.
if (pos == blockStart) {
readBlock(blockStart - blockSize);
pos = blockEnd;
}
assert pos > blockStart && pos <= blockEnd;
// Get the character
return (char)(((int)block[(--pos) - blockStart]) & 0xff);
} // prevChar()
/**
* Retrieves the next line of text from the file.
*
* @return The text line, or null if the end of the file has been reached.
*/
public String nextLine()
throws IOException
{
// Clear old data from the accumulation buffer
buf.setLength(0);
// Record the starting position of the line.
linePos = pos;
// Go until we hit a newline.
char c = 0;
while (pos < length) {
c = nextChar();
if (c == '\n' || c == '\r')
break;
buf.append(c);
} // while( pos < length )
// If newline/cr return pair is found, eat it.
if (pos < length) {
char c2 = nextChar();
if ((c == '\n' && c2 != '\r') || (c == '\r' && c2 != '\n'))
prevChar();
}
// All done!
if (buf.length() == 0 && pos == length)
return null;
return buf.toString();
} // nextLine()
/**
* Retrieves the previous line of text from the file.
*
* @return The text line, or null if the start of the file has been reached.
*/
public String prevLine()
throws IOException
{
// Clear the old data from the accumulation buffer
buf.setLength(0);
// If we're in mid-line, back up til we hit a newline.
char c = 0;
while (pos > 0) {
c = prevChar();
if (c == '\n' || c == '\r')
break;
}
// Handle newline/cr if present
if (pos > 0) {
char c2 = prevChar();
if ((c == '\n' && c2 != '\r') || (c == '\r' && c2 != '\n'))
nextChar();
}
// At start of file? Go no further.
if (pos == 0)
return null;
// Now accumulate chars until we hit another newline.
while (pos > 0) {
c = prevChar();
if (c == '\n' || c == '\r')
break;
buf.append(c);
} // while( pos > 0 )
// Leave the newline where it was.
if (c == '\n' || c == '\r')
nextChar();
// Record the starting position of the line.
linePos = pos;
// Since we read the characters in backwards, invert the order now.
final int len = buf.length();
for (int i = 0; i < len / 2; i++) {
c = buf.charAt(i);
buf.setCharAt(i, buf.charAt(len - i - 1));
buf.setCharAt(len - i - 1, c);
}
// And we're done.
return buf.toString();
} // prevLine()
/**
* Retrieves the file position of the last line fetched by nextLine() or
* prevLine().
*/
public int linePos() {
return linePos;
}
} // class LineReader