--- /dev/null
+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 <E>
+ * the type of the child elements
+ */
+public abstract class BaseClass<E extends BaseClass<?>> implements List<E> {
+ protected boolean dirty;
+ protected BaseClass<?> parent;
+ private List<E> 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<E> list) {
+ this.list = new ArrayList<E>();
+ 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<E> list) {
+ List<E> del = new ArrayList<E>();
+ List<E> add = new ArrayList<E>();
+
+ 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 <i>and all its descendants</i> 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 <i>and all its descendants</i>.
+ *
+ * @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<? extends E> c) {
+ for (E child : c) {
+ _enter(child);
+ }
+
+ return list.addAll(c);
+ }
+
+ @Override
+ public boolean addAll(int index, Collection<? extends E> 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<Object> del = new ArrayList<Object>();
+ 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<E> iterator() {
+ return listIterator(0);
+ }
+
+ @Override
+ public ListIterator<E> listIterator() {
+ return listIterator(0);
+ }
+
+ @Override
+ public ListIterator<E> listIterator(int index) {
+ final int i = index;
+ return new ListIterator<E>() {
+ ListIterator<E> 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> 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<E> subList(int fromIndex, int toIndex) {
+ return list.subList(fromIndex, toIndex);
+ }
+}
import java.io.IOException;
import java.io.InputStreamReader;
import java.security.InvalidParameterException;
-import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
* @author niki
*
*/
-public class Card {
- private List<Contact> contacts;
+public class Card extends BaseClass<Contact> {
private File file;
- private boolean dirty;
private String name;
private Format format;
* 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<String> lines = new LinkedList<String>();
- 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 <i>index</i>.
- *
- * @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;
}
/**
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).
}
/**
- * 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<String> 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<String> lines, Format format) {
- this.contacts = Parser.parse(lines, format);
- setDirty();
-
- for (Contact contact : contacts) {
- contact.setParent(this);
+ static private List<Contact> load(File file, Format format)
+ throws IOException {
+ BufferedReader buffer = new BufferedReader(new InputStreamReader(
+ new FileInputStream(file), "UTF-8"));
+ List<String> lines = new LinkedList<String>();
+ 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 <i>and all its descendants</i> 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);
}
}
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;
* @author niki
*
*/
-public class Contact {
- private List<Data> datas;
+public class Contact extends BaseClass<Data> {
private int nextBKey = 1;
private Map<Integer, Data> binaries;
- private boolean dirty;
- private Card parent;
/**
* Create a new Contact from the given information. Note that the BKeys data
* the information about the contact
*/
public Contact(List<Data> content) {
- this.datas = new LinkedList<Data>();
-
- 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 <i>index</i>.
- *
- * @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.
*
public List<Data> getData(String name) {
List<Data> found = new LinkedList<Data>();
- for (Data data : datas) {
+ for (Data data : this) {
if (data.getName().equals(name))
found.add(data);
}
public void updateFrom(Contact vc) {
updateBKeys(false);
- List<Data> newDatas = new LinkedList<Data>(vc.datas);
+ List<Data> newDatas = new LinkedList<Data>(vc);
for (int i = 0; i < newDatas.size(); i++) {
Data data = newDatas.get(i);
int bkey = Parser.getBKey(data);
}
}
- 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;
}
/**
binaries = new HashMap<Integer, Data>();
}
- for (Data data : datas) {
+ for (Data data : this) {
if (data.isBinary() && (data.getB64Key() <= 0 || force)) {
binaries.put(nextBKey, data);
data.resetB64Key(nextBKey++);
}
/**
- * 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<Data> load(List<Data> content) {
+ List<Data> datas = new ArrayList<Data>();
- /**
- * Notify this element <i>and all its descendants</i> 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} <i>and all its descendants</i>.
- *
- * @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;
}
/**
list.add(add);
return add.length();
}
-
}
package be.nikiroo.jvcard;
import java.security.InvalidParameterException;
-import java.util.LinkedList;
import java.util.List;
/**
* @author niki
*
*/
-public class Data {
+public class Data extends BaseClass<TypeInfo> {
public enum DataPart {
FN_FAMILY, FN_GIVEN, FN_ADDITIONAL, // Name
FN_PRE, FN_POST, // Pre/Post
private String value;
private String group;
private int b64; // -1 = no, 0 = still not ordered, the rest is order
- private List<TypeInfo> types;
- private boolean dirty;
- private Contact parent;
/**
* Create a new {@link Data} with the given values.
* its group if any
*/
public Data(List<TypeInfo> types, String name, String value, String group) {
- if (types == null) {
- types = new LinkedList<TypeInfo>();
- }
+ 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;
}
}
- /**
- * 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 <i>index</i>.
- *
- * @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<TypeInfo> 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}
*
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 <i>and all its descendants</i> 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;
- }
}
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;
}
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 <i>and all its descendants</i> 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
*/
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;
Data data = getData();
if (data != null) {
if (!answer.equals(previous)) {
- data.setTypes(stringToTypes(answer));
+ data.replaceListContent(stringToTypes(answer));
}
return null;
}
this.contacts = new LinkedList<Contact>();
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())) {