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}