abaa9ccbd37d04fe7a48fc8a2a57b8c59ebb267a
[jvcard.git] / src / be / nikiroo / jvcard / BaseClass.java
1 package be.nikiroo.jvcard;
2
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;
9 import java.util.List;
10 import java.util.ListIterator;
11
12 import be.nikiroo.jvcard.tui.StringUtils;
13
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 */
31 public abstract class BaseClass<E extends BaseClass<?>> implements List<E> {
32 protected boolean dirty;
33 protected BaseClass<?> parent;
34 private List<E> list;
35
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
50 /**
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
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>();
62
63 if (list != null) {
64 this.list.addAll(list);
65 }
66
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) {
105 List<E> del = new LinkedList<E>();
106 List<E> add = new LinkedList<E>();
107
108 if (!compare(list, add, del, del, add)) {
109 removeAll(del);
110 addAll(add);
111 }
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
172 if (!((BaseClass) here).isEquals(there, false)) {
173 if (from != null)
174 from.add(here);
175 if (to != null)
176 to.add(there);
177 equ = false;
178 }
179 }
180 }
181
182 return equ;
183 }
184
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 *
211 * @param contentOnly
212 * do not check the state of the object itslef, only its content
213 *
214 * @return TRUE if they are equivalent
215 */
216 @SuppressWarnings({ "unchecked", "rawtypes" })
217 public boolean isEquals(BaseClass<E> other, boolean contentOnly) {
218 if (other == null)
219 return false;
220
221 if (size() != other.size())
222 return false;
223
224 if (!contentOnly) {
225 if (!isSame(other))
226 return false;
227
228 if (!getState().equals(other.getState()))
229 return false;
230 }
231
232 Collections.sort(list, comparator);
233 Collections.sort(other.list, other.comparator);
234 for (int index = 0; index < size(); index++) {
235 if (!((BaseClass) get(index)).isEquals(other.get(index), false))
236 return false;
237 }
238
239 return true;
240 }
241
242 /**
243 * Get the recursive state of the current object, i.e., its children. It
244 * represents the full state information about this object's children. It
245 * does not check the state of the object itself.
246 *
247 * @return a {@link String} representing the current content state of this
248 * object, i.e., its children
249 */
250 public String getContentState() {
251 StringBuilder builder = new StringBuilder();
252
253 for (E child : this) {
254 builder.append(child.getContentState());
255 }
256
257 return StringUtils.getHash(builder.toString());
258 }
259
260 /**
261 * Return the current ID of this object -- it is allowed to change over time
262 * (so, do not cache it).
263 *
264 * @return the current ID
265 */
266 abstract public String getId();
267
268 /**
269 * Get the state of the current object, children <b>not included</b>. It
270 * represents the full state information about this object, but do not check
271 * its children (see {@link BaseClass#getContentState()} for that).
272 *
273 * @return a {@link String} representing the current state of this object,
274 * children not included
275 */
276 abstract public String getState();
277
278 /**
279 * Notify that this element has unsaved changes.
280 */
281 void setDirty() {
282 dirty = true;
283 if (parent != null) {
284 parent.setDirty();
285 }
286 }
287
288 /**
289 * Notify this element <i>and all its descendants</i> that it is in pristine
290 * state (as opposed to dirty).
291 */
292 void setPristine() {
293 dirty = false;
294 for (E child : this) {
295 child.setPristine();
296 }
297 }
298
299 /**
300 * Set the parent of this element <i>and all its descendants</i>.
301 *
302 * @param parent
303 * the new parent
304 */
305 void setParent(BaseClass<?> parent) {
306 this.parent = parent;
307 for (E child : this) {
308 child.setParent(this);
309 }
310 }
311
312 /**
313 * Each element that leaves the parent will pass trough here.
314 *
315 * @param child
316 * the element to remove from this
317 */
318 private void _leave(E child) {
319 setDirty();
320 }
321
322 /**
323 * Each element that enters the parent will pass trough here.
324 *
325 * @param child
326 * the element to add to this
327 */
328 private void _enter(E child) {
329 _enter(child, false);
330 }
331
332 /**
333 * Each element that enters the parent will pass trough here.
334 *
335 * @param child
336 * the element to add to this
337 */
338 private void _enter(E child, boolean initialLoad) {
339 child.setParent(this);
340 if (!initialLoad) {
341 setDirty();
342 child.setDirty();
343 }
344 }
345
346 @Override
347 public boolean add(E e) {
348 _enter(e, false);
349 return list.add(e);
350 }
351
352 @Override
353 @SuppressWarnings("unchecked")
354 public boolean remove(Object o) {
355 if (list.remove(o)) {
356 if (o instanceof BaseClass<?>) {
357 _leave((E) o); // expected warning
358 }
359 return true;
360 }
361
362 return false;
363 }
364
365 @Override
366 public boolean addAll(Collection<? extends E> c) {
367 for (E child : c) {
368 _enter(child);
369 }
370
371 return list.addAll(c);
372 }
373
374 @Override
375 public boolean addAll(int index, Collection<? extends E> c) {
376 for (E child : c) {
377 _enter(child);
378 }
379
380 return list.addAll(index, c);
381 }
382
383 @Override
384 public boolean removeAll(Collection<?> c) {
385 boolean changed = false;
386
387 for (Object o : c) {
388 if (remove(o))
389 changed = true;
390 }
391
392 return changed;
393 }
394
395 @Override
396 public boolean retainAll(Collection<?> c) {
397 ArrayList<Object> del = new ArrayList<Object>();
398 for (Object o : c) {
399 del.add(o);
400 }
401 return removeAll(del);
402 }
403
404 @Override
405 public void clear() {
406 for (E child : this) {
407 _leave(child);
408 }
409
410 list.clear();
411 }
412
413 @Override
414 public E set(int index, E element) {
415 E child = get(index);
416 if (child != null)
417 _leave(child);
418 _enter(element);
419
420 return list.set(index, element);
421 }
422
423 @Override
424 public void add(int index, E element) {
425 _enter(element);
426 list.add(index, element);
427 }
428
429 @Override
430 public E remove(int index) {
431 E child = get(index);
432 _leave(child);
433 return list.remove(index);
434 }
435
436 @Override
437 public Iterator<E> iterator() {
438 return listIterator(0);
439 }
440
441 @Override
442 public ListIterator<E> listIterator() {
443 return listIterator(0);
444 }
445
446 @Override
447 public ListIterator<E> listIterator(int index) {
448 final int i = index;
449 return new ListIterator<E>() {
450 ListIterator<E> base = list.listIterator(i);
451 E last;
452
453 @Override
454 public boolean hasNext() {
455 return base.hasNext();
456 }
457
458 @Override
459 public E next() {
460 last = base.next();
461 return last;
462 }
463
464 @Override
465 public boolean hasPrevious() {
466 return base.hasPrevious();
467 }
468
469 @Override
470 public E previous() {
471 last = base.previous();
472 return last;
473 }
474
475 @Override
476 public int nextIndex() {
477 return base.nextIndex();
478 }
479
480 @Override
481 public int previousIndex() {
482 return base.previousIndex();
483 }
484
485 @Override
486 public void remove() {
487 base.remove();
488 _leave(last);
489 }
490
491 @Override
492 public void set(E e) {
493 base.set(e);
494 _leave(last);
495 _enter(e);
496 }
497
498 @Override
499 public void add(E e) {
500 base.add(e);
501 _enter(e);
502 }
503 };
504 }
505
506 @Override
507 public Object[] toArray() {
508 return list.toArray();
509 }
510
511 @Override
512 public <T> T[] toArray(T[] a) {
513 return list.toArray(a);
514 }
515
516 @Override
517 public int size() {
518 return list.size();
519 }
520
521 @Override
522 public boolean isEmpty() {
523 return list.isEmpty();
524 }
525
526 @Override
527 public boolean contains(Object o) {
528 return list.contains(o);
529 }
530
531 @Override
532 public boolean containsAll(Collection<?> c) {
533 return list.containsAll(c);
534 }
535
536 @Override
537 public E get(int index) {
538 return list.get(index);
539 }
540
541 @Override
542 public int indexOf(Object o) {
543 return list.indexOf(o);
544 }
545
546 @Override
547 public int lastIndexOf(Object o) {
548 return list.lastIndexOf(o);
549 }
550
551 @Override
552 public List<E> subList(int fromIndex, int toIndex) {
553 return list.subList(fromIndex, toIndex);
554 }
555 }