/* * Copyright 2010, 2011, 2012 mapsforge.org * * This program is free software: you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free Software * Foundation, either version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License along with * this program. If not, see <http://www.gnu.org/licenses/>. */ package org.mapsforge.map.reader; import java.io.IOException; import java.io.RandomAccessFile; import java.io.UnsupportedEncodingException; import java.util.logging.Logger; /** * Reads from a {@link RandomAccessFile} into a buffer and decodes the data. */ public class ReadBuffer { private static final String CHARSET_UTF8 = "UTF-8"; private static final Logger LOGGER = Logger.getLogger(ReadBuffer.class.getName()); /** * Maximum buffer size which is supported by this implementation. */ static final int MAXIMUM_BUFFER_SIZE = 2500000; private byte[] bufferData; private int bufferPosition; private final RandomAccessFile inputFile; ReadBuffer(RandomAccessFile inputFile) { this.inputFile = inputFile; } /** * Returns one signed byte from the read buffer. * * @return the byte value. */ public byte readByte() { return this.bufferData[this.bufferPosition++]; } /** * Reads the given amount of bytes from the file into the read buffer and resets the internal buffer position. If * the capacity of the read buffer is too small, a larger one is created automatically. * * @param length * the amount of bytes to read from the file. * @return true if the whole data was read successfully, false otherwise. * @throws IOException * if an error occurs while reading the file. */ public boolean readFromFile(int length) throws IOException { // ensure that the read buffer is large enough if (this.bufferData == null || this.bufferData.length < length) { // ensure that the read buffer is not too large if (length > MAXIMUM_BUFFER_SIZE) { LOGGER.warning("invalid read length: " + length); return false; } this.bufferData = new byte[length]; } // reset the buffer position and read the data into the buffer this.bufferPosition = 0; return this.inputFile.read(this.bufferData, 0, length) == length; } /** * Converts four bytes from the read buffer to a signed int. * <p> * The byte order is big-endian. * * @return the int value. */ public int readInt() { this.bufferPosition += 4; return Deserializer.getInt(this.bufferData, this.bufferPosition - 4); } /** * Converts eight bytes from the read buffer to a signed long. * <p> * The byte order is big-endian. * * @return the long value. */ public long readLong() { this.bufferPosition += 8; return Deserializer.getLong(this.bufferData, this.bufferPosition - 8); } /** * Converts two bytes from the read buffer to a signed int. * <p> * The byte order is big-endian. * * @return the int value. */ public int readShort() { this.bufferPosition += 2; return Deserializer.getShort(this.bufferData, this.bufferPosition - 2); } /** * Converts a variable amount of bytes from the read buffer to a signed int. * <p> * The first bit is for continuation info, the other six (last byte) or seven (all other bytes) bits are for data. * The second bit in the last byte indicates the sign of the number. * * @return the int value. */ public int readSignedInt() { int variableByteDecode = 0; byte variableByteShift = 0; // check if the continuation bit is set while ((this.bufferData[this.bufferPosition] & 0x80) != 0) { variableByteDecode |= (this.bufferData[this.bufferPosition++] & 0x7f) << variableByteShift; variableByteShift += 7; } // read the six data bits from the last byte if ((this.bufferData[this.bufferPosition] & 0x40) != 0) { // negative return -(variableByteDecode | ((this.bufferData[this.bufferPosition++] & 0x3f) << variableByteShift)); } // positive return variableByteDecode | ((this.bufferData[this.bufferPosition++] & 0x3f) << variableByteShift); } /** * Converts a variable amount of bytes from the read buffer to an unsigned int. * <p> * The first bit is for continuation info, the other seven bits are for data. * * @return the int value. */ public int readUnsignedInt() { int variableByteDecode = 0; byte variableByteShift = 0; // check if the continuation bit is set while ((this.bufferData[this.bufferPosition] & 0x80) != 0) { variableByteDecode |= (this.bufferData[this.bufferPosition++] & 0x7f) << variableByteShift; variableByteShift += 7; } // read the seven data bits from the last byte return variableByteDecode | (this.bufferData[this.bufferPosition++] << variableByteShift); } /** * Decodes a variable amount of bytes from the read buffer to a string. * * @return the UTF-8 decoded string (may be null). */ public String readUTF8EncodedString() { return readUTF8EncodedString(readUnsignedInt()); } /** * Decodes the given amount of bytes from the read buffer to a string. * * @param stringLength * the length of the string in bytes. * @return the UTF-8 decoded string (may be null). */ public String readUTF8EncodedString(int stringLength) { if (stringLength > 0 && this.bufferPosition + stringLength <= this.bufferData.length) { this.bufferPosition += stringLength; try { return new String(this.bufferData, this.bufferPosition - stringLength, stringLength, CHARSET_UTF8); } catch (UnsupportedEncodingException e) { throw new IllegalStateException(e); } } LOGGER.warning("invalid string length: " + stringLength); return null; } /** * @return the current buffer position. */ int getBufferPosition() { return this.bufferPosition; } /** * @return the current size of the read buffer. */ int getBufferSize() { return this.bufferData.length; } /** * Sets the buffer position to the given offset. * * @param bufferPosition * the buffer position. */ void setBufferPosition(int bufferPosition) { this.bufferPosition = bufferPosition; } /** * Skips the given number of bytes in the read buffer. * * @param bytes * the number of bytes to skip. */ void skipBytes(int bytes) { this.bufferPosition += bytes; } }