/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 1997-2009 Sun Microsystems, Inc. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common * Development and Distribution License("CDDL") (collectively, the * "License"). You may not use this file except in compliance with the * License. You can obtain a copy of the License at * http://www.netbeans.org/cddl-gplv2.html * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the * specific language governing permissions and limitations under the * License. When distributing the software, include this License Header * Notice in each file and include the License file at * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this * particular file as subject to the "Classpath" exception as provided * by Sun in the GPL Version 2 section of the License file that * accompanied this code. If applicable, add the following below the * License Header, with the fields enclosed by brackets [] replaced by * your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * * Contributor(s): * * The Original Software is NetBeans. The Initial Developer of the Original * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun * Microsystems, Inc. All Rights Reserved. * * If you wish your version of this file to be governed by only the CDDL * or only the GPL Version 2, indicate your decision by adding * "[Contributor] elects to include this software in this distribution * under the [CDDL or GPL Version 2] license." If you do not indicate a * single choice of license, a recipient has the option to distribute * your version of this file under either the CDDL, the GPL Version 2 or * to extend the choice of license to its licensees as provided above. * However, if you add GPL Version 2 code and therefore, elected the GPL * Version 2 license, then the option applies only if the new code is * made subject to such option by the copyright holder. */ package org.openide.text; import java.io.IOException; import javax.swing.event.UndoableEditEvent; import javax.swing.text.*; import javax.swing.undo.CompoundEdit; import org.netbeans.junit.*; import org.openide.awt.UndoRedo; import org.openide.util.Exceptions; /** * Emulating old UndoRedo manager deadlock. * * @author Jaroslav Tulach */ public class UndoRedoWrappingCooperationTest extends NbTestCase implements CloneableEditorSupport.Env { static { System.setProperty("org.openide.windows.DummyWindowManager.VISIBLE", "false"); } /** the support to work with */ private CES support; // Env variables private String content = "Hello"; private boolean valid = true; private boolean modified = false; /** if not null contains message why this document cannot be modified */ private String cannotBeModified; private java.util.Date date = new java.util.Date (); private java.util.List/**/ propL = new java.util.ArrayList (); private java.beans.VetoableChangeListener vetoL; /** Creates new TextTest */ public UndoRedoWrappingCooperationTest (String s) { super(s); } protected javax.swing.text.EditorKit createEditorKit() { return new NbLikeEditorKit(); } // Use these methods with the UndoRedoGroup patch // CompoundEdit beginChunk(Document d) { // ur().beginUndoGroup(); // return null; // } // // void endChunk(CompoundEdit ce) { // ur().endUndoGroup(); // } // Use these methods with compound edit implementation CompoundEdit beginChunk(Document d) { CompoundEdit ce = new CompoundEdit(); support.getUndoRedo().undoableEditHappened (new UndoableEditEvent(d, ce)); return ce; } void endChunk(CompoundEdit ce) { ce.end(); } UndoRedo.Manager ur() { return support.getUndoRedo(); } protected void setUp () { support = new CES (this, org.openide.util.Lookup.EMPTY); } public void testTrivialChunk() throws Exception { content = ""; StyledDocument d = support.openDocument(); // same operations as testSingleChunk, // but don't test modified/canUndo/canRedo state CompoundEdit ce = beginChunk(d); d.insertString(d.getLength(), "a", null); d.insertString(d.getLength(), "b", null); endChunk(ce); assertEquals("data", "ab", d.getText(0, d.getLength())); ur().undo(); assertEquals("after undo data", "", d.getText(0, d.getLength())); ur().redo(); assertEquals("after redo data", "ab", d.getText(0, d.getLength())); } public void testSingleChunk() throws Exception { content = ""; StyledDocument d = support.openDocument(); assertFalse("initially: not modified", support.isModified()); assertFalse("initially: no undo", ur().canUndo()); assertFalse("initially: no redo", ur().canRedo()); CompoundEdit ce = beginChunk(d); assertFalse("start chunk: not modified", support.isModified()); assertFalse("start chunk: no undo", ur().canUndo()); assertFalse("start chunk: no redo", ur().canRedo()); d.insertString(d.getLength(), "a", null); assertTrue("insert: modified", support.isModified()); assertTrue("insert: can undo", ur().canUndo()); assertFalse("insert: no redo", ur().canRedo()); d.insertString(d.getLength(), "b", null); endChunk(ce); assertEquals("chunk: data", "ab", d.getText(0, d.getLength())); assertTrue("endChunk: modified", support.isModified()); assertTrue("endChunk: can undo", ur().canUndo()); assertFalse("endChunk: no redo", ur().canRedo()); ur().undo(); assertEquals("after undo: data", "", d.getText(0, d.getLength())); assertFalse("undo: not modified", support.isModified()); assertFalse("undo: no undo", ur().canUndo()); assertTrue("undo: can redo", ur().canRedo()); ur().redo(); assertEquals("after redo: data", "ab", d.getText(0, d.getLength())); assertTrue("redo: modified", support.isModified()); assertTrue("redo: can undo", ur().canUndo()); assertFalse("redo: no redo", ur().canRedo()); } public void testUndoWhileActiveChunk() throws Exception { content = ""; StyledDocument d = support.openDocument(); CompoundEdit ce = beginChunk(d); d.insertString(d.getLength(), "a", null); d.insertString(d.getLength(), "b", null); assertEquals("before undo: data", "ab", d.getText(0, d.getLength())); ur().undo(); // These asserts assume that an undo in the middle of a chunk // is an undo on the whole chunk so far. // Different semantics could be defined, so that an undo inside // of a chunk affects the tiny pieces. assertEquals("after undo: data", "", d.getText(0, d.getLength())); assertFalse("after undo: not modified", support.isModified()); assertFalse("after undo: no undo", ur().canUndo()); assertTrue("after undo: can redo", ur().canRedo()); // note still in the chunk. d.insertString(d.getLength(), "c", null); d.insertString(d.getLength(), "d", null); endChunk(ce); assertEquals("after endChunk: data", "cd", d.getText(0, d.getLength())); assertTrue("after endChunk: modified", support.isModified()); assertTrue("after endChunk: can undo", ur().canUndo()); assertFalse("after endChunk: no redo", ur().canRedo()); ur().undo(); assertEquals("undo after endChunk: data", "", d.getText(0, d.getLength())); assertFalse("undo after endChunk: not modified", support.isModified()); assertFalse("undo after endChunk: no undo", ur().canUndo()); assertTrue("undo after endChunk: can redo", ur().canRedo()); } public void testSaveDocumentWhileActiveChunk() throws Exception { content = ""; StyledDocument d = support.openDocument(); CompoundEdit ce = beginChunk(d); d.insertString(d.getLength(), "a", null); d.insertString(d.getLength(), "b", null); support.saveDocument (); // creates a separate undoable chunk assertFalse("save: not modified", support.isModified()); assertTrue("save: can undo", ur().canUndo()); assertFalse("save: no redo", ur().canRedo()); d.insertString(d.getLength(), "c", null); d.insertString(d.getLength(), "d", null); endChunk(ce); assertEquals("insert after save: data", "abcd", d.getText(0, d.getLength())); assertTrue("insert after save: modified", support.isModified()); assertTrue("insert after save: can undo", ur().canUndo()); assertFalse("insert after save: no redo", ur().canRedo()); ur().undo(); assertEquals("undo at save: data", "ab", d.getText(0, d.getLength())); assertFalse("undo at save: not modified", support.isModified()); assertTrue("undo at save: can undo", ur().canUndo()); assertTrue("undo at save: can redo", ur().canRedo()); ur().undo(); assertEquals("undo before save: data", "", d.getText(0, d.getLength())); // **************************************************************** // CES BUG??? //assertTrue("undo before save: modified", support.isModified()); // **************************************************************** assertFalse("undo before save: can undo", ur().canUndo()); assertTrue("undo before save: can redo", ur().canRedo()); ur().redo(); assertEquals("redo to save: data", "ab", d.getText(0, d.getLength())); assertFalse("redo to save: not modified", support.isModified()); assertTrue("redo to save: can undo", ur().canUndo()); assertTrue("redo to save: can redo", ur().canRedo()); } public void testNestedChunks() throws Exception { content = ""; StyledDocument d = support.openDocument(); CompoundEdit ce1 = beginChunk(d); d.insertString(d.getLength(), "a", null); d.insertString(d.getLength(), "b", null); CompoundEdit ce2 = beginChunk(d); // creates a separate undoable chunk d.insertString(d.getLength(), "c", null); d.insertString(d.getLength(), "d", null); endChunk(ce1); d.insertString(d.getLength(), "e", null); d.insertString(d.getLength(), "f", null); endChunk(ce2); assertEquals("data", "abcdef", d.getText(0, d.getLength())); // following fails currently because initial implementation does not // support nesting. See discussion at // http://netbeans.org/bugzilla/show_bug.cgi?id=103467 // another ur().undo() right here gets the test to pass // since "ef" are actually two separate edits ur().undo(); assertEquals("undo1", "abcd", d.getText(0, d.getLength())); ur().undo(); assertEquals("undo2", "ab", d.getText(0, d.getLength())); ur().undo(); assertEquals("undo3", "", d.getText(0, d.getLength())); } // // Implementation of the CloneableEditorSupport.Env // public synchronized void addPropertyChangeListener(java.beans.PropertyChangeListener l) { propL.add (l); } public synchronized void removePropertyChangeListener(java.beans.PropertyChangeListener l) { propL.remove (l); } public synchronized void addVetoableChangeListener(java.beans.VetoableChangeListener l) { assertNull ("This is the first veto listener", vetoL); vetoL = l; } public void removeVetoableChangeListener(java.beans.VetoableChangeListener l) { assertEquals ("Removing the right veto one", vetoL, l); vetoL = null; } public org.openide.windows.CloneableOpenSupport findCloneableOpenSupport() { return support; } public String getMimeType() { return "text/plain"; } public java.util.Date getTime() { return date; } public java.io.InputStream inputStream() throws java.io.IOException { return new java.io.ByteArrayInputStream (content.getBytes ()); } public java.io.OutputStream outputStream() throws java.io.IOException { class ContentStream extends java.io.ByteArrayOutputStream { public void close () throws java.io.IOException { super.close (); content = new String (toByteArray ()); } } return new ContentStream (); } public boolean isValid() { return valid; } public boolean isModified() { return modified; } public void markModified() throws java.io.IOException { if (cannotBeModified != null) { IOException e = new IOException (); Exceptions.attachLocalizedMessage(e, cannotBeModified); throw e; } modified = true; } public void unmarkModified() { modified = false; } /** Implementation of the CES */ private final class CES extends CloneableEditorSupport { public boolean plain; public CES (Env env, org.openide.util.Lookup l) { super (env, l); } protected String messageName() { return "Name"; } protected String messageOpened() { return "Opened"; } protected String messageOpening() { return "Opening"; } protected String messageSave() { return "Save"; } protected String messageToolTip() { return "ToolTip"; } protected javax.swing.text.EditorKit createEditorKit() { if (plain) { return super.createEditorKit (); } else { return UndoRedoWrappingCooperationTest.this.createEditorKit (); } } } // end of CES }