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:40 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:40 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/modules/viewmodel/AsynchronousModel.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/spi.viewmodel/src/org/netbeans/modules/viewmodel/AsynchronousModel.java Fri Sep 25 00:16:40 2009 +0200 @@ -0,0 +1,64 @@ +/* + * 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.modules.viewmodel; + +import java.util.concurrent.Executor; +import org.netbeans.spi.viewmodel.AsynchronousModelFilter.CALL; +import org.netbeans.spi.viewmodel.UnknownTypeException; + +/** + * Not in public API, AsynchronousModelFilter is sufficient, since we have a default. + * + * @author Martin Entlicher + */ +public interface AsynchronousModel { + + /** + * Provide the threading information for view models method calls. + * The returned Executor is used to call methods identified by + * {@link CALL} enum. + * + * @param asynchCall Identification of the method call + * @param node Object node + * @return an instance of Executor + */ + Executor asynchronous(CALL asynchCall, Object node) throws UnknownTypeException; + +} diff -r 5cb74240a340 spi.viewmodel/src/org/netbeans/modules/viewmodel/TreeModelHyperNode.java --- a/spi.viewmodel/src/org/netbeans/modules/viewmodel/TreeModelHyperNode.java Thu Sep 24 17:28:44 2009 +0200 +++ b/spi.viewmodel/src/org/netbeans/modules/viewmodel/TreeModelHyperNode.java Fri Sep 25 00:16:40 2009 +0200 @@ -43,12 +43,17 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Set; + +import java.util.concurrent.Executor; +import org.netbeans.spi.viewmodel.AsynchronousModelFilter; +import org.netbeans.spi.viewmodel.AsynchronousModelFilter.CALL; import org.netbeans.spi.viewmodel.Models; import org.netbeans.spi.viewmodel.Models.TreeFeatures; import org.netbeans.spi.viewmodel.TreeModelFilter; import org.netbeans.spi.viewmodel.UnknownTypeException; import org.openide.nodes.Children; import org.openide.nodes.Node; +import org.openide.util.Exceptions; /** * @@ -112,7 +117,31 @@ super(null, model.getColumns(), treeModelRoot, object); this.model = model; } - + + // TODO: Run children of individual models according to individual asynchronous specifications + @Override + protected Executor getModelAsynchronous() { + Executor exec = null; + for (Models.CompoundModel m : model.getModels()) { + try { + Executor e = m.asynchronous(CALL.CHILDREN, object); + if (exec == null) { + exec = e; + } else { + if (e != AsynchronousModelFilter.CURRENT_THREAD) { + exec = e; + } + } + } catch (UnknownTypeException ex) { + Exceptions.printStackTrace(Exceptions.attachMessage(ex, "model = "+model+", object = "+object)); + } + } + if (exec == null) { + exec = AsynchronousModelFilter.CURRENT_THREAD; + } + return exec; + } + @Override protected Object[] getModelChildren(RefreshingInfo refreshInfo) throws UnknownTypeException { if (refreshInfo instanceof HyperRefreshingInfo) { diff -r 5cb74240a340 spi.viewmodel/src/org/netbeans/modules/viewmodel/TreeModelNode.java --- a/spi.viewmodel/src/org/netbeans/modules/viewmodel/TreeModelNode.java Thu Sep 24 17:28:44 2009 +0200 +++ b/spi.viewmodel/src/org/netbeans/modules/viewmodel/TreeModelNode.java Fri Sep 25 00:16:40 2009 +0200 @@ -55,6 +55,7 @@ import java.util.List; import java.util.Map; import java.util.WeakHashMap; +import java.util.concurrent.Executor; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.AbstractAction; @@ -62,6 +63,8 @@ import javax.swing.KeyStroke; import javax.swing.SwingUtilities; +import org.netbeans.spi.viewmodel.AsynchronousModelFilter; +import org.netbeans.spi.viewmodel.AsynchronousModelFilter.CALL; import org.netbeans.spi.viewmodel.ColumnModel; import org.netbeans.spi.viewmodel.ModelEvent; import org.netbeans.spi.viewmodel.Models; @@ -106,6 +109,7 @@ private String shortDescription; private final Object shortDescriptionLock = new Object(); private final Map properties = new HashMap(); + private static final String EVALUATING_STR = NbBundle.getMessage(TreeModelNode.class, "EvaluatingProp"); // init .................................................................... @@ -218,6 +222,27 @@ return shortDescription; } } + Executor exec; + try { + exec = model.asynchronous(CALL.SHORT_DESCRIPTION, object); + } catch (Exception ex) { + Exceptions.printStackTrace(Exceptions.attachMessage(ex, "model = "+model+", object = "+object)); + exec = AsynchronousModelFilter.CURRENT_THREAD; + } + if (exec == AsynchronousModelFilter.CURRENT_THREAD) { + return updateShortDescription(); + } else { + exec.execute(new Runnable() { + public void run() { + updateShortDescription(); + fireShortDescriptionChange(null, null); + } + }); + return EVALUATING_STR; + } + } + + private String updateShortDescription() { try { String sd = model.getShortDescription (object); if (sd != null) { @@ -404,17 +429,7 @@ boolean refreshed = false; if ((ModelEvent.NodeChanged.DISPLAY_NAME_MASK & changeMask) != 0) { try { - String name = model.getDisplayName (object); - if (name == null) { - Throwable t = - new NullPointerException ( - "Model: " + model + ".getDisplayName (" + object + - ") = null!" - ); - Exceptions.printStackTrace(t); - } else { - setName (name, false); - } + setModelDisplayName(); } catch (UnknownTypeException e) { Logger.getLogger(TreeModelNode.class.getName()).log(Level.CONFIG, "Model: "+model, e); } @@ -459,10 +474,10 @@ private static RequestProcessor requestProcessor; // Accessed from test RequestProcessor getRequestProcessor () { - RequestProcessor rp = treeModelRoot.getRequestProcessor(); + /*RequestProcessor rp = treeModelRoot.getRequestProcessor(); if (rp != null) { return rp; - } + }*/ synchronized (TreeModelNode.class) { if (requestProcessor == null) requestProcessor = new RequestProcessor ("TreeModel", 1); @@ -498,19 +513,59 @@ } } } + + private void setModelDisplayName() throws UnknownTypeException { + Executor exec; + try { + exec = model.asynchronous(CALL.DISPLAY_NAME, object); + } catch (Exception ex) { + Exceptions.printStackTrace(Exceptions.attachMessage(ex, "model = "+model+", object = "+object)); + exec = AsynchronousModelFilter.CURRENT_THREAD; + } + if (exec == AsynchronousModelFilter.CURRENT_THREAD) { + String name = model.getDisplayName (object); + if (name == null) { + Throwable t = + new NullPointerException ( + "Model: " + model + ".getDisplayName (" + object + + ") = null!" + ); + Exceptions.printStackTrace(t); + } else { + setName (name, false); + } + } else { + final String originalDisplayName = getDisplayName(); + setName(EVALUATING_STR, false); + exec.execute(new Runnable() { + public void run() { + String name; + try { + name = model.getDisplayName(object); + } catch (UnknownTypeException ex) { + Logger.getLogger(TreeModelNode.class.getName()).log(Level.CONFIG, "Model: "+model, ex); + setName(originalDisplayName, false); + return ; + } + if (name == null) { + Throwable t = + new NullPointerException ( + "Model: " + model + ".getDisplayName (" + object + + ") = null!" + ); + Exceptions.printStackTrace(t); + setName(originalDisplayName, false); + } else { + setName (name, false); + } + } + }); + } + } private void refreshNode () { try { - String name = model.getDisplayName (object); - if (name == null) { - Throwable t = - new NullPointerException ( - "Model: " + model + ".getDisplayName (" + object + - ") = null!" - ); - Exceptions.printStackTrace(t); - } - setName (name, false); + setModelDisplayName(); String iconBase = null; if (model.getRoot() != object) { iconBase = model.getIconBaseWithExtension (object); @@ -834,7 +889,7 @@ /** Special locals subnodes (children) */ static class TreeModelChildren extends Children.Keys - implements LazyEvaluator.Evaluable { + implements Runnable {// LazyEvaluator.Evaluable { private boolean initialezed = false; private final Models.CompoundModel model; @@ -847,6 +902,9 @@ private Object[] children_evaluated; private RefreshingInfo refreshInfo = null; private boolean refreshingStarted = true; + + private RequestProcessor.Task task; + private RequestProcessor lastRp; protected static final Object WAIT_KEY = new Object(); @@ -881,7 +939,7 @@ refreshLazyChildren(refreshSubNodes); } - public void evaluateLazily(Runnable evaluatedNotify) { + public void run() { RefreshingInfo rinfo; synchronized (evaluated) { refreshingStarted = false; @@ -911,7 +969,7 @@ Exceptions.printStackTrace(t); ch = new Object[0]; } - evaluatedNotify.run(); + //evaluatedNotify.run(); boolean fire; synchronized (evaluated) { int eval = evaluated[0]; @@ -942,8 +1000,33 @@ count ); } + + protected Executor getModelAsynchronous() { + Executor exec; + try { + exec = model.asynchronous(CALL.CHILDREN, object); + } catch (Exception ex) { + Exceptions.printStackTrace(Exceptions.attachMessage(ex, "model = "+model+", object = "+object)); + exec = AsynchronousModelFilter.CURRENT_THREAD; + } + return exec; + } private void refreshLazyChildren (RefreshingInfo refreshInfo) { + Executor exec = getModelAsynchronous(); + if (exec == AsynchronousModelFilter.CURRENT_THREAD) { + Object[] ch; + try { + ch = getModelChildren(refreshInfo); + } catch (UnknownTypeException ex) { + ch = new Object [0]; + if (!(object instanceof String)) { + Logger.getLogger(TreeModelNode.class.getName()).log(Level.CONFIG, "Model: "+model, ex); + } + } + applyChildren(ch, refreshInfo); + return ; + } synchronized (evaluated) { evaluated[0] = 0; refreshingStarted = true; @@ -954,8 +1037,19 @@ } //System.err.println(this.hashCode()+" refreshLazyChildren() started = true, evaluated = 0"); } + /*if (exec instanceof RequestProcessor) { + // Have a single task for RP + RequestProcessor rp = (RequestProcessor) exec; + if (rp != lastRp) { + task = rp.create(this); + lastRp = rp; + } + task.schedule(0); + } else {*/ + exec.execute(this); + //} // It's refresh => do not check for this children already being evaluated - treeModelRoot.getChildrenEvaluator().evaluate(this, false); + //treeModelRoot.getChildrenEvaluator().evaluate(this, false); Object[] ch; synchronized (evaluated) { if (evaluated[0] != 1) { @@ -1110,14 +1204,16 @@ } } // ItemChildren - private class MyProperty extends PropertySupport implements LazyEvaluator.Evaluable { + private class MyProperty extends PropertySupport implements Runnable { //LazyEvaluator.Evaluable { - private final String EVALUATING_STR = NbBundle.getMessage(TreeModelNode.class, "EvaluatingProp"); private String id; private ColumnModel columnModel; private boolean nodeColumn; private TreeModelRoot treeModelRoot; private final int[] evaluated = { 0 }; // 0 - not yet, 1 - evaluated, -1 - timeouted + + private RequestProcessor.Task task; + private RequestProcessor lastRp; MyProperty ( @@ -1155,12 +1251,13 @@ } } - public void evaluateLazily(Runnable evaluatedNotify) { + public void run() { Object value = ""; String htmlValue = null; String nonHtmlValue = null; try { value = model.getValueAt (object, id); + //System.err.println(" Value of ("+object+") executed in "+Thread.currentThread()+" is "+value); //System.out.println(" evaluateLazily("+TreeModelNode.this.getDisplayName()+", "+id+"): have value = "+value); //System.out.println(" object = "+object+" class = "+((object != null) ? object.getClass().toString() : "null")); if (value instanceof String) { @@ -1175,9 +1272,9 @@ System.out.println (); } } catch (Throwable t) { - t.printStackTrace(); + Exceptions.printStackTrace(t); } finally { - evaluatedNotify.run(); + //evaluatedNotify.run(); boolean fire; synchronized (properties) { if (value instanceof String) { @@ -1211,10 +1308,33 @@ return properties.get (id); } + Executor exec; + try { + exec = model.asynchronous(CALL.VALUE, object); + } catch (Exception ex) { + Exceptions.printStackTrace(ex); + exec = AsynchronousModelFilter.CURRENT_THREAD; + } + + if (exec == AsynchronousModelFilter.CURRENT_THREAD) { + return getTheValue(); + } + synchronized (evaluated) { evaluated[0] = 0; } - treeModelRoot.getValuesEvaluator().evaluate(this); + /*if (exec instanceof RequestProcessor) { + RequestProcessor rp = (RequestProcessor) exec; + if (rp != lastRp) { + task = rp.create(this); + lastRp = rp; + } + task.schedule(0); + } else {*/ + //System.err.println("getTheValue of ("+object+") executed in "+exec); + exec.execute(this); + //} + //treeModelRoot.getValuesEvaluator().evaluate(this); Object ret = null; @@ -1241,6 +1361,36 @@ // htmlDisplayValue attr will assure that the Evaluating str is there. } return ret; + } + + private Object getTheValue() { + Object value = ""; + String htmlValue = null; + String nonHtmlValue = null; + try { + value = model.getValueAt (object, id); + //System.err.println(" Value of ("+object+") executed in "+Thread.currentThread()+" is "+value); + //System.out.println(" evaluateLazily("+TreeModelNode.this.getDisplayName()+", "+id+"): have value = "+value); + //System.out.println(" object = "+object+" class = "+((object != null) ? object.getClass().toString() : "null")); + if (value instanceof String) { + htmlValue = htmlValue ((String) value); + nonHtmlValue = removeHTML ((String) value); + } + } catch (UnknownTypeException e) { + if (!(object instanceof String)) { + Logger.getLogger(TreeModelNode.class.getName()).log(Level.CONFIG, "Model: "+model+"\n,Column id:" + columnModel.getID (), e); + } + } finally { + synchronized (properties) { + if (value instanceof String) { + properties.put (id, nonHtmlValue); + properties.put (id + "#html", htmlValue); + } else { + properties.put (id, value); + } + } + } + return value; } @Override @@ -1270,20 +1420,48 @@ if (!properties.containsKey(id)) { return null; // The same as value => EVALUATING_STR } + String shortDescription = (String) properties.get (id + "#shortDescription"); + if (shortDescription != null) { + return shortDescription; + } } + Executor exec; + try { + exec = model.asynchronous(CALL.SHORT_DESCRIPTION, object); + } catch (Exception ex) { + Exceptions.printStackTrace(Exceptions.attachMessage(ex, "model = "+model+", value = "+object)); + exec = AsynchronousModelFilter.CURRENT_THREAD; + } + if (exec == AsynchronousModelFilter.CURRENT_THREAD) { + return updateShortDescription(); + } else { + exec.execute(new Runnable() { + public void run() { + updateShortDescription(); + firePropertyChange(id, null, null); + } + }); + return null; + } + } + + private String updateShortDescription() { try { javax.swing.JToolTip tooltip = new javax.swing.JToolTip(); + String sd = null; try { tooltip.putClientProperty("getShortDescription", object); // NOI18N Object tooltipObj = model.getValueAt(tooltip, id); - if (tooltipObj == null) { - return null; - } else { - return adjustHTML(tooltipObj.toString()); + if (tooltipObj != null) { + sd = adjustHTML(tooltipObj.toString()); } + return sd; } finally { // We MUST clear the client property, Swing holds this in a static reference! tooltip.putClientProperty("getShortDescription", null); // NOI18N + synchronized (properties) { + properties.put (id + "#shortDescription", sd); + } } } catch (UnknownTypeException e) { // Ignore models that do not define tooltips for values. @@ -1293,30 +1471,41 @@ public void setValue (final Object value) throws IllegalAccessException, IllegalArgumentException, java.lang.reflect.InvocationTargetException { - RequestProcessor prefferedRequestProcessor = treeModelRoot.getRequestProcessor(); - if (prefferedRequestProcessor == null) { - prefferedRequestProcessor = new RequestProcessor("Debugger Values Setter", 1); // NOI18N + Executor exec; + try { + exec = model.asynchronous(CALL.VALUE, object); + } catch (Exception ex) { + Exceptions.printStackTrace(Exceptions.attachMessage(ex, "model = "+model+", object = "+object)); + exec = AsynchronousModelFilter.CURRENT_THREAD; } - prefferedRequestProcessor.post(new Runnable() { - public void run() { - try { - Object v = value; - model.setValueAt (object, id, v); - v = model.getValueAt(object, id); // Store the new value - synchronized (properties) { - if (v instanceof String) { - properties.put (id, removeHTML ((String) v)); - properties.put (id + "#html", htmlValue ((String) v)); - } else { - properties.put (id, v); - } - } - firePropertyChange (id, null, null); - } catch (UnknownTypeException e) { - Logger.getLogger(TreeModelNode.class.getName()).log(Level.CONFIG, "Column id:" + columnModel.getID ()+"\nModel: "+model, e); + if (exec == AsynchronousModelFilter.CURRENT_THREAD) { + setTheValue(value); + } else { + exec.execute(new Runnable() { + public void run() { + setTheValue(value); + } + }); + } + } + + private void setTheValue(final Object value) { + try { + Object v = value; + model.setValueAt (object, id, v); + v = model.getValueAt(object, id); // Store the new value + synchronized (properties) { + if (v instanceof String) { + properties.put (id, removeHTML ((String) v)); + properties.put (id + "#html", htmlValue ((String) v)); + } else { + properties.put (id, v); } } - }); + firePropertyChange (id, null, null); + } catch (UnknownTypeException e) { + Logger.getLogger(TreeModelNode.class.getName()).log(Level.CONFIG, "Column id:" + columnModel.getID ()+"\nModel: "+model, e); + } } @Override @@ -1325,10 +1514,10 @@ } } - /** The single-threaded evaluator of lazy models. */ + /** The single-threaded evaluator of lazy models. *//* static class LazyEvaluator implements Runnable { - /** Release the evaluator task after this time. */ + /** Release the evaluator task after this time. *//* private static final long EXPIRE_TIME = 1000L; private final List objectsToEvaluate = new LinkedList(); @@ -1394,7 +1583,7 @@ } - } + }*/ } diff -r 5cb74240a340 spi.viewmodel/src/org/netbeans/modules/viewmodel/TreeModelRoot.java --- a/spi.viewmodel/src/org/netbeans/modules/viewmodel/TreeModelRoot.java Thu Sep 24 17:28:44 2009 +0200 +++ b/spi.viewmodel/src/org/netbeans/modules/viewmodel/TreeModelRoot.java Fri Sep 25 00:16:40 2009 +0200 @@ -46,6 +46,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Map; import java.util.WeakHashMap; import java.util.logging.Level; import java.util.logging.Logger; @@ -89,19 +90,18 @@ private DefaultTreeFeatures treeFeatures; private ExplorerManager manager; - /** The children evaluator for view of this root. */ - private TreeModelNode.LazyEvaluator childrenEvaluator; - /** The values evaluator for view of this root. */ - private TreeModelNode.LazyEvaluator valuesEvaluator; - - /** RequestProcessor to be used for evaluations. */ - private RequestProcessor rp; + /** The children evaluator for view of this root. * + private final Map childrenEvaluators + = new WeakHashMap(); + /** The values evaluator for view of this root. * + private final Map valuesEvaluators + = new WeakHashMap(); + */ public TreeModelRoot (Models.CompoundModel model, TreeView treeView) { this.model = model; this.manager = ExplorerManager.find(treeView); this.treeFeatures = new DefaultTreeFeatures(treeView); - getRP(); modelListeners = new ModelChangeListener[] { new ModelChangeListener(model) }; model.addModelListener (modelListeners[0]); } @@ -111,7 +111,6 @@ this.model = model.getMain(); this.manager = ExplorerManager.find(treeView); this.treeFeatures = new DefaultTreeFeatures(treeView); - getRP(); int nl = model.getModels().length; modelListeners = new ModelChangeListener[nl]; for (int i = 0; i < nl; i++) { @@ -125,7 +124,6 @@ this.model = model; this.manager = ExplorerManager.find(outlineView); this.treeFeatures = new DefaultTreeFeatures(outlineView); - getRP(); modelListeners = new ModelChangeListener[] { new ModelChangeListener(model) }; model.addModelListener (modelListeners[0]); } @@ -135,7 +133,6 @@ this.model = model.getMain(); this.manager = ExplorerManager.find(outlineView); this.treeFeatures = new DefaultTreeFeatures(outlineView); - getRP(); int nl = model.getModels().length; modelListeners = new ModelChangeListener[nl]; for (int i = 0; i < nl; i++) { @@ -145,25 +142,6 @@ } } - private void getRP() { - try { - java.lang.reflect.Field rpf = model.getClass().getDeclaredField("rp"); - rpf.setAccessible(true); - this.rp = (RequestProcessor) rpf.get(model); - } catch (Exception ex) { - Exceptions.printStackTrace(ex); - } - /*if (rp == null) { - Exceptions.printStackTrace(new RuntimeException("NULL RP for "+model)); - } else { - Exceptions.printStackTrace(new RuntimeException("RP for "+model+" is: "+rp)); - }*/ - } - - public RequestProcessor getRequestProcessor() { - return rp; - } - public TreeFeatures getTreeFeatures () { return treeFeatures; } @@ -241,20 +219,26 @@ // } // }); // } - - synchronized TreeModelNode.LazyEvaluator getChildrenEvaluator() { + + /* + synchronized TreeModelNode.LazyEvaluator getChildrenEvaluator(RequestProcessor rp) { + TreeModelNode.LazyEvaluator childrenEvaluator = childrenEvaluators.get(rp); if (childrenEvaluator == null) { childrenEvaluator = new TreeModelNode.LazyEvaluator(rp); + childrenEvaluators.put(rp, childrenEvaluator); } return childrenEvaluator; } - synchronized TreeModelNode.LazyEvaluator getValuesEvaluator() { + synchronized TreeModelNode.LazyEvaluator getValuesEvaluator(RequestProcessor rp) { + TreeModelNode.LazyEvaluator valuesEvaluator = valuesEvaluators.get(rp); if (valuesEvaluator == null) { valuesEvaluator = new TreeModelNode.LazyEvaluator(rp); + valuesEvaluators.put(rp, valuesEvaluator); } return valuesEvaluator; } + */ public synchronized void destroy () { if (model != null) { 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:40 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:40 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); diff -r 5cb74240a340 spi.viewmodel/test/unit/src/org/netbeans/modules/viewmodel/AsynchronousTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/spi.viewmodel/test/unit/src/org/netbeans/modules/viewmodel/AsynchronousTest.java Fri Sep 25 00:16:40 2009 +0200 @@ -0,0 +1,328 @@ +/* + * 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.modules.viewmodel; + +import java.beans.BeanInfo; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.Executor; +import javax.swing.SwingUtilities; +import org.netbeans.junit.NbTestCase; +import org.netbeans.spi.viewmodel.AsynchronousModelFilter; +import org.netbeans.spi.viewmodel.AsynchronousModelFilter.CALL; +import org.netbeans.spi.viewmodel.Model; +import org.netbeans.spi.viewmodel.Models; +import org.netbeans.spi.viewmodel.UnknownTypeException; +import org.openide.nodes.Node; +import org.openide.nodes.Node.Property; +import org.openide.nodes.Node.PropertySet; +import org.openide.util.Exceptions; +import org.openide.util.RequestProcessor; + +/** + * + * @author Martin Entlicher + */ +public class AsynchronousTest extends NbTestCase { + + private static final Set SYNCHRONOUS_METHODS = Collections.unmodifiableSet(new HashSet( + Arrays.asList( "isLeaf", // TreeModel + "getIconBase", "canCopy", "canCut", "canRename", // NodeModel + "clipboardCopy", "clipboardCut", "getPasteTypes", "getIconBaseWithExtension", // ExtendedNodeModel + "isReadOnly" ))); // TableModel + + public AsynchronousTest(String s) { + super(s); + } + + public void testDefaultThreadingAccess() throws Exception { + Map defaultRPs = new HashMap(); + defaultRPs.put(CALL.CHILDREN, AsynchronousModelFilter.DEFAULT); + defaultRPs.put(CALL.VALUE, AsynchronousModelFilter.DEFAULT); + + Map defaultMethodThreads = new HashMap(); + AWTChecker awtc = new AWTChecker(); + for (String methodName : SYNCHRONOUS_METHODS) { + defaultMethodThreads.put(methodName, awtc); + } + defaultMethodThreads.put("getDisplayName", awtc); + defaultMethodThreads.put("getShortDescription", awtc); + defaultMethodThreads.put("getShortDescription (called on property values)", awtc); + RPChecker rpc = new RPChecker((RequestProcessor) AsynchronousModelFilter.DEFAULT); + defaultMethodThreads.put("getChildren", rpc); + defaultMethodThreads.put("getChildrenCount", rpc); + defaultMethodThreads.put("getValueAt", rpc); + CheckCallingThreadModel cm = new CheckCallingThreadModel(new String[] { "a", "b", "c" }, 2, defaultMethodThreads); + ArrayList l = new ArrayList (); + l.add(cm); + l.addAll(Arrays.asList(cm.createColumns())); + final Models.CompoundModel mcm = Models.createCompoundModel(l); + SwingUtilities.invokeAndWait(new GUIQuerier(mcm)); + Map failedMethods = cm.getFailedMethods(); + assertEquals(failedMethods.toString(), 0, failedMethods.size()); + } + + public void testSynchronousAccess() throws Exception { + AWTChecker awtc = new AWTChecker(); + CheckCallingThreadModel cm = new CheckCallingThreadModel(new String[] { "a", "b", "c" }, 2, new ConstantCheckersMap(awtc)); + SynchronousModelImpl sm = new SynchronousModelImpl(); + ArrayList l = new ArrayList (); + l.add(cm); + l.addAll(Arrays.asList(cm.createColumns())); + l.add(sm); + final Models.CompoundModel mcm = Models.createCompoundModel(l); + SwingUtilities.invokeAndWait(new GUIQuerier(mcm)); + Map failedMethods = cm.getFailedMethods(); + assertEquals(failedMethods.toString(), 0, failedMethods.size()); + } + + public void testDefaultRPAccess() throws Exception { + RPChecker rpc = new RPChecker((RequestProcessor) AsynchronousModelFilter.DEFAULT); + CheckCallingThreadModel cm = new CheckCallingThreadModel(new String[] { "a", "b", "c" }, 2, new ConstantCheckersMap(rpc)); + DefaultRPModelImpl drpm = new DefaultRPModelImpl(); + ArrayList l = new ArrayList (); + l.add(cm); + l.addAll(Arrays.asList(cm.createColumns())); + l.add(drpm); + final Models.CompoundModel mcm = Models.createCompoundModel(l); + SwingUtilities.invokeAndWait(new GUIQuerier(mcm)); + Map failedMethods = cm.getFailedMethods(); + assertEquals(failedMethods.toString(), 0, failedMethods.size()); + } + + private static final class GUIQuerier implements Runnable { + + private final Models.CompoundModel mcm; + + public GUIQuerier(Models.CompoundModel mcm) { + this.mcm = mcm; + } + + public void run() { + OutlineTable tt = (OutlineTable) Models.createView(mcm); + Node root = tt.getExplorerManager ().getRootContext (); + + root.getChildren().getNodes(); + root.getHtmlDisplayName(); + root.getShortDescription(); + for (Node n : root.getChildren().getNodes()) { + inspectNode(n); + for (Node nn : n.getChildren().getNodes()) { + inspectNode(nn); + } + } + } + + private void inspectNode(Node n) { + n.getDisplayName(); + n.getHtmlDisplayName(); + n.getShortDescription(); + n.getIcon(BeanInfo.ICON_COLOR_16x16); + n.canCopy(); + n.canCut(); + n.canRename(); + n.getNewTypes(); + n.getActions(true); + n.getPreferredAction(); + inspectProperties(n); + } + + private void inspectProperties(Node n) { + PropertySet[] propertySets = n.getPropertySets(); + for (PropertySet ps : propertySets) { + for (Property p : ps.getProperties()) { + try { + p.getValue(); + } catch (IllegalAccessException ex) { + Exceptions.printStackTrace(ex); + } catch (InvocationTargetException ex) { + Exceptions.printStackTrace(ex); + } + p.canRead(); + p.canWrite(); + p.getName(); + p.getDisplayName(); + p.getHtmlDisplayName(); + p.getShortDescription(); + } + } + } + } + + private static interface ThreadChecker { + + boolean isInCorrectThread(); + + } + + private static final class AWTChecker implements ThreadChecker { + + public boolean isInCorrectThread() { + return SwingUtilities.isEventDispatchThread(); + } + + } + + private static final class RPChecker implements ThreadChecker { + + private RequestProcessor rp; + + public RPChecker(RequestProcessor rp) { + this.rp = rp; + } + + public boolean isInCorrectThread() { + return rp.isRequestProcessorThread(); + } + + } + + private final class ConstantCheckersMap implements Map { + + private final ThreadChecker tc; + + public ConstantCheckersMap(ThreadChecker tc) { + this.tc = tc; + } + + public int size() { return Integer.MAX_VALUE; } + + public boolean isEmpty() { return false; } + + public boolean containsKey(Object key) { return true; } + + public boolean containsValue(Object value) { return value == tc; } + + public Object get(Object key) { + if (SYNCHRONOUS_METHODS.contains(key)) { + return new AWTChecker(); + } + return tc; + } + + public Object put(Object key, Object value) { throw new UnsupportedOperationException("N/A"); } + + public Object remove(Object key) { throw new UnsupportedOperationException("N/A"); } + + public void putAll(Map t) { throw new UnsupportedOperationException("N/A"); } + + public void clear() { throw new UnsupportedOperationException("N/A"); } + + public Set keySet() { throw new UnsupportedOperationException("N/A"); } + + public Collection values() { return Collections.singleton(tc); } + + public Set entrySet() { throw new UnsupportedOperationException("N/A"); } + + } + + private static class CheckCallingThreadModel extends CountedModel { + + private final Map threadCheckers; + private final Map failedMethods = new HashMap(); + + public CheckCallingThreadModel(String[] children, int depth, + Map threadCheckers) { + super(children, depth); + this.threadCheckers = threadCheckers; + } + + @Override + protected void countCall(String methodName, Object... params) { + if ("getValueAt".equals(methodName) && params[0] instanceof javax.swing.JToolTip) { + methodName = "getShortDescription (called on property values)"; + } + ThreadChecker tc = threadCheckers.get(methodName); + if (!tc.isInCorrectThread()) { + failedMethods.put(methodName, Thread.currentThread()); + //Thread.dumpStack(); + } + super.countCall(methodName, params); + } + + Map getFailedMethods() { + return failedMethods; + } + + } + + private static class SynchronousModelImpl implements AsynchronousModelFilter { + + public Executor asynchronous(Executor original, CALL asynchCall, Object node) throws UnknownTypeException { + return AsynchronousModelFilter.CURRENT_THREAD; + } + + } + + private static class DefaultRPModelImpl implements AsynchronousModelFilter { + + public Executor asynchronous(Executor original, CALL asynchCall, Object node) throws UnknownTypeException { + return AsynchronousModelFilter.DEFAULT; + } + + } + + private static class CustomRPModelImpl implements AsynchronousModelFilter { + + private Map rps; + + public CustomRPModelImpl(Map rps) { + this.rps = rps; + } + + public Executor asynchronous(Executor original, CALL asynchCall, Object node) throws UnknownTypeException { + RequestProcessor rp = (RequestProcessor) rps.get(asynchCall); + if (rp != null) { + return rp; + } else { + return AsynchronousModelFilter.CURRENT_THREAD; + } + } + + } + +} diff -r 5cb74240a340 spi.viewmodel/test/unit/src/org/netbeans/modules/viewmodel/CountedModel.java --- a/spi.viewmodel/test/unit/src/org/netbeans/modules/viewmodel/CountedModel.java Thu Sep 24 17:28:44 2009 +0200 +++ b/spi.viewmodel/test/unit/src/org/netbeans/modules/viewmodel/CountedModel.java Fri Sep 25 00:16:40 2009 +0200 @@ -263,7 +263,7 @@ } } - private void countCall(String methodName, Object... params) { + protected void countCall(String methodName, Object... params) { CountedCall cc = new CountedCall(methodName, params); if (!countedCalls.add(cc)) { for (CountedCall ecc : countedCalls) {