Source code: com/virtuosotechnologies/asaph/standardmodel/ListHelper.java
1 /*
2 ================================================================================
3
4 FILE: ListHelper.java
5
6 PROJECT:
7
8 Asaph
9
10 CONTENTS:
11
12 Helper for dealing with lists
13
14 PROGRAMMERS:
15
16 Daniel Azuma (DA) <dazuma@kagi.com>
17
18 COPYRIGHT:
19
20 Copyright (C) 2003 Daniel Azuma (dazuma@kagi.com)
21
22 This program is free software; you can redistribute it and/or
23 modify it under the terms of the GNU General Public License as
24 published by the Free Software Foundation; either version 2
25 of the License, or (at your option) any later version.
26
27 This program is distributed in the hope that it will be useful,
28 but WITHOUT ANY WARRANTY; without even the implied warranty of
29 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
30 GNU General Public License for more details.
31
32 You should have received a copy of the GNU General Public
33 License along with this program; if not, write to
34 Free Software Foundation, Inc.
35 59 Temple Place, Suite 330
36 Boston, MA 02111-1307 USA
37
38 ================================================================================
39 */
40
41
42 package com.virtuosotechnologies.asaph.standardmodel;
43
44
45 import java.util.Map;
46 import java.util.HashMap;
47 import javax.swing.undo.AbstractUndoableEdit;
48 import javax.swing.undo.CannotUndoException;
49 import javax.swing.undo.CannotRedoException;
50 import javax.swing.event.UndoableEditListener;
51
52 import com.virtuosotechnologies.lib.base.LinkedObject;
53
54
55 /**
56 * Helper for dealing with lists
57 */
58 /*package*/ class ListHelper
59 {
60 /*package*/ static abstract class ObjectFilter
61 {
62 protected abstract boolean isRelevant(
63 BaseSongMember member);
64 }
65
66
67 /*package*/ static abstract class KeyExtractor
68 {
69 protected abstract String extractKey(
70 BaseSongMember member);
71 }
72
73
74 private BaseSongMember owner_;
75 private LinkedObject delimiter_;
76 private Map idMap_;
77 private KeyExtractor keyExtractor_;
78
79
80 /*package*/ ListHelper(
81 BaseSongMember owner)
82 {
83 this(owner, null);
84 }
85
86
87 /*package*/ ListHelper(
88 BaseSongMember owner,
89 KeyExtractor keyExtractor)
90 {
91 owner_ = owner;
92 delimiter_ = new LinkedObject();
93 keyExtractor_ = keyExtractor;
94 if (keyExtractor_ != null)
95 {
96 idMap_ = new HashMap();
97 }
98 }
99
100
101 private LinkedObject getLink(
102 Object obj)
103 {
104 if (obj == null)
105 {
106 return delimiter_;
107 }
108 if (!(obj instanceof BaseSongMember))
109 {
110 throw new IllegalArgumentException("Reference object not a BaseSongMember");
111 }
112 BaseSongMember ret = (BaseSongMember)obj;
113 if (ret.internalGetParent() != owner_)
114 {
115 throw new IllegalArgumentException("Reference object not a child of the owner");
116 }
117 return ret;
118 }
119
120
121 private String getKey(
122 BaseSongMember object,
123 boolean checkForDuplicates)
124 {
125 if (idMap_ == null)
126 {
127 return null;
128 }
129 String key = keyExtractor_.extractKey(object);
130 if (key != null && checkForDuplicates)
131 {
132 if (idMap_.containsKey(key))
133 {
134 throw new IllegalStateException("Duplicate key: "+key);
135 }
136 }
137 return key;
138 }
139
140
141 /*package*/ int getCount()
142 {
143 int count = 0;
144 for (LinkedObject link = delimiter_.getNext(); link != delimiter_;
145 link = link.getNext())
146 {
147 ++count;
148 }
149 return count;
150 }
151
152
153 /*package*/ int getCount(
154 ObjectFilter filter)
155 {
156 int count = 0;
157 for (LinkedObject link = delimiter_.getNext(); link != delimiter_;
158 link = link.getNext())
159 {
160 BaseSongMember member = (BaseSongMember)link;
161 if (filter.isRelevant(member))
162 {
163 ++count;
164 }
165 }
166 return count;
167 }
168
169
170 /*package*/ BaseSongMember getNthObject(
171 int n)
172 {
173 if (n < 0)
174 {
175 throw new IndexOutOfBoundsException("n == "+n);
176 }
177 int i = n;
178 for (LinkedObject link = delimiter_.getNext(); link != delimiter_;
179 link = link.getNext())
180 {
181 if (i == 0)
182 {
183 return (BaseSongMember)link;
184 }
185 --i;
186 }
187 throw new IndexOutOfBoundsException("n == "+n);
188 }
189
190
191 /*package*/ BaseSongMember getNthObject(
192 int n,
193 ObjectFilter filter)
194 {
195 if (n < 0)
196 {
197 throw new IndexOutOfBoundsException("n == "+n);
198 }
199 int i = n;
200 for (LinkedObject link = delimiter_.getNext(); link != delimiter_;
201 link = link.getNext())
202 {
203 BaseSongMember member = (BaseSongMember)link;
204 if (filter.isRelevant(member))
205 {
206 if (i == 0)
207 {
208 return member;
209 }
210 --i;
211 }
212 }
213 throw new IndexOutOfBoundsException("n == "+n);
214 }
215
216
217 /*package*/ BaseSongMember getNextObject(
218 Object reference)
219 {
220 LinkedObject link = getLink(reference).getNext();
221 if (link == delimiter_)
222 {
223 return null;
224 }
225 else
226 {
227 return (BaseSongMember)link;
228 }
229 }
230
231
232 /*package*/ BaseSongMember getNextObject(
233 Object reference,
234 ObjectFilter filter)
235 {
236 LinkedObject link = getLink(reference);
237 while (true)
238 {
239 link = link.getNext();
240 if (link == delimiter_)
241 {
242 return null;
243 }
244 BaseSongMember ret = (BaseSongMember)link;
245 if (filter.isRelevant(ret))
246 {
247 return ret;
248 }
249 }
250 }
251
252
253 /*package*/ BaseSongMember getPreviousObject(
254 Object reference)
255 {
256 LinkedObject link = getLink(reference).getPrevious();
257 if (link == delimiter_)
258 {
259 return null;
260 }
261 else
262 {
263 return (BaseSongMember)link;
264 }
265 }
266
267
268 /*package*/ BaseSongMember getPreviousObject(
269 Object reference,
270 ObjectFilter filter)
271 {
272 LinkedObject link = getLink(reference);
273 while (true)
274 {
275 link = link.getPrevious();
276 if (link == delimiter_)
277 {
278 return null;
279 }
280 BaseSongMember ret = (BaseSongMember)link;
281 if (filter.isRelevant(ret))
282 {
283 return ret;
284 }
285 }
286 }
287
288
289 /*package*/ BaseSongMember getObjectForKey(
290 String key)
291 {
292 if (key == null)
293 {
294 throw new NullPointerException();
295 }
296 if (idMap_ == null)
297 {
298 return null;
299 }
300 else
301 {
302 return (BaseSongMember)idMap_.get(key);
303 }
304 }
305
306
307 /*package*/ boolean containsObject(
308 Object object)
309 {
310 if (object instanceof BaseSongMember)
311 {
312 return ((BaseSongMember)object).internalGetParent() == owner_;
313 }
314 return false;
315 }
316
317
318 /*package*/ void insertObjectBefore(
319 final BaseSongMember object,
320 Object before,
321 UndoableEditListener undoListener)
322 {
323 final LinkedObject link = getLink(before);
324 final String key = getKey(object, true);
325 object.linkThisBefore(link);
326 if (key != null)
327 {
328 idMap_.put(key, object);
329 }
330 if (undoListener != null)
331 {
332 owner_.internalReportUndoableEdit(undoListener,
333 new AbstractUndoableEdit()
334 {
335 public void undo()
336 throws CannotUndoException
337 {
338 super.undo();
339 object.unlinkThis();
340 object.internalSetDefunct();
341 if (key != null)
342 {
343 idMap_.remove(key);
344 }
345 }
346
347 public void redo()
348 throws CannotRedoException
349 {
350 super.redo();
351 object.internalClearDefunct();
352 object.linkThisBefore(link);
353 if (key != null)
354 {
355 idMap_.put(key, object);
356 }
357 }
358 });
359 }
360 }
361
362
363 /*package*/ void insertObjectAfter(
364 final BaseSongMember object,
365 Object after,
366 UndoableEditListener undoListener)
367 {
368 final LinkedObject link = getLink(after);
369 final String key = getKey(object, true);
370 object.linkThisAfter(link);
371 if (key != null)
372 {
373 idMap_.put(key, object);
374 }
375 if (undoListener != null)
376 {
377 owner_.internalReportUndoableEdit(undoListener,
378 new AbstractUndoableEdit()
379 {
380 public void undo()
381 throws CannotUndoException
382 {
383 super.undo();
384 object.unlinkThis();
385 object.internalSetDefunct();
386 if (key != null)
387 {
388 idMap_.remove(key);
389 }
390 }
391
392 public void redo()
393 throws CannotRedoException
394 {
395 super.redo();
396 object.internalClearDefunct();
397 object.linkThisAfter(link);
398 if (key != null)
399 {
400 idMap_.put(key, object);
401 }
402 }
403 });
404 }
405 }
406
407
408 /*package*/ void appendObject(
409 final BaseSongMember object,
410 UndoableEditListener undoListener)
411 {
412 final String key = getKey(object, true);
413 object.linkThisBefore(delimiter_);
414 if (key != null)
415 {
416 idMap_.put(key, object);
417 }
418 if (undoListener != null)
419 {
420 owner_.internalReportUndoableEdit(undoListener,
421 new AbstractUndoableEdit()
422 {
423 public void undo()
424 throws CannotUndoException
425 {
426 super.undo();
427 object.unlinkThis();
428 object.internalSetDefunct();
429 if (key != null)
430 {
431 idMap_.remove(key);
432 }
433 }
434
435 public void redo()
436 throws CannotRedoException
437 {
438 super.redo();
439 object.internalClearDefunct();
440 object.linkThisBefore(delimiter_);
441 if (key != null)
442 {
443 idMap_.put(key, object);
444 }
445 }
446 });
447 }
448 }
449
450
451 /*package*/ void replaceObject(
452 final Object oldObject,
453 final BaseSongMember newObject,
454 UndoableEditListener undoListener)
455 {
456 if (oldObject == null)
457 {
458 throw new NullPointerException();
459 }
460 final BaseSongMember oldLink = (BaseSongMember)getLink(oldObject);
461 final LinkedObject prev = oldLink.getPrevious();
462 final String oldKey = getKey((BaseSongMember)oldObject, false);
463 final String newKey = getKey(newObject, false);
464 if (newKey != null && idMap_.containsKey(newKey) && !newKey.equals(oldKey))
465 {
466 throw new IllegalStateException("Duplicate key: "+newKey);
467 }
468
469 oldLink.unlinkThis();
470 oldLink.internalSetDefunct();
471 if (oldKey != null)
472 {
473 idMap_.remove(oldKey);
474 }
475 newObject.linkThisAfter(prev);
476 if (newKey != null)
477 {
478 idMap_.put(newKey, newObject);
479 }
480 if (undoListener != null)
481 {
482 owner_.internalReportUndoableEdit(undoListener,
483 new AbstractUndoableEdit()
484 {
485 public void undo()
486 throws CannotUndoException
487 {
488 super.undo();
489 newObject.unlinkThis();
490 newObject.internalSetDefunct();
491 if (newKey != null)
492 {
493 idMap_.remove(newKey);
494 }
495 oldLink.internalClearDefunct();
496 oldLink.linkThisAfter(prev);
497 if (oldKey != null)
498 {
499 idMap_.put(oldKey, oldLink);
500 }
501 }
502
503 public void redo()
504 throws CannotRedoException
505 {
506 super.redo();
507 oldLink.unlinkThis();
508 oldLink.internalSetDefunct();
509 if (oldKey != null)
510 {
511 idMap_.remove(oldKey);
512 }
513 newObject.internalClearDefunct();
514 newObject.linkThisAfter(prev);
515 if (newKey != null)
516 {
517 idMap_.put(newKey, newObject);
518 }
519 }
520 });
521 }
522 }
523
524
525 /*package*/ void removeObject(
526 final Object object,
527 UndoableEditListener undoListener)
528 {
529 if (object == null)
530 {
531 throw new NullPointerException();
532 }
533 final BaseSongMember link = (BaseSongMember)getLink(object);
534 final LinkedObject prev = link.getPrevious();
535 final String key = getKey((BaseSongMember)object, false);
536 link.unlinkThis();
537 link.internalSetDefunct();
538 if (key != null)
539 {
540 idMap_.remove(key);
541 }
542 if (undoListener != null)
543 {
544 owner_.internalReportUndoableEdit(undoListener,
545 new AbstractUndoableEdit()
546 {
547 public void undo()
548 throws CannotUndoException
549 {
550 super.undo();
551 link.internalClearDefunct();
552 link.linkThisAfter(prev);
553 if (key != null)
554 {
555 idMap_.put(key, object);
556 }
557 }
558
559 public void redo()
560 throws CannotRedoException
561 {
562 super.redo();
563 link.unlinkThis();
564 link.internalSetDefunct();
565 if (key != null)
566 {
567 idMap_.remove(key);
568 }
569 }
570 });
571 }
572 }
573
574
575 /*package*/ void clear(
576 UndoableEditListener undoListener)
577 {
578 while (delimiter_.getPrevious() != delimiter_)
579 {
580 final BaseSongMember object = (BaseSongMember)delimiter_.getPrevious();
581 final String key = getKey(object, false);
582 object.unlinkThis();
583 object.internalSetDefunct();
584 if (key != null)
585 {
586 idMap_.remove(key);
587 }
588 if (undoListener != null)
589 {
590 owner_.internalReportUndoableEdit(undoListener,
591 new AbstractUndoableEdit()
592 {
593 public void undo()
594 throws CannotUndoException
595 {
596 super.undo();
597 object.internalClearDefunct();
598 object.linkThisBefore(delimiter_);
599 if (key != null)
600 {
601 idMap_.put(key, object);
602 }
603 }
604
605 public void redo()
606 throws CannotRedoException
607 {
608 super.redo();
609 object.unlinkThis();
610 object.internalSetDefunct();
611 if (key != null)
612 {
613 idMap_.remove(key);
614 }
615 }
616 });
617 }
618 }
619 }
620 }