[Jython-checkins] jython: Implement bytearray.fromhex and strengthen the test.

frank.wierzbicki jython-checkins at python.org
Wed Jun 13 20:44:04 CEST 2012


http://hg.python.org/jython/rev/0ca0f51a32d6
changeset:   6702:0ca0f51a32d6
user:        Jeff Allen <ja...py at farowl.co.uk>
date:        Wed Jun 06 12:52:02 2012 +0100
summary:
  Implement bytearray.fromhex and strengthen the test.
I beefed-up test_fromhex() in test_bytes.py to detect potential signed-byte problems. Now scoring 2 failures and 33 errors.

files:
  Lib/test/test_bytes.py               |   6 +-
  src/org/python/core/BaseBytes.java   |  80 +++++++++++++++-
  src/org/python/core/PyByteArray.java |  49 +++++++++-
  3 files changed, 125 insertions(+), 10 deletions(-)


diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py
--- a/Lib/test/test_bytes.py
+++ b/Lib/test/test_bytes.py
@@ -260,9 +260,9 @@
         self.assertRaises(TypeError, self.type2test.fromhex)
         self.assertRaises(TypeError, self.type2test.fromhex, 1)
         self.assertEqual(self.type2test.fromhex(u''), self.type2test())
-        b = bytearray([0x1a, 0x2b, 0x30])
-        self.assertEqual(self.type2test.fromhex(u'1a2B30'), b)
-        self.assertEqual(self.type2test.fromhex(u'  1A 2B  30   '), b)
+        b = bytearray([0x1a, 0x2b, 0x30, 0xca, 0xfe, 0xba, 0xbe]) # challenging signs
+        self.assertEqual(self.type2test.fromhex(u'1a2B30CafEBabe'), b)
+        self.assertEqual(self.type2test.fromhex(u'  1A 2B  30 CafeBabe   '), b)
         self.assertEqual(self.type2test.fromhex(u'0000'), b'\0\0')
         self.assertRaises(ValueError, self.type2test.fromhex, u'a')
         self.assertRaises(ValueError, self.type2test.fromhex, u'rt')
