From: Niki Roo Date: Wed, 9 Mar 2016 13:11:39 +0000 (+0100) Subject: Fix version, move list management from Card/Contact/... into BaseClass X-Git-Tag: v1.0-beta3~23 X-Git-Url: http://git.nikiroo.be/?p=jvcard.git;a=commitdiff_plain;h=26d2bd0591901a8d52bd24802a8d6827d0e9b833 Fix version, move list management from Card/Contact/... into BaseClass --- diff --git a/src/be/nikiroo/jvcard/BaseClass.java b/src/be/nikiroo/jvcard/BaseClass.java new file mode 100644 index 0000000..fe7742a --- /dev/null +++ b/src/be/nikiroo/jvcard/BaseClass.java @@ -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 + * the type of the child elements + */ +public abstract class BaseClass> implements List { + protected boolean dirty; + protected BaseClass parent; + private List 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 list) { + this.list = new ArrayList(); + 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 list) { + List del = new ArrayList(); + List add = new ArrayList(); + + 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 and all its descendants 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 and all its descendants. + * + * @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 c) { + for (E child : c) { + _enter(child); + } + + return list.addAll(c); + } + + @Override + public boolean addAll(int index, Collection 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 del = new ArrayList(); + 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 iterator() { + return listIterator(0); + } + + @Override + public ListIterator listIterator() { + return listIterator(0); + } + + @Override + public ListIterator listIterator(int index) { + final int i = index; + return new ListIterator() { + ListIterator 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[] 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 subList(int fromIndex, int toIndex) { + return list.subList(fromIndex, toIndex); + } +} diff --git a/src/be/nikiroo/jvcard/Card.java b/src/be/nikiroo/jvcard/Card.java index 53ae972..2622266 100644 --- a/src/be/nikiroo/jvcard/Card.java +++ b/src/be/nikiroo/jvcard/Card.java @@ -8,7 +8,6 @@ import java.io.FileWriter; import java.io.IOException; import java.io.InputStreamReader; import java.security.InvalidParameterException; -import java.util.Arrays; import java.util.LinkedList; import java.util.List; @@ -22,10 +21,8 @@ import be.nikiroo.jvcard.parsers.Parser; * @author niki * */ -public class Card { - private List contacts; +public class Card extends BaseClass { private File file; - private boolean dirty; private String name; private Format format; @@ -45,71 +42,11 @@ public class Card { * if format is NULL */ public Card(File file, Format format) throws IOException { + super(load(file, format)); + this.file = file; this.format = format; this.name = file.getName(); - - BufferedReader buffer = new BufferedReader(new InputStreamReader( - new FileInputStream(file), "UTF-8")); - List lines = new LinkedList(); - for (String line = buffer.readLine(); line != null; line = buffer - .readLine()) { - lines.add(line); - } - buffer.close(); - - load(lines, format); - dirty = false; // initial load, so no change yet, so no need to call - // setPristine() - } - - /** - * Return the number of {@link Contact} present in this {@link Card}. - * - * @return the number of {@link Contact}s - */ - public int size() { - return contacts.size(); - } - - /** - * Return the {@link Contact} at index index. - * - * @param index - * the index of the {@link Contact} to find - * - * @return the {@link Contact} - * - * @throws IndexOutOfBoundsException - * if the index is < 0 or >= {@link Card#size()} - */ - public Contact get(int index) { - return contacts.get(index); - } - - /** - * Add a new {@link Contact} in this {@link Card}. - * - * @param contact - * the new contact - */ - public void add(Contact contact) { - contact.setParent(this); - contact.setDirty(); - contacts.add(contact); - } - - /** - * Remove the given {@link Contact} from its this {@link Card} if it is in. - * - * @return TRUE in case of success - */ - public boolean remove(Contact contact) { - if (contacts.remove(contact)) { - setDirty(); - } - - return false; } /** @@ -166,15 +103,6 @@ public class Card { return Parser.toString(this, format); } - /** - * Check if this {@link Card} has unsaved changes. - * - * @return TRUE if it has - */ - public boolean isDirty() { - return dirty; - } - /** * Return the name of this card (the name of the {@link File} which it was * opened from). @@ -191,51 +119,28 @@ public class Card { } /** - * Load the given data from the given {@link Format} in this {@link Card}. + * Load the data from the given {@link File} under the given {@link Format}. * - * @param serializedContent - * the data + * @param file + * the {@link File} to load from * @param format - * the {@link Format} - */ - protected void load(String serializedContent, Format format) { - // note: fixed size array - List lines = Arrays.asList(serializedContent.split("\n")); - load(lines, format); - } - - /** - * Load the given data from the given {@link Format} in this {@link Card}. + * the {@link Format} to load as * - * @param lines - * the data - * @param format - * the {@link Format} + * @return the list of elements + * @throws IOException + * in case of IO error */ - protected void load(List lines, Format format) { - this.contacts = Parser.parse(lines, format); - setDirty(); - - for (Contact contact : contacts) { - contact.setParent(this); + static private List load(File file, Format format) + throws IOException { + BufferedReader buffer = new BufferedReader(new InputStreamReader( + new FileInputStream(file), "UTF-8")); + List lines = new LinkedList(); + for (String line = buffer.readLine(); line != null; line = buffer + .readLine()) { + lines.add(line); } - } - - /** - * Notify that this element has unsaved changes. - */ - void setDirty() { - dirty = true; - } + buffer.close(); - /** - * Notify this element and all its descendants that it is in pristine - * state (as opposed to dirty). - */ - void setPristine() { - dirty = false; - for (Contact contact : contacts) { - contact.setPristine(); - } + return Parser.parse(lines, format); } } diff --git a/src/be/nikiroo/jvcard/Contact.java b/src/be/nikiroo/jvcard/Contact.java index c5579fe..948fd84 100644 --- a/src/be/nikiroo/jvcard/Contact.java +++ b/src/be/nikiroo/jvcard/Contact.java @@ -1,5 +1,8 @@ package be.nikiroo.jvcard; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import java.util.List; @@ -15,12 +18,9 @@ import be.nikiroo.jvcard.tui.StringUtils; * @author niki * */ -public class Contact { - private List datas; +public class Contact extends BaseClass { private int nextBKey = 1; private Map binaries; - private boolean dirty; - private Card parent; /** * Create a new Contact from the given information. Note that the BKeys data @@ -30,85 +30,10 @@ public class Contact { * the information about the contact */ public Contact(List content) { - this.datas = new LinkedList(); - - boolean fn = false; - boolean n = false; - if (content != null) { - for (Data data : content) { - if (data.getName().equals("N")) { - n = true; - } else if (data.getName().equals("FN")) { - fn = true; - } - - if (!data.getName().equals("VERSION")) { - datas.add(data); - } - } - } - - // required fields: - if (!n) { - datas.add(new Data(null, "N", "", null)); - } - if (!fn) { - datas.add(new Data(null, "FN", "", null)); - } - + super(load(content)); updateBKeys(true); } - /** - * Return the number of {@link Data} present in this {@link Contact}. - * - * @return the number of {@link Data}s - */ - public int size() { - return datas.size(); - } - - /** - * Return the {@link Data} at index index. - * - * @param index - * the index of the {@link Data} to find - * - * @return the {@link Data} - * - * @throws IndexOutOfBoundsException - * if the index is < 0 or >= {@link Contact#size()} - */ - public Data get(int index) { - return datas.get(index); - } - - /** - * Add a new {@link Data} in this {@link Contact}. - * - * @param data - * the new data - */ - public void add(Data data) { - data.setParent(this); - data.setDirty(); - datas.add(data); - } - - /** - * Remove the given {@link Data} from its this {@link Contact} if it is in. - * - * @return TRUE in case of success - */ - public boolean remove(Data data) { - if (datas.remove(data)) { - setDirty(); - return true; - } - - return false; - } - /** * Return the preferred Data field with the given name, or NULL if none. * @@ -158,7 +83,7 @@ public class Contact { public List getData(String name) { List found = new LinkedList(); - for (Data data : datas) { + for (Data data : this) { if (data.getName().equals(name)) found.add(data); } @@ -463,7 +388,7 @@ public class Contact { public void updateFrom(Contact vc) { updateBKeys(false); - List newDatas = new LinkedList(vc.datas); + List newDatas = new LinkedList(vc); for (int i = 0; i < newDatas.size(); i++) { Data data = newDatas.get(i); int bkey = Parser.getBKey(data); @@ -474,33 +399,8 @@ public class Contact { } } - this.datas = newDatas; + replaceListContent(newDatas); this.nextBKey = vc.nextBKey; - - setParent(parent); - setDirty(); - } - - /** - * Delete this {@link Contact} from its parent {@link Card} if any. - * - * @return TRUE in case of success - */ - public boolean delete() { - if (parent != null) { - return parent.remove(this); - } - - return false; - } - - /** - * Check if this {@link Contact} has unsaved changes. - * - * @return TRUE if it has - */ - public boolean isDirty() { - return dirty; } /** @@ -530,7 +430,7 @@ public class Contact { binaries = new HashMap(); } - for (Data data : datas) { + for (Data data : this) { if (data.isBinary() && (data.getB64Key() <= 0 || force)) { binaries.put(nextBKey, data); data.resetB64Key(nextBKey++); @@ -539,37 +439,45 @@ public class Contact { } /** - * Notify that this element has unsaved changes, and notify its parent of - * the same if any. + * Load the data from the given {@link File} under the given {@link Format}. + * + * @param file + * the {@link File} to load from + * @param format + * the {@link Format} to load as + * + * @return the list of elements + * @throws IOException + * in case of IO error */ - protected void setDirty() { - this.dirty = true; - if (this.parent != null) - this.parent.setDirty(); - } + static private List load(List content) { + List datas = new ArrayList(); - /** - * Notify this element and all its descendants that it is in pristine - * state (as opposed to dirty). - */ - void setPristine() { - dirty = false; - for (Data data : datas) { - data.setPristine(); + boolean fn = false; + boolean n = false; + if (content != null) { + for (Data data : content) { + if (data.getName().equals("N")) { + n = true; + } else if (data.getName().equals("FN")) { + fn = true; + } + + if (!data.getName().equals("VERSION")) { + datas.add(data); + } + } } - } - /** - * Set the parent of this {@link Contact} and all its descendants. - * - * @param parent - * the new parent - */ - void setParent(Card parent) { - this.parent = parent; - for (Data data : datas) { - data.setParent(this); + // required fields: + if (!n) { + datas.add(new Data(null, "N", "", null)); } + if (!fn) { + datas.add(new Data(null, "FN", "", null)); + } + + return datas; } /** @@ -603,5 +511,4 @@ public class Contact { list.add(add); return add.length(); } - } diff --git a/src/be/nikiroo/jvcard/Data.java b/src/be/nikiroo/jvcard/Data.java index 377c41e..3e0d3d5 100644 --- a/src/be/nikiroo/jvcard/Data.java +++ b/src/be/nikiroo/jvcard/Data.java @@ -1,7 +1,6 @@ package be.nikiroo.jvcard; import java.security.InvalidParameterException; -import java.util.LinkedList; import java.util.List; /** @@ -11,7 +10,7 @@ import java.util.List; * @author niki * */ -public class Data { +public class Data extends BaseClass { public enum DataPart { FN_FAMILY, FN_GIVEN, FN_ADDITIONAL, // Name FN_PRE, FN_POST, // Pre/Post @@ -24,9 +23,6 @@ public class Data { private String value; private String group; private int b64; // -1 = no, 0 = still not ordered, the rest is order - private List types; - private boolean dirty; - private Contact parent; /** * Create a new {@link Data} with the given values. @@ -41,18 +37,14 @@ public class Data { * its group if any */ public Data(List types, String name, String value, String group) { - if (types == null) { - types = new LinkedList(); - } + super(types); - this.types = types; this.name = name; this.value = value; this.group = group; b64 = -1; - for (TypeInfo type : types) { - type.setParent(this); + for (TypeInfo type : this) { if (type.getName().equals("ENCODING") && type.getValue().equals("b")) { b64 = 0; @@ -61,73 +53,6 @@ public class Data { } } - /** - * Return the number of {@link TypeInfo} present in this {@link Data}. - * - * @return the number of {@link TypeInfo}s - */ - public int size() { - return types.size(); - } - - /** - * Return the {@link TypeInfo} at index index. - * - * @param index - * the index of the {@link TypeInfo} to find - * - * @return the {@link TypeInfo} - * - * @throws IndexOutOfBoundsException - * if the index is < 0 or >= {@link Data#size()} - */ - public TypeInfo get(int index) { - return types.get(index); - } - - /** - * Add a new {@link TypeInfo} in this {@link Data}. - * - * @param type - * the new type - */ - public void add(TypeInfo type) { - type.setParent(this); - type.setDirty(); - types.add(type); - } - - /** - * Remove the given {@link TypeInfo} from its this {@link Data} if it is in. - * - * @return TRUE in case of success - */ - public boolean remove(TypeInfo type) { - if (types.remove(type)) { - setDirty(); - } - - return false; - } - - /** - * Change the {@link TypeInfo}s of this {@link Data}. - * - * @param types - * the new types - */ - @Deprecated - public void setTypes(List types) { - // TODO: check if this method is required - this.types.clear(); - for (TypeInfo type : types) { - this.types.add(type); - type.setParent(this); - } - - setDirty(); - } - /** * Return the name of this {@link Data} * @@ -220,57 +145,4 @@ public class Data { public boolean isBinary() { return b64 >= 0; } - - /** - * Delete this {@link Contact} from its parent {@link Card} if any. - * - * @return TRUE in case of success - */ - public boolean delete() { - if (parent != null) { - return parent.remove(this); - } - - return false; - } - - /** - * Check if this {@link Data} has unsaved changes. - * - * @return TRUE if it has - */ - public boolean isDirty() { - return dirty; - } - - /** - * Notify that this element has unsaved changes, and notify its parent of - * the same if any. - */ - protected void setDirty() { - this.dirty = true; - if (this.parent != null) - this.parent.setDirty(); - } - - /** - * Notify this element and all its descendants that it is in pristine - * state (as opposed to dirty). - */ - void setPristine() { - dirty = false; - for (TypeInfo type : types) { - type.setPristine(); - } - } - - /** - * Set the parent of this {@link Data}. - * - * @param parent - * the new parent - */ - void setParent(Contact parent) { - this.parent = parent; - } } diff --git a/src/be/nikiroo/jvcard/TypeInfo.java b/src/be/nikiroo/jvcard/TypeInfo.java index 26314fa..760f75c 100644 --- a/src/be/nikiroo/jvcard/TypeInfo.java +++ b/src/be/nikiroo/jvcard/TypeInfo.java @@ -1,12 +1,20 @@ package be.nikiroo.jvcard; -public class TypeInfo { +/** + * This class describes a type, that is, a key-value pair. + * + * @author niki + * + */ +@SuppressWarnings("rawtypes") // expected +public class TypeInfo extends BaseClass { private String name; private String value; - private Data parent; - private boolean dirty; + @SuppressWarnings("unchecked") // expected public TypeInfo(String name, String value) { + super(null); + this.name = name; this.value = value; } @@ -18,41 +26,4 @@ public class TypeInfo { public String getValue() { return value; } - - /** - * Check if this {@link TypeInfo} has unsaved changes. - * - * @return TRUE if it has - */ - public boolean isDirty() { - return dirty; - } - - /** - * Notify that this element has unsaved changes, and notify its parent of - * the same if any. - */ - protected void setDirty() { - this.dirty = true; - if (this.parent != null) - this.parent.setDirty(); - } - - /** - * Notify this element and all its descendants that it is in pristine - * state (as opposed to dirty). - */ - void setPristine() { - dirty = false; - } - - /** - * Set the parent of this {@link TypeInfo}. - * - * @param parent - * the new parent - */ - void setParent(Data parent) { - this.parent = parent; - } } \ No newline at end of file diff --git a/src/be/nikiroo/jvcard/tui/Main.java b/src/be/nikiroo/jvcard/tui/Main.java index 4884e70..1656234 100644 --- a/src/be/nikiroo/jvcard/tui/Main.java +++ b/src/be/nikiroo/jvcard/tui/Main.java @@ -24,7 +24,7 @@ import com.googlecode.lanterna.input.KeyStroke; */ public class Main { public static final String APPLICATION_TITLE = "jVcard"; - public static final String APPLICATION_VERSION = "1.0-beta2"; + public static final String APPLICATION_VERSION = "1.0-beta2-dev"; static private Trans transService; diff --git a/src/be/nikiroo/jvcard/tui/panes/ContactDetailsRaw.java b/src/be/nikiroo/jvcard/tui/panes/ContactDetailsRaw.java index a81cec8..d3bd18d 100644 --- a/src/be/nikiroo/jvcard/tui/panes/ContactDetailsRaw.java +++ b/src/be/nikiroo/jvcard/tui/panes/ContactDetailsRaw.java @@ -169,7 +169,7 @@ public class ContactDetailsRaw extends MainContentList { Data data = getData(); if (data != null) { if (!answer.equals(previous)) { - data.setTypes(stringToTypes(answer)); + data.replaceListContent(stringToTypes(answer)); } return null; } diff --git a/src/be/nikiroo/jvcard/tui/panes/ContactList.java b/src/be/nikiroo/jvcard/tui/panes/ContactList.java index 3cec44e..2e6db52 100644 --- a/src/be/nikiroo/jvcard/tui/panes/ContactList.java +++ b/src/be/nikiroo/jvcard/tui/panes/ContactList.java @@ -54,8 +54,7 @@ public class ContactList extends MainContentList { this.contacts = new LinkedList(); if (card != null) { - for (int i = 0; i < card.size(); i++) { - Contact c = card.get(i); + for (Contact c : card) { if (filter == null || c.toString(format).toLowerCase() .contains(filter.toLowerCase())) {