+ @Override
+ public boolean equals(final Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (o == null) {
+ return false;
+ }
+
+ if (o instanceof ImmutableOffsetMap) {
+ final ImmutableOffsetMap<?, ?> om = (ImmutableOffsetMap<?, ?>) o;
+ if (newKeys.isEmpty() && offsets == om.offsets() && Arrays.deepEquals(objects, om.objects())) {
+ return true;
+ }
+ } else if (o instanceof MutableOffsetMap) {
+ final MutableOffsetMap<?, ?> om = (MutableOffsetMap<?, ?>) o;
+ if (offsets == om.offsets && Arrays.deepEquals(objects, om.objects) && newKeys.equals(om.newKeys)) {
+ return true;
+ }
+ } else if (o instanceof Map) {
+ final Map<?, ?> om = (Map<?, ?>)o;
+
+ // Size and key sets have to match
+ if (size() != om.size() || !keySet().equals(om.keySet())) {
+ return false;
+ }
+
+ try {
+ // Ensure all newKeys are present. Note newKeys is guaranteed to
+ // not contain null value.
+ for (Entry<K, V> e : newKeys.entrySet()) {
+ if (!e.getValue().equals(om.get(e.getKey()))) {
+ return false;
+ }
+ }
+
+ // Ensure all objects are present
+ for (Entry<K, Integer> e : offsets.entrySet()) {
+ final Object obj = objects[e.getValue()];
+ if (!NO_VALUE.equals(obj)) {
+ final V v = objectToValue(e.getKey(), obj);
+ if (!v.equals(om.get(e.getKey()))) {
+ return false;
+ }
+ }
+ }
+ } catch (ClassCastException e) {
+ // Can be thrown by om.get() and indicate we have incompatible key types
+ return false;
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public final Set<K> keySet() {
+ return new KeySet();
+ }
+