KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > modules > turbo > Turbo


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19 package org.netbeans.modules.turbo;
20
21 import org.openide.util.Lookup;
22 import org.openide.util.RequestProcessor;
23 import org.openide.ErrorManager;
24
25 import java.util.*;
26 import java.lang.ref.WeakReference JavaDoc;
27
28 /**
29  * Turbo is general purpose entries/attributes dictionary with pluggable
30  * layer enabling high scalability disk swaping implementations.
31  * It allows to store several name identified values
32  * for an entity identified by a key.
33  *
34  * <p>All methods take a <b><code>key</code></b> parameter. It
35  * identifies entity to which attribute values are associated.
36  * It must have properly implemented <code>equals()</code>,
37  * <code>hashcode()</code> and <code>toString()</code> (returning
38  * unique string) methods. Key lifetime must be well
39  * understood to set cache strategy effeciently:
40  * <ul>
41  * <li>MAXIMUM: The implementation monitors key instance lifetime and does
42  * not release data from memory until max. size limit reached or
43  * key instance garbage collected whichever comes sooner. For
44  * unbound caches the key must not be hard referenced from stored
45  * value.
46  *
47  * <li>MINIMUM: For value lookups key's value based equalence is used.
48  * Key's instance lifetime is monitored by instance based equalence.
49  * Reasonable min limit must be set because there can be several
50  * value equalent key instances but only one used key instance
51  * actually stored in cache and lifetime monitored.
52  * </ul>
53  *
54  * <p>Entry <b>name</b> fully indentifies contract between
55  * consumers and providers. Contracts are described elsewhere.
56  *
57  * <p>The dictionary does not support storing <code>null</code>
58  * <b>values</b>. Writing <code>null</code> value means that given
59  * entry should be invalidated and removed (it actualy depends
60  * on externally negotiated contract identified by name). Getting
61  * <code>null</code> as requets result means that given value
62  * is not (yet) known or does not exist at all.
63  *
64  * @author Petr Kuzel
65  */

