All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.tinfour.io.BufferedRandomAccessReader Maven / Gradle / Ivy

/* --------------------------------------------------------------------
 * Copyright 2015 Gary W. Lucas.
 *
 * Licensed 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.
 * ---------------------------------------------------------------------
 */


/*
 * -----------------------------------------------------------------------
 *
 * Revision History:
 * Date     Name         Description
 * ------   ---------    -------------------------------------------------
 * 02/2015  G. Lucas     Created
 *
 * -----------------------------------------------------------------------
 */
package org.tinfour.io;

import java.io.Closeable;
import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;

/**
 * Accesses a random access file using a ByteBuffer with little-endian
 * byte order in support of the LAS file format.
 */
public class BufferedRandomAccessReader implements  Closeable {

  /**
   * The default size for the read (and eventually write) buffer
   */
  private static final int DEFAULT_BUFFER_SIZE = 8 * 1024;


  /**
   * A reference to the file with which this instance is associated
   */
  final File file;

  /**
   * The byte buffer used for reading (and eventually writing) data
   */
  final ByteBuffer buffer;

  /**
   * The instance of the random-access file.
   */
  RandomAccessFile raFile;

  /**
   * The file channel obtained from the random-access file
   */
  FileChannel fileChannel;
  /**
   * the length of the random-access file being read
   */
  long raFileLen;

  /**
   * the position in the virtual file (not in the actual file)
   */
  long raFilePos;

  /**
   * Indicates that at least some data has been read into the buffer.
   */
  boolean bufferContainsData;

  /**
   * Opens the specified file for read-only, random-access.
   * A buffer is created to provide efficient input operations
   * across a series of sequential method calls.  In accordance with
   * the LAS standard, all data in the file is treated as being in
   * little-endian byte order.
   *
   * @param file A valid file object.
   * @throws IOException In the event of an unrecoverable IO condition
   * such as file not found, access denied, etc.
   */
  public BufferedRandomAccessReader(File file) throws IOException {
    if (file == null) {
      throw new NullPointerException();
    }
    buffer = ByteBuffer.allocateDirect(DEFAULT_BUFFER_SIZE);
    buffer.order(ByteOrder.LITTLE_ENDIAN);

    this.file = file;
    raFile = new RandomAccessFile(file, "r");
    fileChannel = raFile.getChannel();
    raFileLen = raFile.length();
    raFilePos = 0;
    raFile.seek(0);
    bufferContainsData = false;

  }

  /**
   * Closes the file resources used by the referenced instance.
   * No further file access will be possible.
   *
   * @throws IOException In the event of an unrecoverable I/O condition.
   */

  @Override
  public void close() throws IOException {
    if (raFile != null) {
      raFile.close();
      raFile = null;
    }

    fileChannel = null;
  }

  /**
   * Gets a File object referring to the currently open file (if any).
   *
   * @return A valid object if defined; otherwise, null.
   */
  public File getFileReference() {
    return file;
  }

  /**
   * Gets the current size of the file in bytes.
   *
   * @return A long integer giving file size in bytes.
   */
  public long getFileSize() {
    return raFileLen;
  }

  /**
   * Provides the current position within the random-access file.
   *
   * @return a long integer value giving offset in bytes from beginning of file.
   */
  public long getFilePosition() {
    return raFilePos;
  }

  /**
   * Prepares the read buffer to access the specified number of
   * bytes. Adjusts internal elements accordingly. If the seek method
   * was previously called, an actual read may not yet occurred but
   * will be executed by this method.
   * at least the required number of bytes.
   *
   * @param bytesToRead Number of bytes to be read, must not exceed
   * the size of the buffer.
   * @throws IOException In the event of an unrecoverable I/O condition.
   */
  private void prepareBufferForRead(int bytesToRead) throws IOException {
    int bytesNotRead = bytesToRead;
    if (raFile == null) {
      throw new IOException("Reading from a file that was closed");
    }

    if (raFilePos >= raFileLen) {
      throw new EOFException();
    }

    boolean readEnabled = true;
    if (bufferContainsData) {
      int remaining = buffer.remaining();
      if (remaining >= bytesNotRead) {
        readEnabled = false;
      } else {
        // remaining < nBytes
        readEnabled = true;
        if (remaining == 0) {
          buffer.clear();
        } else {
          buffer.compact();
          // note that we have to tweak our bookkeeping here
          // because we have a partial in the buffer... so we need
          // to advance the rafPosition ahead so that we don't re-read
          // what we've already pulled in.
          raFilePos += remaining;
          bytesNotRead -= remaining;
        }
      }
    }

    if (readEnabled) {
      raFile.seek(raFilePos);
      int nBytesRead = fileChannel.read(buffer);
      if(nBytesRead<=0){
        throw new IOException("Failed to read "
                +bytesNotRead
                +" from file position "
                +raFilePos);
      }
      buffer.flip();
      bufferContainsData = true;
    }
    raFilePos += bytesNotRead;
  }

