? src/org/openide/resources/wait.gif Index: src/org/openide/loaders/Bundle.properties =================================================================== RCS file: /cvs/openide/src/org/openide/loaders/Bundle.properties,v retrieving revision 1.94 diff -r1.94 Bundle.properties 199a200,201 > > LBL_Wait=Please wait... Index: src/org/openide/loaders/FolderChildren.java =================================================================== RCS file: /cvs/openide/src/org/openide/loaders/FolderChildren.java,v retrieving revision 1.62 diff -r1.62 FolderChildren.java 24a25 > import org.openide.util.NbBundle; 26a28 > import org.openide.nodes.AbstractNode; 39,42d40 < /** initialization of children task */ < private Task initTask; < /** last task that refreshes the children nodes */ < private Task refreshTask; 46a45,66 > /** Whether the children has been already computed. */ > private boolean childrenComputed; > /** this is true between addNotify and removeNotify */ > private boolean active; > > /** Private req processor for the refresh tasks */ > private static RequestProcessor refRP = > new RequestProcessor("FolderChildren_Refresh"); // NOI18N > > /** Private req processor for the wait node addition */ > private static RequestProcessor waitNodeRP = > new RequestProcessor("FolderChildren_WaitNode"); // NOI18N > > /** The key used to denote the please wait node. > * It is also used as a lock for the lazy initializaiton > * of the pleaseWaitNode. > */ > private static final Object PLEASE_WAIT_KEY = new Object(); > > /** wait node - please use method getPleaseWaitNode() > * to get a lazy initialized instance */ > private static Node pleaseWaitNode; 80c100 < refreshChildren (); --- > refreshChildren(); 88c108 < refreshChildren (); --- > refreshChildren(); 95,118c115,116 < void refreshChildren () { < initialize (true, false); < } < < /** Creates a key for given data object. < * This method is here to create something different then data object, < * because the data object should be finalized when not needed and < * that is why it should not be used as a key. < * < * @param obj data object < * @return key representing the data object. < */ < static Object createKey (DataObject obj) { < return new Pair (obj.getPrimaryFile ()); < } < < /** This method takes the key created by createKey and converts it < * into primary file. < * < * @param key the key < * @return primary file of the key < */ < private static FileObject getFile (Object key) { < return ((Pair)key).primaryFile; --- > Task refreshChildren() { > return refRP.post(new ChildrenRefreshRunnable()); 124a123,125 > if (key == PLEASE_WAIT_KEY) { > return new Node[] { getPleaseWaitNode() }; > } 126c127 < FileObject fo = getFile (key); --- > FileObject fo = ((Pair)key).primaryFile; 130d130 < 141,183c141,146 < < /** Improves the searching capability to wait till all children < * are found and then searching thru them. < */ < public Node findChild (final String name) { < // start the initialization < Node[] forget = getNodes (); // DO NOT DELETE < < // waits till the list of children is created < < // JST: the folowing code is replacement for < // initialize (false, false).waitFinished () < // < // the orgiginal code caused deadlocks when called with readAccess, < // because the refreshChildren (which we wait for to be finished) < // method calls setKeys and they require write request < // < // this code could probably be replaced by < // Children.MUTEX.isReadAccess, if such call would be added to Mutex < class Jst implements Runnable { < public boolean checkReadOrWrite; < public boolean inReadAccess = true; < public boolean inWriteAccess = true; < public void run () { < if (checkReadOrWrite) { < inReadAccess = false; < } else { < inWriteAccess = false; < } < } < } < Jst t = new Jst (); < // the code will run either immediatally or after we leave readAccess < // section < Children.MUTEX.postWriteRequest(t); < t.checkReadOrWrite = true; < Children.MUTEX.postReadRequest(t); < < if (!t.inReadAccess && !t.inWriteAccess) { < // if we are not in readAccess we can safely wait till < // refreshChildren finishes and sets new keys for the < initialize (name == null, false).waitFinished (); < refreshTask.waitFinished (); --- > > public Node[] getNodes(boolean optimalResult) { > if (optimalResult) { > active = true; > Task t = refreshChildren(); > t.waitFinished(); 185,188c148 < < // do the regular child lookup < Node node = super.findChild (name); < return node; --- > return getNodes(); 190c150 < --- > 194d153 < initialize (true, true); 196a156,161 > // > active = true; > // set the please wait node after 100 millis. > waitNodeRP.post(new PleaseWaitNodeSetter(), 100); > // start the refresh task to compute the children > refreshChildren(); 204,243c169,174 < setKeys (java.util.Collections.EMPTY_SET); < } < < /** Ensures that the content of children will be filled sometime. < * @param force true if the content should be filled immediatelly < */ < private Task initialize (boolean force, boolean waitFirst) { < < Task t = initTask; < < if (t != null && t.isFinished()) { < // if the original refresh task is finished we whould check if there are < // some new changes in progress and wait for them to finish < FolderList l = FolderList.find (folder.getPrimaryFile(), true); < l.waitProcessingFinished (); < < t = initTask; < } < < if (t != null && !force) { < return t; < } < < // if (t != null) { < // t.waitFinished (); < // refreshTask.waitFinished (); < // return t; < // } < < final Addition add = new Addition (waitFirst); < refreshTask = add.refTask; < < initTask = t = folder.computeChildrenList (add); < t.addTaskListener (add); < < if (waitFirst) { < add.waitFirst (); < } < < return t; --- > // > active = false; > // we don't call the setKeys directly here because > // there can be a task spawned by refreshChildren - so > // we want to clear the children after that task is finished > refreshChildren(); 250,256d180 < < /** time delay between two inserts of nodes */ < private static final int TIME_DELAY = 1024; < < /** Private req processor for Addition's refresh tasks */ < private static RequestProcessor refRP = < new RequestProcessor("FolderChildren_refresher"); 258,299c182,196 < /** Support for incremental adding of new nodes. < * < *

