001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041: package org.netbeans.modules.turbo;
042:
043: import org.openide.util.Lookup;
044: import org.openide.util.RequestProcessor;
045: import org.openide.ErrorManager;
046:
047: import java.util.*;
048: import java.lang.ref.WeakReference;
049:
050: /**
051: * Turbo is general purpose entries/attributes dictionary with pluggable
052: * layer enabling high scalability disk swaping implementations.
053: * It allows to store several name identified values
054: * for an entity identified by a key.
055: *
056: * <p>All methods take a <b><code>key</code></b> parameter. It
057: * identifies entity to which attribute values are associated.
058: * It must have properly implemented <code>equals()</code>,
059: * <code>hashcode()</code> and <code>toString()</code> (returning
060: * unique string) methods. Key lifetime must be well
061: * understood to set cache strategy effeciently:
062: * <ul>
063: * <li>MAXIMUM: The implementation monitors key instance lifetime and does
064: * not release data from memory until max. size limit reached or
065: * key instance garbage collected whichever comes sooner. For
066: * unbound caches the key must not be hard referenced from stored
067: * value.
068: *
069: * <li>MINIMUM: For value lookups key's value based equalence is used.
070: * Key's instance lifetime is monitored by instance based equalence.
071: * Reasonable min limit must be set because there can be several
072: * value equalent key instances but only one used key instance
073: * actually stored in cache and lifetime monitored.
074: * </ul>
075: *
076: * <p>Entry <b>name</b> fully indentifies contract between
077: * consumers and providers. Contracts are described elsewhere.
078: *
079: * <p>The dictionary does not support storing <code>null</code>
080: * <b>values</b>. Writing <code>null</code> value means that given
081: * entry should be invalidated and removed (it actualy depends
082: * on externally negotiated contract identified by name). Getting
083: * <code>null</code> as requets result means that given value
084: * is not (yet) known or does not exist at all.
085: *
086: * @author Petr Kuzel
087: */
088: public final class Turbo {
089:
090: /** Default providers registry. */
091: private static Lookup.Result providers;
092:
093: /** Custom providers 'registry'. */
094: private final CustomProviders customProviders;
095:
096: private static WeakReference defaultInstance;
097:
098: private List listeners = new ArrayList(100);
099:
100: /** memory layer */
101: private final Memory memory;
102:
103: private final Statistics statistics;
104:
105: private static Environment env;
106:
107: /**
108: * Returns default instance. It's size is driven by
109: * keys lifetime. It shrinks on keys become unreferenced
110: * and grows without bounds on inserting keys.
111: */
112: public static synchronized Turbo getDefault() {
113: Turbo turbo = null;
114: if (defaultInstance != null) {
115: turbo = (Turbo) defaultInstance.get();
116: }
117:
118: if (turbo == null) {
119: turbo = new Turbo(null, 47, -1);
120: defaultInstance = new WeakReference(turbo);
121: }
122:
123: return turbo;
124: }
125:
126: /**
127: * Creates new instance with customized providers layer.
128: * @param providers never <code>null</null>
129: * @param min minimum number of entries held by the cache
130: * @param max maximum size or <code>-1</code> for unbound size
131: * (defined just by key instance lifetime)
132: */
133: public static synchronized Turbo createCustom(
134: CustomProviders providers, int min, int max) {
135: return new Turbo(providers, min, max);
136: }
137:
138: private Turbo(CustomProviders customProviders, int min, int max) {
139: statistics = Statistics.createInstance();
140: memory = new Memory(statistics, min, max);
141: this .customProviders = customProviders;
142: if (customProviders == null && providers == null) {
143: Lookup.Template t = new Lookup.Template(TurboProvider.class);
144: synchronized (Turbo.class) {
145: if (env == null)
146: env = new Environment();
147: }
148: providers = env.getLookup().lookup(t);
149: }
150: }
151:
152: /** Tests can set different environment. Must be called before {@link #getDefault}. */
153: static synchronized void initEnvironment(Environment environment) {
154: assert env == null;
155: env = environment;
156: providers = null;
157: }
158:
159: /** Logs cache statistics data. */
160: protected void finalize() throws Throwable {
161: super .finalize();
162: statistics.shutdown();
163: }
164:
165: /**
166: * Reads given attribute for given entity key.
167: * @param key a entity key, never <code>null</code>
168: * @param name identifies requested entry, never <code>null</code>
169: * @return entry value or <code>null</code> if it does not exist or unknown.
170: */
171: public Object readEntry(Object key, String name) {
172:
173: statistics.attributeRequest();
174:
175: // check memory cache
176:
177: if (memory.existsEntry(key, name)) {
178: Object value = memory.get(key, name);
179: statistics.memoryHit();
180: return value;
181: }
182:
183: // iterate over providers
184: List speculative = new ArrayList(57);
185: Object value = loadEntry(key, name, speculative);
186: memory.put(key, name, value != null ? value : Memory.NULL);
187: // XXX should fire here? yes if name avalability changes should be
188: // dispatched to clients that have not called prepare otherwise NO.
189:
190: // refire speculative results, can be optinized later on to fire
191: // them lazilly on prepareAttribute or isPrepared calls
192: Iterator it = speculative.iterator();
193: while (it.hasNext()) {
194: Object[] next = (Object[]) it.next();
195: Object sKey = next[0];
196: String sName = (String) next[1];
197: Object sValue = next[2];
198: assert sKey != null;
199: assert sName != null;
200: fireEntryChange(sKey, sName, sValue);
201: }
202:
203: return value;
204: }
205:
206: private Iterator providers() {
207: if (customProviders == null) {
208: Collection plugins = providers.allInstances();
209: List all = new ArrayList(plugins.size() + 1);
210: all.addAll(plugins);
211: all.add(DefaultTurboProvider.getDefault());
212: return all.iterator();
213: } else {
214: return customProviders.providers();
215: }
216: }
217:
218: /**
219: * Iterate over providers asking for attribute values
220: */
221: private Object loadEntry(Object key, String name, List speculative) {
222:
223: TurboProvider provider;
224: Iterator it = providers();
225: while (it.hasNext()) {
226: provider = (TurboProvider) it.next();
227: try {
228: if (provider.recognizesAttribute(name)
229: && provider.recognizesEntity(key)) {
230: TurboProvider.MemoryCache cache = TurboProvider.MemoryCache
231: .createDefault(memory, speculative);
232: Object value = provider.readEntry(key, name, cache);
233: statistics.providerHit();
234: return value;
235: }
236: } catch (ThreadDeath td) {
237: throw td;
238: } catch (Throwable t) {
239: // error in provider
240: ErrorManager.getDefault().annotate(
241: t,
242: "Error in provider " + provider
243: + ", skipping... "); // NOI18N
244: ErrorManager.getDefault().notify(
245: ErrorManager.INFORMATIONAL, t); // XXX in Junit mode writes to stdout ommiting annotation!!!
246: }
247: }
248:
249: return null;
250: }
251:
252: /**
253: * Writes given attribute, value pair and notifies all listeners.
254: * Written value is stored both into memory and providers layer.
255: * The call speed depends on actually used provider. However do not
256: * rely on synchronous provider call. In future it may return and
257: * fire immediately on writing into memory layer, populating providers
258: * asychronously on background.
259: *
260: * <p>A client calling this method is reponsible for passing
261: * valid value that is accepted by attribute serving providers.
262: *
263: * @param key identifies target, never <code>null</code>
264: * @param name identifies attribute, never <code>null</code>
265: * @param value actual attribute value to be stored, <code>null</code> behaviour
266: * is defined specificaly for each name. It always invalidates memory entry
267: * and it either invalidates or removes value from providers layer
268: * (mening: value unknown versus value is known to not exist).
269: *
270: * <p>Client should consider a size of stored value. It must be corelated
271: * with Turbo memory layer limits to avoid running ouf of memory.
272: *
273: * @return <ul>
274: * <li><code>false</code> on write failure caused by a provider denying the value.
275: * It means attribute contract violation and must be handled e.g.:
276: * <p><code>
277: * boolean success = faq.writeAttribute(fo, name, value);<br>
278: * assert success : "Unexpected name[" + name + "] value[" + value + "] denial for " + key + "!";
279: * </code>
280:
281: * <li><code>true</code> in all other cases includins I/O error.
282: * After all it's just best efford cache. All values can be recomputed.
283: * </ul>
284: */
285: public boolean writeEntry(Object key, String name, Object value) {
286:
287: if (value != null) {
288: Object oldValue = memory.get(key, name);
289: if (oldValue != null && oldValue.equals(value))
290: return true; // XXX assuming provider has the same value, assert it!
291: }
292:
293: int result = storeEntry(key, name, value);
294: if (result >= 0) {
295: // no one denied keep at least in memory cache
296: memory.put(key, name, value);
297: fireEntryChange(key, name, value);
298: return true;
299: } else {
300: return false;
301: }
302: }
303:
304: /**
305: * Stores directly to providers.
306: * @return 0 success, -1 contract failure, 1 other failure
307: */
308: int storeEntry(Object key, String name, Object value) {
309: TurboProvider provider;
310: Iterator it = providers();
311: while (it.hasNext()) {
312: provider = (TurboProvider) it.next();
313: try {
314: if (provider.recognizesAttribute(name)
315: && provider.recognizesEntity(key)) {
316: if (provider.writeEntry(key, name, value)) {
317: return 0;
318: } else {
319: // for debugging purposes log which provider rejected defined name contract
320: IllegalArgumentException ex = new IllegalArgumentException(
321: "Attribute[" + name
322: + "] value rejected by "
323: + provider);
324: ErrorManager.getDefault().notify(
325: ErrorManager.WARNING, ex);
326: return -1;
327: }
328: }
329: } catch (ThreadDeath td) {
330: throw td;
331: } catch (Throwable t) {
332: // error in provider
333: ErrorManager.getDefault().annotate(
334: t,
335: "Error in provider " + provider
336: + ", skipping... "); // NOI18N
337: ErrorManager.getDefault().notify(
338: ErrorManager.INFORMATIONAL, t);
339: }
340: }
341: return 1;
342: }
343:
344: /**
345: * Checks value instant availability and possibly schedules its background
346: * loading. It's designed to be called from UI tread.
347: *
348: * @return <ul>
349: * <li><code>false</code> if not ready and providers must be consulted. It
350: * asynchronously fires event possibly with <code>null</code> value
351: * if given attribute does not exist.
352: *
353: * <li>
354: * If <code>true</code> it's
355: * ready and stays ready at least until next {@link #prepareEntry},
356: * {@link #isPrepared}, {@link #writeEntry} <code>null</code> call
357: * or {@link #readEntry} from the same thread.
358: * </ul>
359: */
360: public boolean prepareEntry(Object key, String name) {
361:
362: statistics.attributeRequest();
363:
364: // check memory cache
365:
366: if (memory.existsEntry(key, name)) {
367: statistics.memoryHit();
368: return true;
369: }
370:
371: // start asynchronous providers queriing
372: scheduleLoad(key, name);
373: return false;
374: }
375:
376: /**
377: * Checks name instant availability. Note that actual
378: * value may be still <code>null</code>, in case
379: * that it's known that value does not exist.
380: *
381: * @return <ul>
382: * <li><code>false</code> if not present in memory for instant access.
383: *
384: * <li><code>true</code> when it's
385: * ready and stays ready at least until next {@link #prepareEntry},
386: * {@link #isPrepared}, {@link #writeEntry} <code>null</code> call
387: * or {@link #readEntry} from the same thread.
388: * </ul>
389: */
390: public boolean isPrepared(Object key, String name) {
391: return memory.existsEntry(key, name);
392: }
393:
394: /**
395: * Gets key instance that it actually used in memory layer.
396: * Client should keep reference to it if it wants to use
397: * key lifetime monitoring cache size strategy.
398: *
399: * @param key key never <code>null</code>
400: * @return key instance that is value-equalent or <code>null</code>
401: * if monitored instance does not exist.
402: */
403: public Object getMonitoredKey(Object key) {
404: return memory.getMonitoredKey(key);
405: }
406:
407: public void addTurboListener(TurboListener l) {
408: synchronized (listeners) {
409: List copy = new ArrayList(listeners);
410: copy.add(l);
411: listeners = copy;
412: }
413: }
414:
415: public void removeTurboListener(TurboListener l) {
416: synchronized (listeners) {
417: List copy = new ArrayList(listeners);
418: copy.remove(l);
419: listeners = copy;
420: }
421:
422: }
423:
424: protected void fireEntryChange(Object key, String name, Object value) {
425: Iterator it = listeners.iterator();
426: while (it.hasNext()) {
427: TurboListener next = (TurboListener) it.next();
428: next.entryChanged(key, name, value);
429: }
430: }
431:
432: /** For debugging purposes only. */
433: public String toString() {
434: StringBuffer sb = new StringBuffer("Turbo delegating to:"); // NOI18N
435: Iterator it = providers();
436: while (it.hasNext()) {
437: TurboProvider provider = (TurboProvider) it.next();
438: sb.append(" [" + provider + "]"); // NOI18N
439: }
440: return sb.toString();
441: }
442:
443: /** Defines binding to external world. Used by tests. */
444: static class Environment {
445: /** Lookup that serves providers. */
446: public Lookup getLookup() {
447: return Lookup.getDefault();
448: }
449: }
450:
451: // Background loading ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
452:
453: /** Holds keys that were requested for background status retrieval. */
454: private final Set prepareRequests = Collections
455: .synchronizedSet(new LinkedHashSet(27));
456:
457: private static PreparationTask preparationTask;
458:
459: /** Tries to locate meta on disk on failure it forward to repository */
460: private void scheduleLoad(Object key, String name) {
461: synchronized (prepareRequests) {
462: if (preparationTask == null) {
463: preparationTask = new PreparationTask(prepareRequests);
464: RequestProcessor.getDefault().post(preparationTask);
465: statistics.backgroundThread();
466: }
467: preparationTask.notifyNewRequest(new Request(key, name));
468: }
469: }
470:
471: /** Requests queue entry featuring value based identity. */
472: private final static class Request {
473: private final Object key;
474: private final String name;
475:
476: public Request(Object key, String name) {
477: this .name = name;
478: this .key = key;
479: }
480:
481: public boolean equals(Object o) {
482: if (this == o)
483: return true;
484: if (!(o instanceof Request))
485: return false;
486:
487: final Request request = (Request) o;
488:
489: if (name != null ? !name.equals(request.name)
490: : request.name != null)
491: return false;
492: if (key != null ? !key.equals(request.key)
493: : request.key != null)
494: return false;
495:
496: return true;
497: }
498:
499: public int hashCode() {
500: int result;
501: result = (key != null ? key.hashCode() : 0);
502: result = 29 * result + (name != null ? name.hashCode() : 0);
503: return result;
504: }
505:
506: public String toString() {
507: return "Request[key=" + key + ", attr=" + name + "]";
508: }
509: }
510:
511: /**
512: * On background fetches data from providers layer.
513: */
514: private final class PreparationTask implements Runnable {
515:
516: private final Set requests;
517:
518: private static final int INACTIVITY_TIMEOUT = 123 * 1000; // 123 sec
519:
520: public PreparationTask(Set requests) {
521: this .requests = requests;
522: }
523:
524: public void run() {
525: try {
526: Thread.currentThread().setName("Turbo Async Fetcher"); // NOI18N
527: while (waitForRequests()) {
528: Request request;
529: synchronized (requests) {
530: request = (Request) requests.iterator().next();
531: requests.remove(request);
532: }
533: Object key = request.key;
534: String name = request.name;
535: Object value;
536: boolean fire;
537: if (memory.existsEntry(key, name)) {
538:
539: synchronized (Memory.class) {
540: fire = memory.existsEntry(key, name) == false;
541: value = memory.get(key, name);
542: }
543: if (fire) {
544: statistics.providerHit(); // from our perpective we achieved hit
545: }
546: } else {
547: value = loadEntry(key, name, null);
548: // possible thread switch, so atomic fire test must be used
549: synchronized (Memory.class) {
550: fire = memory.existsEntry(key, name) == false;
551: Object oldValue = memory.get(key, name);
552: memory.put(key, name, value != null ? value
553: : Memory.NULL);
554: fire |= (oldValue != null && !oldValue
555: .equals(value))
556: || (oldValue == null && value != null);
557: }
558: }
559:
560: // some one was faster, probably previous disk read that silently fetched whole directory
561: // our contract was to fire event once loading, stick to it. Note that get()
562: // silently populates stable memory area
563: // if (fire) { ALWAYS because of above loadAttribute(key, name, null);
564: fireEntryChange(key, name, value); // notify as soon as available in memory
565: // }
566:
567: }
568: } catch (InterruptedException ex) {
569: synchronized (requests) {
570: // forget about recent requests
571: requests.clear();
572: }
573: } finally {
574: synchronized (requests) {
575: preparationTask = null;
576: }
577: }
578: }
579:
580: /**
581: * Wait for requests, it no request comes until timeout
582: * it ommits suicide. It's respawned on next request however.
583: */
584: private boolean waitForRequests() throws InterruptedException {
585: synchronized (requests) {
586: if (requests.size() == 0) {
587: requests.wait(INACTIVITY_TIMEOUT);
588: }
589: return requests.size() > 0;
590: }
591: }
592:
593: public void notifyNewRequest(Request request) {
594: synchronized (requests) {
595: if (requests.add(request)) {
596: statistics.queueSize(requests.size());
597: requests.notify();
598: } else {
599: statistics.duplicate();
600: statistics.providerHit();
601: }
602: }
603: }
604:
605: public String toString() {
606: return "Turbo.PreparationTask queue=[" + requests + "]"; // NOI18N
607: }
608: }
609:
610: }
|