Add some comparisons methods, fix the existing checks
authorNiki Roo <niki@nikiroo.be>
Sat, 12 Mar 2016 12:23:17 +0000 (13:23 +0100)
committerNiki Roo <niki@nikiroo.be>
Sat, 12 Mar 2016 12:23:17 +0000 (13:23 +0100)
src/be/nikiroo/jvcard/BaseClass.java
src/be/nikiroo/jvcard/Card.java
src/be/nikiroo/jvcard/Contact.java
src/be/nikiroo/jvcard/Data.java
src/be/nikiroo/jvcard/TypeInfo.java

index cb07948aac144d6aee8406e0f87882c394e11738..b6be10e47795fd31d1169c07068eb4ca3963c6b2 100644 (file)
@@ -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<E extends BaseClass<?>> implements List<E> {
        protected BaseClass<?> parent;
        private List<E> list;
 
+       private Comparator<E> comparator = new Comparator<E>() {
+               @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<E extends BaseClass<?>> implements List<E> {
         *            the list of new elements
         */
        public void replaceListContent(List<E> list) {
-               List<E> del = new ArrayList<E>();
-               List<E> add = new ArrayList<E>();
+               List<E> del = new LinkedList<E>();
+               List<E> add = new LinkedList<E>();
 
-               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 <tt>this</tt> 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.
+        * <ul>
+        * <li><tt>added</tt>will represent the elements in <tt>list</tt> but not in
+        * <tt>this</tt></li>
+        * <li><tt>removed</tt> will represent the elements in <tt>this</tt> but not
+        * in <tt>list</tt></li>
+        * <li><tt>from<tt> will represent the elements in <tt>list</tt> that are
+        * already contained in <tt>this</tt> but are not equals to them (the
+        * original element from <tt>this</tt> is stored here)</li>
+        * <li><tt>to<tt> will represent the elements in <tt>list</tt> that are
+        * already contained in <tt>this</tt> but are not equals to them (the
+        * changed element from <tt>list</tt> is stored here)</li>
+        * </ul>
+        * 
+        * @param list
+        *            the list of new elements
+        * @param added
+        *            the list to add the <tt>added</tt> elements to, or NULL
+        * @param removed
+        *            the list to add the <tt>removed</tt> elements to, or NULL
+        * @param from
+        *            the map to add the <tt>from</tt> elements, or NULL
+        * @param to
+        *            the map to add the <tt>to</tt> elements, or NULL
+        * 
+        * @return TRUE if the elements are identical
+        */
+       @SuppressWarnings({ "unchecked", "rawtypes" })
+       public boolean compare(List<E> list, List<E> added, List<E> removed,
+                       List<E> from, List<E> to) {
+               Collections.sort(this.list, comparator);
+
+               List<E> mine = new LinkedList<E>(this.list);
+               List<E> other = new LinkedList<E>(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<E> 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<E> 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 <b>not included</b>. 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 <b>and</b> 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.
         */
index e714b5633a9b5047d4a08df36463bbda05bab2f4..986f81d17c0932cba2081d7586b0ce115ad25547 100644 (file)
@@ -58,8 +58,7 @@ public class Card extends BaseClass<Contact> {
                        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<Contact> {
        }
 
        /**
-        * 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<Contact> {
                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}.
         * 
index 948fd8447965a87d06663db2b4513d6bea022d92..4c6d5d443b9075d42823af98660df19c904c88d9 100644 (file)
@@ -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<Data> {
                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<Data> {
 
                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<Data> {
                }
 
                // 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;
        }
index 3e0d3d5e0f7585a89cefb5d5324f362c1d4f4870..ef44813a0fca7d62dcaf1ed032434c97a0960be2 100644 (file)
@@ -145,4 +145,14 @@ public class Data extends BaseClass<TypeInfo> {
        public boolean isBinary() {
                return b64 >= 0;
        }
+
+       @Override
+       public String getId() {
+               return "" + name;
+       }
+
+       @Override
+       public String getState() {
+               return "" + name + value + group;
+       }
 }
index 760f75ce54caa9bfe711ea56a368954bcb2843d0..a1cc306d64d33a692aec043f6cc068f05be71fa2 100644 (file)
@@ -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