New launcher class to start all 3 modes:
[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;
158 while (mine.size() > 0 || other.size() > 0) {
159 E here = (mine.size() > 0) ? mine.remove(0) : null;
160 E there = (other.size() > 0) ? other.remove(0) : null;
161
162 if (here == null || comparator.compare(here, there) > 0) {
163 if (added != null)
164 added.add(there);
165 equ = false;
166 } else if (there == null || comparator.compare(here, there) < 0) {
167 if (removed != null)
168 removed.add(here);
169 equ = false;
170 } else {
171 // they represent the same item
cf77cb35 172 if (!((BaseClass) here).isEquals(there, false)) {
e253bd50
NR
173 if (from != null)
174 from.add(here);
175 if (to != null)
176 to.add(there);
177 equ = false;
178 }
26d2bd05
NR
179 }
180 }
181
e253bd50 182 return equ;
26d2bd05
NR
183 }
184
e253bd50
NR
185 /**
186 * Check if the given instance and this one represent the same objects (they
187 * may have different states).
188 *
189 * @param other
190 * the other instance
191 *
192 * @return TRUE if they represent the same object
193 */
194 public boolean isSame(BaseClass<E> other) {
195 if (other == null)
196 return false;
197
198 if (!getClass().equals(other.getClass()))
199 return false;
200
201 return getId().equals(other.getId());
202 }
203
204 /**
205 * Check if the given instance and this one are equivalent (both objects in
206 * the same state, all child elements equivalent).
207 *
208 * @param other
209 * the other instance
210 *
cf77cb35
NR
211 * @param contentOnly
212 * do not check the state of the object itslef, only its content
213 *
e253bd50
NR
214 * @return TRUE if they are equivalent
215 */
216 @SuppressWarnings({ "unchecked", "rawtypes" })
cf77cb35 217 public boolean isEquals(BaseClass<E> other, boolean contentOnly) {
e253bd50
NR
218 if (other == null)
219 return false;
220
221 if (size() != other.size())
222 return false;
223
cf77cb35
NR
224 if (!contentOnly) {
225 if (!isSame(other))
226 return false;
e253bd50 227
cf77cb35
NR
228 if (!getState().equals(other.getState()))
229 return false;
230 }
e253bd50
NR
231
232 Collections.sort(list, comparator);
233 Collections.sort(other.list, other.comparator);
234 for (int index = 0; index < size(); index++) {
cf77cb35 235 if (!((BaseClass) get(index)).isEquals(other.get(index), false))
e253bd50
NR
236 return false;
237 }
238
239 return true;
240 }
241
cf77cb35
NR
242 /**
243 * Get the recursive state of the current object, i.e., its children. It
0b6140e4 244 * represents the full state information about this object's children.
cf77cb35
NR
245 *
246 * @return a {@link String} representing the current content state of this
0b6140e4 247 * object, i.e., its children included
cf77cb35
NR
248 */
249 public String getContentState() {
250 StringBuilder builder = new StringBuilder();
0b6140e4
NR
251 buildContentStateRaw(builder);
252 return StringUtils.getHash(builder.toString());
253 }
cf77cb35 254
0b6140e4
NR
255 /**
256 * Return the (first) child element with the given ID or NULL if not found.
257 *
258 * @param id
259 * the id to look for
260 *
261 * @return the child element or NULL
262 */
263 public E getById(String id) {
cf77cb35 264 for (E child : this) {
0b6140e4
NR
265 if (id == null) {
266 if (child.getId() == null)
267 return child;
268 } else {
269 if (id.equals(child.getId()))
270 return child;
271 }
cf77cb35
NR
272 }
273
0b6140e4 274 return null;
cf77cb35
NR
275 }
276
e253bd50
NR
277 /**
278 * Return the current ID of this object -- it is allowed to change over time
279 * (so, do not cache it).
280 *
281 * @return the current ID
282 */
283 abstract public String getId();
284
285 /**
286 * Get the state of the current object, children <b>not included</b>. It
cf77cb35
NR
287 * represents the full state information about this object, but do not check
288 * its children (see {@link BaseClass#getContentState()} for that).
e253bd50
NR
289 *
290 * @return a {@link String} representing the current state of this object,
291 * children not included
292 */
293 abstract public String getState();
294
0b6140e4
NR
295 /**
296 * Get the recursive state of the current object, i.e., its children. It
297 * represents the full state information about this object's children.
298 *
299 * It is not hashed.
300 *
301 * @param builder
302 * the {@link StringBuilder} that will represent the current
303 * content state of this object, i.e., its children included
304 */
305 void buildContentStateRaw(StringBuilder builder) {
306 builder.append(getState());
307 for (E child : this) {
308 child.buildContentStateRaw(builder);
309 }
310 }
311
26d2bd05
NR
312 /**
313 * Notify that this element has unsaved changes.
314 */
315 void setDirty() {
316 dirty = true;
b9f192ed
NR
317 if (parent != null) {
318 parent.setDirty();
319 }
26d2bd05
NR
320 }
321
322 /**
323 * Notify this element <i>and all its descendants</i> that it is in pristine
324 * state (as opposed to dirty).
325 */
326 void setPristine() {
327 dirty = false;
328 for (E child : this) {
329 child.setPristine();
330 }
331 }
332
333 /**
334 * Set the parent of this element <i>and all its descendants</i>.
335 *
336 * @param parent
337 * the new parent
338 */
339 void setParent(BaseClass<?> parent) {
340 this.parent = parent;
341 for (E child : this) {
342 child.setParent(this);
343 }
344 }
345
346 /**
347 * Each element that leaves the parent will pass trough here.
348 *
349 * @param child
350 * the element to remove from this
351 */
352 private void _leave(E child) {
353 setDirty();
354 }
355
356 /**
357 * Each element that enters the parent will pass trough here.
358 *
359 * @param child
360 * the element to add to this
361 */
362 private void _enter(E child) {
363 _enter(child, false);
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, boolean initialLoad) {
373 child.setParent(this);
b9f192ed
NR
374 if (!initialLoad) {
375 setDirty();
26d2bd05 376 child.setDirty();
b9f192ed 377 }
26d2bd05
NR
378 }
379
380 @Override
381 public boolean add(E e) {
382 _enter(e, false);
383 return list.add(e);
384 }
385
386 @Override
387 @SuppressWarnings("unchecked")
388 public boolean remove(Object o) {
389 if (list.remove(o)) {
390 if (o instanceof BaseClass<?>) {
391 _leave((E) o); // expected warning
392 }
393 return true;
394 }
395
396 return false;
397 }
398
399 @Override
400 public boolean addAll(Collection<? extends E> c) {
401 for (E child : c) {
402 _enter(child);
403 }
404
405 return list.addAll(c);
406 }
407
408 @Override
409 public boolean addAll(int index, Collection<? extends E> c) {
410 for (E child : c) {
411 _enter(child);
412 }
413
414 return list.addAll(index, c);
415 }
416
417 @Override
418 public boolean removeAll(Collection<?> c) {
419 boolean changed = false;
420
421 for (Object o : c) {
422 if (remove(o))
423 changed = true;
424 }
425
426 return changed;
427 }
428
429 @Override
430 public boolean retainAll(Collection<?> c) {
431 ArrayList<Object> del = new ArrayList<Object>();
432 for (Object o : c) {
433 del.add(o);
434 }
435 return removeAll(del);
436 }
437
438 @Override
439 public void clear() {
440 for (E child : this) {
441 _leave(child);
442 }
443
444 list.clear();
445 }
446
447 @Override
448 public E set(int index, E element) {
449 E child = get(index);
450 if (child != null)
451 _leave(child);
452 _enter(element);
453
454 return list.set(index, element);
455 }
456
457 @Override
458 public void add(int index, E element) {
459 _enter(element);
460 list.add(index, element);
461 }
462
463 @Override
464 public E remove(int index) {
465 E child = get(index);
466 _leave(child);
467 return list.remove(index);
468 }
469
470 @Override
471 public Iterator<E> iterator() {
472 return listIterator(0);
473 }
474
475 @Override
476 public ListIterator<E> listIterator() {
477 return listIterator(0);
478 }
479
480 @Override
481 public ListIterator<E> listIterator(int index) {
482 final int i = index;
483 return new ListIterator<E>() {
484 ListIterator<E> base = list.listIterator(i);
485 E last;
486
487 @Override
488 public boolean hasNext() {
489 return base.hasNext();
490 }
491
492 @Override
493 public E next() {
494 last = base.next();
495 return last;
496 }
497
498 @Override
499 public boolean hasPrevious() {
500 return base.hasPrevious();
501 }
502
503 @Override
504 public E previous() {
505 last = base.previous();
506 return last;
507 }
508
509 @Override
510 public int nextIndex() {
511 return base.nextIndex();
512 }
513
514 @Override
515 public int previousIndex() {
516 return base.previousIndex();
517 }
518
519 @Override
520 public void remove() {
521 base.remove();
522 _leave(last);
523 }
524
525 @Override
526 public void set(E e) {
527 base.set(e);
528 _leave(last);
529 _enter(e);
530 }
531
532 @Override
533 public void add(E e) {
534 base.add(e);
535 _enter(e);
536 }
537 };
538 }
539
540 @Override
541 public Object[] toArray() {
542 return list.toArray();
543 }
544
545 @Override
546 public <T> T[] toArray(T[] a) {
547 return list.toArray(a);
548 }
549
550 @Override
551 public int size() {
552 return list.size();
553 }
554
555 @Override
556 public boolean isEmpty() {
557 return list.isEmpty();
558 }
559
560 @Override
561 public boolean contains(Object o) {
562 return list.contains(o);
563 }
564
565 @Override
566 public boolean containsAll(Collection<?> c) {
567 return list.containsAll(c);
568 }
569
570 @Override
571 public E get(int index) {
572 return list.get(index);
573 }
574
575 @Override
576 public int indexOf(Object o) {
577 return list.indexOf(o);
578 }
579
580 @Override
581 public int lastIndexOf(Object o) {
582 return list.lastIndexOf(o);
583 }
584
585 @Override
586 public List<E> subList(int fromIndex, int toIndex) {
587 return list.subList(fromIndex, toIndex);
588 }
589}