66 public final class Turbo {
67
68     /** Default providers registry. */
69     private static Lookup.Result providers;
70
71     /** Custom providers 'registry'. */
72     private final CustomProviders customProviders;
73
74     private static WeakReference JavaDoc defaultInstance;
75
76     private List listeners = new ArrayList(100);
77
78     /** memory layer */
79     private final Memory memory;
80
81     private final Statistics statistics;
82
83     private static Environment env;
84
85     /**
86      * Returns default instance. It's size is driven by
87      * keys lifetime. It shrinks on keys become unreferenced
88      * and grows without bounds on inserting keys.
89      */

90     public static synchronized Turbo getDefault() {
91         Turbo turbo = null;
92         if (defaultInstance != null) {
93             turbo = (Turbo) defaultInstance.get();
94         }
95
96         if (turbo == null) {
97             turbo = new Turbo(null, 47, -1);
98             defaultInstance = new WeakReference JavaDoc(turbo);
99         }
100
101         return turbo;
102     }
103
104     /**
105      * Creates new instance with customized providers layer.
106      * @param providers never <code>null</null>
107      * @param min minimum number of entries held by the cache
108      * @param max maximum size or <code>-1</code> for unbound size
109      * (defined just by key instance lifetime)
110      */

111     public static synchronized Turbo createCustom(CustomProviders providers, int min, int max) {
112         return new Turbo(providers, min, max);
113     }
114
115     private Turbo(CustomProviders customProviders, int min, int max) {
116         statistics = Statistics.createInstance();
117         memory = new Memory(statistics, min, max);
118         this.customProviders = customProviders;
119         if (customProviders == null && providers == null) {
120             Lookup.Template t = new Lookup.Template(TurboProvider.class);
121             synchronized(Turbo.class) {
122                 if (env == null) env = new Environment();
123             }
124             providers = env.getLookup().lookup(t);
125         }
126     }
127
128     /** Tests can set different environment. Must be called before {@link #getDefault}. */
129     static synchronized void initEnvironment(Environment environment) {
130         assert env == null;
131         env = environment;
132         providers = null;
133     }
134
135     /** Logs cache statistics data. */
136     protected void finalize() throws Throwable JavaDoc {
137         super.finalize();
138         statistics.shutdown();
139     }
140
141     /**
142      * Reads given attribute for given entity key.
143      * @param key a entity key, never <code>null</code>
144      * @param name identifies requested entry, never <code>null</code>
145      * @return entry value or <code>null</code> if it does not exist or unknown.
146      */

147     public Object JavaDoc readEntry(Object JavaDoc key, String JavaDoc name) {
148
149         statistics.attributeRequest();
150
151         // check memory cache
152

153         if (memory.existsEntry(key, name)) {
154             Object JavaDoc value = memory.get(key, name);
155             statistics.memoryHit();
156             return value;
157         }
158
159         // iterate over providers
160
List speculative = new ArrayList(57);
161         Object JavaDoc value = loadEntry(key, name, speculative);
162         memory.put(key, name, value != null ? value : Memory.NULL);
163         // XXX should fire here? yes if name avalability changes should be
164
// dispatched to clients that have not called prepare otherwise NO.
165

166         // refire speculative results, can be optinized later on to fire
167
// them lazilly on prepareAttribute or isPrepared calls
168
Iterator it = speculative.iterator();
169         while (it.hasNext()) {
170             Object JavaDoc[] next = (Object JavaDoc[]) it.next();
171             Object JavaDoc sKey = next[0];
172             String JavaDoc sName = (String JavaDoc) next[1];
173             Object JavaDoc sValue = next[2];
174             assert sKey != null;
175             assert sName != null;
176             fireEntryChange(sKey, sName, sValue);
177         }
178
179         return value;
180     }
181
182     private Iterator providers() {
183         if (customProviders == null) {
184             Collection plugins = providers.allInstances();
185             List all = new ArrayList(plugins.size() +1);
186             all.addAll(plugins);
187             all.add(DefaultTurboProvider.getDefault());
188             return all.iterator();
189         } else {
190             return customProviders.providers();
191         }
192     }
193
194     /**
195      * Iterate over providers asking for attribute values
196      */

197     private Object JavaDoc loadEntry(Object JavaDoc key, String JavaDoc name, List speculative) {
198
199         TurboProvider provider;
200         Iterator it = providers();
201         while (it.hasNext()) {
202             provider = (TurboProvider) it.next();
203             try {
204                 if (provider.recognizesAttribute(name) && provider.recognizesEntity(key)) {
205                     TurboProvider.MemoryCache cache = TurboProvider.MemoryCache.createDefault(memory, speculative);
206                     Object JavaDoc value = provider.readEntry(key, name, cache);
207                     statistics.providerHit();
208                     return value;
209                 }
210             } catch (ThreadDeath JavaDoc td) {
211                 throw td;
212             } catch (Throwable JavaDoc t) {
213                 // error in provider
214
ErrorManager.getDefault().annotate(t, "Error in provider " + provider + ", skipping... "); // NOI18N
215
ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, t); // XXX in Junit mode writes to stdout ommiting annotation!!!
216
}
217         }
218
219         return null;
220     }
221
222     /**
223      * Writes given attribute, value pair and notifies all listeners.
224      * Written value is stored both into memory and providers layer.
225      * The call speed depends on actually used provider. However do not
226      * rely on synchronous provider call. In future it may return and
227      * fire immediately on writing into memory layer, populating providers
228      * asychronously on background.
229      *
230      * <p>A client calling this method is reponsible for passing
231      * valid value that is accepted by attribute serving providers.
232      *
233      * @param key identifies target, never <code>null</code>
234      * @param name identifies attribute, never <code>null</code>
235      * @param value actual attribute value to be stored, <code>null</code> behaviour
236      * is defined specificaly for each name. It always invalidates memory entry
237      * and it either invalidates or removes value from providers layer
238      * (mening: value unknown versus value is known to not exist).
239      *
240      * <p>Client should consider a size of stored value. It must be corelated
241      * with Turbo memory layer limits to avoid running ouf of memory.
242      *
243      * @return <ul>
244      * <li><code>false</code> on write failure caused by a provider denying the value.
245      * It means attribute contract violation and must be handled e.g.:
246      * <p><code>
247      * boolean success = faq.writeAttribute(fo, name, value);<br>
248      * assert success : "Unexpected name[" + name + "] value[" + value + "] denial for " + key + "!";
249      * </code>
250
251      * <li><code>true</code> in all other cases includins I/O error.
252      * After all it's just best efford cache. All values can be recomputed.
253      * </ul>
254      */

255     public boolean writeEntry(Object JavaDoc key, String JavaDoc name, Object JavaDoc value) {
256
257         if (value != null) {
258             Object JavaDoc oldValue = memory.get(key, name);
259             if (oldValue != null && oldValue.equals(value)) return true; // XXX assuming provider has the same value, assert it!
260
}
261
262         int result = storeEntry(key, name, value);
263         if (result >= 0) {
264             // no one denied keep at least in memory cache
265
memory.put(key, name, value);
266             fireEntryChange(key, name, value);
267             return true;
268         } else {
269             return false;
270         }
271     }
272
273     /**
274      * Stores directly to providers.
275      * @return 0 success, -1 contract failure, 1 other failure
276      */

