Fixes to RAMCloud Java bindings

- Fix FindBugs warnings
- Change RejectRule to be a static class
- Change nested Exception classes to be static classes
- Changed RejectRules methods to clarify its roll, and allow chaining
- Represent RejectRules flags as bits in int to decrease number of JNI crossing
- multiWrite value size parameter needs to be int to match C++ decl.
- Add conditional read, write, remove
- Deprecate writeRule method
- Fixed RejectRules related Exception class inheritance
- Declare throws on conditional operations

Change-Id: I07c3a2111d06ca051a29415224d59b9bfa7a4ddb
diff --git a/src/main/java/edu/stanford/ramcloud/JRamCloud.java b/src/main/java/edu/stanford/ramcloud/JRamCloud.java
index 63b0748..83fdd44 100644
--- a/src/main/java/edu/stanford/ramcloud/JRamCloud.java
+++ b/src/main/java/edu/stanford/ramcloud/JRamCloud.java
@@ -14,6 +14,7 @@
  */
 
 package edu.stanford.ramcloud;
+import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
 import java.util.LinkedList;
 
@@ -35,6 +36,8 @@
         System.loadLibrary("edu_stanford_ramcloud_JRamCloud");
     }
 
+    public static final long VERSION_NONEXISTENT = 0L;
+
     /// Pointer to the underlying C++ RAMCloud object associated with this
     /// object.
     private long ramcloudObjectPointer = 0;
@@ -42,85 +45,106 @@
     /**
      * See src/RejectRules.h.
      */
