package org.archive.util.binsearch.impl;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

import org.archive.util.binsearch.SeekableLineReader;
import org.archive.util.zip.GZIPMembersInputStream;

import com.google.common.io.LimitInputStream;

public class NIOSeekableLineReader implements SeekableLineReader {
	private FileChannel fc;
	private long size;
	
	private FileChannelInputStream fcis;
	private InputStreamReader isr;
	private BufferedReader br;

	private int blockSize;
	
	public NIOSeekableLineReader(FileChannel fc, int blockSize) throws IOException {
		this.fc = fc;
		size = fc.size();
		fcis = null;
		this.blockSize = blockSize;
	}
	
	public void seek(long offset) throws IOException {
		fcis = new FileChannelInputStream(fc, offset);
    	isr = new InputStreamReader(fcis, UTF8);
    	br = new BufferedReader(isr, blockSize);
	}
	
	public void seekWithMaxRead(long offset, boolean gzip, int maxLength) throws IOException {
		fcis = new FileChannelInputStream(fc, offset);
    	
    	InputStream is = new LimitInputStream(fcis, maxLength);
    	if (gzip) {
    		is = new GZIPMembersInputStream(is, blockSize);
    	}    	
    	isr = new InputStreamReader(is, UTF8);
    	br = new BufferedReader(isr, blockSize);
    }
	
	public long getOffset() throws IOException
	{
		return fcis.fcOffset;
	}

	public String readLine() throws IOException {
		if(br == null) {
			seek(0);
		}
		return br.readLine();
	}

	public void close() throws IOException {
		// NO-OP
	}

	public long getSize() throws IOException {
		return size;
	}
	public class FileChannelInputStream extends InputStream {
		FileChannel fc;
		long fcOffset;
		public FileChannelInputStream(FileChannel fc, long offset) {
			this.fc = fc;
			this.fcOffset = offset;
		}
		@Override
		public int read() throws IOException {
			byte b[] = new byte[1];
			int amt = read(b, 0, 1);
			if (amt == -1) {
				return -1;
			}
			return b[0] & 0xff;

		}
		@Override
		public int read(byte[] b) throws IOException {
			return read(b, 0, b.length);
		}
		
		public int read(byte[] buffer, int offset, int length) throws IOException {
			ByteBuffer bb = ByteBuffer.wrap(buffer,offset,length);
			int totalRead = 0;
			while(length > 0) {
				int amtRead = fc.read(bb, fcOffset);
				if(amtRead == -1) {
					// EOF:
					break;
				}
				totalRead += amtRead;
				fcOffset += amtRead;
				length -= amtRead;
			}
			return totalRead == 0 ? -1 : totalRead;
		}
	}
}

package org.archive.util.binsearch.impl;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

import org.archive.util.binsearch.SeekableLineReader;
import org.archive.util.zip.GZIPMembersInputStream;

import com.google.common.io.LimitInputStream;

public class NIOSeekableLineReader implements SeekableLineReader {
	private FileChannel fc;
	private long size;
	
	private FileChannelInputStream fcis;
	private InputStreamReader isr;
	private BufferedReader br;

	private int blockSize;
	
	public NIOSeekableLineReader(FileChannel fc, int blockSize) throws IOException {
		this.fc = fc;
		size = fc.size();
		fcis = null;
		this.blockSize = blockSize;
	}
	
	public void seek(long offset) throws IOException {
		fcis = new FileChannelInputStream(fc, offset);
    	isr = new InputStreamReader(fcis, UTF8);
    	br = new BufferedReader(isr, blockSize);
	}
	
	public void seekWithMaxRead(long offset, boolean gzip, int maxLength) throws IOException {
		fcis = new FileChannelInputStream(fc, offset);
    	
    	InputStream is = new LimitInputStream(fcis, maxLength);
    	if (gzip) {
    		is = new GZIPMembersInputStream(is, blockSize);
    	}    	
    	isr = new InputStreamReader(is, UTF8);
    	br = new BufferedReader(isr, blockSize);
    }
	
	public long getOffset() throws IOException
	{
		return fcis.fcOffset;
	}

	public String readLine() throws IOException {
		if(br == null) {
			seek(0);
		}
		return br.readLine();
	}

	public void close() throws IOException {
		if (br != null) {
			br.close();
			br = null;
		}
	}

	public long getSize() throws IOException {
		return size;
	}
	public class FileChannelInputStream extends InputStream {
		FileChannel fc;
		long fcOffset;
		public FileChannelInputStream(FileChannel fc, long offset) {
			this.fc = fc;
			this.fcOffset = offset;
		}
		@Override
		public int read() throws IOException {
			byte b[] = new byte[1];
			int amt = read(b, 0, 1);
			if (amt == -1) {
				return -1;
			}
			return b[0] & 0xff;

		}
		@Override
		public int read(byte[] b) throws IOException {
			return read(b, 0, b.length);
		}
		
		public int read(byte[] buffer, int offset, int length) throws IOException {
			ByteBuffer bb = ByteBuffer.wrap(buffer,offset,length);
			int totalRead = 0;
			while(length > 0) {
				int amtRead = fc.read(bb, fcOffset);
				if(amtRead == -1) {
					// EOF:
					break;
				}
				totalRead += amtRead;
				fcOffset += amtRead;
				length -= amtRead;
			}
			return totalRead == 0 ? -1 : totalRead;
		}
	}
}

fix leak in NIOSeekableLineReader