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}