diff --git a/src/org/python/core/BaseBytes.java b/src/org/python/core/BaseBytes.java
--- a/src/org/python/core/BaseBytes.java
+++ b/src/org/python/core/BaseBytes.java
@@ -2303,6 +2303,80 @@
     }
 
     /**
+     * Almost ready-to-expose implementation of Python class method <code>fromhex(string)</code>.
+     * This assigns a value to the passed byte array object from a string of two-digit hexadecimal
+     * numbers. Spaces (but not whitespace in general) are acceptable around the numbers, not
+     * within. Non-hexadecimal characters or un-paired hex digits raise a <code>ValueError</code>.
+     * Example:
+     *
+     * <pre>
+     * bytearray.fromhex('B9 01EF') -> * bytearray(b'\xb9\x01\xef')."
+     * </pre>
+     *
+     * @param result to receive the decoded values
+     * @param hex specification of the bytes
+     * @throws PyException(ValueError) if non-hex characters, or isolated ones, are encountered
+     */
+    static void basebytes_fromhex(BaseBytes result, String hex) throws PyException {
+
+        final int hexlen = hex.length();
+        result.newStorage(hexlen / 2); // Over-provides storage if hex has spaces
+
+        // We might produce a ValueError with this message.
+        String fmt = "non-hexadecimal number found in fromhex() arg at position %d";
+
+        // Output pointer in the result array
+        byte[] r = result.storage;
+        int p = result.offset;
+
+        /*
+         * When charAt(i) is a hex digit, we will always access hex.charAt(i+1), and catch the
+         * exception if that is beyond the end of the array.
+         */
+        for (int i = 0; i < hexlen; /* i incremented in loop by 1 or 2 */) {
+            char c = hex.charAt(i++);
+            if (c != ' ') {
+                try {
+                    // hexDigit throws IllegalArgumentException if non-hexadecimal character found
+                    int value = hexDigit(c);
+                    c = hex.charAt(i++); // Throw IndexOutOfBoundsException if no second digit
+                    value = (value << 4) + hexDigit(c);
+                    r[p++] = (byte)value;
+                } catch (IllegalArgumentException e) {
+                    throw Py.ValueError(String.format(fmt, i-1));
+                } catch (IndexOutOfBoundsException e) {
+                    throw Py.ValueError(String.format(fmt, i-2));
+                }
+            }
+        }
+        result.size = p - result.offset;
+    }
+
+    /**
+     * Translate one character to its hexadecimal value.
+     *
+     * @param c to translate
+     * @return value 0-15
+     * @throws IllegalArgumentException if c is not '0-'9', 'A'-'F' or 'a'-'f'.
+     */
+    private static int hexDigit(char c) throws IllegalArgumentException {
+        int result = c - '0';
+        if (result >= 0) {
+            if (result < 10) { // digit
+                return result;
+            } else {
+                // If c is a letter, c & 0xDF is its uppercase.
+                // If c is not a letter c & 0xDF is still not a letter.
+                result = (c & 0xDF) - 'A';
+                if (result >= 0 && result < 6) { // A-F or a-f
+                    return result + 10;
+                }
+            }
+        }
+        throw new IllegalArgumentException();
+    }
+
+    /**
      * Almost ready-to-expose implementation of Python <code>join(iterable)</code>.
      *
      * @param iter iterable of objects capable of being regarded as byte arrays
@@ -3157,7 +3231,7 @@
     /**
      * Implementation of Python <code>splitlines()</code>, returning a list of the lines in the byte
      * array, breaking at line boundaries. Line breaks are not included in the resulting segments.
-     * 
+     *
      * @return List of segments
      */
     public PyList splitlines() {
@@ -3168,7 +3242,7 @@
      * Implementation of Python <code>splitlines(keepends)</code>, returning a list of the lines in
      * the string, breaking at line boundaries. Line breaks are not included in the resulting list
      * unless <code>keepends</code> is true.
-     * 
+     *
      * @param keepends if true, include the end of line bytes(s)
      * @return PyList of segments
      */
@@ -3180,7 +3254,7 @@
      * Ready-to-expose implementation of Python <code>splitlines(keepends)</code>, returning a list
      * of the lines in the string, breaking at line boundaries. Line breaks are not included in the
      * resulting list unless keepends is given and true.
-     * 
+     *
      * @param keepends if true, include the end of line bytes(s)
      * @return List of segments
      */
diff --git a/src/org/python/core/PyByteArray.java b/src/org/python/core/PyByteArray.java
--- a/src/org/python/core/PyByteArray.java
+++ b/src/org/python/core/PyByteArray.java
@@ -2,6 +2,7 @@
 
 import java.util.Arrays;
 
+import org.python.expose.ExposedClassMethod;
 import org.python.expose.ExposedMethod;
 import org.python.expose.ExposedNew;
 import org.python.expose.ExposedType;
@@ -161,11 +162,24 @@
     /**
      * Construct bytearray by re-using an array of byte as storage initialised by the client.
      *
-     * @param newStorage pre-initialised storage: the caller should not keep a reference
+     * @param storage pre-initialised with desired value: the caller should not keep a reference
      */
-    PyByteArray(byte[] newStorage) {
+    PyByteArray(byte[] storage) {
         super(TYPE);
-        setStorage(newStorage);
+        setStorage(storage);
+    }
+
+    /**
+     * Construct bytearray by re-using an array of byte as storage initialised by the client.
+     *
+     * @param storage pre-initialised with desired value: the caller should not keep a reference
+     * @param size number of bytes actually used
+     * @throws IllegalArgumentException if the range [0:size] is not within the array bounds of
+     *             the storage.
+     */
+    PyByteArray(byte[] storage, int size) {
+        super(TYPE);
+        setStorage(storage, size);
     }
 
     /**
@@ -1054,6 +1068,33 @@
         return basebytes_find(sub, start, end);
     }
 
+    /**
+     * Implementation of Python class method <code>bytearray.fromhex(string)</code>, that returns .
+     * a new <code>PyByteArray</code> with a value taken from a string of two-digit hexadecimal
+     * numbers. Spaces (but not whitespace in general) are acceptable around the numbers, not
+     * within. Non-hexadecimal characters or un-paired hex digits raise a <code>ValueError</code>. *
+     * Example:
+     *
+     * <pre>
+     * bytearray.fromhex('B9 01EF') -> * bytearray(b'\xb9\x01\xef')."
+     * </pre>
+     *
+     * @param hex specification of the bytes
+     * @throws PyException(ValueError) if non-hex characters, or isolated ones, are encountered
+     */
+    static PyByteArray fromhex(String hex) throws PyException {
+        return bytearray_fromhex(TYPE, hex);
+    }
+
+    @ExposedClassMethod(doc = BuiltinDocs.bytearray_fromhex_doc)
+    static PyByteArray bytearray_fromhex(PyType type, String hex) {
+        // I think type tells us the actual class but we always return exactly a bytearray
+        // PyObject ba = type.__call__();
+        PyByteArray result = new PyByteArray();
+        basebytes_fromhex(result, hex);
+        return result;
+    }
+
     @Override
     public PyObject __iadd__(PyObject o) {
         return bytearray___iadd__(o);
@@ -1455,7 +1496,7 @@
     final PyList bytearray_splitlines(boolean keepends) {
         return basebytes_splitlines(keepends);
     }
-    
+
     /**
      * Implementation of Python <code>startswith(prefix)</code>.
      *

-- 
Repository URL: http://hg.python.org/jython


More information about the Jython-checkins mailing list