1 package be
.nikiroo
.jvcard
;
3 import java
.util
.ArrayList
;
4 import java
.util
.Collection
;
5 import java
.util
.Collections
;
6 import java
.util
.Comparator
;
7 import java
.util
.Iterator
;
8 import java
.util
.LinkedList
;
10 import java
.util
.ListIterator
;
12 import be
.nikiroo
.jvcard
.resources
.StringUtils
;
15 * This class is basically a List with a parent and a "dirty" state check. It
16 * sends all commands down to the initial list, but will mark itself and its
17 * children as dirty or not when needed.
19 * All child elements can identify their parent.
21 * The dirty state is bubbling up (when dirty = true) or down (when dirty =
22 * false) -- so, making changes to a child element will also mark its parent as
23 * "dirty", and marking an element as pristine will also affect all its child
29 * the type of the child elements
31 public abstract class BaseClass
<E
extends BaseClass
<?
>> implements List
<E
> {
32 protected boolean dirty
;
33 protected BaseClass
<?
> parent
;
36 private Comparator
<E
> comparator
= new Comparator
<E
>() {
38 public int compare(E o1
, E o2
) {
39 if (o1
== null && o2
== null)
41 if (o1
== null && o2
!= null)
43 if (o1
!= null && o2
== null)
46 return o1
.getId().compareTo(o2
.getId());
51 * Create a new {@link BaseClass} with the items in the given list as its
54 * Note: the elements will be copied from the {@link List}, you cannot
55 * manage the {@link List} from outside
58 * the descendants of this object, or NULL if none
60 protected BaseClass(List
<E
> list
) {
61 this.list
= new ArrayList
<E
>();
64 this.list
.addAll(list
);
67 for (E child
: this) {
73 * Check if this element has unsaved changes.
75 * @return TRUE if it has
77 public boolean isDirty() {
82 * Delete this element from its parent if any.
84 * @return TRUE in case of success
86 public boolean delete() {
88 return parent
.remove(this);
95 * Replace the elements contained in this with those in the given
98 * Note: the elements will be copied from the {@link List}, you cannot
99 * manage the {@link List} from outside
102 * the list of new elements
104 public void replaceListContent(List
<E
> list
) {
105 List
<E
> del
= new LinkedList
<E
>();
106 List
<E
> add
= new LinkedList
<E
>();
108 if (!compare(list
, add
, del
, del
, add
)) {
115 * Compare the elements contained in <tt>this</tt> with those in the given
116 * {@link List}. It will return TRUE in case of equality, will return FALSE
119 * If not equals, the differences will be represented by the given
120 * {@link List}s if they are not NULL.
122 * <li><tt>added</tt>will represent the elements in <tt>list</tt> but not in
124 * <li><tt>removed</tt> will represent the elements in <tt>this</tt> but not
125 * in <tt>list</tt></li>
126 * <li><tt>from<tt> will represent the elements in <tt>list</tt> that are
127 * already contained in <tt>this</tt> but are not equals to them (the
128 * original element from <tt>this</tt> is stored here)</li>
129 * <li><tt>to<tt> will represent the elements in <tt>list</tt> that are
130 * already contained in <tt>this</tt> but are not equals to them (the
131 * changed element from <tt>list</tt> is stored here)</li>
135 * the list of new elements
137 * the list to add the <tt>added</tt> elements to, or NULL
139 * the list to add the <tt>removed</tt> elements to, or NULL
141 * the map to add the <tt>from</tt> elements, or NULL
143 * the map to add the <tt>to</tt> elements, or NULL
145 * @return TRUE if the elements are identical
147 @SuppressWarnings({ "unchecked", "rawtypes" })
148 public boolean compare(List
<E
> list
, List
<E
> added
, List
<E
> removed
,
149 List
<E
> from
, List
<E
> to
) {
150 Collections
.sort(this.list
, comparator
);
152 List
<E
> mine
= new LinkedList
<E
>(this.list
);
153 List
<E
> other
= new LinkedList
<E
>(list
);
155 Collections
.sort(other
, comparator
);
158 E here
= mine
.size() > 0 ? mine
.remove(0) : null;
159 E there
= other
.size() > 0 ? other
.remove(0) : null;
161 while (here
!= null || there
!= null) {
163 || (there
!= null && comparator
.compare(here
, there
) > 0)) {
168 } else if (there
== null || comparator
.compare(here
, there
) < 0) {
174 // they represent the same item
175 if (!((BaseClass
) here
).isEquals(there
, false)) {
186 if (here
== null && mine
.size() > 0)
187 here
= mine
.remove(0);
188 if (there
== null && other
.size() > 0)
189 there
= other
.remove(0);
196 * Check if the given instance and this one represent the same objects (they
197 * may have different states).
202 * @return TRUE if they represent the same object
204 public boolean isSame(BaseClass
<E
> other
) {
208 if (!getClass().equals(other
.getClass()))
211 return getId().equals(other
.getId());
215 * Check if the given instance and this one are equivalent (both objects in
216 * the same state, all child elements equivalent).
222 * do not check the state of the object itslef, only its content
224 * @return TRUE if they are equivalent
226 @SuppressWarnings({ "unchecked", "rawtypes" })
227 public boolean isEquals(BaseClass
<E
> other
, boolean contentOnly
) {
231 if (size() != other
.size())
238 if (!getState().equals(other
.getState()))
242 Collections
.sort(list
, comparator
);
243 Collections
.sort(other
.list
, other
.comparator
);
244 for (int index
= 0; index
< size(); index
++) {
245 if (!((BaseClass
) get(index
)).isEquals(other
.get(index
), false))
253 * Get the recursive state of the current object, i.e., its children. It
254 * represents the full state information about this object's children. It
255 * may not contains spaces nor new lines.
258 * Not that this state is <b>lossy</b>. You cannot retrieve the data from
259 * the state, it can only be used as an ID to check if thw data are
263 * @return a {@link String} representing the current content state of this
264 * object, i.e., its children included
266 public String
getContentState() {
267 StringBuilder builder
= new StringBuilder();
268 buildContentStateRaw(builder
);
269 return StringUtils
.getHash(builder
.toString());
273 * Return the (first) child element with the given ID or NULL if not found.
278 * @return the child element or NULL
280 public E
getById(String id
) {
281 for (E child
: this) {
283 if (child
.getId() == null)
286 if (id
.equals(child
.getId()))
295 * Return the current ID of this object -- it is allowed to change over time
296 * (so, do not cache it).
298 * @return the current ID
300 abstract public String
getId();
303 * Get the state of the current object, children <b>not included</b>. It
304 * represents the full state information about this object, but do not check
305 * its children (see {@link BaseClass#getContentState()} for that). It may
306 * not contains spaces nor new lines.
309 * Not that this state is <b>lossy</b>. You cannot retrieve the data from
310 * the state, it can only be used as an ID to check if thw data are
314 * @return a {@link String} representing the current state of this object,
315 * children not included
317 abstract public String
getState();
320 * Get the recursive state of the current object, i.e., its children. It
321 * represents the full state information about this object's children.
326 * the {@link StringBuilder} that will represent the current
327 * content state of this object, i.e., its children included
329 void buildContentStateRaw(StringBuilder builder
) {
330 builder
.append(getState());
331 for (E child
: this) {
332 child
.buildContentStateRaw(builder
);
337 * Notify that this element has unsaved changes.
341 if (parent
!= null) {
347 * Notify this element <i>and all its descendants</i> that it is in pristine
348 * state (as opposed to dirty).
352 for (E child
: this) {
358 * Set the parent of this element <i>and all its descendants</i>.
363 void setParent(BaseClass
<?
> parent
) {
364 this.parent
= parent
;
365 for (E child
: this) {
366 child
.setParent(this);
371 * Each element that leaves the parent will pass trough here.
374 * the element to remove from this
376 private void _leave(E child
) {
381 * Each element that enters the parent will pass trough here.
384 * the element to add to this
386 private void _enter(E child
) {
387 _enter(child
, false);
391 * Each element that enters the parent will pass trough here.
394 * the element to add to this
396 private void _enter(E child
, boolean initialLoad
) {
397 child
.setParent(this);
405 public boolean add(E e
) {
411 @SuppressWarnings("unchecked")
412 public boolean remove(Object o
) {
413 if (list
.remove(o
)) {
414 if (o
instanceof BaseClass
<?
>) {
415 _leave((E
) o
); // expected warning
424 public boolean addAll(Collection
<?
extends E
> c
) {
429 return list
.addAll(c
);
433 public boolean addAll(int index
, Collection
<?
extends E
> c
) {
438 return list
.addAll(index
, c
);
442 public boolean removeAll(Collection
<?
> c
) {
443 boolean changed
= false;
454 public boolean retainAll(Collection
<?
> c
) {
455 ArrayList
<Object
> del
= new ArrayList
<Object
>();
459 return removeAll(del
);
463 public void clear() {
464 for (E child
: this) {
472 public E
set(int index
, E element
) {
473 E child
= get(index
);
478 return list
.set(index
, element
);
482 public void add(int index
, E element
) {
484 list
.add(index
, element
);
488 public E
remove(int index
) {
489 E child
= get(index
);
491 return list
.remove(index
);
495 public Iterator
<E
> iterator() {
496 return listIterator(0);
500 public ListIterator
<E
> listIterator() {
501 return listIterator(0);
505 public ListIterator
<E
> listIterator(int index
) {
507 return new ListIterator
<E
>() {
508 ListIterator
<E
> base
= list
.listIterator(i
);
512 public boolean hasNext() {
513 return base
.hasNext();
523 public boolean hasPrevious() {
524 return base
.hasPrevious();
528 public E
previous() {
529 last
= base
.previous();
534 public int nextIndex() {
535 return base
.nextIndex();
539 public int previousIndex() {
540 return base
.previousIndex();
544 public void remove() {
550 public void set(E e
) {
557 public void add(E e
) {
565 public Object
[] toArray() {
566 return list
.toArray();
570 public <T
> T
[] toArray(T
[] a
) {
571 return list
.toArray(a
);
580 public boolean isEmpty() {
581 return list
.isEmpty();
585 public boolean contains(Object o
) {
586 return list
.contains(o
);
590 public boolean containsAll(Collection
<?
> c
) {
591 return list
.containsAll(c
);
595 public E
get(int index
) {
596 return list
.get(index
);
600 public int indexOf(Object o
) {
601 return list
.indexOf(o
);
605 public int lastIndexOf(Object o
) {
606 return list
.lastIndexOf(o
);
610 public List
<E
> subList(int fromIndex
, int toIndex
) {
611 return list
.subList(fromIndex
, toIndex
);