Fix version, move list management from Card/Contact/... into BaseClass
[jvcard.git] / src / be / nikiroo / jvcard / BaseClass.java
diff --git a/src/be/nikiroo/jvcard/BaseClass.java b/src/be/nikiroo/jvcard/BaseClass.java
new file mode 100644 (file)
index 0000000..fe7742a
--- /dev/null
@@ -0,0 +1,368 @@
+package be.nikiroo.jvcard;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+
+/**
+ * This class is basically a List with a parent and a "dirty" state check. It
+ * sends all commands down to the initial list, but will mark itself and its
+ * children as dirty or not when needed.
+ * 
+ * All child elements can identify their parent.
+ * 
+ * The dirty state is bubbling up (when dirty = true) or down (when dirty =
+ * false) -- so, making changes to a child element will also mark its parent as
+ * "dirty", and marking an element as pristine will also affect all its child
+ * elements.
+ * 
+ * @author niki
+ *
+ * @param <E>
+ *            the type of the child elements
+ */
+public abstract class BaseClass<E extends BaseClass<?>> implements List<E> {
+       protected boolean dirty;
+       protected BaseClass<?> parent;
+       private List<E> list;
+
+       /**
+        * Create a new {@link BaseClass} with the given list as its descendants.
+        * 
+        * @param list
+        *            the descendants of this object, or NULL if none
+        */
+       protected BaseClass(List<E> list) {
+               this.list = new ArrayList<E>();
+               list.addAll(list);
+               for (E child : this) {
+                       _enter(child, true);
+               }
+       }
+
+       /**
+        * Check if this element has unsaved changes.
+        * 
+        * @return TRUE if it has
+        */
+       public boolean isDirty() {
+               return dirty;
+       }
+
+       /**
+        * Delete this element from its parent if any.
+        * 
+        * @return TRUE in case of success
+        */
+       public boolean delete() {
+               if (parent != null) {
+                       return parent.remove(this);
+               }
+
+               return false;
+       }
+
+       /**
+        * Replace the elements contained in this with those in the given
+        * {@link List}.
+        * 
+        * Note: the elements will be copied from the {@link List}, you cannot
+        * manage the {@link List} from outside
+        * 
+        * @param list
+        *            the list of new elements
+        */
+       public void replaceListContent(List<E> list) {
+               List<E> del = new ArrayList<E>();
+               List<E> add = new ArrayList<E>();
+
+               for (E oldChild : this) {
+                       if (!list.contains(oldChild)) {
+                               del.add(oldChild);
+                       }
+               }
+               for (E newChild : list) {
+                       if (!contains(newChild)) {
+                               add.add(newChild);
+                       }
+               }
+
+               removeAll(del);
+               addAll(add);
+       }
+
+       /**
+        * Notify that this element has unsaved changes.
+        */
+       void setDirty() {
+               dirty = true;
+       }
+
+       /**
+        * Notify this element <i>and all its descendants</i> that it is in pristine
+        * state (as opposed to dirty).
+        */
+       void setPristine() {
+               dirty = false;
+               for (E child : this) {
+                       child.setPristine();
+               }
+       }
+
+       /**
+        * Set the parent of this element <i>and all its descendants</i>.
+        * 
+        * @param parent
+        *            the new parent
+        */
+       void setParent(BaseClass<?> parent) {
+               this.parent = parent;
+               for (E child : this) {
+                       child.setParent(this);
+               }
+       }
+
+       /**
+        * Each element that leaves the parent will pass trough here.
+        * 
+        * @param child
+        *            the element to remove from this
+        */
+       private void _leave(E child) {
+               setDirty();
+       }
+
+       /**
+        * Each element that enters the parent will pass trough here.
+        * 
+        * @param child
+        *            the element to add to this
+        */
+       private void _enter(E child) {
+               _enter(child, false);
+       }
+
+       /**
+        * Each element that enters the parent will pass trough here.
+        * 
+        * @param child
+        *            the element to add to this
+        */
+       private void _enter(E child, boolean initialLoad) {
+               child.setParent(this);
+               if (!initialLoad)
+                       child.setDirty();
+       }
+
+       @Override
+       public boolean add(E e) {
+               _enter(e, false);
+               return list.add(e);
+       }
+
+       @Override
+       @SuppressWarnings("unchecked")
+       public boolean remove(Object o) {
+               if (list.remove(o)) {
+                       if (o instanceof BaseClass<?>) {
+                               _leave((E) o); // expected warning
+                       }
+                       return true;
+               }
+
+               return false;
+       }
+
+       @Override
+       public boolean addAll(Collection<? extends E> c) {
+               for (E child : c) {
+                       _enter(child);
+               }
+
+               return list.addAll(c);
+       }
+
+       @Override
+       public boolean addAll(int index, Collection<? extends E> c) {
+               for (E child : c) {
+                       _enter(child);
+               }
+
+               return list.addAll(index, c);
+       }
+
+       @Override
+       public boolean removeAll(Collection<?> c) {
+               boolean changed = false;
+
+               for (Object o : c) {
+                       if (remove(o))
+                               changed = true;
+               }
+
+               return changed;
+       }
+
+       @Override
+       public boolean retainAll(Collection<?> c) {
+               ArrayList<Object> del = new ArrayList<Object>();
+               for (Object o : c) {
+                       del.add(o);
+               }
+               return removeAll(del);
+       }
+
+       @Override
+       public void clear() {
+               for (E child : this) {
+                       _leave(child);
+               }
+
+               list.clear();
+       }
+
+       @Override
+       public E set(int index, E element) {
+               E child = get(index);
+               if (child != null)
+                       _leave(child);
+               _enter(element);
+
+               return list.set(index, element);
+       }
+
+       @Override
+       public void add(int index, E element) {
+               _enter(element);
+               list.add(index, element);
+       }
+
+       @Override
+       public E remove(int index) {
+               E child = get(index);
+               _leave(child);
+               return list.remove(index);
+       }
+
+       @Override
+       public Iterator<E> iterator() {
+               return listIterator(0);
+       }
+
+       @Override
+       public ListIterator<E> listIterator() {
+               return listIterator(0);
+       }
+
+       @Override
+       public ListIterator<E> listIterator(int index) {
+               final int i = index;
+               return new ListIterator<E>() {
+                       ListIterator<E> base = list.listIterator(i);
+                       E last;
+
+                       @Override
+                       public boolean hasNext() {
+                               return base.hasNext();
+                       }
+
+                       @Override
+                       public E next() {
+                               last = base.next();
+                               return last;
+                       }
+
+                       @Override
+                       public boolean hasPrevious() {
+                               return base.hasPrevious();
+                       }
+
+                       @Override
+                       public E previous() {
+                               last = base.previous();
+                               return last;
+                       }
+
+                       @Override
+                       public int nextIndex() {
+                               return base.nextIndex();
+                       }
+
+                       @Override
+                       public int previousIndex() {
+                               return base.previousIndex();
+                       }
+
+                       @Override
+                       public void remove() {
+                               base.remove();
+                               _leave(last);
+                       }
+
+                       @Override
+                       public void set(E e) {
+                               base.set(e);
+                               _leave(last);
+                               _enter(e);
+                       }
+
+                       @Override
+                       public void add(E e) {
+                               base.add(e);
+                               _enter(e);
+                       }
+               };
+       }
+
+       @Override
+       public Object[] toArray() {
+               return list.toArray();
+       }
+
+       @Override
+       public <T> T[] toArray(T[] a) {
+               return list.toArray(a);
+       }
+
+       @Override
+       public int size() {
+               return list.size();
+       }
+
+       @Override
+       public boolean isEmpty() {
+               return list.isEmpty();
+       }
+
+       @Override
+       public boolean contains(Object o) {
+               return list.contains(o);
+       }
+
+       @Override
+       public boolean containsAll(Collection<?> c) {
+               return list.containsAll(c);
+       }
+
+       @Override
+       public E get(int index) {
+               return list.get(index);
+       }
+
+       @Override
+       public int indexOf(Object o) {
+               return list.indexOf(o);
+       }
+
+       @Override
+       public int lastIndexOf(Object o) {
+               return list.lastIndexOf(o);
+       }
+
+       @Override
+       public List<E> subList(int fromIndex, int toIndex) {
+               return list.subList(fromIndex, toIndex);
+       }
+}