Index: openide-spec-vers.properties =================================================================== RCS file: /cvs/openide/openide-spec-vers.properties,v retrieving revision 1.163 diff -u -r1.163 openide-spec-vers.properties --- openide-spec-vers.properties 6 Dec 2004 14:39:46 -0000 1.163 +++ openide-spec-vers.properties 6 Jan 2005 15:56:36 -0000 @@ -4,4 +4,4 @@ # Must always be numeric (numbers separated by '.', e.g. 4.11). # See http://openide.netbeans.org/versioning-policy.html for more. -openide.specification.version=5.2 +openide.specification.version=5.3 Index: 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 6 Jan 2005 15:56:36 -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 6 Jan 2005 15:56:37 -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); @@ -1323,15 +1325,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 +1430,7 @@ } UndoRedo ur = getUndoRedo(); sd.removeDocumentListener(getListener()); + sd.putProperty ("beforeModificationListener", null); // NOI18N try { if(ur.canUndo()) { Toolkit.getDefaultToolkit().beep(); @@ -1435,6 +1441,7 @@ ErrorManager.INFORMATIONAL, cne); } finally { sd.addDocumentListener(getListener()); + sd.putProperty ("beforeModificationListener", getListener ()); // NOI18N } } }; @@ -1494,38 +1501,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 +1592,7 @@ if (doc != null) { doc.removeUndoableEditListener (getUndoRedo ()); doc.removeDocumentListener(getListener()); + doc.putProperty ("beforeModificationListener", null); } if (positionManager != null) { @@ -1949,9 +1925,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 +1970,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 +2043,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 +2069,9 @@ // Start listening on changes in document doc.addDocumentListener(getListener()); + doc.putProperty ("beforeModificationListener", getListener ()); // NOI18N // NOI18N } + // } } @@ -2286,7 +2278,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 6 Jan 2005 15:56:37 -0000 @@ -14,6 +14,7 @@ package org.openide.text; +import java.beans.VetoableChangeListener; import javax.swing.text.*; /** @@ -88,5 +89,46 @@ public java.awt.Color getForeground(javax.swing.text.AttributeSet attr) { return null; } + + public void insertString (int offs, String str, AttributeSet a) throws BadLocationException { + insOrRemove (offs, str, a, 0, true); + } + + public void remove (int offs, int len) throws BadLocationException { + insOrRemove (offs, null, null, len, false); + } + + + private void insOrRemove (int offset, String str, AttributeSet set, int len, boolean insert) + 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; + } + } + + try { + if (insert) { + super.insertString (offset, str, set); + } else { + super.remove(offset, len); + } + } catch (BadLocationException ex) { + 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 + } + } + throw ex; + } + } + } // end of Doc } 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 6 Jan 2005 15:56:37 -0000 @@ -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) { @@ -57,6 +59,15 @@ } public void testJustOneCallToModified () throws Exception { + doJustOneCallToModified (); + } + + public void testJustOneCallToModifiedWithNbLikeKit () throws Exception { + editorKit = new NbLikeEditorKit (); + doJustOneCallToModified (); + } + + private void doJustOneCallToModified () throws Exception { content = "Line1\nLine2\n"; // in order to set.getLines() work correctly, the document has to be loaded @@ -80,6 +91,14 @@ } public void testTheDocumentReturnsBackIfModifyIsNotAllowed () throws Exception { + doTheDocumentReturnsBackIfModifyIsNotAllowed (); + } + public void testTheDocumentReturnsBackIfModifyIsNotAllowedWithNbLikeKit () throws Exception { + editorKit = new NbLikeEditorKit (); + doTheDocumentReturnsBackIfModifyIsNotAllowed (); + } + + private void doTheDocumentReturnsBackIfModifyIsNotAllowed () throws Exception { content = "Nic\n"; // in order to set.getLines() work correctly, the document has to be loaded @@ -91,6 +110,37 @@ // 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 { + doBadLocationException (0); + } + public void testBadLocationExceptionNbLikeKit () throws Exception { + editorKit = new NbLikeEditorKit (); + doBadLocationException (1); + } + + private void doBadLocationException (int expected) 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 + } + + 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); @@ -166,28 +216,29 @@ } 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; + if (editorKit instanceof NbLikeEditorKit) { + 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 (); + assertTrue ("No lock is held on document when running notifyModified", x.ok); } - - X x = new X (); - org.openide.util.RequestProcessor.getDefault ().post (x).waitFinished (); - */ } /** Implementation of the CES */ @@ -235,6 +286,13 @@ boolean retValue; retValue = super.notifyModified(); return retValue; + } + + protected javax.swing.text.EditorKit createEditorKit() { + if (editorKit != null) { + return editorKit; + } + return super.createEditorKit (); } }