  /**
   * Reads a C/C++ style null-terminated string of a specified maximum
   * length from the from data file. The source data is treated as specifying
   * a string as one byte per character following the ISO-8859-1 standard.
   * If a zero byte is encountered in the sequence, the string is terminated.
   * Otherwise, it is extended out to the maximum length. Regardless of
   * how many characters are read, the file position is always adjusted
   * forward by the maximum length.
   *
   * @param maximumLength Maximum number of bytes to be read from file.
   * @return A valid, potentially empty string.
   * @throws IOException In the event of an unrecoverable I/O condition.
   */
  public String readAscii(int maximumLength) throws IOException {
    if (maximumLength <= 0) {
      return "";
    }
    StringBuilder builder = new StringBuilder(maximumLength);
    BufferedRandomAccessReader.this.readAscii(builder, maximumLength);
    return builder.toString();
  }

  /**
   * Reads a C/C++ style null-terminated string of a specified maximum
   * length from the from data file. The source data is treated as specifying
   * a string as one byte per character following the ISO-8859-1 standard.
   * If a zero byte is encountered in the sequence, the string is terminated.
   * Otherwise, it is extended out to the maximum length. Regardless of
   * how many characters are read, the file position is always adjusted
   * forward by the maximum length.
   *
   * @param builder The StringBuilder to which data is appended; if
   * the builder already contains text, then it will not be clear out
   * before the data is written.
   * @param maximumLength Maximum number of bytes to be read from file.
   * @return Number of valid characters extracted from the file before
   * a null was encountered.
   * @throws IOException In the event of an unrecoverable I/O condition.
   */
  public int readAscii(final StringBuilder builder, final int maximumLength)
    throws IOException {
    if (maximumLength <= 0) {
      return 0;
    }
    int k = 0;
    while (k < maximumLength) {
      k++;
      int b = this.readByte();
      if (b == 0) {
        if (k < maximumLength) {
          skipBytes(maximumLength - k);
        }
        return k;
      }
      builder.append((char) (b & 0xff));
    }
    return maximumLength;
  }


  /**
   * Reads one input byte and returns true if that byte is nonzero,
   * false if that byte is zero. This method may be used to
   * read the byte written by the writeBoolean method of interface DataOutput.
   *
   * @return The boolean value read from the file.
   * @throws IOException In the event of an unrecoverable I/O condition.
   */

  public boolean readBoolean() throws IOException {
    prepareBufferForRead(1);
    byte test = buffer.get();
    return test != 0;
  }

  /**
   * Reads and returns one input byte. The byte is treated as a signed
   * value in the range -128 through 127, inclusive. This method may be used
   * to read the byte written by the writeByte method of interface DataOutput.
   *
   * @return The 8-bit value read.
   * @throws IOException In the event of an unrecoverable I/O condition.
   */

  public byte readByte() throws IOException {
    prepareBufferForRead(1);
    return buffer.get();
  }


  /**
   * Reads one input byte and returns an integer value
   * in the range 0 through 255.
   *
   * @return An integer primitive based on the unsigned value of a byte
   * read from the source file.
   * @throws IOException In the event of an unrecoverable I/O condition.
   */

  public int readUnsignedByte() throws IOException {
    prepareBufferForRead(1);
    return (int) (buffer.get()) & 0x000000ff;
  }



  /**
   * Reads 4 bytes given in little-endian order and and returns
   * a Java long primitive given values in the range 0 through 4294967295.
   *
   * @throws IOException In the event of an unrecoverable I/O condition.
   * @return a Java long correctly interpreted from the unsigned integer
   * (4-byte) value stored in the data file.
   */
  public long readUnsignedInt() throws IOException {
    prepareBufferForRead(4);
    return  (long)(buffer.getInt())&0xffffffffL;
  }

