jDoc, possible leak on crash, new depth option
[jvcard.git] / src / be / nikiroo / jvcard / BaseClass.java
1 package be.nikiroo.jvcard;
2
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;
12
13 import be.nikiroo.utils.StringUtils;
14
15 /**
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.
19 *
20 * <p>
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.
23 * </p>
24 *
25 * <p>
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
29 * elements.
30 * </p>
31 *
32 * @author niki
33 *
34 * @param <E>
35 * the type of the child elements
36 */
37 public abstract class BaseClass<E extends BaseClass<?>> implements List<E> {
38 protected boolean dirty;
39 protected BaseClass<?> parent;
40 private List<E> list;
41
42 private Comparator<E> comparator = new Comparator<E>() {
43 @Override
44 public int compare(E o1, E o2) {
45 if (o1 == null && o2 == null)
46 return 0;
47 if (o1 == null && o2 != null)
48 return -1;
49 if (o1 != null && o2 == null)
50 return 1;
51
52 return o1.getId().compareTo(o2.getId());
53 }
54 };
55
56 /**
57 * Create a new {@link BaseClass} with the items in the given list as its
58 * descendants.
59 *
60 * Note: the elements will be copied from the {@link List}, you cannot
61 * manage the {@link List} from outside
62 *
63 * @param list
64 * the descendants of this object, or NULL if none
65 */
66 protected BaseClass(List<E> list) {
67 this.list = new ArrayList<E>();
68
69 if (list != null) {
70 this.list.addAll(list);
71 }
72
73 for (E child : this) {
74 _enter(child, true);
75 }
76 }
77
78 /**
79 * Check if this element has unsaved changes.
80 *
81 * @return TRUE if it has
82 */
83 public boolean isDirty() {
84 return dirty;
85 }
86
87 /**
88 * Delete this element from its parent if any.
89 *
90 * @return TRUE in case of success
91 */
92 public boolean delete() {
93 if (parent != null) {
94 return parent.remove(this);
95 }
96
97 return false;
98 }
99
100 /**
101 * Replace the elements contained in this with those in the given
102 * {@link List}.
103 *
104 * Note: the elements will be copied from the {@link List}, you cannot
105 * manage the {@link List} from outside
106 *
107 * @param list
108 * the list of new elements
109 */
110 public void replaceListContent(List<E> list) {
111 List<E> del = new LinkedList<E>();
112 List<E> add = new LinkedList<E>();
113
114 if (!compare(list, add, del, del, add)) {
115 removeAll(del);
116 addAll(add);
117 }
118 }
119
120 /**
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
123 * if not.
124 *
125 * If not equals, the differences will be represented by the given
126 * {@link List}s if they are not NULL.
127 * <ul>
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>
138 * </ul>
139 *
140 * @param list
141 * the list of new elements
142 * @param added
143 * the list to add the <tt>added</tt> elements to, or NULL
144 * @param removed
145 * the list to add the <tt>removed</tt> elements to, or NULL
146 * @param from
147 * the map to add the <tt>from</tt> elements, or NULL
148 * @param to
149 * the map to add the <tt>to</tt> elements, or NULL
150 *
151 * @return TRUE if the elements are identical
152 */
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);
157
158 List<E> mine = new LinkedList<E>(this.list);
159 List<E> other = new LinkedList<E>(list);
160
161 Collections.sort(other, comparator);
162
163 boolean equ = true;
164 E here = mine.size() > 0 ? mine.remove(0) : null;
165 E there = other.size() > 0 ? other.remove(0) : null;
166
167 while (here != null || there != null) {
168 if (here == null
169 || (there != null && comparator.compare(here, there) > 0)) {
170 if (added != null)
171 added.add(there);
172 there = null;
173 equ = false;
174 } else if (there == null || comparator.compare(here, there) < 0) {
175 if (removed != null)
176 removed.add(here);
177 here = null;
178 equ = false;
179 } else {
180 // they represent the same item
181 if (!((BaseClass) here).isEquals(there, false)) {
182 if (from != null)
183 from.add(here);
184 if (to != null)
185 to.add(there);
186 equ = false;
187 }
188 here = null;
189 there = null;
190 }
191
192 if (here == null && mine.size() > 0)
193 here = mine.remove(0);
194 if (there == null && other.size() > 0)
195 there = other.remove(0);
196 }
197
198 return equ;
199 }
200
201 /**
202 * Check if the given instance and this one represent the same objects (they
203 * may have different states).
204 *
205 * @param other
206 * the other instance
207 *
208 * @return TRUE if they represent the same object
209 */
210 public boolean isSame(BaseClass<E> other) {
211 if (other == null)
212 return false;
213
214 if (!getClass().equals(other.getClass()))
215 return false;
216
217 return getId().equals(other.getId());
218 }
219
220 /**
221 * Check if the given instance and this one are equivalent (both objects in
222 * the same state, all child elements equivalent).
223 *
224 * @param other
225 * the other instance
226 *
227 * @param contentOnly
228 * do not check the state of the object itslef, only its content
229 *
230 * @return TRUE if they are equivalent
231 */
232 @SuppressWarnings({ "unchecked", "rawtypes" })
233 public boolean isEquals(BaseClass<E> other, boolean contentOnly) {
234 if (other == null)
235 return false;
236
237 if (size() != other.size())
238 return false;
239
240 if (!contentOnly) {
241 if (!isSame(other))
242 return false;
243
244 if (!getState().equals(other.getState()))
245 return false;
246 }
247
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))
252 return false;
253 }
254
255 return true;
256 }
257
258 /**
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.
262 *
263 * <p>
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.
266 * </p>
267 *
268 * @param self
269 * also include state information about the current object itself
270 * (as opposed to its children)
271 *
272 * @return a {@link String} representing the current content state of this
273 * object, i.e., its children included
274 */
275 public String getContentState(boolean self) {
276 StringBuilder builder = new StringBuilder();
277 buildContentStateRaw(builder, self);
278 return StringUtils.getMd5Hash(builder.toString());
279 }
280
281 /**
282 * Return the (first) child element with the given ID or NULL if not found.
283 *
284 * @param id
285 * the id to look for
286 *
287 * @return the child element or NULL
288 */
289 public E getById(String id) {
290 for (E child : this) {
291 if (id == null) {
292 if (child.getId() == null)
293 return child;
294 } else {
295 if (id.equals(child.getId()))
296 return child;
297 }
298 }
299
300 return null;
301 }
302
303 /**
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.
307 *
308 * @param depth
309 * the depth into which to descend (0 = only this object, not its
310 * children, negative value for infinite depth)
311 *
312 * @return the debug {@link String}
313 */
314 public String getDebugInfo(int depth) {
315 StringBuilder builder = new StringBuilder();
316 getDebugInfo(builder, depth, 0);
317 return builder.toString();
318 }
319
320 /**
321 * Return the current ID of this object -- it is allowed to change over time
322 * (so, do not cache it).
323 *
324 * @return the current ID
325 */
326 abstract public String getId();
327
328 /**
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.
333 *
334 * <p>
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
337 * identical.
338 * </p>
339 *
340 * @return a {@link String} representing the current state of this object,
341 * children not included
342 */
343 abstract public String getState();
344
345 /**
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
348 * children.
349 * <p>
350 * It is not hashed.
351 *
352 * @param builder
353 * the {@link StringBuilder} that will represent the current
354 * content state of this object, i.e., its children included
355 * @param self
356 * also include state information about the current object itself
357 * (as opposed to its children)
358 */
359 void buildContentStateRaw(StringBuilder builder, boolean self) {
360 Collections.sort(this.list, comparator);
361 if (self)
362 builder.append(getState());
363 for (E child : this) {
364 child.buildContentStateRaw(builder, true);
365 }
366 }
367
368 /**
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.
372 *
373 * @param depth
374 * the depth into which to descend (0 = only this object, not its
375 * children, negative value for infinite depth)
376 *
377 * @param tab
378 * the current tabulation increment
379 */
380 void getDebugInfo(StringBuilder builder, int depth, int tab) {
381 for (int i = 0; i < tab; i++)
382 builder.append(" ");
383 builder.append(getContentState(false) + " " + getId());
384
385 if (depth != 0)
386 builder.append(": [");
387
388 if (depth != 0) {
389 for (E child : this) {
390 builder.append("\n");
391 child.getDebugInfo(builder, depth - 1, tab + 1);
392 }
393 }
394 if (depth != 0) {
395 builder.append("\n");
396 for (int i = 0; i < tab; i++)
397 builder.append(" ");
398 builder.append("]");
399 }
400 }
401
402 /**
403 * Notify that this element <i>and all its parent elements</i> has unsaved
404 * changes.
405 */
406 void setDirty() {
407 dirty = true;
408 if (parent != null) {
409 parent.setDirty();
410 }
411 }
412
413 /**
414 * Notify this element <i>and all its descendants</i> that it is in pristine
415 * state (as opposed to dirty).
416 */
417 void setPristine() {
418 dirty = false;
419 for (E child : this) {
420 child.setPristine();
421 }
422 }
423
424 /**
425 * Set the parent of this element.
426 * <p>
427 * Will also check and fix if needed the parent (this) of all its
428 * descendants (recursively).
429 *
430 * @param parent
431 * the new parent
432 */
433 void setParent(BaseClass<?> parent) {
434 this.parent = parent;
435 for (E child : this) {
436 child.setParent(this);
437 }
438 }
439
440 /**
441 * Escape the given value to VCF standard.
442 *
443 * @param value
444 * the value to escape
445 *
446 * @return the escaped value
447 */
448 protected String escape(String value) {
449 if (value == null)
450 return null;
451
452 return value.replaceAll(",", "\\\\,").replaceAll(";", "\\\\;")
453 .replaceAll("\n", "\\\\n");
454 }
455
456 /**
457 * Unescape the given value from the VCF standard.
458 *
459 * @param value
460 * the value to unescape
461 *
462 * @return the unescaped value
463 */
464 protected String unescape(String value) {
465 if (value == null)
466 return null;
467
468 return value.replaceAll("\\\\,", ",").replaceAll("\\\\;", ";")
469 .replaceAll("\\\\n", "\n");
470 }
471
472 /**
473 * Each element that leaves the parent will pass trough here.
474 *
475 * @param child
476 * the element to remove from this
477 */
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");
482 }
483
484 child.parent = null;
485 setDirty();
486 }
487
488 /**
489 * Each element that enters the parent will pass trough here.
490 *
491 * @param child
492 * the element to add to this
493 */
494 private void _enter(E child) {
495 _enter(child, false);
496 }
497
498 /**
499 * Each element that enters the parent will pass trough here.
500 *
501 * @param child
502 * the element to add to this
503 */
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");
508 }
509
510 child.setParent(this);
511 if (!initialLoad) {
512 setDirty();
513 child.setDirty();
514 }
515 }
516
517 @Override
518 public boolean add(E e) {
519 _enter(e, false);
520 return list.add(e);
521 }
522
523 @Override
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
529 }
530 return true;
531 }
532
533 return false;
534 }
535
536 @Override
537 public boolean addAll(Collection<? extends E> c) {
538 for (E child : c) {
539 _enter(child);
540 }
541
542 return list.addAll(c);
543 }
544
545 @Override
546 public boolean addAll(int index, Collection<? extends E> c) {
547 for (E child : c) {
548 _enter(child);
549 }
550
551 return list.addAll(index, c);
552 }
553
554 @Override
555 public boolean removeAll(Collection<?> c) {
556 boolean changed = false;
557
558 for (Object o : c) {
559 if (remove(o))
560 changed = true;
561 }
562
563 return changed;
564 }
565
566 @Override
567 public boolean retainAll(Collection<?> c) {
568 ArrayList<Object> del = new ArrayList<Object>();
569 for (Object o : c) {
570 del.add(o);
571 }
572 return removeAll(del);
573 }
574
575 @Override
576 public void clear() {
577 for (E child : this) {
578 _leave(child);
579 }
580
581 list.clear();
582 }
583
584 @Override
585 public E set(int index, E element) {
586 E child = get(index);
587 if (child != null)
588 _leave(child);
589 _enter(element);
590
591 return list.set(index, element);
592 }
593
594 @Override
595 public void add(int index, E element) {
596 _enter(element);
597 list.add(index, element);
598 }
599
600 @Override
601 public E remove(int index) {
602 E child = get(index);
603 _leave(child);
604 return list.remove(index);
605 }
606
607 @Override
608 public Iterator<E> iterator() {
609 return listIterator(0);
610 }
611
612 @Override
613 public ListIterator<E> listIterator() {
614 return listIterator(0);
615 }
616
617 @Override
618 public ListIterator<E> listIterator(int index) {
619 final int i = index;
620 return new ListIterator<E>() {
621 ListIterator<E> base = list.listIterator(i);
622 E last;
623
624 @Override
625 public boolean hasNext() {
626 return base.hasNext();
627 }
628
629 @Override
630 public E next() {
631 last = base.next();
632 return last;
633 }
634
635 @Override
636 public boolean hasPrevious() {
637 return base.hasPrevious();
638 }
639
640 @Override
641 public E previous() {
642 last = base.previous();
643 return last;
644 }
645
646 @Override
647 public int nextIndex() {
648 return base.nextIndex();
649 }
650
651 @Override
652 public int previousIndex() {
653 return base.previousIndex();
654 }
655
656 @Override
657 public void remove() {
658 base.remove();
659 _leave(last);
660 }
661
662 @Override
663 public void set(E e) {
664 base.set(e);
665 _leave(last);
666 _enter(e);
667 }
668
669 @Override
670 public void add(E e) {
671 base.add(e);
672 _enter(e);
673 }
674 };
675 }
676
677 @Override
678 public Object[] toArray() {
679 return list.toArray();
680 }
681
682 @Override
683 public <T> T[] toArray(T[] a) {
684 return list.toArray(a);
685 }
686
687 @Override
688 public int size() {
689 return list.size();
690 }
691
692 @Override
693 public boolean isEmpty() {
694 return list.isEmpty();
695 }
696
697 @Override
698 public boolean contains(Object o) {
699 return list.contains(o);
700 }
701
702 @Override
703 public boolean containsAll(Collection<?> c) {
704 return list.containsAll(c);
705 }
706
707 @Override
708 public E get(int index) {
709 return list.get(index);
710 }
711
712 @Override
713 public int indexOf(Object o) {
714 return list.indexOf(o);
715 }
716
717 @Override
718 public int lastIndexOf(Object o) {
719 return list.lastIndexOf(o);
720 }
721
722 @Override
723 public List<E> subList(int fromIndex, int toIndex) {
724 return list.subList(fromIndex, toIndex);
725 }
726 }