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