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. */