From e253bd50bb05519f4a16fed4fb95d5b3340128ea Mon Sep 17 00:00:00 2001 From: Niki Roo Date: Sat, 12 Mar 2016 13:23:17 +0100 Subject: [PATCH] Add some comparisons methods, fix the existing checks --- src/be/nikiroo/jvcard/BaseClass.java | 173 +++++++++++++++++++++++++-- src/be/nikiroo/jvcard/Card.java | 17 ++- src/be/nikiroo/jvcard/Contact.java | 22 +++- src/be/nikiroo/jvcard/Data.java | 10 ++ src/be/nikiroo/jvcard/TypeInfo.java | 14 ++- 5 files changed, 215 insertions(+), 21 deletions(-) diff --git a/src/be/nikiroo/jvcard/BaseClass.java b/src/be/nikiroo/jvcard/BaseClass.java index cb07948..b6be10e 100644 --- a/src/be/nikiroo/jvcard/BaseClass.java +++ b/src/be/nikiroo/jvcard/BaseClass.java @@ -2,7 +2,10 @@ 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; @@ -28,6 +31,20 @@ 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 items in the given list as its * descendants. @@ -83,24 +100,158 @@ 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); } - for (E newChild : list) { - if (!contains(newChild)) { - add.add(newChild); + } + + /** + * 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; + while (mine.size() > 0 || other.size() > 0) { + E here = (mine.size() > 0) ? mine.remove(0) : null; + E there = (other.size() > 0) ? other.remove(0) : null; + + if (here == null || comparator.compare(here, there) > 0) { + if (added != null) + added.add(there); + equ = false; + } else if (there == null || comparator.compare(here, there) < 0) { + if (removed != null) + removed.add(here); + equ = false; + } else { + // they represent the same item + if (!((BaseClass) here).isEquals(there)) { + if (from != null) + from.add(here); + if (to != null) + to.add(there); + equ = false; + } } } - removeAll(del); - addAll(add); + 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 + * + * @return TRUE if they are equivalent + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public boolean isEquals(BaseClass other) { + if (other == null) + return false; + + if (size() != other.size()) + return false; + + if (!isSame(other)) + return false; + + if (!getState().equals(other.getState())) + return false; + + 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))) + return false; + } + + return true; } + /** + * 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, that is, two + * objects with the same state (and class) must return TRUE if + * {@link BaseClass#isEquals(BaseClass)} is called and their children + * are equivalent. + * + * @return a {@link String} representing the current state of this object, + * children not included + */ + abstract public String getState(); + /** * Notify that this element has unsaved changes. */ diff --git a/src/be/nikiroo/jvcard/Card.java b/src/be/nikiroo/jvcard/Card.java index e714b56..986f81d 100644 --- a/src/be/nikiroo/jvcard/Card.java +++ b/src/be/nikiroo/jvcard/Card.java @@ -58,8 +58,7 @@ public class Card extends BaseClass { this.file = file; switch (format) { case VCard21: - this.name = file.getName().replaceAll( - ".[vV][cC][fF]$", ""); + this.name = file.getName().replaceAll(".[vV][cC][fF]$", ""); break; case Abook: default: @@ -177,11 +176,11 @@ public class Card extends BaseClass { } /** - * Return the input which was used to open this {@link Card}. + * Return the {@link File} which was used to open this {@link Card}. * * @return the input */ - public File getInput() { + public File getFile() { return file; } @@ -219,6 +218,16 @@ public class Card extends BaseClass { return toString(Format.VCard21); } + @Override + public String getId() { + return "" + name; + } + + @Override + public String getState() { + return "" + name + format; + } + /** * Load the data from the given {@link File} under the given {@link Format}. * diff --git a/src/be/nikiroo/jvcard/Contact.java b/src/be/nikiroo/jvcard/Contact.java index 948fd84..4c6d5d4 100644 --- a/src/be/nikiroo/jvcard/Contact.java +++ b/src/be/nikiroo/jvcard/Contact.java @@ -7,6 +7,7 @@ import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.UUID; import be.nikiroo.jvcard.parsers.Format; import be.nikiroo.jvcard.parsers.Parser; @@ -403,6 +404,16 @@ public class Contact extends BaseClass { this.nextBKey = vc.nextBKey; } + @Override + public String getId() { + return "" + getPreferredDataValue("UID"); + } + + @Override + public String getState() { + return "" + getPreferredDataValue("UID"); + } + /** * Return a {@link String} representation of this contact, in vCard 2.1, * without BKeys. @@ -455,12 +466,15 @@ public class Contact extends BaseClass { boolean fn = false; boolean n = false; + boolean uid = false; if (content != null) { for (Data data : content) { if (data.getName().equals("N")) { n = true; } else if (data.getName().equals("FN")) { fn = true; + } else if (data.getName().equals("UID")) { + uid = true; } if (!data.getName().equals("VERSION")) { @@ -470,12 +484,12 @@ public class Contact extends BaseClass { } // required fields: - if (!n) { + if (!n) // required since vCard 3.0, supported in 2.1 datas.add(new Data(null, "N", "", null)); - } - if (!fn) { + if (!fn) // not required anymore but still supported in 4.0 datas.add(new Data(null, "FN", "", null)); - } + if (!uid) // supported by vCard, required by this program + datas.add(new Data(null, "UID", UUID.randomUUID().toString(), null)); return datas; } diff --git a/src/be/nikiroo/jvcard/Data.java b/src/be/nikiroo/jvcard/Data.java index 3e0d3d5..ef44813 100644 --- a/src/be/nikiroo/jvcard/Data.java +++ b/src/be/nikiroo/jvcard/Data.java @@ -145,4 +145,14 @@ public class Data extends BaseClass { public boolean isBinary() { return b64 >= 0; } + + @Override + public String getId() { + return "" + name; + } + + @Override + public String getState() { + return "" + name + value + group; + } } diff --git a/src/be/nikiroo/jvcard/TypeInfo.java b/src/be/nikiroo/jvcard/TypeInfo.java index 760f75c..a1cc306 100644 --- a/src/be/nikiroo/jvcard/TypeInfo.java +++ b/src/be/nikiroo/jvcard/TypeInfo.java @@ -6,12 +6,12 @@ package be.nikiroo.jvcard; * @author niki * */ -@SuppressWarnings("rawtypes") // expected +@SuppressWarnings("rawtypes") public class TypeInfo extends BaseClass { private String name; private String value; - @SuppressWarnings("unchecked") // expected + @SuppressWarnings("unchecked") public TypeInfo(String name, String value) { super(null); @@ -26,4 +26,14 @@ public class TypeInfo extends BaseClass { public String getValue() { return value; } + + @Override + public String getId() { + return "" + name; + } + + @Override + public String getState() { + return "" + name + value; + } } \ No newline at end of file -- 2.27.0