Remote: jdoc + description + some fixes:
[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
e4444b0b
NR
254 * represents the full state information about this object's children. It
255 * may not contains spaces nor new lines.
256 *
257 * <p>
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
260 * identical.
261 * </p>
cf77cb35
NR
262 *
263 * @return a {@link String} representing the current content state of this
0b6140e4 264 * object, i.e., its children included
cf77cb35
NR
265 */
266 public String getContentState() {
267 StringBuilder builder = new StringBuilder();
0b6140e4
NR
268 buildContentStateRaw(builder);
269 return StringUtils.getHash(builder.toString());
270 }
cf77cb35 271
0b6140e4
NR
272 /**
273 * Return the (first) child element with the given ID or NULL if not found.
274 *
275 * @param id
276 * the id to look for
277 *
278 * @return the child element or NULL
279 */
280 public E getById(String id) {
cf77cb35 281 for (E child : this) {
0b6140e4
NR
282 if (id == null) {
283 if (child.getId() == null)
284 return child;
285 } else {
286 if (id.equals(child.getId()))
287 return child;
288 }
cf77cb35
NR
289 }
290
0b6140e4 291 return null;
cf77cb35
NR
292 }
293
e253bd50
NR
294 /**
295 * Return the current ID of this object -- it is allowed to change over time
296 * (so, do not cache it).
297 *
298 * @return the current ID
299 */
300 abstract public String getId();
301
302 /**
303 * Get the state of the current object, children <b>not included</b>. It
cf77cb35 304 * represents the full state information about this object, but do not check
e4444b0b
NR
305 * its children (see {@link BaseClass#getContentState()} for that). It may
306 * not contains spaces nor new lines.
307 *
308 * <p>
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
311 * identical.
312 * </p>
e253bd50
NR
313 *
314 * @return a {@link String} representing the current state of this object,
315 * children not included
316 */
317 abstract public String getState();
318
0b6140e4
NR
319 /**
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.
322 *
323 * It is not hashed.
324 *
325 * @param builder
326 * the {@link StringBuilder} that will represent the current
327 * content state of this object, i.e., its children included
328 */
329 void buildContentStateRaw(StringBuilder builder) {
330 builder.append(getState());
331 for (E child : this) {
332 child.buildContentStateRaw(builder);
333 }
334 }
335
26d2bd05
NR
336 /**
337 * Notify that this element has unsaved changes.
338 */
339 void setDirty() {
340 dirty = true;
b9f192ed
NR
341 if (parent != null) {
342 parent.setDirty();
343 }
26d2bd05
NR
344 }
345
346 /**
347 * Notify this element <i>and all its descendants</i> that it is in pristine
348 * state (as opposed to dirty).
349 */
350 void setPristine() {
351 dirty = false;
352 for (E child : this) {
353 child.setPristine();
354 }
355 }
356
357 /**
358 * Set the parent of this element <i>and all its descendants</i>.
359 *
360 * @param parent
361 * the new parent
362 */
363 void setParent(BaseClass<?> parent) {
364 this.parent = parent;
365 for (E child : this) {
366 child.setParent(this);
367 }
368 }
369
370 /**
371 * Each element that leaves the parent will pass trough here.
372 *
373 * @param child
374 * the element to remove from this
375 */
376 private void _leave(E child) {
377 setDirty();
378 }
379
380 /**
381 * Each element that enters the parent will pass trough here.
382 *
383 * @param child
384 * the element to add to this
385 */
386 private void _enter(E child) {
387 _enter(child, false);
388 }
389
390 /**
391 * Each element that enters the parent will pass trough here.
392 *
393 * @param child
394 * the element to add to this
395 */
396 private void _enter(E child, boolean initialLoad) {
397 child.setParent(this);
b9f192ed
NR
398 if (!initialLoad) {
399 setDirty();
26d2bd05 400 child.setDirty();
b9f192ed 401 }
26d2bd05
NR
402 }
403
404 @Override
405 public boolean add(E e) {
406 _enter(e, false);
407 return list.add(e);
408 }
409
410 @Override
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
416 }
417 return true;
418 }
419
420 return false;
421 }
422
423 @Override
424 public boolean addAll(Collection<? extends E> c) {
425 for (E child : c) {
426 _enter(child);
427 }
428
429 return list.addAll(c);
430 }
431
432 @Override
433 public boolean addAll(int index, Collection<? extends E> c) {
434 for (E child : c) {
435 _enter(child);
436 }
437
438 return list.addAll(index, c);
439 }
440
441 @Override
442 public boolean removeAll(Collection<?> c) {
443 boolean changed = false;
444
445 for (Object o : c) {
446 if (remove(o))
447 changed = true;
448 }
449
450 return changed;
451 }
452
453 @Override
454 public boolean retainAll(Collection<?> c) {
455 ArrayList<Object> del = new ArrayList<Object>();
456 for (Object o : c) {
457 del.add(o);
458 }
459 return removeAll(del);
460 }
461
462 @Override
463 public void clear() {
464 for (E child : this) {
465 _leave(child);
466 }
467
468 list.clear();
469 }
470
471 @Override
472 public E set(int index, E element) {
473 E child = get(index);
474 if (child != null)
475 _leave(child);
476 _enter(element);
477
478 return list.set(index, element);
479 }
480
481 @Override
482 public void add(int index, E element) {
483 _enter(element);
484 list.add(index, element);
485 }
486
487 @Override
488 public E remove(int index) {
489 E child = get(index);
490 _leave(child);
491 return list.remove(index);
492 }
493
494 @Override
495 public Iterator<E> iterator() {
496 return listIterator(0);
497 }
498
499 @Override
500 public ListIterator<E> listIterator() {
501 return listIterator(0);
502 }
503
504 @Override
505 public ListIterator<E> listIterator(int index) {
506 final int i = index;
507 return new ListIterator<E>() {
508 ListIterator<E> base = list.listIterator(i);
509 E last;
510
511 @Override
512 public boolean hasNext() {
513 return base.hasNext();
514 }
515
516 @Override
517 public E next() {
518 last = base.next();
519 return last;
520 }
521
522 @Override
523 public boolean hasPrevious() {
524 return base.hasPrevious();
525 }
526
527 @Override
528 public E previous() {
529 last = base.previous();
530 return last;
531 }
532
533 @Override
534 public int nextIndex() {
535 return base.nextIndex();
536 }
537
538 @Override
539 public int previousIndex() {
540 return base.previousIndex();
541 }
542
543 @Override
544 public void remove() {
545 base.remove();
546 _leave(last);
547 }
548
549 @Override
550 public void set(E e) {
551 base.set(e);
552 _leave(last);
553 _enter(e);
554 }
555
556 @Override
557 public void add(E e) {
558 base.add(e);
559 _enter(e);
560 }
561 };
562 }
563
564 @Override
565 public Object[] toArray() {
566 return list.toArray();
567 }
568
569 @Override
570 public <T> T[] toArray(T[] a) {
571 return list.toArray(a);
572 }
573
574 @Override
575 public int size() {
576 return list.size();
577 }
578
579 @Override
580 public boolean isEmpty() {
581 return list.isEmpty();
582 }
583
584 @Override
585 public boolean contains(Object o) {
586 return list.contains(o);
587 }
588
589 @Override
590 public boolean containsAll(Collection<?> c) {
591 return list.containsAll(c);
592 }
593
594 @Override
595 public E get(int index) {
596 return list.get(index);
597 }
598
599 @Override
600 public int indexOf(Object o) {
601 return list.indexOf(o);
602 }
603
604 @Override
605 public int lastIndexOf(Object o) {
606 return list.lastIndexOf(o);
607 }
608
609 @Override
610 public List<E> subList(int fromIndex, int toIndex) {
611 return list.subList(fromIndex, toIndex);
612 }
613}