/* Wotonomy: OpenStep design patterns for pure Java applications. Copyright (C) 2000 Michael Powers This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, see http://www.gnu.org */ package net.wotonomy.datastore; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import java.util.Observable; public class DefaultDataView extends Observable implements DataView { protected DataSoup backingSoup; protected List objectList; protected List keyList; protected List addedObjectList; protected List removedObjectList; protected List addedKeyList; protected List removedKeyList; protected Collection updatedObjects; protected boolean fullyLoaded; DefaultDataView(DataSoup aSoup, Collection aKeyList) { backingSoup = aSoup; objectList = new ArrayList(); keyList = new ArrayList(); addedObjectList = new ArrayList(); removedObjectList = new ArrayList(); addedKeyList = new ArrayList(); removedKeyList = new ArrayList(); updatedObjects = new LinkedList(); fullyLoaded = false; setKeyList(aKeyList); } void setKeyList(Collection aCollection) { fullyLoaded = false; addedObjectList.clear(); removedObjectList.clear(); addedKeyList.clear(); removedKeyList.clear(); updatedObjects.clear(); keyList.clear(); objectList.clear(); if ((aCollection == null) || (aCollection.size() == 0)) { return; } keyList.addAll(aCollection); for (int i = 0; i < keyList.size(); i++) { objectList.add(null); } } public Object get(int i) { if (i > keyList.size()) return null; Object o = objectList.get(i); if (o == null) { Object key = keyList.get(i); if (key == null) return null; // FIXME!! // NOTE: this is the gateway for getting object from the soup o = backingSoup.getObjectByKey((DataKey) key); objectList.set(i, o); } return o; } public int indexOf(Object o) { if (o == null) return -1; for (int i = 0; i < size(); i++) { if (o.equals(get(i))) { return i; } } return -1; } private int indexOfIdenticalObject(Object o) { if (o == null) return -1; for (int i = 0; i < size(); i++) { if (o == get(i)) { return i; } } return -1; } public int lastIndexOf(Object o) { if (o == null) return -1; int lastIndex = -1; for (int i = 0; i < size(); i++) { if (o.equals(get(i))) { lastIndex = i; } } return lastIndex; } protected void loadAllObjects() { if (fullyLoaded) return; for (int i = 0; i < keyList.size(); i++) { get(i); } fullyLoaded = true; } // convenience to return the first object, or null. public Object getObject() { return get(0); } // marked object as updated public void update(Object anObject) { if (contains(anObject)) { if (!addedObjectList.contains(anObject)) { updatedObjects.add(anObject); } } // notification setChanged(); notifyObservers(anObject); } // DefaultDataViews know their parent soup to perform the query // and take the subset public DataView query(String aProperty, Object beginKey, Object endKey) { return this; } public boolean commit() { int index; Object o; DataKey key; for (int i = 0; i < addedObjectList.size(); i++) { o = addedObjectList.get(i); key = backingSoup.addObject(o); index = indexOfIdenticalObject(o); keyList.set(index, key); } addedObjectList.clear(); addedKeyList.clear(); for (int i = 0; i < removedObjectList.size(); i++) { backingSoup.removeObject((DataKey) removedKeyList.get(i)); } removedObjectList.clear(); removedKeyList.clear(); int i; Iterator it = updatedObjects.iterator(); while (it.hasNext()) { i = objectList.indexOf(it.next()); backingSoup.updateObject((DataKey) keyList.get(i), objectList.get(i)); } updatedObjects.clear(); // notification setChanged(); notifyObservers(this); return true; } public DataKey getKeyForObject(Object anObject) { int index = indexOfIdenticalObject(anObject); if (index == -1) return null; return (DataKey) keyList.get(index); } public Object getObjectForKey(DataKey aKey) { int index = keyList.indexOf(aKey); if (index == -1) return null; return get(index); } // interface Collection public int size() { return keyList.size(); } public boolean isEmpty() { return keyList.isEmpty(); } public void clear() { setKeyList(null); }; public int hashCode() { return keyList.hashCode(); }; public boolean equals(Object o) { if (!(o instanceof DefaultDataView)) return false; return keyList.equals(((DefaultDataView) o).keyList); } public boolean contains(Object o) { loadAllObjects(); return objectList.contains(o); } public boolean containsAll(Collection c) { loadAllObjects(); return objectList.containsAll(c); } public boolean add(Object o) { // if previously removed, restore to list if (removedObjectList.contains(o)) { int index = removedObjectList.indexOf(o); removedObjectList.remove(index); Object key = removedKeyList.remove(index); objectList.add(o); keyList.add(key); // notification setChanged(); notifyObservers(o); return true; } // register and add to lists Object key = backingSoup.registerTemporaryObject(o); addedObjectList.add(o); addedKeyList.add(key); objectList.add(o); keyList.add(key); // notification setChanged(); notifyObservers(o); return true; } public Object remove(int index) { Object result = get(index); if (remove(result)) { return result; } return null; } public boolean remove(Object o) { loadAllObjects(); int index = objectList.indexOf(o); if (index == -1) return false; objectList.remove(index); Object key = keyList.remove(index); if (updatedObjects.contains(o)) { updatedObjects.remove(o); } // if not previously added, track removal if (!(removedObjectList.contains(o))) { removedObjectList.add(o); removedKeyList.add(key); } // notification setChanged(); notifyObservers(o); return true; } /** * Set completely replaces the object at the specified index with the specified * object. The new object is not marked as inserted, and the old object is not * marked as deleted: the new object will be stored in the soup with the same * key. The old object is returned. */ public Object set(int index, Object element) { Object result = objectList.set(index, element); update(element); return result; } public void add(int index, Object o) { // if previously removed, restore to list if (removedObjectList.contains(o)) { int i = removedObjectList.indexOf(o); removedObjectList.remove(i); Object key = removedKeyList.remove(i); objectList.add(index, o); keyList.add(index, key); // notification setChanged(); notifyObservers(o); return; } // register and add to lists Object key = backingSoup.registerTemporaryObject(o); addedObjectList.add(o); addedKeyList.add(key); objectList.add(index, o); keyList.add(index, key); // notification setChanged(); notifyObservers(o); } public boolean addAll(Collection c) { boolean result = true; Iterator it = c.iterator(); while (it.hasNext()) { result = result && add(it.next()); } return result; } public boolean addAll(int index, Collection c) { int originalSize = size(); boolean result = true; Iterator it = c.iterator(); while (it.hasNext()) { add(index, it.next()); } return (originalSize + c.size() == size()); } public boolean removeAll(Collection c) { boolean result = true; Iterator it = c.iterator(); while (it.hasNext()) { result = result && remove(it.next()); } return result; } public boolean retainAll(Collection c) { removeAll(new ArrayList(objectList)); return addAll(c); } public List subList(int fromIndex, int toIndex) { List result = new LinkedList(); for (int i = fromIndex; i < toIndex; i++) { result.add(get(i)); } return result; } public Iterator iterator() { loadAllObjects(); return objectList.iterator(); /* * // uncomment to enable on-demand loading return new Iterator() { int index = * 0; public boolean hasNext() { return ( index + 1 < keyList.size() ); } public * Object next() { return get( index++ ); } public void remove() { Object o = * get( index ); if ( o != null ) DefaultDataView.this.remove( o ); } }; */ } public ListIterator listIterator() { return new DataViewIterator(this); } public ListIterator listIterator(int index) { return new DataViewIterator(this, index); } public Object[] toArray() { loadAllObjects(); return objectList.toArray(); } public java.lang.Object[] toArray(Object[] array) { loadAllObjects(); return objectList.toArray(array); } protected class DataViewIterator implements ListIterator { DataView theView; int currentIndex; // TODO: should track current object in addition to index // to track external changes to the view. (or should be listener) Object currentObject; public DataViewIterator(DataView aView) { this(aView, 0); } public DataViewIterator(DataView aView, int index) { theView = aView; if (theView.size() > index) { currentIndex = index; currentObject = theView.get(currentIndex); } else { index = -1; currentObject = null; } } public void add(Object o) { currentIndex++; theView.add(currentIndex, o); } public boolean hasNext() { return (theView.size() > currentIndex + 1); } public boolean hasPrevious() { return (currentIndex > -1); } public Object next() { return theView.get(++currentIndex); } public int nextIndex() { return currentIndex + 1; } public Object previous() { return theView.get(currentIndex--); } public int previousIndex() { return currentIndex; } public void remove() { theView.remove(currentIndex--); } public void set(Object o) { theView.set(currentIndex, o); } } } /* * $Log$ Revision 1.2 2006/02/19 16:26:19 cgruber Move non-unit-test code to * tests project Fix up code to work with proper imports Fix maven dependencies. * * Revision 1.1 2006/02/16 13:18:56 cgruber Check in all sources in * eclipse-friendly maven-enabled packages. * * Revision 1.2 2001/02/15 21:12:41 mpowers Added accessors for key throughout * the api. This breaks compatibility. insertObject now returns the permanent * key for the newly created object. The old way returned a copy of the object * which was an additional read that was often ignored. Now you can read it only * if you need it. Furthermore, there was not other way of getting the permanent * key. * * Revision 1.1.1.1 2000/12/21 15:47:14 mpowers Contributing wotonomy. * * Revision 1.2 2000/12/20 16:25:36 michael Added log to all files. * * */