diff -r 8a7c045234cc apisupport.project/src/org/netbeans/modules/apisupport/project/ui/wizard/loader/NewLoaderIterator.java --- a/apisupport.project/src/org/netbeans/modules/apisupport/project/ui/wizard/loader/NewLoaderIterator.java Mon Jun 20 08:44:49 2011 +0200 +++ b/apisupport.project/src/org/netbeans/modules/apisupport/project/ui/wizard/loader/NewLoaderIterator.java Mon Jun 20 14:31:38 2011 +0200 @@ -169,7 +169,12 @@ if (v == null) { return false; } - return v.compareTo(new SpecificationVersion("1.24")) >= 0; // NOI18N + SpecificationVersion l = getModuleInfo().getDependencyVersion("org.openide.loaders"); // NOI18N + if (l == null) { + return false; + } + return v.compareTo(new SpecificationVersion("1.24")) >= 0 // NOI18N + && l.compareTo(new SpecificationVersion("7.26")) >= 0; // NOI18N } catch (IOException ex) { return false; } diff -r 8a7c045234cc apisupport.project/src/org/netbeans/modules/apisupport/project/ui/wizard/loader/templateDataObjectMulti.javx --- a/apisupport.project/src/org/netbeans/modules/apisupport/project/ui/wizard/loader/templateDataObjectMulti.javx Mon Jun 20 08:44:49 2011 +0200 +++ b/apisupport.project/src/org/netbeans/modules/apisupport/project/ui/wizard/loader/templateDataObjectMulti.javx Mon Jun 20 14:31:38 2011 +0200 @@ -6,45 +6,25 @@ package ${PACKAGENAME}; import java.io.IOException; -import java.util.concurrent.Callable; -import org.netbeans.core.api.multiview.MultiViews; import org.netbeans.core.spi.multiview.MultiViewElement; import org.netbeans.core.spi.multiview.text.MultiViewEditorElement; import org.openide.filesystems.FileObject; -import org.openide.loaders.DataNode; import org.openide.loaders.DataObjectExistsException; import org.openide.loaders.MultiDataObject; import org.openide.loaders.MultiFileLoader; -import org.openide.text.DataEditorSupport; -import org.openide.nodes.Children; -import org.openide.nodes.CookieSet; -import org.openide.nodes.Node; -import org.openide.text.CloneableEditorSupport; -import org.openide.text.CloneableEditorSupport.Pane; import org.openide.util.Lookup; import org.openide.windows.TopComponent; -public class ${PREFIX}DataObject extends MultiDataObject implements Callable { +public class ${PREFIX}DataObject extends MultiDataObject { public ${PREFIX}DataObject(FileObject pf, MultiFileLoader loader) throws DataObjectExistsException, IOException { super(pf, loader); - CookieSet cookies = getCookieSet(); - cookies.add((Node.Cookie) DataEditorSupport.create(this, getPrimaryEntry(), cookies, this)); + registerEditor("${MIMETYPE}", true); } @Override - protected Node createNodeDelegate() { - return new DataNode(this, Children.LEAF, getLookup()); - } - - @Override - public Lookup getLookup() { - return getCookieSet().getLookup(); - } - - @Override - public Pane call() { - return (Pane)MultiViews.createCloneableMultiView("${MIMETYPE}", this); + protected int associateLookup() { + return 1; } @MultiViewElement.Registration( diff -r 8a7c045234cc openide.loaders/apichanges.xml --- a/openide.loaders/apichanges.xml Mon Jun 20 08:44:49 2011 +0200 +++ b/openide.loaders/apichanges.xml Mon Jun 20 14:31:38 2011 +0200 @@ -109,6 +109,32 @@ + + + Simpler way to subclass MultiDataObject + + + + + Added registerEditor method which might clash + with some existing method in subclasses, although it is + hopefully not very likely. + + +

+ New way to subclass MultiDataObject: override + + int associateLookup() + to return 1. In constructor call + + registerEditor("your/mimetype", ...) to get default + editor support. No need to override any other methods to get + well performing MultiDataObject. +

