1 /*
   2  * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package javax.swing.undo;
  27 
  28 import javax.swing.event.*;
  29 import javax.swing.UIManager;
  30 import java.util.*;
  31 import sun.swing.text.UndoableEditLockSupport;
  32 
  33 /**
  34  * {@code UndoManager} manages a list of {@code UndoableEdits},
  35  * providing a way to undo or redo the appropriate edits.  There are
  36  * two ways to add edits to an <code>UndoManager</code>.  Add the edit
  37  * directly using the <code>addEdit</code> method, or add the
  38  * <code>UndoManager</code> to a bean that supports
  39  * <code>UndoableEditListener</code>.  The following examples creates
  40  * an <code>UndoManager</code> and adds it as an
  41  * <code>UndoableEditListener</code> to a <code>JTextField</code>:
  42  * <pre>
  43  *   UndoManager undoManager = new UndoManager();
  44  *   JTextField tf = ...;
  45  *   tf.getDocument().addUndoableEditListener(undoManager);
  46  * </pre>
  47  * <p>
  48  * <code>UndoManager</code> maintains an ordered list of edits and the
  49  * index of the next edit in that list. The index of the next edit is
  50  * either the size of the current list of edits, or if
  51  * <code>undo</code> has been invoked it corresponds to the index
  52  * of the last significant edit that was undone. When
  53  * <code>undo</code> is invoked all edits from the index of the next
  54  * edit to the last significant edit are undone, in reverse order.
  55  * For example, consider an <code>UndoManager</code> consisting of the
  56  * following edits: <b>A</b> <i>b</i> <i>c</i> <b>D</b>.  Edits with a
  57  * upper-case letter in bold are significant, those in lower-case
  58  * and italicized are insignificant.
  59  * <p>
  60  * <a id="figure1"></a>
  61  * <table class="borderless">
  62  * <caption style="display:none">Figure 1</caption>
  63  * <tr><td>
  64  *     <img src="doc-files/UndoManager-1.gif" alt="">
  65  * <tr><td style="text-align:center">Figure 1
  66  * </table>
  67  * <p>
  68  * As shown in <a href="#figure1">figure 1</a>, if <b>D</b> was just added, the
  69  * index of the next edit will be 4. Invoking <code>undo</code>
  70  * results in invoking <code>undo</code> on <b>D</b> and setting the
  71  * index of the next edit to 3 (edit <i>c</i>), as shown in the following
  72  * figure.
  73  * <p>
  74  * <a id="figure2"></a>
  75  * <table class="borderless">
  76  * <caption style="display:none">Figure 2</caption>
  77  * <tr><td>
  78  *     <img src="doc-files/UndoManager-2.gif" alt="">
  79  * <tr><td style="text-align:center">Figure 2
  80  * </table>
  81  * <p>
  82  * The last significant edit is <b>A</b>, so that invoking
  83  * <code>undo</code> again invokes <code>undo</code> on <i>c</i>,
  84  * <i>b</i>, and <b>A</b>, in that order, setting the index of the
  85  * next edit to 0, as shown in the following figure.
  86  * <p>
  87  * <a id="figure3"></a>
  88  * <table class="borderless">
  89  * <caption style="display:none">Figure 3</caption>
  90  * <tr><td>
  91  *     <img src="doc-files/UndoManager-3.gif" alt="">
  92  * <tr><td style="text-align:center">Figure 3
  93  * </table>
  94  * <p>
  95  * Invoking <code>redo</code> results in invoking <code>redo</code> on
  96  * all edits between the index of the next edit and the next
  97  * significant edit (or the end of the list).  Continuing with the previous
  98  * example if <code>redo</code> were invoked, <code>redo</code> would in
  99  * turn be invoked on <b>A</b>, <i>b</i> and <i>c</i>.  In addition
 100  * the index of the next edit is set to 3 (as shown in <a
 101  * href="#figure2">figure 2</a>).
 102  * <p>
 103  * Adding an edit to an <code>UndoManager</code> results in
 104  * removing all edits from the index of the next edit to the end of
 105  * the list.  Continuing with the previous example, if a new edit,
 106  * <i>e</i>, is added the edit <b>D</b> is removed from the list
 107  * (after having <code>die</code> invoked on it).  If <i>c</i> is not
 108  * incorporated by the next edit
 109  * (<code><i>c</i>.addEdit(<i>e</i>)</code> returns true), or replaced
 110  * by it (<code><i>e</i>.replaceEdit(<i>c</i>)</code> returns true),
 111  * the new edit is added after <i>c</i>, as shown in the following
 112  * figure.
 113  * <p>
 114  * <a id="figure4"></a>
 115  * <table class="borderless">
 116  * <caption style="display:none">Figure 4</caption>
 117  * <tr><td>
 118  *     <img src="doc-files/UndoManager-4.gif" alt="">
 119  * <tr><td style="text-align:center">Figure 4
 120  * </table>
 121  * <p>
 122  * Once <code>end</code> has been invoked on an <code>UndoManager</code>
 123  * the superclass behavior is used for all <code>UndoableEdit</code>
 124  * methods.  Refer to <code>CompoundEdit</code> for more details on its
 125  * behavior.
 126  * <p>
 127  * Unlike the rest of Swing, this class is thread safe.
 128  * <p>
 129  * <strong>Warning:</strong>
 130  * Serialized objects of this class will not be compatible with
 131  * future Swing releases. The current serialization support is
 132  * appropriate for short term storage or RMI between applications running
 133  * the same version of Swing.  As of 1.4, support for long term storage
 134  * of all JavaBeans&trade;
 135  * has been added to the <code>java.beans</code> package.
 136  * Please see {@link java.beans.XMLEncoder}.
 137  *
 138  * @author Ray Ryan
 139  */
 140 @SuppressWarnings("serial") // Same-version serialization only
 141 public class UndoManager extends CompoundEdit implements UndoableEditListener {
 142     private enum Action {
 143         UNDO,
 144         REDO,
 145         ANY
 146     }
 147     int indexOfNextAdd;
 148     int limit;
 149 
 150     /**
 151      * Creates a new <code>UndoManager</code>.
 152      */
 153     public UndoManager() {
 154         super();
 155         indexOfNextAdd = 0;
 156         limit = 100;
 157         edits.ensureCapacity(limit);
 158     }
 159 
 160     /**
 161      * Returns the maximum number of edits this {@code UndoManager}
 162      * holds. A value less than 0 indicates the number of edits is not
 163      * limited.
 164      *
 165      * @return the maximum number of edits this {@code UndoManager} holds
 166      * @see #addEdit
 167      * @see #setLimit
 168      */
 169     public synchronized int getLimit() {
 170         return limit;
 171     }
 172 
 173     /**
 174      * Empties the undo manager sending each edit a <code>die</code> message
 175      * in the process.
 176      *
 177      * @see AbstractUndoableEdit#die
 178      */
 179     public synchronized void discardAllEdits() {
 180         for (UndoableEdit e : edits) {
 181             e.die();
 182         }
 183         edits = new Vector<UndoableEdit>();
 184         indexOfNextAdd = 0;
 185         // PENDING(rjrjr) when vector grows a removeRange() method
 186         // (expected in JDK 1.2), trimEdits() will be nice and
 187         // efficient, and this method can call that instead.
 188     }
 189 
 190     /**
 191      * Reduces the number of queued edits to a range of size limit,
 192      * centered on the index of the next edit.
 193      */
 194     protected void trimForLimit() {
 195         if (limit >= 0) {
 196             int size = edits.size();
 197 //          System.out.print("limit: " + limit +
 198 //                           " size: " + size +
 199 //                           " indexOfNextAdd: " + indexOfNextAdd +
 200 //                           "\n");
 201 
 202             if (size > limit) {
 203                 int halfLimit = limit/2;
 204                 int keepFrom = indexOfNextAdd - 1 - halfLimit;
 205                 int keepTo   = indexOfNextAdd - 1 + halfLimit;
 206 
 207                 // These are ints we're playing with, so dividing by two
 208                 // rounds down for odd numbers, so make sure the limit was
 209                 // honored properly. Note that the keep range is
 210                 // inclusive.
 211 
 212                 if (keepTo - keepFrom + 1 > limit) {
 213                     keepFrom++;
 214                 }
 215 
 216                 // The keep range is centered on indexOfNextAdd,
 217                 // but odds are good that the actual edits Vector
 218                 // isn't. Move the keep range to keep it legal.
 219 
 220                 if (keepFrom < 0) {
 221                     keepTo -= keepFrom;
 222                     keepFrom = 0;
 223                 }
 224                 if (keepTo >= size) {
 225                     int delta = size - keepTo - 1;
 226                     keepTo += delta;
 227                     keepFrom += delta;
 228                 }
 229 
 230 //              System.out.println("Keeping " + keepFrom + " " + keepTo);
 231                 trimEdits(keepTo+1, size-1);
 232                 trimEdits(0, keepFrom-1);
 233             }
 234         }
 235     }
 236 
 237     /**
 238      * Removes edits in the specified range.
 239      * All edits in the given range (inclusive, and in reverse order)
 240      * will have <code>die</code> invoked on them and are removed from
 241      * the list of edits. This has no effect if
 242      * <code>from</code> &gt; <code>to</code>.
 243      *
 244      * @param from the minimum index to remove
 245      * @param to the maximum index to remove
 246      */
 247     protected void trimEdits(int from, int to) {
 248         if (from <= to) {
 249 //          System.out.println("Trimming " + from + " " + to + " with index " +
 250 //                           indexOfNextAdd);
 251             for (int i = to; from <= i; i--) {
 252                 UndoableEdit e = edits.elementAt(i);
 253 //              System.out.println("JUM: Discarding " +
 254 //                                 e.getUndoPresentationName());
 255                 e.die();
 256                 // PENDING(rjrjr) when Vector supports range deletion (JDK
 257                 // 1.2) , we can optimize the next line considerably.
 258                 edits.removeElementAt(i);
 259             }
 260 
 261             if (indexOfNextAdd > to) {
 262 //              System.out.print("...right...");
 263                 indexOfNextAdd -= to-from+1;
 264             } else if (indexOfNextAdd >= from) {
 265 //              System.out.println("...mid...");
 266                 indexOfNextAdd = from;
 267             }
 268 
 269 //          System.out.println("new index " + indexOfNextAdd);
 270         }
 271     }
 272 
 273     /**
 274      * Sets the maximum number of edits this <code>UndoManager</code>
 275      * holds. A value less than 0 indicates the number of edits is not
 276      * limited. If edits need to be discarded to shrink the limit,
 277      * <code>die</code> will be invoked on them in the reverse
 278      * order they were added.  The default is 100.
 279      *
 280      * @param l the new limit
 281      * @throws RuntimeException if this {@code UndoManager} is not in progress
 282      *                          ({@code end} has been invoked)
 283      * @see #isInProgress
 284      * @see #end
 285      * @see #addEdit
 286      * @see #getLimit
 287      */
 288     public synchronized void setLimit(int l) {
 289         if (!inProgress) throw new RuntimeException("Attempt to call UndoManager.setLimit() after UndoManager.end() has been called");
 290         limit = l;
 291         trimForLimit();
 292     }
 293 
 294 
 295     /**
 296      * Returns the next significant edit to be undone if <code>undo</code>
 297      * is invoked. This returns <code>null</code> if there are no edits
 298      * to be undone.
 299      *
 300      * @return the next significant edit to be undone
 301      */
 302     protected UndoableEdit editToBeUndone() {
 303         int i = indexOfNextAdd;
 304         while (i > 0) {
 305             UndoableEdit edit = edits.elementAt(--i);
 306             if (edit.isSignificant()) {
 307                 return edit;
 308             }
 309         }
 310 
 311         return null;
 312     }
 313 
 314     /**
 315      * Returns the next significant edit to be redone if <code>redo</code>
 316      * is invoked. This returns <code>null</code> if there are no edits
 317      * to be redone.
 318      *
 319      * @return the next significant edit to be redone
 320      */
 321     protected UndoableEdit editToBeRedone() {
 322         int count = edits.size();
 323         int i = indexOfNextAdd;
 324 
 325         while (i < count) {
 326             UndoableEdit edit = edits.elementAt(i++);
 327             if (edit.isSignificant()) {
 328                 return edit;
 329             }
 330         }
 331 
 332         return null;
 333     }
 334 
 335     /**
 336      * Undoes all changes from the index of the next edit to
 337      * <code>edit</code>, updating the index of the next edit appropriately.
 338      *
 339      * @param edit the edit to be undo to
 340      * @throws CannotUndoException if one of the edits throws
 341      *         <code>CannotUndoException</code>
 342      */
 343     protected void undoTo(UndoableEdit edit) throws CannotUndoException {
 344         boolean done = false;
 345         while (!done) {
 346             UndoableEdit next = edits.elementAt(--indexOfNextAdd);
 347             next.undo();
 348             done = next == edit;
 349         }
 350     }
 351 
 352     /**
 353      * Redoes all changes from the index of the next edit to
 354      * <code>edit</code>, updating the index of the next edit appropriately.
 355      *
 356      * @param edit the edit to be redo to
 357      * @throws CannotRedoException if one of the edits throws
 358      *         <code>CannotRedoException</code>
 359      */
 360     protected void redoTo(UndoableEdit edit) throws CannotRedoException {
 361         boolean done = false;
 362         while (!done) {
 363             UndoableEdit next = edits.elementAt(indexOfNextAdd++);
 364             next.redo();
 365             done = next == edit;
 366         }
 367     }
 368 
 369     /**
 370      * Convenience method that invokes one of <code>undo</code> or
 371      * <code>redo</code>. If any edits have been undone (the index of
 372      * the next edit is less than the length of the edits list) this
 373      * invokes <code>redo</code>, otherwise it invokes <code>undo</code>.
 374      *
 375      * @see #canUndoOrRedo
 376      * @see #getUndoOrRedoPresentationName
 377      * @throws CannotUndoException if one of the edits throws
 378      *         <code>CannotUndoException</code>
 379      * @throws CannotRedoException if one of the edits throws
 380      *         <code>CannotRedoException</code>
 381      */
 382     public void undoOrRedo() throws CannotRedoException, CannotUndoException {
 383         tryUndoOrRedo(Action.ANY);
 384     }
 385 
 386     /**
 387      * Returns true if it is possible to invoke <code>undo</code> or
 388      * <code>redo</code>.
 389      *
 390      * @return true if invoking <code>canUndoOrRedo</code> is valid
 391      * @see #undoOrRedo
 392      */
 393     public synchronized boolean canUndoOrRedo() {
 394         if (indexOfNextAdd == edits.size()) {
 395             return canUndo();
 396         } else {
 397             return canRedo();
 398         }
 399     }
 400 
 401     /**
 402      * Undoes the appropriate edits.  If <code>end</code> has been
 403      * invoked this calls through to the superclass, otherwise
 404      * this invokes <code>undo</code> on all edits between the
 405      * index of the next edit and the last significant edit, updating
 406      * the index of the next edit appropriately.
 407      *
 408      * @throws CannotUndoException if one of the edits throws
 409      *         <code>CannotUndoException</code> or there are no edits
 410      *         to be undone
 411      * @see CompoundEdit#end
 412      * @see #canUndo
 413      * @see #editToBeUndone
 414      */
 415     public void undo() throws CannotUndoException {
 416         tryUndoOrRedo(Action.UNDO);
 417     }
 418 
 419     /**
 420      * Returns true if edits may be undone.  If <code>end</code> has
 421      * been invoked, this returns the value from super.  Otherwise
 422      * this returns true if there are any edits to be undone
 423      * (<code>editToBeUndone</code> returns non-<code>null</code>).
 424      *
 425      * @return true if there are edits to be undone
 426      * @see CompoundEdit#canUndo
 427      * @see #editToBeUndone
 428      */
 429     public synchronized boolean canUndo() {
 430         if (inProgress) {
 431             UndoableEdit edit = editToBeUndone();
 432             return edit != null && edit.canUndo();
 433         } else {
 434             return super.canUndo();
 435         }
 436     }
 437 
 438     /**
 439      * Redoes the appropriate edits.  If <code>end</code> has been
 440      * invoked this calls through to the superclass.  Otherwise
 441      * this invokes <code>redo</code> on all edits between the
 442      * index of the next edit and the next significant edit, updating
 443      * the index of the next edit appropriately.
 444      *
 445      * @throws CannotRedoException if one of the edits throws
 446      *         <code>CannotRedoException</code> or there are no edits
 447      *         to be redone
 448      * @see CompoundEdit#end
 449      * @see #canRedo
 450      * @see #editToBeRedone
 451      */
 452     public void redo() throws CannotRedoException {
 453         tryUndoOrRedo(Action.REDO);
 454     }
 455 
 456     private void tryUndoOrRedo(Action action) {
 457         UndoableEditLockSupport lockSupport = null;
 458         boolean undo;
 459         synchronized (this) {
 460             if (action == Action.ANY) {
 461                 undo = indexOfNextAdd == edits.size();
 462             } else {
 463                 undo = action == Action.UNDO;
 464             }
 465             if (inProgress) {
 466                 UndoableEdit edit = undo ? editToBeUndone() : editToBeRedone();
 467                 if (edit == null) {
 468                     throw undo ? new CannotUndoException() :
 469                             new CannotRedoException();
 470                 }
 471                 lockSupport = getEditLockSupport(edit);
 472                 if (lockSupport == null) {
 473                     if (undo) {
 474                         undoTo(edit);
 475                     } else {
 476                         redoTo(edit);
 477                     }
 478                     return;
 479                 }
 480             } else {
 481                 if (undo) {
 482                     super.undo();
 483                 } else {
 484                     super.redo();
 485                 }
 486                 return;
 487             }
 488         }
 489         // the edit synchronization is required
 490         while (true) {
 491             lockSupport.lockEdit();
 492             UndoableEditLockSupport editLockSupport = null;
 493             try {
 494                 synchronized (this) {
 495                     if (action == Action.ANY) {
 496                         undo = indexOfNextAdd == edits.size();
 497                     }
 498                     if (inProgress) {
 499                         UndoableEdit edit = undo ? editToBeUndone() :
 500                                 editToBeRedone();
 501                         if (edit == null) {
 502                             throw undo ? new CannotUndoException() :
 503                                     new CannotRedoException();
 504                         }
 505                         editLockSupport = getEditLockSupport(edit);
 506                         if (editLockSupport == null ||
 507                                 editLockSupport == lockSupport) {
 508                             if (undo) {
 509                                 undoTo(edit);
 510                             } else {
 511                                 redoTo(edit);
 512                             }
 513                             return;
 514                         }
 515                     } else {
 516                         if (undo) {
 517                             super.undo();
 518                         } else {
 519                             super.redo();
 520                         }
 521                         return;
 522                     }
 523                 }
 524             } finally {
 525                 if (lockSupport != null) {
 526                     lockSupport.unlockEdit();
 527                 }
 528                 lockSupport = editLockSupport;
 529             }
 530         }
 531     }
 532 
 533     private UndoableEditLockSupport getEditLockSupport(UndoableEdit anEdit) {
 534         return anEdit instanceof UndoableEditLockSupport ?
 535                 (UndoableEditLockSupport)anEdit : null;
 536     }
 537 
 538     /**
 539      * Returns true if edits may be redone.  If <code>end</code> has
 540      * been invoked, this returns the value from super.  Otherwise,
 541      * this returns true if there are any edits to be redone
 542      * (<code>editToBeRedone</code> returns non-<code>null</code>).
 543      *
 544      * @return true if there are edits to be redone
 545      * @see CompoundEdit#canRedo
 546      * @see #editToBeRedone
 547      */
 548     public synchronized boolean canRedo() {
 549         if (inProgress) {
 550             UndoableEdit edit = editToBeRedone();
 551             return edit != null && edit.canRedo();
 552         } else {
 553             return super.canRedo();
 554         }
 555     }
 556 
 557     /**
 558      * Adds an <code>UndoableEdit</code> to this
 559      * <code>UndoManager</code>, if it's possible.  This removes all
 560      * edits from the index of the next edit to the end of the edits
 561      * list.  If <code>end</code> has been invoked the edit is not added
 562      * and <code>false</code> is returned.  If <code>end</code> hasn't
 563      * been invoked this returns <code>true</code>.
 564      *
 565      * @param anEdit the edit to be added
 566      * @return true if <code>anEdit</code> can be incorporated into this
 567      *              edit
 568      * @see CompoundEdit#end
 569      * @see CompoundEdit#addEdit
 570      */
 571     public synchronized boolean addEdit(UndoableEdit anEdit) {
 572         boolean retVal;
 573 
 574         // Trim from the indexOfNextAdd to the end, as we'll
 575         // never reach these edits once the new one is added.
 576         trimEdits(indexOfNextAdd, edits.size()-1);
 577 
 578         retVal = super.addEdit(anEdit);
 579         if (inProgress) {
 580           retVal = true;
 581         }
 582 
 583         // Maybe super added this edit, maybe it didn't (perhaps
 584         // an in progress compound edit took it instead. Or perhaps
 585         // this UndoManager is no longer in progress). So make sure
 586         // the indexOfNextAdd is pointed at the right place.
 587         indexOfNextAdd = edits.size();
 588 
 589         // Enforce the limit
 590         trimForLimit();
 591 
 592         return retVal;
 593     }
 594 
 595 
 596     /**
 597      * Turns this <code>UndoManager</code> into a normal
 598      * <code>CompoundEdit</code>.  This removes all edits that have
 599      * been undone.
 600      *
 601      * @see CompoundEdit#end
 602      */
 603     public synchronized void end() {
 604         super.end();
 605         this.trimEdits(indexOfNextAdd, edits.size()-1);
 606     }
 607 
 608     /**
 609      * Convenience method that returns either
 610      * <code>getUndoPresentationName</code> or
 611      * <code>getRedoPresentationName</code>.  If the index of the next
 612      * edit equals the size of the edits list,
 613      * <code>getUndoPresentationName</code> is returned, otherwise
 614      * <code>getRedoPresentationName</code> is returned.
 615      *
 616      * @return undo or redo name
 617      */
 618     public synchronized String getUndoOrRedoPresentationName() {
 619         if (indexOfNextAdd == edits.size()) {
 620             return getUndoPresentationName();
 621         } else {
 622             return getRedoPresentationName();
 623         }
 624     }
 625 
 626     /**
 627      * Returns a description of the undoable form of this edit.
 628      * If <code>end</code> has been invoked this calls into super.
 629      * Otherwise if there are edits to be undone, this returns
 630      * the value from the next significant edit that will be undone.
 631      * If there are no edits to be undone and <code>end</code> has not
 632      * been invoked this returns the value from the <code>UIManager</code>
 633      * property "AbstractUndoableEdit.undoText".
 634      *
 635      * @return a description of the undoable form of this edit
 636      * @see     #undo
 637      * @see     CompoundEdit#getUndoPresentationName
 638      */
 639     public synchronized String getUndoPresentationName() {
 640         if (inProgress) {
 641             if (canUndo()) {
 642                 return editToBeUndone().getUndoPresentationName();
 643             } else {
 644                 return UIManager.getString("AbstractUndoableEdit.undoText");
 645             }
 646         } else {
 647             return super.getUndoPresentationName();
 648         }
 649     }
 650 
 651     /**
 652      * Returns a description of the redoable form of this edit.
 653      * If <code>end</code> has been invoked this calls into super.
 654      * Otherwise if there are edits to be redone, this returns
 655      * the value from the next significant edit that will be redone.
 656      * If there are no edits to be redone and <code>end</code> has not
 657      * been invoked this returns the value from the <code>UIManager</code>
 658      * property "AbstractUndoableEdit.redoText".
 659      *
 660      * @return a description of the redoable form of this edit
 661      * @see     #redo
 662      * @see     CompoundEdit#getRedoPresentationName
 663      */
 664     public synchronized String getRedoPresentationName() {
 665         if (inProgress) {
 666             if (canRedo()) {
 667                 return editToBeRedone().getRedoPresentationName();
 668             } else {
 669                 return UIManager.getString("AbstractUndoableEdit.redoText");
 670             }
 671         } else {
 672             return super.getRedoPresentationName();
 673         }
 674     }
 675 
 676     /**
 677      * An <code>UndoableEditListener</code> method. This invokes
 678      * <code>addEdit</code> with <code>e.getEdit()</code>.
 679      *
 680      * @param e the <code>UndoableEditEvent</code> the
 681      *        <code>UndoableEditEvent</code> will be added from
 682      * @see #addEdit
 683      */
 684     public void undoableEditHappened(UndoableEditEvent e) {
 685         addEdit(e.getEdit());
 686     }
 687 
 688     /**
 689      * Returns a string that displays and identifies this
 690      * object's properties.
 691      *
 692      * @return a String representation of this object
 693      */
 694     public String toString() {
 695         return super.toString() + " limit: " + limit +
 696             " indexOfNextAdd: " + indexOfNextAdd;
 697     }
 698 }