/*
* This file is part of the Wayback archival access software
* (http://archive-access.sourceforge.net/projects/wayback/).
*
* Licensed to the Internet Archive (IA) by one or more individual
* contributors.
*
* The IA 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 org.archive.wayback.util.flatfile;
import java.io.IOException;
import java.io.RandomAccessFile;
/**
* Just like a BufferedReader, except the buffer scrolls backvards, allowing
* this one to support 'readPrevLine()' instead of readLine().
*
* @author brad
* @version $Date$, $Revision$
*/
public class ReverseBufferedReader {
private static int DEFAULT_BUFFER_SIZE = 4096;
// if the StringBuilder 'buffer' ever gets larger than
// MAX_BUFFER_MULTIPLE * bufferSize, fail: it means there is a line that's
// just too big to handle.
private static int MAX_BUFFER_MULTIPLE = 10;
private static char NEWLINE = '\n';
//private static char LINEFEED = '\r';
private StringBuilder buffer;
private RandomAccessFile raf;
private int bufferSize = DEFAULT_BUFFER_SIZE;
private long lastOffset;
private boolean hitBOF;
private String lineDelimiter;
/**
* @param raf
* @throws IOException
*/
public ReverseBufferedReader(RandomAccessFile raf) throws IOException {
this.raf = raf;
lastOffset = raf.getFilePointer();
hitBOF = lastOffset == 0;
lineDelimiter = String.valueOf(NEWLINE);
buffer = new StringBuilder(bufferSize);
}
/**
* @param bufferSize The bufferSize to set.
*/
public void setBufferSize(int bufferSize) {
this.bufferSize = bufferSize;
}
private boolean getMoreData() throws IOException {
byte[] buf = new byte[bufferSize];
if(lastOffset == 0) {
hitBOF = true;
return false;
}
// make sure we don't run out of RAM from a seriously big line:
if(buffer.length() > (bufferSize * MAX_BUFFER_MULTIPLE)) {
hitBOF = true;
return false;
}
long nextOffset = lastOffset - bufferSize;
if(nextOffset < 0) nextOffset = 0;
// TODO: is (int) conversion safe here?
int amountToRead = (int) (lastOffset - nextOffset);
raf.seek(nextOffset);
raf.read(buf,0,amountToRead);
lastOffset = nextOffset;
buffer.insert(0,new String(buf,0,amountToRead,"UTF-8"));
return true;
}
/**
* @return String previous line read from the file
* @throws IOException
*/
public String readPrevLine() throws IOException {
if(hitBOF) return null;
int lastFound;
while(true) {
lastFound = buffer.lastIndexOf(lineDelimiter);
if(lastFound == -1) {
if(!getMoreData()) return null;
// no more data!
} else {
// still in business with current buffer...
String ret = buffer.substring(lastFound+1);
buffer.setLength(lastFound);
return ret;
}
}
}
/**
* @throws IOException
*/
public void close() throws IOException {
raf.close();
}
}