+
+ +
Children for DataFolder are more effective diff -r 8a7c045234cc openide.loaders/manifest.mf --- a/openide.loaders/manifest.mf Mon Jun 20 08:44:49 2011 +0200 +++ b/openide.loaders/manifest.mf Mon Jun 20 14:31:38 2011 +0200 @@ -1,6 +1,6 @@ Manifest-Version: 1.0 OpenIDE-Module: org.openide.loaders -OpenIDE-Module-Specification-Version: 7.25 +OpenIDE-Module-Specification-Version: 7.26 OpenIDE-Module-Localizing-Bundle: org/openide/loaders/Bundle.properties OpenIDE-Module-Provides: org.netbeans.modules.templates.v1_0 OpenIDE-Module-Layer: org/netbeans/modules/openide/loaders/layer.xml diff -r 8a7c045234cc openide.loaders/src/org/openide/loaders/MultiDOEditor.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/openide.loaders/src/org/openide/loaders/MultiDOEditor.java Mon Jun 20 14:31:38 2011 +0200 @@ -0,0 +1,147 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2011 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 2011 Sun Microsystems, Inc. + */ +package org.openide.loaders; + +import java.io.Serializable; +import java.lang.reflect.Method; +import java.util.concurrent.Callable; +import java.util.logging.Level; +import org.netbeans.api.actions.Editable; +import org.netbeans.api.actions.Openable; +import org.openide.cookies.CloseCookie; +import org.openide.cookies.EditorCookie; +import org.openide.cookies.LineCookie; +import org.openide.cookies.PrintCookie; +import org.openide.nodes.CookieSet; +import org.openide.nodes.Node.Cookie; +import org.openide.text.CloneableEditorSupport; +import org.openide.text.CloneableEditorSupport.Pane; +import org.openide.text.DataEditorSupport; +import org.openide.util.Exceptions; +import org.openide.util.Lookup; +import org.openide.windows.CloneableOpenSupport; + +/** + * + * @author Jaroslav Tulach + */ +final class MultiDOEditor implements Callable, CookieSet.Factory { + private CloneableEditorSupport support; + private static final Method factory; + static { + ClassLoader l = Lookup.getDefault().lookup(ClassLoader.class); + if (l == null) { + l = Thread.currentThread().getContextClassLoader(); + } + if (l == null) { + l = MultiDOEditor.class.getClassLoader(); + } + Method m = null; + try { + Class multiviews = Class.forName("org.netbeans.core.api.multiview.MultiViews", true, l); // NOI18N + m = multiviews.getMethod("createCloneableMultiView", String.class, Serializable.class); // NOI18N + } catch (NoSuchMethodException ex) { + MultiDataObject.LOG.log(Level.WARNING, "Cannot find a method", ex); // NOI18N + } catch (ClassNotFoundException ex) { + MultiDataObject.LOG.info("Not using multiviews for MultiDataObject.registerEditor()"); // NOI18N + MultiDataObject.LOG.log(Level.FINE, "Cannot find a class", ex); // NOI18N + } + factory = m; + } + private final MultiDataObject outer; + private final String mimeType; + private final boolean useMultiview; + + MultiDOEditor(MultiDataObject outer, String mimeType, boolean useMultiview) { + this.outer = outer; + this.mimeType = mimeType; + this.useMultiview = useMultiview; + } + + @Override + public Pane call() throws Exception { + if (factory != null) { + return (Pane) factory.invoke(null, mimeType, outer); + } + return null; + } + + @Override + public T createCookie(Class klass) { + if ( + klass.isAssignableFrom(DataEditorSupport.class) || + DataEditorSupport.class.isAssignableFrom(klass) || + klass.isAssignableFrom(Openable.class) || + klass.isAssignableFrom(Editable.class) || + klass.isAssignableFrom(EditorCookie.Observable.class) || + klass.isAssignableFrom(PrintCookie.class) || + klass.isAssignableFrom(CloseCookie.class) || + klass.isAssignableFrom(LineCookie.class) + ) { + synchronized (this) { + if (support == null) { + support = DataEditorSupport.create( + outer, outer.getPrimaryEntry(), + outer.getCookieSet(), useMultiview ? this : null + ); + } + } + return klass.cast(support); + } + return null; + } + + public static void registerEditor(MultiDataObject multi, String mime, boolean useMultiview) { + MultiDOEditor ed = new MultiDOEditor(multi, mime, useMultiview); + try { + multi.getCookieSet().add(new Class[]{ + Class.forName("org.openide.text.SimpleES"), // NOI18N + SaveAsCapable.class, Openable.class, EditorCookie.Observable.class, + PrintCookie.class, CloseCookie.class, Editable.class, + DataEditorSupport.class, CloneableEditorSupport.class, + CloneableOpenSupport.class + }, ed); + } catch (ClassNotFoundException ex) { + throw new IllegalStateException(ex); + } + } +} diff -r 8a7c045234cc openide.loaders/src/org/openide/loaders/MultiDataObject.java --- a/openide.loaders/src/org/openide/loaders/MultiDataObject.java Mon Jun 20 08:44:49 2011 +0200 +++ b/openide.loaders/src/org/openide/loaders/MultiDataObject.java Mon Jun 20 14:31:38 2011 +0200 @@ -49,11 +49,24 @@ import java.beans.PropertyVetoException; import java.io.*; import java.lang.ref.WeakReference; +import java.lang.reflect.Method; import java.util.*; +import java.util.concurrent.Callable; import java.util.logging.*; import javax.swing.event.*; +import org.netbeans.api.actions.Editable; +import org.netbeans.api.actions.Openable; +import org.openide.cookies.CloseCookie; +import org.openide.cookies.EditorCookie; +import org.openide.cookies.LineCookie; +import org.openide.cookies.PrintCookie; import org.openide.filesystems.*; import org.openide.nodes.*; +import org.openide.nodes.Node.Cookie; +import org.openide.text.CloneableEditor; +import org.openide.text.CloneableEditorSupport; +import org.openide.text.CloneableEditorSupport.Pane; +import org.openide.text.DataEditorSupport; import org.openide.util.*; /** Provides support for handling of data objects with multiple files. @@ -235,6 +248,9 @@ */ @Override protected Node createNodeDelegate () { + if (associateLookup() >= 1) { + return new DataNode(this, Children.LEAF, getLookup()); + } DataNode dataNode = (DataNode) super.createNodeDelegate (); return dataNode; } @@ -922,6 +938,57 @@ return super.getCookie (type); } + @Override + public Lookup getLookup() { + int version = associateLookup(); + assert version <= 1; + if (version >= 1) { + return getCookieSet().getLookup(); + } + return super.getLookup(); + } + + /** Influences behavior of {@link #getLookup()} method. Depending on the + * returned integer, one can get different, better and more modern content + * of the {@link Lookup}: + *
    + *
  • version 0 - delegates to getNodeDelegate().getLookup().
  • + *
  • version 1 - delegates to getCookieSet().getLookup() + * and makes sure {@link FileObject}, this and + * {@link Node} are in the lookup. The {@link Node} is created lazily + * by calling {@link #getNodeDelegate()}. + *
  • + *
+ * General suggestion is to always return the highest supported version + * when creating new objects and to stick with certain version when backward + * compatibility is requested. + * + * @return version identifying content of the lookup (currently 0 or 1) + * @since 7.26 + */ + protected int associateLookup() { + return 0; + } + + /** Utility method to register editor for this {@link DataObject}. + * Call it from constructor with appropriate mimeType. The system will + * make sure that appropriate cookies ({@link Openable}, {@link Editable}, + * {@link CloseCookie}, {@link EditorCookie}, {@link SaveAsCapable}, + * {@link LineCookie} are registered into {@link #getCookieSet()}. + *

+ * The selected editor is + * MultiView component, if requested (this requires presence of + * the MultiView API + * in the system. Otherwise it is plain {@link CloneableEditor}. + * + * @param mimeType mime type to associate with + * @param useMultiview should the used component be multiview? + * @since 7.26 + */ + protected final void registerEditor(final String mimeType, boolean useMultiview) { + MultiDOEditor.registerEditor(this, mimeType, useMultiview); + } + /** Fires cookie change. */ final void fireCookieChange () { @@ -1336,20 +1403,34 @@ } } + final void updateNodeInCookieSet() { + if (associateLookup() >= 1) { + CookieSet set = getCookieSet(false); + if (set != null) { + set.assign(Node.class, getNodeDelegate()); + } + } + } + void checkCookieSet(Class c) { } /** Change listener and implementation of before. */ private final class ChangeAndBefore implements ChangeListener, CookieSet.Before { + @Override public void stateChanged (ChangeEvent ev) { fireCookieChange (); } + @Override public void beforeLookup(Class clazz) { if (clazz.isAssignableFrom(FileObject.class)) { updateFilesInCookieSet(); } + if (clazz.isAssignableFrom(Node.class)) { + updateNodeInCookieSet(); + } checkCookieSet(clazz); } } diff -r 8a7c045234cc openide.loaders/src/org/openide/text/SimpleES.java --- a/openide.loaders/src/org/openide/text/SimpleES.java Mon Jun 20 08:44:49 2011 +0200 +++ b/openide.loaders/src/org/openide/text/SimpleES.java Mon Jun 20 14:31:38 2011 +0200 @@ -49,6 +49,7 @@ import org.openide.cookies.CloseCookie; import org.openide.cookies.EditCookie; import org.openide.cookies.EditorCookie; +import org.openide.cookies.LineCookie; import org.openide.cookies.OpenCookie; import org.openide.cookies.PrintCookie; import org.openide.cookies.SaveCookie; @@ -57,6 +58,7 @@ import org.openide.loaders.DataObject; import org.openide.loaders.MultiDataObject; import org.openide.loaders.MultiDataObject.Entry; +import org.openide.loaders.SaveAsCapable; import org.openide.nodes.CookieSet; import org.openide.nodes.Node.Cookie; import org.openide.windows.CloneableOpenSupport; @@ -67,7 +69,8 @@ * @author Jaroslav Tulach */ final class SimpleES extends DataEditorSupport -implements OpenCookie, EditCookie, EditorCookie.Observable, PrintCookie, CloseCookie { +implements OpenCookie, EditCookie, EditorCookie.Observable, +PrintCookie, CloseCookie, SaveAsCapable, LineCookie { /** SaveCookie for this support instance. The cookie is adding/removing * data object's cookie set depending on if modification flag was set/unset. */ private final SaveCookie saveCookie = new SaveCookie() { diff -r 8a7c045234cc openide.loaders/test/unit/src/org/openide/loaders/MultiDataObjectVersion1Test.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/openide.loaders/test/unit/src/org/openide/loaders/MultiDataObjectVersion1Test.java Mon Jun 20 14:31:38 2011 +0200 @@ -0,0 +1,216 @@ +/* + * 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.loaders; + + +import org.openide.filesystems.*; +import org.netbeans.junit.*; +import java.io.IOException; +import java.util.*; +import java.util.logging.Level; +import org.openide.*; +import org.openide.cookies.CloseCookie; +import org.openide.cookies.EditCookie; +import org.openide.cookies.EditorCookie; +import org.openide.cookies.LineCookie; +import org.openide.cookies.OpenCookie; +import org.openide.cookies.PrintCookie; +import org.openide.nodes.Node; +import org.openide.util.Enumerations; + +public class MultiDataObjectVersion1Test extends NbTestCase { + FileSystem fs; + DataObject one; + DataFolder from; + DataFolder to; + ErrorManager err; + + + public MultiDataObjectVersion1Test (String name) { + super (name); + } + + @Override + protected Level logLevel() { + return Level.INFO; + } + + @Override + protected int timeOut() { + return 45000; + } + + @Override + public void setUp() throws Exception { + clearWorkDir(); + + super.setUp(); + + MockServices.setServices(Pool.class); + + err = ErrorManager.getDefault().getInstance("TEST-" + getName()); + + LocalFileSystem lfs = new LocalFileSystem(); + lfs.setRootDirectory(getWorkDir()); + fs = lfs; + FileUtil.createData(fs.getRoot(), "from/x.prima"); + FileUtil.createData(fs.getRoot(), "from/x.seconda"); + FileUtil.createFolder(fs.getRoot(), "to/"); + + one = DataObject.find(fs.findResource("from/x.prima")); + assertEquals(SimpleObject.class, one.getClass()); + + from = one.getFolder(); + to = DataFolder.findFolder(fs.findResource("to/")); + + assertEquals("Nothing there", 0, to.getPrimaryFile().getChildren().length); + } + + + public void testWhatIsInTheLookup() throws Exception { + assertTrue(this.one instanceof SimpleObject); + SimpleObject s = (SimpleObject)this.one; + + assertEquals("DO in lookup", s, s.getLookup().lookup(DataObject.class)); + assertEquals("Fo in lookup", s.getPrimaryFile(), s.getLookup().lookup(FileObject.class)); + assertEquals("no node created yet", 0, s.cnt); + Node lkpNd = s.getLookup().lookup(Node.class); + assertEquals("now node created", 1, s.cnt); + assertEquals("Node in lookup", s.getNodeDelegate(), lkpNd); + } + + public void testEditorInLookup() throws Exception { + assertTrue(this.one instanceof SimpleObject); + SimpleObject s = (SimpleObject)this.one; + + LineCookie line = s.getLookup().lookup(LineCookie.class); + assertNotNull("Line cookie is there", line); + assertEquals("Edit cookie", line, s.getLookup().lookup(EditCookie.class)); + assertEquals("Editor cookie", line, s.getLookup().lookup(EditorCookie.class)); + assertEquals("Editor objservable cookie", line, s.getLookup().lookup(EditorCookie.Observable.class)); + assertEquals("SaveAsCapable", line, s.getLookup().lookup(SaveAsCapable.class)); + assertEquals("Open cookie", line, s.getLookup().lookup(OpenCookie.class)); + assertEquals("Close cookie", line, s.getLookup().lookup(CloseCookie.class)); + assertEquals("Print cookie", line, s.getLookup().lookup(PrintCookie.class)); + } + + + public static final class Pool extends DataLoaderPool { + @Override + protected Enumeration loaders() { + return Enumerations.singleton(SimpleLoader.getLoader(SimpleLoader.class)); + } + } + + public static final class SimpleLoader extends MultiFileLoader { + public SimpleLoader() { + super(SimpleObject.class); + } + protected String displayName() { + return "SimpleLoader"; + } + @Override + protected FileObject findPrimaryFile(FileObject fo) { + if (!fo.isFolder()) { + // emulate the behaviour of form data object + + /* emulate!? this one is written too well ;-) + FileObject primary = FileUtil.findBrother(fo, "prima"); + FileObject secondary = FileUtil.findBrother(fo, "seconda"); + + if (primary == null || secondary == null) { + return null; + } + + if (primary != fo && secondary != fo) { + return null; + } + */ + + // here is the common code for the worse behaviour + if (fo.hasExt("prima")) { + return FileUtil.findBrother(fo, "seconda") != null ? fo : null; + } + + if (fo.hasExt("seconda")) { + return FileUtil.findBrother(fo, "prima"); + } + } + return null; + } + protected MultiDataObject createMultiObject(FileObject primaryFile) throws DataObjectExistsException, IOException { + return new SimpleObject(this, primaryFile); + } + protected MultiDataObject.Entry createPrimaryEntry(MultiDataObject obj, FileObject primaryFile) { + return new FileEntry(obj, primaryFile); + } + protected MultiDataObject.Entry createSecondaryEntry(MultiDataObject obj, FileObject secondaryFile) { + return new FileEntry(obj, secondaryFile); + } + } + + + public static final class SimpleObject extends MultiDataObject { + private int cnt; + public SimpleObject(SimpleLoader l, FileObject fo) throws DataObjectExistsException { + super(fo, l); + registerEditor("mime/type", false); + } + + @Override + protected int associateLookup() { + return 1; + } + + @Override + protected Node createNodeDelegate() { + cnt++; + return super.createNodeDelegate(); + } + + + } + +}