X-Git-Url: http://git.nikiroo.be/?a=blobdiff_plain;f=src%2Fbe%2Fnikiroo%2Fjvcard%2FBaseClass.java;h=df0fcf00de78f998ad87d03842dc4947f86a966a;hb=e4444b0bc462544629d9e7e7ab62b96a4d9cab10;hp=fe7742ab1cea0d52c1d71b77742b2ea6b7ad066a;hpb=26d2bd0591901a8d52bd24802a8d6827d0e9b833;p=jvcard.git diff --git a/src/be/nikiroo/jvcard/BaseClass.java b/src/be/nikiroo/jvcard/BaseClass.java index fe7742a..df0fcf0 100644 --- a/src/be/nikiroo/jvcard/BaseClass.java +++ b/src/be/nikiroo/jvcard/BaseClass.java @@ -2,10 +2,15 @@ package be.nikiroo.jvcard; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; import java.util.Iterator; +import java.util.LinkedList; import java.util.List; import java.util.ListIterator; +import be.nikiroo.jvcard.resources.StringUtils; + /** * 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 @@ -28,15 +33,37 @@ public abstract class BaseClass> implements List { protected BaseClass parent; private List list; + private Comparator comparator = new Comparator() { + @Override + public int compare(E o1, E o2) { + if (o1 == null && o2 == null) + return 0; + if (o1 == null && o2 != null) + return -1; + if (o1 != null && o2 == null) + return 1; + + return o1.getId().compareTo(o2.getId()); + } + }; + /** - * Create a new {@link BaseClass} with the given list as its descendants. + * Create a new {@link BaseClass} with the items in the given list as its + * descendants. + * + * Note: the elements will be copied from the {@link List}, you cannot + * manage the {@link List} from outside * * @param list * the descendants of this object, or NULL if none */ protected BaseClass(List list) { this.list = new ArrayList(); - list.addAll(list); + + if (list != null) { + this.list.addAll(list); + } + for (E child : this) { _enter(child, true); } @@ -75,22 +102,235 @@ public abstract class BaseClass> implements List { * the list of new elements */ public void replaceListContent(List list) { - List del = new ArrayList(); - List add = new ArrayList(); + List del = new LinkedList(); + List add = new LinkedList(); - for (E oldChild : this) { - if (!list.contains(oldChild)) { - del.add(oldChild); + if (!compare(list, add, del, del, add)) { + removeAll(del); + addAll(add); + } + } + + /** + * Compare the elements contained in this with those in the given + * {@link List}. It will return TRUE in case of equality, will return FALSE + * if not. + * + * If not equals, the differences will be represented by the given + * {@link List}s if they are not NULL. + *
    + *
  • addedwill represent the elements in list but not in + * this
  • + *
  • removed will represent the elements in this but not + * in list
  • + *
  • from will represent the elements in list that are + * already contained in this but are not equals to them (the + * original element from this is stored here)
  • + *
  • to will represent the elements in list that are + * already contained in this but are not equals to them (the + * changed element from list is stored here)
  • + *
