import java.io.IOException;
import java.util.Arrays;

import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.TearDown;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

@BenchmarkMode(org.openjdk.jmh.annotations.Mode.SingleShotTime)
@OutputTimeUnit(java.util.concurrent.TimeUnit.MILLISECONDS)
@State(org.openjdk.jmh.annotations.Scope.Thread)
@Fork(value = 1, jvmArgsAppend = { "-ea" })
@Warmup(batchSize = 1000)
@Measurement(batchSize = 1000)
public class FileRead {
	// XXX put in any directory where the files are located.
	String dirName = "resources/";
	// XXX put in any filenames you like to test.
	@Param({ "100b.txt", "1k.txt", "10k.txt", "100k.txt", "1MB.txt", "10MB.txt" })
	String fileName;

	java.io.File file;
	byte[] result;
	byte[] expected;

	@Setup
	public void setup() throws IOException {
		file = new java.io.File(dirName + fileName).getAbsoluteFile();
		result = null;
		expected = java.nio.file.Files.readAllBytes(file.toPath());
	}

	@TearDown
	public void check() {
		assert Arrays.equals(expected, result) : "Nothing changed?";
	}

	public static final int MAX_ARRAY_LENGTH = Integer.MAX_VALUE - 8;

	@Benchmark
	public void readAllBytesOld() throws IOException {
		try (java.io.InputStream input = new java.io.FileInputStream(file)) {
			result = input.readAllBytes();
		}
	}

	@Benchmark
	public void readAllBytesNew() throws IOException {
		try (java.io.InputStream input = new java.io.FileInputStream(file) {

			@Override
			public byte[] readAllBytes() throws IOException {
				long length = this.getChannel().size();
				// use jdk.internal.util.ArraysSupport.newLength(int, int, int)?
				if (length > MAX_ARRAY_LENGTH)
					throw new OutOfMemoryError("File too large for array: " + length);
				return readNBytes(this, (int) length);
			}

			byte[] readNBytes(java.io.InputStream input, int byteLength) throws IOException {
				if (byteLength == 0)
					return new byte[0];
				byte[] byteBuf = new byte[byteLength]; // exact buffer size
				int byteCount = 0;
				int byteTransferSize = byteBuf.length;
				int bytesRead;
				while ((bytesRead = input.read(byteBuf, byteCount, byteTransferSize)) >= 0) {
					byteCount += bytesRead;
					byteTransferSize = byteBuf.length - byteCount;
					if (byteTransferSize <= 0) {
						break;
					}
				}
				return (byteBuf.length == byteCount) ? byteBuf : Arrays.copyOf(byteBuf, byteCount);
			}
		}) {
			result = input.readAllBytes();
		}
	}

	public static void main(String[] args) throws RunnerException, InterruptedException {
		Options opt = new OptionsBuilder().include(FileRead.class.getSimpleName()).shouldFailOnError(true).build();
		new Runner(opt).run();
	}
}
