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 &lt;/entity&gt;
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     *  &lt;entity name="a.b.c" &gt;
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}