From ac765721b6b49c2d0ca957bfbc576b7ad52b04b8 Mon Sep 17 00:00:00 2001
From: Chris Larsen <clarsen@yahoo-inc.com>
Date: Wed, 16 Mar 2016 20:57:50 -0700
Subject: [PATCH] [core] Add byte to double and long conversion methods to
 Utils. These will be used for #653 to write timestamps and numeric values to
 the ByteIterator maps.

---
 core/src/main/java/com/yahoo/ycsb/Utils.java  |  63 ++++++++-
 .../test/java/com/yahoo/ycsb/TestUtils.java   | 129 ++++++++++++++++++
 2 files changed, 191 insertions(+), 1 deletion(-)
 create mode 100644 core/src/test/java/com/yahoo/ycsb/TestUtils.java

diff --git a/core/src/main/java/com/yahoo/ycsb/Utils.java b/core/src/main/java/com/yahoo/ycsb/Utils.java
index f49bc0f5..5fe699af 100644
--- a/core/src/main/java/com/yahoo/ycsb/Utils.java
+++ b/core/src/main/java/com/yahoo/ycsb/Utils.java
@@ -1,5 +1,5 @@
 /**                                                                                                                                                                                
- * Copyright (c) 2010 Yahoo! Inc. All rights reserved.                                                                                                                             
+ * Copyright (c) 2010 Yahoo! Inc., 2016 YCSB contributors. All rights reserved.                                                                                                                             
  *                                                                                                                                                                                 
  * Licensed under the Apache License, Version 2.0 (the "License"); you                                                                                                             
  * may not use this file except in compliance with the License. You                                                                                                                
@@ -113,4 +113,65 @@ public class Utils
 	 }
 	 return Math.abs(hashval);
       }
+
+      /**
+       * Reads a big-endian 8-byte long from an offset in the given array.
+       * @param bytes The array to read from.
+       * @return A long integer.
+       * @throws IndexOutOfBoundsException if the byte array is too small.
+       * @throws NullPointerException if the byte array is null.
+       */
+      public static long bytesToLong(final byte[] bytes) {
+        return (bytes[0] & 0xFFL) << 56
+             | (bytes[1] & 0xFFL) << 48
+             | (bytes[2] & 0xFFL) << 40
+             | (bytes[3] & 0xFFL) << 32
+             | (bytes[4] & 0xFFL) << 24
+             | (bytes[5] & 0xFFL) << 16
+             | (bytes[6] & 0xFFL) << 8
+             | (bytes[7] & 0xFFL) << 0;
+      }
+      
+      /**
+       * Writes a big-endian 8-byte long at an offset in the given array.
+       * @param val The value to encode.
+       * @throws IndexOutOfBoundsException if the byte array is too small.
+       */
+      public static byte[] longToBytes(final long val) {
+        final byte[] bytes = new byte[8];
+        bytes[0] = (byte) (val >>> 56);
+        bytes[1] = (byte) (val >>> 48);
+        bytes[2] = (byte) (val >>> 40);
+        bytes[3] = (byte) (val >>> 32);
+        bytes[4] = (byte) (val >>> 24);
+        bytes[5] = (byte) (val >>> 16);
+        bytes[6] = (byte) (val >>>  8);
+        bytes[7] = (byte) (val >>>  0);
+        return bytes;
+      }
+      
+      /**
+       * Parses the byte array into a double.
+       * The byte array must be at least 8 bytes long and have been encoded using 
+       * {@link #doubleToBytes}. If the array is longer than 8 bytes, only the
+       * first 8 bytes are parsed.
+       * @param bytes The byte array to parse, at least 8 bytes.
+       * @return A double value read from the byte array.
+       * @throws IllegalArgumentException if the byte array is not 8 bytes wide.
+       */
+      public static double bytesToDouble(final byte[] bytes) {
+        if (bytes.length < 8) {
+          throw new IllegalArgumentException("Byte array must be 8 bytes wide.");
+        }
+        return Double.longBitsToDouble(bytesToLong(bytes));
+      }
+      
+      /**
+       * Encodes the double value as an 8 byte array.
+       * @param val The double value to encode.
+       * @return A byte array of length 8.
+       */
+      public static byte[] doubleToBytes(final double val) {
+        return longToBytes(Double.doubleToRawLongBits(val));
+      }
 }
