diff -r 3aaa9fcea5e4 openide.text/src/org/openide/text/CloneableEditorSupport.java --- a/openide.text/src/org/openide/text/CloneableEditorSupport.java Thu Jul 31 17:25:59 2014 +0200 +++ b/openide.text/src/org/openide/text/CloneableEditorSupport.java Thu Aug 07 16:16:46 2014 +0200 @@ -629,7 +629,28 @@ } return; } - + synchronized (this) { + while (isSaving) { + try { + wait(); + } catch (InterruptedException ex) { + ERR.log(Level.INFO, null, ex); + } + } + isSaving = true; + } + try { + saveDocumentImpl(myDoc, log); + } finally { + synchronized (this) { + isSaving = false; + notifyAll(); + } + } + } + private transient boolean isSaving; + + private void saveDocumentImpl(final StyledDocument myDoc, final boolean log) throws IOException { long prevLST = lastSaveTime; if (prevLST != -1) { final long externalMod = cesEnv().getTime().getTime(); diff -r 3aaa9fcea5e4 openide.text/test/unit/src/org/openide/text/CloneableEditorSupportDoubleSaveTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/openide.text/test/unit/src/org/openide/text/CloneableEditorSupportDoubleSaveTest.java Thu Aug 07 16:16:46 2014 +0200 @@ -0,0 +1,291 @@ +/* + * 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.openide.text; + +import java.awt.GraphicsEnvironment; +import java.beans.PropertyChangeListener; +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.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import javax.swing.text.Document; +import junit.framework.Test; +import junit.framework.TestSuite; +import org.netbeans.junit.NbTestCase; +import org.openide.nodes.Node; +import org.openide.util.Exceptions; +import org.openide.util.Lookup; +import org.openide.util.RequestProcessor; +import org.openide.windows.CloneableOpenSupport; +import org.openide.windows.CloneableTopComponent; + +/** + * + * @author Miloslav Metelka + */ +public class CloneableEditorSupportDoubleSaveTest extends NbTestCase +implements CloneableEditorSupport.Env { + + public static Test suite() { + return GraphicsEnvironment.isHeadless() ? new TestSuite() : new TestSuite(CloneableEditorSupportDoubleSaveTest.class); + } + + 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 List/**/ propL = new ArrayList (); + private transient VetoableChangeListener vetoL; + + private transient AtomicBoolean saveToStreamStarted = new AtomicBoolean(); + private transient AtomicBoolean docModDuringSaveStream = new AtomicBoolean(); + private transient AtomicBoolean saveDocumentFinished = new AtomicBoolean(); + + private static CloneableEditorSupportDoubleSaveTest RUNNING; + private Thread letMeGo; + + public CloneableEditorSupportDoubleSaveTest(String s) { + super(s); + } + + @Override + protected void setUp () { + support = new CES (this, Lookup.EMPTY); + RUNNING = this; + } + + @Override + protected boolean runInEQ() { + return true; + } + + private Object writeReplace () { + return new Replace (); + } + + public void testModDuringLongOutputStreamSave() throws Exception { + Document doc = support.openDocument(); + doc.insertString(0, "a", null); + RequestProcessor.getDefault().post(new Runnable() { + @Override + public void run() { + try { + support.saveDocument(); + saveDocumentFinished.set(true); + } catch (IOException ex) { + throw new IllegalStateException(ex); + } + } + }); + + waitFor(saveToStreamStarted); + + doc.insertString(1, "hoj", null); + letMeGo = Thread.currentThread(); + + // shedule a wakeup call + RequestProcessor.getDefault().post(new Runnable() { + @Override + public void run() { + docModDuringSaveStream.set(true); + } + }, 5000); + support.saveDocument(); + docModDuringSaveStream.set(true); + + waitFor(saveDocumentFinished); + + assertFalse("Document has been saved (for 2nd time)", isModified()); + + assertEquals("ahoj", content); + } + + // + // Implementation of the CloneableEditorSupport.Env + // + + private void waitFor(AtomicBoolean b) { + while (!b.get()) { + try { + Thread.sleep(1); + } catch (InterruptedException ex) { + throw new IllegalStateException(ex); + } + } + } + + public synchronized void addPropertyChangeListener(PropertyChangeListener l) { + propL.add (l); + } + public synchronized void removePropertyChangeListener(PropertyChangeListener l) { + propL.remove (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 ()); + } + } + + if (letMeGo != Thread.currentThread()) { + saveToStreamStarted.set(true); + waitFor(docModDuringSaveStream); + } + 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 () { + @Override + 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 { + public CES (CloneableEditorSupport.Env env, Lookup l) { + super (env, l); + } + + 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"; + } + + @Override + protected void initializeCloneableEditor(CloneableEditor editor) { + editor.setActivatedNodes(new Node[] { Node.EMPTY }); + } + + + } + + private static final class Replace implements Serializable { + public Object readResolve () { + return RUNNING; + } + } + +} +