diff -r 5cb74240a340 spi.viewmodel/apichanges.xml --- a/spi.viewmodel/apichanges.xml Thu Sep 24 17:28:44 2009 +0200 +++ b/spi.viewmodel/apichanges.xml Fri Sep 25 00:16:09 2009 +0200 @@ -337,7 +337,21 @@ - + + + Introduction of AsynchronousModelFilter. + + + + + + This API introduced AsynchronousModelFilter that + can be used by clients to override the default threading of their model implementations. + + + + + diff -r 5cb74240a340 spi.viewmodel/manifest.mf --- a/spi.viewmodel/manifest.mf Thu Sep 24 17:28:44 2009 +0200 +++ b/spi.viewmodel/manifest.mf Fri Sep 25 00:16:09 2009 +0200 @@ -1,5 +1,5 @@ Manifest-Version: 1.0 OpenIDE-Module: org.netbeans.spi.viewmodel/2 OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/viewmodel/Bundle.properties -OpenIDE-Module-Specification-Version: 1.19 +OpenIDE-Module-Specification-Version: 1.20 diff -r 5cb74240a340 spi.viewmodel/src/org/netbeans/spi/viewmodel/AsynchronousModelFilter.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/spi.viewmodel/src/org/netbeans/spi/viewmodel/AsynchronousModelFilter.java Fri Sep 25 00:16:09 2009 +0200 @@ -0,0 +1,115 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 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]" + * + * 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 2009 Sun Microsystems, Inc. + */ + +package org.netbeans.spi.viewmodel; + +import java.util.concurrent.Executor; + +import org.openide.util.RequestProcessor; + +/** + * Change threading of implemented models. + * Methods implemented in {@link TreeModel}, {@link NodeModel} ({@link ExtendedNodeModel}) + * and {@link TableModel} can be called synchronously in AWT thread as a direct + * response to user action (this is the default behavior), + * or asynchronously in a Request Processor or other thread. + * Register an implementation of this along with other models, + * if you need to change the original threading. + * + * @author Martin Entlicher + * @since 1.20 + */ +public interface AsynchronousModelFilter extends Model { + + /** + * This enumeration identifies method(s) of view models for which + * threading information is provided by + * {@link #asynchronous(java.util.concurrent.Executor, org.netbeans.spi.viewmodel.AsynchronousModelFilter.CALL, java.lang.Object)} method. + *
+ * CHILDREN for TreeModel.getChildrenCount() and TreeModel.getChildren() + *
+ * DISPLAY_NAME is for NodeModel.getDisplayName() and ExtendedNodeModel.setName() + *
+ * SHORT_DESCRIPTION for NodeModel.getShortDescription() + *
+ * VALUE for TableModel.getValueAt() and TableModel.setValueAt() + *
+ * The rest of the methods on models are called synchronously, or additional + * enums can be added in the future. + */ + static enum CALL { CHILDREN, DISPLAY_NAME, SHORT_DESCRIPTION, VALUE } + + /** + * Executor for invocation of models method calls in the current thread. + * This will make method invocation synchronous. It's important that the + * methods execute fast so that they do not block AWT thread. + * This is the default executor for {@link CALL#DISPLAY_NAME} and + * {@link CALL#SHORT_DESCRIPTION}. + */ + static final Executor CURRENT_THREAD = new Executor() { + + public void execute(Runnable command) { + command.run(); + } + + }; + + /** + * Executor, which uses a shared {@link RequestProcessor} with + * throughoutput = 1 for models method calls, making the method invocation + * asynchronous. The UI gives a visual feedback to the user if models method + * calls take a long time. Use this to keep the UI responsive. + * This is the default executor for {@link CALL#CHILDREN} and + * {@link CALL#VALUE}. + */ + static final Executor DEFAULT = new RequestProcessor("Asynchronous view model", 1); // NOI18N + + /** + * Change the threading information for view models method calls. + * The returned Executor is used to call methods identified by + * {@link CALL} enum. + * + * @param original The original {@link Executor} + * @param asynchCall Identification of the method call + * @param node Object node + * @return an instance of Executor + */ + Executor asynchronous(Executor original, CALL asynchCall, Object node) throws UnknownTypeException; + +} diff -r 5cb74240a340 spi.viewmodel/src/org/netbeans/spi/viewmodel/Models.java --- a/spi.viewmodel/src/org/netbeans/spi/viewmodel/Models.java Thu Sep 24 17:28:44 2009 +0200 +++ b/spi.viewmodel/src/org/netbeans/spi/viewmodel/Models.java Fri Sep 25 00:16:09 2009 +0200 @@ -57,6 +57,7 @@ import java.util.List; import java.util.Set; import java.util.WeakHashMap; +import java.util.concurrent.Executor; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.AbstractAction; @@ -64,11 +65,13 @@ import javax.swing.JComponent; import javax.swing.SwingUtilities; +import org.netbeans.modules.viewmodel.AsynchronousModel; import org.netbeans.modules.viewmodel.DefaultTreeExpansionManager; import org.netbeans.modules.viewmodel.HyperCompoundModel; import org.netbeans.modules.viewmodel.OutlineTable; import org.netbeans.modules.viewmodel.TreeModelRoot; +import org.netbeans.spi.viewmodel.AsynchronousModelFilter.CALL; import org.openide.awt.Actions; import org.openide.explorer.view.TreeView; import org.openide.nodes.Node; @@ -87,7 +90,7 @@ /** Cached default implementations of expansion models. */ private static final WeakHashMap defaultExpansionModels = new WeakHashMap(); - + /** * Empty model - returns default root node with no children. */ @@ -224,23 +227,19 @@ ModelLists ml = new ModelLists(); List otherModels; - RequestProcessor rp = null; // Either the list contains 10 lists of individual models + one list of mixed models - // + optional TreeExpansionModelFilter(s) + optional RequestProcessor + // + optional TreeExpansionModelFilter(s) + optional AsynchronousModelFilter(s) // ; or the models directly boolean hasLists = false; - if (models.size() == 11 || models.size() == 12 || models.size() == 13) { + int modelsSize = models.size(); + if (11 <= modelsSize && modelsSize <= 14) { Iterator it = models.iterator (); boolean failure = false; while (it.hasNext ()) { Object model = it.next(); if (!(model instanceof List)) { - if (model instanceof RequestProcessor && !it.hasNext()) { - failure = false; - } else { - failure = true; - } + failure = true; break; } } @@ -264,20 +263,18 @@ revertOrder(ml.nodeActionsProviderFilters); ml.columnModels = (List) models.get(9); otherModels = (List) models.get(10); - if (models.size() > 11) { // TreeExpansionModelFilter or RequestProcessor - if (models.get(11) instanceof List) { - ml.treeExpansionModelFilters = (List) models.get(11); - } else { - rp = (RequestProcessor) models.get(11); - ml.treeExpansionModelFilters = Collections.emptyList(); - } + if (modelsSize > 11) { // TreeExpansionModelFilter + ml.treeExpansionModelFilters = (List) models.get(11); + //if (modelsSize > 12) { // AsynchronousModel + // ml.asynchModels = (List) models.get(12); + if (modelsSize > 12) { // AsynchronousModelFilter + ml.asynchModelFilters = (List) models.get(12); + } + //} } else { ml.treeExpansionModelFilters = Collections.emptyList(); } //treeExpansionModelFilters = (models.size() > 11) ? (List) models.get(11) : (List) Collections.EMPTY_LIST; - if (models.size() > 12) { - rp = (RequestProcessor) models.get(12); - } } else { // We have the models, need to find out what they implement ml.treeModels = new LinkedList (); ml.treeModelFilters = new LinkedList (); @@ -289,14 +286,15 @@ ml.tableModelFilters = new LinkedList (); ml.nodeActionsProviders = new LinkedList (); ml.nodeActionsProviderFilters = new LinkedList (); + //ml.asynchModels = new LinkedList (); + ml.asynchModelFilters = new LinkedList (); ml.columnModels = new LinkedList (); otherModels = (List) models; } ml.addOtherModels(otherModels); - + DefaultTreeExpansionModel defaultExpansionModel = null; if (ml.treeExpansionModels.isEmpty()) { - DefaultTreeExpansionModel defaultExpansionModel = null; synchronized (defaultExpansionModels) { defaultExpansionModel = defaultExpansionModels.get(models); if (defaultExpansionModel != null) { @@ -320,10 +318,10 @@ System.out.println("Node Action Provider Filters = "+nodeActionsProviderFilters); System.out.println("Column Models = "+columnModels); */ - return createCompoundModel(ml, propertiesHelpID, rp); + return createCompoundModel(ml, propertiesHelpID); } - private static CompoundModel createCompoundModel (ModelLists ml, String propertiesHelpID, RequestProcessor rp) { + private static CompoundModel createCompoundModel (ModelLists ml, String propertiesHelpID) { if (ml.treeModels.isEmpty ()) { TreeModel etm = new EmptyTreeModel(); ml.treeModels = Collections.singletonList(etm); @@ -345,6 +343,9 @@ defaultExpansionModel = (DefaultTreeExpansionModel) ml.treeExpansionModels.get(0); } } + /*if (ml.asynchModels.isEmpty()) { + ml.asynchModels = Collections.singletonList((AsynchronousModel) new DefaultAsynchronousModel()); + }*/ CompoundModel cm = new CompoundModel ( createCompoundTreeModel ( @@ -368,8 +369,11 @@ new DelegatingTableModel (ml.tableModels), ml.tableModelFilters ), - propertiesHelpID, - rp + createCompoundAsynchronousModel ( + new DefaultAsynchronousModel(),//new DelegatingAsynchronousModel (ml.asynchModels), + ml.asynchModelFilters + ), + propertiesHelpID ); if (defaultExpansionModel != null) { defaultExpansionModel.setCompoundModel(cm); @@ -534,6 +538,16 @@ expansionModel = new CompoundTreeExpansionModel (expansionModel, filter); } return expansionModel; + } + + private static AsynchronousModel createCompoundAsynchronousModel ( + AsynchronousModel asynchModel, + List filters + ) { + for (AsynchronousModelFilter filter : filters) { + asynchModel = new CompoundAsynchronousModel (asynchModel, filter); + } + return asynchModel; } @@ -1596,6 +1610,21 @@ } + private final static class CompoundAsynchronousModel implements AsynchronousModel { + private AsynchronousModel asynchModel; + private AsynchronousModelFilter asynchModelFilter; + + CompoundAsynchronousModel(AsynchronousModel asynchModel, AsynchronousModelFilter asynchModelFilter) { + this.asynchModel = asynchModel; + this.asynchModelFilter = asynchModelFilter; + } + + public Executor asynchronous(CALL asynchCall, Object node) throws UnknownTypeException { + return asynchModelFilter.asynchronous(asynchModel.asynchronous(asynchCall, node), asynchCall, node); + } + + } + /** * Creates one {@link org.netbeans.spi.viewmodel.TableModel} * from given list of TableModels. DelegatingTableModel asks all underlaying @@ -1912,7 +1941,7 @@ return new String (sb); } } - + private static class DefaultTreeExpansionModel implements TreeExpansionModel { private Reference cmRef; @@ -1972,6 +2001,19 @@ return new DefaultTreeExpansionModel(cmRef.get()); } + } + + private static final class DefaultAsynchronousModel implements AsynchronousModel { + + public Executor asynchronous(CALL asynchCall, Object node) { + if (asynchCall.equals(CALL.CHILDREN) || asynchCall.equals(CALL.VALUE)) { + // For backward compatibility + return AsynchronousModelFilter.DEFAULT; + } else { + return AsynchronousModelFilter.CURRENT_THREAD; + } + } + } /** @@ -3180,7 +3222,7 @@ private ColumnModel[] columnModels; private TableModel tableModel; private TreeExpansionModel treeExpansionModel; - private RequestProcessor rp; // Accessed from TreeModelRoot + private AsynchronousModel asynchModel; private CompoundModel mainSubModel; private CompoundModel[] subModels; @@ -3209,8 +3251,8 @@ NodeActionsProvider nodeActionsProvider, List columnModels, TableModel tableModel, - String propertiesHelpID, - RequestProcessor rp + AsynchronousModel asynchModel, + String propertiesHelpID ) { if (treeModel == null) throw new NullPointerException (); if (treeModel == null) throw new NullPointerException (); @@ -3229,8 +3271,8 @@ this.columnModels = columnModels.toArray ( new ColumnModel [columnModels.size ()] ); + this.asynchModel = asynchModel; this.propertiesHelpID = propertiesHelpID; - this.rp = rp; } private CompoundModel(CompoundModel mainSubModel, @@ -3654,6 +3696,12 @@ } } + // AsynchronousModel + + public Executor asynchronous(CALL asynchCall, Object node) throws UnknownTypeException { + return asynchModel.asynchronous(asynchCall, node); + } + } private static final class ModelLists extends Object { @@ -3669,6 +3717,8 @@ public List nodeActionsProviders = Collections.emptyList(); public List nodeActionsProviderFilters = Collections.emptyList(); public List columnModels = Collections.emptyList(); + //public List asynchModels = Collections.emptyList(); + public List asynchModelFilters = Collections.emptyList(); public void addOtherModels(List otherModels) { Iterator it = otherModels.iterator (); @@ -3730,6 +3780,18 @@ else nodeActionsProviderFilters.add(0, (NodeActionsProviderFilter) model); } + /*if (model instanceof AsynchronousModel) { + asynchModels = new ArrayList(asynchModels); + asynchModels.add((AsynchronousModel) model); + }*/ + if (model instanceof AsynchronousModelFilter) { + asynchModelFilters = new ArrayList(asynchModelFilters); + if (first) + asynchModelFilters.add((AsynchronousModelFilter) model); + else + asynchModelFilters.add(0, (AsynchronousModelFilter) model); + } + if (model instanceof ColumnModel) { columnModels = new ArrayList(columnModels); columnModels.add((ColumnModel) model);