Index: trunk/src/com/drew/lang/RandomAccessReader.java
===================================================================
--- trunk/src/com/drew/lang/RandomAccessReader.java	(revision 10862)
+++ trunk/src/com/drew/lang/RandomAccessReader.java	(revision 13061)
@@ -1,4 +1,4 @@
 /*
- * Copyright 2002-2016 Drew Noakes
+ * Copyright 2002-2017 Drew Noakes
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
@@ -22,8 +22,11 @@
 package com.drew.lang;
 
-import com.drew.lang.annotations.NotNull;
-
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+
+import com.drew.lang.annotations.NotNull;
+import com.drew.lang.annotations.Nullable;
+import com.drew.metadata.StringValue;
 
 /**
@@ -36,5 +39,4 @@
  * <ul>
  *     <li>{@link ByteArrayReader}</li>
- *     <li>{@link RandomAccessStreamReader}</li>
  * </ul>
  *
@@ -45,4 +47,6 @@
     private boolean _isMotorolaByteOrder = true;
 
+    public abstract int toUnshiftedOffset(int localOffset);
+
     /**
      * Gets the byte value at the specified byte <code>index</code>.
@@ -57,5 +61,5 @@
      * @throws IOException if the byte is unable to be read
      */
-    protected abstract byte getByte(int index) throws IOException;
+    public abstract byte getByte(int index) throws IOException;
 
     /**
@@ -89,9 +93,9 @@
      * Returns the length of the data source in bytes.
      * <p>
-     * This is a simple operation for implementations (such as {@link RandomAccessFileReader} and
+     * This is a simple operation for implementations (such as
      * {@link ByteArrayReader}) that have the entire data source available.
      * <p>
-     * Users of this method must be aware that sequentially accessed implementations such as
-     * {@link RandomAccessStreamReader} will have to read and buffer the entire data source in
+     * Users of this method must be aware that sequentially accessed implementations
+     * will have to read and buffer the entire data source in
      * order to determine the length.
      *
@@ -207,10 +211,10 @@
         if (_isMotorolaByteOrder) {
             // Motorola - MSB first
-            return (short) (((short)getByte(index    ) << 8 & (short)0xFF00) |
-                            ((short)getByte(index + 1)      & (short)0xFF));
+            return (short) ((getByte(index    ) << 8 & (short)0xFF00) |
+                            (getByte(index + 1)      & (short)0xFF));
         } else {
             // Intel ordering - LSB first
-            return (short) (((short)getByte(index + 1) << 8 & (short)0xFF00) |
-                            ((short)getByte(index    )      & (short)0xFF));
+            return (short) ((getByte(index + 1) << 8 & (short)0xFF00) |
+                            (getByte(index    )      & (short)0xFF));
         }
     }
@@ -229,12 +233,12 @@
         if (_isMotorolaByteOrder) {
             // Motorola - MSB first (big endian)
-            return (((int)getByte(index    )) << 16 & 0xFF0000) |
-                   (((int)getByte(index + 1)) << 8  & 0xFF00) |
-                   (((int)getByte(index + 2))       & 0xFF);
+            return ((getByte(index    )) << 16 & 0xFF0000) |
+                   ((getByte(index + 1)) << 8  & 0xFF00) |
+                   ((getByte(index + 2))       & 0xFF);
         } else {
             // Intel ordering - LSB first (little endian)
-            return (((int)getByte(index + 2)) << 16 & 0xFF0000) |
-                   (((int)getByte(index + 1)) << 8  & 0xFF00) |
-                   (((int)getByte(index    ))       & 0xFF);
+            return ((getByte(index + 2)) << 16 & 0xFF0000) |
+                   ((getByte(index + 1)) << 8  & 0xFF00) |
+                   ((getByte(index    ))       & 0xFF);
         }
     }
@@ -256,5 +260,5 @@
                    (((long)getByte(index + 1)) << 16 & 0xFF0000L) |
                    (((long)getByte(index + 2)) << 8  & 0xFF00L) |
-                   (((long)getByte(index + 3))       & 0xFFL);
+                   ((getByte(index + 3))       & 0xFFL);
         } else {
             // Intel ordering - LSB first (little endian)
@@ -262,5 +266,5 @@
                    (((long)getByte(index + 2)) << 16 & 0xFF0000L) |
                    (((long)getByte(index + 1)) << 8  & 0xFF00L) |
-                   (((long)getByte(index    ))       & 0xFFL);
+                   ((getByte(index    ))       & 0xFFL);
         }
     }
@@ -312,5 +316,5 @@
                    ((long)getByte(index + 5) << 16 & 0xFF0000L) |
                    ((long)getByte(index + 6) << 8  & 0xFF00L) |
-                   ((long)getByte(index + 7)       & 0xFFL);
+                   (getByte(index + 7)       & 0xFFL);
         } else {
             // Intel ordering - LSB first
@@ -322,5 +326,5 @@
                    ((long)getByte(index + 2) << 16 & 0xFF0000L) |
                    ((long)getByte(index + 1) << 8  & 0xFF00L) |
-                   ((long)getByte(index    )       & 0xFFL);
+                   (getByte(index    )       & 0xFFL);
         }
     }
@@ -365,11 +369,17 @@
 
     @NotNull
-    public String getString(int index, int bytesRequested) throws IOException
-    {
-        return new String(getBytes(index, bytesRequested));
-    }
-
-    @NotNull
-    public String getString(int index, int bytesRequested, String charset) throws IOException
+    public StringValue getStringValue(int index, int bytesRequested, @Nullable Charset charset) throws IOException
+    {
+        return new StringValue(getBytes(index, bytesRequested), charset);
+    }
+
+    @NotNull
+    public String getString(int index, int bytesRequested, @NotNull Charset charset) throws IOException
+    {
+        return new String(getBytes(index, bytesRequested), charset.name());
+    }
+
+    @NotNull
+    public String getString(int index, int bytesRequested, @NotNull String charset) throws IOException
     {
         byte[] bytes = getBytes(index, bytesRequested);
@@ -392,16 +402,43 @@
      */
     @NotNull
