blob: 5cc154a59a6377195836f4cc94ea90b4f0d53b82 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.openide.text;
import java.beans.PropertyChangeSupport;
import java.io.StringWriter;
import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.text.*;
import junit.framework.AssertionFailedError;
import org.netbeans.junit.NbTestCase;
import org.openide.ErrorManager.Annotation;
import org.openide.util.Exceptions;
import org.openide.util.Lookup;
import org.openide.util.lookup.*;
/** Testing different features of CloneableEditorSupport
*
* @author Jaroslav Tulach
*/
public class NotifyModifiedTest extends NbTestCase
implements CloneableEditorSupport.Env {
private static final Logger err = Logger.getLogger(NotifyModifiedTest.class.getName());
static {
System.setProperty("org.openide.windows.DummyWindowManager.VISIBLE", "false");
}
static {
System.setProperty("org.openide.util.Lookup", "org.openide.text.NotifyModifiedTest$Lkp");
}
/** the support to work with */
protected CES support;
/** the content of lookup of support */
private InstanceContent ic;
// Env variables
private String content = "";
private boolean valid = true;
private volatile boolean modified = false;
private java.util.Date date = new java.util.Date ();
private PropertyChangeSupport propL = new PropertyChangeSupport (this);
private java.beans.VetoableChangeListener vetoL;
private boolean shouldVetoNotifyModified;
/** kit to create */
private javax.swing.text.EditorKit editorKit;
public NotifyModifiedTest(java.lang.String testName) {
super(testName);
}
@Override
protected int timeOut() {
return 20000;
}
@Override
protected Level logLevel() {
return Level.FINE;
}
@Override
protected void setUp () {
ic = new InstanceContent ();
support = new CES (this, new AbstractLookup (ic));
}
//
// overwrite editor kit
//
protected javax.swing.text.EditorKit createEditorKit () {
return null;
}
protected void checkThatDocumentLockIsNotHeld () {
}
protected void doesVetoedInsertFireBadLocationException (javax.swing.text.BadLocationException e) {
// Since 6.43 (UndoRedoManager addition) the CES installs DocumentFilter (that prevents modification)
// to any AbstractDocument-based document including javax.swing.text.PlainDocument so: if (e == null) => fail
if (e == null) {
fail("On non-nblike documents, vetoed insert does not generate BadLocationException");
}
}
//
// test methods
//
public void testJustOneCallToModified () throws Exception {
content = "Line1\nLine2\n";
// in order to set.getLines() work correctly, the document has to be loaded
javax.swing.text.Document doc = support.openDocument();
assertEquals ("No modification", 0, support.notifyModified);
doc.insertString (3, "Ahoj", null);
assertEquals ("One modification", 1, support.notifyModified);
doc.insertString (7, "Kuk", null);
assertEquals ("Still one modification", 1, support.notifyModified);
doc.remove (7, 3);
assertEquals ("Still one modification2", 1, support.notifyModified);
support.saveDocument (); Thread.sleep(300);
assertEquals ("Marked unmodified", 1, support.notifyUnmodified);
doc.remove (0, 1);
assertEquals ("Modifies again", 2, support.notifyModified);
}
public void testTheDocumentReturnsBackIfModifyIsNotAllowed () throws Exception {
content = "Nic\n";
// in order to set.getLines() work correctly, the document has to be loaded
javax.swing.text.Document doc = support.openDocument();
assertEquals ("No modification", 0, support.notifyModified);
shouldVetoNotifyModified = true;
// should be reverted in SwingUtilities.invokeLater
try {
doc.insertString (0, "Ahoj", null);
// Previous insert should fail with exception
doesVetoedInsertFireBadLocationException (null);
} catch (javax.swing.text.BadLocationException e) {
// Expecting the thrown exception
doesVetoedInsertFireBadLocationException (e);
}
waitEQ ();
assertEquals ("One modification called (but it was vetoed)", 1, support.notifyModified);
assertEquals ("No unmodification called", 0, support.notifyUnmodified);
String first = doc.getText (0, 1);
assertEquals ("First letter is N", "N", first);
}
public void testTheDocumentReturnsBackIfModifyIsNotAllowedMultipleTimes () throws Exception {
class R implements Runnable {
int[] i = { 0 };
Document[] doc = { null };
public void run () {
try {
if (i[0] % 2 == 0) {
doc[0].insertString (0, "Ahoj", null);
} else {
doc[0].remove (0, 2);
}
// Previous insert should fail with exception
doesVetoedInsertFireBadLocationException (null);
} catch (javax.swing.text.BadLocationException e) {
// Expecting the thrown exception
doesVetoedInsertFireBadLocationException (e);
}
}
}
R r = new R ();
doTheDocumentReturnsBackIfModifyIsNotAllowedMultipleTimes (r.i, r.doc, r);
}
public void testTheDocumentReturnsBackIfModifyIsNotAllowedMultipleTimesInAtomicSection () throws Exception {
class R implements Runnable {
int[] i = { 0 };
javax.swing.text.StyledDocument[] doc = { null };
private boolean inAtomic;
public void run () {
if (!inAtomic) {
inAtomic = true;
NbDocument.runAtomic(doc[0], this);
inAtomic = false;
} else {
try {
doc[0].insertString (0, "Ahoj", null);
} catch (javax.swing.text.BadLocationException e) {
// Expected - Since 6.43 (UndoRedoManager addition) the CES installs DocumentFilter (that prevents modification)
// to any AbstractDocument-based document including javax.swing.text.PlainDocument
// so even individual modifications inside atomic runnable fail.
// fail ("Inside atomic no BadLocationException due to unmodifiable source");
}
}
}
}
R r = new R ();
doTheDocumentReturnsBackIfModifyIsNotAllowedMultipleTimes (r.i, r.doc, r);
}
/** Passing parameters by reference - e.g. arrays of size 1, so [0] can be filled and changed... */
private void doTheDocumentReturnsBackIfModifyIsNotAllowedMultipleTimes (int[] i, Document[] doc, Runnable op) throws Exception {
content = "EmptyContentForTheDocument\n";
// in order to set.getLines() work correctly, the document has to be loaded
doc[0] = support.openDocument();
assertEquals ("No modification", 0, support.notifyModified);
shouldVetoNotifyModified = true;
for (i[0] = 0; i[0] < 10; i[0]++) {
// do operation that will be forbidden
op.run ();
waitEQ ();
support.assertModified (false, "Is still unmodified");
}
String first = doc[0].getText (0, doc[0].getLength ());
if (!first.equals (content)) {
fail ("Expected: " + content +
" but was: " + first);
}
assertEquals ("Vetoed modifications", 10, support.notifyModified);
assertEquals ("No unmodification called", 0, support.notifyUnmodified);
}
public void testBadLocationException () throws Exception {
content = "Nic\n";
// in order to set.getLines() work correctly, the document has to be loaded
javax.swing.text.Document doc = support.openDocument();
assertEquals ("No modification", 0, support.notifyModified);
try {
doc.insertString (10, "Ahoj", null);
fail ("This should generate bad location exception");
} catch (javax.swing.text.BadLocationException ex) {
// ok
}
// Since 6.43 (UndoRedoManager addition) the CES installs DocumentFilter (that prevents modification)
// to any AbstractDocument-based document including javax.swing.text.PlainDocument
// so even non-NbLikeEditorKit will have 1 attempt
int expected = createEditorKit () instanceof NbLikeEditorKit ? 1 : 1;
assertEquals (expected + " modification called (but it was vetoed)", expected, support.notifyModified);
assertEquals (expected + " unmodification called", expected, support.notifyUnmodified);
String first = doc.getText (0, 1);
assertEquals ("First letter is N", "N", first);
}
public void testDoModificationsInAtomicBlock () throws Exception {
content = "Something";
final javax.swing.text.StyledDocument doc = support.openDocument();
class R implements Runnable {
public void run () {
try {
doc.insertString (0, "Ahoj", null);
} catch (javax.swing.text.BadLocationException ex) {
AssertionFailedError e = new AssertionFailedError (ex.getMessage ());
e.initCause (ex);
throw e;
}
}
}
R r = new R ();
NbDocument.runAtomic (doc, r);
assertEquals ("One modification", 1, support.notifyModified);
assertEquals ("no unmod", 0, support.notifyUnmodified);
}
public void testDoModificationsInAtomicBlockAndRefuseThem () throws Exception {
content = "Something";
final javax.swing.text.StyledDocument doc = support.openDocument();
shouldVetoNotifyModified = true;
class R implements Runnable {
public boolean gotIntoRunnable;
public void run () {
gotIntoRunnable = true;
try {
doc.insertString (0, "Ahoj", null);
fail("Unexpected to pass the insertString() due vetoed notifyModify()");
doc.remove (0, 1);
doc.remove (0, 1);
} catch (javax.swing.text.BadLocationException ex) {
// Expected - since 6.43 (UndoRedoManager addition) the CES installs DocumentFilter (that prevents modification)
// so even individual modifications (inside runAtomic() will fail with BLE
// AssertionFailedError e = new AssertionFailedError (ex.getMessage ());
// e.initCause (ex);
// throw e;
}
}
}
R r = new R ();
NbDocument.runAtomic (doc, r);
waitEQ ();
// Since 6.43 (UndoRedoManager addition) if doc supports "supportsModificationListener" property
// it is required to call notifyModify() before writeLock that spans atomic modification
// (NbLikeEditorKit.Doc.insOrRemoveOrRunnable() was fixed to adhere.
assertTrue ("Runable should be started", r.gotIntoRunnable);
if (support.notifyModified == 0) {
fail ("At least One notification expected");
}
assertEquals ("no unmod", 0, support.notifyUnmodified);
support.assertModified (false, "Document is not modified");
String text = doc.getText (0, doc.getLength ());
assertEquals ("The text is the same as original content", content, text);
}
public void testRevertModificationAfterSave () throws Exception {
content = "Ahoj";
final javax.swing.text.StyledDocument doc = support.openDocument();
doc.insertString (4, " Jardo", null);
doc.insertString (0, ":", null);
String text = doc.getText (0, doc.getLength ());
support.saveDocument (); Thread.sleep(300);
support.assertModified (false, "Not modified");
shouldVetoNotifyModified = true;
try {
doc.remove (0, 5);
doesVetoedInsertFireBadLocationException (null);
} catch (BadLocationException ex) {
doesVetoedInsertFireBadLocationException (ex);
}
waitEQ ();
support.assertModified (false, "Not modified");
text = doc.getText (0, doc.getLength ());
if (!":Ahoj Jardo".equals (text)) {
fail ("The text as after save ':Ahoj Jardo' but was: " + text);
}
}
public void testAtomicBlockWithoutModifications () throws Exception {
content = "Something";
final javax.swing.text.StyledDocument doc = support.openDocument();
class R implements Runnable {
public void run () {
}
}
R r = new R ();
NbDocument.runAtomic (doc, r);
assertEquals ("The same number of modification and unmodifications", support.notifyModified, support.notifyUnmodified);
assertEquals ("Actually it is zero", 0, support.notifyUnmodified);
}
public void testDoInsertAfterEmptyBlock () throws Exception {
testAtomicBlockWithoutModifications (); // may produce notifyModify (see impl)
support.getDocument ().insertString (0, "Ahoj", null);
assertEquals ("One modification now", 1, support.notifyModified);
assertEquals ("No unmodified", 0, support.notifyUnmodified);
support.assertModified (true, "Is modified");
}
public void testDoRemoveAfterEmptyBlock () throws Exception {
testAtomicBlockWithoutModifications (); // may produce notifyModify (see impl)
support.getDocument ().remove (0, 4);
assertEquals ("One modification now", 1, support.notifyModified);
assertEquals ("No unmodified", 0, support.notifyUnmodified);
support.assertModified (true, "Is modified");
}
public void testAtomicBlockWithoutModificationsAfterInsert () throws Exception {
doAtomicBlockWithoutModificationsAfterInsert (false, 1);
}
public void testAtomicBlockWithoutModificationsAfterInsertDouble () throws Exception {
doAtomicBlockWithoutModificationsAfterInsert (false, 2);
}
public void testAtomicUserBlockWithoutModificationsAfterInsert () throws Exception {
doAtomicBlockWithoutModificationsAfterInsert (true, 1);
}
public void testAtomicUserBlockWithoutModificationsAfterInsertDouble () throws Exception {
doAtomicBlockWithoutModificationsAfterInsert (true, 2);
}
private void doAtomicBlockWithoutModificationsAfterInsert (final boolean asUser, final int cnt) throws Exception {
content = "Something";
final javax.swing.text.StyledDocument doc = support.openDocument();
doc.insertString(0, "Ahoj", null);
class R implements Runnable {
public int counter = cnt;
public void run () {
if (--counter > 0) {
if (asUser) {
try {
NbDocument.runAtomicAsUser(doc, this);
} catch (javax.swing.text.BadLocationException ex) {
throw (AssertionFailedError)new AssertionFailedError (ex.getMessage()).initCause(ex);
}
} else {
NbDocument.runAtomic(doc, this);
}
}
}
}
R r = new R ();
support.assertModified (true, "Document must be modified");
if (asUser) {
NbDocument.runAtomicAsUser(doc, r);
} else {
NbDocument.runAtomic (doc, r);
}
support.assertModified (true, "Document must stay modified");
}
public void testUndoDoesMarkFileAsDirtyIssue56963 () throws Exception {
content = "Somecontent";
err.info("Going to open");
final javax.swing.text.StyledDocument doc = support.openDocument();
err.info("Opened: " + doc);
int len = doc.getLength ();
assertEquals ("Content opened", "Somecontent", doc.getText (0, len));
err.info("Going to remove " + len + " characters");
doc.remove (0, len);
err.info("Removed");Thread.sleep(300);
assertEquals ("Empty", 0, doc.getLength ());
assertTrue ("Can undo", support.getUndoRedo ().canUndo ());
err.info("Going to save");
support.saveDocument (); Thread.sleep(300);
waitEQ ();
err.info("Saved");
assertTrue ("Can undo as well", support.getUndoRedo ().canUndo ());
err.info("Going to undo");
support.getUndoRedo ().undo ();
waitEQ ();
err.info("Undoed");
assertEquals ("Lengh it back", len, doc.getLength ());
assertEquals ("Content is back", "Somecontent", doc.getText (0, len));
err.info("Before assertModified");
support.assertModified (true, "Document is Modified");
err.info("Before redo");
support.getUndoRedo ().redo ();
waitEQ ();
err.info("After redo");
assertEquals ("Zero length", 0, doc.getLength ());
support.assertModified (false, "Document is UnModified");
}
public void testReloadWithoutModifiedIssue57104 () throws Exception {
content = "Somecontent";
final javax.swing.text.StyledDocument doc = support.openDocument();
int len = doc.getLength ();
assertEquals ("Content opened", "Somecontent", doc.getText (0, len));
doc.remove (0, len);
assertEquals ("Empty", 0, doc.getLength ());
assertTrue ("Can undo", support.getUndoRedo ().canUndo ());
support.saveDocument (); Thread.sleep(300);
waitEQ ();
assertTrue ("Can undo as well", support.getUndoRedo ().canUndo ());
content = "Newcontent";
int newLen = content.length ();
assertEquals ("Once modified", 1, support.notifyModified);
assertEquals ("Once unmodified after save", 1, support.notifyUnmodified);
propL.firePropertyChange (PROP_TIME, null, null);
Thread.sleep(200); // Wait until reload EDT preparation task gets scheduled
waitEQ ();
Object newDoc = support.openDocument ();
assertSame ("Reload does not change the document", newDoc, doc);
assertEquals ("Length it new", newLen, doc.getLength ());
assertEquals ("Content is new", "Newcontent", doc.getText (0, newLen));
// getUndoRedo().discardAllEdits(); in CES around line 1848 uses doc.runAtomic() => may call notifyModify()
int expectedCount = 1;
assertEquals ("Modified", expectedCount, support.notifyModified);
assertEquals ("Unmodified", expectedCount, support.notifyUnmodified);
}
public void testUndoMarksFileUnmodified () throws Exception {
content = "Somecontent";
final javax.swing.text.StyledDocument doc = support.openDocument();
int len = doc.getLength ();
assertEquals ("Content opened", "Somecontent", doc.getText (0, len));
doc.remove (0, len);
support.assertModified (true, "Document is modified");
assertTrue ("Can undo", support.getUndoRedo ().canUndo ());
support.getUndoRedo ().undo ();
support.assertModified (false, "Document is unmodified");
}
public void testReloadWhenModifiedIssue57104 () throws Exception {
content = "Somecontent";
final javax.swing.text.StyledDocument doc = support.openDocument();
int len = doc.getLength ();
assertEquals ("Content opened", "Somecontent", doc.getText (0, len));
err.info("wait so first modification really happens later in time then the lastSaveTime is set to");
Thread.sleep(300);
doc.remove (0, len);
err.info("After remove");
assertEquals ("Empty", 0, doc.getLength ());
assertTrue ("Can undo", support.getUndoRedo ().canUndo ());
err.info("Before save");
Thread.sleep(300); support.saveDocument (); Thread.sleep(300);
waitEQ ();
err.info("After save");
assertTrue ("Can undo as well", support.getUndoRedo ().canUndo ());
assertEquals ("Once modified", 1, support.notifyModified);
assertEquals ("Once unmodified after save", 1, support.notifyUnmodified);
err.info("Before undo");
support.getUndoRedo ().undo ();
waitEQ ();
err.info("After undo");
assertEquals ("Lengh it back", len, doc.getLength ());
assertEquals ("Content is back", "Somecontent", doc.getText (0, len));
waitEQ ();
support.assertModified (true, "Document is Modified");
assertEquals ("One more modified", 2, support.notifyModified);
assertEquals ("No unmodifications", 1, support.notifyUnmodified);
content = "Newcontent";
int newLen = content.length ();
// does the reload
propL.firePropertyChange (PROP_TIME, null, null);
Thread.sleep(100); // Wait till reload will start processing
waitEQ (); // Wait till EDT part of the reload is over
Object newDoc = support.openDocument ();
assertSame ("Reload does not change the document", newDoc, doc);
assertEquals ("Length it new", newLen, doc.getLength ());
assertEquals ("Content is new", "Newcontent", doc.getText (0, newLen));
assertEquals ("No more modified", 2, support.notifyModified);
assertEquals ("But one more unmodified", 2, support.notifyUnmodified);
}
private void waitEQ () throws Exception {
// repeat five times to handle also runnables started from AWT
for (int i = 0; i < 5; i++) {
javax.swing.SwingUtilities.invokeAndWait (new Runnable () { public void run () { } });
}
}
//
// Implementation of the CloneableEditorSupport.Env
//
public synchronized void addPropertyChangeListener(java.beans.PropertyChangeListener l) {
propL.addPropertyChangeListener (l);
}
public synchronized void removePropertyChangeListener(java.beans.PropertyChangeListener l) {
propL.removePropertyChangeListener (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 {
@Override
public void close () throws java.io.IOException {
super.close ();
content = new String (toByteArray ());
date = new Date();
}
}
return new ContentStream ();
}
public boolean isValid() {
return valid;
}
public boolean isModified() {
return modified;
}
public void markModified() throws java.io.IOException {
modified = true;
//new Exception ("markModified: " + modified).printStackTrace(System.out);
// The document is expected to hold write-lock when markModified() is called - this change
// allows to get rid of extra unmarkModified() done when an atomic-lock section
// does not perform any document modification.
// checkThatDocumentLockIsNotHeld ();
}
public void unmarkModified() {
modified = false;
//new Exception ("unmarkModified: " + modified).printStackTrace(System.out);
// checkThatDocumentLockIsNotHeld ();
}
/** Implementation of the CES */
protected final class CES extends CloneableEditorSupport {
public int notifyUnmodified;
public int notifyModified;
public CES (Env env, 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";
}
@Override
protected void notifyUnmodified () {
notifyUnmodified++;
// Exceptions.printStackTrace(new java.lang.Exception("notifyUnmodified: " + notifyUnmodified));
super.notifyUnmodified();
}
@Override
protected boolean notifyModified () {
notifyModified++;
if (shouldVetoNotifyModified) {
return false;
}
// Exceptions.printStackTrace(new java.lang.Exception("notifyModified: " + notifyModified));
boolean retValue;
retValue = super.notifyModified();
return retValue;
}
@Override
protected javax.swing.text.EditorKit createEditorKit() {
javax.swing.text.EditorKit k = NotifyModifiedTest.this.createEditorKit ();
if (k != null) {
return k;
}
return super.createEditorKit ();
}
public void assertModified (boolean modified, String msg) {
assertEquals (msg, modified, isModified ());
}
}
}