diff -r f013258a9cba java.source/nbproject/project.xml --- a/java.source/nbproject/project.xml Thu Oct 01 10:33:43 2009 +0200 +++ b/java.source/nbproject/project.xml Wed Oct 07 15:16:10 2009 +0200 @@ -352,7 +352,7 @@ - 6.16 + 6.26 diff -r f013258a9cba java.source/src/org/netbeans/modules/java/JavaDataObject.java --- a/java.source/src/org/netbeans/modules/java/JavaDataObject.java Thu Oct 01 10:33:43 2009 +0200 +++ b/java.source/src/org/netbeans/modules/java/JavaDataObject.java Wed Oct 07 15:16:10 2009 +0200 @@ -193,6 +193,11 @@ protected @Override CloneableEditor createCloneableEditor() { return new JavaEditor(this); } + + @Override + protected boolean asynchronousOpen() { + return true; + } public @Override boolean close(boolean ask) { return super.close(ask); diff -r f013258a9cba openide.loaders/nbproject/project.xml --- a/openide.loaders/nbproject/project.xml Thu Oct 01 10:33:43 2009 +0200 +++ b/openide.loaders/nbproject/project.xml Wed Oct 07 15:16:10 2009 +0200 @@ -134,7 +134,7 @@ - 6.16 + 6.26 diff -r f013258a9cba openide.loaders/src/org/openide/loaders/DefaultES.java --- a/openide.loaders/src/org/openide/loaders/DefaultES.java Thu Oct 01 10:33:43 2009 +0200 +++ b/openide.loaders/src/org/openide/loaders/DefaultES.java Wed Oct 07 15:16:10 2009 +0200 @@ -109,7 +109,12 @@ removeSaveCookie(); } - + + @Override + protected boolean asynchronousOpen() { + return true; + } + /** Helper method. Adds save cookie to the data object. */ private void addSaveCookie() { DataObject obj = getDataObject(); diff -r f013258a9cba openide.text/apichanges.xml --- a/openide.text/apichanges.xml Thu Oct 01 10:33:43 2009 +0200 +++ b/openide.text/apichanges.xml Wed Oct 07 15:16:10 2009 +0200 @@ -46,6 +46,28 @@ Text API + + + Add CloneableEditorSupport.asynchronousOpen + + + + + +

+ Add CloneableEditorSupport.asynchronous to control if CloneableEditorSupport.open opens document synchronously or not. + If CloneableEditorSupport.asynchronous return true then CloneableEditorSupport.open opens document synchronously + and handles UserQuestionException. + If CloneableEditorSupport.asynchronous return false then CloneableEditorSupport.open does not open document. Document is then + opened during initialization of CloneableEditor form non AWT thread and UserQuestionException is handled there. + Implementation of method asynchronous in CloneableEditorSupport returns false ie. it keeps original behavior + of CloneableEditorSupport.open. Subclasses can overwrite method asynchronous to return true to avoid blocking AWT thread by + call CloneableEditorSupport.open which calls CloneableEditorSupport.openDocument. +

+
+ + +
Add NbDocument.findRecentEditorPane diff -r f013258a9cba openide.text/manifest.mf --- a/openide.text/manifest.mf Thu Oct 01 10:33:43 2009 +0200 +++ b/openide.text/manifest.mf Wed Oct 07 15:16:10 2009 +0200 @@ -1,7 +1,7 @@ Manifest-Version: 1.0 OpenIDE-Module: org.openide.text OpenIDE-Module-Install: org/netbeans/modules/openide/text/Installer.class -OpenIDE-Module-Specification-Version: 6.25 +OpenIDE-Module-Specification-Version: 6.26 OpenIDE-Module-Localizing-Bundle: org/openide/text/Bundle.properties AutoUpdate-Essential-Module: true diff -r f013258a9cba openide.text/src/org/openide/text/CloneableEditor.java --- a/openide.text/src/org/openide/text/CloneableEditor.java Thu Oct 01 10:33:43 2009 +0200 +++ b/openide.text/src/org/openide/text/CloneableEditor.java Wed Oct 07 15:16:10 2009 +0200 @@ -45,6 +45,7 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.*; +import java.lang.reflect.InvocationTargetException; import java.util.concurrent.Callable; import java.util.logging.Level; import java.util.logging.Logger; @@ -53,6 +54,8 @@ import javax.swing.border.EmptyBorder; import javax.swing.text.*; import org.netbeans.modules.openide.text.Installer; +import org.openide.DialogDisplayer; +import org.openide.NotifyDescriptor; import org.openide.awt.UndoRedo; import org.openide.cookies.EditorCookie; import org.openide.util.*; @@ -222,6 +225,8 @@ /** Flag to avoid recursive call of initVisual. */ private boolean isInInitVisual = false; + private boolean confirmed = false; + public DoInitialize(QuietEditorPane tmp) { this.tmp = tmp; this.tmpComp = initLoading(); @@ -262,7 +267,10 @@ switch (phase++) { case 0: synchronized (CLOSE_LAST_LOCK) { - initNonVisual(); + phase = initNonVisual(phase); + if (phase == Integer.MAX_VALUE) { + break; + } } if (newInitialize()) { WindowManager.getDefault().invokeWhenUIReady(this); @@ -307,7 +315,7 @@ } } - private void initNonVisual() { + private int initNonVisual (int phase) { if (LOG.isLoggable(Level.FINE)) { LOG.log(Level.FINE,"DoInitialize.initNonVisual Enter" + " Time:" + System.currentTimeMillis() @@ -319,9 +327,67 @@ Task prepareTask = support.prepareDocument(); assert prepareTask != null : "Failed to get prepareTask"; prepareTask.waitFinished(); - Throwable ex = support.getPrepareDocumentRuntimeException(); - if (ex instanceof CloneableEditorSupport.DelegateIOExc) { - if ("org.openide.text.DataEditorSupport$Env$ME".equals(ex.getCause().getClass().getName())) { + final Throwable ex = support.getPrepareDocumentRuntimeException(); + if (support.asynchronousOpen()) { + if (ex instanceof CloneableEditorSupport.DelegateIOExc) { + if (ex.getCause() instanceof UserQuestionException) { + class Query implements Runnable { + + private DoInitialize doInit; + + public Query (DoInitialize doInit) { + this.doInit = doInit; + } + + public void run() { + UserQuestionException e = (UserQuestionException) ex.getCause(); + NotifyDescriptor nd = new NotifyDescriptor.Confirmation( + e.getLocalizedMessage(), NotifyDescriptor.YES_NO_OPTION + ); + nd.setOptions(new Object[] { NotifyDescriptor.YES_OPTION, NotifyDescriptor.NO_OPTION }); + + Object res = DialogDisplayer.getDefault().notify(nd); + + if (NotifyDescriptor.OK_OPTION.equals(res)) { + doInit.confirmed = true; + try { + e.confirmed(); + } catch (IOException ex1) { + Exceptions.printStackTrace(ex1); + + return; + } + } else { + return; + } + } + } + + Query query = new Query(this); + try { + SwingUtilities.invokeAndWait(query); + } catch (InterruptedException exc) { + Exceptions.printStackTrace(exc); + } catch (InvocationTargetException exc) { + Exceptions.printStackTrace(exc); + } + if (confirmed) { + prepareTask = support.prepareDocument(); + assert prepareTask != null : "Failed to get prepareTask"; + prepareTask.waitFinished(); + } else { + //Cancel initialization sequence and close editor + SwingUtilities.invokeLater(new Runnable () { + public void run () { + CloneableEditor.this.close(); + } + }); + return Integer.MAX_VALUE; + } + } + } + } else { + if (ex instanceof CloneableEditorSupport.DelegateIOExc) { if (ex.getCause() instanceof UserQuestionException) { UserQuestionException e = (UserQuestionException) ex.getCause(); try { @@ -385,6 +451,7 @@ } notifyAll(); } + return phase; } private void initCustomEditor() { @@ -538,7 +605,7 @@ }); isInInitVisual = false; initVisualFinished = true; - + //#168415: Notify clients that pane creation is finished. CloneableEditorSupport ces = cloneableEditorSupport(); if (ces != null) { @@ -556,6 +623,7 @@ }); } } // end of DoInitialize + @Override protected CloneableTopComponent createClonedObject() { return support.createCloneableTopComponent(); diff -r f013258a9cba openide.text/src/org/openide/text/CloneableEditorSupport.java --- a/openide.text/src/org/openide/text/CloneableEditorSupport.java Thu Oct 01 10:33:43 2009 +0200 +++ b/openide.text/src/org/openide/text/CloneableEditorSupport.java Wed Oct 07 15:16:10 2009 +0200 @@ -98,6 +98,7 @@ import org.openide.util.Exceptions; import org.openide.util.Mutex; import org.openide.util.UserCancelException; +import org.openide.util.WeakSet; /** Support for associating an editor and a Swing {@link Document}. @@ -233,6 +234,9 @@ private Map> lineSetWHM; private boolean annotationsLoaded; + /** Classes that have been warned about overriding asynchronousOpen() */ + private static final Set warnedClasses = new WeakSet(); + /** Creates new CloneableEditorSupport attached to given environment. * * @param env environment that is source of all actions around the @@ -413,7 +417,28 @@ } } } - + + /** + * Controls behavior of method open. + * If it returns false method open will load document synchronously + * and process UserQuestionException. + * If it returns true document will be loaded in CloneableEditor creation ie. asynchronously + * and UserQuestionException will be processed there. Asynchronous loading is added to avoid + * blocking AWT thread when method open is called in AWT thread - issue #171713 + * + * @return + */ + protected boolean asynchronousOpen() { + Class clazz = getClass(); + + if (warnedClasses.add(clazz)) { + Logger.getAnonymousLogger().warning( + clazz.getName() + " should override asynchronousOpen()" //NOI18N + ); + } + return false; + } + /** Overrides superclass method, first processes document preparation. * @see #prepareDocument */ @Override @@ -424,40 +449,44 @@ return; } - try { - if (getListener().loadExc instanceof UserQuestionException) { - getListener().loadExc = null; - prepareTask = null; - documentStatus = DOCUMENT_NO; - } - - //Assign reference to local variable to avoid gc before return - StyledDocument doc = openDocument(); + if (getListener().loadExc instanceof UserQuestionException) { + getListener().loadExc = null; + prepareTask = null; + documentStatus = DOCUMENT_NO; + } + + if (asynchronousOpen()) { super.open(); - } catch (final UserQuestionException e) { - class Query implements Runnable, Callable { + } else { + try { + //Assign reference to local variable to avoid gc before return + StyledDocument doc = openDocument(); + super.open(); + } catch (final UserQuestionException e) { + class Query implements Runnable, Callable { - public void run() { - askUserAndDoOpen(e, this); + public void run() { + askUserAndDoOpen(e, this); + } + + public Void call() throws IOException { + getListener().loadExc = null; + prepareTask = null; + documentStatus = DOCUMENT_NO; + //Assign reference to local variable to avoid gc before return + StyledDocument doc = openDocument(); + + CloneableEditorSupport.super.open(); + return null; + } + } - public Void call() throws IOException { - getListener().loadExc = null; - prepareTask = null; - documentStatus = DOCUMENT_NO; - //Assign reference to local variable to avoid gc before return - StyledDocument doc = openDocument(); - - CloneableEditorSupport.super.open(); - return null; - } - + Query query = new Query(); + Mutex.EVENT.readAccess(query); + } catch (IOException e) { + ERR.log(Level.INFO, null, e); } - - Query query = new Query(); - Mutex.EVENT.readAccess(query); - } catch (IOException e) { - ERR.log(Level.INFO, null, e); } } @@ -567,11 +596,11 @@ * to clear before (used for reloading) */ private Task prepareDocument(final boolean notUsed) { assert Thread.holdsLock(getLock()); - + if (prepareTask != null) { return prepareTask; } - + boolean failed = true; //#144722: Help variable to make sure we always return non null task from prepareDocument diff -r f013258a9cba openide.text/test/unit/src/org/openide/text/CloneableEditorNeverendingLoadingAsync2Test.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/openide.text/test/unit/src/org/openide/text/CloneableEditorNeverendingLoadingAsync2Test.java Wed Oct 07 15:16:10 2009 +0200 @@ -0,0 +1,268 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 1997-2009 Sun Microsystems, Inc. All rights reserved. + * + * 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. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun 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.PropertyChangeEvent; +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 org.netbeans.junit.NbTestCase; +import org.openide.cookies.EditorCookie; +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; + +/** + * Test that CES.open() is not blocked when document loading hangs. + * This is for CES.open asynchronous document loading. + * + * @author Marek Slama + */ +public class CloneableEditorNeverendingLoadingAsync2Test 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 List propL = new ArrayList(); + private transient VetoableChangeListener vetoL; + + private static CloneableEditorNeverendingLoadingAsync2Test RUNNING; + + private boolean blocked; + + public CloneableEditorNeverendingLoadingAsync2Test(String s) { + super(s); + } + + @Override + protected void setUp () { + support = new CES (this, Lookup.EMPTY); + RUNNING = this; + } + + private Object writeReplace () { + return new Replace (); + } + + private int eventCounter; + + /** Test that CES.open finishes even if document loading is blocked */ + public void testOpenFinishes() throws Exception { + class R implements Runnable { + boolean running; + + public void run() { + running = true; + support.open(); + running = false; + } + } + eventCounter = 0; + support.addPropertyChangeListener(new PropertyChangeListener() { + public void propertyChange(PropertyChangeEvent evt) { + if (evt.getPropertyName().equals(EditorCookie.Observable.PROP_OPENED_PANES)) { + eventCounter++; + } + } + }); + + R running = new R(); + Task task = RequestProcessor.getDefault().post(running); + + synchronized (this) { + while (!blocked) { + wait(); + } + } + + task.waitFinished(); + + while (eventCounter < 1) { + Thread.sleep(100); + } + + assertNull("No document is opened", support.getDocument()); + assertFalse("Open is still running", running.running); + } + + // + // Implementation of the CloneableEditorSupport.Env + // + + 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 synchronized InputStream inputStream() throws IOException { + blocked = true; + notifyAll(); + try { + wait(); + } catch (InterruptedException ex) { + Exceptions.printStackTrace(ex); + } + blocked = false; + return new ByteArrayInputStream(new byte[0]); + } + + public OutputStream outputStream() throws IOException { + class ContentStream extends ByteArrayOutputStream { + @Override + 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 () { + @Override + public String getLocalizedMessage () { + return notify; + } + }; + Exceptions.attachLocalizedMessage(e, cannotBeModified); + throw e; + } + + modified = true; + } + + public void unmarkModified() { + modified = false; + } + + /** Implementation of the CES */ + private final class CES extends CloneableEditorSupport { + public CES (Env env, Lookup l) { + super (env, l); + } + + protected String messageName() { + return "Name"; + } + + @Override + protected boolean asynchronousOpen() { + return true; + } + + 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; + } + } +} diff -r f013258a9cba openide.text/test/unit/src/org/openide/text/CloneableEditorNeverendingLoadingAsyncTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/openide.text/test/unit/src/org/openide/text/CloneableEditorNeverendingLoadingAsyncTest.java Wed Oct 07 15:16:10 2009 +0200 @@ -0,0 +1,266 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 1997-2009 Sun Microsystems, Inc. All rights reserved. + * + * 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. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun 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.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 org.netbeans.junit.NbTestCase; +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; + +/** + * Test that CES.getDocument() is not blocked when document is being loaded. + * This is for CES.open asynchronous document loading though it should have + * no impact on this test because it calls directly openDocument. + * + * @author Marek Slama + */ +public class CloneableEditorNeverendingLoadingAsyncTest 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 List propL = new ArrayList(); + private transient VetoableChangeListener vetoL; + + private static CloneableEditorNeverendingLoadingAsyncTest RUNNING; + + private boolean blocked; + + public CloneableEditorNeverendingLoadingAsyncTest(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 testGetDocumentReturnsImmediatelly() throws Exception { + class R implements Runnable { + boolean running; + + public void run() { + running = true; + try { + //When CES.asynchronousOpen returns true then CES.open does not call openDocument + //so we will call openDocument directly here. + support.openDocument(); + } catch (IOException ex) { + } + running = false; + } + } + + R running = new R(); + Task task = RequestProcessor.getDefault().post(running); + + assertFalse("Does not finish the opening as it is blocked", task.waitFinished(1000)); + synchronized (this) { + while (!blocked) { + wait(); + } + } + assertNull("No document is opened", support.getDocument()); + assertTrue("Open is still running", running.running); + synchronized (this) { + notifyAll(); + } + task.waitFinished(); + + assertNotNull("Document open finished", support.getDocument()); + } + + // + // Implementation of the CloneableEditorSupport.Env + // + + 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 synchronized InputStream inputStream() throws IOException { + blocked = true; + notifyAll(); + try { + wait(); + } catch (InterruptedException ex) { + Exceptions.printStackTrace(ex); + } + blocked = false; + return new ByteArrayInputStream(new byte[0]); + } + + public OutputStream outputStream() throws IOException { + class ContentStream extends ByteArrayOutputStream { + @Override + 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 () { + @Override + public String getLocalizedMessage () { + return notify; + } + }; + Exceptions.attachLocalizedMessage(e, cannotBeModified); + throw e; + } + + modified = true; + } + + public void unmarkModified() { + modified = false; + } + + /** Implementation of the CES */ + private final class CES extends CloneableEditorSupport { + public CES (Env env, Lookup l) { + super (env, l); + } + + protected String messageName() { + return "Name"; + } + + @Override + protected boolean asynchronousOpen() { + return true; + } + + 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; + } + } +} diff -r f013258a9cba openide.text/test/unit/src/org/openide/text/CloneableEditorUserQuestionAsyncTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/openide.text/test/unit/src/org/openide/text/CloneableEditorUserQuestionAsyncTest.java Wed Oct 07 15:16:10 2009 +0200 @@ -0,0 +1,402 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 1997-2009 Sun Microsystems, Inc. All rights reserved. + * + * 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. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun 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.awt.Dialog; +import java.beans.PropertyChangeEvent; +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.util.Date; +import javax.swing.SwingUtilities; +import org.netbeans.junit.NbTestCase; +import org.netbeans.junit.RandomlyFails; +import org.openide.DialogDescriptor; +import org.openide.DialogDisplayer; +import org.openide.NotifyDescriptor; +import org.openide.cookies.EditorCookie; +import org.openide.util.Exceptions; +import org.openide.util.Lookup; +import org.openide.util.UserQuestionException; +import org.openide.util.test.MockLookup; +import org.openide.windows.CloneableOpenSupport; + +/** + * Testing usage of UserQuestionException in CES when CES.open loads + * document asynchronously + * + * @author Marek Slama + */ +@RandomlyFails +public class CloneableEditorUserQuestionAsyncTest extends NbTestCase +implements CloneableEditorSupport.Env { + static { + System.setProperty("org.openide.windows.DummyWindowManager.VISIBLE", "false"); + } + /** the support to work with */ + private CES support; + + // Env variables + private String content = ""; + private boolean valid = true; + private boolean modified = false; + /** if not null contains message why this document cannot be modified */ + private String cannotBeModified; + private Date date = new Date(); + private VetoableChangeListener vetoL; + private IOException toThrow; + + public CloneableEditorUserQuestionAsyncTest(String testName) { + super(testName); + } + + protected @Override void setUp() { + MockLookup.setInstances(new DD()); + support = new CES(this, Lookup.EMPTY); + } + + public void testExceptionThrownWhenDocumentIsBeingReadInAWT () throws Exception { + support.addPropertyChangeListener(new PropertyChangeListener() { + public void propertyChange(PropertyChangeEvent evt) { + if (evt.getPropertyName().equals(EditorCookie.Observable.PROP_OPENED_PANES)) { + eventCounter++; + } + } + }); + final MyEx my = new MyEx(); + class Run implements Runnable { + public Exception ex; + public Error err; + private int action; + public Run (int action) { + this.action = action; + } + public void run () { + try { + if (action == 1) { + doExceptionThrownWhenDocumentIsBeingRead1Start(my); + } else if (action == 2) { + doExceptionThrownWhenDocumentIsBeingRead1Check(my); + } else if (action == 3) { + doExceptionThrownWhenDocumentIsBeingRead2Start(my); + } else if (action == 4) { + doExceptionThrownWhenDocumentIsBeingRead2Check(my); + } + } catch (Exception x) { + this.ex = x; + } catch (Error x) { + this.err = x; + } + } + } + Run r; + r = new Run(1); + eventCounter = 0; + SwingUtilities.invokeAndWait (r); + if (r.ex != null) throw r.ex; + if (r.err != null) throw r.err; + while (eventCounter < 2) { + Thread.sleep(100); + } + + r = new Run(2); + SwingUtilities.invokeAndWait (r); + if (r.ex != null) throw r.ex; + if (r.err != null) throw r.err; + + r = new Run(3); + eventCounter = 0; + SwingUtilities.invokeAndWait (r); + if (r.ex != null) throw r.ex; + if (r.err != null) throw r.err; + while (eventCounter < 2) { + Thread.sleep(100); + } + + r = new Run(4); + SwingUtilities.invokeAndWait (r); + if (r.ex != null) throw r.ex; + if (r.err != null) throw r.err; + } + + private int eventCounter = 0; + public void testExceptionThrownWhenDocumentIsBeingRead () throws Exception { + assertFalse (SwingUtilities.isEventDispatchThread ()); + MyEx my = new MyEx(); + + support.addPropertyChangeListener(new PropertyChangeListener() { + public void propertyChange(PropertyChangeEvent evt) { + if (evt.getPropertyName().equals(EditorCookie.Observable.PROP_OPENED_PANES)) { + eventCounter++; + } + } + }); + + eventCounter = 0; + doExceptionThrownWhenDocumentIsBeingRead1Start(my); + //Wait till document is closed + while (eventCounter < 2) { + Thread.sleep(100); + } + + doExceptionThrownWhenDocumentIsBeingRead1Check(my); + + eventCounter = 0; + doExceptionThrownWhenDocumentIsBeingRead2Start(my); + //Wait till document is opened + while (eventCounter < 2) { + Thread.sleep(100); + } + doExceptionThrownWhenDocumentIsBeingRead2Check(my); + } + + public void testOpenDocumentIsLoadedUsingIOException() throws Exception{ + doOpenDocumentIsLoaded (new IOException ("Plain I/O exc")); + } + + public void testOpenDocumentIsLoadedUsingUserQuestionException() throws Exception { + doOpenDocumentIsLoaded (new MyEx()); + } + + private void doOpenDocumentIsLoaded (IOException my) throws Exception { + toThrow = my; + try{ + support.openDocument(); + fail ("Document should not be loaded, we throw an exception"); + } + catch (IOException e){ + assertSame ("The expected exception", my, e); + } + + assertNull ("No document", support.getDocument()); + assertFalse ("Not loaded", support.isDocumentLoaded()); + + toThrow = null; + support.openDocument (); + + assertNotNull ("We can later open the document", support.getDocument ()); + assertTrue ("And it is correctly marked as loaded", support.isDocumentLoaded ()); + } + + private class MyEx extends UserQuestionException { + private int confirmed; + + public @Override String getLocalizedMessage() { + return "locmsg"; + } + + public @Override String getMessage() { + return "msg"; + } + + public void confirmed () { + confirmed++; + toThrow = null; + } + } + + private void doExceptionThrownWhenDocumentIsBeingRead1Start (MyEx my) throws Exception { + toThrow = my; + + DD.toReturn = NotifyDescriptor.NO_OPTION; + support.open(); + } + + private void doExceptionThrownWhenDocumentIsBeingRead1Check (MyEx my) throws Exception { + assertNotNull ("Some otions", DD.options); + assertEquals ("Two options", 2, DD.options.length); + assertEquals ("Yes", NotifyDescriptor.YES_OPTION, DD.options[0]); + assertEquals ("No", NotifyDescriptor.NO_OPTION, DD.options[1]); + assertEquals ("confirmed not called", 0, my.confirmed); + + assertNull ("Still no document", support.getDocument ()); + } + + private void doExceptionThrownWhenDocumentIsBeingRead2Start (MyEx my) throws Exception { + DD.options = null; + DD.toReturn = NotifyDescriptor.YES_OPTION; + support.open (); + } + + private void doExceptionThrownWhenDocumentIsBeingRead2Check (MyEx my) throws Exception { + assertEquals ("confirmed called", 1, my.confirmed); + assertNotNull ("Some otions", DD.options); + assertEquals ("Two options", 2, DD.options.length); + assertEquals ("Yes", NotifyDescriptor.YES_OPTION, DD.options[0]); + assertEquals ("No", NotifyDescriptor.NO_OPTION, DD.options[1]); + DD.options = null; + + assertNotNull ("Document opened", support.getDocument ()); + } + + // + // Implementation of the CloneableEditorSupport.Env + // + + public void addPropertyChangeListener(PropertyChangeListener l) {} + + public void removePropertyChangeListener(PropertyChangeListener 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 support; + } + + public String getMimeType() { + return "text/plain"; + } + + public Date getTime() { + return date; + } + + public InputStream inputStream() throws IOException { + if (toThrow != null) { + throw toThrow; + } + return new ByteArrayInputStream(content.getBytes()); + } + + public OutputStream outputStream() throws IOException { + class ContentStream extends ByteArrayOutputStream { + public @Override 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 @Override 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 (Env env, Lookup l) { + super (env, l); + } + + @Override + protected boolean asynchronousOpen() { + return true; + } + + 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"; + } + + } // end of CES + + /** Our own dialog displayer. + */ + private static final class DD extends DialogDisplayer { + public static Object[] options; + public static Object toReturn; + + public Dialog createDialog(DialogDescriptor descriptor) { + throw new IllegalStateException ("Not implemented"); + } + + public Object notify(NotifyDescriptor descriptor) { + assertNull (options); + assertNotNull (toReturn); + options = descriptor.getOptions(); + Object r = toReturn; + toReturn = null; + return r; + } + + } // end of DD + +}