< * There is a deadlock warning: < *

    < *
  1. A thread waiting in the waitFirst method can have MUTEX.readAccess < *
  2. Thread running run () needs access to MUTEX.writeAccess (because of setKeys) < *
  3. Be sure that the thread leaves waitFirst before writeAccess is needed < *
< */ < private class Addition < implements TaskListener, FolderListListener, Runnable { < static final long serialVersionUID =-4194617547214845940L; < < /** last time of addition */ < private long time = System.currentTimeMillis () + TIME_DELAY; < /** delay */ < private int delay = TIME_DELAY; < < /** update the nodes during processing or only at the end */ < private boolean processingUpdate; < < /** a keys to be later refreshed */ < private List refKeys; < < // fix of #27025. We have to keep reference to all data objects < // for the whole lifetime of this object otherwise it can happen < // that a data object is GCed between calling refreshKeys and < // createNodes < private List dataObjects = new ArrayList(); < < /** processing of DataFolder.computeChildrenList finished */ < private boolean processingFinished; < < /** a task that is used to request refreshing of keys */ < private RequestProcessor.Task refTask = refRP.create(this); // NOI18N < < /** @param processingUpdate update the nodes during < * processing or only at the end < */ < public Addition (boolean processingUpdate) { < this.processingUpdate = processingUpdate; --- > /** Lazy initialization of the please wait node */ > private static Node getPleaseWaitNode() { > boolean created = false; > synchronized (PLEASE_WAIT_KEY) { > if (pleaseWaitNode == null) { > pleaseWaitNode = new AbstractNode(Children.LEAF); > created = true; > } > } > if (created) { > pleaseWaitNode.setName("wait"); // NOI18N > pleaseWaitNode.setDisplayName( > NbBundle.getBundle(FolderChildren.class).getString("LBL_Wait") > ); > ((AbstractNode)pleaseWaitNode).setIconBase("org/openide/resources/wait"); //NOI18N 300a198,199 > return pleaseWaitNode.cloneNode(); > } 302,305c201,208 < /** Another object has been recognized. < * @param obj the object recognized < * @param arr array where the implementation should add the < * object --- > /** > * Instances of this class are posted to the request processor refRP > * (FolderChildren_refresher). We do this because we do not want > * to call setKeys synchronously. > */ > private final class ChildrenRefreshRunnable implements Runnable { > /** calls setKeys with the folder children > * or with empty collection if active is false 307,329c210,213 < public void process(DataObject obj, java.util.List arr) { < if (!filter.acceptDataObject (obj)) { < return; < } < < // first accepted object is notified to the waiting thread in < boolean first = arr.isEmpty (); < arr.add (obj); < < // see comment for the variable dataObjects < dataObjects.add (obj); < < if (!processingUpdate) { < // if we should not notify during processing update < // skip the rest < return; < } < < if (first) { < synchronized (this) { < this.notify (); < refreshKeys (arr); < } --- > public void run() { > if (! active) { > setKeys (java.util.Collections.EMPTY_SET); > childrenComputed = false; 332,342c216,219 < < if (System.currentTimeMillis () > time) { < if (!arr.isEmpty ()) { < // add the nodes < synchronized (this) { < refreshKeys (arr); < } < delay *= 2; < } < < time = System.currentTimeMillis () + delay; --- > DataObject []ch = folder.getChildren(); > Object []keys = new Object[ch.length]; > for (int i = 0; i < keys.length; i++) { > keys[i] = new Pair(ch[i].getPrimaryFile()); 343a221,222 > childrenComputed = true; > setKeys(Arrays.asList(keys)); 345,395c224,230 < < /** All objects has been recognized. < * @param arr list of DataObjects < */ < public void finished(java.util.List arr) { < synchronized (this) { < this.notify (); < this.processingFinished = true; < // change the order because initialize method has already finished < refreshKeys (arr); < } < } < < /** Getter for first map. < */ < public synchronized void waitFirst () { < try { < this.wait (50); < } catch (InterruptedException e) { < throw new IllegalStateException(); < } < } < < /** Called when a task finishes running. < * @param task the finished task < */ < public void taskFinished(Task task) { < initTask = Task.EMPTY; < } < < /** Refreshes the children. < * @param ch collection of children data objects < */ < private void refreshKeys (List ch) { < if (err != null) err.log("refreshKeys: " + ch); < ListIterator it = ch.listIterator (); < LinkedList l = new LinkedList (); < < while (it.hasNext ()) { < DataObject obj = (DataObject)it.next (); < l.add (createKey (obj)); < } < < // remember the keys < refKeys = l; < // request method run to be called from request processor thread < refTask.schedule (0); < } < < /** Refreshes the keys in safe thread (that nobody should wait for) < */ --- > } > > /** > * Instances of this class are posted to the request processor refRP > * (FolderChildren_refresher). > */ > private final class PleaseWaitNodeSetter implements Runnable { 397,419c232,234 < List toSet; < synchronized (this) { // 1 < toSet = refKeys; < refKeys = null; < } < < if (toSet != null) { < // toSet can be null if < // 1. a task blocks on synchronized(this) // 1 < // 2. refreshKeys calls refKey = + refTask.schedule < // 3. a task uses the new value, but is already planned => < // 4. it will be run next time with toSet == null < if (FolderChildren.this.err != null) FolderChildren.this.err.log("setKeys: " + toSet); < FolderChildren.this.setKeys(toSet); < } < synchronized (this) { < if (processingFinished) { < if (FolderChildren.this.refreshTask == refTask) { < if (err != null) err.log("fix for #30153 applied "); < FolderChildren.this.refreshTask = Task.EMPTY; < // refTask.cancel(); < } < } --- > if (active && ! childrenComputed) { > // set the please wait node > setKeys (java.util.Collections.singleton(PLEASE_WAIT_KEY)); 423c238 < --- > 425c240,244 < */ --- > * It serves as a key for the given data object. > * It is here to create something different then data object, > * because the data object should be finalized when not needed and > * that is why it should not be used as a key. > */