/******************************************************************************* * Copyright (c) 2015 * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *******************************************************************************/ package jsettlers.graphics.reader.bytereader; import java.io.IOException; import java.io.RandomAccessFile; /** * This class provides a little endian wrapper of a reader. * * @author michael */ public class ByteReader { private static final int CACHE_SIZE = 10000; /** * The position the input stram is currently at. */ private long inputStreamPosition = 0; /** * The current cache position the user is reading from next. * <p> * It may never be greater than CACHE_SIZE. * * @see ByteReader#assertCacheHolds(int); */ private int cachePosition; /** * The start of the cache in the file. * <p> * Initialize so that jump does good work. */ private long cacheStart = -2 * CACHE_SIZE; /** * The data cache */ private byte[] cache = new byte[CACHE_SIZE]; private final RandomAccessFile in; /** * Creates a new reader. * * @param in * The in reader. * @throws IOException * It an IO error occured. */ public ByteReader(RandomAccessFile in) throws IOException { this.in = in; jumpCachePosition(0); } /** * Reads a 16 bit int. * * @return The int. * @throws IOException * If an io error occured. */ public int read16() throws IOException { assertCacheHolds(2); int c = this.cachePosition; int byte0 = this.cache[c]; int byte1 = this.cache[c + 1]; this.cachePosition = c + 2; return (0xff & byte0) | ((0xff & byte1) << 8); } /** * Sets the cache position pointer and the cache so that the pointer is on the given position in the file. * * @param newCachePosition * A file position * @throws IOException */ private void jumpCachePosition(long newCachePosition) throws IOException { long positionInCache = newCachePosition - this.cacheStart; if (positionInCache >= 0 && positionInCache < CACHE_SIZE) { this.cachePosition = (int) positionInCache; } else { // we have to reload... this.in.seek(newCachePosition); this.inputStreamPosition = newCachePosition; this.cacheStart = this.inputStreamPosition; this.inputStreamPosition += this.in.read(this.cache, 0, CACHE_SIZE); this.cachePosition = 0; } } /** * Asserts that the cache hold bytecount valid bytes. * <p> * if the rest of the cache is not long enough * * @param bytecount * @throws IOException */ private void assertCacheHolds(int bytecount) throws IOException { if (bytecount >= CACHE_SIZE) { throw new IllegalArgumentException( "Cache buffer to small to read that many bytes"); } int remaining = CACHE_SIZE - this.cachePosition; if (remaining < bytecount) { // refill buffer for (int i = 0; i < remaining; i++) { this.cache[i] = this.cache[this.cachePosition + i]; } // todo: save until where cache is valid, if file to long. int length = (int) Math.min(CACHE_SIZE - remaining, this.in.length() - this.in.getFilePointer()); this.inputStreamPosition += this.in.read(this.cache, remaining, length); // TODO: store how long buffer is valid this.cacheStart += this.cachePosition; this.cachePosition = 0; } } /** * Reads an int with 32 bit from the stram. * * @return The int's value. * @throws IOException * If an IO error occured. */ public int read32() throws IOException { assertCacheHolds(4); byte byte0 = this.cache[this.cachePosition++]; byte byte1 = this.cache[this.cachePosition++]; byte byte2 = this.cache[this.cachePosition++]; byte byte3 = this.cache[this.cachePosition++]; int value = (0xff & byte0) | ((0xff & byte1) << 8) | ((0xff & byte2) << 16) | ((0xff & byte3) << 24); return value; } /** * Assumes to read the given data. * * @param toRead * The array that the read bytes should be like. * @throws IOException * If the read data does not match the given data. */ public void assumeToRead(byte[] toRead) throws IOException { assertCacheHolds(toRead.length); for (int i = 0; i < toRead.length; i++) { byte read = this.cache[this.cachePosition++]; if (read != toRead[i]) { throw new IOException("IO error: expected to read " + toRead[i] + " but got " + read + " (i=" + i + ")"); } } } /** * Reads a signed 16 bit value. * * @return The signed 16 bit value * @throws IOException * If an IO error occured. */ public int read16signed() throws IOException { int read = read16(); if (read < 0x8000) { return read; } else { return read - 0x10000; } } /** * Reads a byte from the stream. * * @return The byte's value. * @throws IOException * If an io error occured. */ public int read8() throws IOException { assertCacheHolds(1); return 0xff & this.cache[this.cachePosition++]; } /** * Reads a byte stream from the stream. * <p> * Warning: this is not guaranteed to work for long arrays. * * @param b * The byte array to read to. * @param off * The offset in the array * @param len * The number of bytes to read. * @return The number of really read bytes. * @throws IOException * If an io error occurred. */ public int read(byte[] b, int off, int len) throws IOException { assertCacheHolds(len); for (int i = 0; i < len; i++) { b[off + i] = this.cache[this.cachePosition++]; } return len; } /** * Skipps to a given position. * * @param pos * The position to go to. * @return The actual position we went to. * @throws IOException * If an IO error occured. */ public long skipTo(long pos) throws IOException { jumpCachePosition(pos); return this.cacheStart + this.cachePosition; } /** * gets the number of read or skipped bytes. It is equal to the position in the stream, as long as no {@link IOException}s occurred and reset() * was not used. * * @return The number. */ public long getReadBytes() { return this.cacheStart + this.cachePosition; } /** * Closes the underlying stream. * * @throws IOException * If the close failed. */ public void close() throws IOException { this.in.close(); } }