+ * + * @param list + * the list of new elements + * @param added + * the list to add the added elements to, or NULL + * @param removed + * the list to add the removed elements to, or NULL + * @param from + * the map to add the from elements, or NULL + * @param to + * the map to add the to elements, or NULL + * + * @return TRUE if the elements are identical + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public boolean compare(List list, List added, List removed, + List from, List to) { + Collections.sort(this.list, comparator); + + List mine = new LinkedList(this.list); + List other = new LinkedList(list); + + Collections.sort(other, comparator); + + boolean equ = true; + E here = mine.size() > 0 ? mine.remove(0) : null; + E there = other.size() > 0 ? other.remove(0) : null; + + while (here != null || there != null) { + if (here == null + || (there != null && comparator.compare(here, there) > 0)) { + if (added != null) + added.add(there); + there = null; + equ = false; + } else if (there == null || comparator.compare(here, there) < 0) { + if (removed != null) + removed.add(here); + here = null; + equ = false; + } else { + // they represent the same item + if (!((BaseClass) here).isEquals(there, false)) { + if (from != null) + from.add(here); + if (to != null) + to.add(there); + equ = false; + } + here = null; + there = null; } + + if (here == null && mine.size() > 0) + here = mine.remove(0); + if (there == null && other.size() > 0) + there = other.remove(0); + } + + return equ; + } + + /** + * Check if the given instance and this one represent the same objects (they + * may have different states). + * + * @param other + * the other instance + * + * @return TRUE if they represent the same object + */ + public boolean isSame(BaseClass other) { + if (other == null) + return false; + + if (!getClass().equals(other.getClass())) + return false; + + return getId().equals(other.getId()); + } + + /** + * Check if the given instance and this one are equivalent (both objects in + * the same state, all child elements equivalent). + * + * @param other + * the other instance + * + * @param contentOnly + * do not check the state of the object itslef, only its content + * + * @return TRUE if they are equivalent + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public boolean isEquals(BaseClass other, boolean contentOnly) { + if (other == null) + return false; + + if (size() != other.size()) + return false; + + if (!contentOnly) { + if (!isSame(other)) + return false; + + if (!getState().equals(other.getState())) + return false; } - for (E newChild : list) { - if (!contains(newChild)) { - add.add(newChild); + + Collections.sort(list, comparator); + Collections.sort(other.list, other.comparator); + for (int index = 0; index < size(); index++) { + if (!((BaseClass) get(index)).isEquals(other.get(index), false)) + return false; + } + + return true; + } + + /** + * Get the recursive state of the current object, i.e., its children. It + * represents the full state information about this object's children. It + * may not contains spaces nor new lines. + * + *

+ * Not that this state is lossy. You cannot retrieve the data from + * the state, it can only be used as an ID to check if thw data are + * identical. + *

+ * + * @return a {@link String} representing the current content state of this + * object, i.e., its children included + */ + public String getContentState() { + StringBuilder builder = new StringBuilder(); + buildContentStateRaw(builder); + return StringUtils.getHash(builder.toString()); + } + + /** + * Return the (first) child element with the given ID or NULL if not found. + * + * @param id + * the id to look for + * + * @return the child element or NULL + */ + public E getById(String id) { + for (E child : this) { + if (id == null) { + if (child.getId() == null) + return child; + } else { + if (id.equals(child.getId())) + return child; } } - removeAll(del); - addAll(add); + return null; + } + + /** + * Return the current ID of this object -- it is allowed to change over time + * (so, do not cache it). + * + * @return the current ID + */ + abstract public String getId(); + + /** + * Get the state of the current object, children not included. It + * represents the full state information about this object, but do not check + * its children (see {@link BaseClass#getContentState()} for that). It may + * not contains spaces nor new lines. + * + *

+ * Not that this state is lossy. You cannot retrieve the data from + * the state, it can only be used as an ID to check if thw data are + * identical. + *

+ * + * @return a {@link String} representing the current state of this object, + * children not included + */ + abstract public String getState(); + + /** + * Get the recursive state of the current object, i.e., its children. It + * represents the full state information about this object's children. + * + * It is not hashed. + * + * @param builder + * the {@link StringBuilder} that will represent the current + * content state of this object, i.e., its children included + */ + void buildContentStateRaw(StringBuilder builder) { + builder.append(getState()); + for (E child : this) { + child.buildContentStateRaw(builder); + } } /** @@ -98,6 +338,9 @@ public abstract class BaseClass> implements List { */ void setDirty() { dirty = true; + if (parent != null) { + parent.setDirty(); + } } /** @@ -152,8 +395,10 @@ public abstract class BaseClass> implements List { */ private void _enter(E child, boolean initialLoad) { child.setParent(this); - if (!initialLoad) + if (!initialLoad) { + setDirty(); child.setDirty(); + } } @Override