277     int storeEntry(Object JavaDoc key, String JavaDoc name, Object JavaDoc value) {
278         TurboProvider provider;
279         Iterator it = providers();
280         while (it.hasNext()) {
281             provider = (TurboProvider) it.next();
282             try {
283                 if (provider.recognizesAttribute(name) && provider.recognizesEntity(key)) {
284                     if (provider.writeEntry(key, name, value)) {
285                         return 0;
286                     } else {
287                         // for debugging purposes log which provider rejected defined name contract
288
IllegalArgumentException JavaDoc ex = new IllegalArgumentException JavaDoc("Attribute[" + name + "] value rejected by " + provider);
289                         ErrorManager.getDefault().notify(ErrorManager.WARNING, ex);
290                         return -1;
291                     }
292                 }
293             } catch (ThreadDeath JavaDoc td) {
294                 throw td;
295             } catch (Throwable JavaDoc t) {
296                 // error in provider
297
ErrorManager.getDefault().annotate(t, "Error in provider " + provider + ", skipping... "); // NOI18N
298
ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, t);
299             }
300         }
301         return 1;
302     }
303
304     /**
305      * Checks value instant availability and possibly schedules its background
306      * loading. It's designed to be called from UI tread.
307      *
308      * @return <ul>
309      * <li><code>false</code> if not ready and providers must be consulted. It
310      * asynchronously fires event possibly with <code>null</code> value
311      * if given attribute does not exist.
312      *
313      * <li>
314      * If <code>true</code> it's
315      * ready and stays ready at least until next {@link #prepareEntry},
316      * {@link #isPrepared}, {@link #writeEntry} <code>null</code> call
317      * or {@link #readEntry} from the same thread.
318      * </ul>
319      */

320     public boolean prepareEntry(Object JavaDoc key, String JavaDoc name) {
321
322         statistics.attributeRequest();
323
324         // check memory cache
325

326         if (memory.existsEntry(key, name)) {
327             statistics.memoryHit();
328             return true;
329         }
330
331         // start asynchronous providers queriing
332
scheduleLoad(key, name);
333         return false;
334     }
335
336     /**
337      * Checks name instant availability. Note that actual
338      * value may be still <code>null</code>, in case
339      * that it's known that value does not exist.
340      *
341      * @return <ul>
342      * <li><code>false</code> if not present in memory for instant access.
343      *
344      * <li><code>true</code> when it's
345      * ready and stays ready at least until next {@link #prepareEntry},
346      * {@link #isPrepared}, {@link #writeEntry} <code>null</code> call
347      * or {@link #readEntry} from the same thread.
348      * </ul>
349      */

350     public boolean isPrepared(Object JavaDoc key, String JavaDoc name) {
351         return memory.existsEntry(key, name);
352     }
353
354     /**
355      * Gets key instance that it actually used in memory layer.
356      * Client should keep reference to it if it wants to use
357      * key lifetime monitoring cache size strategy.
358      *
359      * @param key key never <code>null</code>
360      * @return key instance that is value-equalent or <code>null</code>
361      * if monitored instance does not exist.
362      */

363     public Object JavaDoc getMonitoredKey(Object JavaDoc key) {
364         return memory.getMonitoredKey(key);
365     }
366
367     public void addTurboListener(TurboListener l) {
368         synchronized(listeners) {
369             List copy = new ArrayList(listeners);
370             copy.add(l);
371             listeners = copy;
372         }
373     }
374
375     public void removeTurboListener(TurboListener l) {
376         synchronized(listeners) {
377             List copy = new ArrayList(listeners);
378             copy.remove(l);
379             listeners = copy;
380         }
381
382     }
383
384     protected void fireEntryChange(Object JavaDoc key, String JavaDoc name, Object JavaDoc value) {
385         Iterator it = listeners.iterator();
386         while (it.hasNext()) {
387             TurboListener next = (TurboListener) it.next();
388             next.entryChanged(key, name, value);
389         }
390     }
391
392     /** For debugging purposes only. */
393     public String JavaDoc toString() {
394         StringBuffer JavaDoc sb = new StringBuffer JavaDoc("Turbo delegating to:"); // NOI18N
395
Iterator it = providers();
396         while (it.hasNext()) {
397             TurboProvider provider = (TurboProvider) it.next();
398             sb.append(" [" + provider + "]"); // NOI18N
399
}
400         return sb.toString();
401     }
402
403     /** Defines binding to external world. Used by tests. */
404     static class Environment {
405         /** Lookup that serves providers. */
406         public Lookup getLookup() {
407             return Lookup.getDefault();
408         }
409     }
410
411     // Background loading ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
412

