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