diff -r f950fdcb90e1 spi.viewmodel/apichanges.xml --- a/spi.viewmodel/apichanges.xml Mon Sep 21 15:17:38 2009 +0200 +++ b/spi.viewmodel/apichanges.xml Mon Sep 21 16:39:44 2009 +0200 @@ -337,7 +337,27 @@ - + + + Introduction of AsynchronousModel. + + + + + + This API introduced AsynchronousModel and AsynchronousModelFilter that + can be used by clients to define threading of their model implementations. +

+ Added classes:
+ org.netbeans.spi.viewmodel.AsynchronousModel, + org.netbeans.spi.viewmodel.AsynchronousModelFilter, +

+
+ + + +
+ diff -r f950fdcb90e1 spi.viewmodel/manifest.mf --- a/spi.viewmodel/manifest.mf Mon Sep 21 15:17:38 2009 +0200 +++ b/spi.viewmodel/manifest.mf Mon Sep 21 16:39:44 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 f950fdcb90e1 spi.viewmodel/src/org/netbeans/spi/viewmodel/AsynchronousModel.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/spi.viewmodel/src/org/netbeans/spi/viewmodel/AsynchronousModel.java Mon Sep 21 16:39:44 2009 +0200 @@ -0,0 +1,144 @@ +/* + * 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 org.openide.util.RequestProcessor; + +/** + * Provides threading information about 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, or asynchronously in a Request Processor thread. + * Implementation of this interface can define the threading of method + * calls. + * Register an implementation of this along with other models, + * if you need to change the default threading. + * + * @author Martin Entlicher + * @since 1.20 + */ +public interface AsynchronousModel extends Model { + + /** + * This enumeration identifies method(s) of view models for which + * threading information is provided by {@link #asynchronous(CALL, java.lang.Object)} method. + *
+ * DISPLAY_NAME is for NodeModel.getDisplayName() and ExtendedNodeModel.setName() + *
+ * SHORT_DESCRIPTION for NodeModel.getShortDescription() + *
+ * VALUE for TableModel.getValueAt() and TableModel.setValueAt() + *
+ * CHILDREN for TreeModel.getChildrenCount() and TreeModel.getChildren() + *
+ * The rest of the methods on models are called synchronously, or additional enums can be added. + */ + static enum CALL { DISPLAY_NAME, SHORT_DESCRIPTION, VALUE, CHILDREN } + + /** + * Provide the threading information for view models method calls. + * The returned ThreadProvider gives information about the threading + * of method calls identified by {@link CALL} enum. + * + * @param asynchCall Identification of the method call + * @param node Object node + * @return an instance of ThreadProvider + */ + ThreadProvider asynchronous(CALL asynchCall, Object node) throws UnknownTypeException; + + /** + * Provider of the threading information. + */ + public static final class ThreadProvider extends Object { + + /** + * Threading information for using the current thread for models method calls. + * This will make method invocation synchronous. It's important that the + * methods execute fast so that they do not block AWT thread. + */ + public static final ThreadProvider CURRENT_THREAD = new ThreadProvider(null); + + /** + * Threading information for using a shared {@link RequestProcessor} with + * throughoutput = 1 for models method calls. The UI gives a visual feedback + * to the user if models method calls take a long time. Use this to keep + * the UI responsive. + */ + public static final ThreadProvider DEFAULT_RP = new ThreadProvider(new RequestProcessor("Asynchronous view model", 1)); // NOI18N + + private final RequestProcessor rp; // RP or null for AWT (current thread) + + /** + * Creates a threading information providing the given {@link RequestProcessor} + * for models method calls. Use this to keep the UI responsive when + * models method calls take a long time. + * @param rp the {@link RequestProcessor} to use. + * @return The threading information + */ + public static ThreadProvider createRPProvider(RequestProcessor rp) { + if (rp == null) { + throw new NullPointerException("RequestProcessor must not be null."); // NOI18N + } + return new ThreadProvider(rp); + } + + private ThreadProvider(RequestProcessor rp) { + this.rp = rp; + } + + /** + * Tells if the current thread is to be used for models method calls. + * @return true when the current thread is to be used for + * models method calls. + */ + public boolean isCurrentThread() { + return rp == null; + } + + /** + * Get the {@link RequestProcessor} that is used by this {@link ThreadProvider}, + * if any. + * @return the {@link RequestProcessor} or null. + */ + public RequestProcessor getRequestProcessor() { + return rp; + } + } +} diff -r f950fdcb90e1 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 Mon Sep 21 16:39:44 2009 +0200 @@ -0,0 +1,73 @@ +/* + * 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 org.netbeans.spi.viewmodel.AsynchronousModel.CALL; +import org.netbeans.spi.viewmodel.AsynchronousModel.ThreadProvider; + +/** + * Change threading information about 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, or asynchronously in a Request Processor thread. + * Implementation of this interface can change the threading of method + * calls provided by an original {@link AsynchronousModel}. + * 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 { + + /** + * Change the threading information for view models method calls. + * The returned ThreadProvider gives information about the threading + * of method calls identified by {@link CALL} enum. + * + * @param original The original {@link AsynchronousModel} + * @param asynchCall Identification of the method call + * @param node Object node + * @return an instance of ThreadProvider + */ + ThreadProvider asynchronous(AsynchronousModel original, + CALL asynchCall, Object node) throws UnknownTypeException; + +} diff -r f950fdcb90e1 spi.viewmodel/src/org/netbeans/spi/viewmodel/Models.java --- a/spi.viewmodel/src/org/netbeans/spi/viewmodel/Models.java Mon Sep 21 15:17:38 2009 +0200 +++ b/spi.viewmodel/src/org/netbeans/spi/viewmodel/Models.java Mon Sep 21 16:39:44 2009 +0200 @@ -231,23 +231,19 @@ } } 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 AsynchronousModel(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; } } @@ -271,20 +267,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 > 13) { // AsynchronousModelFilter + ml.asynchModelFilters = (List) models.get(13); + } } } 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 (); @@ -296,6 +290,8 @@ 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; } @@ -313,10 +309,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); @@ -333,6 +329,9 @@ defaultExpansionModels.put(ml, defaultExpansionModel); } ml.treeExpansionModels = Collections.singletonList((TreeExpansionModel) defaultExpansionModel); + } + if (ml.asynchModels.isEmpty()) { + ml.asynchModels = Collections.singletonList((AsynchronousModel) new DefaultAsynchronousModel()); } CompoundModel cm = new CompoundModel ( @@ -357,8 +356,11 @@ new DelegatingTableModel (ml.tableModels), ml.tableModelFilters ), - propertiesHelpID, - rp + createCompoundAsynchronousModel ( + new DelegatingAsynchronousModel (ml.asynchModels), + ml.asynchModelFilters + ), + propertiesHelpID ); if (defaultExpansionModel != null) { defaultExpansionModel.setCompoundModel(cm); @@ -523,6 +525,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; } @@ -1585,6 +1597,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 ThreadProvider asynchronous(CALL asynchCall, Object node) throws UnknownTypeException { + return asynchModelFilter.asynchronous(asynchModel, asynchCall, node); + } + + } + /** * Creates one {@link org.netbeans.spi.viewmodel.TableModel} * from given list of TableModels. DelegatingTableModel asks all underlaying @@ -1901,6 +1928,40 @@ return new String (sb); } } + + private static final class DelegatingAsynchronousModel implements AsynchronousModel { + + private AsynchronousModel[] models; + + public DelegatingAsynchronousModel(List models) { + this(models.toArray(new AsynchronousModel[0])); + } + + private DelegatingAsynchronousModel(AsynchronousModel[] models) { + this.models = models; + } + + public ThreadProvider asynchronous(CALL asynchCall, Object node) throws UnknownTypeException { + // It's not much clear how to merge several AsynchronousModels. + // Just let the RP win. + // Usage of AsynchronousModelFilter is preferred. + ThreadProvider tp = null; + for (int i = 0; i < models.length; i++) { + ThreadProvider tpm = models[i].asynchronous(asynchCall, node); + if (tpm != null) { + if (tp == null) { + tp = tpm; + } else { + if (tpm.getRequestProcessor() != null) { + tp = tpm; + } // current otherwise + } + } + } + return tp; + } + + } private static class DefaultTreeExpansionModel implements TreeExpansionModel { @@ -1961,6 +2022,19 @@ return new DefaultTreeExpansionModel(cmRef.get()); } + } + + private static final class DefaultAsynchronousModel implements AsynchronousModel { + + public ThreadProvider asynchronous(CALL asynchCall, Object node) { + if (asynchCall.equals(CALL.CHILDREN) || asynchCall.equals(CALL.VALUE)) { + // For backward compatibility + return ThreadProvider.DEFAULT_RP; + } else { + return ThreadProvider.CURRENT_THREAD; + } + } + } /** @@ -3160,7 +3234,8 @@ * @author Jan Jancura */ public static final class CompoundModel implements TreeModel, - ExtendedNodeModel, CheckNodeModel, NodeActionsProvider, TableModel, TreeExpansionModel { + ExtendedNodeModel, CheckNodeModel, NodeActionsProvider, TableModel, + TreeExpansionModel, AsynchronousModel { private TreeModel treeModel; private ExtendedNodeModel nodeModel; @@ -3169,7 +3244,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; @@ -3198,8 +3273,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 (); @@ -3218,8 +3293,8 @@ this.columnModels = columnModels.toArray ( new ColumnModel [columnModels.size ()] ); + this.asynchModel = asynchModel; this.propertiesHelpID = propertiesHelpID; - this.rp = rp; } private CompoundModel(CompoundModel mainSubModel, @@ -3643,6 +3718,12 @@ } } + // AsynchronousModel + + public ThreadProvider asynchronous(CALL asynchCall, Object node) throws UnknownTypeException { + return asynchModel.asynchronous(asynchCall, node); + } + } private static final class ModelLists extends Object { @@ -3658,6 +3739,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 (); @@ -3719,6 +3802,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);