/** * Copyright (C) 2009 Future Invent Informationsmanagement GmbH. All rights * reserved. * * 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 3 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 . */ package org.fuin.utils4j; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.ListIterator; /** * A wrapper for lists that keeps track of all changes made to the list since * construction. Only adding, replacing or deleting elements is tracked (not * changes inside the objects). Duplicates elements are not allowed for the list * - This is like a {@link java.util.Set} but at the same time ordered like a {@link List} * . It's also possible to revert all changes. */ public class ChangeTrackingUniqueList implements List, Taggable { private final List list; private final List added; private final List deleted; private boolean tagged; /** * Constructor with covered list. The list is tagged at construction time - * This means {@link #isTagged()} will return true without * calling {@link #tag()} first. If this behavior is not wanted you can call * {@link #untag()} after constructing the list. * * @param list * Wrapped list - Be aware that this list will be changed by this * class. There is no internal copy of the list - The reference * itself is used. */ public ChangeTrackingUniqueList(final List list) { super(); Utils4J.checkNotNull("list", list); this.list = list; this.added = new ArrayList(); this.deleted = new ArrayList(); tagged = true; } /** * Returns if the list has changed. If the list is not in tag mode (this * means {@link #isTagged()} returns true) this method will * always return false. * * @return If elements have been added or deleted true else * false. */ public final boolean isChanged() { return (added.size() > 0) || (deleted.size() > 0); } /** * Returns deleted elements. If the list is not in tag mode (this means * {@link #isTagged()} returns true) this method will always * return an empty list. * * @return Elements that have been deleted since construction of this * instance - Unmodifiable list! */ public final List getDeleted() { return Collections.unmodifiableList(deleted); } /** * Returns added elements. If the list is not in tag mode (this means * {@link #isTagged()} returns true) this method will always * return an empty list. * * @return Elements that have been added since construction of this instance * - Unmodifiable list! */ public final List getAdded() { return Collections.unmodifiableList(added); } /** * Roll back all changes made since construction. WARNING: The position of * the elements is not guaranteed to be the same again! This is the * same function ad {@link #revertToTag()}. If the list is not in tag mode ( * this means {@link #isTagged()} returns true) this method * will do nothing. */ public final void revert() { if (tagged) { // Remove the added entries final Iterator addedIt = added.iterator(); while (addedIt.hasNext()) { final Object entry = addedIt.next(); list.remove(entry); addedIt.remove(); } // Add the removed entries final Iterator removedIt = deleted.iterator(); while (removedIt.hasNext()) { final Object entry = removedIt.next(); list.add(entry); removedIt.remove(); } } } private void addIntern(final Object o) { if (tagged) { final int idx = deleted.indexOf(o); if (idx == -1) { added.add(o); } else { deleted.remove(idx); } } } /** * {@inheritDoc} */ public final boolean add(final Object o) { if (list.contains(o)) { throw new IllegalArgumentException("The argument is already in the list: " + o); } final boolean b = list.add(o); if (b) { addIntern(o); } return b; } /** * {@inheritDoc} */ public final void add(final int index, final Object o) { if (list.contains(o)) { throw new IllegalArgumentException("The argument is already in the list: " + o); } list.add(index, o); addIntern(o); } /** * {@inheritDoc} */ public final boolean addAll(final Collection c) { int count = 0; final Iterator it = c.iterator(); while (it.hasNext()) { if (add(it.next())) { count++; } } return (count > 0); } /** * {@inheritDoc} */ public final boolean addAll(final int index, final Collection c) { int count = 0; final Iterator it = c.iterator(); while (it.hasNext()) { add(index + count, it.next()); count++; } return (count > 0); } /** * {@inheritDoc} */ public final void clear() { for (int i = 0; i < list.size(); i++) { final Object o = list.get(i); if (tagged && !added.contains(o)) { deleted.add(o); } } if (tagged) { added.clear(); } list.clear(); } /** * {@inheritDoc} */ public final boolean contains(final Object o) { return list.contains(o); } /** * {@inheritDoc} */ public final boolean containsAll(final Collection c) { return list.containsAll(c); } /** * {@inheritDoc} */ public final Object get(final int index) { return list.get(index); } /** * {@inheritDoc} */ public final int indexOf(final Object o) { return list.indexOf(o); } /** * {@inheritDoc} */ public final boolean isEmpty() { return list.isEmpty(); } /** * {@inheritDoc} */ public final Iterator iterator() { return list.iterator(); } /** * {@inheritDoc} */ public final int lastIndexOf(final Object o) { return list.lastIndexOf(o); } /** * {@inheritDoc} */ public final ListIterator listIterator() { return list.listIterator(); } /** * {@inheritDoc} */ public final ListIterator listIterator(final int index) { return list.listIterator(index); } private void removeIntern(final Object o) { if (tagged) { final int idx = added.indexOf(o); if (idx == -1) { deleted.add(o); } else { added.remove(idx); } } } /** * {@inheritDoc} */ public final boolean remove(final Object o) { final boolean b = list.remove(o); if (b) { removeIntern(o); } return b; } /** * {@inheritDoc} */ public final Object remove(final int index) { final Object o = list.remove(index); if (o != null) { removeIntern(o); } return o; } /** * {@inheritDoc} */ public final boolean removeAll(final Collection c) { boolean changed = false; final Iterator it = c.iterator(); while (it.hasNext()) { final Object o = it.next(); if (remove(o)) { changed = true; } } return changed; } /** * {@inheritDoc} */ public final boolean retainAll(final Collection c) { boolean changed = false; for (int i = list.size() - 1; i >= 0; i--) { final Object o = list.get(i); if (!c.contains(o)) { remove(i); changed = true; } } return changed; } /** * {@inheritDoc} */ public final Object set(final int index, final Object o) { final Object removed = list.set(index, o); addIntern(o); removeIntern(removed); return removed; } /** * {@inheritDoc} */ public final int size() { return list.size(); } /** * {@inheritDoc} */ public final List subList(final int fromIndex, final int toIndex) { return list.subList(fromIndex, toIndex); } /** * {@inheritDoc} */ public final Object[] toArray() { return list.toArray(); } /** * {@inheritDoc} */ public final Object[] toArray(final Object[] a) { return list.toArray(a); } /** * {@inheritDoc} */ public final String toString() { return list.toString(); } /** * {@inheritDoc} */ public final boolean hasChangedSinceTagging() { return isChanged(); } /** * {@inheritDoc} */ public final boolean isTagged() { return tagged; } /** * {@inheritDoc} */ public final void revertToTag() { revert(); } /** * {@inheritDoc} */ public final void tag() { if (!tagged) { tagged = true; } } /** * {@inheritDoc} */ public final void untag() { if (tagged) { tagged = false; added.clear(); deleted.clear(); } } }