1 package be
.nikiroo
.jvcard
;
3 import java
.security
.InvalidParameterException
;
4 import java
.util
.ArrayList
;
5 import java
.util
.Collection
;
6 import java
.util
.Collections
;
7 import java
.util
.Comparator
;
8 import java
.util
.Iterator
;
9 import java
.util
.LinkedList
;
10 import java
.util
.List
;
11 import java
.util
.ListIterator
;
13 import be
.nikiroo
.utils
.StringUtils
;
16 * This class is basically a List with a parent and a "dirty" state check. It
17 * sends all commands down to the initial list, but will mark itself and its
18 * children as dirty or not when needed.
21 * All child elements can identify their parent, and must not be added to 2
22 * different objects without without first being removed from the previous one.
26 * The dirty state is bubbling up (when dirty = true) or down (when dirty =
27 * false) -- so, making changes to a child element will also mark its parent as
28 * "dirty", and marking an element as pristine will also affect all its child
35 * the type of the child elements
37 public abstract class BaseClass
<E
extends BaseClass
<?
>> implements List
<E
> {
38 protected boolean dirty
;
39 protected BaseClass
<?
> parent
;
42 private Comparator
<E
> comparator
= new Comparator
<E
>() {
44 public int compare(E o1
, E o2
) {
45 if (o1
== null && o2
== null)
47 if (o1
== null && o2
!= null)
49 if (o1
!= null && o2
== null)
52 return o1
.getId().compareTo(o2
.getId());
57 * Create a new {@link BaseClass} with the items in the given list as its
60 * Note: the elements will be copied from the {@link List}, you cannot
61 * manage the {@link List} from outside
64 * the descendants of this object, or NULL if none
66 protected BaseClass(List
<E
> list
) {
67 this.list
= new ArrayList
<E
>();
70 this.list
.addAll(list
);
73 for (E child
: this) {
79 * Check if this element has unsaved changes.
81 * @return TRUE if it has
83 public boolean isDirty() {
88 * Delete this element from its parent if any.
90 * @return TRUE in case of success
92 public boolean delete() {
94 return parent
.remove(this);
101 * Replace the elements contained in this with those in the given
104 * Note: the elements will be copied from the {@link List}, you cannot
105 * manage the {@link List} from outside
108 * the list of new elements
110 public void replaceListContent(List
<E
> list
) {
111 List
<E
> del
= new LinkedList
<E
>();
112 List
<E
> add
= new LinkedList
<E
>();
114 if (!compare(list
, add
, del
, del
, add
)) {
121 * Compare the elements contained in <tt>this</tt> with those in the given
122 * {@link List}. It will return TRUE in case of equality, will return FALSE
125 * If not equals, the differences will be represented by the given
126 * {@link List}s if they are not NULL.
128 * <li><tt>added</tt> will represent the elements in <tt>list</tt> but not
129 * in <tt>this</tt></li>
130 * <li><tt>removed</tt> will represent the elements in <tt>this</tt> but not
131 * in <tt>list</tt></li>
132 * <li><tt>from<tt> will represent the elements in <tt>list</tt> that are
133 * already contained in <tt>this</tt> but are not equals to them (the
134 * original element from <tt>this</tt> is stored here)</li>
135 * <li><tt>to<tt> will represent the elements in <tt>list</tt> that are
136 * already contained in <tt>this</tt> but are not equals to them (the
137 * changed element from <tt>list</tt> is stored here)</li>
141 * the list of new elements
143 * the list to add the <tt>added</tt> elements to, or NULL
145 * the list to add the <tt>removed</tt> elements to, or NULL
147 * the map to add the <tt>from</tt> elements, or NULL
149 * the map to add the <tt>to</tt> elements, or NULL
151 * @return TRUE if the elements are identical
153 @SuppressWarnings({ "unchecked", "rawtypes" })
154 public boolean compare(List
<E
> list
, List
<E
> added
, List
<E
> removed
,
155 List
<E
> from
, List
<E
> to
) {
156 Collections
.sort(this.list
, comparator
);
158 List
<E
> mine
= new LinkedList
<E
>(this.list
);
159 List
<E
> other
= new LinkedList
<E
>(list
);
161 Collections
.sort(other
, comparator
);
164 E here
= mine
.size() > 0 ? mine
.remove(0) : null;
165 E there
= other
.size() > 0 ? other
.remove(0) : null;
167 while (here
!= null || there
!= null) {
169 || (there
!= null && comparator
.compare(here
, there
) > 0)) {
174 } else if (there
== null || comparator
.compare(here
, there
) < 0) {
180 // they represent the same item
181 if (!((BaseClass
) here
).isEquals(there
, false)) {
192 if (here
== null && mine
.size() > 0)
193 here
= mine
.remove(0);
194 if (there
== null && other
.size() > 0)
195 there
= other
.remove(0);
202 * Check if the given instance and this one represent the same objects (they
203 * may have different states).
208 * @return TRUE if they represent the same object
210 public boolean isSame(BaseClass
<E
> other
) {
214 if (!getClass().equals(other
.getClass()))
217 return getId().equals(other
.getId());
221 * Check if the given instance and this one are equivalent (both objects in
222 * the same state, all child elements equivalent).
228 * do not check the state of the object itslef, only its content
230 * @return TRUE if they are equivalent
232 @SuppressWarnings({ "unchecked", "rawtypes" })
233 public boolean isEquals(BaseClass
<E
> other
, boolean contentOnly
) {
237 if (size() != other
.size())
244 if (!getState().equals(other
.getState()))
248 Collections
.sort(list
, comparator
);
249 Collections
.sort(other
.list
, other
.comparator
);
250 for (int index
= 0; index
< size(); index
++) {
251 if (!((BaseClass
) get(index
)).isEquals(other
.get(index
), false))
259 * Get the recursive state of the current object, i.e., its children
260 * included. It represents the full state information about this object's
261 * children. It may not contains spaces nor new lines.
264 * Not that this state is <b>lossy</b>. You cannot retrieve the data from
265 * the state, it can only be used as an ID to check if data are identical.
269 * also include state information about the current object itself
270 * (as opposed to its children)
272 * @return a {@link String} representing the current content state of this
273 * object, i.e., its children included
275 public String
getContentState(boolean self
) {
276 StringBuilder builder
= new StringBuilder();
277 buildContentStateRaw(builder
, self
);
278 return StringUtils
.getMd5Hash(builder
.toString());
282 * Return the (first) child element with the given ID or NULL if not found.
287 * @return the child element or NULL
289 public E
getById(String id
) {
290 for (E child
: this) {
292 if (child
.getId() == null)
295 if (id
.equals(child
.getId()))
304 * Return a {@link String} that can be used to identify this object in DEBUG
305 * mode, i.e., a "toString" method that can identify the object's content
306 * but still be readable in a log.
309 * the depth into which to descend (0 = only this object, not its
310 * children, negative value for infinite depth)
312 * @return the debug {@link String}
314 public String
getDebugInfo(int depth
) {
315 StringBuilder builder
= new StringBuilder();
316 getDebugInfo(builder
, depth
, 0);
317 return builder
.toString();
321 * Return the current ID of this object -- it is allowed to change over time
322 * (so, do not cache it).
324 * @return the current ID
326 abstract public String
getId();
329 * Get the state of the current object, children <b>not included</b>. It
330 * represents the full state information about this object, but do not check
331 * its children (see {@link BaseClass#getContentState()} for that). It may
332 * not contains spaces nor new lines.
335 * Not that this state is <b>lossy</b>. You cannot retrieve the data from
336 * the state, it can only be used as an ID to check if the data are
340 * @return a {@link String} representing the current state of this object,
341 * children not included
343 abstract public String
getState();
346 * Get the recursive state of the current object, i.e., its children
347 * included. It represents the full state information about this object's
353 * the {@link StringBuilder} that will represent the current
354 * content state of this object, i.e., its children included
356 * also include state information about the current object itself
357 * (as opposed to its children)
359 void buildContentStateRaw(StringBuilder builder
, boolean self
) {
360 Collections
.sort(this.list
, comparator
);
362 builder
.append(getState());
363 for (E child
: this) {
364 child
.buildContentStateRaw(builder
, true);
369 * Populate a {@link StringBuilder} that can be used to identify this object
370 * in DEBUG mode, i.e., a "toString" method that can identify the object's
371 * content but still be readable in a log.
374 * the depth into which to descend (0 = only this object, not its
375 * children, negative value for infinite depth)
378 * the current tabulation increment
380 void getDebugInfo(StringBuilder builder
, int depth
, int tab
) {
381 for (int i
= 0; i
< tab
; i
++)
383 builder
.append(getContentState(false) + " " + getId());
386 builder
.append(": [");
389 for (E child
: this) {
390 builder
.append("\n");
391 child
.getDebugInfo(builder
, depth
- 1, tab
+ 1);
395 builder
.append("\n");
396 for (int i
= 0; i
< tab
; i
++)
403 * Notify that this element <i>and all its parent elements</i> has unsaved
408 if (parent
!= null) {
414 * Notify this element <i>and all its descendants</i> that it is in pristine
415 * state (as opposed to dirty).
419 for (E child
: this) {
425 * Set the parent of this element.
427 * Will also check and fix if needed the parent (this) of all its
428 * descendants (recursively).
433 void setParent(BaseClass
<?
> parent
) {
434 this.parent
= parent
;
435 for (E child
: this) {
436 child
.setParent(this);
441 * Escape the given value to VCF standard.
444 * the value to escape
446 * @return the escaped value
448 protected String
escape(String value
) {
452 return value
.replaceAll(",", "\\\\,").replaceAll(";", "\\\\;")
453 .replaceAll("\n", "\\\\n");
457 * Unescape the given value from the VCF standard.
460 * the value to unescape
462 * @return the unescaped value
464 protected String
unescape(String value
) {
468 return value
.replaceAll("\\\\,", ",").replaceAll("\\\\;", ";")
469 .replaceAll("\\\\n", "\n");
473 * Each element that leaves the parent will pass trough here.
476 * the element to remove from this
478 private void _leave(E child
) {
479 if (child
.parent
!= null && child
.parent
!= this) {
480 throw new InvalidParameterException(
481 "You are removing this child from its rightful parent, it must be yours to do so");
489 * Each element that enters the parent will pass trough here.
492 * the element to add to this
494 private void _enter(E child
) {
495 _enter(child
, false);
499 * Each element that enters the parent will pass trough here.
502 * the element to add to this
504 private void _enter(E child
, boolean initialLoad
) {
505 if (child
.parent
!= null && child
.parent
!= this) {
506 throw new InvalidParameterException(
507 "You are stealing this child from its rightful parent, you must remove it first");
510 child
.setParent(this);
518 public boolean add(E e
) {
524 @SuppressWarnings("unchecked")
525 public boolean remove(Object o
) {
526 if (list
.remove(o
)) {
527 if (o
instanceof BaseClass
<?
>) {
528 _leave((E
) o
); // expected warning
537 public boolean addAll(Collection
<?
extends E
> c
) {
542 return list
.addAll(c
);
546 public boolean addAll(int index
, Collection
<?
extends E
> c
) {
551 return list
.addAll(index
, c
);
555 public boolean removeAll(Collection
<?
> c
) {
556 boolean changed
= false;
567 public boolean retainAll(Collection
<?
> c
) {
568 ArrayList
<Object
> del
= new ArrayList
<Object
>();
572 return removeAll(del
);
576 public void clear() {
577 for (E child
: this) {
585 public E
set(int index
, E element
) {
586 E child
= get(index
);
591 return list
.set(index
, element
);
595 public void add(int index
, E element
) {
597 list
.add(index
, element
);
601 public E
remove(int index
) {
602 E child
= get(index
);
604 return list
.remove(index
);
608 public Iterator
<E
> iterator() {
609 return listIterator(0);
613 public ListIterator
<E
> listIterator() {
614 return listIterator(0);
618 public ListIterator
<E
> listIterator(int index
) {
620 return new ListIterator
<E
>() {
621 ListIterator
<E
> base
= list
.listIterator(i
);
625 public boolean hasNext() {
626 return base
.hasNext();
636 public boolean hasPrevious() {
637 return base
.hasPrevious();
641 public E
previous() {
642 last
= base
.previous();
647 public int nextIndex() {
648 return base
.nextIndex();
652 public int previousIndex() {
653 return base
.previousIndex();
657 public void remove() {
663 public void set(E e
) {
670 public void add(E e
) {
678 public Object
[] toArray() {
679 return list
.toArray();
683 public <T
> T
[] toArray(T
[] a
) {
684 return list
.toArray(a
);
693 public boolean isEmpty() {
694 return list
.isEmpty();
698 public boolean contains(Object o
) {
699 return list
.contains(o
);
703 public boolean containsAll(Collection
<?
> c
) {
704 return list
.containsAll(c
);
708 public E
get(int index
) {
709 return list
.get(index
);
713 public int indexOf(Object o
) {
714 return list
.indexOf(o
);
718 public int lastIndexOf(Object o
) {
719 return list
.lastIndexOf(o
);
723 public List
<E
> subList(int fromIndex
, int toIndex
) {
724 return list
.subList(fromIndex
, toIndex
);