001 package edu.nrao.sss.catalog; 002 003 import java.io.Writer; 004 import java.util.ArrayList; 005 import java.util.Collection; 006 import java.util.Collections; 007 import java.util.Comparator; 008 import java.util.List; 009 import java.util.Set; 010 import java.util.concurrent.CopyOnWriteArraySet; 011 012 import javax.xml.bind.JAXBException; 013 import javax.xml.bind.annotation.XmlAttribute; 014 015 import edu.nrao.sss.util.Identifiable; 016 import edu.nrao.sss.util.JaxbUtility; 017 018 /** 019 * A generic catalog. In addition to holding a collection of catalog 020 * {@link CatalogItem items}, this catalog supports the notion of 021 * {@link CatalogItemGroup groups} of catalog items. 022 * <p> 023 * <b>Version Info:</b> 024 * <table style="margin-left:2em"> 025 * <tr><td>$Revision: 2204 $</td></tr> 026 * <tr><td>$Date: 2009-04-16 11:58:16 -0600 (Thu, 16 Apr 2009) $</td></tr> 027 * <tr><td>$Author: dharland $ (last person to modify)</td></tr> 028 * </table></p> 029 * 030 * @author David M. Harland 031 * @since 2006-10-23 032 */ 033 public abstract class Catalog<I extends CatalogItem<I>, 034 G extends CatalogItemGroup<I,G,C>, 035 C extends Catalog<I,G,C>> 036 implements Cloneable, CatalogItemGroupListener<I,G,C> 037 { 038 private static final String INIT_NAME = "[New Catalog]"; 039 private static final String NO_NAME = "[Unnamed Catalog]"; 040 private static final String DEFAULT_RESERVED_GROUP_NAME = "All Items"; 041 042 //============================================================================ 043 // INSTANCE VARIABLES AND CONSTRUCTORS 044 //============================================================================ 045 046 //IDENTIFICATION 047 private long id; 048 private String name; 049 050 //CONTENTS 051 private G mainGroup; //Master list of items 052 private List<G> groups; //Does NOT contain mainGroup 053 054 //OTHER 055 private String reservedGroupName; 056 057 //NONPERSISTED PROPERTIES 058 private Set<CatalogListener<I,G,C>> listeners; 059 private boolean suppressNotification; 060 061 /** Helps create a new catalog with a default name. */ 062 public Catalog() 063 { 064 this(INIT_NAME); 065 } 066 067 /** 068 * Helps create a new catalog with the given name. 069 * 070 * @param nameOfCatalog the name of this catalog. If this value is 071 * <i>null</i>, this catalog will be given a 072 * default name. 073 */ 074 public Catalog(String nameOfCatalog) 075 { 076 id = getIdOfUnidentified(); 077 name = (nameOfCatalog == null) ? NO_NAME : nameOfCatalog; 078 079 reservedGroupName = DEFAULT_RESERVED_GROUP_NAME; 080 081 groups = new ArrayList<G>(); 082 mainGroup = createMainGroup(); 083 084 listeners = new CopyOnWriteArraySet<CatalogListener<I,G,C>>(); 085 suppressNotification = false; 086 087 mainGroup.addListener(this); 088 } 089 090 /** 091 * Creates and returns a new group to use as the holder of items for this 092 * catalog. 093 * @return a new group to use as this catalog's main group. 094 */ 095 abstract protected G createMainGroup(); 096 097 /** 098 * Returns a group of the same type of items used by this catalog. 099 * @return a group of the same type of items used by this catalog. 100 */ 101 abstract public G createGroup(); 102 103 //============================================================================ 104 // IDENTIFICATION 105 //============================================================================ 106 107 /** 108 * Sets the ID of this catalog. If {@code id} is <i>null</i>, this catalog's 109 * ID will be set to an <i>unidentified</i> ID. 110 * 111 * @param id a new ID for this catalog. 112 */ 113 protected void setId(Long id) 114 { 115 this.id = (id == null) ? getIdOfUnidentified() : id; 116 } 117 118 /** 119 * Returns a value that represents an undefined ID. 120 * Subclasses may override this method to suit their needs. 121 */ 122 protected long getIdOfUnidentified() 123 { 124 return -1L; 125 } 126 127 /** 128 * Resets this catalog's ID, and the IDs of all its groups, 129 * to a value that represents the unidentified state. 130 * Note that it does nothing to the IDs of its items, because 131 * {@link CatalogItem} does not implement the {@link Identifiable} 132 * interface. Subclasses of this catalog whose items are 133 * <tt>Identifiable</tt> should override this method, call 134 * <tt>super.clearId()</tt>, then clear the IDs of their items. 135 * <p> 136 * This method is useful for preparing a catalog for storage in a database. 137 * The ID property (as of now, though this may change in the future) is 138 * used by our persistence mechanism to identify objects. If you are 139 * persisting this catalog for the first time, you may need to call 140 * this method before performing a save. This is especially true if 141 * you have created this source from XML, as the XML unmarshalling 142 * brings along the ID property.</p> 143 */ 144 public void clearId() 145 { 146 this.id = getIdOfUnidentified(); 147 148 for (G group : this.groups) 149 group.setId(null); 150 } 151 152 /** 153 * Returns this catalog's ID. 154 * @return this catalog's ID. 155 */ 156 @XmlAttribute 157 public Long getId() 158 { 159 return id; 160 } 161 162 /** 163 * Sets the name of this catalog. 164 * <p> 165 * If {@code newName} is <i>null</i> or the empty string 166 * (<tt>""</tt>), the request to change the name will be 167 * denied and the current name will remain in place.</p> 168 * 169 * @param newName the new name for this catalog. 170 */ 171 public void setName(String newName) 172 { 173 if (newName != name && newName.length() > 0) 174 name = newName; 175 } 176 177 /** 178 * Returns the name of this catalog. 179 * @return the name of this catalog. 180 */ 181 public String getName() 182 { 183 return name; 184 } 185 186 //============================================================================ 187 // OTHER PROPERTIES 188 //============================================================================ 189 190 /** 191 * Returns a list of notes about this catalog. 192 * Each note is free-form text with no particular structure. 193 * <p> 194 * This method returns the list actually held by this {@code Catalog}, so 195 * any list manipulations may be performed by first fetching the list and 196 * then operating on it.</p> 197 * 198 * @return a list of notes about this catalog. 199 */ 200 public List<String> getNotes() 201 { 202 return mainGroup.getNotes(); 203 } 204 205 /** This is here for mechanisms that need setX/getX pairs, such as JAXB. */ 206 @SuppressWarnings("unused") 207 private void setNotes(List<String> replacementList) 208 { 209 mainGroup.setNotes(replacementList); 210 } 211 212 //============================================================================ 213 // ADDING ITEMS & GROUPS 214 //============================================================================ 215 //---------------------------------------------------------------------------- 216 // Items 217 //---------------------------------------------------------------------------- 218 219 /** 220 * Adds a new item to this catalog. The new item is added only if this 221 * catalog holds no item to which it is equal. 222 * 223 * @param newItem a new item for this catalog. 224 * 225 * @return the item that was added to, or was already a part of, 226 * this catalog. This will be either {@code newItem} if this 227 * catalog contains no item equal to {@code newItem}, or the 228 * first item in this catalog that is equal to {@code newItem}. 229 */ 230 public I addItem(I newItem) 231 { 232 return mainGroup.add(newItem); 233 } 234 235 /** 236 * Adds a new item to this catalog at the specified position. 237 * The new item is added only if this 238 * catalog holds no item to which it is equal. 239 * 240 * @param index the position at which to insert the new item. 241 * 242 * @param newItem a prospective new item in this catalog. 243 * 244 * @return the item that was added to, or was already a part of, 245 * this catalog. This will be either {@code newItem} if this 246 * catalog contains no item equal to {@code newItem}, or the 247 * first item in this catalog that is equal to {@code newItem}. 248 */ 249 public I addItem(int index, I newItem) 250 { 251 return mainGroup.add(index, newItem); 252 } 253 254 /** Adds a new item to this catalog. */ 255 I addItem(I newItem, G caller) 256 { 257 return (caller == mainGroup) ? newItem : addItem(newItem); 258 } 259 260 /** 261 * Adds {@code newItems} to this catalog. 262 * Only non-null elements of {@code newItems} that are not 263 * already part of this catalog are classified as new items. 264 * 265 * @param newItems new items in this catalog. 266 * 267 * @return the collection of items that were added to this catalog. 268 */ 269 public Collection<I> addItems(Collection<? extends I> newItems) 270 { 271 Collection<I> addedItems = new ArrayList<I>(); 272 273 if (newItems != null) 274 { 275 for (I newItem : newItems) 276 { 277 if ((newItem != null) && !contains(newItem)) 278 { 279 addedItems.add(this.addItem(newItem)); 280 } 281 } 282 } 283 284 return addedItems; 285 } 286 287 /** 288 * Adds {@code newItems} to this catalog at the specified position. 289 * Only non-null elements of {@code newItems} that are not 290 * already part of this catalog are classified as new items. 291 * 292 * @param index the position at which to insert the first new item. 293 * Subsequent new items are added at successive positions. 294 * 295 * @param newItems new items in this catalog. 296 * 297 * @return the collection of items that were added to this catalog. 298 */ 299 public Collection<I> addItems(int index, Collection<? extends I> newItems) 300 { 301 Collection<I> addedItems = new ArrayList<I>(); 302 303 if (newItems != null) 304 { 305 for (I newItem : newItems) 306 { 307 if ((newItem != null) && !contains(newItem)) 308 { 309 addedItems.add(this.addItem(index++, newItem)); 310 } 311 } 312 } 313 314 return addedItems; 315 } 316 317 /** 318 * Adds {@code newItem} even if this catalog already has an item to which 319 * it is equal. Contrast this with {@link #addItem(CatalogItem)}. 320 * <p> 321 * The only times {@code newItem} is <i>not</i> added to this catalog are 322 * when it is <i>null</i> and when a reference to it is already contained 323 * in this catalog.</p> 324 * 325 * @param newItem a potential new item for this catalog. 326 * 327 * @return {@code newItem}. 328 */ 329 public I addItemEvenIfEqualToCurrentItem(I newItem) 330 { 331 return mainGroup.addEvenIfEqualToCurrentMember(newItem); 332 } 333 334 /** 335 * Adds {@code newItem} even if this catalog already has an item to which 336 * it is equal. Contrast this with {@link #addItem(int, CatalogItem)}. 337 * <p> 338 * The only times {@code newItem} is <i>not</i> added to this catalog are 339 * when it is <i>null</i> and when a reference to it is already contained 340 * in this catalog.</p> 341 * 342 * @param index the position at which to add {@code newItem}. 343 * 344 * @param newItem a potential new item for this catalog. 345 * 346 * @return {@code newItem}. 347 */ 348 public I addItemEvenIfEqualToCurrentItem(int index, I newItem) 349 { 350 return mainGroup.addEvenIfEqualToCurrentMember(index, newItem); 351 } 352 353 /** Forces the addition of a new item. */ 354 I addItemEvenIfEqualToCurrentItem(I newItem, G caller) 355 { 356 return (caller == mainGroup) ? newItem 357 : addItemEvenIfEqualToCurrentItem(newItem); 358 } 359 360 //---------------------------------------------------------------------------- 361 // Groups 362 //---------------------------------------------------------------------------- 363 364 /** 365 * Adds a new group to this catalog. 366 * All of the new group's members are also added to this catalog. 367 * <p> 368 * This catalog is now enforcing uniqueness for the names of its groups. 369 * If {@code newGroup} has the same name as a group that is already in 370 * this catalog, a <b>side effect</b> of this {@code add} method is 371 * that {@code newGroup}'s name will be changed to 372 * <tt>newGroup.getName() + " (Copy <i>#</i>)"</tt>, where "#" is the 373 * n<sup>th</sup> duplication of this name.</p> 374 * 375 * @param newGroup a new group of items for this catalog. 376 * 377 * @return <i>true</i> if this catalog changed as a result of the call. 378 * Reasons for a return value of <i>false</i>: 379 * <ol> 380 * <li>{@code newGroup} is <i>null</i></li> 381 * <li>{@code newGroup} is already a part of this catalog</li> 382 * </ol> 383 */ 384 public boolean addGroup(G newGroup) 385 { 386 return addGroup(groups.size(), newGroup); 387 } 388 389 /** 390 * Adds a new group to this catalog at the specified position. 391 * All of the new group's members are also added to this catalog. 392 * <p> 393 * This catalog is now enforcing uniqueness for the names of its groups. 394 * If {@code newGroup} has the same name as a group that is already in 395 * this catalog, a <b>side effect</b> of this {@code add} method is 396 * that {@code newGroup}'s name will be changed to 397 * <tt>newGroup.getName() + " (Copy <i>#</i>)"</tt>, where "#" is the 398 * n<sup>th</sup> duplication of this name.</p> 399 * 400 * @param index the position at which to insert the new group. 401 * 402 * @param newGroup a new group of items for this catalog. 403 * 404 * @return <i>true</i> if this catalog changed as a result of the call. 405 * Reasons for a return value of <i>false</i>: 406 * <ol> 407 * <li>{@code newGroup} is <i>null</i></li> 408 * <li>{@code newGroup} is already a part of this catalog</li> 409 * </ol> 410 */ 411 @SuppressWarnings("unchecked") 412 public boolean addGroup(int index, G newGroup) 413 { 414 //Quick exit if newGroup is null or is already in this catalog 415 if ((newGroup == null) || (newGroup.getCatalog() == this)) 416 return false; 417 418 //Remove newGroup from its former catalog 419 C formerCatalog = newGroup.getCatalog(); 420 if (formerCatalog != null) 421 formerCatalog.groups.remove(newGroup); 422 423 //Tell newGroup it now belongs to this catalog 424 newGroup.simplySetCatalog((C)this); 425 426 //Add the newGroup's members to this catalog. If this catalog already 427 //contains EQUAL members, replace the corresponding member of the group 428 //with a reference to the equal member in this catalog. 429 if (newGroup != mainGroup) //Redundant? 430 { 431 //addAll(newGroup.getMembers()); 432 List<I> newMemberList = newGroup.getInternalMemberList(); 433 List<I> catalogItemList = mainGroup.getInternalMemberList(); 434 435 int memberCount = newMemberList.size(); 436 for (int i=0; i < memberCount; i++) 437 { 438 I newItem = newMemberList.get(i); 439 440 int itemIndex = catalogItemList.indexOf(newItem); 441 442 //If catalog already contains item that is EQUAL, but not identical, 443 //to newItem, update the group with the item from the catalog. 444 if (itemIndex >= 0) 445 { 446 I catalogItem = catalogItemList.get(itemIndex); 447 if (newItem != catalogItem) //but newItem.equals(catalogItem) 448 { 449 newMemberList.remove(i); 450 newMemberList.add(i, catalogItem); 451 } 452 } 453 //Otherwise, add the new item to this catalog. 454 else 455 { 456 mainGroup.add(newItem); 457 } 458 } 459 } 460 461 //The new group's name might already be in use by this catalog and 462 //could even be the reserved name. Change it, if necessary. 463 newGroup.adjustNameForCatalog((C)this); 464 465 //Add newGroup to our collection 466 groups.add(index, newGroup); 467 tellListenersAboutNewGroup(newGroup, index); 468 469 return true; 470 } 471 472 /** 473 * Adds {@code newGroups} to this catalog. 474 * <p> 475 * This is a convenience method that is the equivalent of iterating over 476 * {@code newGroups} and calling {@link #addGroup(CatalogItemGroup)}.</p> 477 * 478 * @param newGroups new groups in this catalog. 479 * 480 * @return <i>true</i> if one or more groups were added to this catalog. 481 */ 482 public boolean addGroups(Collection<? extends G> newGroups) 483 { 484 boolean added = false; 485 486 if (newGroups != null) 487 { 488 for (G newGroup : newGroups) 489 added |= addGroup(newGroup); 490 } 491 492 return added; 493 } 494 495 /** 496 * Adds {@code newGroups} to this catalog at the specified position. 497 * <p> 498 * This is a convenience method that is the equivalent of iterating over 499 * {@code newGroups} and calling {@link #addGroup(int, CatalogItemGroup)}.</p> 500 * 501 * @param index the position at which to insert the first new group. 502 * Subsequent new groups are added at successive positions. 503 * 504 * @param newGroups new groups in this catalog. 505 * 506 * @return <i>true</i> if one or more groups were added to this catalog. 507 */ 508 public boolean addGroups(int index, Collection<? extends G> newGroups) 509 { 510 boolean added = false; 511 512 if (newGroups != null) 513 { 514 for (G newGroup : newGroups) 515 added |= addGroup(index++, newGroup); 516 } 517 518 return added; 519 } 520 521 /** 522 * Adds newGroup directly to this catalog's list of groups 523 * without also adding its sources and lookup tables, and 524 * without notifying listeners. 525 * This method is used only by the CatalogItemGroup class and 526 * should be used only if you absolutely know what you are doing. 527 */ 528 void simplyAddGroup(G newGroup) 529 { 530 if ((newGroup != null) && !groups.contains(newGroup)) 531 groups.add(newGroup); 532 } 533 534 @SuppressWarnings("unchecked") 535 protected void tellListenersAboutNewGroup(G newGroup, int index) 536 { 537 if (!suppressNotification) 538 for (CatalogListener<I,G,C> listener : listeners) 539 listener.groupAdded((C)this, newGroup, index); 540 } 541 542 //============================================================================ 543 // REMOVING ITEMS & GROUPS 544 //============================================================================ 545 546 //Be aware: while the add-item and move-item methods simply delegate to the 547 //mainGroup, for removal of items, the mainGroup calls back into this 548 //catalog. This is done so that any items removed from mainGroup may 549 //also be removed from all other groups in this catalog. One consequence 550 //of this is that the group's remove methods are not called, and this 551 //bypasses the group listeners. To remedy this, we notify the listeners 552 //here. (Note that this catalog is likely the only listener to the 553 //main group, but our code here should not rely on that.) 554 555 /** Removes an item from this catalog, if the caller is the main group. */ 556 I removeItem(I item, G caller) 557 { 558 return (caller == mainGroup) ? removeItem(item) : null; 559 } 560 561 /** 562 * Removes {@code item} from this catalog, if present. 563 * The {@code item} 564 * is also removed from every group of this catalog in which it is present. 565 * 566 * @param item the item that is to be removed from this catalog. 567 * 568 * @return {@code item} if it had been an item in this catalog, 569 * <i>null</i> if it had not. 570 */ 571 public I removeItem(I item) 572 { 573 List<I> items = mainGroup.getInternalMemberList(); 574 575 I formerItem = items.contains(item) ? item : null; 576 577 if (formerItem != null) 578 { 579 //Remove the item from all groups 580 for (G group : groups) 581 group.remove(formerItem); 582 583 int index = items.indexOf(formerItem); 584 items.remove(formerItem); 585 memberRemoved(mainGroup, formerItem, index); 586 } 587 588 return formerItem; 589 } 590 591 /** 592 * Removes the item found at {@code index}. 593 * @param index the index of the item to be removed. 594 * @return the removed item, or <i>null</i> if no item was removed. 595 */ 596 public I removeItem(int index) 597 { 598 return removeItem(mainGroup.get(index)); 599 } 600 601 /** 602 * Returns <i>true</i> if this catalog will allow removal of {@code group}. 603 * @param group the group to be removed. 604 * @return <i>true</i> if this catalog will allow removal of {@code group}. 605 */ 606 boolean allowsRemovalOf(G group) 607 { 608 return group != mainGroup; 609 } 610 611 /** 612 * Removes {@code group} from this catalog. 613 * Note that the items held by {@code group} are <i>not</i> removed 614 * from this catalog. 615 * 616 * @param group the group that is to be removed from this catalog. 617 * 618 * @return {@code group} if it had belonged to this catalog and was 619 * able to be removed, <i>null</i> otherwise. 620 */ 621 public G removeGroup(G group) 622 { 623 //Quick exit if group is null, does not belong to this catalog, 624 //or is not permitted to leave 625 if ((group == null) || (group.getCatalog() != this) || 626 !this.allowsRemovalOf(group)) 627 return null; 628 629 //Tell group that it belongs to no catalog 630 group.simplySetCatalog(null); 631 632 //Remove the group from our collection 633 int index = groups.indexOf(group); 634 boolean removed = groups.remove(group); 635 if (removed) 636 tellListenersAboutFormerGroup(group, index); 637 638 return group; 639 } 640 641 /** 642 * Removes the group at {@code index} from this catalog. 643 * Note that the items held by {@code group} are <i>not</i> removed 644 * from this catalog. 645 * 646 * @param index the index of the group to be removed. 647 * 648 * @return the group found at {@code index} if it was 649 * able to be removed, <i>null</i> otherwise. 650 */ 651 public G removeGroup(int index) 652 { 653 return removeGroup(groups.get(index)); 654 } 655 656 /** Removes items from this catalog, if the caller is the main group. */ 657 boolean removeItems(Collection<? extends I> items, G caller) 658 { 659 return (caller == mainGroup) ? removeItems(items) : false; 660 } 661 662 /** 663 * Removes all elements of {@code unwantedItems} that are contained in this 664 * catalog. 665 * @param unwantedItems the items that are to be removed from this catalog. 666 * @return <i>true</i> if this catalog changed as a result of the call. 667 */ 668 boolean removeItems(Collection<? extends I> unwantedItems) 669 { 670 boolean result = false; 671 672 if (unwantedItems != null) 673 { 674 for (G group : groups) 675 group.removeAll(unwantedItems); 676 677 List<I> myItems = mainGroup.getInternalMemberList(); 678 for (I unwantedItem : unwantedItems) 679 { 680 int index = myItems.indexOf(unwantedItem); 681 if (index >= 0) 682 { 683 result |= myItems.remove(unwantedItem); 684 memberRemoved(mainGroup, unwantedItem, index); 685 } 686 } 687 } 688 689 return result; 690 } 691 692 /** 693 * Removes all groups from this catalog. 694 * No catalog items are removed as a result of the call. 695 * 696 * @see #removeAllItems() 697 * @see #clear() 698 */ 699 @SuppressWarnings("unchecked") 700 public void removeAllGroups() 701 { 702 for (G group : groups) 703 group.simplySetCatalog(null); 704 705 List<G> clonedGroupList = null; 706 synchronized(this) { 707 clonedGroupList = (List<G>)((ArrayList<G>)groups).clone(); 708 } 709 710 //Remove all client-made groups; we NEVER remove the main group 711 groups.clear(); 712 713 //Notify listeners. Traversing the group list backwards ensures 714 //that the index sent to listener matches current state of list. 715 for (int g=clonedGroupList.size()-1; g >= 0; g--) 716 tellListenersAboutFormerGroup(clonedGroupList.get(g), g); 717 } 718 719 /** 720 * Removes all items from this catalog and its groups, 721 * but does not remove the groups themselves. 722 * @see #removeAllGroups() 723 * @see #clear() 724 */ 725 @SuppressWarnings("unchecked") 726 public void removeAllItems() 727 { 728 for (G group : groups) 729 group.clear(); 730 731 List<I> myItems = mainGroup.getInternalMemberList(); 732 733 List<I> clonedItemList = null; 734 synchronized(this) { 735 clonedItemList = (List<I>)((ArrayList<I>)myItems).clone(); 736 } 737 738 myItems.clear(); 739 740 //Notify listeners. Traversing the member list backwards ensures 741 //that the index sent to listener matches current state of list. 742 for (int i=clonedItemList.size()-1; i >= 0; i--) 743 memberRemoved(mainGroup, clonedItemList.get(i), i); 744 } 745 746 /** 747 * Removes all groups <i>and</i> all items from this catalog. 748 * @see #removeAllItems() 749 * @see #removeAllGroups() 750 */ 751 public void clear() 752 { 753 removeAllGroups(); 754 removeAllItems(); 755 } 756 757 @SuppressWarnings("unchecked") 758 protected void tellListenersAboutFormerGroup(G formerGroup, int index) 759 { 760 if (!suppressNotification) 761 for (CatalogListener<I,G,C> listener : listeners) 762 listener.groupRemoved((C)this, formerGroup, index); 763 } 764 765 //============================================================================ 766 // MOVING ITEMS & GROUPS 767 //============================================================================ 768 //---------------------------------------------------------------------------- 769 // Items 770 //---------------------------------------------------------------------------- 771 772 /** 773 * Moves {@code item} to an index one higher than its current index. 774 * The item will not be moved if it is not in this catalog, or if 775 * it is already in the highest position. 776 * <p> 777 * If {@code item} is in this catalog in multiple positions, the instance 778 * with the <i>highest</i> index will be moved.</p> 779 * 780 * @param item the item to be moved. 781 * 782 * @return if no movement occurred, <i>null</i> is returned. Otherwise 783 * the item that was moved is returned. This will be either 784 * {@code item}, or an equal item that was already part of 785 * this catalog. 786 */ 787 public I incrementIndexOfItem(I item) 788 { 789 return mainGroup.incrementIndexOf(item); 790 } 791 792 /** 793 * Moves {@code item} to an index one lower than its current index. 794 * The item will not be moved if it is not in this catalog, or if 795 * it is already in the lowest position. 796 * <p> 797 * If {@code item} is in this catalog in multiple positions, the instance 798 * with the <i>lowest</i> index will be moved.</p> 799 * 800 * @param item the item to be moved. 801 * 802 * @return if no movement occurred, <i>null</i> is returned. Otherwise 803 * the item that was moved is returned. This will be either 804 * {@code item}, or an equal item that was already part of 805 * this catalog. 806 */ 807 public I decrementIndexOfItem(I item) 808 { 809 return mainGroup.decrementIndexOf(item); 810 } 811 812 /** 813 * Moves the item at {@code index} to an index of {@code index}-plus-one. 814 * No move will occur if {@code index} is out of bounds, or if it is already 815 * the highest index of this catalog. 816 * 817 * @param index the index of the item to be moved. 818 * 819 * @return the item that was moved, or <i>null</i> if no item was moved. 820 */ 821 public I incrementIndexOfItemAt(int index) 822 { 823 return mainGroup.incrementIndexOfMemberAt(index); 824 } 825 826 /** 827 * Moves the item at {@code index} to an index of {@code index}-minus-one. 828 * No move will occur if {@code index} is out of bounds, or if it is already 829 * the lowest index of this catalog. 830 * 831 * @param index the index of the item to be moved. 832 * 833 * @return the item that was moved, or <i>null</i> if no item was moved. 834 */ 835 public I decrementIndexOfItemAt(int index) 836 { 837 return mainGroup.decrementIndexOfMemberAt(index); 838 } 839 840 /** 841 * Swaps the items at the given positions. 842 * <p> 843 * If the indices are equal, no action is taken. 844 * If one or both of the indices are not in the range 845 * [0,size()), the underlying {@link List} will throw 846 * an exception.</p> 847 * 848 * @param index1 the position of the one of the items. 849 * @param index2 the position of another of the items. 850 * 851 * @return a list with the item originally at the lower index followed by 852 * the item originally at the higher index. If no swap was made 853 * the returned list will be empty. 854 */ 855 public List<I> swapItems(int index1, int index2) 856 { 857 return mainGroup.swap(index1, index2); 858 } 859 860 /** 861 * Moves an item {@code fromIndex} {@code toIndex}. 862 * 863 * @param fromIndex the current index of an item. 864 * @param toIndex the new index of that same item. 865 * @return the item that moved. 866 */ 867 public I moveItem(int fromIndex, int toIndex) 868 { 869 return mainGroup.move(fromIndex, toIndex); 870 } 871 872 /** 873 * Moves {@code item} {@code toIndex}. 874 * 875 * @param item the item to be moved. 876 * @param toIndex the new position for {@code item}. 877 * @return the item that moved, or <i>null</i> if no movement occurred. 878 * Note that the item that moved might be {@code item} or, 879 * alternatively, an item <em>equal to</em> {@code item}, 880 * if this catalog has such an item. 881 */ 882 public I moveItem(I item, int toIndex) 883 { 884 return mainGroup.move(item, toIndex); 885 } 886 887 /** 888 * Uses {@code comparator} to sort the items of this catalog. 889 * @param comparator used to order this catalog's items. 890 */ 891 public void sort(Comparator<? super I> comparator) 892 { 893 mainGroup.sort(comparator); 894 } 895 896 //---------------------------------------------------------------------------- 897 // Groups 898 //---------------------------------------------------------------------------- 899 900 /** 901 * Moves {@code group} to an index one higher than its current index. 902 * The group will not be moved if it is not in this catalog, or if 903 * it is already in the highest position. 904 * <p> 905 * If {@code group} is in this catalog in multiple positions, the instance 906 * with the <i>highest</i> index will be moved. (It is not generally 907 * possible for a group to occupy multiple positions in a catalog.) Note 908 * that the special group that holds all items of this catalog may 909 * not be moved.</p> 910 * 911 * @param group the group to be moved. 912 * 913 * @return if no movement occurred, <i>null</i> is returned. Otherwise 914 * the group that was moved is returned. This will be either 915 * {@code group}, or an equal group that was already part of 916 * this catalog. 917 */ 918 public G incrementIndexOfGroup(G group) 919 { 920 G movedGroup = null; 921 922 int currentIndex = groups.indexOf(group); 923 int highestIndex = groups.size() - 1; 924 925 if ((0 <= currentIndex) && (currentIndex < highestIndex)) 926 { 927 suppressNotification = true; 928 929 movedGroup = removeGroup(currentIndex); 930 groups.add(currentIndex + 1, movedGroup); 931 932 suppressNotification = false; 933 tellListenersAboutMovedGroup(movedGroup, currentIndex, currentIndex+1); 934 } 935 936 return movedGroup; 937 } 938 939 /** 940 * Moves {@code group} to an index one lower than its current index. 941 * The group will not be moved if it is not in this catalog, or if 942 * it is already in the lowest position. 943 * <p> 944 * If {@code group} is in this catalog in multiple positions, the instance 945 * with the <i>lowest</i> index will be moved. (It is not generally 946 * possible for a group to occupy multiple positions in a catalog.) Note 947 * that the special group that holds all items of this catalog may 948 * not be moved.</p> 949 * 950 * @param group the group to be moved. 951 * 952 * @return if no movement occurred, <i>null</i> is returned. Otherwise 953 * the group that was moved is returned. This will be either 954 * {@code group}, or an equal group that was already part of 955 * this catalog. 956 */ 957 public G decrementIndexOfGroup(G group) 958 { 959 G movedGroup = null; 960 961 int currentIndex = groups.indexOf(group); 962 963 if (0 < currentIndex) 964 { 965 suppressNotification = true; 966 967 movedGroup = removeGroup(currentIndex); 968 groups.add(currentIndex - 1, movedGroup); 969 970 suppressNotification = false; 971 tellListenersAboutMovedGroup(movedGroup, currentIndex, currentIndex-1); 972 } 973 974 return movedGroup; 975 } 976 977 /** 978 * Moves the group at {@code index} to an index of {@code index}-plus-one. 979 * No move will occur if {@code index} is out of bounds, or if it is already 980 * the highest index of this catalog. 981 * 982 * @param index the index of the group to be moved. 983 * 984 * @return the group that was moved, or <i>null</i> if no group was moved. 985 */ 986 public G incrementIndexOfGroupAt(int index) 987 { 988 G movedGroup = null; 989 990 int highestIndex = groups.size() - 1; 991 992 if ((0 <= index) && (index < highestIndex)) 993 { 994 movedGroup = groups.remove(index); 995 groups.add(index + 1, movedGroup); 996 997 suppressNotification = false; 998 tellListenersAboutMovedGroup(movedGroup, index, index+1); 999 } 1000 1001 return movedGroup; 1002 } 1003 1004 /** 1005 * Moves the group at {@code index} to an index of {@code index}-minus-one. 1006 * No move will occur if {@code index} is out of bounds, or if it is already 1007 * the lowest index of this catalog. 1008 * 1009 * @param index the index of the group to be moved. 1010 * 1011 * @return the group that was moved, or <i>null</i> if no group was moved. 1012 */ 1013 public G decrementIndexOfGroupAt(int index) 1014 { 1015 G movedGroup = null; 1016 1017 int highestIndex = groups.size() - 1; 1018 1019 if ((0 < index) && (index <= highestIndex)) 1020 { 1021 movedGroup = groups.remove(index); 1022 groups.add(index - 1, movedGroup); 1023 1024 suppressNotification = false; 1025 tellListenersAboutMovedGroup(movedGroup, index, index+1); 1026 } 1027 1028 return movedGroup; 1029 } 1030 1031 /** 1032 * Swaps the groups at the given positions. 1033 * <p> 1034 * If the indices are equal, no action is taken. 1035 * If one or both of the indices are not in the range 1036 * [0,size()), the underlying {@link List} will throw 1037 * an exception.</p> 1038 * 1039 * @param index1 the position of the one of the groups. 1040 * @param index2 the position of another of the groups. 1041 * 1042 * @return a list with the group originally at the lower index followed by 1043 * the group originally at the higher index. If no swap was made 1044 * the returned list will be empty. 1045 */ 1046 public List<G> swapGroups(int index1, int index2) 1047 { 1048 List<G> swappedGroups = new ArrayList<G>(); 1049 1050 //No swap if indices are same 1051 if (index1 != index2) 1052 { 1053 int low = Math.min(index1, index2); 1054 int high = Math.max(index1, index2); 1055 1056 G lowGroup = groups.get(low); 1057 G highGroup = groups.get(high); 1058 1059 groups.remove(high); 1060 groups.add(high, lowGroup); 1061 groups.remove(low); 1062 groups.add(low, highGroup); 1063 1064 tellListenersAboutMovedGroup(highGroup, high, low); 1065 1066 //The indices are incremented here because listeners will receive this 1067 //message after the one above. So to them, it looks as if lowGroup is 1068 //currently at position low+1. 1069 tellListenersAboutMovedGroup(lowGroup, low+1, high+1); 1070 1071 swappedGroups.add(lowGroup); 1072 swappedGroups.add(highGroup); 1073 } 1074 1075 return swappedGroups; 1076 } 1077 1078 /** 1079 * Moves a group {@code fromIndex} {@code toIndex}. 1080 * 1081 * @param fromIndex the current index of a group. 1082 * @param toIndex the new index of that same group. 1083 * @return the group that moved, even if it moved back to the same position. 1084 */ 1085 public G moveGroup(int fromIndex, int toIndex) 1086 { 1087 G movedGroup = null; 1088 1089 movedGroup = groups.remove(fromIndex); 1090 1091 if (movedGroup != null) 1092 { 1093 int indexMax = groups.size(); 1094 if (toIndex > indexMax) 1095 toIndex = indexMax; 1096 1097 groups.add(toIndex, movedGroup); 1098 1099 suppressNotification = false; 1100 tellListenersAboutMovedGroup(movedGroup, fromIndex, toIndex); 1101 } 1102 1103 return movedGroup; 1104 } 1105 1106 /** 1107 * Moves {@code group} {@code toIndex}. 1108 * 1109 * @param group the group to be moved. 1110 * @param toIndex the new position for {@code group}. 1111 * @return the group that moved, or <i>null</i> if no movement occurred. 1112 */ 1113 public G moveGroup(G group, int toIndex) 1114 { 1115 int fromIndex = groups.indexOf(group); 1116 1117 return (fromIndex < 0) ? null : moveGroup(fromIndex, toIndex); 1118 } 1119 1120 /** 1121 * Uses {@code comparator} to sort the members of this group. 1122 * @param comparator used to order this group's members 1123 */ 1124 @SuppressWarnings("unchecked") 1125 public void sortGroups(Comparator<? super G> comparator) 1126 { 1127 Collections.sort(groups, comparator); 1128 1129 if (!suppressNotification) 1130 for (CatalogListener<I,G,C> listener : listeners) 1131 listener.groupsSorted((C)this); 1132 } 1133 1134 @SuppressWarnings("unchecked") 1135 protected void tellListenersAboutMovedGroup(G group, 1136 int fromIndex, int toIndex) 1137 { 1138 if (!suppressNotification) 1139 for (CatalogListener<I,G,C> listener : listeners) 1140 listener.groupMoved((C)this, group, fromIndex, toIndex); 1141 } 1142 1143 //============================================================================ 1144 // FETCHING ITEMS & GROUPS 1145 //============================================================================ 1146 //---------------------------------------------------------------------------- 1147 // Items 1148 //---------------------------------------------------------------------------- 1149 1150 /** 1151 * Returns the number of items in this catalog. 1152 * (A group is <i>not</i> considered to be an item.) 1153 * 1154 * @return the number of items in this catalog. 1155 */ 1156 public int size() 1157 { 1158 return mainGroup.size(); 1159 } 1160 1161 /** 1162 * Returns the item at the given position. 1163 * 1164 * @param index the position of the item to return. 1165 * 1166 * @return the item at {@code index}. 1167 */ 1168 public I getItem(int index) 1169 { 1170 return mainGroup.get(index); 1171 } 1172 1173 /** 1174 * Returns a list of this catalog's items. 1175 * (A group is <i>not</i> considered to be an item.) 1176 * <p> 1177 * Note that the returned list is <i>not</i> held internally by this catalog. 1178 * This means that any changes made to the returned list will <i>not</i> 1179 * be reflected in this object. The elements of the list, though, are the 1180 * actual items of this catalog, so changes made to them will be reflected 1181 * in this catalog.</p> 1182 * 1183 * @return a list of this catalog's items. The returned value is guaranteed 1184 * to be non-null, but it may be an empty list. 1185 */ 1186 public List<I> getItems() 1187 { 1188 return mainGroup.getAll(); 1189 } 1190 1191 /** 1192 * Returns the internal item list. 1193 * This method is meant solely for use by frameworks (such 1194 * as JAXB or Hibernate) that rely on getX/setX pairs for 1195 * persisted properties. It is {@code protected}, as opposed to 1196 * {@code private}, so that subclasses can create and annotate 1197 * methods that call this one. 1198 */ 1199 protected List<I> getInternalItemList() 1200 { 1201 return mainGroup.getInternalMemberList(); 1202 } 1203 1204 /** 1205 * Replaces the internal item list with {@code replacementList}. 1206 * This method is meant solely for use by frameworks (such 1207 * as JAXB or Hibernate) that rely on getX/setX pairs for 1208 * persisted properties. It is {@code protected}, as opposed to 1209 * {@code private}, so that subclasses can create and annotate 1210 * methods that call this one. 1211 */ 1212 protected void setInternalItemList(List<I> replacementList) 1213 { 1214 mainGroup.setInternalMemberList(replacementList); 1215 } 1216 1217 /** 1218 * Returns <i>true</i> if this catalog holds {@code item}. 1219 * @param item a possible item in this catalog. 1220 * @return <i>true</i> if this catalog holds {@code item}. 1221 */ 1222 public boolean contains(I item) 1223 { 1224 return mainGroup.contains(item); 1225 } 1226 1227 /** 1228 * Returns the index of the first occurence of {@code item} in this catalog. 1229 * See {@link java.util.List#indexOf(Object)} for more details. 1230 * @param item a potential item of this catalog 1231 * @return the index of the first occurence of {@code item} in this catalog, 1232 * or -1 if this catalog does not contain {@code item}. 1233 */ 1234 public int indexOf(I item) 1235 { 1236 return mainGroup.getInternalMemberList().indexOf(item); 1237 } 1238 1239 /** 1240 * Returns the index of the lasst occurence of {@code item} in this catalog. 1241 * See {@link java.util.List#lastIndexOf(Object)} for more details. 1242 * @param item a potential item of this catalog 1243 * @return the index of the last occurence of {@code item} in this catalog, 1244 * or -1 if this catalog does not contain {@code item}. 1245 */ 1246 public int lastIndexOf(I item) 1247 { 1248 return mainGroup.getInternalMemberList().lastIndexOf(item); 1249 } 1250 1251 //---------------------------------------------------------------------------- 1252 // Groups 1253 //---------------------------------------------------------------------------- 1254 1255 /** 1256 * Returns the group at the given position. 1257 * 1258 * @param index the position of the group to return. 1259 * 1260 * @return the group at {@code index}. 1261 */ 1262 public G getGroup(int index) 1263 { 1264 return groups.get(index); 1265 } 1266 1267 /** 1268 * Returns a group with the given name, or <i>null</i> if this catalog 1269 * has no such group. 1270 * 1271 * @param groupName 1272 * the name of group potentially in this catalog. 1273 * 1274 * @return 1275 * a group with the given name, or <i>null</i> if this catalog 1276 * has no such group. 1277 */ 1278 public G getGroup(String groupName) 1279 { 1280 for (G group : groups) 1281 if (group.getName().equals(groupName)) 1282 return group; 1283 1284 return null; 1285 } 1286 1287 /** 1288 * Returns a list of this catalog's groups. 1289 * <p> 1290 * Note that the returned list is <i>not</i> held internally by this catalog. 1291 * This means that any changes made to the returned list will <i>not</i> 1292 * be reflected in this object. The elements of the list, though, are the 1293 * actual groups of this catalog, so changes made to them will be reflected 1294 * in this catalog.</p> 1295 * 1296 * @return a list of this catalog's groups. The returned value is guaranteed 1297 * to be non-null, but it may be an empty list. 1298 */ 1299 public List<G> getGroups() 1300 { 1301 return new ArrayList<G>(groups); 1302 } 1303 1304 /** 1305 * Returns a list of groups that contain {@code item}. 1306 * @param item an item of this catalog that may be in one or more groups. 1307 * @return a list of groups that contain {@code item}. 1308 */ 1309 public List<G> getGroupsThatContain(I item) 1310 { 1311 List<G> result = new ArrayList<G>(); 1312 1313 for (G group : groups) 1314 if (group.getInternalMemberList().contains(item)) 1315 result.add(group); 1316 1317 return result; 1318 } 1319 1320 /** 1321 * Returns <i>true</i> if this catalog contains a group with the 1322 * given {@code name}. 1323 * @param name potential name of a group contained by this catalog. 1324 * @return <i>true</i> if this catalog has a group named {@code name}. 1325 */ 1326 public boolean containsGroupNamed(String name) 1327 { 1328 //See if name is the special main-group name 1329 boolean contains = getReservedGroupName().equals(name); 1330 1331 //If not, see if any of the regular groups have that name 1332 if (!contains) 1333 { 1334 for (G group : groups) 1335 { 1336 if (group.getName().equals(name)) 1337 { 1338 contains = true; 1339 break; 1340 } 1341 } 1342 } 1343 1344 return contains; 1345 } 1346 1347 /** 1348 * Returns the internal group list. 1349 * This method is meant solely for use by frameworks (such 1350 * as JAXB or Hibernate) that rely on getX/setX pairs for 1351 * persisted properties. It is {@code protected}, as opposed to 1352 * {@code private}, so that subclasses can create and annotate 1353 * methods that call this one. 1354 */ 1355 protected List<G> getInternalGroupList() 1356 { 1357 return groups; 1358 } 1359 1360 /** 1361 * Replaces the internal group list with {@code replacementList}. 1362 * This method is meant solely for use by frameworks (such 1363 * as JAXB or Hibernate) that rely on getX/setX pairs for 1364 * persisted properties. It is {@code protected}, as opposed to 1365 * {@code private}, so that subclasses can create and annotate 1366 * methods that call this one. 1367 */ 1368 @SuppressWarnings("unchecked") 1369 protected void setInternalGroupList(List<G> replacementList) 1370 { 1371 groups = (replacementList == null) ? new ArrayList<G>() 1372 : replacementList; 1373 for (G group : groups) 1374 group.simplySetCatalog((C)this); 1375 } 1376 1377 /** 1378 * Returns the name that this catalog uses for its special group. 1379 * The special group is the internal group that holds all the items 1380 * of this catalog. No other group may use this name. 1381 * 1382 * @return the name that this catalog uses for its special group. 1383 */ 1384 public String getReservedGroupName() 1385 { 1386 return reservedGroupName; 1387 } 1388 1389 /** 1390 * Sets the name that this catalog will treat as a reserved name. 1391 * The reserved name is used for the special group that holds all 1392 * the items in this catalog. 1393 * 1394 * @param reservedName the name this catalog will use for its special 1395 * group. If this value is <i>null</i>, this catalog 1396 * will use a default name in its place. 1397 */ 1398 protected void setReservedGroupName(String reservedName) 1399 { 1400 reservedGroupName = (reservedName == null) ? DEFAULT_RESERVED_GROUP_NAME 1401 : reservedName; 1402 } 1403 1404 //============================================================================ 1405 // LISTENERS 1406 //============================================================================ 1407 //---------------------------------------------------------------------------- 1408 // Adding & Removing 1409 //---------------------------------------------------------------------------- 1410 1411 /** 1412 * Adds {@code newListener} to this catalog list. 1413 * <p> 1414 * The listener will be informed whenever: 1415 * <ul> 1416 * <li>A new item or group is added to this catalog</li> 1417 * <li>A current item or group is removed from this catalog</li> 1418 * <li>A current item or group is moved from one position 1419 * in this catalog to another</li> 1420 * </ul></p> 1421 * 1422 * @param newListener the listener to be added to this catalog's list. 1423 */ 1424 public void addListener(CatalogListener<I,G,C> newListener) 1425 { 1426 if (newListener != null) 1427 listeners.add(newListener); 1428 } 1429 1430 /** 1431 * Removes {@code listener} from this catalog's list. 1432 * @param listener the listener to be removed from this catalog's list. 1433 */ 1434 public void removeListener(CatalogListener<I,G,C> listener) 1435 { 1436 listeners.remove(listener); 1437 } 1438 1439 /** Removes all listeners from this catalog. */ 1440 public void removeAllListeners() 1441 { 1442 listeners.clear(); 1443 } 1444 1445 //---------------------------------------------------------------------------- 1446 // Listening to Main Group 1447 //---------------------------------------------------------------------------- 1448 1449 @SuppressWarnings("unchecked") 1450 public void memberAdded(G group, I newMember, int index) 1451 { 1452 //Catalog is allowed to listen only to its own mainGroup 1453 if ((group == mainGroup) && !suppressNotification) 1454 { 1455 for (CatalogListener<I,G,C> listener : listeners) 1456 listener.itemAdded((C)this, newMember, index); 1457 } 1458 } 1459 1460 @SuppressWarnings("unchecked") 1461 public void memberRemoved(G group, I formerMember, int index) 1462 { 1463 //Catalog is allowed to listen only to its own mainGroup 1464 if ((group == mainGroup) && !suppressNotification) 1465 { 1466 for (CatalogListener<I,G,C> listener : listeners) 1467 listener.itemRemoved((C)this, formerMember, index); 1468 } 1469 } 1470 1471 @SuppressWarnings("unchecked") 1472 public void memberMoved(G group, I member, int fromIndex, int toIndex) 1473 { 1474 //Catalog is allowed to listen only to its own mainGroup 1475 if ((group == mainGroup) && !suppressNotification) 1476 { 1477 for (CatalogListener<I,G,C> listener : listeners) 1478 listener.itemMoved((C)this, member, fromIndex, toIndex); 1479 } 1480 } 1481 1482 @SuppressWarnings("unchecked") 1483 public void memberReplaced(G group, I formerMember, I newMember, int index) 1484 { 1485 //Catalog is allowed to listen only to its own mainGroup 1486 if ((group == mainGroup) && !suppressNotification) 1487 { 1488 for (CatalogListener<I,G,C> listener : listeners) 1489 listener.itemReplaced((C)this, formerMember, newMember, index); 1490 } 1491 } 1492 1493 @SuppressWarnings("unchecked") 1494 public void membersSorted(G group) 1495 { 1496 //Catalog is allowed to listen only to its own mainGroup 1497 if ((group == mainGroup) && !suppressNotification) 1498 { 1499 for (CatalogListener<I,G,C> listener : listeners) 1500 listener.itemsSorted((C)this); 1501 } 1502 } 1503 1504 //============================================================================ 1505 // TEXT 1506 //============================================================================ 1507 1508 /** 1509 * Returns a text representation of this catalog. 1510 * @return a text representation of this catalog. 1511 */ 1512 public String toString() 1513 { 1514 StringBuilder buff = new StringBuilder(); 1515 1516 buff.append("name=").append(name); 1517 buff.append(", id=").append(id); 1518 buff.append(", items=").append(size()); 1519 buff.append(", groups=").append(groups.size()); 1520 1521 return buff.toString(); 1522 } 1523 1524 /** 1525 * Returns an XML representation of this catalog. 1526 * @return an XML representation of this catalog. 1527 * @throws JAXBException if anything goes wrong during the conversion to XML. 1528 */ 1529 public String toXml() throws JAXBException 1530 { 1531 return JaxbUtility.getSharedInstance().objectToXmlString(this); 1532 } 1533 1534 /** 1535 * Writes an XML representation of this catalog to {@code writer}. 1536 * @param writer the device to which XML is written. 1537 * @throws JAXBException if anything goes wrong during the conversion to XML. 1538 */ 1539 public void writeAsXmlTo(Writer writer) throws JAXBException 1540 { 1541 JaxbUtility.getSharedInstance().writeObjectAsXmlTo(writer, this, null); 1542 } 1543 1544 //============================================================================ 1545 // TRANSFORMATION TO OTHER FORMS 1546 //============================================================================ 1547 1548 /** 1549 * Creates and returns a group that has the same items in the same order 1550 * as this catalog. 1551 * @return a group representation of this catalog. 1552 */ 1553 public G toGroup() 1554 { 1555 G group = createGroup(); 1556 1557 //Replaced deactivated line below w/ code that uses the internal 1558 //member list directly. Did this because group.addAll will 1559 //weed out duplicate entries, but if the mainGroup actually 1560 //has duplicate entries, we need to expose that. 1561 //group.addAll(mainGroup.getAll()); 1562 group.getInternalMemberList().addAll(mainGroup.getInternalMemberList()); 1563 1564 group.setName(getName()); 1565 1566 return group; 1567 } 1568 1569 //============================================================================ 1570 // 1571 //============================================================================ 1572 1573 /** 1574 * Returns a catalog that is a copy of this one. 1575 * <p> 1576 * The returned catalog has items and groups with the same properties 1577 * as those of this catalog, but those items and groups are new and 1578 * distinct from those held herein.</p> 1579 * <p> 1580 * If anything goes wrong during the cloning procedure, 1581 * a {@code RuntimeException} will be thrown.</p> 1582 */ 1583 @SuppressWarnings("unchecked") 1584 public C clone() 1585 { 1586 C clone = null; 1587 1588 try 1589 { 1590 //This line takes care of the primitive fields properly 1591 //(This is the line that requires suppression of unchecked conversions.) 1592 clone = (C)super.clone(); 1593 1594 //We do NOT want the clone to have the same ID as the original. 1595 //The ID is here for the persistence layer; it is in charge of 1596 //setting IDs. To help it, we put the clone's ID in the uninitialized 1597 //state. 1598 clone.id = getIdOfUnidentified(); 1599 1600 //Clear the listeners. 1601 clone.listeners = new CopyOnWriteArraySet<CatalogListener<I,G,C>>(); 1602 1603 //Create a new main group and clone items individually. 1604 clone.mainGroup = clone.createMainGroup(); 1605 for (I thisItem : this.mainGroup.getInternalMemberList()) 1606 clone.mainGroup.add(thisItem.clone()); 1607 1608 //Copies the notes and any properties created by subclasses. 1609 clone.mainGroup.copyPropertiesFrom(this.mainGroup); 1610 1611 //Create a new list of groups and add clones of groups. 1612 clone.groups = new ArrayList<G>(); 1613 for (G thisGroup : this.groups) 1614 clone.addGroup(thisGroup.clone()); 1615 } 1616 catch (Exception ex) 1617 { 1618 throw new RuntimeException(ex); 1619 } 1620 1621 return clone; 1622 } 1623 1624 /** Returns <i>true</i> if {@code o} is equal to this catalog. */ 1625 @SuppressWarnings("unchecked") 1626 @Override 1627 public boolean equals(Object o) 1628 { 1629 //Quick exit if o is null 1630 if (o == null) 1631 return false; 1632 1633 //Quick exit if o is this 1634 if (o == this) 1635 return true; 1636 1637 //Quick exit if classes are different 1638 if (!o.getClass().equals(this.getClass())) 1639 return false; 1640 1641 Catalog other = (Catalog)o; 1642 1643 //NOTE: Absence of ID and reservedGroupName is intentional 1644 1645 //Items & groups must be equal and in same order 1646 return this.name.equals(other.name) && 1647 this.mainGroup.equals(other.mainGroup) && 1648 this.groups.equals(other.groups); 1649 } 1650 1651 /** Returns a hash code value for this catalog. */ 1652 public int hashCode() 1653 { 1654 //Taken from the Effective Java book by Joshua Bloch. 1655 //The constants 17 & 37 are arbitrary & carry no meaning. 1656 int result = 17; 1657 1658 //NOTE: Keep this method in synch w/ equals 1659 1660 result = 37 * result + name.hashCode(); 1661 result = 37 * result + mainGroup.hashCode(); 1662 result = 37 * result + groups.hashCode(); 1663 1664 return result; 1665 } 1666 }