-    public String getNullTerminatedString(int index, int maxLengthBytes) throws IOException
-    {
-        // NOTE currently only really suited to single-byte character strings
-
-        byte[] bytes = getBytes(index, maxLengthBytes);
+    public String getNullTerminatedString(int index, int maxLengthBytes, @NotNull Charset charset) throws IOException
+    {
+        return new String(getNullTerminatedBytes(index, maxLengthBytes), charset.name());
+    }
+
+    @NotNull
+    public StringValue getNullTerminatedStringValue(int index, int maxLengthBytes, @Nullable Charset charset) throws IOException
+    {
+        byte[] bytes = getNullTerminatedBytes(index, maxLengthBytes);
+
+        return new StringValue(bytes, charset);
+    }
+
+    /**
+     * Returns the sequence of bytes punctuated by a <code>\0</code> value.
+     *
+     * @param index The index within the buffer at which to start reading the string.
+     * @param maxLengthBytes The maximum number of bytes to read. If a <code>\0</code> byte is not reached within this limit,
+     * the returned array will be <code>maxLengthBytes</code> long.
+     * @return The read byte array, excluding the null terminator.
+     * @throws IOException The buffer does not contain enough bytes to satisfy this request.
+     */
+    @NotNull
+    public byte[] getNullTerminatedBytes(int index, int maxLengthBytes) throws IOException
+    {
+        byte[] buffer = getBytes(index, maxLengthBytes);
 
         // Count the number of non-null bytes
         int length = 0;
-        while (length < bytes.length && bytes[length] != '\0')
+        while (length < buffer.length && buffer[length] != 0)
             length++;
 
-        return new String(bytes, 0, length);
+        if (length == maxLengthBytes)
+            return buffer;
+
+        byte[] bytes = new byte[length];
+        if (length > 0)
+            System.arraycopy(buffer, 0, bytes, 0, length);
+        return bytes;
     }
 }
