diff --git a/openide.text/manifest.mf b/openide.text/manifest.mf
--- a/openide.text/manifest.mf
+++ b/openide.text/manifest.mf
@@ -2,5 +2,5 @@
OpenIDE-Module: org.openide.text
OpenIDE-Module-Localizing-Bundle: org/openide/text/Bundle.properties
AutoUpdate-Essential-Module: true
-OpenIDE-Module-Specification-Version: 6.60
+OpenIDE-Module-Specification-Version: 6.61
diff --git a/openide.text/nbproject/project.xml b/openide.text/nbproject/project.xml
--- a/openide.text/nbproject/project.xml
+++ b/openide.text/nbproject/project.xml
@@ -111,7 +111,7 @@
- 6.15
+ 6.70
diff --git a/openide.text/src/org/openide/text/CloneableEditorSupport.java b/openide.text/src/org/openide/text/CloneableEditorSupport.java
--- a/openide.text/src/org/openide/text/CloneableEditorSupport.java
+++ b/openide.text/src/org/openide/text/CloneableEditorSupport.java
@@ -180,7 +180,7 @@
private UndoRedo.Manager undoRedo;
/** lines set for this object */
- private Line.Set lineSet;
+ private Line.Set[] lineSet = new Line.Set[] { null };
/** Helper variable to prevent multiple cocurrent printing of this
* instance. */
@@ -196,7 +196,7 @@
private Set listeners;
/** last selected editor pane. */
- private transient Reference lastSelected;
+ private transient Reference lastSelected[] = new Reference[] { null };
/** The time of the last save to determine the real external modifications */
private long lastSaveTime;
@@ -905,17 +905,33 @@
return null;
}
+
+ @Override
+ protected void afterRedirect(CloneableOpenSupport redirectedTo) {
+ super.afterRedirect(redirectedTo);
+ // synchronize field from redirected instance, i.e. for correct getOpenedPanes answers
+ if (redirectedTo instanceof CloneableEditorSupport) {
+ CloneableEditorSupport other = ((CloneableEditorSupport)redirectedTo);
+ this.lastSelected = other.lastSelected;
+ this.openClose = other.openClose;
+ this.lineSet = other.lineSet;
+ }
+ // notify EditorCookie.Observable listeners if any
+ if (propertyChangeSupport != null) {
+ propertyChangeSupport.firePropertyChange(EditorCookie.Observable.PROP_OPENED_PANES, null, null);
+ }
+ }
/** Returns the lastly selected Pane or null
*/
final Pane getLastSelected() {
- Reference r = lastSelected;
+ Reference r = lastSelected[0];
return (r == null) ? null : r.get();
}
final void setLastSelected(Pane lastSelected) {
- this.lastSelected = new WeakReference(lastSelected);
+ this.lastSelected[0] = new WeakReference(lastSelected);
}
//
@@ -1639,19 +1655,19 @@
*/
Line.Set updateLineSet(boolean clear) {
synchronized (getLock()) {
- if ((lineSet != null) && !clear) {
- return lineSet;
+ if ((lineSet[0] != null) && !clear) {
+ return lineSet[0];
}
if ((getDoc() == null) ||
(openClose.getDocumentStatusLA() == DocumentStatus.RELOADING))
{
- lineSet = new EditorSupportLineSet.Closed(CloneableEditorSupport.this);
+ lineSet[0] = new EditorSupportLineSet.Closed(CloneableEditorSupport.this);
} else {
- lineSet = new EditorSupportLineSet(CloneableEditorSupport.this,getDoc());
+ lineSet[0] = new EditorSupportLineSet(CloneableEditorSupport.this,getDoc());
}
- return lineSet;
+ return lineSet[0];
}
}
diff --git a/openide.text/test/unit/src/org/openide/text/CloneableEditorSupportCOSRedirectorTest.java b/openide.text/test/unit/src/org/openide/text/CloneableEditorSupportCOSRedirectorTest.java
new file mode 100644
--- /dev/null
+++ b/openide.text/test/unit/src/org/openide/text/CloneableEditorSupportCOSRedirectorTest.java
@@ -0,0 +1,402 @@
+/*
+ * 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.
+ * Portions Copyrighted 2007 Sun Microsystems, Inc.
+ */
+package org.openide.text;
+
+import java.awt.GraphicsEnvironment;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.lang.ref.WeakReference;
+import java.util.Arrays;
+import java.util.concurrent.atomic.AtomicBoolean;
+import javax.swing.SwingUtilities;
+import javax.swing.text.Document;
+import javax.swing.text.EditorKit;
+import junit.framework.Test;
+import junit.framework.TestSuite;
+import org.netbeans.junit.MockServices;
+import org.netbeans.junit.NbTestCase;
+import org.openide.DialogDescriptor;
+import org.openide.DialogDisplayer;
+import org.openide.NotifyDescriptor;
+import org.openide.cookies.EditorCookie;
+import org.openide.util.Lookup;
+import org.openide.util.lookup.AbstractLookup;
+import org.openide.util.lookup.InstanceContent;
+import org.openide.windows.CloneableOpenSupport;
+import org.openide.windows.CloneableOpenSupportRedirector;
+
+/**
+ * based on functionality from CloneableEditorSupportRedirectorTest
+ * @author Vladimir Voskresensky
+ */
+public class CloneableEditorSupportCOSRedirectorTest extends NbTestCase
+implements CloneableEditorSupport.Env {
+
+ public static Test suite() {
+ return GraphicsEnvironment.isHeadless() ? new TestSuite() : new TestSuite(CloneableEditorSupportCOSRedirectorTest.class);
+ }
+
+ static {
+ System.setProperty("org.openide.windows.DummyWindowManager.VISIBLE", "false");
+ }
+ /** the content of lookup of support */
+ private InstanceContent ic;
+ private Redirector red;
+ private DialogDisplayer dd;
+
+
+ // Env variables
+ private final StringBuilder content;
+ private boolean modified = false;
+ private final java.util.Date date = new java.util.Date ();
+ private java.util.List propL = new java.util.ArrayList();
+ private java.beans.VetoableChangeListener vetoL;
+
+
+
+ public CloneableEditorSupportCOSRedirectorTest(String testName) {
+ super(testName);
+ this.content = new StringBuilder("");
+ }
+
+ public CloneableEditorSupportCOSRedirectorTest(StringBuilder content) {
+ super("");
+ this.content = content;
+ }
+
+ protected void setUp () {
+ ic = new InstanceContent ();
+ CES support = new CES (this, new AbstractLookup(ic));
+
+ MockServices.setServices(Redirector.class, DD.class);
+ red = Lookup.getDefault().lookup(Redirector.class);
+ assertNotNull(red);
+ dd = Lookup.getDefault().lookup(DD.class);
+ assertNotNull(dd);
+
+ CloneableEditorSupportCOSRedirectorTest t = new CloneableEditorSupportCOSRedirectorTest(this.content);
+ red.master = support;
+ InstanceContent slave = new InstanceContent();
+ red.slave = new CES(t, new AbstractLookup (slave));
+ slave.add(red.master);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ red.master.close();
+ red.slave.close();
+ content.delete(0, content.length());
+ }
+
+ public void testSameDocument() throws Exception {
+ red.master.open();
+ red.slave.open();
+ javax.swing.text.Document doc = red.slave.openDocument ();
+ assertNotNull (doc);
+
+ assertSame(doc, red.master.getDocument());
+
+ String s = doc.getText (0, doc.getLength ());
+ assertEquals ("Same text as in the stream", content.toString(), s);
+
+ assertFalse ("No redo", red.slave.getUndoRedo ().canRedo ());
+ assertFalse ("No undo", red.slave.getUndoRedo ().canUndo ());
+ }
+
+ public void testLineLookupIsPropagated () throws Exception {
+ content.append("Line1\nLine2\n");
+ red.master.open();
+ red.slave.open();
+ Integer template = new Integer (1);
+ ic.add (template); // put anything into the lookup
+
+ // in order to set.getLines() work correctly, the document has to be loaded
+ red.master.openDocument();
+
+ Line.Set set = red.master.getLineSet();
+ assertSame("Same lines", set, red.slave.getLineSet());
+ java.util.List list = set.getLines();
+ assertEquals ("Three lines", 3, list.size ());
+
+ Line l = (Line)list.get (0);
+ Integer i = l.getLookup ().lookup (Integer.class);
+ assertEquals ("The original integer", template, i);
+ ic.remove (template);
+ i = l.getLookup ().lookup (Integer.class);
+ assertNull ("Lookup is dynamic, so now there is nothing", i);
+ }
+
+
+ public void testGetInputStream () throws Exception {
+ content.append("goes\nto\nInputStream");
+ red.master.open();
+ red.slave.open();
+ String added = "added before\n";
+ javax.swing.text.Document doc = red.master.openDocument ();
+ assertNotNull (doc);
+
+ // modify the document
+ doc.insertString(0, added, null);
+ compareStreamWithString(red.master.getInputStream(), added + content);
+ compareStreamWithString(red.slave.getInputStream(), added + content);
+ }
+
+ public void testGetInputStreamWhenClosed () throws Exception {
+ content.append("basic\ncontent");
+ red.master.open();
+ red.slave.open();
+ compareStreamWithString(red.master.getInputStream(), content);
+ compareStreamWithString(red.slave.getInputStream(), content);
+ // we should be doing this with the document still closed
+ assertNull("The document is supposed to be still closed", red.master.getDocument ());
+ }
+
+ public void testDocumentCanBeGarbageCollectedWhenClosed () throws Exception {
+ content.append("Ahoj\nMyDoc");
+ red.master.open();
+ red.slave.open();
+ javax.swing.text.Document doc = red.master.openDocument ();
+ assertNotNull (doc);
+
+ assertTrue ("Document is loaded", red.master.isDocumentLoaded ());
+ assertTrue ("Document is loaded", red.slave.isDocumentLoaded ());
+ assertTrue ("Can be closed without problems", red.slave.close ());
+ assertFalse ("Document is not loaded", red.master.isDocumentLoaded ());
+ assertFalse ("Document is not loaded", red.slave.isDocumentLoaded ());
+
+ WeakReference> ref = new WeakReference(doc);
+ doc = null;
+
+ assertGC ("Document can dissapear", ref);
+ }
+
+ /**
+ * Tests that the wrapEditorComponent() method returns the passed
+ * parameter (doesn't wrap the passed component in some additional UI).
+ */
+ public void testWrapEditorComponent() {
+ javax.swing.JPanel panel = new javax.swing.JPanel();
+ assertSame(red.master.wrapEditorComponent(panel), panel);
+ assertSame(red.slave.wrapEditorComponent(panel), panel);
+ }
+
+ public void testAfterOpenOfSlaveThereArePanesAndEvent() throws Exception {
+ red.master.open();
+ final AtomicBoolean wasNonEmtpyOpenedPanesEvent = new AtomicBoolean(false);
+ final AtomicBoolean emtpyPanes = new AtomicBoolean(false);
+ final AtomicBoolean nonEmtpyPanes = new AtomicBoolean(false);
+
+ PropertyChangeListener l = new PropertyChangeListener() {
+
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if (EditorCookie.Observable.PROP_OPENED_PANES.equals(evt.getPropertyName())) {
+ wasNonEmtpyOpenedPanesEvent.set(red.slave.getOpenedPanes() != null);
+ }
+ }
+
+ };
+ red.slave.addPropertyChangeListener(l);
+
+ class Check implements Runnable {
+ @Override
+ public void run() {
+ // editor not yet opened, attach listener and open from there
+ emtpyPanes.set(red.slave.getOpenedPanes() == null);
+ red.slave.open();
+ nonEmtpyPanes.set(red.slave.getOpenedPanes() != null);
+ }
+ }
+ Check check = new Check();
+
+ SwingUtilities.invokeAndWait(check);
+ red.slave.removePropertyChangeListener(l);
+ assertTrue("No panes are open before red.slave.open", emtpyPanes.get());
+ assertTrue("Some panes are now open after red.slave.open", nonEmtpyPanes.get());
+ assertTrue("PROP_OPENED_PANES event was not fired", wasNonEmtpyOpenedPanesEvent.get());
+ }
+
+ public void testGetEditorKit() {
+ EditorKit kit = CloneableEditorSupport.getEditorKit("text/plain");
+ assertNotNull("EditorKit should never be null", kit);
+ // There shouldn't be any EK registered and we should get the default one
+ assertEquals("Wrong default EditorKit", "org.openide.text.CloneableEditorSupport$PlainEditorKit", kit.getClass().getName());
+ }
+
+ private void compareStreamWithString(InputStream is, CharSequence s) throws Exception{
+ int i;
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ while ((i = is.read()) != -1) {
+ baos.write(i);
+ }
+ byte b1[] = baos.toByteArray();
+ byte b2[] = s.toString().getBytes();
+ assertTrue("Same bytes as would result from the string: " + s, Arrays.equals(b1, b2));
+ }
+
+ //
+ // Implementation of the CloneableEditorred.master.Env
+ //
+
+ public synchronized void addPropertyChangeListener(java.beans.PropertyChangeListener l) {
+ propL.add (l);
+ }
+ public synchronized void removePropertyChangeListener(java.beans.PropertyChangeListener l) {
+ propL.remove (l);
+ }
+
+ public synchronized void addVetoableChangeListener(java.beans.VetoableChangeListener l) {
+ assertNull ("This is the first veto listener", vetoL);
+ vetoL = l;
+ }
+ public void removeVetoableChangeListener(java.beans.VetoableChangeListener l) {
+ assertEquals ("Removing the right veto one", vetoL, l);
+ vetoL = null;
+ }
+
+ public org.openide.windows.CloneableOpenSupport findCloneableOpenSupport() {
+ return red.master;
+ }
+
+ public String getMimeType() {
+ return "text/plain";
+ }
+
+ public java.util.Date getTime() {
+ return date;
+ }
+
+ public java.io.InputStream inputStream() throws java.io.IOException {
+ return new java.io.ByteArrayInputStream (content.toString().getBytes ());
+ }
+ public java.io.OutputStream outputStream() throws java.io.IOException {
+ class ContentStream extends java.io.ByteArrayOutputStream {
+ public void close () throws java.io.IOException {
+ super.close ();
+ content.append(new String (toByteArray ()));
+ }
+ }
+
+ return new ContentStream ();
+ }
+
+ public boolean isValid() {
+ return true;
+ }
+
+ public boolean isModified() {
+ return modified;
+ }
+
+ public void markModified() throws java.io.IOException {
+ 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";
+ }
+
+ }
+
+
+ public static final class Redirector extends CloneableOpenSupportRedirector {
+ CES master;
+ CES slave;
+
+ @Override
+ protected CloneableOpenSupport redirect(CloneableOpenSupport.Env env) {
+ if (env == slave.cesEnv()) {
+ return master;
+ }
+ return null;
+ }
+
+ @Override
+ protected void opened(CloneableOpenSupport.Env env) {
+ }
+
+ @Override
+ protected void closed(CloneableOpenSupport.Env env) {
+ }
+ }
+ /**
+ * Our own dialog displayer when modified CES is closed we agree to close it.
+ */
+ public static final class DD extends DialogDisplayer {
+
+ public static Object[] options;
+ public static Object toReturn;
+ public static boolean disableTest;
+
+ public java.awt.Dialog createDialog(DialogDescriptor descriptor) {
+ throw new IllegalStateException("Not implemented");
+ }
+
+ public Object notify(NotifyDescriptor descriptor) {
+ return NotifyDescriptor.CLOSED_OPTION;
+ }
+ } // end of DD
+}
diff --git a/openide.windows/apichanges.xml b/openide.windows/apichanges.xml
--- a/openide.windows/apichanges.xml
+++ b/openide.windows/apichanges.xml
@@ -50,6 +50,21 @@
Window System API
+
+
+ Added method afterRedirect(CloneableOpenSupport) to CloneableOpenSupport
+
+
+
+
+
+
The method is called when CloneableOpenSupportRedirector found another instance of CloneableOpenSupport to open instead the current.
+ It is possible to override afterRedirect in derived classes and handle this situation.
+
+
+
+
+Override window system branding properties
diff --git a/openide.windows/manifest.mf b/openide.windows/manifest.mf
--- a/openide.windows/manifest.mf
+++ b/openide.windows/manifest.mf
@@ -1,6 +1,6 @@
Manifest-Version: 1.0
OpenIDE-Module: org.openide.windows
-OpenIDE-Module-Specification-Version: 6.69
+OpenIDE-Module-Specification-Version: 6.70
OpenIDE-Module-Localizing-Bundle: org/openide/windows/Bundle.properties
AutoUpdate-Essential-Module: true
diff --git a/openide.windows/src/org/openide/windows/CloneableOpenSupport.java b/openide.windows/src/org/openide/windows/CloneableOpenSupport.java
--- a/openide.windows/src/org/openide/windows/CloneableOpenSupport.java
+++ b/openide.windows/src/org/openide/windows/CloneableOpenSupport.java
@@ -97,6 +97,7 @@
CloneableOpenSupport redirect = CloneableOpenSupportRedirector.findRedirect(this);
if (redirect != null) {
redirect.open();
+ this.afterRedirectImpl(redirect);
return;
}
//Bugfix #10688 open() is now run in AWT thread
@@ -246,6 +247,30 @@
*/
protected abstract String messageOpened();
+ private void afterRedirectImpl(CloneableOpenSupport redirectedTo) {
+ // there is a common patern in user code:
+ // CloneableEditorSupport ces = ...;
+ // ces.edit();
+ // JEditorPane[] panes = ces.getOpenedPanes();
+ // if (panes != null) panes[0].setPosition(offset);
+ // in case when redirection has happened during edit() call
+ // 'ces' instance returns null for getOpenedPanes
+ // but we want panes to be available for 'ces' instance after redirection;
+ // remember editors from redirected instance to have correct opened panes
+ // for this instance as well
+ this.allEditors = redirectedTo.allEditors;
+// this.env = redirect.env;
+ afterRedirect(redirectedTo);
+ }
+
+ /**
+ * Called to notify that another redirected CloneableOpenSupport was opened instead of this one.
+ * @param redirectedTo redirected instance which was opened instead of this one
+ * @since 6.70
+ */
+ protected void afterRedirect(CloneableOpenSupport redirectedTo) {
+ }
+
/** Abstract interface that is used by CloneableOpenSupport to
* talk to outside world.
*/