xIndex: arch/arch-openide-editor.xml =================================================================== RCS file: /cvs/openide/arch/arch-openide-editor.xml,v retrieving revision 1.18 diff -u -r1.18 arch-openide-editor.xml --- arch/arch-openide-editor.xml 9 Sep 2004 18:09:52 -0000 1.18 +++ arch/arch-openide-editor.xml 14 Feb 2005 14:52:11 -0000 @@ -375,6 +375,21 @@ This property can be used by the modules and is part of the Editor API. + +In order to fix issue 51872 the +openide needs a way how to be notified about change of a document outside of its Document lock. +DocumentListeners are always notified under the lock, so a special contract has +been established (since version 5.3) by registering an instance of VetoableListener +by calling putProperty ("beforeModificationListener", listener). The +NetBeans aware document are adviced to honor this property and call the listener +outside of the document lock when a modification is made. The actual contract +of the call can be seen in +NbLikeEditorKit.java +in methods +insertString and remove. + + + Index: src/org/openide/text/CloneableEditorSupport.java =================================================================== RCS file: /cvs/openide/src/org/openide/text/CloneableEditorSupport.java,v retrieving revision 1.140 diff -u -r1.140 CloneableEditorSupport.java --- src/org/openide/text/CloneableEditorSupport.java 5 Jan 2005 17:05:25 -0000 1.140 +++ src/org/openide/text/CloneableEditorSupport.java 14 Feb 2005 14:52:12 -0000 @@ -556,8 +556,10 @@ public void run() { try { doc.removeDocumentListener(getListener()); + doc.putProperty ("beforeModificationListener", null); // NOI18N doc.remove(0, doc.getLength()); // remove all text doc.addDocumentListener(getListener()); + doc.putProperty ("beforeModificationListener", getListener ()); // NOI18N } catch(BadLocationException ble) { ErrorManager.getDefault().notify( ErrorManager.INFORMATIONAL, ble); @@ -704,12 +706,14 @@ } final StyledDocument myDoc = getDocument(); - final IOException[] holder = new IOException[1]; - // save the document as a reader - myDoc.render(new Runnable() { + // save the document as a reader + class SaveAsReader implements Runnable { + private boolean doMarkAsUnmodified; + private IOException ex; + public void run() { - try { + try { OutputStream os = null; // write the document @@ -725,7 +729,7 @@ // remember time of last save lastSaveTime = System.currentTimeMillis(); - notifyUnmodified (); + doMarkAsUnmodified = true; } catch (BadLocationException ex) { ErrorManager.getDefault().notify(ex); @@ -744,14 +748,25 @@ // update cached info about lines updateLineSet (true); updateTitles (); - } catch (IOException e) { - holder[0] = e; - } + } catch (IOException e) { + this.ex = e; + } } - }); - - // refire the exception - if (holder[0] != null) throw holder[0]; + + public void after () throws IOException { + if (doMarkAsUnmodified) { + notifyUnmodified (); + } + + if (ex != null) { + throw ex; + } + } + } + + SaveAsReader saveAsReader = new SaveAsReader (); + myDoc.render (saveAsReader); + saveAsReader.after (); } /** @@ -1323,15 +1338,18 @@ } /** Conditionally calls notifyModified + * @return true if the modification was allowed, false if it should be prohibited */ - private final void callNotifyModified () { + private final boolean callNotifyModified () { if (!alreadyModified) { alreadyModified = true; if (!notifyModified ()) { revertUpcomingUndo (); alreadyModified = false; + return false; } } + return true; } /** Called when the document is being modified. @@ -1425,6 +1443,7 @@ } UndoRedo ur = getUndoRedo(); sd.removeDocumentListener(getListener()); + sd.putProperty ("beforeModificationListener", null); // NOI18N try { if(ur.canUndo()) { Toolkit.getDefaultToolkit().beep(); @@ -1435,6 +1454,7 @@ ErrorManager.INFORMATIONAL, cne); } finally { sd.addDocumentListener(getListener()); + sd.putProperty ("beforeModificationListener", getListener ()); // NOI18N } } }; @@ -1494,38 +1514,6 @@ } - // other public methods ................................................................ - - - /* JST: Commented out - * Set actions for toolbar. - * @param actions list of actions - * - public void setActions (SystemAction[] actions) { - this.actions = actions; - } - - /** Utility method which enables or disables listening to modifications - * on asociated document. - *

- * Could be useful if we have to modify document, but do not want the - * Save and Save All actions to be enabled/disabled automatically. - * Initially modifications are listened to. - * @param listenToModifs whether to listen to modifications - * - public void setModificationListening (final boolean listenToModifs) { - if (this.listenToModifs == listenToModifs) return; - this.listenToModifs = listenToModifs; - if (doc == null) return; - if (listenToModifs) - doc.addi(getModifL()); - else - doc.removeDocumentListener(getModifL()); - } - */ - - - /** Loads the document for this object. * @param kit kit to use * @param d original document to load data into @@ -1617,6 +1605,7 @@ if (doc != null) { doc.removeUndoableEditListener (getUndoRedo ()); doc.removeDocumentListener(getListener()); + doc.putProperty ("beforeModificationListener", null); } if (positionManager != null) { @@ -1949,9 +1938,10 @@ * document, environment and also temporarilly on undoredo. */ private final class Listener extends Object - implements ChangeListener, DocumentListener, PropertyChangeListener, Runnable { + implements ChangeListener, DocumentListener, PropertyChangeListener, + Runnable, java.beans.VetoableChangeListener { - Listener() {} + Listener() {} /** Stores exception from loadDocument, can be set in run method */ private IOException loadExc; @@ -1993,6 +1983,18 @@ //modified(); (bugfix #1492) } + public void vetoableChange (PropertyChangeEvent evt) throws java.beans.PropertyVetoException { + if ("modified".equals (evt.getPropertyName ())) { // NOI18N + if (Boolean.TRUE.equals (evt.getNewValue ())) { + if (!callNotifyModified ()) { + throw new java.beans.PropertyVetoException ("Not allowed", evt); // NOI18N + } + } else { + notifyUnmodified (); + } + } + } + /** Gives notification that there was an insert into the document. * @param ev event describing the action */ @@ -2054,6 +2056,7 @@ * which can prevent dedloks that sometimes occured during file reload. */ doc.removeDocumentListener(getListener()); + doc.putProperty ("beforeModificationListener", null); // NOI18N try { loadExc = null; LOCAL_LOAD_TASK.set(Boolean.TRUE); @@ -2079,7 +2082,9 @@ // Start listening on changes in document doc.addDocumentListener(getListener()); + doc.putProperty ("beforeModificationListener", getListener ()); // NOI18N // NOI18N } + // } } @@ -2286,7 +2291,7 @@ public void undo() { super.undo(); - if (saveTime == lastSaveTime) { + if (saveTime == lastSaveTime && alreadyModified) { notifyUnmodified(); } } Index: test/unit/src/org/openide/text/NbLikeEditorKit.java =================================================================== RCS file: /cvs/openide/test/unit/src/org/openide/text/NbLikeEditorKit.java,v retrieving revision 1.1 diff -u -r1.1 NbLikeEditorKit.java --- test/unit/src/org/openide/text/NbLikeEditorKit.java 28 Jul 2004 12:34:10 -0000 1.1 +++ test/unit/src/org/openide/text/NbLikeEditorKit.java 14 Feb 2005 14:52:12 -0000 @@ -14,6 +14,7 @@ package org.openide.text; +import java.beans.VetoableChangeListener; import javax.swing.text.*; /** @@ -37,18 +38,17 @@ } public void runAtomic (Runnable r) { - runAtomicAsUser (r); - } - - public void runAtomicAsUser (Runnable r) {// throws BadLocationException; - writeLock (); try { - r.run (); - } finally { - writeUnlock (); + runAtomicAsUser (r); + } catch (BadLocationException ex) { + throw (IllegalStateException)new IllegalStateException (ex.getMessage ()).initCause (ex); } } + public void runAtomicAsUser (Runnable r) throws BadLocationException { + insOrRemoveOrRunnable (-1, null, null, -1, false, r); + } + public javax.swing.text.Style getLogicalStyle(int p) { return null; } @@ -88,5 +88,66 @@ public java.awt.Color getForeground(javax.swing.text.AttributeSet attr) { return null; } + + private int changes; + public void insertString (int offs, String str, AttributeSet a) throws BadLocationException { + insOrRemoveOrRunnable (offs, str, a, 0, true, null); + } + + public void remove (int offs, int len) throws BadLocationException { + insOrRemoveOrRunnable (offs, null, null, len, false, null); + } + + + private void insOrRemoveOrRunnable (int offset, String str, AttributeSet set, int len, boolean insert, Runnable run) + throws BadLocationException { + Object o = getProperty ("beforeModificationListener"); + if (o instanceof VetoableChangeListener) { + VetoableChangeListener l = (VetoableChangeListener)o; + try { + l.vetoableChange (new java.beans.PropertyChangeEvent (this, "modified", null, Boolean.TRUE)); + } catch (java.beans.PropertyVetoException ex) { + return; + } + } + + boolean unmodify = false; + try { + if (run != null) { + writeLock (); + int prevChanges = changes; + try { + run.run (); + } finally { + writeUnlock (); + } + if (changes == prevChanges) { + notifyUnmodified (o); + } + } else { + changes++; + if (insert) { + super.insertString (offset, str, set); + } else { + super.remove(offset, len); + } + } + } catch (BadLocationException ex) { + notifyUnmodified (o); + throw ex; + } + } + + private void notifyUnmodified (Object o) { + if (o instanceof VetoableChangeListener) { + VetoableChangeListener l = (VetoableChangeListener)o; + try { + l.vetoableChange (new java.beans.PropertyChangeEvent (this, "modified", null, Boolean.FALSE)); + } catch (java.beans.PropertyVetoException ignore) { + // ignore this + } + } + } + } // end of Doc } Index: test/unit/src/org/openide/text/NotifyModifiedOnNbEditorLikeKitTest.java =================================================================== RCS file: test/unit/src/org/openide/text/NotifyModifiedOnNbEditorLikeKitTest.java diff -N test/unit/src/org/openide/text/NotifyModifiedOnNbEditorLikeKitTest.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ test/unit/src/org/openide/text/NotifyModifiedOnNbEditorLikeKitTest.java 14 Feb 2005 14:52:12 -0000 @@ -0,0 +1,83 @@ +/* + * Sun Public License Notice + * + * The contents of this file are subject to the Sun Public License + * Version 1.0 (the "License"). You may not use this file except in + * compliance with the License. A copy of the License is available at + * http://www.sun.com/ + * + * The Original Code is NetBeans. The Initial Developer of the Original + * Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun + * Microsystems, Inc. All Rights Reserved. + */ + +package org.openide.text; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import junit.framework.*; + +import org.netbeans.junit.*; + +import org.openide.util.Lookup; +import org.openide.util.lookup.*; + + +/** Testing different features of NotifyModifieTest with NbEditorKit + * + * @author Jaroslav Tulach + */ +public class NotifyModifiedOnNbEditorLikeKitTest extends NotifyModifiedTest { + private NbLikeEditorKit k; + + public NotifyModifiedOnNbEditorLikeKitTest (String s) { + super (s); + } + + // + // overwrite editor kit + // + + protected javax.swing.text.EditorKit createEditorKit () { + NbLikeEditorKit k = new NbLikeEditorKit (); + return k; + } + + protected void checkThatDocumentLockIsNotHeld () { + class X implements Runnable { + private boolean second; + private boolean ok; + + public void run () { + if (second) { + ok = true; + return; + } else { + second = true; + javax.swing.text.Document doc = support.getDocument (); + assertNotNull (doc); + // we have to pass thru read access + doc.render (this); + + if (ok) { + try { + // we have to be allowed to do modifications as well + doc.insertString (0, "A", null); + doc.remove (0, 1); + } catch (javax.swing.text.BadLocationException ex) { + ok = false; + } + } + + return; + } + } + } + + X x = new X (); + org.openide.util.RequestProcessor.getDefault ().post (x).waitFinished (); + assertTrue ("No lock is held on document when running notifyModified", x.ok); + } +} Index: test/unit/src/org/openide/text/NotifyModifiedTest.java =================================================================== RCS file: /cvs/openide/test/unit/src/org/openide/text/NotifyModifiedTest.java,v retrieving revision 1.1 diff -u -r1.1 NotifyModifiedTest.java --- test/unit/src/org/openide/text/NotifyModifiedTest.java 5 Jan 2005 17:05:25 -0000 1.1 +++ test/unit/src/org/openide/text/NotifyModifiedTest.java 14 Feb 2005 14:52:12 -0000 @@ -32,7 +32,7 @@ public class NotifyModifiedTest extends NbTestCase implements CloneableEditorSupport.Env { /** the support to work with */ - private CES support; + protected CES support; /** the content of lookup of support */ private InstanceContent ic; @@ -45,6 +45,8 @@ private java.util.List/**/ propL = new java.util.ArrayList (); private java.beans.VetoableChangeListener vetoL; private boolean shouldVetoNotifyModified; + /** kit to create */ + private javax.swing.text.EditorKit editorKit; public NotifyModifiedTest(java.lang.String testName) { @@ -56,6 +58,24 @@ support = new CES (this, new AbstractLookup (ic)); } + + // + // overwrite editor kit + // + + protected javax.swing.text.EditorKit createEditorKit () { + return null; + } + + protected void checkThatDocumentLockIsNotHeld () { + } + + + // + // test methods + // + + public void testJustOneCallToModified () throws Exception { content = "Line1\nLine2\n"; @@ -91,11 +111,78 @@ // should be reverted in SwingUtilities.invokeLater doc.insertString (0, "Ahoj", null); 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 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 + } + + int expected = createEditorKit () instanceof NbLikeEditorKit ? 1 : 0; + 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 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); + } private void waitEQ () throws Exception { javax.swing.SwingUtilities.invokeAndWait (new Runnable () { public void run () { } }); @@ -165,33 +252,8 @@ checkThatDocumentLockIsNotHeld (); } - private void checkThatDocumentLockIsNotHeld () { - /* - class X implements Runnable { - private boolean second; - private boolean ok; - - public void run () { - if (second) { - ok = true; - return; - } else { - second = true; - javax.swing.text.Document doc = support.getDocument (); - assertNotNull (doc); - doc.render (this); - return; - } - } - } - - X x = new X (); - org.openide.util.RequestProcessor.getDefault ().post (x).waitFinished (); - */ - } - /** Implementation of the CES */ - private final class CES extends CloneableEditorSupport { + protected final class CES extends CloneableEditorSupport { public int notifyUnmodified; public int notifyModified; @@ -235,6 +297,14 @@ boolean retValue; retValue = super.notifyModified(); return retValue; + } + + protected javax.swing.text.EditorKit createEditorKit() { + javax.swing.text.EditorKit k = NotifyModifiedTest.this.createEditorKit (); + if (k != null) { + return k; + } + return super.createEditorKit (); } }