content: /*
   * Copyright 2002-2012 Drew Noakes
   *
   *    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.
   *
   * More information about this project is available at:
   *
   *    http://drewnoakes.com/code/exif/
   *    http://code.google.com/p/metadata-extractor/
   */
  
  package com.almalence.util.exifreader.lang;
  
  import com.almalence.util.exifreader.lang.annotations.NotNull;
  
  import java.io.IOException;
  import java.io.RandomAccessFile;
  import java.io.UnsupportedEncodingException;
  
  /**
   * Provides methods to read specific values from a byte array, with a consistent, checked exception structure for
   * issues.
   * <p/>
   * By default, the reader operates with Motorola byte order (big endianness).  This can be changed by calling
   * {@see setMotorolaByteOrder(boolean)}.
   * 
   * @author Drew Noakes http://drewnoakes.com
   * */
  public class RandomAccessFileReader implements BufferReader
  {
      @NotNull
      private final RandomAccessFile _file;
      private final long _length;
      private int _currentIndex;
      private boolean _isMotorolaByteOrder = true;
  
      @SuppressWarnings({ "ConstantConditions" })
      @com.almalence.util.exifreader.lang.annotations.SuppressWarnings(value = "EI_EXPOSE_REP2", justification = "Design intent")
      public RandomAccessFileReader(@NotNull RandomAccessFile file) throws IOException
      {
          if (file == null)
              throw new NullPointerException();
          
          _file = file;
          _length = _file.length();
      }
  
      @Override
      public long getLength()
      {
          return _length;
      }
  
  
      @Override
      public void setMotorolaByteOrder(boolean motorolaByteOrder)
      {
          _isMotorolaByteOrder = motorolaByteOrder;
      }
  
      @Override
      public boolean isMotorolaByteOrder()
      {
          return _isMotorolaByteOrder;
      }
  
      private byte read() throws BufferBoundsException
      {
          final int b;
          try {
              b = _file.read();
          } catch (IOException e) {
              throw new BufferBoundsException("IOException reading from file.", e);
          }
  
          if (b < 0)
              throw new BufferBoundsException("Unexpected end of file encountered.");
          assert(b <= 0xff);
          _currentIndex++;
          return (byte) b;
      }
  
      private void seek(final int index) throws BufferBoundsException
      {
          if (index == _currentIndex)
              return;
          try {
              _file.seek(index);
              _currentIndex = index;
          } catch (IOException e) {
              throw new BufferBoundsException("IOException seeking in file.", e);
          }
      }
  
      @Override
      public short getUInt8(int index) throws BufferBoundsException
      {
          checkBounds(index, 1);
          seek(index);
  
          return (short) (read() & 255);
      }
  
      @Override
      public byte getInt8(int index) throws BufferBoundsException
      {
          checkBounds(index, 1);
          seek(index);
  
          return read();
      }
  
      @Override
      public int getUInt16(int index) throws BufferBoundsException
      {
          checkBounds(index, 2);
          seek(index);
  
          if (_isMotorolaByteOrder) {
              // Motorola - MSB first
              return (read() << 8 & 0xFF00) |
                     (read()      & 0xFF);
          } else {
              // Intel ordering - LSB first
              return (read()      & 0xFF) |
                     (read() << 8 & 0xFF00);
          }
      }
  
      @Override
      public short getInt16(int index) throws BufferBoundsException
      {
          checkBounds(index, 2);
          seek(index);
  
          if (_isMotorolaByteOrder) {
              // Motorola - MSB first
              return (short) (((short) read() << 8 & (short)0xFF00) |
                              ((short) read()      & (short)0xFF));
          } else {
              // Intel ordering - LSB first
              return (short) (((short) read()      & (short)0xFF) |
                              ((short) read() << 8 & (short)0xFF00));
          }
      }
  
      @Override
      public long getUInt32(int index) throws BufferBoundsException
      {
          checkBounds(index, 4);
          seek(index);
  
          if (_isMotorolaByteOrder) {
              // Motorola - MSB first (big endian)
              return (((long) read()) << 24 & 0xFF000000L) |
                     (((long) read()) << 16 & 0xFF0000L) |
                     (((long) read()) << 8  & 0xFF00L) |
                     (((long) read())       & 0xFFL);
          } else {
              // Intel ordering - LSB first (little endian)
              return (((long) read())       & 0xFFL) |
                     (((long) read()) << 8  & 0xFF00L) |
                     (((long) read()) << 16 & 0xFF0000L) |
                     (((long) read()) << 24 & 0xFF000000L);
          }
      }
  
      @Override
      public int getInt32(int index) throws BufferBoundsException
      {
          checkBounds(index, 4);
          seek(index);
  
          if (_isMotorolaByteOrder) {
              // Motorola - MSB first (big endian)
              return (read() << 24 & 0xFF000000) |
                     (read() << 16 & 0xFF0000) |
                     (read() << 8  & 0xFF00) |
                     (read()       & 0xFF);
          } else {
              // Intel ordering - LSB first (little endian)
              return (read()       & 0xFF) |
                     (read() << 8  & 0xFF00) |
                     (read() << 16 & 0xFF0000) |
                     (read() << 24 & 0xFF000000);
  
          }
      }
  
      @Override
      public long getInt64(int index) throws BufferBoundsException
      {
          checkBounds(index, 8);
          seek(index);
  
          if (_isMotorolaByteOrder) {
              // Motorola - MSB first
              return ((long) read() << 56 & 0xFF00000000000000L) |
                     ((long) read() << 48 & 0xFF000000000000L) |
                     ((long) read() << 40 & 0xFF0000000000L) |
                     ((long) read() << 32 & 0xFF00000000L) |
                     ((long) read() << 24 & 0xFF000000L) |
                     ((long) read() << 16 & 0xFF0000L) |
                     ((long) read() << 8  & 0xFF00L) |
                     ((long) read()       & 0xFFL);
          } else {
              // Intel ordering - LSB first
              return ((long) read()       & 0xFFL) |
                     ((long) read() << 8  & 0xFF00L) |
                     ((long) read() << 16 & 0xFF0000L) |
                     ((long) read() << 24 & 0xFF000000L) |
                     ((long) read() << 32 & 0xFF00000000L) |
                     ((long) read() << 40 & 0xFF0000000000L) |
                     ((long) read() << 48 & 0xFF000000000000L) |
                     ((long) read() << 56 & 0xFF00000000000000L);
  
          }
      }
  
      @Override
      public float getS15Fixed16(int index) throws BufferBoundsException
      {
          checkBounds(index, 4);
          seek(index);
  
          if (_isMotorolaByteOrder) {
              float res = (read() & 255) << 8 |
                          (read() & 255);
              int d =     (read() & 255) << 8 |
                          (read() & 255);
              return (float)(res + d/65536.0);
          } else {
              // this particular branch is untested
              int d =     (read() & 255) |
                          (read() & 255) << 8;
              float res = (read() & 255) |
                          (read() & 255) << 8;
              return (float)(res + d/65536.0);
          }
      }
  
      @Override
      public float getFloat32(int index) throws BufferBoundsException
      {
          return Float.intBitsToFloat(getInt32(index));
      }
  
      @Override
      public double getDouble64(int index) throws BufferBoundsException
      {
          return Double.longBitsToDouble(getInt64(index));
      }
      
      @Override
      @NotNull
      public byte[] getBytes(int index, int count) throws BufferBoundsException
      {
          checkBounds(index, count);
          seek(index);
  
          byte[] bytes = new byte[count];
          final int bytesRead;
          try {
              bytesRead = _file.read(bytes);
              _currentIndex += bytesRead;
          } catch (IOException e) {
              throw new BufferBoundsException("Unexpected end of file encountered.", e);
          }
          if (bytesRead != count)
              throw new BufferBoundsException("Unexpected end of file encountered.");
          return bytes;
      }
  
      @Override
      @NotNull
      public String getString(int index, int bytesRequested) throws BufferBoundsException
      {
          return new String(getBytes(index, bytesRequested));
      }
  
      @Override
      @NotNull
      public String getString(int index, int bytesRequested, String charset) throws BufferBoundsException
      {
          checkBounds(index, bytesRequested);
  
          byte[] bytes = getBytes(index, bytesRequested);
          try {
              return new String(bytes, charset);
          } catch (UnsupportedEncodingException e) {
              return new String(bytes);
          }
      }
  
      @Override
      @NotNull
      public String getNullTerminatedString(int index, int maxLengthBytes) throws BufferBoundsException
      {
          // NOTE currently only really suited to single-byte character strings
  
          checkBounds(index, maxLengthBytes);
          seek(index);
  
          // Check for null terminators
          int length = 0;
          while ((index + length) < _length && read() != '\0' && length < maxLengthBytes)
              length++;
  
          byte[] bytes = getBytes(index, length);
          return new String(bytes);
      }
  
      private void checkBounds(final int index, final int bytesRequested) throws BufferBoundsException
      {
          if (bytesRequested < 0)
              throw new BufferBoundsException("Requested negative number of bytes.");
          if (index < 0)
              throw new BufferBoundsException("Requested data from a negative index within the file.");
          if ((long)index + (long)bytesRequested - 1L >= _length)
              throw new BufferBoundsException("Requested data from beyond the end of the file.");
      }
  }
  