001/* A mutation request specified in MoML.
002
003 Copyright (c) 2000-2018 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 */
027package ptolemy.moml;
028
029import java.net.URL;
030import java.util.List;
031
032import ptolemy.kernel.InstantiableNamedObj;
033import ptolemy.kernel.undo.UndoStackAttribute;
034import ptolemy.kernel.util.ChangeRequest;
035import ptolemy.kernel.util.NamedObj;
036
037///////////////////////////////////////////////////////////////////
038//// MoMLChangeRequest
039
040/**
041 A mutation request specified in MoML.  This class provides the preferred
042 mechanism for implementing mutations on a model while it is executing.
043 To use it, create an instance of this class, specifying MoML code as
044 an argument to the constructor.  Then queue the instance of this class
045 with a composite entity by calling its requestChange() method.
046 <p>
047 If a context is given to the constructor, then the MoML will
048 be executed in that context.  If that context has other objects
049 that defer their MoML definitions to it (i.e., it is a class
050 definition and there are instances of the class), then the
051 MoML will also be executed in the context of those objects
052 that defer to it.  Thus, the change to a class will propagate
053 to instances.  If the context is (deeply) contained by another
054 object that has objects that defer their MoML definitions to
055 it, then the changes are also propagated to those objects.
056 Thus, even when class definitions are nested within class
057 definitions, a change within a class definition will
058 propagate to all instances of the class(es).
059 <p>
060 The parser used to implement the change will be the parser contained
061 by a ParserAttribute of the top-level element of the context.  If no
062 context is given, or there is no ParserAttribute in its top level,
063 then a new parser is created, and a new ParserAttribute is placed
064 in the top level.
065 <p>
066 Note that if a context is specified that is above a class
067 definition, and a change within the class definition is made
068 by referencing the contents of the class definition using dotted
069 names, then the change will not propagate. Thus, changes should be
070 made in the most specific context (lowest level in the hierarchy)
071 possible.
072
073 @author  Edward A. Lee
074 @version $Id$
075 @since Ptolemy II 1.0
076 @Pt.ProposedRating Yellow (eal)
077 @Pt.AcceptedRating Red (neuendor)
078 */
079public class MoMLChangeRequest extends ChangeRequest {
080    /** Construct a mutation request.
081     *  The originator is the source of the change request.
082     *  Since no context is given, a new parser will be used, and it
083     *  will create a new top level.
084     *  A listener to changes will probably want to check the originator
085     *  so that when it is notified of errors or successful completion
086     *  of changes, it can tell whether the change is one it requested.
087     *  Alternatively, it can call waitForCompletion().
088     *  All external references are assumed to be absolute URLs.  Whenever
089     *  possible, use a different constructor that specifies the base.
090     *  @param originator The originator of the change request.
091     *  @param request The mutation request in MoML.
092     */
093    public MoMLChangeRequest(Object originator, String request) {
094        this(originator, null, request, null);
095    }
096
097    /** Construct a mutation request to be executed in the specified context.
098     *  The context is typically a Ptolemy II container, such as an entity,
099     *  within which the objects specified by the MoML code will be placed.
100     *  This method resets and uses a parser that is a static member
101     *  of this class.
102     *  A listener to changes will probably want to check the originator
103     *  so that when it is notified of errors or successful completion
104     *  of changes, it can tell whether the change is one it requested.
105     *  Alternatively, it can call waitForCompletion().
106     *  All external references are assumed to be absolute URLs.  Whenever
107     *  possible, use a different constructor that specifies the base.
108     *  @param originator The originator of the change request.
109     *  @param context The context in which to execute the MoML.
110     *  @param request The mutation request in MoML.
111     */
112    public MoMLChangeRequest(Object originator, NamedObj context,
113            String request) {
114        this(originator, context, request, null);
115    }
116
117    /** Construct a mutation request to be executed in the specified context.
118     *  The context is typically a Ptolemy II container, such as an entity,
119     *  within which the objects specified by the MoML code will be placed.
120     *  If the top-level containing the specified context has a
121     *  ParserAttribute, then the parser associated with that attribute
122     *  is used.  Otherwise, a new parser is created, and set to be the
123     *  top-level parser.
124     *  A listener to changes will probably want to check the originator
125     *  so that when it is notified of errors or successful completion
126     *  of changes, it can tell whether the change is one it requested.
127     *  Alternatively, it can call waitForCompletion(), although there
128     *  is severe risk of deadlock when doing that.
129     *  @param originator The originator of the change request.
130     *  @param context The context in which to execute the MoML.
131     *  @param request The mutation request in MoML.
132     *  @param base The URL relative to which external references should
133     *   be resolved.
134     */
135    public MoMLChangeRequest(Object originator, NamedObj context,
136            String request, URL base) {
137        super(originator, request);
138        _context = context;
139        _base = base;
140    }
141
142    /** Construct a mutation request to be executed in the specified context.
143     *  The context is typically a Ptolemy II container, such as an entity,
144     *  within which the objects specified by the MoML code will be placed.
145     *  This method resets and uses a parser that is a static member
146     *  of this class.
147     *  This constructor also accepts a boolean argument to tell whether the
148     *  change is structural.  Non-structural changes do not require
149     *  repainting.
150     *  @param originator The originator of the change request.
151     *  @param context The context in which to execute the MoML.
152     *  @param request The mutation request in MoML.
153     *  @param structural Whether or not this is a structural change.
154     */
155    public MoMLChangeRequest(Object originator, NamedObj context,
156            String request, boolean structural) {
157        super(originator, request, structural);
158        _context = context;
159    }
160
161    ///////////////////////////////////////////////////////////////////
162    ////                         public methods                    ////
163
164    /** Return the context specified in the constructor, or null if none
165     *  was specified.
166     *  @return The context.
167     */
168    public NamedObj getContext() {
169        return _context;
170    }
171
172    /** Return the first container, moving up the hierarchy, for which there
173     *  are other objects that defer their MoML definitions to it.
174     *  If there is no such container, then return null. If the specified
175     *  object has other objects deferring to it, then return the specified
176     *  object.  NOTE: It used to be that the returned value of this method
177     *  was the recommended context to specify to a constructor. This is
178     *  no longer necessary.  Propagation is automatically taken care of
179     *  if the context is contained by a deferred-to parent. Thus, you
180     *  should give the most immediate container that makes sense for
181     *  the context.  It is harmless, however, to use this method to
182     *  get the context, so older code will work fine.
183     *  @param object The NamedObj to which other objects defer their MoML
184     *  definitions.
185     *  @return An object that deeply contains this one, or null.
186     *  @deprecated No longer needed; just use the specified object as
187     *  a context.
188
189     */
190    @Deprecated
191    public static NamedObj getDeferredToParent(NamedObj object) {
192        if (object == null) {
193            return null;
194        } else if (!(object instanceof InstantiableNamedObj)) {
195            return getDeferredToParent(object.getContainer());
196        } else {
197            List deferList = ((InstantiableNamedObj) object).getChildren();
198
199            if (deferList != null && deferList.size() > 0) {
200                return object;
201            } else {
202                return getDeferredToParent(object.getContainer());
203            }
204        }
205    }
206
207    /** Set whether or not this change is undoable.
208     *  @param undoable whether or not this change should be treated
209     *   as an incremental change that is undoable
210     */
211    public void setUndoable(boolean undoable) {
212        _undoable = undoable;
213    }
214
215    /** Set whether or not the undo from this change should be merged with
216     *  the previous undoable change.
217     *  @param mergeWithPrevious whether or not this change should be merged
218     */
219    public void setMergeWithPreviousUndo(boolean mergeWithPrevious) {
220        _mergeWithPreviousUndo = mergeWithPrevious;
221    }
222
223    /** Specify whether or not to report errors via the handler that
224     *  is registered with the parser. The initial default is to not
225     *  report errors to the registered handler. If this method is
226     *  called with a true argument, errors will be reported to the
227     *  registered handler. Note that in either case, if the handler
228     *  returns ErrorHandler.CANCEL, then exceptions will be reported
229     *  to any change listeners that are registered with this object
230     *  via their changeFailed() method.  If the handler returns
231     *  ErrorHandler.CONTINUE, then the exception will not be reported
232     *  to any change listeners and the change listener will think
233     *  that the change succeeded.
234     *
235     *  @see ErrorHandler
236     *  @param report False to disable error reporting.
237     */
238    public void setReportErrorsToHandler(boolean report) {
239        _reportToHandler = report;
240    }
241
242    ///////////////////////////////////////////////////////////////////
243    ////                         protected methods                 ////
244
245    /** Execute the change by evaluating the request and propagating
246     *  the request if appropriate.
247     *  @exception Exception If an exception is thrown
248     *   while evaluating the request.
249     */
250    @Override
251    protected void _execute() throws Exception {
252        // NOTE: To see what is being parsed, change _DEBUG to true.
253        if (_DEBUG) {
254            System.out.println("****** Executing MoML change:");
255            System.out.println(getDescription());
256
257            if (_context != null) {
258                System.out
259                        .println("------ in context " + _context.getFullName());
260            }
261        }
262
263        // Check to see whether there is a parser...
264        if (_context != null) {
265            _parser = ParserAttribute.getParser(_context);
266            _parser.reset();
267        }
268
269        if (_parser == null) {
270            // There is no previously associated parser (can only
271            // happen if _context is null).
272            _parser = new MoMLParser();
273        }
274
275        if (_context != null) {
276            _parser.setContext(_context);
277        }
278
279        // Tell the parser whether this change is undoable.
280        if (_undoable) {
281            _parser.setUndoable(true);
282        }
283
284        ErrorHandler handler = MoMLParser.getErrorHandler();
285
286        if (!_reportToHandler) {
287            MoMLParser.setErrorHandler(null);
288        }
289
290        _preParse(_parser);
291        try {
292            _parser.parse(_base, getDescription());
293        } finally {
294            if (!_reportToHandler) {
295                MoMLParser.setErrorHandler(handler);
296            }
297        }
298
299        // Merge the undo entry created if needed
300        if (_undoable && _mergeWithPreviousUndo) {
301            UndoStackAttribute undoInfo = UndoStackAttribute
302                    .getUndoInfo(_context);
303            undoInfo.mergeTopTwo();
304        }
305        _postParse(_parser);
306    }
307
308    /** Do nothing. This is a strategy pattern method that is called
309     *  by the _execute() method just after doing the parse.
310     *  Subclasses may override this.
311     *  @param parser The parser
312     */
313    protected void _postParse(MoMLParser parser) {
314    }
315
316    /** Do nothing. This is a strategy pattern method that is called
317     *  by the _execute() method just before doing the parse.
318     *  Subclasses may override this to do some setup of the parser.
319     *  @param parser The parser
320     */
321    protected void _preParse(MoMLParser parser) {
322    }
323
324    ///////////////////////////////////////////////////////////////////
325    ////                         private variables                 ////
326    // The URL relative to which external references should be resolved.
327    private URL _base;
328
329    // The context in which to execute the request.
330    private NamedObj _context;
331
332    // Flag to print out information about what's being done.
333    private static boolean _DEBUG = false;
334
335    // Indicates that the undo MoML from this change request should be merged
336    // in with the undo MoML from the previos undoable change request if they
337    // both have the same context.
338    private boolean _mergeWithPreviousUndo = false;
339
340    // The parser given in the constructor.
341    private MoMLParser _parser;
342
343    // Flag indicating whether to report to the handler registered
344    // with the parser.
345    private boolean _reportToHandler = false;
346
347    // Flag indicating if this change is undoable or not
348    private boolean _undoable = false;
349}