001/* Holds information about the current undo context 002 003 Copyright (c) 2000-2014 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 PT_COPYRIGHT_VERSION_2 024 COPYRIGHTENDKEY 025 026 */ 027package ptolemy.moml; 028 029import java.util.Stack; 030 031import ptolemy.kernel.util.IllegalActionException; 032import ptolemy.kernel.util.NamedObj; 033 034/////////////////////////////////////////////////////////////////// 035//// UndoContext 036 037/** 038 Holds information about the current undo context. It is used while parsing 039 an incremental model change to hold the following information: 040 <ul> 041 <li> Whether or not undo MoML should be generated for the current context. 042 <li> Whether or not undo MoML should be generated for any child nodes. 043 <li> The undo MoML to start the undo entry for this context. 044 <li> The undo MoML for any child elements, pushed onto a stack. 045 <li> The closing undo MoML for this context, if any. This is appended 046 after the undo MoML for the child nodes. 047 </ul> 048 <p> 049 At the end of an element, if the context is undoable, then the 050 undo MoML is generated by taking the main undo MoML StringBuffer, 051 appending the undo MoML for children in the reverse order to the 052 order they were parsed, and finally appending any closing undo MoML 053 that is present. 054 055 @author Neil Smyth 056 @version $Id$ 057 @since Ptolemy II 2.1 058 @Pt.ProposedRating Red (cxh) 059 @Pt.AcceptedRating Red (cxh) 060 */ 061public class UndoContext { 062 /////////////////////////////////////////////////////////////////// 063 //// Constructors //// 064 065 /** 066 * Create a new UndoContext which may or may not need undo MoML 067 * generated. 068 * 069 * @param undoableContext Whether or not undo MoML is required for this 070 * context 071 */ 072 public UndoContext(boolean undoableContext) { 073 _undoable = undoableContext; 074 _undoChildEntries = new Stack(); 075 _undoMoML = new StringBuffer(); 076 _closingUndoMoML = new StringBuffer(); 077 } 078 079 /////////////////////////////////////////////////////////////////// 080 //// public methods //// 081 082 /** Append some MoML to be appended after the undo MoML for child nodes. 083 * Note that these will appear in the reverse order in which this method 084 * is called, which makes sense since generally these are nested calls. 085 * @param undoMoML The MoMl to be appended after any child nodes 086 */ 087 public void appendClosingUndoMoML(String undoMoML) { 088 _closingUndoMoML.insert(0, undoMoML); 089 return; 090 } 091 092 /** 093 * Append some MoML to the current buffer. 094 * 095 * @param undoMoML The undo MoML to append to the current buffer. 096 */ 097 public void appendUndoMoML(String undoMoML) { 098 _undoMoML.append(undoMoML); 099 return; 100 } 101 102 /** 103 * Used to handle the "rename" element. Replace the value of the "name" 104 * attribute the given value. This is a bit of a hack as ideally a child 105 * context should not modify the parent context, but with rename that is 106 * exactly what is required. 107 * 108 * @param newName the value to give to the value of the name attribute. 109 * @exception IllegalActionException if there is currently no undo MoML at 110 * this level, or if there is no name attribute present. 111 */ 112 public void applyRename(String newName) throws IllegalActionException { 113 if (_undoMoML.length() == 0) { 114 // this should not happen 115 throw new IllegalActionException("Failed to create undo entry:\n" 116 + "Cannot rename an element whose parent " 117 + "undo context does not have any undo MoML. Requested " 118 + "new name: " + newName); 119 } 120 121 String undo = _undoMoML.toString(); 122 String marker = "name=\""; 123 int startIndex = undo.indexOf("name=\""); 124 125 if (startIndex == -1) { 126 // this should not happen 127 throw new IllegalActionException("Failed to create undo entry:\n" 128 + "Cannot rename an element whose parent " 129 + "undo context does not have a name attribute in its " 130 + "undo MoML. Requested new name: " + newName); 131 } 132 133 // Move the startIndex to after the marker 134 startIndex += marker.length(); 135 136 // Now get the end index 137 int endIndex = undo.indexOf("\"", startIndex); 138 139 if (endIndex == -1) { 140 // Also should not happen 141 // this should not happen 142 throw new IllegalActionException("Failed to create undo entry:\n" 143 + "Cannot rename an element whose parent " 144 + "undo context does not have a valid name attribute " 145 + "in its undo MoML. Requested new name: " + newName); 146 } 147 148 // Finally update the string buffer 149 _undoMoML.replace(startIndex, endIndex, newName); 150 } 151 152 /** 153 * Generate the undo entry by processing children entries and the 154 * closing undo MoML. First appends the contents of the children 155 * undo entry stack to this elements undo MoML, followed by any 156 * closing MoML. Note that child elements have their undo MoML 157 * appended in reverse order to that in which they were parsed. 158 * 159 * @return the generated undo entry. 160 */ 161 public String generateUndoEntry() { 162 // First append the undo MoML for the children 163 while (!_undoChildEntries.isEmpty()) { 164 _undoMoML.append((String) _undoChildEntries.pop()); 165 } 166 167 // Next append any closing MoML 168 _undoMoML.append(_closingUndoMoML.toString()); 169 170 // Return the result 171 return _undoMoML.toString(); 172 } 173 174 /** 175 * Get the undo MoML for this element as it currently stands. Note that 176 * this method does not append the MoML for children or any closing MoML 177 * that has been set. 178 * 179 * @return The UndoMoML value. 180 */ 181 public String getUndoMoML() { 182 return _undoMoML.toString(); 183 } 184 185 /** 186 * Return whether or not this context has any undo MoML to be processed. 187 * 188 * @return true if this context has any undo MoML to be processed. 189 */ 190 public boolean hasUndoMoML() { 191 return _undoMoML.length() > 0; 192 } 193 194 /** 195 * Return whether or not child nodes need to generate MoML. 196 * 197 * @return true if this context has undoable children. 198 */ 199 public boolean hasUndoableChildren() { 200 return _childrenUndoable; 201 } 202 203 /** 204 * Tells if the current context is undoable or not. 205 * 206 * @return true if this context is undoable. 207 */ 208 public boolean isUndoable() { 209 return _undoable; 210 } 211 212 /** Return the closing element corresponding to the starting element 213 * returned by moveContextStart(), or an empty string if none is 214 * needed. 215 * <p> 216 * For example, if the containee is not already immediately 217 * contained, and the container is an entity, the </entity> 218 * is appended to the model. 219 * @param context The current context. 220 * @param containee The containee whose immediate context we want. 221 * @return The MoML that closes the MoML returned by moveContextStart(), 222 * or an empty string if none is needed. 223 * @see #moveContextStart(NamedObj, NamedObj) 224 */ 225 public static String moveContextEnd(NamedObj context, NamedObj containee) { 226 if (moveContextStart(context, containee).equals("")) { 227 return ""; 228 } 229 230 // If we get to here, then containee and its 231 // container cannot be null. 232 NamedObj container = containee.getContainer(); 233 return "</" + container.getElementName() + ">\n"; 234 } 235 236 /** Return the MoML start element to put us in the 237 * context of the immediate container of the containee, 238 * assuming the current context is as given by the 239 * <i>context</i> argument. Return an empty string if the 240 * specified context is the immediate container of the 241 * specified containee. 242 * <p> 243 * For example, if the context has full name ".top" and the 244 * containee has full name ".top.a.b.c.d", then MoML to move 245 * down the model such as the following is returned: 246 * <entity name="a.b.c" > 247 * @param context The current context. 248 * @param containee The containee whose immediate context we want. 249 * @return The MoML to put us in the right context from the current 250 * context, or an empty string if we are already in that context 251 * or if either argument is null. 252 * @see #moveContextEnd(NamedObj, NamedObj) 253 */ 254 public static String moveContextStart(NamedObj context, 255 NamedObj containee) { 256 if (context == null || containee == null) { 257 return ""; 258 } 259 260 NamedObj container = containee.getContainer(); 261 262 if (container == null || container == context) { 263 return ""; 264 } 265 266 String entityContext = container.getName(context); 267 String elemName = container.getElementName(); 268 return "<" + elemName + " name=\"" + entityContext + "\" >\n"; 269 } 270 271 /** 272 * Push the passed in MoML onto the stack of element undo entries. Note 273 * that undo entries are pushed onto a stack so that at the end of the 274 * element they can be aggregated to form the full undo MoML - in the 275 * reverse order to that in which the elements originally appeared. 276 * 277 * @param entry Description of Parameter 278 */ 279 public void pushUndoEntry(String entry) { 280 // FIXME: Should we set _childrenUndoable = true here? 281 // If we push an undo child entry, then is it always undoable? 282 _undoChildEntries.push(entry); 283 } 284 285 /** 286 * Set whether or not the child contexts are undoable. 287 * 288 * @param isUndoable the new state 289 */ 290 public void setChildrenUndoable(boolean isUndoable) { 291 _childrenUndoable = isUndoable; 292 } 293 294 /** 295 * Set whether or not the current context is undoable. 296 * 297 * @param isUndoable the new state 298 */ 299 public void setUndoable(boolean isUndoable) { 300 _undoable = isUndoable; 301 } 302 303 /** Return a string representation of this object. */ 304 @Override 305 public String toString() { 306 return "UndoContext: " + (isUndoable() ? "are" : "are not") 307 + " undoable and " 308 + (hasUndoableChildren() ? "has" : "does not have") 309 + " undoable children\n" + "undoMoML: " + getUndoMoML() + "\n" 310 + "closingUndoMoML: " + _closingUndoMoML.toString() + "\n"; 311 } 312 313 /////////////////////////////////////////////////////////////////// 314 //// private members //// 315 // Flag indicating if child elements should be undoable 316 private boolean _childrenUndoable; 317 318 // Whether or not this level is undoable 319 private boolean _undoable; 320 321 // Holds the currently generated undoable MoML for this level. Note the 322 // MoML is generated in reverse element order for any given context. 323 private StringBuffer _undoMoML; 324 325 // Holds the undo MoML that is to be appended after the MoML for 326 // any child nodes 327 private StringBuffer _closingUndoMoML; 328 329 // Holds the stack of MoML entries, one for each element for 330 // which undo MoML was generated 331 private Stack _undoChildEntries; 332}