Rename TarUtils.getXXX methods as formatXXX
Update Javadoc
Throw IllegalArgumentException if value won't fit in buffer
Treat long values as unsigned
Use String instead of StringBuffer for names etc

git-svn-id: https://svn.apache.org/repos/asf/commons/proper/compress/trunk@761372 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/main/java/org/apache/commons/compress/archivers/tar/TarArchiveEntry.java b/src/main/java/org/apache/commons/compress/archivers/tar/TarArchiveEntry.java
index 91283a8..fdba86c 100644
--- a/src/main/java/org/apache/commons/compress/archivers/tar/TarArchiveEntry.java
+++ b/src/main/java/org/apache/commons/compress/archivers/tar/TarArchiveEntry.java
@@ -82,7 +82,7 @@
 
 public class TarArchiveEntry implements TarConstants, ArchiveEntry {
     /** The entry's name. */
-    private StringBuffer name;
+    private String name;
 
     /** The entry's permission mode. */
     private int mode;
@@ -103,16 +103,16 @@
     private byte linkFlag;
 
     /** The entry's link name. */
-    private StringBuffer linkName;
+    private String linkName;
 
     /** The entry's magic tag. */
-    private StringBuffer magic;
+    private String magic;
 
     /** The entry's user name. */
-    private StringBuffer userName;
+    private String userName;
 
     /** The entry's group name. */
-    private StringBuffer groupName;
+    private String groupName;
 
     /** The entry's major device number. */
     private int devMajor;
@@ -139,9 +139,9 @@
      * Construct an empty entry and prepares the header values.
      */
     private TarArchiveEntry () {
-        this.magic = new StringBuffer(MAGIC_POSIX);
-        this.name = new StringBuffer();
-        this.linkName = new StringBuffer();
+        this.magic = MAGIC_POSIX;
+        this.name = "";
+        this.linkName = "";
 
         String user = System.getProperty("user.name", "");
 
@@ -151,8 +151,8 @@
 
         this.userId = 0;
         this.groupId = 0;
-        this.userName = new StringBuffer(user);
-        this.groupName = new StringBuffer("");
+        this.userName = user;
+        this.groupName = "";
         this.file = null;
     }
 
@@ -170,16 +170,16 @@
 
         this.devMajor = 0;
         this.devMinor = 0;
-        this.name = new StringBuffer(name);
+        this.name = name;
         this.mode = isDir ? DEFAULT_DIR_MODE : DEFAULT_FILE_MODE;
         this.linkFlag = isDir ? LF_DIR : LF_NORMAL;
         this.userId = 0;
         this.groupId = 0;
         this.size = 0;
         this.modTime = (new Date()).getTime() / MILLIS_PER_SECOND;
-        this.linkName = new StringBuffer("");
-        this.userName = new StringBuffer("");
-        this.groupName = new StringBuffer("");
+        this.linkName = "";
+        this.userName = "";
+        this.groupName = "";
         this.devMajor = 0;
         this.devMinor = 0;
 
@@ -219,22 +219,24 @@
 
         this.file = file;
 
-        this.linkName = new StringBuffer("");
-        this.name = new StringBuffer(fileName);
+        this.linkName = "";
 
         if (file.isDirectory()) {
             this.mode = DEFAULT_DIR_MODE;
             this.linkFlag = LF_DIR;
 
-            int nameLength = name.length();
+            int nameLength = fileName.length();
             if (nameLength == 0 || name.charAt(nameLength - 1) != '/') {
-                this.name.append("/");
+                this.name = fileName + "/";
+            } else {
+                this.name = fileName;                
             }
             this.size = 0;
         } else {
             this.mode = DEFAULT_FILE_MODE;
             this.linkFlag = LF_NORMAL;
             this.size = file.length();
+            this.name = fileName;
         }
 
         this.modTime = file.lastModified() / MILLIS_PER_SECOND;
@@ -314,7 +316,7 @@
      * @param name This entry's new name.
      */
     public void setName(String name) {
-        this.name = new StringBuffer(normalizeFileName(name));
+        this.name = normalizeFileName(name);
     }
 
     /**
@@ -386,7 +388,7 @@
      * @param userName This entry's new user name.
      */
     public void setUserName(String userName) {
-        this.userName = new StringBuffer(userName);
+        this.userName = userName;
     }
 
     /**
@@ -404,7 +406,7 @@
      * @param groupName This entry's new group name.
      */
     public void setGroupName(String groupName) {
-        this.groupName = new StringBuffer(groupName);
+        this.groupName = groupName;
     }
 
     /**
@@ -559,12 +561,12 @@
     public void writeEntryHeader(byte[] outbuf) {
         int offset = 0;
 
-        offset = TarUtils.getNameBytes(name, outbuf, offset, NAMELEN);
-        offset = TarUtils.getOctalBytes(mode, outbuf, offset, MODELEN);
-        offset = TarUtils.getOctalBytes(userId, outbuf, offset, UIDLEN);
-        offset = TarUtils.getOctalBytes(groupId, outbuf, offset, GIDLEN);
-        offset = TarUtils.getLongOctalBytes(size, outbuf, offset, SIZELEN);
-        offset = TarUtils.getLongOctalBytes(modTime, outbuf, offset, MODTIMELEN);
+        offset = TarUtils.formatNameBytes(name, outbuf, offset, NAMELEN);
+        offset = TarUtils.formatOctalBytes(mode, outbuf, offset, MODELEN);
+        offset = TarUtils.formatOctalBytes(userId, outbuf, offset, UIDLEN);
+        offset = TarUtils.formatOctalBytes(groupId, outbuf, offset, GIDLEN);
+        offset = TarUtils.formatLongOctalBytes(size, outbuf, offset, SIZELEN);
+        offset = TarUtils.formatLongOctalBytes(modTime, outbuf, offset, MODTIMELEN);
 
         int csOffset = offset;
 
@@ -573,12 +575,12 @@
         }
 
         outbuf[offset++] = linkFlag;
-        offset = TarUtils.getNameBytes(linkName, outbuf, offset, NAMELEN);
-        offset = TarUtils.getNameBytes(magic, outbuf, offset, MAGICLEN);
-        offset = TarUtils.getNameBytes(userName, outbuf, offset, UNAMELEN);
-        offset = TarUtils.getNameBytes(groupName, outbuf, offset, GNAMELEN);
-        offset = TarUtils.getOctalBytes(devMajor, outbuf, offset, DEVLEN);
-        offset = TarUtils.getOctalBytes(devMinor, outbuf, offset, DEVLEN);
+        offset = TarUtils.formatNameBytes(linkName, outbuf, offset, NAMELEN);
+        offset = TarUtils.formatNameBytes(magic, outbuf, offset, MAGICLEN);
+        offset = TarUtils.formatNameBytes(userName, outbuf, offset, UNAMELEN);
+        offset = TarUtils.formatNameBytes(groupName, outbuf, offset, GNAMELEN);
+        offset = TarUtils.formatOctalBytes(devMajor, outbuf, offset, DEVLEN);
+        offset = TarUtils.formatOctalBytes(devMinor, outbuf, offset, DEVLEN);
 
         while (offset < outbuf.length) {
             outbuf[offset++] = 0;
@@ -586,7 +588,7 @@
 
         long chk = TarUtils.computeCheckSum(outbuf);
 
-        TarUtils.getCheckSumOctalBytes(chk, outbuf, csOffset, CHKSUMLEN);
+        TarUtils.formatCheckSumOctalBytes(chk, outbuf, csOffset, CHKSUMLEN);
     }
 
     /**
diff --git a/src/main/java/org/apache/commons/compress/archivers/tar/TarUtils.java b/src/main/java/org/apache/commons/compress/archivers/tar/TarUtils.java
index bfb8e9e..8df868a 100644
--- a/src/main/java/org/apache/commons/compress/archivers/tar/TarUtils.java
+++ b/src/main/java/org/apache/commons/compress/archivers/tar/TarUtils.java
@@ -41,30 +41,35 @@
      * @param length The maximum number of bytes to parse.
      * @return The long value of the octal string.
      */
-    public static long parseOctal(byte[] buffer, int offset, int length) {
+    public static long parseOctal(byte[] buffer, final int offset, final int length) {
         long    result = 0;
         boolean stillPadding = true;
         int     end = offset + length;
 
         for (int i = offset; i < end; ++i) {
-            if (buffer[i] == 0) { // Found trailing null
+            final byte currentByte = buffer[i];
+            if (currentByte == 0) { // Found trailing null
                 break;
             }
 
             // Ignore leading spaces ('0' can be ignored anyway)
-            if (buffer[i] == (byte) ' ' || buffer[i] == '0') {
+            if (currentByte == (byte) ' ' || currentByte == '0') {
                 if (stillPadding) {
                     continue;
                 }
 
-                if (buffer[i] == (byte) ' ') { // Found trailing space
+                if (currentByte == (byte) ' ') { // Found trailing space
                     break;
                 }
             }
 
             stillPadding = false;
             // CheckStyle:MagicNumber OFF
-            result = (result << 3) + (buffer[i] - '0');// TODO needs to reject invalid bytes
+            if (currentByte < '0' || currentByte > '7'){
+                throw new IllegalArgumentException(
+                        "Invalid octal digit at position "+i+" in '"+new String(buffer, offset, length)+"'");
+            }
+            result = (result << 3) + (currentByte - '0');// TODO needs to reject invalid bytes
             // CheckStyle:MagicNumber ON
         }
 
@@ -81,7 +86,7 @@
      * @param length The maximum number of bytes to parse.
      * @return The entry name.
      */
-    public static StringBuffer parseName(byte[] buffer, int offset, int length) {
+    public static String parseName(byte[] buffer, final int offset, final int length) {
         StringBuffer result = new StringBuffer(length);
         int          end = offset + length;
 
@@ -93,7 +98,7 @@
             result.append((char) buffer[i]);
         }
 
-        return result;
+        return result.toString();
     }
 
     /**
@@ -111,7 +116,7 @@
      * @param length The maximum number of header bytes to copy.
      * @return The updated offset, i.e. offset + length
      */
-    public static int getNameBytes(StringBuffer name, byte[] buf, int offset, int length) {
+    public static int formatNameBytes(String name, byte[] buf, final int offset, final int length) {
         int i;
 
         // copy until end of input or output is reached.
@@ -128,51 +133,54 @@
     }
 
     /**
-     * Fill buffer with octal number, with leading zeroes
+     * Fill buffer with unsigned octal number, padded with leading zeroes.
      * 
-     * The output for negative numbers is not specified,
-     * but currently the method returns a buffer filled with zeros.
-     * This may change.
-     * 
-     * @param value number to convert to octal (assumed >=0)
+     * @param value number to convert to octal - treated as unsigned
      * @param buffer destination buffer
      * @param offset starting offset in buffer
      * @param length length of buffer to fill
+     * @throws IllegalArgumentException if the value will not fit in the buffer
      */
-    public static void formatUnsignedOctalString(long value, byte[] buffer,
-            int offset, int length) {
-        length--;
+    public static void formatUnsignedOctalString(final long value, byte[] buffer,
+            final int offset, final int length) {
+        int remaining = length;
+        remaining--;
         if (value == 0) {
-            buffer[offset + length--] = (byte) '0';
+            buffer[offset + remaining--] = (byte) '0';
         } else {
-            for (long val = value; length >= 0 && val > 0; --length) {
+            long val = value;
+            for (; remaining >= 0 && val != 0; --remaining) {
                 // CheckStyle:MagicNumber OFF
-                buffer[offset + length] = (byte) ((byte) '0' + (byte) (val & 7));
-                val = val >> 3;
+                buffer[offset + remaining] = (byte) ((byte) '0' + (byte) (val & 7));
+                val = val >>> 3;
                 // CheckStyle:MagicNumber ON
             }
+            if (val != 0){
+                throw new IllegalArgumentException
+                (value+"="+Long.toOctalString(value)+ " will not fit in octal number buffer of length "+length);
+            }
         }
 
-        for (; length >= 0; --length) { // leading zeros
-            buffer[offset + length] = (byte) '0';
+        for (; remaining >= 0; --remaining) { // leading zeros
+            buffer[offset + remaining] = (byte) '0';
         }
     }
 
     /**
      * Write an octal integer into a buffer.
      *
-     * Adds a trailing space and NUL to end of the buffer.
-     * [Appears to be standard for V7 Unix BSD]
-     * Converts the long value (assumed positive) to the buffer.
-     * Adds leading zeros to the buffer.
+     * Uses {@link #formatUnsignedOctalString} to format
+     * the value as an octal string with leading zeros.
+     * The converted number is followed by space and NUL
      * 
      * @param value The value to write
      * @param buf The buffer to receive the output
      * @param offset The starting offset into the buffer
      * @param length The size of the output buffer
      * @return The updated offset, i.e offset+length
+     * @throws IllegalArgumentException if the value (and trailer) will not fit in the buffer
      */
-    public static int getOctalBytes(long value, byte[] buf, int offset, int length) {
+    public static int formatOctalBytes(final long value, byte[] buf, final int offset, final int length) {
 
         int idx=length-2; // For space and trailing null
         formatUnsignedOctalString(value, buf, offset, idx);
@@ -185,17 +193,19 @@
 
     /**
      * Write an octal long integer into a buffer.
-     * Converts the long value (assumed positive) to the buffer.
-     * Adds leading zeros to the buffer.
-     * The buffer is terminated with a space.
+     * 
+     * Uses {@link #formatUnsignedOctalString} to format
+     * the value as an octal string with leading zeros.
+     * The converted number is followed by a space.
      * 
      * @param value The value to write as octal
      * @param buf The destinationbuffer.
      * @param offset The starting offset into the buffer.
      * @param length The length of the buffer
      * @return The updated offset
+     * @throws IllegalArgumentException if the value (and trailer) will not fit in the buffer
      */
-    public static int getLongOctalBytes(long value, byte[] buf, int offset, int length) {
+    public static int formatLongOctalBytes(final long value, byte[] buf, final int offset, final int length) {
 
         int idx=length-1; // For space
         
@@ -207,18 +217,19 @@
 
     /**
      * Writes an octal value into a buffer.
-     *
-     * Converts the long value (assumed positive) to the buffer.
-     * Adds leading zeros to the buffer.
-     * Checksum is followed by NUL and then space.
+     * 
+     * Uses {@link #formatUnsignedOctalString} to format
+     * the value as an octal string with leading zeros.
+     * The converted number is followed by NUL and then space.
      *
      * @param value The value to convert
      * @param buf The destination buffer
      * @param offset The starting offset into the buffer.
      * @param length The size of the buffer.
      * @return The updated value of offset, i.e. offset+length
+     * @throws IllegalArgumentException if the value (and trailer) will not fit in the buffer
      */
-    public static int getCheckSumOctalBytes(long value, byte[] buf, int offset, int length) {
+    public static int formatCheckSumOctalBytes(final long value, byte[] buf, final int offset, final int length) {
 
         int idx=length-2; // for NUL and space
         formatUnsignedOctalString(value, buf, offset, idx);
@@ -235,7 +246,7 @@
      * @param buf The tar entry's header buffer.
      * @return The computed checksum.
      */
-    public static long computeCheckSum(byte[] buf) {
+    public static long computeCheckSum(final byte[] buf) {
         long sum = 0;
 
         for (int i = 0; i < buf.length; ++i) {
diff --git a/src/test/java/org/apache/commons/compress/archivers/tar/TarUtilsTest.java b/src/test/java/org/apache/commons/compress/archivers/tar/TarUtilsTest.java
index ee9257f..0284b3b 100644
--- a/src/test/java/org/apache/commons/compress/archivers/tar/TarUtilsTest.java
+++ b/src/test/java/org/apache/commons/compress/archivers/tar/TarUtilsTest.java
@@ -25,18 +25,18 @@
     
     public void testName(){
         byte [] buff = new byte[20];
-        StringBuffer sb1 = new StringBuffer("abcdefghijklmnopqrstuvwxyz");
-        int off = TarUtils.getNameBytes(sb1, buff, 1, buff.length-1);
+        String sb1 = "abcdefghijklmnopqrstuvwxyz";
+        int off = TarUtils.formatNameBytes(sb1, buff, 1, buff.length-1);
         assertEquals(off, 20);
-        StringBuffer sb2 = TarUtils.parseName(buff, 1, 10);
-        assertEquals(sb2.toString(),sb1.substring(0,10));
+        String sb2 = TarUtils.parseName(buff, 1, 10);
+        assertEquals(sb2,sb1.substring(0,10));
         sb2 = TarUtils.parseName(buff, 1, 19);
-        assertEquals(sb2.toString(),sb1.substring(0,19));
+        assertEquals(sb2,sb1.substring(0,19));
         buff = new byte[30];
-        off = TarUtils.getNameBytes(sb1, buff, 1, buff.length-1);
+        off = TarUtils.formatNameBytes(sb1, buff, 1, buff.length-1);
         assertEquals(off, 30);
         sb2 = TarUtils.parseName(buff, 1, buff.length-1);
-        assertEquals(sb1.toString(), sb2.toString());
+        assertEquals(sb1, sb2);
     }
     
     private void fillBuff(byte []buffer, String input){
@@ -61,14 +61,17 @@
         value = TarUtils.parseOctal(buffer,0, 11);
         assertEquals(077777777777L, value);
         fillBuff(buffer, "abcdef"); // Invalid input
-        value = TarUtils.parseOctal(buffer,0, 11);
-//        assertEquals(0, value); // Or perhaps an Exception?
+        try {
+            value = TarUtils.parseOctal(buffer,0, 11);
+            fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException expected) {
+        }
     }
     
     private void checkRoundTripOctal(final long value) {
         byte [] buffer = new byte[12];
         long parseValue;
-        TarUtils.getLongOctalBytes(value, buffer, 0, buffer.length);
+        TarUtils.formatLongOctalBytes(value, buffer, 0, buffer.length);
         parseValue = TarUtils.parseOctal(buffer,0, buffer.length);
         assertEquals(value,parseValue);
     }
@@ -84,24 +87,33 @@
     // Check correct trailing bytes are generated
     public void testTrailers() {
         byte [] buffer = new byte[12];
-        TarUtils.getLongOctalBytes(123, buffer, 0, buffer.length);
+        TarUtils.formatLongOctalBytes(123, buffer, 0, buffer.length);
         assertEquals(' ', buffer[buffer.length-1]);
         assertEquals('3', buffer[buffer.length-2]); // end of number
-        TarUtils.getOctalBytes(123, buffer, 0, buffer.length);
+        TarUtils.formatOctalBytes(123, buffer, 0, buffer.length);
         assertEquals(0  , buffer[buffer.length-1]);
         assertEquals(' ', buffer[buffer.length-2]);
         assertEquals('3', buffer[buffer.length-3]); // end of number
-        TarUtils.getCheckSumOctalBytes(123, buffer, 0, buffer.length);
+        TarUtils.formatCheckSumOctalBytes(123, buffer, 0, buffer.length);
         assertEquals(' ', buffer[buffer.length-1]);
         assertEquals(0  , buffer[buffer.length-2]);
         assertEquals('3', buffer[buffer.length-3]); // end of number
     }
     
     public void testNegative() {
-        byte [] buffer = new byte[10];
+        byte [] buffer = new byte[22];
         TarUtils.formatUnsignedOctalString(-1, buffer, 0, buffer.length);
-        // Currently negative numbers generate all zero buffer. This may need to change.
-        assertEquals("0000000000", new String(buffer));
-        
+        assertEquals("1777777777777777777777", new String(buffer));
+    }
+
+    public void testOverflow() {
+        byte [] buffer = new byte[8-1]; // a lot of the numbers have 8-byte buffers (nul term)
+        TarUtils.formatUnsignedOctalString(07777777L, buffer, 0, buffer.length);
+        assertEquals("7777777", new String(buffer));        
+        try {
+            TarUtils.formatUnsignedOctalString(017777777L, buffer, 0, buffer.length);
+            fail("Should have cause IllegalArgumentException");
+        } catch (IllegalArgumentException expected) {
+        }
     }
 }