001/* An ordered list of objects with names. 002 003 Copyright (c) 1997-2014 The Regents of the University of California. 004 All rights reserved. 005 Permission is hereby granted, without written agreement and without 006 license or royalty fees, to use, copy, modify, and distribute this 007 software and its documentation for any purpose, provided that the above 008 copyright notice and the following two paragraphs appear in all copies 009 of this software. 010 011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 015 SUCH DAMAGE. 016 017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 022 ENHANCEMENTS, OR MODIFICATIONS. 023 024 PT_COPYRIGHT_VERSION_2 025 COPYRIGHTENDKEY 026 027 028 NOTE: This class could leverage better 029 the underlying LinkedList class. E.g., it could be 030 capable of returning an enumeration sorted alphabetically by name. 031 This would require extensions to the interface, but not modifications 032 of the current interface. 033 */ 034package ptolemy.kernel.util; 035 036import java.io.Serializable; 037import java.util.Collections; 038import java.util.Enumeration; 039import java.util.HashMap; 040import java.util.Iterator; 041import java.util.LinkedList; 042import java.util.List; 043import java.util.NoSuchElementException; 044 045/////////////////////////////////////////////////////////////////// 046//// NamedList 047 048/** 049 An ordered list of objects with names. 050 The objects must implement the Nameable interface. 051 The names are required to be unique and non-null. 052 <p> 053 This class is biased towards sequential accesses. 054 If it is used with name lookups, the list should be small. 055 It is implemented as a linked list rather than hash table to 056 preserve ordering information, and thus would not provide efficient 057 name lookup for large lists. 058 Also, it permits the name of an object in the list 059 to change without this list being informed. 060 <p> 061 An instance of this class may have a container, but that container 062 is only used for error reporting. 063 064 @author Mudit Goel, Edward A. Lee, Contributor: Jason E. Smith 065 @version $Id$ 066 @since Ptolemy II 0.2 067 @Pt.ProposedRating Green (eal) 068 @Pt.AcceptedRating Green (cxh) 069 @see Nameable 070 */ 071@SuppressWarnings("serial") 072public final class NamedList implements Cloneable, Serializable { 073 /** Construct an empty NamedList with no container. 074 */ 075 public NamedList() { 076 super(); 077 } 078 079 /** Construct an empty list with a Nameable container. 080 * @param container The container (for error reporting). 081 */ 082 public NamedList(Nameable container) { 083 super(); 084 _container = container; 085 } 086 087 /** Copy constructor. Create a copy of the specified list, but 088 * with no container. This is useful to permit enumerations over 089 * a list while the list continues to be modified. If the argument 090 * list is null, then the resulting copy will be an empty named list. 091 * @param original The list to copy. 092 */ 093 public NamedList(NamedList original) { 094 super(); 095 096 if (original != null) { 097 _namedList.addAll(original.elementList()); 098 if (_hashEnabled) { 099 _hashedList = (HashMap<String, Nameable>) original._hashedList 100 .clone(); 101 } 102 } 103 104 _container = null; 105 } 106 107 /////////////////////////////////////////////////////////////////// 108 //// public methods //// 109 110 /** Add an element to the end of the list. 111 * The element is required to have a name that does not coincide with 112 * that of an element already on the list. 113 * @param element Element to be added to the list. 114 * @exception IllegalActionException If the argument has no name. 115 * @exception NameDuplicationException If the name coincides with 116 * an element already on the list. 117 */ 118 public void append(Nameable element) 119 throws IllegalActionException, NameDuplicationException { 120 String newName = element.getName(); 121 122 if (newName == null) { 123 throw new IllegalActionException(_container, 124 _NULL_NAME_EXCEPTION_STRING); 125 } 126 127 // NOTE: Having to do this named lookup each time is expensive. 128 if (get(newName) == null) { 129 _namedList.add(element); 130 if (_hashEnabled) { 131 _hashedList.put(newName, element); 132 } 133 } else { 134 throw new NameDuplicationException(_container, element); 135 } 136 } 137 138 /** Build an independent copy of the list. 139 * The elements themselves are not cloned. 140 * @return A new instance of NamedList. 141 */ 142 @Override 143 public Object clone() { 144 return new NamedList(this); 145 } 146 147 /** Return an unmodifiable list with the contents of this named list. 148 * @return A list of Nameable objects. 149 */ 150 public List elementList() { 151 return Collections.unmodifiableList(_namedList); 152 } 153 154 /** Enumerate the elements of the list. 155 * @deprecated Use elementList() instead. 156 * @return An enumeration of Nameable objects. 157 */ 158 @Deprecated 159 public Enumeration elements() { 160 return Collections.enumeration(_namedList); 161 } 162 163 /** Get the first element. 164 * @exception NoSuchElementException If the list is empty. 165 * @return The specified element. 166 */ 167 public Nameable first() throws NoSuchElementException { 168 return _namedList.getFirst(); 169 } 170 171 /** Get an element by name. 172 * @param name The name of the desired element. 173 * @return The requested element if it is found, and null otherwise. 174 */ 175 public Nameable get(String name) { 176 if (_hashEnabled) { 177 // If the name was changed, then _hashedList will be wrong 178 Nameable found = _hashedList.get(name); 179 if (found != null) { 180 if (found.getName().equals(name)) { 181 return found; 182 } else { 183 // The name of the Nameable was changed, but the 184 // _hashedList was not updated, so we remove the old 185 // entry. 186 _hashedList.remove(name); 187 } 188 } 189 } 190 191 // Do a linear search 192 Iterator iterator = _namedList.iterator(); 193 194 while (iterator.hasNext()) { 195 Nameable obj = (Nameable) iterator.next(); 196 197 if (name.equals(obj.getName())) { 198 if (_hashEnabled) { 199 // The name of the NamedObj was likely changed, so 200 // add it to the hashedList. 201 _hashedList.put(name, obj); 202 } 203 return obj; 204 } 205 } 206 207 return null; 208 } 209 210 /** Return true if the specified object is on the list. 211 * @param element Element to be searched for in the list. 212 * @return A boolean indicating whether the element is on the list. 213 */ 214 public boolean includes(Nameable element) { 215 if (_hashEnabled) { 216 return get(element.getName()) != null; 217 } 218 return _namedList.contains(element); 219 } 220 221 /** Insert a new element after the specified element. 222 * If there is no such element, then append the new element 223 * to the end of the list. 224 * @param name The element after which to insert the new element. 225 * @param element The element to insert. 226 * @exception IllegalActionException If the element to insert has no name. 227 * @exception NameDuplicationException If the element to insert has a 228 * name that coincides with one already on the list. 229 */ 230 public void insertAfter(String name, Nameable element) 231 throws IllegalActionException, NameDuplicationException { 232 int index = _getIndexOf(name); 233 234 if (index == -1) { 235 // name doesn't exist in list 236 append(element); 237 } else { 238 // name exists in list 239 _insertAt(index + 1, element); 240 } 241 if (_hashEnabled) { 242 _hashedList.put(element.getName(), element); 243 } 244 } 245 246 /** Insert a new element before the specified element. 247 * If there is no such element, then the insert the new element 248 * at the beginning of the list. 249 * @param name The element before which to insert the new element. 250 * @param element The element to insert. 251 * @exception IllegalActionException If the element to insert has no name. 252 * @exception NameDuplicationException If the element to insert has a 253 * name that coincides with one already on the list. 254 */ 255 public void insertBefore(String name, Nameable element) 256 throws IllegalActionException, NameDuplicationException { 257 int index = _getIndexOf(name); 258 259 if (index == -1) { 260 // name doesn't exist in list 261 prepend(element); 262 } else { 263 // name exists in the list 264 _insertAt(index, element); 265 } 266 if (_hashEnabled) { 267 _hashedList.put(element.getName(), element); 268 } 269 } 270 271 /** Get the last element. 272 * @exception NoSuchElementException If the list is empty. 273 * @return The last element. 274 */ 275 public Nameable last() throws NoSuchElementException { 276 return _namedList.getLast(); 277 } 278 279 /** Move the specified element down by one in the list. 280 * If the specified element is not in the list, then throw 281 * an exception. If the element is 282 * already at the end of the list, the leave it where it is. 283 * @param element Element to move down in the list. 284 * @return The index of the specified object prior to moving it, 285 * or -1 if it was not moved (it is already last). 286 * @exception IllegalActionException If the argument is not 287 * on the list. 288 */ 289 public int moveDown(Nameable element) throws IllegalActionException { 290 int index = _namedList.indexOf(element); 291 292 if (index < 0) { 293 // The element is not on the list. 294 throw new IllegalActionException(element, "Not on the list."); 295 } else if (index < _namedList.size() - 1) { 296 _namedList.remove(element); 297 _namedList.add(index + 1, element); 298 return index; 299 } else { 300 return -1; 301 } 302 } 303 304 /** Move the specified element to the beginning of the list. 305 * If the specified element is not in the list, then throw 306 * an exception. 307 * @return The index of the specified object prior to moving it, 308 * or -1 if it was not moved (it is already first). 309 * @param element Element to move to the top of the list. 310 * @exception IllegalActionException If the argument is not 311 * on the list. 312 */ 313 public int moveToFirst(Nameable element) throws IllegalActionException { 314 int index = _namedList.indexOf(element); 315 316 if (index < 0) { 317 // The element is not on the list. 318 throw new IllegalActionException(element, "Not on the list."); 319 } else if (index > 0) { 320 _namedList.remove(element); 321 _namedList.add(0, element); 322 return index; 323 } else { 324 return -1; 325 } 326 } 327 328 /** Move the specified element to the specified position in the list. 329 * If the specified element is not in the list, then throw 330 * an exception. 331 * @param element Element to move. 332 * @param index The position to which to move it. 333 * @return The index of the specified object prior to moving it, 334 * or -1 if it was not moved (it is already at the specified 335 * position). 336 * @exception IllegalActionException If the argument is not 337 * on the list, or if the specified position is out of range. 338 */ 339 public int moveToIndex(Nameable element, int index) 340 throws IllegalActionException { 341 int priorIndex = _namedList.indexOf(element); 342 343 if (priorIndex < 0) { 344 // The element is not on the list. 345 throw new IllegalActionException(element, "Not on the list."); 346 } else if (index < 0 || index >= _namedList.size()) { 347 throw new IllegalActionException(element, "Index out of range."); 348 } else if (priorIndex != index) { 349 _namedList.remove(element); 350 _namedList.add(index, element); 351 return priorIndex; 352 } else { 353 return -1; 354 } 355 } 356 357 /** Move the specified element to the end of the list. 358 * If the specified element is not in the list, then throw 359 * an exception. 360 * @param element Element to move to the end of the list. 361 * @return The index of the specified object prior to moving it, 362 * or -1 if it was not moved (it is already last). 363 * @exception IllegalActionException If the argument is not 364 * on the list. 365 */ 366 public int moveToLast(Nameable element) throws IllegalActionException { 367 int index = _namedList.indexOf(element); 368 369 if (index < 0) { 370 // The element is not on the list. 371 throw new IllegalActionException(element, "Not on the list."); 372 } else if (index < _namedList.size() - 1) { 373 _namedList.remove(element); 374 _namedList.add(element); 375 return index; 376 } else { 377 return -1; 378 } 379 } 380 381 /** Move the specified element up by one in the list. 382 * If the specified element is not in the list, then 383 * throw an exception. 384 * @param element Element to move up in the list. 385 * @return The index of the specified object prior to moving it, 386 * or -1 if it was not moved (it is already first). 387 * @exception IllegalActionException If the argument is not 388 * on the list. 389 */ 390 public int moveUp(Nameable element) throws IllegalActionException { 391 int index = _namedList.indexOf(element); 392 393 if (index < 0) { 394 // The element is not on the list. 395 throw new IllegalActionException(element, "Not on the list."); 396 } else if (index > 0) { 397 _namedList.remove(element); 398 _namedList.add(index - 1, element); 399 return index; 400 } else { 401 return -1; 402 } 403 } 404 405 /** Add an element to the beginning of the list. 406 * The element is required to have a name that does not coincide with 407 * that of an element already on the list. 408 * @param element Element to be added to the list. 409 * @exception IllegalActionException If the argument is not 410 * on the list. 411 * @exception IllegalActionException If the argument has no name. 412 * @exception NameDuplicationException If the name coincides with 413 * an element already on the list. 414 */ 415 public void prepend(Nameable element) 416 throws IllegalActionException, NameDuplicationException { 417 _insertAt(0, element); 418 if (_hashEnabled) { 419 _hashedList.put(element.getName(), element); 420 } 421 } 422 423 /** Remove the specified element. If the element is not on the 424 * list, do nothing. 425 * @param element Element to be removed. 426 */ 427 public void remove(Nameable element) { 428 _namedList.remove(element); 429 if (_hashEnabled) { 430 _hashedList.remove(element.getName()); 431 } 432 } 433 434 /** Remove an element specified by name. If no such element exists 435 * on the list, do nothing. 436 * @param name Name of the element to be removed. 437 * @return A reference to the removed object, or null if no 438 * object with the specified name is found. 439 */ 440 public Nameable remove(String name) { 441 Nameable element = get(name); 442 443 if (element != null) { 444 remove(element); 445 if (_hashEnabled) { 446 _hashedList.remove(name); 447 } 448 return element; 449 } 450 451 return null; 452 } 453 454 /** Remove all elements from the list. */ 455 public void removeAll() { 456 _namedList.clear(); 457 if (_hashEnabled) { 458 _hashedList.clear(); 459 } 460 } 461 462 /** Return the number of elements in the list. 463 * @return A non-negative integer. 464 */ 465 public int size() { 466 return _namedList.size(); 467 } 468 469 /** Return a string description of the list. 470 * @return A string description of the list. 471 */ 472 @Override 473 public String toString() { 474 if (_namedList != null) { 475 return _namedList.toString(); 476 } 477 return "[]"; 478 } 479 480 /////////////////////////////////////////////////////////////////// 481 //// private methods //// 482 483 /* Get the index of the element with the specified name. 484 * This is private because the 485 * interface to this class does not include the notion of indexes. 486 * @param name The name of the desired element. 487 * @return The index of the desired element, or -1 if it not on the list. 488 */ 489 private int _getIndexOf(String name) { 490 Iterator iterator = _namedList.iterator(); 491 int count = 0; 492 493 while (iterator.hasNext()) { 494 Nameable obj = (Nameable) iterator.next(); 495 496 if (name.equals(obj.getName())) { 497 return count; 498 } 499 500 count++; 501 } 502 503 return -1; 504 } 505 506 /* Add an element at the specified position index in the list. 507 * The element is inserted just prior to that element that currently 508 * has the specified position index. The index can range from 0..size() 509 * (i.e., one past the current last index). If the index is equal to 510 * size(), the element is appended as the new last element. 511 * This is private because the 512 * interface to this class does not include the notion of indexes. 513 * @param index Where to insert the new element. 514 * @param element The element to insert. 515 * @exception IllegalActionException If the Element to insert has no name. 516 * @exception NameDuplicationException If the Element to insert has a 517 * name that coincides with one already on the list. 518 */ 519 private void _insertAt(int index, Nameable element) 520 throws IllegalActionException, NameDuplicationException { 521 if (element.getName() == null) { 522 throw new IllegalActionException(_container, 523 _NULL_NAME_EXCEPTION_STRING); 524 } else if (get(element.getName()) == null) { 525 _namedList.add(index, element); 526 return; 527 } 528 529 throw new NameDuplicationException(_container, element); 530 } 531 532 /* 533 * Activates use of a hashmap to quicken lookup times of items 534 * stored in this list. 535 */ 536 private void enableHash() { 537 538 _hashedList = new HashMap<String, Nameable>(_threshold + 1, 3.0f); 539 540 Iterator iterator = _namedList.iterator(); 541 542 while (iterator.hasNext()) { 543 Nameable obj = (Nameable) iterator.next(); 544 _hashedList.put(obj.getName(), obj); 545 } 546 547 _hashEnabled = true; 548 } 549 550 /////////////////////////////////////////////////////////////////// 551 //// private variables //// 552 553 /** @serial The container (owner) of this list. */ 554 private Nameable _container; 555 556 private static final int _threshold = 100; 557 558 /** @serial A LinkedList containing the elements. */ 559 private LinkedList<Nameable> _namedList = new LinkedList<Nameable>() { 560 @Override 561 public boolean add(Nameable obj) { 562 // Findbugs: "Ambiguous invocation of either an outer or 563 // inherited method java.util.LinkedList.size()," so we use super.size() 564 if (super.size() > _threshold && !_hashEnabled) { 565 enableHash(); 566 } 567 return super.add(obj); 568 } 569 }; 570 571 /** @serial A HashMap linking names to LinkedList entries */ 572 private HashMap<String, Nameable> _hashedList = null; 573 574 /** @serial A boolean indicating that the hashmap was enabled */ 575 private boolean _hashEnabled = false; 576 577 // Constant strings. 578 private static final String _NULL_NAME_EXCEPTION_STRING = "Attempt to add an object with a null name to a NamedList."; 579 580}