413
414     /** Holds keys that were requested for background status retrieval. */
415     private final Set prepareRequests = Collections.synchronizedSet(new LinkedHashSet(27));
416
417     private static PreparationTask preparationTask;
418
419     /** Tries to locate meta on disk on failure it forward to repository */
420     private void scheduleLoad(Object JavaDoc key, String JavaDoc name) {
421         synchronized(prepareRequests) {
422             if (preparationTask == null) {
423                 preparationTask = new PreparationTask(prepareRequests);
424                 RequestProcessor.getDefault().post(preparationTask);
425                 statistics.backgroundThread();
426             }
427             preparationTask.notifyNewRequest(new Request(key, name));
428         }
429     }
430
431     /** Requests queue entry featuring value based identity. */
432     private final static class Request {
433         private final Object JavaDoc key;
434         private final String JavaDoc name;
435
436         public Request(Object JavaDoc key, String JavaDoc name) {
437             this.name = name;
438             this.key = key;
439         }
440
441         public boolean equals(Object JavaDoc o) {
442             if (this == o) return true;
443             if (!(o instanceof Request)) return false;
444
445             final Request request = (Request) o;
446
447             if (name != null ? !name.equals(request.name) : request.name != null) return false;
448             if (key != null ? !key.equals(request.key) : request.key != null) return false;
449
450             return true;
451         }
452
453         public int hashCode() {
454             int result;
455             result = (key != null ? key.hashCode() : 0);
456             result = 29 * result + (name != null ? name.hashCode() : 0);
457             return result;
458         }
459
460         public String JavaDoc toString() {
461             return "Request[key=" + key + ", attr=" + name + "]";
462         }
463     }
464
465     /**
466      * On background fetches data from providers layer.
467      */

468     private final class PreparationTask implements Runnable JavaDoc {
469
470         private final Set requests;
471
472         private static final int INACTIVITY_TIMEOUT = 123 * 1000; // 123 sec
473

474         public PreparationTask(Set requests) {
475             this.requests = requests;
476         }
477
478         public void run() {
479             try {
480                 Thread.currentThread().setName("Turbo Async Fetcher"); // NOI18N
481
while (waitForRequests()) {
482                     Request request;
483                     synchronized (requests) {
484                         request = (Request) requests.iterator().next();
485                         requests.remove(request);
486                     }
487                     Object JavaDoc key = request.key;
488                     String JavaDoc name = request.name;
489                     Object JavaDoc value;
490                     boolean fire;
491                     if (memory.existsEntry(key, name)) {
492
493                         synchronized(Memory.class) {
494                             fire = memory.existsEntry(key, name) == false;
495                             value = memory.get(key, name);
496                         }
497                         if (fire) {
498                             statistics.providerHit(); // from our perpective we achieved hit
499
}
500                     } else {
501                         value = loadEntry(key, name, null);
502                         // possible thread switch, so atomic fire test must be used
503
synchronized(Memory.class) {
504                             fire = memory.existsEntry(key, name) == false;
505                             Object JavaDoc oldValue = memory.get(key, name);
506                             memory.put(key, name, value != null ? value : Memory.NULL);
507                             fire |= (oldValue != null && !oldValue.equals(value))
508                                  || (oldValue == null && value != null);
509                         }
510                     }
511
512                     // some one was faster, probably previous disk read that silently fetched whole directory
513
// our contract was to fire event once loading, stick to it. Note that get()
514
// silently populates stable memory area
515
// if (fire) { ALWAYS because of above loadAttribute(key, name, null);
516
fireEntryChange(key, name, value); // notify as soon as available in memory
517
// }
518

519                 }
520             } catch (InterruptedException JavaDoc ex) {
521                 synchronized(requests) {
522                     // forget about recent requests
523
requests.clear();
524                 }
525             } finally {
526                 synchronized(requests) {
527                     preparationTask = null;
528                 }
529             }
530         }
531
532         /**
533          * Wait for requests, it no request comes until timeout
534          * it ommits suicide. It's respawned on next request however.
535          */

536         private boolean waitForRequests() throws InterruptedException JavaDoc {
537             synchronized(requests) {
538                 if (requests.size() == 0) {
539                     requests.wait(INACTIVITY_TIMEOUT);
540                 }
541                 return requests.size() > 0;
542             }
543         }
544
545         public void notifyNewRequest(Request request) {
546             synchronized(requests) {
547                 if (requests.add(request)) {
548                     statistics.queueSize(requests.size());
549                     requests.notify();
550                 } else {
551                     statistics.duplicate();
552                     statistics.providerHit();
553                 }
554             }
555         }
556
557         public String JavaDoc toString() {
558             return "Turbo.PreparationTask queue=[" + requests +"]"; // NOI18N
559
}
560     }
561
562
563 }
564
Popular Tags