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}