Undo/Redo可能なJTextArea

JTextAreaにはUndo/Redoの機能はありません。
けど、比較的簡単に実装することができます。

package util;

import java.awt.Event;
import java.awt.event.ActionEvent;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JTextArea;
import javax.swing.KeyStroke;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.AbstractDocument.DefaultDocumentEvent;
import javax.swing.undo.UndoManager;

/**
 * Undo/Redo可能なJTextArea。
 * @deprecated	UndoHelperを使ってください。
 * @author a-san
 */
public class UndoableTextArea extends JTextArea {
    public static final String ACTION_KEY_UNDO = "undo";
    public static final String ACTION_KEY_REDO = "redo";
    UndoManager undoManager = new UndoManager();
    
    /** JTextAreaにUndo/Redo機能をつけます。 */
    public UndoableTextArea() {
        ActionMap amap = this.getActionMap();
        InputMap imap = this.getInputMap();
        if (amap.get(ACTION_KEY_UNDO) == null) {
            UndoAction undoAction = new UndoAction();
            amap.put(ACTION_KEY_UNDO, undoAction);
            imap.put((KeyStroke) undoAction.getValue(Action.ACCELERATOR_KEY), ACTION_KEY_UNDO);
        }
        if (amap.get(ACTION_KEY_REDO) == null) {
            RedoAction redoAction = new RedoAction();
            amap.put(ACTION_KEY_REDO, redoAction);
            imap.put((KeyStroke) redoAction.getValue(Action.ACCELERATOR_KEY), ACTION_KEY_REDO);
        }
        // リスナを登録
        this.getDocument().addDocumentListener(new DocListener());
    }

    /**
     * 元に戻す
     */
    class UndoAction extends AbstractAction {
        UndoAction() {
            super("元に戻す(U)");
            putValue(MNEMONIC_KEY, new Integer('U'));
            putValue(SHORT_DESCRIPTION, "元に戻す");
            putValue(LONG_DESCRIPTION, "元に戻す");
            putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke('Z', Event.CTRL_MASK));
            putValue(SMALL_ICON, SwingUtil.getImageIcon("/resources/EditUndo.png"));
        }
        public void actionPerformed(ActionEvent e) {
            if (undoManager.canUndo()) {
                undoManager.undo();
            }
        }
    }

    /**
     * やり直し
     */
    class RedoAction extends AbstractAction {
        RedoAction() {
            super("やり直し(R)");
            putValue(MNEMONIC_KEY, new Integer('R'));
            putValue(SHORT_DESCRIPTION, "やり直し");
            putValue(LONG_DESCRIPTION, "やり直し");
            putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke('Y', Event.CTRL_MASK));
            putValue(SMALL_ICON, SwingUtil.getImageIcon("/resources/EditRedo.png"));
        }
        public void actionPerformed(ActionEvent e) {
            if (undoManager.canRedo()) {
                undoManager.redo();
            }
        }
    }
    /** ドキュメントが変更されたときのリスナー. */
    private class DocListener implements DocumentListener {
        public void insertUpdate(DocumentEvent e) {
            if (e instanceof DefaultDocumentEvent) {
                DefaultDocumentEvent de = (DefaultDocumentEvent) e;
                undoManager.addEdit(de);
            }
        }
        public void removeUpdate(DocumentEvent e) {
            if (e instanceof DefaultDocumentEvent) {
                DefaultDocumentEvent de = (DefaultDocumentEvent) e;
                undoManager.addEdit(de);
            }
        }
        public void changedUpdate(DocumentEvent e) {
            // 属性が変わったときは、何もしなくてよい。
        }
    }   
}

テキストエディタ系は、JTextAreaの他に、JTextField, JPasswordField, JTextPaneがあります。
↑このように継承すると、それぞれ個別に対応しなければならないので、それはイマイチです。
なので、外部に委譲する形にしました。↓
これで、すべてのテキストエディタ系に対応できます。

package util;

import java.awt.Event;
import java.awt.event.ActionEvent;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.KeyStroke;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.JTextComponent;
import javax.swing.text.AbstractDocument.DefaultDocumentEvent;
import javax.swing.undo.UndoManager;

/**
 * 指定されたテキストコンポーネントにUndo/Redo機能をつけます。
 * 
 * @author a-san
 */
public class UndoHelper {
    public static final String ACTION_KEY_UNDO = "undo";
    public static final String ACTION_KEY_REDO = "redo";
    UndoManager undoManager = new UndoManager();
    
    /** 指定されたテキストコンポーネントにUndo/Redo機能をつけます。 */
    public UndoHelper(JTextComponent textComponent) {
        ActionMap amap = textComponent.getActionMap();
        InputMap imap = textComponent.getInputMap();
        if (amap.get(ACTION_KEY_UNDO) == null) {
            UndoAction undoAction = new UndoAction();
            amap.put(ACTION_KEY_UNDO, undoAction);
            imap.put((KeyStroke) undoAction.getValue(Action.ACCELERATOR_KEY), ACTION_KEY_UNDO);
        }
        if (amap.get(ACTION_KEY_REDO) == null) {
            RedoAction redoAction = new RedoAction();
            amap.put(ACTION_KEY_REDO, redoAction);
            imap.put((KeyStroke) redoAction.getValue(Action.ACCELERATOR_KEY), ACTION_KEY_REDO);
        }
        // リスナを登録
        textComponent.getDocument().addDocumentListener(new DocListener());
    }
    public UndoManager getUndoManager() { return undoManager; }
    /**
     * 元に戻す
     */
    class UndoAction extends AbstractAction {
        UndoAction() {
            super("元に戻す(U)");
            putValue(MNEMONIC_KEY, new Integer('U'));
            putValue(SHORT_DESCRIPTION, "元に戻す");
            putValue(LONG_DESCRIPTION, "元に戻す");
            putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke('Z', Event.CTRL_MASK));
            putValue(SMALL_ICON, SwingUtil.getImageIcon("/resources/EditUndo.png"));
        }
        public void actionPerformed(ActionEvent e) {
            if (undoManager.canUndo()) {
                undoManager.undo();
            }
        }
    }

    /**
     * やり直し
     */
    class RedoAction extends AbstractAction {
        RedoAction() {
            super("やり直し(R)");
            putValue(MNEMONIC_KEY, new Integer('R'));
            putValue(SHORT_DESCRIPTION, "やり直し");
            putValue(LONG_DESCRIPTION, "やり直し");
            putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke('Y', Event.CTRL_MASK));
            putValue(SMALL_ICON, SwingUtil.getImageIcon("/resources/EditRedo.png"));
        }
        public void actionPerformed(ActionEvent e) {
            if (undoManager.canRedo()) {
                undoManager.redo();
            }
        }
    }
    /** ドキュメントが変更されたときのリスナー. */
    private class DocListener implements DocumentListener {
        public void insertUpdate(DocumentEvent e) {
            if (e instanceof DefaultDocumentEvent) {
                DefaultDocumentEvent de = (DefaultDocumentEvent) e;
                undoManager.addEdit(de);
            }
        }
        public void removeUpdate(DocumentEvent e) {
            if (e instanceof DefaultDocumentEvent) {
                DefaultDocumentEvent de = (DefaultDocumentEvent) e;
                undoManager.addEdit(de);
            }
        }
        public void changedUpdate(DocumentEvent e) {
            // 属性が変わったときは、何もしなくてよい。
        }
    }   
}

使い方は以下のとおり。

    JTextField nameField = new JTextField();
    UndoHelper helper = new UndoHelper(nameField);