From 24b2f9d1db6956c33b595a1694e66679ac529b9a Mon Sep 17 00:00:00 2001 From: Vyacheslav Tyutyunkov Date: Wed, 27 Mar 2013 17:11:51 +0700 Subject: [PATCH] #21 --- jejdb/src/cpp/jejdb.c | 5 +- jejdb/src/java/org/ejdb/bson/BSONDecoder.java | 124 +++++++++++++++++++--- jejdb/src/java/org/ejdb/bson/BSONEncoder.java | 5 +- jejdb/src/java/org/ejdb/bson/BSONObject.java | 78 +++++++++++++- jejdb/src/java/org/ejdb/bson/io/InputBuffer.java | 122 +++++++++++++++++++++ jejdb/src/java/org/ejdb/bson/types/ObjectId.java | 13 +++ jejdb/src/java/org/ejdb/bson/util/RegexFlag.java | 19 +++- jejdb/src/test/org/ejdb/driver/test/EJDBTest.java | 18 ++-- 8 files changed, 352 insertions(+), 32 deletions(-) create mode 100644 jejdb/src/java/org/ejdb/bson/io/InputBuffer.java diff --git a/jejdb/src/cpp/jejdb.c b/jejdb/src/cpp/jejdb.c index f3dbb6d..18b53b9 100755 --- a/jejdb/src/cpp/jejdb.c +++ b/jejdb/src/cpp/jejdb.c @@ -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; diff --git a/jejdb/src/java/org/ejdb/bson/BSONDecoder.java b/jejdb/src/java/org/ejdb/bson/BSONDecoder.java index 9919b77..f4ee2ea 100644 --- a/jejdb/src/java/org/ejdb/bson/BSONDecoder.java +++ b/jejdb/src/java/org/ejdb/bson/BSONDecoder.java @@ -1,36 +1,130 @@ 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; diff --git a/jejdb/src/java/org/ejdb/bson/BSONEncoder.java b/jejdb/src/java/org/ejdb/bson/BSONEncoder.java index 42844f9..3de9cd2 100644 --- a/jejdb/src/java/org/ejdb/bson/BSONEncoder.java +++ b/jejdb/src/java/org/ejdb/bson/BSONEncoder.java @@ -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); } diff --git a/jejdb/src/java/org/ejdb/bson/BSONObject.java b/jejdb/src/java/org/ejdb/bson/BSONObject.java index f5df116..c909afc 100644 --- a/jejdb/src/java/org/ejdb/bson/BSONObject.java +++ b/jejdb/src/java/org/ejdb/bson/BSONObject.java @@ -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 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 thatData = ((BSONObject) o).data; + + if (thatData.size() != data.size()) { + return false; + } + + try { + Iterator> i = data.entrySet().iterator(); + while (i.hasNext()) { + Map.Entry 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 index 0000000..065db8a --- /dev/null +++ b/jejdb/src/java/org/ejdb/bson/io/InputBuffer.java @@ -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); + } +} diff --git a/jejdb/src/java/org/ejdb/bson/types/ObjectId.java b/jejdb/src/java/org/ejdb/bson/types/ObjectId.java index c8fcb40..dcafd38 100644 --- a/jejdb/src/java/org/ejdb/bson/types/ObjectId.java +++ b/jejdb/src/java/org/ejdb/bson/types/ObjectId.java @@ -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("); diff --git a/jejdb/src/java/org/ejdb/bson/util/RegexFlag.java b/jejdb/src/java/org/ejdb/bson/util/RegexFlag.java index 93baae2..40372f8 100644 --- a/jejdb/src/java/org/ejdb/bson/util/RegexFlag.java +++ b/jejdb/src/java/org/ejdb/bson/util/RegexFlag.java @@ -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); diff --git a/jejdb/src/test/org/ejdb/driver/test/EJDBTest.java b/jejdb/src/test/org/ejdb/driver/test/EJDBTest.java index 0c01d6d..7d0bd8b 100644 --- a/jejdb/src/test/org/ejdb/driver/test/EJDBTest.java +++ b/jejdb/src/test/org/ejdb/driver/test/EJDBTest.java @@ -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); } } -- 2.7.4