  /**
   * Read two bytes  and returns a
   * Java int primitive.
   *
   * @return A Java integer primitive in the range 0 to 65535.
   * @throws IOException In the event of an unrecoverable I/O condition.
   */
  public int readUnsignedShort() throws IOException {
    prepareBufferForRead(2);
    return (int)(buffer.getShort())&0xffff;
  }

  /**
   * Read 4 bytes and return Java integer.
   *
   * @return A Java integer primitive.
   * @throws IOException In the event of an unrecoverable I/O condition.
   */
  public int readInt() throws IOException {
      prepareBufferForRead(4);
    return buffer.getInt();
  }

    /**
   * Read 4 bytes and return Java integer.
   *
   * @return A Java integer primitive.
   * @throws IOException In the event of an unrecoverable I/O condition.
   */
  public int readIntBigEndian() throws IOException {
      prepareBufferForRead(4);
      int i = buffer.getInt();
      return (i>>>24)|((i>>8)&0x0000ff00)|((i<<8)&0x00ff0000)|(i<<24);
  }



  /**
   * Read 8 bytes from the file and returns a Java double.
   *
   * @return A Java double primitive.
   * @throws IOException In the event of an unrecoverable I/O condition.
   *
   */
  public double readDoubleBigEndian() throws IOException {
    prepareBufferForRead(8);
    long r = buffer.getLong();
    long b
      = (r >>> 56)
      | ((r >> 48) & 0x000000000000ff00L)
      | ((r >> 40) & 0x0000000000ff0000L)
      | ((r >> 32) & 0x00000000ff000000L)
      | ((r << 32) & 0x000000ff00000000L)
      | ((r << 40) & 0x0000ff0000000000L)
      | ((r << 48) & 0x00ff000000000000L)
      | (r << 56);
    return Double.longBitsToDouble(b);
  }
  /**
   * Read 8 bytes from the file and returns a Java double.
   *
   * @return A Java double primitive.
   * @throws IOException In the event of an unrecoverable I/O condition.
   *
   */
  public double readDouble() throws IOException {
   prepareBufferForRead(8);
    return buffer.getDouble();
  }

  /**
   * Reads 4 bytes from the file and returns a Java float.
   *
   * @return A Java float primitive.
   * @throws IOException In the event of an unrecoverable I/O condition.
   */
  public float readFloat() throws IOException {
     prepareBufferForRead(4);
    return buffer.getFloat();
  }

  /**
   * Read 8 bytes from the file and returns a java long
   *
   * @return AJava long primitive
   * @throws IOException In the event of an unrecoverable I/O condition.
   */
  public long readLong() throws IOException {
        prepareBufferForRead(8);
    return buffer.getLong();
  }

  /**
   * Reads two bytes from the file treating them as being in little-endian order
   * and returns a short.
   *
   * @return A Java short primitive in the range -32768 to 32767.
   * @throws IOException In the event of an unrecoverable I/O condition.
   */
  public short readShort() throws IOException {
     prepareBufferForRead(2);
    return buffer.getShort();
  }

  /**
   * Sets the virtual file-pointer position measured from the
   * beginning of the file.
   *
   * @param position The file position, measured in bytes from the
   * beginning of the file at which to set the virtual file position.
   * @throws IOException In the event of an unrecoverable I/O condition.
   */
  public void seek(long position) throws IOException {

    if (bufferContainsData) {
      int bufferPosition = buffer.position();
      int bufferRemaining = buffer.remaining();
      long pos0 = raFilePos - bufferPosition;
      long pos1 = raFilePos + bufferRemaining - 1;
      if (pos0 <= position && position <= pos1) {
        raFilePos = position;
        long bufferPos = position - pos0;
        buffer.position((int) bufferPos);
        return;
      }
    }

    bufferContainsData = false;
    buffer.clear();
    raFilePos = position;
  }

  /**
   * Makes an attempt to advance the virtual file position by n
   * bytes in order to match the functionality of the DataInput interface.
   *
   * @param n The number of bytes byte which to advance the file position
   * @return the number of bytes skipped.
   * @throws IOException In the event of an unrecoverable I/O condition.
   */

  public int skipBytes(int n) throws IOException {

    raFilePos += n;
    if (bufferContainsData) {
      int remaining = buffer.remaining();
      if (n < remaining) {
        int position = buffer.position();
        buffer.position(position + n);
      } else {
        buffer.clear();
        bufferContainsData = false;
      }
    }
    return n;
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy