001/* A list of graph elements.
002
003 Copyright (c) 2001-2015 The University of Maryland
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 MARYLAND 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 MARYLAND HAS BEEN ADVISED OF THE POSSIBILITY OF
015 SUCH DAMAGE.
016
017 THE UNIVERSITY OF MARYLAND 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 MARYLAND HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
022 ENHANCEMENTS, OR MODIFICATIONS.
023
024 */
025package ptolemy.graph;
026
027import java.util.ArrayList;
028import java.util.Collection;
029import java.util.Collections;
030import java.util.HashMap;
031import java.util.HashSet;
032import java.util.List;
033import java.util.Map;
034
035////////////////////////////////////////////////////////////////////////// //
036//ElementList
037
038/**
039 A list of graph elements. This class manages the storage and weight
040 information associated with a list of unique graph elements.
041 This class is normally for use internally within graph classes.
042
043 @author Shuvra S. Bhattacharyya
044 @version $Id$
045 @since Ptolemy II 2.0
046 @Pt.ProposedRating Red (cxh)
047 @Pt.AcceptedRating Red (cxh)
048 */
049public class ElementList extends LabeledList {
050    /** Construct an empty element list.
051     *  @param descriptor A one-word description of the type of elements
052     *  that are to be stored in this list.
053     *  @param graph The graph associated with this element list.
054     */
055    public ElementList(String descriptor, Graph graph) {
056        super();
057        _descriptor = descriptor;
058        _graph = graph;
059        _weightMap = new HashMap<Object, ArrayList>();
060        _unweightedSet = new HashSet();
061    }
062
063    /** Construct an empty element list with enough storage allocated for the
064     *  specified number of elements.  Memory management is more
065     *  efficient with this constructor if the number of elements is
066     *  known.
067     *  @param descriptor A one-word description of the type of elements
068     *  that are to be stored in this list.
069     *  @param graph The graph associated with this element list.
070     *  @param elementCount The number of elements.
071     */
072    public ElementList(String descriptor, Graph graph, int elementCount) {
073        super();
074        _descriptor = descriptor;
075        _graph = graph;
076        _weightMap = new HashMap<Object, ArrayList>(elementCount);
077        _unweightedSet = new HashSet(elementCount);
078    }
079
080    ///////////////////////////////////////////////////////////////////
081    ////                         public methods                    ////
082
083    /** Disassociate the given element from its weight information.
084     *  @param element The element.
085     *  @return True if the weight information was disassociated.
086     */
087    public boolean cancelWeight(Element element) {
088        // FIXME: needs better documentation
089        boolean removed = false;
090
091        if (element.hasWeight()) {
092            Object weight = element.getWeight();
093            ArrayList sameWeightList = _weightMap.get(weight);
094
095            if (sameWeightList == null) {
096                return false;
097            }
098
099            removed = sameWeightList.remove(element);
100
101            if (sameWeightList.isEmpty()) {
102                _weightMap.remove(weight);
103            }
104        } else {
105            removed = _unweightedSet.remove(element);
106        }
107
108        return removed;
109    }
110
111    /** Given an element in this list, check if the weight has
112     *  changed (since the element was added to the graph or was
113     *  last validated, whichever is more recent), and if so,
114     *  change the current mapping of a weight to the element or
115     *  remove the element from the set of unweighted elements.
116     *
117     *  @param element The graph element.
118     *  @return True if the weight associated with the element has
119     *  changed as determined by the equals method.
120     */
121    public boolean changeWeight(Element element) {
122        boolean weightValueHasChanged = false;
123        boolean found = false;
124        Object newWeight = element.hasWeight() ? element.getWeight() : null;
125
126        if (_unweightedSet.contains(element)) {
127            weightValueHasChanged = newWeight != null;
128
129            if (weightValueHasChanged) {
130                _unweightedSet.remove(element);
131                registerWeight(element);
132            }
133        } else {
134            // Find the weight that was previously associated with this
135            // element, if there was one.
136            // FindBugs: ptolemy.graph.ElementList.changeWeight(Element) makes inefficient use of keySet iterator instead of entrySet iterator
137
138            // Iterator weights = _weightMap.keySet().iterator();
139            Object nextWeight = null;
140            List nextList = null;
141
142            // while (weights.hasNext() && !found) {
143            //     nextWeight = weights.next();
144            //     nextList = (List) _weightMap.get(nextWeight);
145            //     found = nextList.contains(element);
146            // }
147            for (Map.Entry<Object, ArrayList> entry : _weightMap.entrySet()) {
148                nextList = entry.getValue();
149                found = nextList.contains(element);
150                if (found) {
151                    nextWeight = entry.getKey();
152                    break;
153                }
154            }
155
156            if (found) {
157                // Note that the weight can change without the weight
158                // comparison here changing (if the change does not affect
159                // comparison under the equals method).
160                weightValueHasChanged = !nextWeight.equals(newWeight);
161
162                if (weightValueHasChanged) {
163                    nextList.remove(element);
164
165                    if (nextList.isEmpty()) {
166                        _weightMap.remove(nextWeight);
167                    }
168
169                    registerWeight(element);
170                }
171            } else {
172                // FIXME: use an internal error exception here.
173                throw new RuntimeException("Internal error: the specified "
174                        + _descriptor + " is neither unweighted nor associated "
175                        + "with a weight."
176                        + GraphException.elementDump(element, _graph));
177            }
178        }
179
180        return weightValueHasChanged;
181    }
182
183    /** Clear all of the elements in this list.
184     */
185    @Override
186    public void clear() {
187        super.clear();
188        _weightMap.clear();
189        _unweightedSet.clear();
190    }
191
192    /** Test if the specified object is an element weight in this
193     *  list. Equality is
194     *  determined by the <code>equals</code> method. If the specified
195     *  weight is null, return false.
196     *
197     *  @param weight The element weight to be tested.
198     *  @return True if the specified object is an element weight in this list.
199     */
200    public boolean containsWeight(Object weight) {
201        // FIXME: on null, return true if there is an unweighted element.
202        return _weightMap.containsKey(weight);
203    }
204
205    /** Return an element in this list that has a specified weight. If multiple
206     *  elements have the specified weight, then return one of them
207     *  arbitrarily. If the specified weight is null, return an unweighted
208     *  element (again arbitrarily chosen if there are multiple unweighted
209     *  elements).
210     *  @param weight The specified weight.
211     *  @return An element that has this weight.
212     *  @exception GraphWeightException If the specified weight
213     *  is not an element weight in this list or if the specified weight
214     *  is null but the list does not contain any unweighted edges.
215     */
216    public Element element(Object weight) {
217        Collection elements = elements(weight);
218
219        if (elements.isEmpty()) {
220            throw new GraphWeightException(weight, null, _graph,
221                    "Invalid weight argument, the number of elements for"
222                            + " this weight is zero.");
223        }
224
225        return (Element) elements.iterator().next();
226    }
227
228    /** Return all the elements in this list in the form of an unmodifiable
229     *  collection.
230     *  @return All the elements in this list.
231     */
232    public Collection elements() {
233        return Collections.unmodifiableCollection(this);
234    }
235
236    /** Return all the elements in this graph that have a specified weight.
237     *  The elements are returned in the form of an unmodifiable collection.
238     *  If the specified weight is null, return all the unweighted elements.
239     *  If no elements have the specified weight (or if the argument is null and
240     *  there are no unweighted elements), return an empty collection.
241     *  Each element in the returned collection is an instance of
242     *  {@link Element}.
243     *  @param weight The specified weight.
244     *  @return The elements in this graph that have the specified weight.
245     */
246    public Collection elements(Object weight) {
247        if (weight == null) {
248            return Collections.unmodifiableCollection(_unweightedSet);
249        } else {
250            Collection sameWeightElements = _weightMap.get(weight);
251
252            if (sameWeightElements == null) {
253                return _emptyCollection;
254            } else {
255                return Collections.unmodifiableCollection(sameWeightElements);
256            }
257        }
258    }
259
260    /** Associate a graph element to its weight given the relevant mapping of
261     *  weights to elements, and the set of unweighted elements of the same
262     *  type (nodes or edges). If the element is unweighted, add it to the set
263     *  of unweighted elements.
264     *  @param element The element.
265     */
266    public void registerWeight(Element element) {
267        if (element.hasWeight()) {
268            Object weight = element.getWeight();
269            ArrayList sameWeightList = _weightMap.get(weight);
270
271            if (sameWeightList == null) {
272                sameWeightList = new ArrayList();
273                _weightMap.put(weight, sameWeightList);
274            }
275
276            sameWeightList.add(element);
277        } else {
278            _unweightedSet.add(element);
279        }
280    }
281
282    /** Remove an element from this list if it exists in the list.
283     *  This is an <em>O(1)</em> operation.
284     * @param element The element to be removed.
285     * @return True if the element was removed.
286     */
287    public boolean remove(Element element) {
288        boolean removed = super.remove(element);
289
290        if (removed) {
291            cancelWeight(element);
292        }
293
294        return removed;
295    }
296
297    /** Validate the weight of a given graph element, given the previous
298     *  weight of that element.
299     *  @param element The element.
300     *  @param oldWeight The previous weight (null if the element was
301     *  previously unweighted).
302     *  @return True if the weight is valid.
303     */
304    public boolean validateWeight(Element element, Object oldWeight) {
305        boolean changed = false;
306        Object newWeight = element.hasWeight() ? element.getWeight() : null;
307
308        if (oldWeight == null) {
309            if (!_unweightedSet.contains(element)) {
310                // This 'dump' of a null weight will also dump the graph.
311                // We use null as an argument instead of oldWeight to
312                // avoid FindBugs warnings.
313                throw new GraphWeightException(/*oldWeight*/null, null, _graph,
314                        "Incorrect previous weight specified.");
315            }
316
317            if (newWeight == null) {
318                return false;
319            }
320
321            _unweightedSet.remove(element);
322            changed = true;
323        } else {
324            // The weight may have changed in value even if comparison under
325            // the equals method has not changed. Thus we proceed
326            // with the removal unconditionally.
327            List elementList = _weightMap.get(oldWeight);
328
329            if (elementList == null || !elementList.remove(element)) {
330                throw new GraphWeightException(oldWeight, null, _graph,
331                        "Incorrect previous weight specified.");
332            }
333
334            changed = !oldWeight.equals(newWeight);
335        }
336
337        registerWeight(element);
338        return changed;
339    }
340
341    ///////////////////////////////////////////////////////////////////
342    ////                         private variables                 ////
343    // A one-word description of the type of elements stored in this list
344    private String _descriptor;
345
346    // The graph that this element list is associated with.
347    private Graph _graph;
348
349    // An unmodifiable, empty collection.
350    private static final Collection _emptyCollection = Collections
351            .unmodifiableCollection(new ArrayList(0));
352
353    // The set of elements that do not have weights. Each member is an
354    // Element.
355    private HashSet _unweightedSet;
356
357    // A mapping from element weights to the associated elements. Unweighted
358    // elements are not represented in this map. Keys in this this map
359    // are instances of of Object, and values instances of ArrayList
360    // whose elements are instances of Element.
361    private HashMap<Object, ArrayList> _weightMap;
362}