-    public class RejectRules {
-	private long givenVersion;
-        private boolean doesntExist;
-        private boolean exists;
-        private boolean versionLeGiven;
-        private boolean versionNeGiven;
+    public static class RejectRules {
+        // these needs to be in sync with JNI code;
+        public static final int DoesntExist = 1;
+        public static final int Exists = 1 << 1;
+        public static final int VersionLeGiven = 1 << 2;
+        public static final int VersionNeGiven = 1 << 3;
+
+        protected long givenVersion;
+        protected int flags;
 
         public RejectRules() {
-            this.givenVersion = -1;
-            this.exists = this.doesntExist = this.versionLeGiven = this.versionNeGiven = false;
+            clear();
         }
 
-        public void setLeVersion(long version) {
+        public void clear() {
+            this.givenVersion = VERSION_NONEXISTENT;
+            this.flags = 0x0;
+        }
+
+        public RejectRules set(int flags, long version) {
+            this.flags = flags;
+            this.givenVersion = version;
+            return this;
+        }
+
+        public RejectRules set(int flags) {
+            return set(flags,VERSION_NONEXISTENT);
+        }
+
+        public RejectRules rejectIfLeVersion(long version) {
             setVersion(version);
-            this.versionLeGiven = true;
+            this.flags |= VersionLeGiven;
+            return this;
         }
 
-        public void setExists() {
-            this.exists = true;
+        public RejectRules rejectIfExists() {
+            this.flags |= Exists;
+            return this;
         }
 
-        public void setDoesntExists() {
-            this.doesntExist = true;
+        public RejectRules rejectIfDoesntExists() {
+            this.flags |= DoesntExist;
+            return this;
         }
 
-        public void setNeVersion(long version) {
+        public RejectRules rejectIfNeVersion(long version) {
             setVersion(version);
-            this.versionNeGiven = true;
+            this.flags |= VersionNeGiven;
+            return this;
         }
 
         private void setVersion(long version) {
             this.givenVersion = version;
-        }	
+        }
     }
-    
+
     public static class MultiReadObject {
         public long[] tableId;
         public byte[] key[];
-	public short[] keyLength;
-        
+        public short[] keyLength;
+
         public MultiReadObject(int size){
             this.tableId = new long[size];
             this.key = new byte[size][];
-	    this.keyLength = new short[size];
+            this.keyLength = new short[size];
         }
-	
+
 	public void setObject(int num, long tableId, byte key[]){
             this.tableId[num] = tableId;
             this.key[num] = key;
-	    this.keyLength[num] = (short) this.key[num].length;
+            this.keyLength[num] = (short) this.key[num].length;
 	}
     }
-    
+
     public static class MultiWriteObject {
         public long[] tableId;
         public byte[] key[];
-	public short[] keyLength;
+        public short[] keyLength;
         public byte[] value[];
-	public short[] valueLength;
+        public int[] valueLength;
         public RejectRules[] rules;
 
         public MultiWriteObject(int size) {
             this.tableId = new long[size];
             this.key = new byte[size][];
-	    this.keyLength = new short[size];
+            this.keyLength = new short[size];
             this.value = new byte[size][];
-	    this.valueLength = new short[size];
+            this.valueLength = new int[size];
             this.rules = new RejectRules[size];
         }
-	
+
 	public void setObject(int num, long tableId, byte key[], byte value[], RejectRules rules){
 	    this.tableId[num] = tableId;
 	    this.key[num] = key;
 	    this.keyLength[num] = (short) key.length;
 	    this.value[num] = value;
-	    this.valueLength[num] = (short) value.length;
+	    this.valueLength[num] = value.length;
 	    this.rules[num] = rules;
 	}
-	
+
     }
 
     public static class MultiWriteRspObject {
@@ -159,27 +183,27 @@
         public String
         getKey()
         {
-            return new String(key);
+            return new String(key,StandardCharsets.UTF_8);
         }
 
         public String
         getValue()
         {
-            return new String(value);
+            return new String(value,StandardCharsets.UTF_8);
         }
 
         final public byte[] key;
         final public byte[] value;
         final public long version;
     }
-    
+
     public static class TableEnumeratorObject {
 	TableEnumeratorObject(Object[] _object, long _nextHash)
-        {
+	{
 	    object = _object;
 	    nextHash = _nextHash;
 	}
-	
+
 	final public Object[] object;
 	final public long nextHash;
     }
@@ -187,32 +211,32 @@
     public class TableEnumerator {
         private long tableEnumeratorObjectPointer = 0;
         private long ramCloudObjectPointer = 0;
-        
+
         public TableEnumerator(long tableId)
         {
             ramCloudObjectPointer = ramcloudObjectPointer;
             tableEnumeratorObjectPointer = init(tableId);
         }
-        
+
         private native long init(long tableId);
         public native boolean hasNext();
         public native Object next();
     }
 
     public class TableEnumerator2 {
-        
+
 	protected long tableId;
 	protected LinkedList<JRamCloud.Object> rcobjs = null;
 	protected long nextHash = 0;
 	protected boolean done = false;
-	
+
         public TableEnumerator2(long tableId)
         {
 	    this.tableId = tableId;
 	    rcobjs = new LinkedList<>();
         }
         public boolean hasNext() {
-	    if (rcobjs.isEmpty()) 
+	    if (rcobjs.isEmpty())
 	    {
 		if (done) {
 		    return false;
@@ -229,13 +253,13 @@
 	    }
 	    return true;
 	}
-	
-	public Object next() 
+
+	public Object next()
 	{
 	    return rcobjs.pop();
 	}
     }
-    
+
     /**
      * Connect to the RAMCloud cluster specified by the given coordinator's
      * service locator string. This causes the JNI code to instantiate the
@@ -265,7 +289,8 @@
      * object. The user really should have called disconnect, but in case
      * they did not, be sure to clean up after them.
      */
-    public void
+    @Override
+    protected void
     finalize()
     {
         System.err.println("warning: JRamCloud::disconnect() was not called " +
@@ -280,34 +305,36 @@
     public Object
     read(long tableId, String key)
     {
-        return read(tableId, key.getBytes());
+        return read(tableId, key.getBytes(StandardCharsets.UTF_8));
     }
 
     /**
      * Convenience read() wrapper that take a String key argument.
+     * @throws RejectRulesException
      */
     public Object
-    read(long tableId, String key, RejectRules rules)
+    read(long tableId, String key, RejectRules rules) throws RejectRulesException
     {
-        return read(tableId, key.getBytes(), rules);
+        return read(tableId, key.getBytes(StandardCharsets.UTF_8), rules);
     }
-    
+
     /**
      * Convenience remove() wrapper that take a String key argument.
      */
     public long
     remove(long tableId, String key)
     {
-        return remove(tableId, key.getBytes());
+        return remove(tableId, key.getBytes(StandardCharsets.UTF_8));
     }
 
     /**
      * Convenience remove() wrapper that take a String key argument.
+     * @throws RejectRulesException
      */
     public long
-    remove(long tableId, String key, RejectRules rules)
+    remove(long tableId, String key, RejectRules rules) throws RejectRulesException
     {
-        return remove(tableId, key.getBytes(), rules);
+        return remove(tableId, key.getBytes(StandardCharsets.UTF_8), rules);
     }
 
     /**
@@ -316,16 +343,17 @@
     public long
     write(long tableId, String key, String value)
     {
-        return write(tableId, key.getBytes(), value.getBytes());
+        return write(tableId, key.getBytes(StandardCharsets.UTF_8), value.getBytes(StandardCharsets.UTF_8));
     }
 
     /**
      * Convenience write() wrapper that take String key and value arguments.
+     * @throws RejectRulesException
      */
     public long
-    write(long tableId, String key, String value, RejectRules rules)
+    write(long tableId, String key, String value, RejectRules rules) throws RejectRulesException
     {
-        return write(tableId, key.getBytes(), value.getBytes(), rules);
+        return write(tableId, key.getBytes(StandardCharsets.UTF_8), value.getBytes(StandardCharsets.UTF_8), rules);
     }
 
     /**
@@ -335,19 +363,20 @@
     public long
     write(long tableId, String key, byte[] value)
     {
-        return write(tableId, key.getBytes(), value);
+        return write(tableId, key.getBytes(StandardCharsets.UTF_8), value);
     }
 
     /**
      * Convenience write() wrapper that takes a String key and a byte[] value
      * argument.
+     * @throws RejectRulesException
      */
     public long
-    write(long tableId, String key, byte[] value, RejectRules rules)
+    write(long tableId, String key, byte[] value, RejectRules rules) throws RejectRulesException
     {
-        return write(tableId, key.getBytes(), value, rules);
+        return write(tableId, key.getBytes(StandardCharsets.UTF_8), value, rules);
     }
-    
+
     private static native long connect(String coordinatorLocator);
     private static native void disconnect(long ramcloudObjectPointer);
 
@@ -356,79 +385,79 @@
     public native void dropTable(String name);
     public native long getTableId(String name);
     public native Object read(long tableId, byte[] key);
-    public native Object read(long tableId, byte[] key, RejectRules rules);
+    public native Object read(long tableId, byte[] key, RejectRules rules) throws RejectRulesException;
     public native Object[] multiRead(long[] tableId, byte[] keydata[], short[] keyDataSize, int requestNum);
     public native long remove(long tableId, byte[] key);
-    public native long remove(long tableId, byte[] key, RejectRules rules);
+    public native long remove(long tableId, byte[] key, RejectRules rules) throws RejectRulesException;
     public native long write(long tableId, byte[] key, byte[] value);
-    public native long write(long tableId, byte[] key, byte[] value, RejectRules rules);
+    public native long write(long tableId, byte[] key, byte[] value, RejectRules rules) throws RejectRulesException;
+    @Deprecated
     public native long writeRule(long tableId, byte[] key, byte[] value, RejectRules rules);
-    public native MultiWriteRspObject[] multiWrite(long[] tableId, byte[] key[], short[] keyDataSize, byte[] value[], short[] valueDataSize, int requestNum, RejectRules[] rules);
+    public native MultiWriteRspObject[] multiWrite(long[] tableId, byte[] key[], short[] keyDataSize, byte[] value[], int[] valueDataSize, int requestNum, RejectRules[] rules);
     public native TableEnumeratorObject getTableObjects(long tableId, long nextHash);
 
     /*
      * The following exceptions may be thrown by the JNI functions:
      */
 
-    public class TableDoesntExistException extends Exception {
+    public static class TableDoesntExistException extends RuntimeException {
         public TableDoesntExistException(String message)
         {
             super(message);
         }
     }
 
-    public class ObjectDoesntExistException extends Exception {
+    public static class ObjectDoesntExistException extends RejectRulesException {
         public ObjectDoesntExistException(String message)
         {
             super(message);
         }
     }
 
-    public class ObjectExistsException extends Exception {
+    public static class ObjectExistsException extends RejectRulesException {
         public ObjectExistsException(String message)
         {
             super(message);
         }
     }
 
-    public class WrongVersionException extends Exception {
+    public static class WrongVersionException extends RejectRulesException {
         public WrongVersionException(String message)
         {
             super(message);
         }
     }
-    
-    public class InvalidObjectException extends Exception {
+
+    public static class InvalidObjectException extends RuntimeException {
         public InvalidObjectException(String message) {
             super(message);
         }
     }
-    
-    public class RejectRulesException extends Exception {
+
+    public static class RejectRulesException extends Exception {
         public RejectRulesException(String message) {
             super(message);
         }
     }
-    
+
     public static void tableEnumeratorTest(JRamCloud ramcloud) {
         long startTime = 0;
         for (int x = 0 ; x < 2 ; x ++){
         for(int N = 1000; N < 10000; N += 1000) {
             long EnumerateTesttable = ramcloud.createTable("EnumerateTest");
             for(int i = 0 ; i < N ; ++i) {
-                String key = new Integer(i).toString();
-                ramcloud.write(EnumerateTesttable, key.getBytes(), "Hello, World!".getBytes());
+                String key = String.valueOf(i);
+                ramcloud.write(EnumerateTesttable, key.getBytes(StandardCharsets.UTF_8), "Hello, World!".getBytes(StandardCharsets.UTF_8));
             }
 
-            MultiReadObject mread[] = new MultiReadObject[N];
             long tableIdList[] = new long[N];
             byte[] keydata[] = new byte[N][];
             short keydataSize[] = new short[N];
-	    startTime = System.nanoTime();
+            startTime = System.nanoTime();
             for (int j = 0 ; j < N ; ++j) {
                 tableIdList[j] = EnumerateTesttable;
-                String key = new Integer(j).toString();
-                keydata[j] = key.getBytes();
+                String key = String.valueOf(j);
+                keydata[j] = key.getBytes(StandardCharsets.UTF_8);
                 keydataSize[j] = (short) keydata[j].length;
             }
             JRamCloud.Object out[] = ramcloud.multiRead(tableIdList, keydata, keydataSize, N);
@@ -448,13 +477,13 @@
                     System.out.println("tableEnumerator object: key = [" + tableEntry.getKey() + "], value = [" + tableEntry.getValue() + "]");
                 }
             }
-	    System.out.println("old TableEnumerator : " + N + " Time : " + (System.nanoTime()-startTime));
+            System.out.println("old TableEnumerator : " + N + " Time : " + (System.nanoTime()-startTime));
 
             startTime = System.nanoTime();
             JRamCloud.TableEnumerator2 tableEnum2 = ramcloud.new TableEnumerator2(EnumerateTesttable);
-	    while (tableEnum2.hasNext()) {
-		Object tableEntry2 = tableEnum2.next();
-		if (tableEntry2 != null) {
+            while (tableEnum2.hasNext()) {
+                Object tableEntry2 = tableEnum2.next();
+                if (tableEntry2 != null) {
                     System.out.println("tableEnumerator2 object: key = [" + tableEntry2.getKey() + "], value = [" + tableEntry2.getValue() + "]");
                 }
             }
@@ -492,15 +521,16 @@
         }
 
         ramcloud.write(tableId, "thisIsTheKey", "thisIsTheValue");
-        
+
         long before = System.nanoTime();
         for (int i = 0; i < 1000; i++) {
+            @SuppressWarnings("unused")
             JRamCloud.Object unused = ramcloud.read(tableId, "thisIsTheKey");
         }
         long after = System.nanoTime();
         System.out.println("Avg read latency: " +
             ((double)(after - before) / 1000 / 1000) + " usec");
-        
+
         // multiRead test
         long tableId4 = ramcloud.createTable("table4");
         System.out.println("table4 id " + tableId4);
@@ -516,9 +546,9 @@
 
 	MultiReadObject mr = new MultiReadObject(2);
 	MultiWriteObject mw = new MultiWriteObject(2);
-	
-	mr.setObject(0, tableId4, "object1-1".getBytes());
-	mr.setObject(1, tableId5, "object2-1".getBytes());
+
+	mr.setObject(0, tableId4, "object1-1".getBytes(StandardCharsets.UTF_8));
+	mr.setObject(1, tableId5, "object2-1".getBytes(StandardCharsets.UTF_8));
 
         JRamCloud.Object out[] = ramcloud.multiRead(mr.tableId, mr.key, mr.keyLength, 2);
         for (int i = 0 ; i < 2 ; i++){
@@ -527,12 +557,12 @@
         }
 
         for (int i = 0; i < 1000; i++) {
-            String key1 = "key1" + new Integer(i).toString();
-            String key2 = "key2" + new Integer(i).toString();
-	    
-	    mw.setObject(0, tableId4, key1.getBytes(), "v0-value".getBytes(), null);
-	    mw.setObject(1, tableId5, key2.getBytes(), "v1".getBytes(), null);
-      	    
+            String key1 = "key1" + String.valueOf(i);
+            String key2 = "key2" + String.valueOf(i);
+
+            mw.setObject(0, tableId4, key1.getBytes(StandardCharsets.UTF_8), "v0-value".getBytes(StandardCharsets.UTF_8), null);
+            mw.setObject(1, tableId5, key2.getBytes(StandardCharsets.UTF_8), "v1".getBytes(StandardCharsets.UTF_8), null);
+
             MultiWriteRspObject[] rsp = ramcloud.multiWrite(mw.tableId, mw.key, mw.keyLength, mw.value, mw.valueLength, 2, mw.rules);
             if (rsp != null) {
                 for (int j = 0; j < rsp.length; j++) {
@@ -541,20 +571,20 @@
             }
         }
         for (int i = 0; i < 1000; i++) {
-            String key1 = "key1" + new Integer(i).toString();
-            String key2 = "key2" + new Integer(i).toString();
+            String key1 = "key1" + String.valueOf(i);
+            String key2 = "key2" + String.valueOf(i);
 
-	    mr.setObject(0, tableId4, key1.getBytes());
-	    mr.setObject(1, tableId5, key2.getBytes());
-	    
+            mr.setObject(0, tableId4, key1.getBytes(StandardCharsets.UTF_8));
+            mr.setObject(1, tableId5, key2.getBytes(StandardCharsets.UTF_8));
+
             out = ramcloud.multiRead(mr.tableId, mr.key, mr.keyLength, 2);
             for (int j = 0; j < 2; j++) {
                 System.out.println("multi read object: key = [" + out[j].getKey() + "], value = [" + out[j].getValue() + "]");
             }
         }
 
-	tableEnumeratorTest(ramcloud);
-	
+        tableEnumeratorTest(ramcloud);
+
         ramcloud.dropTable("table4");
         ramcloud.dropTable("table5");
         ramcloud.dropTable("table6");