/* * Copyright 2010-2013 10gen Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.mongodb.hadoop.util; import com.mongodb.LazyDBList; import com.mongodb.LazyDBObject; import org.apache.hadoop.io.RawComparator; import org.bson.BSONObject; import org.bson.BasicBSONObject; import org.bson.LazyBSONCallback; import org.bson.LazyBSONDecoder; import org.bson.LazyBSONList; import org.bson.LazyBSONObject; import org.bson.types.BSONTimestamp; import org.bson.types.BasicBSONList; import org.bson.types.Binary; import org.bson.types.Code; import org.bson.types.CodeWScope; import org.bson.types.MaxKey; import org.bson.types.MinKey; import org.bson.types.ObjectId; import org.bson.types.Symbol; import java.nio.ByteBuffer; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.regex.Pattern; /** * @author Mike O'Brien, Sweet Song */ public class BSONComparator implements RawComparator<BSONObject> { private static final BSONComparator INSTANCE; private static final Map<Class<?>, Integer> TYPES; private static final LazyBSONDecoder DECODER; static { INSTANCE = new BSONComparator(); // Create a Map of class to their BSON compare order // http://docs.mongodb.org/manual/faq/developers/#what-is-the-compare-order-for-bson-types Map<Class<?>, Integer> aType = new HashMap<Class<?>, Integer>(); aType.put(MinKey.class, 1); aType.put(null, 2); aType.put(Integer.class, 3); aType.put(Double.class, 3); aType.put(Float.class, 3); aType.put(String.class, 4); aType.put(Symbol.class, 4); aType.put(LazyBSONObject.class, 5); aType.put(BasicBSONObject.class, 5); // The callback function for LazyBSONObject during get() // actually converts LazyBSONObject/List to DB objects aType.put(LazyDBObject.class, 5); aType.put(LazyDBList.class, 6); aType.put(LazyBSONList.class, 6); aType.put(BasicBSONList.class, 6); aType.put(Binary.class, 7); aType.put(byte[].class, 7); aType.put(ObjectId.class, 8); aType.put(Boolean.class, 9); aType.put(Date.class, 10); aType.put(BSONTimestamp.class, 10); aType.put(Pattern.class, 11); aType.put(Code.class, 13); aType.put(CodeWScope.class, 13); aType.put(MaxKey.class, 12); TYPES = aType; DECODER = new LazyBSONDecoder(); } public static BSONComparator getInstance() { return INSTANCE; } private Iterator<Entry<String, Object>> getIterator(final BSONObject obj) { if (obj instanceof BasicBSONObject) { return ((BasicBSONObject) obj).entrySet().iterator(); } else { return ((LazyBSONObject) obj).entrySet().iterator(); } } /** * @param one, two - two objects with the same type Cast the two objects and compare the values */ private int compareValues(final Object one, final Object two) { int diff = 0; // Most of the objects have their own comparator if (one instanceof Number) { // Need to be comparing all numeric values to one another, // so cast all of them to Double diff = Double.valueOf(one.toString()).compareTo(Double.valueOf(two.toString())); } else if (one instanceof String) { diff = ((String) one).compareTo((String) two); } else if (one instanceof BSONObject) { // BasicBSONObject and BasicBSONList both covered in this cast diff = compare((BSONObject) one, (BSONObject) two); } else if (one instanceof Binary) { ByteBuffer buff1 = ByteBuffer.wrap(((Binary) one).getData()); ByteBuffer buff2 = ByteBuffer.wrap(((Binary) two).getData()); diff = buff1.compareTo(buff2); } else if (one instanceof byte[]) { ByteBuffer buff1 = ByteBuffer.wrap((byte[]) one); ByteBuffer buff2 = ByteBuffer.wrap((byte[]) two); diff = buff1.compareTo(buff2); } else if (one instanceof ObjectId) { diff = ((ObjectId) one).compareTo((ObjectId) two); } else if (one instanceof Boolean) { diff = ((Boolean) one).compareTo((Boolean) two); } else if (one instanceof Date) { diff = ((Date) one).compareTo((Date) two); } else if (one instanceof BSONTimestamp) { diff = ((BSONTimestamp) one).compareTo((BSONTimestamp) two); } // MinKey, MaxKey, Pattern, Code, and CodeWScope aren't cast options return diff; } /** * @param obj1 BSONObject to be compared * @param obj2 BSONObject to be compared * * @return order (-1, 0, 1) Given the keys shared by both maps, find the sort order of the two maps */ public int compare(final BSONObject obj1, final BSONObject obj2) { Iterator<Entry<String, Object>> iter1 = getIterator(obj1); Iterator<Entry<String, Object>> iter2 = getIterator(obj2); while (iter1.hasNext()) { // If the key, values up to now are the same, but 2 has more elements left if (!iter2.hasNext()) { return -1; } Entry<String, Object> entry1 = iter1.next(); Entry<String, Object> entry2 = iter2.next(); // Different keys at this index int diff = entry1.getKey().compareTo(entry2.getKey()); if (diff != 0) { return diff; } // Comparing the values (could be null values) Object one = entry1.getValue(); Object two = entry2.getValue(); // For now MinKey won't be used here, so if the value is not // null, then the comparison order must be greater than nulls if (one == null && two == null) { continue; } if (one == null) { return -1; } if (two == null) { return 1; } else { // Whether they're the same type Integer oneValue = TYPES.get(one.getClass()); Integer twoValue = TYPES.get(two.getClass()); diff = oneValue.compareTo(twoValue); if (diff != 0) { return diff; } diff = compareValues(one, two); // If not the same, return immediately, else keep checking if (diff != 0) { return diff; } } } if (iter2.hasNext()) { return 1; } return 0; } @Override public int compare(final byte[] b1, final int s1, final int l1, final byte[] b2, final int s2, final int l2) { LazyBSONCallback cb = new LazyBSONCallback(); DECODER.decode(b1, cb); BSONObject a = (BSONObject) cb.get(); cb.reset(); DECODER.decode(b2, cb); BSONObject b = (BSONObject) cb.get(); return compare(a, b); } }