Implemented a recursive equality checker for JsonElement. Compensates for MongoDB rounding issues, non deterministic map entry order etc. Also reduces CPU load with 30-40%.

This commit is contained in:
Olof Larsson 2013-05-27 12:27:45 +02:00
parent b50c8cd048
commit 6d57d6f51c
4 changed files with 181 additions and 4 deletions

View File

@ -139,7 +139,7 @@ public class DriverMongo extends DriverAbstract
Long mtime = ((Number)raw.removeField(MTIME_FIELD)).longValue();
raw.removeField(ID_FIELD);
JsonElement element = MongoGsonConverter.mongo2GsonObject(raw);
JsonElement element = GsonMongoConverter.mongo2GsonObject(raw);
return new SimpleEntry<JsonElement, Long>(element, mtime);
}
@ -149,7 +149,7 @@ public class DriverMongo extends DriverAbstract
{
DBCollection dbcoll = fixColl(coll);
BasicDBObject dbo = MongoGsonConverter.gson2MongoObject(data);
BasicDBObject dbo = GsonMongoConverter.gson2MongoObject(data);
Long mtime = System.currentTimeMillis();
dbo.put(MTIME_FIELD, mtime);
dbo.put(ID_FIELD, id);

View File

@ -0,0 +1,177 @@
package com.massivecraft.mcore.store;
import java.util.Map.Entry;
import org.apache.commons.lang.StringUtils;
import com.massivecraft.mcore.xlib.gson.JsonArray;
import com.massivecraft.mcore.xlib.gson.JsonElement;
import com.massivecraft.mcore.xlib.gson.JsonNull;
import com.massivecraft.mcore.xlib.gson.JsonObject;
import com.massivecraft.mcore.xlib.gson.JsonPrimitive;
import com.massivecraft.mcore.xlib.gson.internal.LazilyParsedNumber;
public class GsonEqualsChecker
{
// The argument one must be JsonElement, and can not be null.
// The argument twoObject may be anything, even null.
public static boolean equals(JsonElement one, Object twoObject)
{
// Null check (one can't ever be null)
if (twoObject == null) return false;
// Object identity speedup
if (one == twoObject) return true;
// Type-Switch
if (one.isJsonObject())
{
// JsonObject
return objectEquals((JsonObject)one, twoObject);
}
else if (one.isJsonArray())
{
// JsonArray
return arrayEquals((JsonArray)one, twoObject);
}
else if (one.isJsonPrimitive())
{
// JsonPrimitive
return primitiveEquals((JsonPrimitive)one, twoObject);
}
else if (one.isJsonNull())
{
// JsonNull
return nullEquals((JsonNull)one, twoObject);
}
else
{
// ???
throw new IllegalArgumentException("Unsupported value type for: " + one);
}
}
// The argument one must be JsonObject, and can not be null.
// The argument twoObject may be anything, even null.
public static boolean objectEquals(JsonObject one, Object twoObject)
{
// Null check (one can't ever be null)
if (twoObject == null) return false;
// Object identity speedup
if (one == twoObject) return true;
// twoObject must be JsonObject
if (!(twoObject instanceof JsonObject)) return false;
// Cast to JsonObject
JsonObject two = (JsonObject)twoObject;
// Size must be the same
if (one.entrySet().size() != two.entrySet().size()) return false;
// And each entry must exist and be the same
for (Entry<String, JsonElement> entry : one.entrySet())
{
if (!equals(entry.getValue(), two.get(entry.getKey()))) return false;
}
return true;
}
// The argument one must be JsonArray, and can not be null.
// The argument twoObject may be anything, even null.
public static boolean arrayEquals(JsonArray one, Object twoObject)
{
// Null check (one can't ever be null)
if (twoObject == null) return false;
// Object identity speedup
if (one == twoObject) return true;
// twoObject must be JsonArray
if (!(twoObject instanceof JsonArray)) return false;
// Cast to JsonArray
JsonArray two = (JsonArray)twoObject;
// Size must be the same
int size = one.size();
if (two.size() != size) return false;
// And each element index must be the same
for (int i = 0; i < size ; i++)
{
if (!equals(one.get(i), two.get(i))) return false;
}
return true;
}
// The argument one must be JsonPrimitive, and can not be null.
// The argument twoObject may be anything, even null.
public static boolean primitiveEquals(JsonPrimitive one, Object twoObject)
{
// Null check (one can't ever be null)
if (twoObject == null) return false;
// Object identity speedup
if (one == twoObject) return true;
// if twoObject is JsonObject or JsonArray we are not equal.
if (!(twoObject instanceof JsonPrimitive)) return false;
// Cast to JsonPrimitive
JsonPrimitive two = (JsonPrimitive)twoObject;
// Boolean check
if (one.isBoolean())
{
return one.getAsBoolean() == two.getAsBoolean();
}
// Number check
if (one.isNumber())
{
Number oneNumber = one.getAsNumber();
Number twoNumber = two.getAsNumber();
boolean floating;
if (oneNumber instanceof LazilyParsedNumber)
{
floating = StringUtils.contains(oneNumber.toString(), '.');
}
else
{
floating = (oneNumber instanceof Double || oneNumber instanceof Float);
}
if (floating)
{
// Our epsilon is pretty big in order to see float and double as the same.
return Math.abs(oneNumber.doubleValue() - twoNumber.doubleValue()) < 0.0001D;
}
else
{
return oneNumber.longValue() == twoNumber.longValue();
}
}
// String check
if (one.isString())
{
return one.getAsString().equals(two.getAsString());
}
throw new IllegalArgumentException("Unsupported value type for: " + one);
}
// The argument one must be JsonNull, and can not be null.
// The argument twoObject may be anything, even null.
public static boolean nullEquals(JsonNull one, Object twoObject)
{
// Null check (one can't ever be null)
if (twoObject == null) return false;
return twoObject == JsonNull.INSTANCE;
}
}

View File

@ -15,7 +15,7 @@ import com.massivecraft.mcore.xlib.mongodb.BasicDBList;
import com.massivecraft.mcore.xlib.mongodb.BasicDBObject;
import com.massivecraft.mcore.xlib.mongodb.DBObject;
public final class MongoGsonConverter
public final class GsonMongoConverter
{
// -------------------------------------------- //
// CONSTANTS

View File

@ -52,7 +52,7 @@ public class MStore
if (one == null) return two == null;
if (two == null) return one == null;
return one.toString().equals(two.toString());
return GsonEqualsChecker.equals(one, two);
}
// -------------------------------------------- //