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}