Add a method to parse a binary string to a Prefix.

This enables us to get human-readable output out of the radix tress.

Addresses ONOS-1957.

Change-Id: Ia28f6e56e7067bfe447423ba8451d762c4baa49c
diff --git a/src/main/java/net/onrc/onos/apps/sdnip/Prefix.java b/src/main/java/net/onrc/onos/apps/sdnip/Prefix.java
index 81010d6..489beda 100644
--- a/src/main/java/net/onrc/onos/apps/sdnip/Prefix.java
+++ b/src/main/java/net/onrc/onos/apps/sdnip/Prefix.java
@@ -1,7 +1,9 @@
 package net.onrc.onos.apps.sdnip;
 
+import java.math.BigInteger;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
 import java.util.Arrays;
 
 import com.google.common.net.InetAddresses;
@@ -28,6 +30,8 @@
 
     public static final int MIN_PREFIX_LENGTH = 0;
 
+    private static final int BINARY_RADIX = 2;
+
     private final int prefixLength;
     private final byte[] address;
     private final String binaryString;
@@ -237,23 +241,46 @@
     }
 
     /**
-     * Print the prefix to a String showing the bits of the address.
+     * Creates a Prefix object from a binary string.
+     * <p/>
+     * This is the reverse operation of {@link Prefix#toBinaryString()}. It
+     * takes a binary string (a String containing only '0' and '1'
+     * characters) and parses it to create a corresponding Prefix object.
      *
-     * @return the bit-string of the prefix
+     * @param binaryString the binary string to convert to Prefix
+     * @return a Prefix object representing the same prefix as the input string
      */
-    public String printAsBits() {
-        StringBuilder result = new StringBuilder();
-        for (int i = 0; i < address.length; i++) {
-            byte b = address[i];
-            for (int j = 0; j < Byte.SIZE; j++) {
-                byte mask = (byte) (0x80 >>> j);
-                result.append(((b & mask) == 0) ? "0" : "1");
-                if (i * Byte.SIZE + j == prefixLength - 1) {
-                    return result.toString();
-                }
-            }
-            result.append(' ');
+    public static Prefix fromBinaryString(String binaryString) {
+        if (binaryString.length() > MAX_PREFIX_LENGTH) {
+            throw new IllegalArgumentException(
+                    "Binary string length must not be greater than "
+                    + MAX_PREFIX_LENGTH);
         }
-        return result.substring(0, result.length() - 1);
+
+        for (int i = 0; i < binaryString.length(); i++) {
+            char character = binaryString.charAt(i);
+            if (character != '0' && character != '1') {
+                throw new IllegalArgumentException(
+                        "Binary string may only contain the characters "
+                        + "\'0\' or \'1\'");
+            }
+        }
+
+        // Pad the binary string out to be MAX_PREFIX_LENGTH bits long
+        StringBuilder paddedBinaryString = new StringBuilder(MAX_PREFIX_LENGTH);
+        paddedBinaryString.append(binaryString);
+        for (int i = binaryString.length(); i < MAX_PREFIX_LENGTH; i++) {
+            paddedBinaryString.append('0');
+        }
+
+        // BigInteger will parse the binary string to an integer using radix 2
+        BigInteger bigInteger =
+                new BigInteger(paddedBinaryString.toString(), BINARY_RADIX);
+
+        // Convert the integer to a byte array
+        ByteBuffer bb = ByteBuffer.allocate(ADDRESS_LENGTH_BYTES);
+        bb.putInt(bigInteger.intValue());
+
+        return new Prefix(bb.array(), binaryString.length());
     }
 }
diff --git a/src/main/java/net/onrc/onos/apps/sdnip/web/IncomingRequestResource.java b/src/main/java/net/onrc/onos/apps/sdnip/web/IncomingRequestResource.java
index 5a35bb4..7f175f0 100644
--- a/src/main/java/net/onrc/onos/apps/sdnip/web/IncomingRequestResource.java
+++ b/src/main/java/net/onrc/onos/apps/sdnip/web/IncomingRequestResource.java
@@ -54,7 +54,7 @@
                 }
 
                 output.append("    {\"prefix\": \"");
