001/* A list of unique objects with an efficient mapping from the objects
002 into consecutive integer labels.
003
004 Copyright (c) 2001-2015 The University of Maryland.
005 All rights reserved.
006 Permission is hereby granted, without written agreement and without
007 license or royalty fees, to use, copy, modify, and distribute this
008 software and its documentation for any purpose, provided that the above
009 copyright notice and the following two paragraphs appear in all copies
010 of this software.
011
012 IN NO EVENT SHALL THE UNIVERSITY OF MARYLAND BE LIABLE TO ANY PARTY
013 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
014 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
015 THE UNIVERSITY OF MARYLAND HAS BEEN ADVISED OF THE POSSIBILITY OF
016 SUCH DAMAGE.
017
018 THE UNIVERSITY OF MARYLAND SPECIFICALLY DISCLAIMS ANY WARRANTIES,
019 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
020 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
021 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
022 MARYLAND HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
023 ENHANCEMENTS, OR MODIFICATIONS.
024
025 PT_COPYRIGHT_VERSION_2
026 COPYRIGHTENDKEY
027
028
029 */
030package ptolemy.graph;
031
032import java.util.ArrayList;
033import java.util.Collection;
034import java.util.Collections;
035import java.util.HashMap;
036import java.util.Iterator;
037import java.util.List;
038import java.util.ListIterator;
039
040///////////////////////////////////////////////////////////////////
041//// LabeledList
042
043/** A list of unique objects (<i>elements</i>) with a mapping from the
044 elements into consecutive integer labels. The labels are consecutive
045 integers between 0 and <em>N</em>-1 inclusive, where <em>N</em> is
046 the total number of elements in the list. This list features
047 <em>O</em>(1) list insertion, <em>O</em>(1) testing for membership in
048 the list, <em>O</em>(1) access of a list element from its associated
049 label, and <em>O</em>(1) access of a label from its corresponding
050 element.  Removal from the list is, however, an
051 <em>O</em>(<em>1</em>) operation.  The element labels are useful, for
052 example, in creating mappings from list elements into elements of
053 arbitrary arrays.  More generally, element labels can be used to
054 maintain arbitrary <em>m</em>-dimensional matrices that are indexed
055 by the list elements (via the associated element labels).
056
057 <p> Element labels maintain their consistency (remain constant) during
058 periods when no elements are removed from the list. When elements are
059 removed, the labels assigned to the remaining elements may
060 change (see {@link #remove(Object)} for details).
061
062 <p> Elements themselves must be non-null and distinct, as determined by the
063 <code>equals</code> method.
064
065 <p> This class supports all required operations of
066 the {@link java.util.List} interface, except
067 for the {@link java.util.List#subList(int, int)} operation,
068 which results in an UnsupportedOperationException.
069
070 @author Shuvra S. Bhattacharyya
071 @version $Id$
072 @since Ptolemy II 2.0
073 @Pt.ProposedRating Red (cxh)
074 @Pt.AcceptedRating Red (cxh)
075 */
076public class LabeledList implements List {
077    /** Construct an empty list.
078     */
079    public LabeledList() {
080        _elements = new ArrayList(0);
081        _labels = new HashMap(0);
082    }
083
084    /** Construct an empty list with enough storage allocated to hold
085     *  the specified number of elements.  Memory management is more
086     *  efficient with this constructor (assuming the number of elements is
087     *  known).
088     *  @param size The number of elements.
089     */
090    public LabeledList(int size) {
091        _elements = new ArrayList(size);
092        _labels = new HashMap(size);
093    }
094
095    ///////////////////////////////////////////////////////////////////
096    ////                         public methods                    ////
097
098    /** Add an element to the list. The label assigned to this element
099     *  will be equal to the number of elements in the list
100     *  prior to insertion of the element.
101     *  @param element The element to insert.
102     *  @return True unconditionally (assuming an exception does not occur).
103     *  @exception IllegalArgumentException If the specified element is null,
104     *  or if it already exists in the list.
105     */
106    @Override
107    public boolean add(Object element) {
108        if (element == null) {
109            throw new IllegalArgumentException(
110                    "Attempt to insert a null " + "element");
111        } else if (_labels.containsKey(element)) {
112            throw new IllegalArgumentException("Attempt to insert a duplicate "
113                    + "element." + _elementDump(element));
114        } else {
115            _labels.put(element, Integer.valueOf(_elements.size()));
116            _elements.add(element);
117            return true;
118        }
119    }
120
121    /** Unsupported optional method of the List interface.
122     *  @param index Unused.
123     *  @param element Unused.
124     *  @exception UnsupportedOperationException Always thrown.
125     */
126    @Override
127    public void add(int index, Object element) {
128        throw new UnsupportedOperationException();
129    }
130
131    /** Unsupported optional method of the List interface.
132     *  @param collection Unused.
133     *  @exception UnsupportedOperationException Always thrown.
134     *  @return never returns.
135     */
136    @Override
137    public boolean addAll(Collection collection) {
138        throw new UnsupportedOperationException();
139    }
140
141    /** Unsupported optional method of the List interface.
142     *  @param index Unused.
143     *  @param collection Unused.
144     *  @exception UnsupportedOperationException Always thrown.
145     *  @return never returns.
146     */
147    @Override
148    public boolean addAll(int index, Collection collection) {
149        throw new UnsupportedOperationException();
150    }
151
152    /** Clear all of the elements in this list.
153     */
154    @Override
155    public void clear() {
156        _elements.clear();
157        _labels.clear();
158    }
159
160    /** Return true if the specified object is an element of this list.
161     *  @param object The specified object.
162     *  @return True if the specified object is an element of this list;
163     *  false if the object is null or is not in the list.
164     */
165    @Override
166    public boolean contains(Object object) {
167        if (object == null) {
168            return false;
169        } else {
170            return _labels.containsKey(object);
171        }
172    }
173
174    /** Returns true if this list contains all of the elements of the
175     *  specified collection.
176     *  @param collection The specified collection.
177     *  @return True if this list contains all of the elements of the
178     *  specified collection.
179     */
180    @Override
181    public boolean containsAll(Collection collection) {
182        Iterator elements = collection.iterator();
183
184        while (elements.hasNext()) {
185            if (!contains(elements.next())) {
186                return false;
187            }
188        }
189
190        return true;
191    }
192
193    /** Compares the specified object with this list for equality.
194     *  @param object The object.
195     *  @return True if the specified object is equal to this list.
196     */
197    @Override
198    public boolean equals(Object object) {
199        return _elements.equals(object);
200    }
201
202    /** Return the element that has a specified label.
203     *  @param label The label.
204     *  @return The element.
205     *  @exception IndexOutOfBoundsException If there is no element that
206     *  has the specified label.
207     */
208    @Override
209    public Object get(int label) {
210        if (label < 0 || label >= _elements.size()) {
211            throw new IndexOutOfBoundsException("Invalid label: " + label);
212        }
213
214        return _elements.get(label);
215    }
216
217    /** Return the hash code value for this list.
218     *  @return The hash code value.
219     */
220    @Override
221    public int hashCode() {
222        return _elements.hashCode();
223    }
224
225    /** Return the label in this list of the specified
226     *  element; return -1 if the element is null or this list does not
227     *  contain the element.
228     *  @param element The element.
229     *  @return The label of the element.
230     *  @see #label(Object)
231     */
232    @Override
233    public int indexOf(Object element) {
234        if (element == null) {
235            return -1;
236        } else {
237            Integer label = (Integer) _labels.get(element);
238
239            if (label == null) {
240                return -1;
241            } else {
242                return label.intValue();
243            }
244        }
245    }
246
247    /** Returns true if this list contains no elements.
248     *  @return True if this list contains no elements.
249     */
250    @Override
251    public boolean isEmpty() {
252        return size() == 0;
253    }
254
255    /** Return an iterator over the elements in the list. The iterator
256     *  returned is safe in that it cannot be used to modify the list.
257     *  FIXME: what happens when you try to modify the list.
258     *  @return An iterator over the elements in the list;
259     */
260    @Override
261    public Iterator iterator() {
262        return Collections.unmodifiableList(_elements).iterator();
263    }
264
265    /** Return the label of the specified element.
266     *  @param element  The specified element.
267     *  @return The corresponding label.
268     *  @exception IllegalArgumentException If the specified element is not
269     *  in this list.
270     *  @exception NullPointerException If the specified element is null.
271     *  @see #indexOf(Object)
272     */
273    public final int label(Object element) {
274        if (element == null) {
275            throw new NullPointerException("Null element specified.");
276        } else {
277            Integer label = (Integer) _labels.get(element);
278
279            if (label == null) {
280                throw new IllegalArgumentException("The specified object is not"
281                        + " an element of this list. " + _elementDump(element));
282            } else {
283                return label.intValue();
284            }
285        }
286    }
287
288    /** Returns the index in this list of the last occurrence of the specified
289     *  element, or -1 if this list does not contain this element.
290     *  Since elements in a labeled list are distinct, this is the same
291     *  as {@link #indexOf(Object)}, and is maintained only for conformance
292     *  with the list interface.
293     */
294    @Override
295    public int lastIndexOf(Object element) {
296        return _elements.indexOf(element);
297    }
298
299    /** Return a list iterator over the elements in the list. The iterator
300     *  returned is safe in that it cannot be used to modify the list.
301     *  @return A list iterator over the elements in the list;
302     */
303    @Override
304    public ListIterator listIterator() {
305        return Collections.unmodifiableList(_elements).listIterator();
306    }
307
308    /** Return a list iterator over the elements in this list, starting
309     *  at a specified position in the list. The iterator
310     *  returned is safe in that it cannot be used to modify the list.
311     *  @param index The specified starting position.
312     *  @return A list iterator over the elements in the list;
313     */
314    @Override
315    public ListIterator listIterator(int index) {
316        return Collections.unmodifiableList(_elements).listIterator(index);
317    }
318
319    /** Remove an element from this list.
320     * Elements that have higher-valued
321     * labels than this element will have their labels reduced in value
322     * by one. All other element labels will remain unchanged.
323     * If the specified element is not in the list, the list will be
324     * unchanged.
325     * FIXME: leave indices indeterminate after removal.
326     * @param element The element.
327     * @return True If this list contained the element.
328     */
329    @Override
330    public boolean remove(Object element) {
331        int label;
332
333        try {
334            label = label(element);
335        } catch (IllegalArgumentException exception) {
336            throw new IllegalArgumentException("Attempt to remove a "
337                    + "non-existent element. " + _elementDump(element));
338        }
339
340        _labels.remove(element);
341        _elements.remove(label);
342        _labelElements(label);
343        return true;
344    }
345
346    /** Remove and return an element with a specified label from this list.
347     * Elements that have higher-valued
348     * labels than this element will have their labels reduced in value
349     * by one. All other element labels will remain unchanged.
350     * @param label The specified label.
351     * @return The element that is removed.
352     * @exception IndexOutOfBoundsException If there is no element with
353     * the specified label.
354     */
355    @Override
356    public Object remove(int label) {
357        Object element = get(label);
358        _labels.remove(element);
359
360        Object removed = _elements.remove(label);
361        _labelElements(label);
362        return removed;
363    }
364
365    /** Unsupported optional method of the List interface.
366     *  @param collection Unused.
367     *  @return never returns.
368     *  @exception UnsupportedOperationException Always thrown.
369     */
370    @Override
371    public boolean removeAll(Collection collection) {
372        throw new UnsupportedOperationException();
373    }
374
375    /** Unsupported optional method of the List interface.
376     *  @param collection Unused.
377     *  @return never returns.
378     *  @exception UnsupportedOperationException Always thrown.
379     */
380    @Override
381    public boolean retainAll(Collection collection) {
382        throw new UnsupportedOperationException();
383    }
384
385    /** Unsupported optional method of the List interface.
386     *  @param index Unused.
387     *  @param element Unused.
388     *  @return never returns.
389     *  @exception UnsupportedOperationException Always thrown.
390     */
391    @Override
392    public Object set(int index, Object element) {
393        throw new UnsupportedOperationException();
394    }
395
396    /** Return the number of elements in this list.
397     *  @return The number of elements.
398     */
399    @Override
400    public int size() {
401        return _elements.size();
402    }
403
404    /** Unsupported method of the List interface.
405     *  @param fromIndex Unused.
406     *  @param toIndex Unused.
407     *  @return never returns.
408     *  @exception UnsupportedOperationException Always thrown.
409     */
410    @Override
411    public List subList(int fromIndex, int toIndex) {
412        throw new UnsupportedOperationException();
413    }
414
415    /** Returns an array containing all of the elements in this list in
416     *  proper sequence.
417     *  @return An array containing all of the elements in this list.
418     */
419    @Override
420    public Object[] toArray() {
421        return _elements.toArray();
422    }
423
424    /** Returns an array containing all of the elements in this list in
425     *  proper sequence; the runtime type of the returned array is that of
426     *  the specified array.
427     *  @param array The specified array.
428     *  @return An array containing all of the elements in this list.
429     */
430    @Override
431    public Object[] toArray(Object[] array) {
432        return _elements.toArray(array);
433    }
434
435    /** Return a string representation of this list, given a delimiter
436     *  for separating successive elements, and a flag that indicates
437     *  whether element labels should be included in the string.
438     *  The string
439     *  representation is constructed by concatenating
440     *  the string representations of the individual elements,
441     *  according to the order of their labels. The element strings
442     *  are separated by the specified delimiter, and are optionally
443     *  preceded by the associated labels.
444     *  @param delimiter The delimiter that separates elements in the
445     *  generated string.
446     *  @param includeLabels If this is true, then precede each
447     *  element with its label (followed by a colon and space) in the
448     *  generated string; otherwise, omit the labels.
449     *  @return A string representation of this list.
450     */
451    public String toString(String delimiter, boolean includeLabels) {
452        Iterator elements = iterator();
453        StringBuffer result = new StringBuffer();
454
455        while (elements.hasNext()) {
456            Object element = elements.next();
457            result.append((includeLabels ? label(element) + ": " : "") + element
458                    + (elements.hasNext() ? delimiter : ""));
459        }
460
461        return result.toString();
462    }
463
464    /** Return a string representation of this list with elements separated
465     *  by new lines, and element labels omitted from the representation.
466     *  The string
467     *  representation is constructed by the concatenating
468     *  string representations of the individual elements,
469     *  according to the order of their labels. The element strings
470     *  are separated by newlines. The element labels are not included
471     *  in the string representation.
472     *  @return A string representation of this list.
473     */
474    @Override
475    public String toString() {
476        return toString("\n", false);
477    }
478
479    ///////////////////////////////////////////////////////////////////
480    ////                         private methods                   ////
481    /** Return a dump of a list element that is suitable for inclusion
482     *  in an error message.
483     */
484    private String _elementDump(Object element) {
485        return "The offending element follows:\n"
486                + (element == null ? "null" : element) + "\n";
487    }
488
489    /** Fill in the labels map with the appropriate indices of
490     *  the array list, starting at a specified index.
491     */
492    private void _labelElements(int startIndex) {
493        for (int i = startIndex; i < _elements.size(); i++) {
494            _labels.put(_elements.get(i), Integer.valueOf(i));
495        }
496    }
497
498    ///////////////////////////////////////////////////////////////////
499    ////                         private variables                 ////
500    /** The elements that are associated with this list. */
501    private ArrayList _elements;
502
503    /** Translation from list element to label. The keys of this HashMap
504     * are list elements (instances of Object), and the values are
505     * the corresponding element labels (instances of Integer).
506     * This translation can also be
507     * done with indexOf(), but a HashMap is faster.
508     */
509    private HashMap _labels;
510}