/* * Copyright (c) 2011-2014 Jeppetto and Jonathan Thompson * * 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 org.iternine.jeppetto.dao.mongodb.enhance; import org.iternine.jeppetto.dao.JeppettoException; import com.mongodb.DBCollection; import org.bson.BSONObject; import org.bson.util.StringRangeSet; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.Set; @SuppressWarnings({ "unchecked" }) public class DirtyableDBObjectList implements List, DirtyableDBObject { //------------------------------------------------------------- // Variables - Private //------------------------------------------------------------- private List delegate; private boolean rewrite = false; private Set<Integer> modifiedIndexes = new HashSet<Integer>(); private int firstAppendedIndex; private boolean modifiableDelegate; private DBCollection persistentCollection; //------------------------------------------------------------- // Constructors //------------------------------------------------------------- /** * Default constructor that uses an ArrayList as the delegate and expects no non-Jeppetto * access. */ public DirtyableDBObjectList() { this(new ArrayList(), false); } /** * Constructor that takes is passed the delegate list along w/ an indication as to * whether the delegate is modifiable by code outside of Jeppetto. * * @param delegate the underlying List implementation * @param modifiableDelegate true if access is possible to the delegate by non-Jeppetto code */ public DirtyableDBObjectList(List delegate, boolean modifiableDelegate) { this.delegate = delegate; this.firstAppendedIndex = delegate.size(); this.modifiableDelegate = modifiableDelegate; } //------------------------------------------------------------- // Implementation - List //------------------------------------------------------------- @Override public void add(int index, Object element) { rewrite |= index < delegate.size(); delegate.add(index, element); } @Override public boolean addAll(int index, Collection elements) { rewrite |= index < delegate.size(); return delegate.addAll(index, elements); } @Override public Object remove(int index) { Object removed = delegate.remove(index); rewrite |= index < firstAppendedIndex; return removed; } @Override public boolean removeAll(Collection collection) { boolean changed = delegate.removeAll(collection); rewrite |= changed; return changed; } @Override public Object set(int index, Object element) { if (index < firstAppendedIndex) { modifiedIndexes.add(index); } return delegate.set(index, element); } @Override public boolean add(Object element) { return delegate.add(element); } @Override public boolean remove(Object object) { boolean changed = delegate.remove(object); rewrite |= changed; return changed; } @Override public boolean addAll(Collection elements) { return delegate.addAll(elements); } @Override public boolean retainAll(Collection collection) { boolean changed = delegate.retainAll(collection); rewrite |= changed; return changed; } @Override public void clear() { rewrite = true; delegate.clear(); } @Override public int size() { return delegate.size(); } @Override public boolean isEmpty() { return delegate.isEmpty(); } @Override public boolean contains(Object o) { return delegate.contains(o); } @Override public Iterator iterator() { return listIterator(0); } @Override public Object[] toArray() { // TODO: Convert to DirtyableDBObjects return delegate.toArray(); } @Override public Object[] toArray(Object[] objects) { // TODO: Convert to DirtyableDBObjects return delegate.toArray(objects); } @Override public boolean containsAll(Collection objects) { return delegate.containsAll(objects); } @Override public Object get(int index) { Object element = delegate.get(index); if (element == null || element instanceof DirtyableDBObject || DBObjectUtil.needsNoConversion(element.getClass())) { return element; } // TODO: revisit whether these semantics makes sense Object converted = DBObjectUtil.toDBObject(element); delegate.set(index, converted); return converted; } @Override public int indexOf(Object o) { return delegate.indexOf(o); } @Override public int lastIndexOf(Object o) { return delegate.lastIndexOf(o); } @Override public ListIterator listIterator() { return listIterator(0); } @Override public ListIterator listIterator(final int index) { return new ListIterator() { private ListIterator delegateIterator = delegate.listIterator(index); private int modifiableIndex = -1; @Override public boolean hasNext() { return delegateIterator.hasNext(); } @Override public Object next() { modifiableIndex = delegateIterator.nextIndex(); Object next = delegateIterator.next(); if (next instanceof DirtyableDBObject || next == null || DBObjectUtil.needsNoConversion(next.getClass())) { return next; } Object converted = DBObjectUtil.toDBObject(next); delegate.set(modifiableIndex, converted); return converted; } @Override public boolean hasPrevious() { return delegateIterator.hasPrevious(); } @Override public Object previous() { modifiableIndex = delegateIterator.previousIndex(); Object previous = delegateIterator.previous(); if (previous instanceof DirtyableDBObject || previous == null || DBObjectUtil.needsNoConversion(previous.getClass())) { return previous; } Object converted = DBObjectUtil.toDBObject(previous); delegate.set(modifiableIndex, converted); return converted; } @Override public int nextIndex() { return delegateIterator.nextIndex(); } @Override public int previousIndex() { return delegateIterator.previousIndex(); } @Override public void remove() { delegateIterator.remove(); rewrite |= modifiableIndex < firstAppendedIndex; } @Override public void set(Object o) { delegateIterator.set(o); // If items >= firstAppendedIndex are removed, the modifiedIndexes value will still be correct. // If items below it are removed, rewrite will be set to true and this will be ignored. modifiedIndexes.add(modifiableIndex); } @Override public void add(Object o) { throw new UnsupportedOperationException(); } }; } @Override public List subList(int fromIndex, int toIndex) { return delegate.subList(fromIndex, toIndex); // TODO: Need to track changes in the sublist } //------------------------------------------------------------- // Implementation - DirtyableDBObject //------------------------------------------------------------- @Override public boolean isDirty() { return rewrite || delegate.size() > firstAppendedIndex || !modifiedIndexes.isEmpty() || getDirtyKeys().hasNext(); } @Override public void markPersisted(DBCollection dbCollection) { for (Object object : delegate) { if (!(object instanceof DirtyableDBObject)) { continue; } DirtyableDBObject dirtyableDBObject = (DirtyableDBObject) object; dirtyableDBObject.markPersisted(dbCollection); } rewrite = false; modifiedIndexes.clear(); firstAppendedIndex = delegate.size(); persistentCollection = dbCollection; } @Override public boolean isPersisted(DBCollection dbCollection) { return dbCollection.equals(persistentCollection); } @Override public Iterator<String> getDirtyKeys() { return new Iterator<String>() { private Iterator delegateIterator = delegate.iterator(); private int i = -1; @Override public boolean hasNext() { while (delegateIterator.hasNext()) { Object object = delegateIterator.next(); if (++i >= firstAppendedIndex || modifiedIndexes.contains(i) || !(object instanceof DirtyableDBObject) || ((DirtyableDBObject) object).isDirty()) { return true; } } return false; } @Override public String next() { return Integer.toString(i); } @Override public void remove() { throw new JeppettoException("Can't remove items from dirtyKeys"); } }; } @Override public Object getDelegate() { return delegate; } //------------------------------------------------------------- // Implementation - DBObject //------------------------------------------------------------- @Override public void markAsPartialObject() { throw new JeppettoException("Can't mark DirtyableDBObjectList as partial"); } @Override public boolean isPartialObject() { return false; } @Override public Set<String> keySet() { return new StringRangeSet(delegate.size()); } @Override public boolean containsField(String s) { return getNonNegativeInt(s) < size(); } @Override public boolean containsKey(String s) { return containsField(s); } @Override public Object removeField(String s) { int i = getNonNegativeInt(s); if (i >= size()) { return null; } return remove(i); } @Override public Map toMap() { Map result = new HashMap(); for (String key : keySet()) { result.put(key, get(key)); } return result; } @Override public Object get(String s) { return get(getNonNegativeInt(s)); } @Override public void putAll(Map m) { for (Map.Entry entry : (Set<Map.Entry>) m.entrySet()) { put(entry.getKey().toString(), entry.getValue() ); } } @Override public void putAll(BSONObject o) { for (String k : o.keySet()) { put(k, o.get(k)); } } @Override public Object put(String s, Object v) { int i = getNonNegativeInt(s); while (i > size()) { delegate.add(null); } delegate.add(v); return v; } //------------------------------------------------------------- // Methods - Public //------------------------------------------------------------- public boolean isRewrite() { return rewrite; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof List)) { return false; } List thatList = o instanceof DirtyableDBObjectList ? ((DirtyableDBObjectList) o).delegate : (List) o; return delegate.equals(thatList); } @Override public int hashCode() { return delegate.hashCode(); } //------------------------------------------------------------- // Methods - Private //------------------------------------------------------------- private int getNonNegativeInt(String s) { int i; try { i = Integer.parseInt(s); } catch (NumberFormatException e) { throw new IllegalArgumentException("Unable to handle non-numeric value " + s); } if (i < 0) { throw new IllegalArgumentException(s + " is an invalid index value"); } return i; } }