Fix some bugs in remote/sync (still not complete)
[jvcard.git] / src / be / nikiroo / jvcard / BaseClass.java
CommitLineData
26d2bd05
NR
1package be.nikiroo.jvcard;
2
3import java.util.ArrayList;
4import java.util.Collection;
e253bd50
NR
5import java.util.Collections;
6import java.util.Comparator;
26d2bd05 7import java.util.Iterator;
e253bd50 8import java.util.LinkedList;
26d2bd05
NR
9import java.util.List;
10import java.util.ListIterator;
11
7da41ecd 12import be.nikiroo.jvcard.resources.StringUtils;
cf77cb35 13
26d2bd05
NR
14/**
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.
18 *
19 * All child elements can identify their parent.
20 *
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
24 * elements.
25 *
26 * @author niki
27 *
28 * @param <E>
29 * the type of the child elements
30 */
31public abstract class BaseClass<E extends BaseClass<?>> implements List<E> {
32 protected boolean dirty;
33 protected BaseClass<?> parent;
34 private List<E> list;
35
e253bd50
NR
36 private Comparator<E> comparator = new Comparator<E>() {
37 @Override
38 public int compare(E o1, E o2) {
39 if (o1 == null && o2 == null)
40 return 0;
41 if (o1 == null && o2 != null)
42 return -1;
43 if (o1 != null && o2 == null)
44 return 1;
45
46 return o1.getId().compareTo(o2.getId());
47 }
48 };
49
26d2bd05 50 /**
b9f192ed
NR
51 * Create a new {@link BaseClass} with the items in the given list as its
52 * descendants.
53 *
54 * Note: the elements will be copied from the {@link List}, you cannot
55 * manage the {@link List} from outside
26d2bd05
NR
56 *
57 * @param list
58 * the descendants of this object, or NULL if none
59 */
60 protected BaseClass(List<E> list) {
61 this.list = new ArrayList<E>();
b9f192ed 62
49a08b5d
NR
63 if (list != null) {
64 this.list.addAll(list);
65 }
b9f192ed 66
26d2bd05
NR
67 for (E child : this) {
68 _enter(child, true);
69 }
70 }
71
72 /**
73 * Check if this element has unsaved changes.
74 *
75 * @return TRUE if it has
76 */
77 public boolean isDirty() {
78 return dirty;
79 }
80
81 /**
82 * Delete this element from its parent if any.
83 *
84 * @return TRUE in case of success
85 */
86 public boolean delete() {
87 if (parent != null) {
88 return parent.remove(this);
89 }
90
91 return false;
92 }
93
94 /**
95 * Replace the elements contained in this with those in the given
96 * {@link List}.
97 *
98 * Note: the elements will be copied from the {@link List}, you cannot
99 * manage the {@link List} from outside
100 *
101 * @param list
102 * the list of new elements
103 */
104 public void replaceListContent(List<E> list) {
e253bd50
NR
105 List<E> del = new LinkedList<E>();
106 List<E> add = new LinkedList<E>();
26d2bd05 107
e253bd50
NR
108 if (!compare(list, add, del, del, add)) {
109 removeAll(del);
110 addAll(add);
26d2bd05 111 }
e253bd50
NR
112 }
113
114 /**
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
117 * if not.
118 *
119 * If not equals, the differences will be represented by the given
120 * {@link List}s if they are not NULL.
121 * <ul>
122 * <li><tt>added</tt>will represent the elements in <tt>list</tt> but not in
123 * <tt>this</tt></li>
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>
132 * </ul>
133 *
134 * @param list
135 * the list of new elements
136 * @param added
137 * the list to add the <tt>added</tt> elements to, or NULL
138 * @param removed
139 * the list to add the <tt>removed</tt> elements to, or NULL
140 * @param from
141 * the map to add the <tt>from</tt> elements, or NULL
142 * @param to
143 * the map to add the <tt>to</tt> elements, or NULL
144 *
145 * @return TRUE if the elements are identical
146 */
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);
151
152 List<E> mine = new LinkedList<E>(this.list);
153 List<E> other = new LinkedList<E>(list);
154
155 Collections.sort(other, comparator);
156
157 boolean equ = true;
4298276a
NR
158 E here = mine.size() > 0 ? mine.remove(0) : null;
159 E there = other.size() > 0 ? other.remove(0) : null;
e253bd50 160
4298276a
NR
161 while (here != null || there != null) {
162 if (here == null
163 || (there != null && comparator.compare(here, there) > 0)) {
e253bd50
NR
164 if (added != null)
165 added.add(there);
4298276a 166 there = null;
e253bd50
NR
167 equ = false;
168 } else if (there == null || comparator.compare(here, there) < 0) {
169 if (removed != null)
170 removed.add(here);
4298276a 171 here = null;
e253bd50
NR
172 equ = false;
173 } else {
174 // they represent the same item
cf77cb35 175 if (!((BaseClass) here).isEquals(there, false)) {
e253bd50
NR
176 if (from != null)
177 from.add(here);
178 if (to != null)
179 to.add(there);
180 equ = false;
181 }
4298276a
NR
182 here = null;
183 there = null;
26d2bd05 184 }
4298276a
NR
185
186 if (here == null && mine.size() > 0)
187 here = mine.remove(0);
188 if (there == null && other.size() > 0)
189 there = other.remove(0);
26d2bd05
NR
190 }
191
e253bd50 192 return equ;
26d2bd05
NR
193 }
194
e253bd50
NR
195 /**
196 * Check if the given instance and this one represent the same objects (they
197 * may have different states).
198 *
199 * @param other
200 * the other instance
201 *
202 * @return TRUE if they represent the same object
203 */
204 public boolean isSame(BaseClass<E> other) {
205 if (other == null)
206 return false;
207
208 if (!getClass().equals(other.getClass()))
209 return false;
210
211 return getId().equals(other.getId());
212 }
213
214 /**
215 * Check if the given instance and this one are equivalent (both objects in
216 * the same state, all child elements equivalent).
217 *
218 * @param other
219 * the other instance
220 *
cf77cb35
NR
221 * @param contentOnly
222 * do not check the state of the object itslef, only its content
223 *
e253bd50
NR
224 * @return TRUE if they are equivalent
225 */
226 @SuppressWarnings({ "unchecked", "rawtypes" })
cf77cb35 227 public boolean isEquals(BaseClass<E> other, boolean contentOnly) {
e253bd50
NR
228 if (other == null)
229 return false;
230
231 if (size() != other.size())
232 return false;
233
cf77cb35
NR
234 if (!contentOnly) {
235 if (!isSame(other))
236 return false;
e253bd50 237
cf77cb35
NR
238 if (!getState().equals(other.getState()))
239 return false;
240 }
e253bd50
NR
241
242 Collections.sort(list, comparator);
243 Collections.sort(other.list, other.comparator);
244 for (int index = 0; index < size(); index++) {
cf77cb35 245 if (!((BaseClass) get(index)).isEquals(other.get(index), false))
e253bd50
NR
246 return false;
247 }
248
249 return true;
250 }
251
cf77cb35
NR
252 /**
253 * Get the recursive state of the current object, i.e., its children. It
0b6140e4 254 * represents the full state information about this object's children.
cf77cb35
NR
255 *
256 * @return a {@link String} representing the current content state of this
0b6140e4 257 * object, i.e., its children included
cf77cb35
NR
258 */
259 public String getContentState() {
260 StringBuilder builder = new StringBuilder();
0b6140e4
NR
261 buildContentStateRaw(builder);
262 return StringUtils.getHash(builder.toString());
263 }
cf77cb35 264
0b6140e4
NR
265 /**
266 * Return the (first) child element with the given ID or NULL if not found.
267 *
268 * @param id
269 * the id to look for
270 *
271 * @return the child element or NULL
272 */
273 public E getById(String id) {
cf77cb35 274 for (E child : this) {
0b6140e4
NR
275 if (id == null) {
276 if (child.getId() == null)
277 return child;
278 } else {
279 if (id.equals(child.getId()))
280 return child;
281 }
cf77cb35
NR
282 }
283
0b6140e4 284 return null;
cf77cb35
NR
285 }
286
e253bd50
NR
287 /**
288 * Return the current ID of this object -- it is allowed to change over time
289 * (so, do not cache it).
290 *
291 * @return the current ID
292 */
293 abstract public String getId();
294
295 /**
296 * Get the state of the current object, children <b>not included</b>. It
cf77cb35
NR
297 * represents the full state information about this object, but do not check
298 * its children (see {@link BaseClass#getContentState()} for that).
e253bd50
NR
299 *
300 * @return a {@link String} representing the current state of this object,
301 * children not included
302 */
303 abstract public String getState();
304
0b6140e4
NR
305 /**
306 * Get the recursive state of the current object, i.e., its children. It
307 * represents the full state information about this object's children.
308 *
309 * It is not hashed.
310 *
311 * @param builder
312 * the {@link StringBuilder} that will represent the current
313 * content state of this object, i.e., its children included
314 */
315 void buildContentStateRaw(StringBuilder builder) {
316 builder.append(getState());
317 for (E child : this) {
318 child.buildContentStateRaw(builder);
319 }
320 }
321
26d2bd05
NR
322 /**
323 * Notify that this element has unsaved changes.
324 */
325 void setDirty() {
326 dirty = true;
b9f192ed
NR
327 if (parent != null) {
328 parent.setDirty();
329 }
26d2bd05
NR
330 }
331
332 /**
333 * Notify this element <i>and all its descendants</i> that it is in pristine
334 * state (as opposed to dirty).
335 */
336 void setPristine() {
337 dirty = false;
338 for (E child : this) {
339 child.setPristine();
340 }
341 }
342
343 /**
344 * Set the parent of this element <i>and all its descendants</i>.
345 *
346 * @param parent
347 * the new parent
348 */
349 void setParent(BaseClass<?> parent) {
350 this.parent = parent;
351 for (E child : this) {
352 child.setParent(this);
353 }
354 }
355
356 /**
357 * Each element that leaves the parent will pass trough here.
358 *
359 * @param child
360 * the element to remove from this
361 */
362 private void _leave(E child) {
363 setDirty();
364 }
365
366 /**
367 * Each element that enters the parent will pass trough here.
368 *
369 * @param child
370 * the element to add to this
371 */
372 private void _enter(E child) {
373 _enter(child, false);
374 }
375
376 /**
377 * Each element that enters the parent will pass trough here.
378 *
379 * @param child
380 * the element to add to this
381 */
382 private void _enter(E child, boolean initialLoad) {
383 child.setParent(this);
b9f192ed
NR
384 if (!initialLoad) {
385 setDirty();
26d2bd05 386 child.setDirty();
b9f192ed 387 }
26d2bd05
NR
388 }
389
390 @Override
391 public boolean add(E e) {
392 _enter(e, false);
393 return list.add(e);
394 }
395
396 @Override
397 @SuppressWarnings("unchecked")
398 public boolean remove(Object o) {
399 if (list.remove(o)) {
400 if (o instanceof BaseClass<?>) {
401 _leave((E) o); // expected warning
402 }
403 return true;
404 }
405
406 return false;
407 }
408
409 @Override
410 public boolean addAll(Collection<? extends E> c) {
411 for (E child : c) {
412 _enter(child);
413 }
414
415 return list.addAll(c);
416 }
417
418 @Override
419 public boolean addAll(int index, Collection<? extends E> c) {
420 for (E child : c) {
421 _enter(child);
422 }
423
424 return list.addAll(index, c);
425 }
426
427 @Override
428 public boolean removeAll(Collection<?> c) {
429 boolean changed = false;
430
431 for (Object o : c) {
432 if (remove(o))
433 changed = true;
434 }
435
436 return changed;
437 }
438
439 @Override
440 public boolean retainAll(Collection<?> c) {
441 ArrayList<Object> del = new ArrayList<Object>();
442 for (Object o : c) {
443 del.add(o);
444 }
445 return removeAll(del);
446 }
447
448 @Override
449 public void clear() {
450 for (E child : this) {
451 _leave(child);
452 }
453
454 list.clear();
455 }
456
457 @Override
458 public E set(int index, E element) {
459 E child = get(index);
460 if (child != null)
461 _leave(child);
462 _enter(element);
463
464 return list.set(index, element);
465 }
466
467 @Override
468 public void add(int index, E element) {
469 _enter(element);
470 list.add(index, element);
471 }
472
473 @Override
474 public E remove(int index) {
475 E child = get(index);
476 _leave(child);
477 return list.remove(index);
478 }
479
480 @Override
481 public Iterator<E> iterator() {
482 return listIterator(0);
483 }
484
485 @Override
486 public ListIterator<E> listIterator() {
487 return listIterator(0);
488 }
489
490 @Override
491 public ListIterator<E> listIterator(int index) {
492 final int i = index;
493 return new ListIterator<E>() {
494 ListIterator<E> base = list.listIterator(i);
495 E last;
496
497 @Override
498 public boolean hasNext() {
499 return base.hasNext();
500 }
501
502 @Override
503 public E next() {
504 last = base.next();
505 return last;
506 }
507
508 @Override
509 public boolean hasPrevious() {
510 return base.hasPrevious();
511 }
512
513 @Override
514 public E previous() {
515 last = base.previous();
516 return last;
517 }
518
519 @Override
520 public int nextIndex() {
521 return base.nextIndex();
522 }
523
524 @Override
525 public int previousIndex() {
526 return base.previousIndex();
527 }
528
529 @Override
530 public void remove() {
531 base.remove();
532 _leave(last);
533 }
534
535 @Override
536 public void set(E e) {
537 base.set(e);
538 _leave(last);
539 _enter(e);
540 }
541
542 @Override
543 public void add(E e) {
544 base.add(e);
545 _enter(e);
546 }
547 };
548 }
549
550 @Override
551 public Object[] toArray() {
552 return list.toArray();
553 }
554
555 @Override
556 public <T> T[] toArray(T[] a) {
557 return list.toArray(a);
558 }
559
560 @Override
561 public int size() {
562 return list.size();
563 }
564
565 @Override
566 public boolean isEmpty() {
567 return list.isEmpty();
568 }
569
570 @Override
571 public boolean contains(Object o) {
572 return list.contains(o);
573 }
574
575 @Override
576 public boolean containsAll(Collection<?> c) {
577 return list.containsAll(c);
578 }
579
580 @Override
581 public E get(int index) {
582 return list.get(index);
583 }
584
585 @Override
586 public int indexOf(Object o) {
587 return list.indexOf(o);
588 }
589
590 @Override
591 public int lastIndexOf(Object o) {
592 return list.lastIndexOf(o);
593 }
594
595 @Override
596 public List<E> subList(int fromIndex, int toIndex) {
597 return list.subList(fromIndex, toIndex);
598 }
599}