--- a/editor.lib/src/org/netbeans/editor/BaseDocument.java +++ a/editor.lib/src/org/netbeans/editor/BaseDocument.java @@ -106,12 +106,7 @@ import org.netbeans.modules.editor.lib.drawing.DrawGraphics; import org.netbeans.modules.editor.lib.impl.MarkVector; import org.netbeans.modules.editor.lib.impl.MultiMark; -import org.netbeans.modules.editor.lib2.document.ContentEdit; -import org.netbeans.modules.editor.lib2.document.EditorDocumentContent; -import org.netbeans.modules.editor.lib2.document.LineElementRoot; -import org.netbeans.modules.editor.lib2.document.ReadWriteBuffer; -import org.netbeans.modules.editor.lib2.document.ReadWriteUtils; -import org.netbeans.modules.editor.lib2.document.StableCompoundEdit; +import org.netbeans.modules.editor.lib2.document.*; import org.netbeans.spi.lexer.MutableTextInput; import org.netbeans.spi.lexer.TokenHierarchyControl; import org.openide.filesystems.FileObject; @@ -131,6 +126,7 @@ static { EditorPackageAccessor.register(new Accessor()); + EditorDocumentHandler.setEditorDocumentServices(BaseDocument.class, BaseDocumentServices.INSTANCE); } // -J-Dorg.netbeans.editor.BaseDocument.level=FINE @@ -347,6 +343,8 @@ private UndoableEdit removeUpdateLineUndo; private DocumentFilter.FilterBypass filterBypass; + + private int runExclusiveDepth; private Preferences prefs; private final PreferenceChangeListener prefsListener = new PreferenceChangeListener() { @@ -997,7 +995,7 @@ private void checkModifiable(int offset) throws BadLocationException { if (!modifiable) { - throw new GuardedException("Modification prohibited", offset); + throw new GuardedException("Modification prohibited", offset); // NOI18N } } @@ -1618,6 +1616,10 @@ final void atomicLockImpl () { boolean alreadyAtomicLocker; synchronized (this) { + if (runExclusiveDepth > 0) { + throw new IllegalStateException( + "Document modifications or atomic locking not allowed in runExclusive()"); // NOI18N + } alreadyAtomicLocker = Thread.currentThread() == getCurrentWriter() && atomicDepth > 0; if (alreadyAtomicLocker) { atomicDepth++; @@ -1765,6 +1767,29 @@ return atomicDepth; } + void runExclusive(Runnable r) { + boolean writeLockDone = false; + synchronized (this) { + Thread currentWriter = getCurrentWriter(); + if (currentWriter != Thread.currentThread()) { + assert (runExclusiveDepth == 0) : "runExclusiveDepth=" + runExclusiveDepth + " != 0"; // NOI18N + writeLock(); + writeLockDone = true; + } + runExclusiveDepth++; + } + try { + r.run(); + } finally { + runExclusiveDepth--; + if (writeLockDone) { + writeUnlock(); + assert (runExclusiveDepth == 0) : "runExclusiveDepth=" + runExclusiveDepth + " != 0"; // NOI18N + } + + } + } + @Override public void addDocumentListener(DocumentListener listener) { if (LOG_LISTENER.isLoggable(Level.FINE)) { @@ -2013,7 +2038,7 @@ if (atomicEdits == null) atomicEdits = new AtomicCompoundEdit(); } - + public @Override String toString() { return super.toString() + ", mimeType='" + mimeType + "'" + //NOI18N @@ -2356,4 +2381,23 @@ handleInsertString(offset, text, attrs); } } + + /** + * Implementation of EditorDocumentServices for BaseDocument. + * + * @author Miloslav Metelka + */ + private static final class BaseDocumentServices implements EditorDocumentServices { + + static final EditorDocumentServices INSTANCE = new BaseDocumentServices(); + + @Override + public void runExclusive(Document doc, Runnable r) { + BaseDocument bDoc = (BaseDocument) doc; + bDoc.runExclusive(r); + } + + + } + } --- a/editor.lib/test/unit/src/org/netbeans/editor/BaseDocumentTest.java +++ a/editor.lib/test/unit/src/org/netbeans/editor/BaseDocumentTest.java @@ -47,6 +47,8 @@ import javax.swing.undo.UndoManager; import org.netbeans.junit.NbTestCase; import org.netbeans.lib.editor.util.swing.DocumentUtilities; +import org.openide.util.Exceptions; +import org.openide.util.RequestProcessor; /** * Test functionality of BaseDocument. @@ -58,7 +60,194 @@ public BaseDocumentTest(String testName) { super(testName); } + + public void testRunExclusive() throws Exception { + final BaseDocument doc = new BaseDocument(false, "text/plain"); // NOI18N + doc.insertString(0, "Nazdar", null); + + doc.runExclusive(new Runnable() { + @Override + public void run() { + try { + doc.getText(0, doc.getLength()); + } catch (Exception ex) { + fail("Unexpected exception ex=" + ex); + } + } + }); + + doc.runExclusive(new Runnable() { + @Override + public void run() { + try { + doc.insertString(0, "a", null); + fail("Exception expected upon insertString()"); + } catch (IllegalStateException ex) { + // Expected + } catch (Exception ex) { + fail("Unexpected exception ex=" + ex); + } + } + }); + + doc.runExclusive(new Runnable() { + @Override + public void run() { + try { + doc.runAtomic(new Runnable() { + @Override + public void run() { + fail("Should never run"); + } + }); + fail("Exception expected upon runAtomic()"); + } catch (IllegalStateException ex) { + // Expected + } catch (Exception ex) { + fail("Unexpected exception ex=" + ex); + } + } + }); + + doc.runAtomic(new Runnable() { + @Override + public void run() { + doc.runExclusive(new Runnable() { + @Override + public void run() { + try { + doc.getText(0, doc.getLength()); + } catch (BadLocationException ex) { + fail("Unexpected exception ex=" + ex); + } + } + }); + } + }); + + doc.runAtomic(new Runnable() { + @Override + public void run() { + doc.runExclusive(new Runnable() { + @Override + public void run() { + try { + doc.insertString(0, "a", null); + } catch (IllegalStateException ex) { + // Expected + } catch (BadLocationException ex) { + fail("Unexpected exception ex=" + ex); + } + } + }); + } + }); + + // doc.render() in runExclusive() + doc.runExclusive(new Runnable() { + @Override + public void run() { + doc.render(new Runnable() { + @Override + public void run() { + try { + doc.getText(0, doc.getLength()); + } catch (BadLocationException ex) { + fail("Unexpected exception ex=" + ex); + } + } + }); + } + }); + + // Nested runExclusive() + doc.runExclusive(new Runnable() { + @Override + public void run() { + doc.runExclusive(new Runnable() { + @Override + public void run() { + try { + doc.getText(0, doc.getLength()); + } catch (BadLocationException ex) { + fail("Unexpected exception ex=" + ex); + } + } + }); + } + }); + } + + public void testRunExclusiveThreading() throws Exception { + final BaseDocument doc = new BaseDocument(false, "text/plain"); // NOI18N + doc.insertString(0, "Nazdar", null); + + // Test thread access (runExclusive() and attempt read lock. + final boolean t2Started[] = new boolean[1]; + final boolean t2DocAccess[] = new boolean[1]; + doc.runExclusive(new Runnable() { + @Override + public void run() { + RequestProcessor.getDefault().post(new Runnable() { + @Override + public void run() { + t2Started[0] = true; + doc.render(new Runnable() { + @Override + public void run() { + t2DocAccess[0] = true; + } + }); + } + }); + while (!t2Started[0]) { + tSleep(1); + } + tSleep(5); + assertFalse("Read lock access granted when in runExclusive", t2DocAccess[0]); + } + }); + tSleep(2); + assertTrue("Read lock access not granted in T2", t2DocAccess[0]); + + // Reversed test (read lock and attempt runExclusive()). + t2Started[0] = false; + t2DocAccess[0] = false; + doc.render(new Runnable() { + @Override + public void run() { + RequestProcessor.getDefault().post(new Runnable() { + @Override + public void run() { + t2Started[0] = true; + doc.runExclusive(new Runnable() { + @Override + public void run() { + t2DocAccess[0] = true; + } + }); + } + }); + while (!t2Started[0]) { + tSleep(1); + } + tSleep(5); + assertFalse("runExclusive doc access granted when in render()", t2DocAccess[0]); + } + }); + tSleep(2); + assertTrue("runExclusive() access not granted in T2", t2DocAccess[0]); + } + + private static final void tSleep(long millis) { + try { + Thread.sleep(millis); + } catch (InterruptedException ex) { + fail("Failed sleep"); + } + } + public void testBackwardBiasPosition() throws Exception { BaseDocument doc = new BaseDocument(false, "text/plain"); // NOI18N UndoManager undoManager = new UndoManager(); --- a/editor.lib2/apichanges.xml +++ a/editor.lib2/apichanges.xml @@ -107,6 +107,21 @@ + + EditorDocumentUtils.runExclusive method added + + + + + +

