Line 25
Link Here
|
|
|
25 |
import org.openide.util.NbBundle; |
Line 27
Link Here
|
|
|
28 |
import org.openide.nodes.AbstractNode; |
Lines 39-42
Link Here
|
39 |
/** initialization of children task */ |
|
|
40 |
private Task initTask; |
41 |
/** last task that refreshes the children nodes */ |
42 |
private Task refreshTask; |
Line 47
Link Here
|
|
|
45 |
|
46 |
/** Private req processor for the refresh tasks */ |
47 |
private static RequestProcessor refRP = |
48 |
new RequestProcessor("FolderChildren_refresher"); |
49 |
|
50 |
/** The key used to denote the please wait node. |
51 |
* It is also used as a lock for the lazy initializaiton |
52 |
* of the pleaseWaitNode. |
53 |
*/ |
54 |
private static final Object PLEASE_WAIT_KEY = new Object(); |
55 |
|
56 |
/** wait node - please use method getPleaseWaitNode() |
57 |
* to get a lazy initialized instance */ |
58 |
private static Node pleaseWaitNode; |
Line 96
Link Here
|
96 |
initialize (true, false); |
108 |
refRP.post(new ChildrenRefreshRunnable(false)); |
97 |
-- |
|
|
Lines 99-118
Link Here
|
99 |
/** Creates a key for given data object. |
111 |
/** Lazy initialization of the please wait node */ |
100 |
* This method is here to create something different then data object, |
112 |
private static Node getPleaseWaitNode() { |
101 |
* because the data object should be finalized when not needed and |
113 |
boolean created = false; |
102 |
* that is why it should not be used as a key. |
114 |
synchronized (PLEASE_WAIT_KEY) { |
103 |
* |
115 |
if (pleaseWaitNode == null) { |
104 |
* @param obj data object |
116 |
pleaseWaitNode = new AbstractNode(Children.LEAF); |
105 |
* @return key representing the data object. |
117 |
created = true; |
106 |
*/ |
118 |
} |
107 |
static Object createKey (DataObject obj) { |
119 |
} |
108 |
return new Pair (obj.getPrimaryFile ()); |
120 |
if (created) { |
109 |
} |
121 |
pleaseWaitNode.setName("wait"); // NOI18N |
110 |
|
122 |
pleaseWaitNode.setDisplayName( |
111 |
/** This method takes the key created by createKey and converts it |
123 |
NbBundle.getBundle(FolderChildren.class).getString("LBL_Wait") |
112 |
* into primary file. |
124 |
); |
113 |
* |
125 |
((AbstractNode)pleaseWaitNode).setIconBase("org/openide/resources/wait"); //NOI18N |
114 |
* @param key the key |
126 |
} |
115 |
* @return primary file of the key |
127 |
return pleaseWaitNode; |
116 |
*/ |
|
|
117 |
private static FileObject getFile (Object key) { |
118 |
return ((Pair)key).primaryFile; |
119 |
-- |
Line 120
Link Here
|
120 |
|
129 |
|
121 |
-- |
|
|
Line 125
Link Here
|
|
|
134 |
if (key == PLEASE_WAIT_KEY) { |
135 |
return new Node[] { getPleaseWaitNode() }; |
136 |
} |
Line 126
Link Here
|
126 |
FileObject fo = getFile (key); |
138 |
FileObject fo = ((Pair)key).primaryFile; |
127 |
-- |
|
|
Lines 141-183
Link Here
|
141 |
|
153 |
|
142 |
/** Improves the searching capability to wait till all children |
154 |
public Node[] getNodes(boolean optimalResult) { |
143 |
* are found and then searching thru them. |
155 |
if (optimalResult) { |
144 |
*/ |
156 |
Task t = refRP.post(new ChildrenRefreshRunnable(false)); |
145 |
public Node findChild (final String name) { |
157 |
t.waitFinished(); |
146 |
// start the initialization |
158 |
return getNodes(); |
147 |
Node[] forget = getNodes (); // DO NOT DELETE |
|
|
148 |
|
149 |
// waits till the list of children is created |
150 |
|
151 |
// JST: the folowing code is replacement for |
152 |
// initialize (false, false).waitFinished () |
153 |
// |
154 |
// the orgiginal code caused deadlocks when called with readAccess, |
155 |
// because the refreshChildren (which we wait for to be finished) |
156 |
// method calls setKeys and they require write request |
157 |
// |
158 |
// this code could probably be replaced by |
159 |
// Children.MUTEX.isReadAccess, if such call would be added to Mutex |
160 |
class Jst implements Runnable { |
161 |
public boolean checkReadOrWrite; |
162 |
public boolean inReadAccess = true; |
163 |
public boolean inWriteAccess = true; |
164 |
public void run () { |
165 |
if (checkReadOrWrite) { |
166 |
inReadAccess = false; |
167 |
} else { |
168 |
inWriteAccess = false; |
169 |
} |
170 |
} |
171 |
} |
172 |
Jst t = new Jst (); |
173 |
// the code will run either immediatally or after we leave readAccess |
174 |
// section |
175 |
Children.MUTEX.postWriteRequest(t); |
176 |
t.checkReadOrWrite = true; |
177 |
Children.MUTEX.postReadRequest(t); |
178 |
|
179 |
if (!t.inReadAccess && !t.inWriteAccess) { |
180 |
// if we are not in readAccess we can safely wait till |
181 |
// refreshChildren finishes and sets new keys for the |
182 |
initialize (name == null, false).waitFinished (); |
183 |
refreshTask.waitFinished (); |
184 |
-- |
Lines 185-188
Link Here
|
185 |
|
160 |
return super.getNodes(optimalResult); |
186 |
// do the regular child lookup |
|
|
187 |
Node node = super.findChild (name); |
188 |
return node; |
189 |
-- |
Line 190
Link Here
|
190 |
|
162 |
|
191 |
-- |
|
|
Line 194
Link Here
|
194 |
initialize (true, true); |
166 |
// set the please wait node |
195 |
-- |
167 |
setKeys (java.util.Collections.singleton(PLEASE_WAIT_KEY)); |
Line 197
Link Here
|
|
|
170 |
// start the refresh task |
171 |
refreshChildren(); |
Lines 204-234
Link Here
|
204 |
setKeys (java.util.Collections.EMPTY_SET); |
|
|
205 |
} |
206 |
|
207 |
/** Ensures that the content of children will be filled sometime. |
208 |
* @param force true if the content should be filled immediatelly |
209 |
*/ |
210 |
private Task initialize (boolean force, boolean waitFirst) { |
211 |
|
212 |
Task t = initTask; |
213 |
|
214 |
if (t != null && t.isFinished()) { |
215 |
// if the original refresh task is finished we whould check if there are |
216 |
// some new changes in progress and wait for them to finish |
217 |
FolderList l = FolderList.find (folder.getPrimaryFile(), true); |
218 |
l.waitProcessingFinished (); |
219 |
|
220 |
t = initTask; |
221 |
} |
222 |
|
223 |
if (t != null && !force) { |
224 |
return t; |
225 |
} |
226 |
|
227 |
// if (t != null) { |
228 |
// t.waitFinished (); |
229 |
// refreshTask.waitFinished (); |
230 |
// return t; |
231 |
// } |
232 |
|
233 |
final Addition add = new Addition (waitFirst); |
234 |
refreshTask = add.refTask; |
Lines 236-243
Link Here
|
236 |
initTask = t = folder.computeChildrenList (add); |
180 |
// the parameter true means to set the children to empty |
237 |
t.addTaskListener (add); |
181 |
// we don't call the setKeys directly here because |
238 |
|
182 |
// there can be a task spawned by refreshChildren - so |
239 |
if (waitFirst) { |
183 |
// we want to clear the children after that task is finished |
240 |
add.waitFirst (); |
184 |
refRP.post(new ChildrenRefreshRunnable(true)); |
241 |
} |
|
|
242 |
|
243 |
return t; |
244 |
-- |
Lines 251-365
Link Here
|
251 |
/** time delay between two inserts of nodes */ |
192 |
/** |
252 |
private static final int TIME_DELAY = 1024; |
193 |
* Instances of this class are posted to the request processor refRP |
253 |
|
194 |
* (FolderChildren_refresher). We do this because we do not want |
254 |
/** Private req processor for Addition's refresh tasks */ |
195 |
* to call setKeys synchronously. |
255 |
private static RequestProcessor refRP = |
196 |
*/ |
256 |
new RequestProcessor("FolderChildren_refresher"); |
197 |
private final class ChildrenRefreshRunnable implements Runnable { |
257 |
|
198 |
/** if set to true we just clear the children */ |
258 |
/** Support for incremental adding of new nodes. |
199 |
private boolean clear; |
259 |
* |
200 |
|
260 |
* <P> |
201 |
/** Remember the parameter. */ |
261 |
* There is a deadlock warning: |
202 |
ChildrenRefreshRunnable(boolean clear) { |
262 |
* <OL> |
203 |
this.clear = clear; |
263 |
* <LI>A thread waiting in the waitFirst method can have MUTEX.readAccess |
|
|
264 |
* <LI>Thread running run () needs access to MUTEX.writeAccess (because of setKeys) |
265 |
* <LI>Be sure that the thread leaves waitFirst before writeAccess is needed |
266 |
* </OL> |
267 |
*/ |
268 |
private class Addition |
269 |
implements TaskListener, FolderListListener, Runnable { |
270 |
static final long serialVersionUID =-4194617547214845940L; |
271 |
|
272 |
/** last time of addition */ |
273 |
private long time = System.currentTimeMillis () + TIME_DELAY; |
274 |
/** delay */ |
275 |
private int delay = TIME_DELAY; |
276 |
|
277 |
/** update the nodes during processing or only at the end */ |
278 |
private boolean processingUpdate; |
279 |
|
280 |
/** a keys to be later refreshed */ |
281 |
private List refKeys; |
282 |
|
283 |
// fix of #27025. We have to keep reference to all data objects |
284 |
// for the whole lifetime of this object otherwise it can happen |
285 |
// that a data object is GCed between calling refreshKeys and |
286 |
// createNodes |
287 |
private List dataObjects = new ArrayList(); |
288 |
|
289 |
/** processing of DataFolder.computeChildrenList finished */ |
290 |
private boolean processingFinished; |
291 |
|
292 |
/** a task that is used to request refreshing of keys */ |
293 |
private RequestProcessor.Task refTask = refRP.create(this); // NOI18N |
294 |
|
295 |
/** @param processingUpdate update the nodes during |
296 |
* processing or only at the end |
297 |
*/ |
298 |
public Addition (boolean processingUpdate) { |
299 |
this.processingUpdate = processingUpdate; |
300 |
} |
301 |
|
302 |
/** Another object has been recognized. |
303 |
* @param obj the object recognized |
304 |
* @param arr array where the implementation should add the |
305 |
* object |
306 |
*/ |
307 |
public void process(DataObject obj, java.util.List arr) { |
308 |
if (!filter.acceptDataObject (obj)) { |
309 |
return; |
310 |
} |
311 |
|
312 |
// first accepted object is notified to the waiting thread in |
313 |
boolean first = arr.isEmpty (); |
314 |
arr.add (obj); |
315 |
|
316 |
// see comment for the variable dataObjects |
317 |
dataObjects.add (obj); |
318 |
|
319 |
if (!processingUpdate) { |
320 |
// if we should not notify during processing update |
321 |
// skip the rest |
322 |
return; |
323 |
} |
324 |
|
325 |
if (first) { |
326 |
synchronized (this) { |
327 |
this.notify (); |
328 |
refreshKeys (arr); |
329 |
} |
330 |
return; |
331 |
} |
332 |
|
333 |
if (System.currentTimeMillis () > time) { |
334 |
if (!arr.isEmpty ()) { |
335 |
// add the nodes |
336 |
synchronized (this) { |
337 |
refreshKeys (arr); |
338 |
} |
339 |
delay *= 2; |
340 |
} |
341 |
|
342 |
time = System.currentTimeMillis () + delay; |
343 |
} |
344 |
} |
345 |
|
346 |
/** All objects has been recognized. |
347 |
* @param arr list of DataObjects |
348 |
*/ |
349 |
public void finished(java.util.List arr) { |
350 |
synchronized (this) { |
351 |
this.notify (); |
352 |
this.processingFinished = true; |
353 |
// change the order because initialize method has already finished |
354 |
refreshKeys (arr); |
355 |
} |
356 |
} |
357 |
|
358 |
/** Getter for first map. |
359 |
*/ |
360 |
public synchronized void waitFirst () { |
361 |
try { |
362 |
this.wait (50); |
363 |
} catch (InterruptedException e) { |
364 |
throw new IllegalStateException(); |
365 |
} |
366 |
-- |
Lines 367-369
Link Here
|
367 |
|
205 |
/** calls setKeys with the folder children |
368 |
/** Called when a task finishes running. |
206 |
* or with empty collection if clear set to true. |
369 |
* @param task the finished task |
|
|
370 |
-- |
Lines 371-395
Link Here
|
371 |
public void taskFinished(Task task) { |
|
|
372 |
initTask = Task.EMPTY; |
373 |
} |
374 |
|
375 |
/** Refreshes the children. |
376 |
* @param ch collection of children data objects |
377 |
*/ |
378 |
private void refreshKeys (List ch) { |
379 |
if (err != null) err.log("refreshKeys: " + ch); |
380 |
ListIterator it = ch.listIterator (); |
381 |
LinkedList l = new LinkedList (); |
382 |
|
383 |
while (it.hasNext ()) { |
384 |
DataObject obj = (DataObject)it.next (); |
385 |
l.add (createKey (obj)); |
386 |
} |
387 |
|
388 |
// remember the keys |
389 |
refKeys = l; |
390 |
// request method run to be called from request processor thread |
391 |
refTask.schedule (0); |
392 |
} |
393 |
|
394 |
/** Refreshes the keys in safe thread (that nobody should wait for) |
395 |
*/ |
Lines 397-410
Link Here
|
397 |
List toSet; |
209 |
if (clear) { |
398 |
synchronized (this) { // 1 |
210 |
setKeys (java.util.Collections.EMPTY_SET); |
399 |
toSet = refKeys; |
211 |
return; |
400 |
refKeys = null; |
|
|
401 |
} |
402 |
|
403 |
if (toSet != null) { |
404 |
// toSet can be null if |
405 |
// 1. a task blocks on synchronized(this) // 1 |
406 |
// 2. refreshKeys calls refKey = <newvalue> + refTask.schedule |
407 |
// 3. a task uses the new value, but is already planned => |
408 |
// 4. it will be run next time with toSet == null |
409 |
if (FolderChildren.this.err != null) FolderChildren.this.err.log("setKeys: " + toSet); |
410 |
FolderChildren.this.setKeys(toSet); |
411 |
-- |
Lines 412-419
Link Here
|
412 |
synchronized (this) { |
213 |
DataObject []ch = folder.getChildren(); |
413 |
if (processingFinished) { |
214 |
Object []keys = new Object[ch.length]; |
414 |
if (FolderChildren.this.refreshTask == refTask) { |
215 |
for (int i = 0; i < keys.length; i++) { |
415 |
if (err != null) err.log("fix for #30153 applied "); |
216 |
keys[i] = new Pair(ch[i].getPrimaryFile()); |
416 |
FolderChildren.this.refreshTask = Task.EMPTY; |
|
|
417 |
// refTask.cancel(); |
418 |
} |
419 |
} |
420 |
-- |
Line 421
Link Here
|
|
|
218 |
setKeys(Arrays.asList(keys)); |
Line 423
Link Here
|
423 |
|
221 |
|
424 |
-- |
|
|
Line 425
Link Here
|
425 |
*/ |
223 |
* It serves as a key for the given data object. |
426 |
-- |
224 |
* It is here to create something different then data object, |
|
|
225 |
* because the data object should be finalized when not needed and |
226 |
* that is why it should not be used as a key. |
227 |
*/ |