001/* A graph model for basic ptolemy models. 002 003 Copyright (c) 1999-2016 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 */ 028package ptolemy.vergil.basic; 029 030import java.util.List; 031 032import diva.graph.GraphEvent; 033import diva.graph.modular.CompositeModel; 034import diva.graph.modular.ModularGraphModel; 035import diva.graph.modular.NodeModel; 036import ptolemy.data.ObjectToken; 037import ptolemy.data.Token; 038import ptolemy.data.expr.Variable; 039import ptolemy.kernel.Port; 040import ptolemy.kernel.util.Attribute; 041import ptolemy.kernel.util.ChangeListener; 042import ptolemy.kernel.util.ChangeRequest; 043import ptolemy.kernel.util.InternalErrorException; 044import ptolemy.kernel.util.Locatable; 045import ptolemy.kernel.util.Location; 046import ptolemy.kernel.util.NamedObj; 047import ptolemy.util.MessageHandler; 048import ptolemy.vergil.kernel.AttributeNodeModel; 049import ptolemy.vergil.kernel.CompositePtolemyModel; 050 051/////////////////////////////////////////////////////////////////// 052//// AbstractBasicGraphModel 053 054/** 055 This base class provides some common services for visual notations for 056 Ptolemy II models. It assumes that the semantic object of a particular 057 graph object is fixed, and provides facilities for making changes to the 058 model via a change request. It supports visible attributes. 059 <p> 060 This class uses a change listener to detect changes to the Ptolemy model 061 that do not originate from this class. These changes are propagated 062 as structure changed graph events to all graphListeners registered with this 063 model. This mechanism allows a graph visualization of a ptolemy model to 064 remain synchronized with the state of a mutating model. 065 066 @author Steve Neuendorffer, Contributor: Edward A. Lee 067 @version $Id$ 068 @since Ptolemy II 2.0 069 @Pt.ProposedRating Yellow (neuendor) 070 @Pt.AcceptedRating Red (johnr) 071 */ 072public abstract class AbstractBasicGraphModel extends ModularGraphModel 073 implements ChangeListener { 074 /** Create a graph model for the specified Ptolemy II model. 075 * Note that the argument need not be a CompositeEntity, although 076 * if it is not, then it is a rather trivial graph that only has 077 * hierarchy. I.e., there can be no links. 078 * @param composite The Ptolemy II model. 079 */ 080 public AbstractBasicGraphModel(NamedObj composite) { 081 super(composite); 082 _composite = composite; 083 composite.addChangeListener(this); 084 } 085 086 /////////////////////////////////////////////////////////////////// 087 //// public methods //// 088 089 /** Notify the listener that a change has been successfully executed. 090 * If the originator of this change is not this graph model, then 091 * issue a graph event to indicate that the structure of the graph 092 * has changed. 093 * @param change The change that has been executed. 094 */ 095 @Override 096 public void changeExecuted(ChangeRequest change) { 097 // Ignore anything that comes from this graph model. 098 // The other methods take care of issuing the graph event in 099 // that case. 100 // NOTE: Unfortunately, when you perform look inside, you 101 // get a new graph model, and that graph model is modified 102 // (for example, by adding icons). This means that the 103 // original graph model will be notified of changes, 104 // rather spuriously. We tried having this ignore 105 // any change whose source was an instance of GraphModel, 106 // but this breaks MVC. If you have two views open 107 // on the graph, then the second view will not be notified 108 // of changes. 109 // Note that a change listener is registered with the top-level 110 // model, as it probably has to be, since a change to a model 111 // can have repercussions anywhere in the model. 112 113 // If this change request is not a structural change we won't 114 // repaint the model. 115 if (change != null && (change.getSource() == this 116 || !change.isStructuralChange())) { 117 return; 118 } 119 120 // update the graph model. 121 if (_update()) { 122 // Notify any graph listeners 123 // that the graph might have 124 // completely changed. 125 dispatchGraphEvent(new GraphEvent(this, 126 GraphEvent.STRUCTURE_CHANGED, getRoot())); 127 } 128 } 129 130 /** Notify the listener that the change has failed with the 131 * specified exception. 132 * @param change The change that has failed. 133 * @param exception The exception that was thrown. 134 */ 135 @Override 136 public void changeFailed(ChangeRequest change, Exception exception) { 137 // Report it if it has not been reported. 138 if (change == null) { 139 MessageHandler.error("Change failed", exception); 140 } else if (!change.isErrorReported()) { 141 change.setErrorReported(true); 142 MessageHandler.error("Change failed", exception); 143 } 144 145 // update the graph model. 146 if (_update()) { 147 dispatchGraphEvent(new GraphEvent(this, 148 GraphEvent.STRUCTURE_CHANGED, getRoot())); 149 } 150 } 151 152 /** Disconnect an edge from its two endpoints and notify graph 153 * listeners with an EDGE_HEAD_CHANGED and an EDGE_TAIL_CHANGED 154 * event whose source is the given source. 155 * @param eventSource The source of the event that will be dispatched, 156 * e.g. the view that made this call. 157 * @param edge The edge that is to be disconnected. 158 */ 159 public abstract void disconnectEdge(Object eventSource, Object edge); 160 161 /** Return a MoML String that will delete the given edge from the 162 * Ptolemy model. 163 * @param edge The edge that is to be disconnected. 164 * @return A valid MoML string. 165 */ 166 public abstract String getDeleteEdgeMoML(Object edge); 167 168 /** Return a MoML String that will delete the given node from the 169 * Ptolemy model. 170 * @param node The edge that is to be disconnected. 171 * @return A valid MoML string. 172 */ 173 public abstract String getDeleteNodeMoML(Object node); 174 175 /** Return the model for the given composite object. 176 * In this base class, return an instance of CompositePtolemyModel 177 * if the object is the root object of this graph model. 178 * Otherwise return null. 179 * @param composite A composite object. 180 * @return An instance of CompositePtolemyModel if the object is the root 181 * object of this graph model. Otherwise return null. 182 */ 183 @Override 184 public CompositeModel getCompositeModel(Object composite) { 185 if (composite != null && composite.equals(_composite)) { 186 return _compositeModel; 187 } else { 188 return null; 189 } 190 } 191 192 /** Return the node model for the given object. If the object is an 193 * attribute, then return an attribute model. Otherwise, return null. 194 * @param node An object which is assumed to be in this graph model. 195 * @return An instance of the inner class AttributeNodeModel if 196 * the object is an instance of Locatable whose container is an 197 * instance of Attribute, and otherwise, null. 198 */ 199 @Override 200 public NodeModel getNodeModel(Object node) { 201 if (node instanceof Locatable 202 && ((Locatable) node).getContainer() instanceof Attribute) { 203 return _attributeModel; 204 } 205 206 return null; 207 } 208 209 /** Return the property of the object associated with 210 * the given property name. In this implementation 211 * properties are stored in variables of the graph object (which is 212 * always a Ptolemy NamedObj). If no variable with the given name 213 * exists in the object, then return null. Otherwise retrieve the 214 * token from the variable. If the token is an instance of ObjectToken, 215 * then get the value from the token and return it. Otherwise, return 216 * the result of calling toString on the token. 217 * @param object The graph object, which is assumed to be an instance of 218 * NamedObj. 219 * @param propertyName The name of the new property. 220 * @return The property of the object associated with the given property 221 * name. 222 * @see #setProperty(Object, String, Object) 223 */ 224 @Override 225 public Object getProperty(Object object, String propertyName) { 226 try { 227 NamedObj namedObject = (NamedObj) object; 228 Attribute a = namedObject.getAttribute(propertyName); 229 Token t = ((Variable) a).getToken(); 230 231 if (t instanceof ObjectToken) { 232 return ((ObjectToken) t).getValue(); 233 } else { 234 return t.toString(); 235 } 236 } catch (Throwable throwable) { 237 return null; 238 } 239 } 240 241 /** Return the Ptolemy II model associated with this graph model. 242 * @return The Ptolemy II model. 243 */ 244 public NamedObj getPtolemyModel() { 245 return _composite; 246 } 247 248 /** Return the semantic object corresponding to the given node, edge, 249 * or composite. A "semantic object" is an object associated with 250 * a node in the graph. In this base class, if the argument is an 251 * instance of Port, then return the port. If the argument is an 252 * instance of Locatable, then return the container of the Locatable. 253 * @param element A graph element. 254 * @return The semantic object associated with this element, or null 255 * if the object is not recognized. 256 * @see #setSemanticObject(Object, Object) 257 */ 258 @Override 259 public Object getSemanticObject(Object element) { 260 if (element instanceof Port) { 261 return element; 262 } else if (element instanceof Locatable) { 263 return ((Locatable) element).getContainer(); 264 } 265 266 return null; 267 } 268 269 /** Return true if the given object is a 270 * node in this model, which in this case means 271 * that it is an instance of Locatable. 272 * @param object The object to test for being a node 273 * (vs. an edge). 274 * @return True if the given object is a node in this model. 275 */ 276 @Override 277 public boolean isNode(Object object) { 278 Object nodeModel = getNodeModel(object); 279 280 if (nodeModel != null) { 281 return true; 282 } 283 284 // If the node model is null, then this could 285 // be a Locatable with no container, which we will 286 // assume is a node. 287 if (object instanceof Locatable) { 288 NamedObj container = ((Locatable) object).getContainer(); 289 290 if (container == null) { 291 return true; 292 } 293 } 294 295 return false; 296 } 297 298 /** Delete a node from its parent graph and notify 299 * graph listeners with a NODE_REMOVED event. 300 * @param eventSource The source of the event that will be dispatched, 301 * e.g. the view that made this call. 302 * @param node The node to be removed. 303 */ 304 public abstract void removeNode(Object eventSource, Object node); 305 306 /** Set the property of the given graph object associated with 307 * the given property name to the given value. In this implementation 308 * properties are stored in variables of the graph object (which is 309 * always a Ptolemy NamedObj). If no variable with the given name exists 310 * in the graph object, then create a new variable contained 311 * by the graph object with the given name. 312 * If the value is a string, then set the expression of the variable 313 * to that string. Otherwise create a new object token contained the 314 * value and place that in the variable instead. 315 * The operation is performed in a ptolemy change request. 316 * @param object The graph object. 317 * @param propertyName The property name. 318 * @param value The new value of the property. 319 * @see #getProperty(Object, String) 320 */ 321 @Override 322 public void setProperty(final Object object, final String propertyName, 323 final Object value) { 324 throw new UnsupportedOperationException("hack"); 325 } 326 327 /** Set the semantic object corresponding to the given node, edge, 328 * or composite. The semantic objects in this graph model are 329 * fixed, so this method throws an UnsupportedOperationException. 330 * @param object The graph object that represents a node or an edge. 331 * @param semantic The semantic object to associate with the given 332 * graph object. 333 * @see #getSemanticObject(Object) 334 */ 335 @Override 336 public void setSemanticObject(Object object, Object semantic) { 337 throw new UnsupportedOperationException("Ptolemy Graph Model does" 338 + " not allow semantic objects" + " to be changed"); 339 } 340 341 /** Remove any listeners we have created. The frame displaying this 342 * graph model should call this function when the frame is closed. 343 */ 344 public void removeListeners() { 345 _composite.removeChangeListener(this); 346 } 347 348 /////////////////////////////////////////////////////////////////// 349 //// protected methods //// 350 351 /** Return the location attribute contained in the given object, or 352 * a new location contained in the given object if there was no location. 353 * @param object The object for which a location is needed. 354 * @return The location of the object, or a new location if none. 355 */ 356 protected Locatable _getLocation(NamedObj object) { 357 List<?> locations = object.attributeList(Locatable.class); 358 359 if (locations.size() > 0) { 360 return (Locatable) locations.get(0); 361 } else { 362 try { 363 // NOTE: We need the location right away, so we go ahead 364 // and create it. However, we also issue a MoMLChangeRequest 365 // so that the change propagates, and any models that defer 366 // to this one (e.g. subclasses) also have locations. 367 // This is necessary so that if the location later moves, 368 // then the move can be duplicated in the deferrers. 369 Location location = new Location(object, "_location"); 370 371 // Since this isn't delegated to the MoML parser, 372 // we have to handle propagation here. 373 location.propagateExistence(); 374 375 return location; 376 } catch (Exception e) { 377 throw new InternalErrorException("Failed to create " 378 + "location, even though one does not exist:" 379 + e.getMessage()); 380 } 381 } 382 } 383 384 /** Update the graph model. This is called whenever a change request is 385 * executed. This base class checks each of the contained nodes, and 386 * if any has a semantic object with no container, then that node 387 * is removed. Subclasses will override this to update internal data 388 * structures that may be cached. 389 * @return True if the graph model changes (always true in this 390 * base class). 391 */ 392 protected boolean _update() { 393 return true; 394 } 395 396 /////////////////////////////////////////////////////////////////// 397 //// private variables //// 398 // The node model for a visible attribute. 399 private AttributeNodeModel _attributeModel = new AttributeNodeModel(); 400 401 // The root of this graph model, as a CompositeEntity. 402 private NamedObj _composite; 403 404 // The model for composite entities. 405 private CompositePtolemyModel _compositeModel = new CompositePtolemyModel(); 406}