+ Added EditorDocumentUtils.runExclusive(Runnable) method for gaining + exclusive access to document without making any document mutations. +

+
+ +
+ Add "weight" attribute to EditorActionRegistration. --- a/editor.lib2/manifest.mf +++ a/editor.lib2/manifest.mf @@ -1,6 +1,6 @@ Manifest-Version: 1.0 OpenIDE-Module: org.netbeans.modules.editor.lib2/1 -OpenIDE-Module-Implementation-Version: 26 +OpenIDE-Module-Implementation-Version: 27 OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/editor/lib2/Bundle.properties OpenIDE-Module-Layer: org/netbeans/modules/editor/lib2/resources/layer.xml OpenIDE-Module-Needs: org.netbeans.modules.editor.actions --- a/editor.lib2/nbproject/project.properties +++ a/editor.lib2/nbproject/project.properties @@ -43,7 +43,7 @@ is.autoload=true javac.source=1.6 javac.compilerargs=-Xlint:unchecked -spec.version.base=1.55.0 +spec.version.base=1.56.0 javadoc.arch=${basedir}/arch.xml javadoc.apichanges=${basedir}/apichanges.xml --- a/editor.lib2/nbproject/project.xml +++ a/editor.lib2/nbproject/project.xml @@ -187,6 +187,7 @@ org.netbeans.api.editor + org.netbeans.api.editor.document org.netbeans.spi.editor.codegen org.netbeans.spi.editor.highlighting org.netbeans.spi.editor.highlighting.support --- a/editor.lib2/src/org/netbeans/api/editor/document/EditorDocumentUtils.java +++ a/editor.lib2/src/org/netbeans/api/editor/document/EditorDocumentUtils.java @@ -0,0 +1,94 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2012 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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]" + * + * 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. + * + * Contributor(s): + * + * Portions Copyrighted 2012 Sun Microsystems, Inc. + */ +package org.netbeans.api.editor.document; + +import javax.swing.text.Document; +import org.netbeans.modules.editor.lib2.document.EditorDocumentHandler; + +/** + * Utilities operation on top of an editor document. + * + * @author Miloslav Metelka + * @since 1.56 + */ +public final class EditorDocumentUtils { + + private EditorDocumentUtils() { + // No instances + } + + /** + * Execute a non mutating runnable under an exclusive document lock over the document + * (no other read locks or write locks are taking place). + *
+ * Nested calls to {@link #runExclusive(Document, Runnable) } are allowed. + * The given runnable may also call {@link Document#render(Runnable) }. + *
+ * However any mutations by {@link Document#insertString(int, String, javax.swing.text.AttributeSet) } + * or {@link Document#remove(int, int) } are prohibited. + *
+ * Calling atomic transactions (BaseDocument.runAtomic() or NbDocument.runAtomic()) + * from the given runnable is prohibited as well. + *
+ * Calls to {@link #runExclusive(Document, Runnable) } within an atomic section + * are allowed (but no document mutations may be done during runExclusive() call). + *
+ * Calls to {@link Document#render(java.lang.Runnable) } within + * {@link #runExclusive(Document, Runnable) } are allowed. + *
+ * Calls to {@link #runExclusive(Document, Runnable) } within + * {@link Document#render(java.lang.Runnable) } are prohibited and may lead to starvation. + * + * @param doc document being exclusively locked. For non-editor document implementations + * (currently org.netbeans.editor.BaseDocument) the implementation + * synchronizes over the document and does not check for mutations within runExclusive(). + * @param r runnable to be performed. It is not allowed to mutate the document by any insertions or removals. + * @throws IllegalStateException in case the given runnable wants to mutate the document. + * + * @since 1.56 + */ + public static void runExclusive(Document doc, Runnable r) { + EditorDocumentHandler.runExclusive(doc, r); + } + + +} --- a/editor.lib2/src/org/netbeans/modules/editor/lib2/document/EditorDocumentHandler.java +++ a/editor.lib2/src/org/netbeans/modules/editor/lib2/document/EditorDocumentHandler.java @@ -0,0 +1,81 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2012 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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]" + * + * 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. + * + * Contributor(s): + * + * Portions Copyrighted 2012 Sun Microsystems, Inc. + */ +package org.netbeans.modules.editor.lib2.document; + +import javax.swing.text.Document; + +/** + * Performer of various document services implemented currently + * by org.netbeans.editor.BaseDocument in editor.lib module. + * + * @author Miloslav Metelka + */ +public final class EditorDocumentHandler { + + private EditorDocumentHandler() { + // no instances + } + + public static Class editorDocClass; + + public static EditorDocumentServices editorDocServices; + + public static void setEditorDocumentServices(Class docClass, EditorDocumentServices docServices) { + // Currently expect just a single implementation: BaseDocument + if (editorDocClass != null) { + throw new IllegalStateException("Only single registration expected. Already registered: " + editorDocClass); + } + EditorDocumentHandler.editorDocClass = docClass; + EditorDocumentHandler.editorDocServices = docServices; + } + + public static void runExclusive(Document doc, Runnable r) { + if (doc.getClass() == editorDocClass) { + editorDocServices.runExclusive(doc, r); + } else { + synchronized (doc) { + r.run(); + } + } + } + +} --- a/editor.lib2/src/org/netbeans/modules/editor/lib2/document/EditorDocumentServices.java +++ a/editor.lib2/src/org/netbeans/modules/editor/lib2/document/EditorDocumentServices.java @@ -0,0 +1,62 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2012 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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]" + * + * 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. + * + * Contributor(s): + * + * Portions Copyrighted 2012 Sun Microsystems, Inc. + */ +package org.netbeans.modules.editor.lib2.document; + +import javax.swing.text.Document; + +/** + * Various services for a document implementation + * (currently only org.netbeans.editor.BaseDocument). + *
+ * This class together with EditorDocumentHandler allows an efficient + * performing of methods from {@link org.netbeans.api.editor.document.EditorDocumentUtils}. + * + * @author Miloslav Metelka + */ +public interface EditorDocumentServices { + + /** + * @see {@link org.netbeans.api.editor.document.EditorDocumentUtils#runExclusive(java.lang.Runnable)}. + */ + void runExclusive(Document doc, Runnable r); + +}