diff --git a/core/src/test/java/com/yahoo/ycsb/TestUtils.java b/core/src/test/java/com/yahoo/ycsb/TestUtils.java
new file mode 100644
index 00000000..cde51776
--- /dev/null
+++ b/core/src/test/java/com/yahoo/ycsb/TestUtils.java
@@ -0,0 +1,129 @@
+/**
+ * Copyright (c) 2016 YCSB contributors. All rights reserved.
+ *
+ * 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. See accompanying
+ * LICENSE file.
+ */
+
+package com.yahoo.ycsb;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Arrays;
+
+import org.testng.annotations.Test;
+
+public class TestUtils {
+
+  @Test
+  public void bytesToFromLong() throws Exception {
+    byte[] bytes = new byte[8];
+    assertEquals(Utils.bytesToLong(bytes), 0L);
+    assertArrayEquals(Utils.longToBytes(0), bytes);
+    
+    bytes[7] = 1;
+    assertEquals(Utils.bytesToLong(bytes), 1L);
+    assertArrayEquals(Utils.longToBytes(1L), bytes);
+    
+    bytes = new byte[] { 127, -1, -1, -1, -1, -1, -1, -1 };
+    assertEquals(Utils.bytesToLong(bytes), Long.MAX_VALUE);
+    assertArrayEquals(Utils.longToBytes(Long.MAX_VALUE), bytes);
+    
+    bytes = new byte[] { -128, 0, 0, 0, 0, 0, 0, 0 };
+    assertEquals(Utils.bytesToLong(bytes), Long.MIN_VALUE);
+    assertArrayEquals(Utils.longToBytes(Long.MIN_VALUE), bytes);
+    
+    bytes = new byte[] { (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, 
+        (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF  };
+    assertEquals(Utils.bytesToLong(bytes), -1L);
+    assertArrayEquals(Utils.longToBytes(-1L), bytes);
+    
+    // if the array is too long we just skip the remainder
+    bytes = new byte[] { 0, 0, 0, 0, 0, 0, 0, 1, 42, 42, 42 };
+    assertEquals(Utils.bytesToLong(bytes), 1L);
+  }
+  
+  @Test
+  public void bytesToFromDouble() throws Exception {
+    byte[] bytes = new byte[8];
+    assertEquals(Utils.bytesToDouble(bytes), 0, 0.0001);
+    assertArrayEquals(Utils.doubleToBytes(0), bytes);
+    
+    bytes = new byte[] { 63, -16, 0, 0, 0, 0, 0, 0 };
+    assertEquals(Utils.bytesToDouble(bytes), 1, 0.0001);
+    assertArrayEquals(Utils.doubleToBytes(1), bytes);
+    
+    bytes = new byte[] { -65, -16, 0, 0, 0, 0, 0, 0 };
+    assertEquals(Utils.bytesToDouble(bytes), -1, 0.0001);
+    assertArrayEquals(Utils.doubleToBytes(-1), bytes);
+    
+    bytes = new byte[] { 127, -17, -1, -1, -1, -1, -1, -1 };
+    assertEquals(Utils.bytesToDouble(bytes), Double.MAX_VALUE, 0.0001);
+    assertArrayEquals(Utils.doubleToBytes(Double.MAX_VALUE), bytes);
+    
+    bytes = new byte[] { 0, 0, 0, 0, 0, 0, 0, 1 };
+    assertEquals(Utils.bytesToDouble(bytes), Double.MIN_VALUE, 0.0001);
+    assertArrayEquals(Utils.doubleToBytes(Double.MIN_VALUE), bytes);
+    
+    bytes = new byte[] { 127, -8, 0, 0, 0, 0, 0, 0 };
+    assertTrue(Double.isNaN(Utils.bytesToDouble(bytes)));
+    assertArrayEquals(Utils.doubleToBytes(Double.NaN), bytes);
+    
+    bytes = new byte[] { 63, -16, 0, 0, 0, 0, 0, 0, 42, 42, 42 };
+    assertEquals(Utils.bytesToDouble(bytes), 1, 0.0001);
+  }
+  
+  @Test (expectedExceptions = NullPointerException.class)
+  public void bytesToLongNull() throws Exception {
+    Utils.bytesToLong(null);
+  }
+  
+  @Test (expectedExceptions = IndexOutOfBoundsException.class)
+  public void bytesToLongTooShort() throws Exception {
+    Utils.bytesToLong(new byte[] { 0, 0, 0, 0, 0, 0, 0 });
+  }
+  
+  @Test (expectedExceptions = IllegalArgumentException.class)
+  public void bytesToDoubleTooShort() throws Exception {
+    Utils.bytesToDouble(new byte[] { 0, 0, 0, 0, 0, 0, 0 });
+  }
+  
+  /**
+   * Since this version of TestNG doesn't appear to have an assertArrayEquals,
+   * this will compare the two to make sure they're the same. 
+   * @param actual Actual array to validate
+   * @param expected What the array should contain
+   * @throws AssertionError if the test fails.
+   */
+  public void assertArrayEquals(final byte[] actual, final byte[] expected) {
+    if (actual == null && expected != null) {
+      throw new AssertionError("Expected " + Arrays.toString(expected) + 
+          " but found [null]");
+    }
+    if (actual != null && expected == null) {
+      throw new AssertionError("Expected [null] but found " + 
+          Arrays.toString(actual));
+    }
+    if (actual.length != expected.length) {
+      throw new AssertionError("Expected length " + expected.length + 
+          " but found " + actual.length);
+    }
+    for (int i = 0; i < expected.length; i++) {
+      if (actual[i] != expected[i]) {
+        throw new AssertionError("Expected byte [" + expected[i] + 
+            "] at index " + i + " but found [" + actual[i] + "]");
+      }
+    }
+  }
+}
\ No newline at end of file
-- 
GitLab