diff --git a/editor.lib/nbproject/project.xml b/editor.lib/nbproject/project.xml --- a/editor.lib/nbproject/project.xml +++ b/editor.lib/nbproject/project.xml @@ -202,6 +202,11 @@ + org.netbeans.modules.editor.mimelookup + + + + org.netbeans.modules.editor.settings.storage diff --git a/editor.lib/src/org/netbeans/editor/BaseDocument.java b/editor.lib/src/org/netbeans/editor/BaseDocument.java --- a/editor.lib/src/org/netbeans/editor/BaseDocument.java +++ b/editor.lib/src/org/netbeans/editor/BaseDocument.java @@ -45,7 +45,6 @@ package org.netbeans.editor; import java.awt.Font; -import java.beans.PropertyVetoException; import java.beans.VetoableChangeListener; import java.util.Hashtable; import java.util.Dictionary; @@ -56,6 +55,7 @@ import java.beans.PropertyChangeListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeSupport; +import java.util.Collection; import java.util.EventListener; import java.util.HashSet; import java.util.Set; @@ -112,11 +112,10 @@ 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.spi.editor.document.UndoableEditWrapper; import org.netbeans.spi.lexer.MutableTextInput; import org.netbeans.spi.lexer.TokenHierarchyControl; import org.openide.filesystems.FileObject; -import org.openide.util.RequestProcessor; -import org.openide.util.RequestProcessor.Task; import org.openide.util.WeakListeners; /** @@ -345,6 +344,8 @@ private CharSequence text; private UndoableEdit removeUpdateLineUndo; + + private Collection undoEditWrappers; private DocumentFilter.FilterBypass filterBypass; @@ -564,6 +565,8 @@ findSupportChange(null); // update doc by find settings TrailingWhitespaceRemove.install(this); + + undoEditWrappers = MimeLookup.getLookup(mimeType).lookupAll(UndoableEditWrapper.class); if (weakPrefsListener == null) { // the listening could have already been initialized from setMimeType(), which @@ -1571,6 +1574,18 @@ } protected @Override void fireUndoableEditUpdate(UndoableEditEvent e) { + // Possibly wrap contained edit + if (undoEditWrappers != null) { + UndoableEdit origEdit = e.getEdit(); + UndoableEdit edit = origEdit; + for (UndoableEditWrapper wrapper : undoEditWrappers) { + edit = wrapper.wrap(edit, this); + } + if (edit != origEdit) { + e = new UndoableEditEvent(this, edit); + } + } + // Fire to the list of listeners that was used before the atomic lock started // This fixes issue #47881 and appears to be somewhat more logical // than the default approach to fire all the current listeners diff --git a/editor.lib/test/unit/src/org/netbeans/editor/TestingUndoableEditWrapper.java b/editor.lib/test/unit/src/org/netbeans/editor/TestingUndoableEditWrapper.java new file mode 100644 --- /dev/null +++ b/editor.lib/test/unit/src/org/netbeans/editor/TestingUndoableEditWrapper.java @@ -0,0 +1,72 @@ +/* + * 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.editor; + +import javax.swing.text.Document; +import javax.swing.undo.CompoundEdit; +import javax.swing.undo.UndoableEdit; +import org.netbeans.spi.editor.document.UndoableEditWrapper; + +/** + * + * @author mmetelka + */ +//@MimeRegistration(mimeType="", service=UndoableEditWrapper.class) +public class TestingUndoableEditWrapper implements UndoableEditWrapper { + + @Override + public UndoableEdit wrap(UndoableEdit edit, Document doc) { + WrapCompoundEdit wrapEdit = new WrapCompoundEdit(); + wrapEdit.addEdit(edit); + wrapEdit.end(); + return wrapEdit; + } + + static final class WrapCompoundEdit extends CompoundEdit { + + WrapCompoundEdit() { + } + + } + + +} diff --git a/editor.lib/test/unit/src/org/netbeans/editor/UndoableEditWrapperTest.java b/editor.lib/test/unit/src/org/netbeans/editor/UndoableEditWrapperTest.java new file mode 100644 --- /dev/null +++ b/editor.lib/test/unit/src/org/netbeans/editor/UndoableEditWrapperTest.java @@ -0,0 +1,80 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 1997-2010 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]" + * + * 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.netbeans.editor; + +import javax.swing.event.UndoableEditEvent; +import javax.swing.event.UndoableEditListener; +import javax.swing.undo.UndoableEdit; +import org.netbeans.api.editor.mimelookup.MimePath; +import org.netbeans.api.editor.mimelookup.test.MockMimeLookup; +import org.netbeans.junit.NbTestCase; + +/** + * + * @author Miloslav Metelka + */ +public class UndoableEditWrapperTest extends NbTestCase { + + /** Creates a new instance of ZOrderTest */ + public UndoableEditWrapperTest(String name) { + super(name); + } + + public void testWrapping() throws Exception { + MimePath mimePath = MimePath.EMPTY; + MockMimeLookup.setInstances(mimePath, new TestingUndoableEditWrapper()); + BaseDocument bDoc = new BaseDocument(false, ""); + bDoc.addUndoableEditListener(new UndoableEditListener() { + @Override + public void undoableEditHappened(UndoableEditEvent e) { + UndoableEdit edit = e.getEdit(); + assertEquals("Expected WrapCompoundEdit.class", + TestingUndoableEditWrapper.WrapCompoundEdit.class, edit.getClass()); + } + }); + bDoc.insertString(0, "Test", null); + } + +} diff --git a/editor.lib2/manifest.mf b/editor.lib2/manifest.mf --- a/editor.lib2/manifest.mf +++ b/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 diff --git a/editor.lib2/nbproject/project.properties b/editor.lib2/nbproject/project.properties --- a/editor.lib2/nbproject/project.properties +++ b/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 diff --git a/editor.lib2/nbproject/project.xml b/editor.lib2/nbproject/project.xml --- a/editor.lib2/nbproject/project.xml +++ b/editor.lib2/nbproject/project.xml @@ -188,6 +188,7 @@ org.netbeans.api.editor org.netbeans.spi.editor.codegen + org.netbeans.spi.editor.document org.netbeans.spi.editor.highlighting org.netbeans.spi.editor.highlighting.support org.netbeans.spi.editor.typinghooks diff --git a/editor.lib2/src/org/netbeans/spi/editor/document/UndoableEditWrapper.java b/editor.lib2/src/org/netbeans/spi/editor/document/UndoableEditWrapper.java new file mode 100644 --- /dev/null +++ b/editor.lib2/src/org/netbeans/spi/editor/document/UndoableEditWrapper.java @@ -0,0 +1,68 @@ +/* + * 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.spi.editor.document; + +import javax.swing.text.Document; +import javax.swing.undo.UndoableEdit; +import org.netbeans.api.annotations.common.NonNull; + +/** + * Wrap undoable edits generated by document implementation into a custom undoable edit. + *
+ * Instances should be registered by using @MimeRegistration. + * + * @author Miloslav Metelka + */ +public interface UndoableEditWrapper { + + /** + * Wrap given undoable edit by a custom undoable edit implementation + * (or leave it as it is). + * + * @param edit original undoable edit generated by document (or previous wrapper). + * @param doc document which generated the original undoable edit. + * @return wrap edit or original edit. + * @since 1.56 + */ + @NonNull UndoableEdit wrap(@NonNull UndoableEdit edit, @NonNull Document doc); + +} diff --git a/openide.text/src/org/openide/text/NbDocument.java b/openide.text/src/org/openide/text/NbDocument.java --- a/openide.text/src/org/openide/text/NbDocument.java +++ b/openide.text/src/org/openide/text/NbDocument.java @@ -51,6 +51,8 @@ import javax.swing.JToolBar; import javax.swing.SwingUtilities; import javax.swing.text.*; +import javax.swing.undo.UndoableEdit; +import org.openide.awt.UndoRedo; import org.openide.cookies.EditorCookie; @@ -519,6 +521,29 @@ ((Annotatable) doc).removeAnnotation(annotation); } + public static T getEditToBeUndoneOfType(EditorCookie ec, Class type) { + return getEditToBeUndoneRedoneOfType(ec, type, false); + } + + public static T getEditToBeRedoneOfType(EditorCookie ec, Class type) { + return getEditToBeUndoneRedoneOfType(ec, type, true); + } + + private static T getEditToBeUndoneRedoneOfType(EditorCookie ec, Class type, boolean redone) { + UndoRedo ur; + if (ec instanceof CloneableEditorSupport && + ((ur = ((CloneableEditorSupport)ec).getUndoRedo()) instanceof UndoRedoManager)) + { + UndoRedoManager urManager = (UndoRedoManager) ur; + UndoableEdit edit = urManager.editToBeUndoneRedone(redone); + if (type.isInstance(edit)) { + @SuppressWarnings("unchecked") T inst = (T) edit; + return inst; + } + } + return null; + } + /** Specialized version of document that knows how to lock the document * for complex modifications. */ diff --git a/openide.text/src/org/openide/text/UndoRedoManager.java b/openide.text/src/org/openide/text/UndoRedoManager.java --- a/openide.text/src/org/openide/text/UndoRedoManager.java +++ b/openide.text/src/org/openide/text/UndoRedoManager.java @@ -564,6 +564,11 @@ undoGroup = null; } + UndoableEdit editToBeUndoneRedone(boolean redone) { // Access for NbDocument + WrapUndoEdit wrapEdit = (WrapUndoEdit) (redone ? editToBeRedone() : editToBeUndone()); + return wrapEdit.delegate(); + } + static String editToString(UndoableEdit edit) { if (edit instanceof WrapUndoEdit) { return toStringTerse(edit) + "->" + toStringTerse(((WrapUndoEdit)edit).delegate()); // NOI18N diff --git a/openide.text/test/unit/src/org/openide/text/EditToBeUndoneRedoneTest.java b/openide.text/test/unit/src/org/openide/text/EditToBeUndoneRedoneTest.java new file mode 100644 --- /dev/null +++ b/openide.text/test/unit/src/org/openide/text/EditToBeUndoneRedoneTest.java @@ -0,0 +1,285 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 1997-2010 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]" + * + * 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.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import java.beans.VetoableChangeListener; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Serializable; +import java.util.Date; +import javax.swing.event.UndoableEditEvent; +import javax.swing.text.AttributeSet; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import javax.swing.text.EditorKit; +import javax.swing.text.Position; +import javax.swing.text.StyledDocument; +import javax.swing.undo.AbstractUndoableEdit; +import javax.swing.undo.CannotRedoException; +import javax.swing.undo.CannotUndoException; +import javax.swing.undo.CompoundEdit; +import javax.swing.undo.UndoableEdit; +import org.netbeans.junit.NbTestCase; +import org.openide.awt.UndoRedo; +import org.openide.cookies.EditorCookie; +import org.openide.text.CloneableEditorSupport.Env; +import org.openide.util.Exceptions; +import org.openide.util.Lookup; +import org.openide.util.RequestProcessor; +import org.openide.util.RequestProcessor.Task; +import org.openide.windows.CloneableOpenSupport; +import org.openide.windows.CloneableTopComponent; + +/** + * Deadlock of a thread simulating reloading of document and another thread trying to close the file. + * + * @author Miloslav Metelka + */ +public class EditToBeUndoneRedoneTest extends NbTestCase +implements CloneableEditorSupport.Env { + static { + System.setProperty("org.openide.windows.DummyWindowManager.VISIBLE", "false"); + } + /** the support to work with */ + private transient CES support; + + // Env variables + private transient String content = ""; + private transient boolean valid = true; + private transient boolean modified = false; + /** if not null contains message why this document cannot be modified */ + private transient String cannotBeModified; + private transient Date date = new Date (); + private transient final PropertyChangeSupport pcl; + private transient VetoableChangeListener vetoL; + + private transient volatile boolean inReloadBeforeSupportLock; + private transient volatile boolean closing; + + private static EditToBeUndoneRedoneTest RUNNING; + + public EditToBeUndoneRedoneTest(String s) { + super(s); + pcl = new PropertyChangeSupport(this); + } + + protected void setUp () { + support = new CES (this, Lookup.EMPTY); + RUNNING = this; + } + + protected boolean runInEQ() { + return false; + } + + @Override + protected int timeOut() { + return 15000; + } + + private Object writeReplace () { + return new Replace (); + } + + public void testUndoRedoEdits() throws Exception { + Document doc = support.openDocument(); + doc.insertString(0, "a", null); + UndoRedo.Manager ur = support.getUndoRedo(); + MyEdit myEdit = NbDocument.getEditToBeUndoneOfType(support, MyEdit.class); + assertNotNull("Expected valid myEdit", myEdit); + ur.undo(); + myEdit = NbDocument.getEditToBeRedoneOfType(support, MyEdit.class); + assertNotNull("Expected valid myEdit", myEdit); + } + + // + // Implementation of the CloneableEditorSupport.Env + // + + public synchronized void addPropertyChangeListener(PropertyChangeListener l) { + pcl.addPropertyChangeListener(l); + } + public synchronized void removePropertyChangeListener(PropertyChangeListener l) { + pcl.removePropertyChangeListener(l); + } + + public synchronized void addVetoableChangeListener(VetoableChangeListener l) { + assertNull ("This is the first veto listener", vetoL); + vetoL = l; + } + public void removeVetoableChangeListener(VetoableChangeListener l) { + assertEquals ("Removing the right veto one", vetoL, l); + vetoL = null; + } + + public CloneableOpenSupport findCloneableOpenSupport() { + return RUNNING.support; + } + + public String getMimeType() { + return "text/plain"; + } + + public Date getTime() { + return date; + } + + public InputStream inputStream() throws IOException { + return new ByteArrayInputStream (content.getBytes ()); + } + public OutputStream outputStream() throws IOException { + class ContentStream extends ByteArrayOutputStream { + public void close () throws IOException { + super.close (); + content = new String (toByteArray ()); + } + } + + return new ContentStream (); + } + + public boolean isValid() { + return valid; + } + + public boolean isModified() { + return modified; + } + + public void markModified() throws IOException { + if (cannotBeModified != null) { + final String notify = cannotBeModified; + IOException e = new IOException () { + public String getLocalizedMessage () { + return notify; + } + }; + Exceptions.attachLocalizedMessage(e, cannotBeModified); + throw e; + } + + modified = true; + } + + public void unmarkModified() { + modified = false; + } + + /** Implementation of the CES */ + private static final class CES extends CloneableEditorSupport implements EditorCookie { + public CES (Env env, Lookup l) { + super (env, l); + } + + @Override + protected EditorKit createEditorKit () { + // Important to use NbLikeEditorKit since otherwise FilterDocument + // would be created with improper runAtomic() + return new MyKit (); + } + public CloneableTopComponent.Ref getRef () { + return allEditors; + } + + 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"; + } + + } + + private static final class Replace implements Serializable { + public Object readResolve () { + return RUNNING; + } + } + + private static final class MyEdit extends CompoundEdit { // Marker custom undo edit + + } + + private static final class MyKit extends NbLikeEditorKit { + + @Override + public Document createDefaultDocument() { + return new Doc() { + + @Override + protected void fireUndoableEditUpdate(UndoableEditEvent e) { + UndoableEdit edit = e.getEdit(); + MyEdit wrapEdit = new MyEdit(); + wrapEdit.addEdit(edit); + wrapEdit.end(); + e = new UndoableEditEvent(e.getSource(), wrapEdit); + + super.fireUndoableEditUpdate(e); + } + + }; + } + + + } +}