#21
authorVyacheslav Tyutyunkov <tyutyunkov@gmail.com>
Wed, 27 Mar 2013 10:11:51 +0000 (17:11 +0700)
committerVyacheslav Tyutyunkov <tyutyunkov@gmail.com>
Wed, 27 Mar 2013 10:11:51 +0000 (17:11 +0700)
jejdb/src/cpp/jejdb.c
jejdb/src/java/org/ejdb/bson/BSONDecoder.java
jejdb/src/java/org/ejdb/bson/BSONEncoder.java
jejdb/src/java/org/ejdb/bson/BSONObject.java
jejdb/src/java/org/ejdb/bson/io/InputBuffer.java [new file with mode: 0644]
jejdb/src/java/org/ejdb/bson/types/ObjectId.java
jejdb/src/java/org/ejdb/bson/util/RegexFlag.java
jejdb/src/test/org/ejdb/driver/test/EJDBTest.java

index f3dbb6d..18b53b9 100755 (executable)
@@ -558,12 +558,11 @@ JNIEXPORT jobject JNICALL Java_org_ejdb_driver_EJDBCollection_save (JNIEnv *env,
 
        jclass clazz = (*env)->GetObjectClass(env, jdata);
        jmethodID putMethodID = (*env)->GetMethodID(env, clazz, "put", "(Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object;");
+       jfieldID idKeyID = (*env)->GetStaticFieldID(env, clazz, "ID_KEY", "Ljava/lang/String;");
+       jstring oidField = (*env)->GetStaticObjectField(env, clazz, idKeyID);
 
-       jstring oidField = (*env)->NewStringUTF(env, "_id");
        (*env)->CallObjectMethod(env, jdata, putMethodID, oidField, result);
 
-       (*env)->DeleteLocalRef(env, oidField);
-
        update_coll_meta(env, obj, coll);
 
        return result;
index 9919b77..f4ee2ea 100644 (file)
 package org.ejdb.bson;
 
+import org.ejdb.bson.io.InputBuffer;
+import org.ejdb.bson.types.ObjectId;
+import org.ejdb.bson.util.RegexFlag;
+
+import java.util.Date;
+import java.util.regex.Pattern;
+
 /**
  * @author Tyutyunkov Vyacheslav (tve@softmotions.com)
  * @version $Id$
  */
-public class BSONDecoder {
+class BSONDecoder {
+
+    private InputBuffer input;
 
     public BSONObject decode(byte[] data) {
-        return new BSONObject();
+        if (isBusy()) {
+            throw new IllegalStateException("other decoding in process");
+        }
+
+        if (data == null) {
+            throw new IllegalArgumentException("can not read object from null");
+        }
+
+        input = InputBuffer.createFromByteArray(data);
+        BSONObject result = read();
+        input = null;
+
+        return result;
     }
 
-    public int readInt(byte[] data) {
-        return readInt(data, 0);
+    public boolean isBusy() {
+        return input != null;
     }
 
-    public int readInt(byte[] data, int offset) {
-        int result = 0;
-        for (int i = 0; i < 4; ++i) {
-            result |= (0xFF & data[offset + i]) << (i * 8);
+
+    protected BSONObject read() {
+        int length = input.readInt();
+
+        BSONObject result = this.readObject(input.subBuffer(length - 5));
+
+        if (0x00 != input.read()) {
+            throw new IllegalArgumentException("unexpected end of document");
         }
 
         return result;
     }
 
-    public long readLong(byte[] data) {
-        return readLong(data, 0);
-    }
+    protected BSONObject readObject(InputBuffer input) {
+        BSONObject result = new BSONObject();
+        while (input.isAvailable()) {
+            byte type = input.read();
+            String name = input.readString();
+            switch (type) {
+                case BSON.DOUBLE:
+                    result.put(name, Double.longBitsToDouble(input.readLong()));
+                    break;
+
+                case BSON.STRING:
+                    int strlen = input.readInt();
+                    result.put(name, input.readString(strlen));
+                    break;
+
+                case BSON.OBJECT:
+                    int objlen = input.readInt();
+                    result.put(name, readObject(input.subBuffer(objlen - 5)));
+                    input.read();
+                    break;
+
+                case BSON.ARRAY:
+                    int arrlen = input.readInt();
+                    BSONObject arrObject = readObject(input.subBuffer(arrlen - 5));
+                    Object[] array = new Object[arrObject.size()];
+                    for (int i = 0; i < array.length; ++i) {
+                        array[i] = arrObject.get(String.valueOf(i));
+                    }
+                    result.put(name, array);
+                    input.read();
+                    break;
+
+                case BSON.BINARY:
+                    int binlen = input.readInt();
+                    byte subtype = input.read();
+                    if (0x00 != subtype) {
+                        throw new IllegalArgumentException("unexpected binary type: " + subtype);
+                    }
+                    result.put(name, input.readBytes(binlen));
+                    break;
+
+                case BSON.OBJECT_ID:
+                    result.put(name, new ObjectId(input.readBytes(12)));
+                    break;
+
+                case BSON.BOOLEAN:
+                    byte bvalue = input.read();
+                    if (0x00 != bvalue && 0x01 != bvalue) {
+                        throw new IllegalArgumentException("unexpected boolean value");
+                    }
+                    result.put(name, 0x01 == bvalue);
+                    break;
+
+                case BSON.DATE:
+                    result.put(name, new Date(input.readLong()));
+                    break;
+
+                case BSON.NULL:
+                    result.put(name, null);
+                    break;
+
+                case BSON.REGEX:
+                    //noinspection MagicConstant
+                    result.put(name, Pattern.compile(input.readString(), RegexFlag.stringToRegexFlags(input.readString())));
+                    break;
+
+                case BSON.INT:
+                    result.put(name, input.readInt());
+                    break;
+
+                case BSON.LONG:
+                    result.put(name, input.readLong());
+                    break;
 
-    public long readLong(byte[] data, int offset) {
-        long result = 0;
-        for (int i = 0; i < 8; ++i) {
-            result |= (0xFFL & data[offset + i]) << (i * 8);
+                default:
+                    throw new IllegalArgumentException("unexpected type: " + type);
+            }
         }
 
         return result;
index 42844f9..3de9cd2 100644 (file)
@@ -17,7 +17,7 @@ import java.util.regex.Pattern;
  * @author Tyutyunkov Vyacheslav (tve@softmotions.com)
  * @version $Id$
  */
-public class BSONEncoder {
+class BSONEncoder {
 
     private OutputBuffer output;
 
@@ -51,6 +51,8 @@ public class BSONEncoder {
             writeField(field, object.get(field));
         }
 
+        output.write((byte) 0x00);
+
         int end = output.getPosition();
 
         output.writeIntAt(start, end - start);
@@ -153,6 +155,7 @@ public class BSONEncoder {
             writeField(String.valueOf(i), value.next());
             ++i;
         }
+        output.write((byte) 0x00);
         output.writeIntAt(sp, output.getPosition() - sp);
     }
 
index f5df116..c909afc 100644 (file)
@@ -2,9 +2,10 @@ package org.ejdb.bson;
 
 import org.ejdb.bson.types.ObjectId;
 
-import java.util.Collections;
+import java.lang.reflect.Array;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.Map;
 import java.util.Set;
 
@@ -13,7 +14,7 @@ import java.util.Set;
  * @version $Id$
  */
 public class BSONObject {
-    private static final String ID_KEY = "_id";
+    public static final String ID_KEY = "_id";
 
     private Map<String, Object> data;
 
@@ -80,11 +81,84 @@ public class BSONObject {
         return data.get(key);
     }
 
+    public ObjectId getId() {
+        return (ObjectId) get(ID_KEY);
+    }
+
+    public int size() {
+        return data.size();
+    }
+
     public boolean containsField(String key) {
         return data.containsKey(key);
     }
 
     @Override
+    public boolean equals(Object o) {
+        if (this != o && (null == o || !(o instanceof BSONObject))) {
+            return false;
+        }
+
+        Map<String, Object> thatData = ((BSONObject) o).data;
+
+        if (thatData.size() != data.size()) {
+            return false;
+        }
+
+        try {
+            Iterator<Map.Entry<String, Object>> i = data.entrySet().iterator();
+            while (i.hasNext()) {
+                Map.Entry<String, Object> e = i.next();
+                String key = e.getKey();
+                Object value = e.getValue();
+                if (value == null) {
+                    if (!(thatData.get(key) == null && thatData.containsKey(key))) {
+                        return false;
+                    }
+                } else {
+                    if (!equalObjects(value, thatData.get(key))) {
+                        return false;
+                    }
+                }
+            }
+        } catch (ClassCastException unused) {
+            return false;
+        } catch (NullPointerException unused) {
+            return false;
+        }
+
+        return true;
+    }
+
+    private boolean equalObjects(Object o1, Object o2) {
+        if (o1.getClass().isArray()) {
+            int len = Array.getLength(o1);
+            if (len != Array.getLength(o2)) {
+                return false;
+            }
+
+            for (int i = 0; i < len; ++i) {
+                Object item1 = Array.get(o1, i);
+                Object item2 = Array.get(o2, i);
+
+                boolean isEquals = item1 == null ? item2 == null : equalObjects(item1, item2);
+                if (!isEquals) {
+                    return false;
+                }
+            }
+
+            return true;
+        } else {
+            return !o1.equals(o2) ? false : true;
+        }
+    }
+
+    @Override
+    public int hashCode() {
+        return data.hashCode();
+    }
+
+    @Override
     public String toString() {
         return data.toString();
     }
diff --git a/jejdb/src/java/org/ejdb/bson/io/InputBuffer.java b/jejdb/src/java/org/ejdb/bson/io/InputBuffer.java
new file mode 100644 (file)
index 0000000..065db8a
--- /dev/null
@@ -0,0 +1,122 @@
+package org.ejdb.bson.io;
+
+import org.ejdb.bson.BSONObject;
+
+import java.io.UnsupportedEncodingException;
+
+/**
+ * @author Tyutyunkov Vyacheslav (tve@softmotions.com)
+ * @version $Id$
+ */
+public class InputBuffer {
+    private byte[] data;
+    private int offset;
+    private int position;
+    private int limit;
+
+    private InputBuffer(byte[] data, int offset, int limit) {
+        this.data = data;
+        this.position = 0;
+        this.offset = offset;
+        this.limit = limit;
+    }
+
+    public byte read() {
+        ensure(1);
+
+        byte result = data[offset + position];
+        position += 1;
+
+        return result;
+    }
+
+    public int readInt() {
+        ensure(4);
+
+        int result = 0;
+        for (int i = 0; i < 4; ++i) {
+            result |= (0xFF & data[offset + position + i]) << (i * 8);
+        }
+        position += 4;
+
+        return result;
+    }
+
+    public long readLong() {
+        ensure(8);
+
+        long result = 0;
+        for (int i = 0; i < 8; ++i) {
+            result |= (0xFFL & data[offset + position + i]) << (i * 8);
+        }
+        position += 8;
+
+        return result;
+    }
+
+    public byte[] readBytes(int count) {
+        ensure(count);
+        byte[] bytes = new byte[count];
+        System.arraycopy(data, offset + position, bytes, 0, count);
+        position += count;
+
+        return bytes;
+    }
+
+    public String readString() {
+        return readString(0);
+    }
+
+    public String readString(int length) {
+        if (length > 0) {
+            ensure(length);
+            if ((byte) 0x00 != data[offset + position + length - 1]) {
+                throw new IllegalArgumentException("unexpected end of string");
+            }
+
+            try {
+                String s = new String(data, offset + position, length - 1, "UTF-8");
+                position += length;
+                return s;
+            } catch (UnsupportedEncodingException e) {
+                throw new RuntimeException("can not decode string", e);
+            }
+        }
+
+        length = 0;
+        while (position + length < limit && data[offset + position + length] != (byte)0x00) {
+            ++length;
+        }
+
+        if (position + length > limit) {
+            throw new IllegalArgumentException("unexpected end of string");
+        }
+
+        String s = new String(data, offset + position, length);
+        position += length + 1;
+        return s;
+    }
+
+    public boolean isAvailable() {
+        return position < limit;
+    }
+
+    public InputBuffer subBuffer(int limit) {
+        ensure(limit);
+
+        InputBuffer result = new InputBuffer(data, offset + position, limit);
+        position += limit;
+
+        return result;
+    }
+
+    protected void ensure(int size) {
+        if (size > limit - position) {
+            throw new IllegalArgumentException("can not allocate sub buffer: not enought bytes");
+        }
+    }
+
+    public static InputBuffer createFromByteArray(byte[] data) {
+        return new InputBuffer(data, 0, data.length);
+    }
+}
index c8fcb40..dcafd38 100644 (file)
@@ -1,5 +1,7 @@
 package org.ejdb.bson.types;
 
+import java.util.Arrays;
+
 /**
  * @author Tyutyunkov Vyacheslav (tve@softmotions.com)
  * @version $Id$
@@ -35,6 +37,17 @@ public class ObjectId {
     }
 
     @Override
+    public boolean equals(Object o) {
+        return this == o || null != o && o instanceof ObjectId && Arrays.equals(data, ((ObjectId) o).data);
+
+    }
+
+    @Override
+    public int hashCode() {
+        return Arrays.hashCode(data);
+    }
+
+    @Override
     public String toString() {
         StringBuilder builder = new StringBuilder(34);
         builder.append("ObjectId(");
index 93baae2..40372f8 100644 (file)
@@ -28,15 +28,28 @@ public final class RegexFlag {
 
     public static String regexFlagsToString(int flags) {
         StringBuilder result = new StringBuilder();
-        for (RegexFlag regexFlag : regexFlags) {
-            if ((flags & regexFlag.getFlag()) > 0 && regexFlag.isSupported()) {
-                result.append(regexFlag.getCharacter());
+        for (RegexFlag rf : regexFlags) {
+            if ((flags & rf.getFlag()) > 0 && rf.isSupported()) {
+                result.append(rf.getCharacter());
             }
         }
 
         return result.toString();
     }
 
+    public static int stringToRegexFlags(String str) {
+        int flags = 0;
+
+        for (int i = 0; i < str.length(); ++i) {
+            RegexFlag rf = characterToRegexFlagMap.get(str.charAt(i));
+            if (rf != null && rf.isSupported()) {
+                flags |= rf.getFlag();
+            }
+        }
+
+        return flags;
+    }
+
     public static void registerRegexFlag(int flag, char character, boolean supported) {
         RegexFlag rf = new RegexFlag(flag, character, supported);
         regexFlags.add(rf);
index 0c01d6d..7d0bd8b 100644 (file)
@@ -56,15 +56,16 @@ public class EJDBTest extends TestCase {
 
         BSONObject obj = new BSONObject("test", "test").append("test2", 123);
 
-
         ObjectId oid = coll.save(obj);
         assertNotNull(oid);
+        assertEquals(oid, obj.getId());
 
         BSONObject lobj = coll.load(oid);
         assertNotNull(lobj);
-        assertEquals(lobj.get("_id"), oid);
-        assertEquals(obj.get("test"), lobj.get("test"));
-        assertEquals(obj.get("test2"), lobj.get("test2"));
+        assertEquals(obj, lobj);
+//        assertEquals(lobj.get("_id"), oid);
+//        assertEquals(obj.get("test"), lobj.get("test"));
+//        assertEquals(obj.get("test2"), lobj.get("test2"));
 
         EJDBQuery query = coll.createQuery(new BSONObject());
         EJDBResultSet rs = query.find();
@@ -128,14 +129,15 @@ public class EJDBTest extends TestCase {
         assertNotNull(ss.get(2));
 
         BSONObject obj12 = parrots.load(ss.get(0));
-        assertEquals(ss.get(0), obj12.get("_id"));
+        assertNotNull(obj12);
+        assertEquals(ss.get(0), obj12.getId());
+        assertEquals(obj1, obj12);
 
         EJDBQuery query = parrots.createQuery(new BSONObject());
         EJDBResultSet rs = query.find();
         assertEquals(rs.length(), 2);
         rs.close();
 
-
         query = parrots.createQuery(new BSONObject("name", Pattern.compile("(grenny|bounty)", Pattern.CASE_INSENSITIVE)),
                                     new BSONObject("$orderby", new BSONObject("name", 1)));
 
@@ -212,7 +214,7 @@ public class EJDBTest extends TestCase {
         assertFalse(bars.isTransactionActive());
         BSONObject bar2 = bars.load(boid);
         assertNotNull(bar2);
-        assertEquals(bar2.get("_id"), boid);
-        assertEquals(bar2.get("foo"), bar.get("foo"));
+        assertEquals(bar2.getId(), boid);
+        assertEquals(bar2, bar);
     }
 }