-                output.append(entry.getKey());
+                output.append(Prefix.fromBinaryString(entry.getKey().toString()));
                 output.append("\", \"nexthop\": \"");
                 output.append(entry.getValue().getNextHop().getHostAddress());
                 output.append("\"}");
diff --git a/src/test/java/net/onrc/onos/apps/sdnip/PrefixTest.java b/src/test/java/net/onrc/onos/apps/sdnip/PrefixTest.java
index ae3b442..957cbe0 100644
--- a/src/test/java/net/onrc/onos/apps/sdnip/PrefixTest.java
+++ b/src/test/java/net/onrc/onos/apps/sdnip/PrefixTest.java
@@ -4,10 +4,13 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 
 import org.junit.After;
 import org.junit.Before;
@@ -69,11 +72,12 @@
         Prefix p3 = new Prefix(b3, 12);
         Prefix p4 = new Prefix(b4, 11);
 
-        //Have different input byte arrays, but should be equal after construction
+        // Different input byte arrays, but Prefixes should be equal after
+        // construction
         assertTrue(p1.equals(p2));
         assertTrue(p2.equals(p3));
 
-        //Same input byte array, but should be false
+        // Same input byte array, but should be false
         assertFalse(p1.equals(p4));
 
         assertTrue(Arrays.equals(p1.getAddress(), p3.getAddress()));
@@ -104,8 +108,8 @@
 
     @Test
     public void testPrefixReturnsSame() {
-        //Create a prefix of all ones for each prefix length.
-        //Check that Prefix doesn't mangle it
+        // Create a prefix of all ones for each prefix length.
+        // Check that Prefix doesn't mangle it
         for (int prefixLength = 1; prefixLength <= Prefix.MAX_PREFIX_LENGTH; prefixLength++) {
             byte[] address = new byte[Prefix.ADDRESS_LENGTH_BYTES];
 
@@ -191,4 +195,51 @@
         // Should throw IllegalArgumentException
         new Prefix(b, Prefix.MAX_PREFIX_LENGTH + 1);
     }
+
+    @Test
+    public void testFromBinaryString() {
+        Map<String, String> binaryToDecimalDot = new HashMap<>();
+
+        binaryToDecimalDot.put("", "0.0.0.0/0");
+        binaryToDecimalDot.put("1", "128.0.0.0/1");
+        binaryToDecimalDot.put("10000001", "129.0.0.0/8");
+        binaryToDecimalDot.put("1011011110", "183.128.0.0/10");
+        binaryToDecimalDot.put("01001101000000001010010110110101", "77.0.165.181/32");
+
+        for (Map.Entry<String, String> entry : binaryToDecimalDot.entrySet()) {
+            Prefix p = Prefix.fromBinaryString(entry.getKey());
+            assertEquals(entry.getValue(), p.toString());
+        }
+    }
+
+    @Test
+    public void testFromBinaryStringInvalidInput() {
+        try {
+            Prefix.fromBinaryString("34102");
+            fail("Non-binary input should throw an exception");
+        } catch (IllegalArgumentException e) {
+            assertTrue(true);
+        }
+
+        try {
+            Prefix.fromBinaryString("-0101");
+            fail("Non-binary input should throw an exception");
+        } catch (IllegalArgumentException e) {
+            assertTrue(true);
+        }
+
+        try {
+            Prefix.fromBinaryString("01010101010101010101010101010102");
+            fail("Non-binary input should throw an exception");
+        } catch (IllegalArgumentException e) {
+            assertTrue(true);
+        }
+
+        try {
+            Prefix.fromBinaryString("010101010101010101010101010101011");
+            fail("Input too long should throw an exception");
+        } catch (IllegalArgumentException e) {
+            assertTrue(true);
+        }
+    }
 }