001: /*
002: * Copyright (c) 1998 - 2005 Versant Corporation
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * Versant Corporation - initial API and implementation
010: */
011: package com.versant.core.storagemanager;
012:
013: import com.versant.core.common.State;
014: import com.versant.core.metadata.FetchGroup;
015: import com.versant.core.metadata.ClassMetaData;
016: import com.versant.core.metadata.ModelMetaData;
017: import com.versant.core.common.OID;
018: import com.versant.core.common.*;
019: import com.versant.core.server.CachedQueryResult;
020: import com.versant.core.server.CompiledQuery;
021: import com.versant.core.metric.*;
022:
023: import java.util.*;
024: import java.io.PrintStream;
025:
026: /**
027: * Count limited StorageCache implementation that uses an LRU algorithm to
028: * limit the number of cached instances and query results.
029: */
030: public final class LRUStorageCache implements StorageCache, HasMetrics {
031:
032: private final Map stateMap; // OID -> StateEntry
033: private final Map queryMap; // QueryEntry -> QueryEntry
034:
035: private ModelMetaData jmd;
036: private boolean enabled = true;
037: private boolean queryCacheEnabled = true;
038: private int maxObjects = 10000;
039: private int maxQueries = 1000;
040: private int objectCount;
041: private int queryCount;
042:
043: private long now;
044: private long evictAllTimestamp;
045:
046: // single linked active Tx list
047: private Tx txTail, txHead;
048:
049: // double linked LRU State list
050: private StateEntry stateTail; // least recently accessed State
051: private StateEntry stateHead; // most recently accessed State
052:
053: // double linked list of StateEntry's for each class
054: private StateEntry[] classStateHead;
055:
056: // time each class was last evicted
057: private long[] classEvictionTimestamp;
058:
059: // double linked LRU query key list
060: private QueryEntry queryHead;
061: private QueryEntry queryTail;
062:
063: // double linked list for each class containing the queries that depend it
064: private QueryEntryNode[] classQueryHead;
065:
066: private int hitCount;
067: private int missCount;
068: private int queryHitCount;
069: private int queryMissCount;
070:
071: private static final Tx DISABLED_TX = new Tx(0);
072:
073: private static final String CAT_CACHE = "L2Cache";
074:
075: private final BaseMetric metricCacheSize = new BaseMetric(
076: "CacheSize", "Cache Size", CAT_CACHE,
077: "Number of objects in the level 2 cache", 0,
078: Metric.CALC_AVERAGE);
079: private final BaseMetric metricCacheMaxSize = new BaseMetric(
080: "CacheMaxSize", "Cache Max Size", CAT_CACHE,
081: "Max number of objects to store in the level 2 cache", 0,
082: Metric.CALC_AVERAGE);
083: private final BaseMetric metricCacheHit = new BaseMetric(
084: "CacheHit", "Cache Hit", CAT_CACHE,
085: "Number of times data was found in cache", 3,
086: Metric.CALC_DELTA_PER_SECOND);
087: private final BaseMetric metricCacheMiss = new BaseMetric(
088: "CacheMiss", "Cache Miss", CAT_CACHE,
089: "Number of times data was not found in cache", 3,
090: Metric.CALC_DELTA_PER_SECOND);
091:
092: private final BaseMetric metricQueryCacheSize = new BaseMetric(
093: "QueryCacheSize", "Query Cache Size", CAT_CACHE,
094: "Number of queries in the cache", 0, Metric.CALC_AVERAGE);
095: private final BaseMetric metricQueryCacheMaxSize = new BaseMetric(
096: "QueryCacheMaxSize", "Query Cache Max Size", CAT_CACHE,
097: "Max number of queries to store in the cache", 0,
098: Metric.CALC_AVERAGE);
099: private final BaseMetric metricQueryCacheHit = new BaseMetric(
100: "QueryCacheHit", "Query Cache Hit", CAT_CACHE,
101: "Number of times query results were found in cache", 3,
102: Metric.CALC_DELTA_PER_SECOND);
103: private final BaseMetric metricQueryCacheMiss = new BaseMetric(
104: "QueryCacheMiss", "Query Cache Miss", CAT_CACHE,
105: "Number of times query results were not found in cache", 3,
106: Metric.CALC_DELTA_PER_SECOND);
107:
108: private final PercentageMetric metricCacheFullPercent = new PercentageMetric(
109: "CacheFullPercent",
110: "Cache Full %",
111: CAT_CACHE,
112: "Number of objects in the cache as a percentage of the max",
113: metricCacheSize, metricCacheMaxSize);
114: private final PercentageSumMetric metricCacheHitPercent = new PercentageSumMetric(
115: "CacheHitPercent", "Cache Hit %", CAT_CACHE,
116: "Cache hit rate percentage", metricCacheHit,
117: metricCacheMiss);
118: private final PercentageMetric metricQueryCacheFullPercent = new PercentageMetric(
119: "QueryCacheFullPercent",
120: "Query Cache Full %",
121: CAT_CACHE,
122: "Number of queries in the cache as a percentage of the max",
123: metricQueryCacheSize, metricQueryCacheMaxSize);
124: private final PercentageSumMetric metricQueryCacheHitPercent = new PercentageSumMetric(
125: "QueryCacheHitPercent", "Query Cache Hit %", CAT_CACHE,
126: "Query Cache hit rate percentage", metricQueryCacheHit,
127: metricQueryCacheMiss);
128:
129: public LRUStorageCache() {
130: stateMap = new HashMap();
131: queryMap = new HashMap();
132: }
133:
134: public void setJDOMetaData(ModelMetaData jmd) {
135: this .jmd = jmd;
136: int n = jmd.classes.length;
137: classStateHead = new StateEntry[n];
138: classEvictionTimestamp = new long[n];
139: classQueryHead = new QueryEntryNode[n];
140: }
141:
142: public boolean isEnabled() {
143: return enabled;
144: }
145:
146: public synchronized void setEnabled(boolean enabled) {
147: this .enabled = enabled;
148: if (!enabled) {
149: evictAll(null);
150: }
151: }
152:
153: public boolean isQueryCacheEnabled() {
154: return queryCacheEnabled;
155: }
156:
157: public void setQueryCacheEnabled(boolean queryCacheEnabled) {
158: this .queryCacheEnabled = queryCacheEnabled;
159: }
160:
161: public synchronized Object beginTx() {
162: if (!enabled) {
163: return DISABLED_TX;
164: }
165: Tx tx = new Tx(++now);
166: if (txHead == null) {
167: txTail = txHead = tx;
168: } else {
169: txHead.next = tx;
170: txHead = tx;
171: }
172: return tx;
173: }
174:
175: public synchronized void endTx(Object o) {
176: if (o == DISABLED_TX) {
177: return;
178: }
179: Tx tx = (Tx) o;
180: tx.finished = true;
181: // remove all eviction markers for each finished tx that is the oldest
182: // tx i.e. there is no older tx that has not finished yet
183: for (; txTail.finished;) {
184: for (int i = txTail.evictedCount - 1; i >= 0; i--) {
185: OID oid = txTail.evicted[i];
186: StateEntry e = (StateEntry) stateMap.get(oid);
187: if (e != null && e.timestamp == tx.started) {
188: stateMap.remove(oid);
189: }
190: }
191: if (txTail.next == null) {
192: txTail = txHead = null;
193: break;
194: }
195: txTail = txTail.next;
196: }
197: }
198:
199: public synchronized State getState(OID oid, FetchGroup fetchGroup) {
200: if (!enabled) {
201: return null;
202: }
203: StateEntry e = (StateEntry) stateMap.get(oid);
204: if (e != null && e.state != null) {
205: removeFromStateList(e);
206: addToHeadOfStateList(e);
207: if (fetchGroup == null
208: || e.state.containsFetchGroup(fetchGroup)) {
209: ++hitCount;
210: return e.state.getCopy();
211: }
212: }
213: ++missCount;
214: return null;
215: }
216:
217: public synchronized boolean contains(OID oid) {
218: if (!enabled) {
219: return false;
220: }
221: StateEntry e = (StateEntry) stateMap.get(oid);
222: return e != null && e.state != null;
223: }
224:
225: public synchronized CachedQueryResult getQueryResult(
226: CompiledQuery cq, Object[] params) {
227: if (!enabled || !queryCacheEnabled) {
228: return null;
229: }
230: QueryEntry e = (QueryEntry) queryMap.get(new QueryEntry(cq,
231: params));
232: if (e != null && e.res != null) {
233: ++queryHitCount;
234: removeFromQueryList(e);
235: addToHeadOfQueryList(e);
236: return e.res;
237: } else {
238: ++queryMissCount;
239: return null;
240: }
241: }
242:
243: public synchronized int getQueryResultCount(CompiledQuery cq,
244: Object[] params) {
245: if (!enabled || !queryCacheEnabled) {
246: return -1;
247: }
248: QueryEntry e = (QueryEntry) queryMap.get(new QueryEntry(cq,
249: params));
250: if (e != null) {
251: ++queryHitCount;
252: removeFromQueryList(e);
253: addToHeadOfQueryList(e);
254: return e.res != null ? e.res.results.size() : e.resultCount;
255: } else {
256: ++queryMissCount;
257: return -1;
258: }
259: }
260:
261: public synchronized void evict(Object tx, CompiledQuery cq,
262: Object[] params) {
263: if (!enabled) {
264: return;
265: }
266: QueryEntry e = (QueryEntry) queryMap.get(new QueryEntry(cq,
267: params));
268: if (e != null) {
269: removeFromQueryList(e);
270: removeFromClassQueryLists(e);
271: queryMap.remove(e);
272: }
273: }
274:
275: public synchronized void add(Object otx, StatesReturned container) {
276: if (!enabled) {
277: return;
278: }
279: Tx tx = (Tx) otx;
280: if (tx.started <= evictAllTimestamp) {
281: // cannot accept any data from tx as evict all was done after
282: // it started
283: return;
284: }
285: for (Iterator i = container.iterator(); i.hasNext();) {
286: EntrySet.Entry me = (EntrySet.Entry) i.next();
287: OID oid = (OID) me.getKey();
288: State state = (State) me.getValue();
289: if (!state.isCacheble()
290: || tx.started <= classEvictionTimestamp[state
291: .getClassIndex()]) {
292: continue; // class was evicted after tx started so dont add
293: }
294: StateEntry e = (StateEntry) stateMap.get(oid);
295: if (e != null) {
296: if (tx.started <= e.timestamp || e.state == null) {
297: continue; // data in cache is newer or evicted so dont add
298: }
299: e.state.updateFrom(state);
300: e.timestamp = now;
301: } else {
302: e = new StateEntry(now, oid, state);
303: stateMap.put(oid, e);
304: addToHeadOfStateList(e);
305: addToClassStateList(e);
306: discardExcessStates();
307: }
308: }
309: }
310:
311: public synchronized void add(Object tx, CompiledQuery cq,
312: Object[] params, CachedQueryResult queryData) {
313: if (!enabled) {
314: return;
315: }
316: addImp((Tx) tx, cq, params, queryData,
317: queryData.results == null ? 0 : queryData.results
318: .size());
319: }
320:
321: private void addImp(Tx tx, CompiledQuery cq, Object[] params,
322: CachedQueryResult queryData, int resultCount) {
323: if (tx.started <= evictAllTimestamp) {
324: // cannot accept any data from tx as evict all was done after
325: // it started
326: return;
327: }
328: QueryEntry e = new QueryEntry(cq, params);
329: QueryEntry existing = (QueryEntry) queryMap.get(e);
330: if (existing != null) {
331: if (tx.started <= existing.timestamp) {
332: return; // data in cache is newer so dont add
333: }
334: e = existing;
335: } else {
336: int[] indexes = e.cq.getClassIndexes();
337: // dont add to cache if any of the classes involved have been
338: // evicted since we started as the query data may be stale
339: for (int i = indexes.length - 1; i >= 0; i--) {
340: if (classEvictionTimestamp[indexes[i]] >= tx.started) {
341: return;
342: }
343: }
344: queryMap.put(e, e);
345: addToHeadOfQueryList(e);
346: discardExcessQueries();
347: addToClassQueryLists(e);
348: }
349: e.res = queryData;
350: e.resultCount = resultCount;
351: e.timestamp = tx.started;
352: }
353:
354: public synchronized void add(Object tx, CompiledQuery cq,
355: Object[] params, int count) {
356: if (!queryCacheEnabled) {
357: return;
358: }
359: addImp((Tx) tx, cq, params, null, count);
360: }
361:
362: public synchronized void evict(Object otx, OID[] oids, int offset,
363: int length, int expected) {
364: if (!enabled) {
365: return;
366: }
367: Tx tx = (Tx) otx;
368: OID[] a;
369: int evictedCount = tx.evictedCount;
370: if (evictedCount > 0) {
371: if (tx.evicted.length - evictedCount >= length) {
372: a = tx.evicted;
373: } else {
374: a = new OID[evictedCount + length];
375: System.arraycopy(tx.evicted, 0, a, 0, evictedCount);
376: tx.evicted = a;
377: }
378: } else {
379: tx.evicted = a = new OID[expected < length ? length
380: : expected];
381: }
382: System.arraycopy(oids, offset, a, evictedCount, length);
383: long started = tx.started;
384: for (int i = 0; i < length; i++) {
385: OID oid = a[i + evictedCount];
386: StateEntry e = (StateEntry) stateMap.get(oid);
387: if (e == null) { // create eviction marker
388: stateMap.put(oid, new StateEntry(started, oid, null));
389: } else if (e.state != null) {
390: removeFromStateList(e);
391: removeFromClassStateList(e);
392: e.state = null;
393: e.timestamp = started;
394: } else if (e.timestamp < started) {
395: e.timestamp = started;
396: continue;
397: }
398: int ci = oid.getClassIndex();
399: classEvictionTimestamp[ci] = now;
400: removeQueriesForClass(ci);
401: }
402: tx.evictedCount += length;
403: }
404:
405: public synchronized void evict(Object tx, ClassMetaData[] classes,
406: int classCount) {
407: if (!enabled) {
408: return;
409: }
410: for (int i = 0; i < classCount; i++) {
411: int ci = classes[i].index;
412: classEvictionTimestamp[ci] = now;
413: // evict all states for class
414: for (StateEntry e = classStateHead[ci]; e != null;) {
415: removeFromStateList(e);
416: stateMap.remove(e.oid);
417: e.state = null;
418: e.oid = null;
419: StateEntry prev = e.classPrev;
420: e.classNext = null;
421: e.classPrev = null;
422: e = prev;
423: }
424: classStateHead[ci] = null;
425: // evict all queries for the class
426: removeQueriesForClass(ci);
427: classes[i].cacheStrategyAllDone = false;
428: }
429: }
430:
431: public synchronized void evictAll(Object tx) {
432: evictAllTimestamp = now;
433: stateMap.clear();
434: objectCount = 0;
435: queryMap.clear();
436: queryCount = 0;
437: for (int i = classQueryHead.length - 1; i >= 0; i--) {
438: classQueryHead[i] = null;
439: }
440: for (int i = jmd.classes.length - 1; i >= 0; i--) {
441: jmd.classes[i].cacheStrategyAllDone = false;
442: }
443: stateHead = stateTail = null;
444: }
445:
446: public int getObjectCount() {
447: return objectCount;
448: }
449:
450: public int getMaxObjects() {
451: return maxObjects;
452: }
453:
454: public synchronized void setMaxObjects(int maxObjects) {
455: this .maxObjects = maxObjects;
456: discardExcessStates();
457: }
458:
459: public int getMaxQueries() {
460: return maxQueries;
461: }
462:
463: public synchronized void setMaxQueries(int maxQueries) {
464: this .maxQueries = maxQueries;
465: discardExcessQueries();
466: }
467:
468: public int getHitCount() {
469: return hitCount;
470: }
471:
472: public int getMissCount() {
473: return missCount;
474: }
475:
476: public int getQueryHitCount() {
477: return queryHitCount;
478: }
479:
480: public int getQueryMissCount() {
481: return queryMissCount;
482: }
483:
484: private void discardExcessStates() {
485: for (; objectCount > maxObjects && stateTail != null; --objectCount) {
486: StateEntry e = stateTail;
487: stateMap.remove(e.oid);
488: stateTail = e.lruNext;
489: e.lruNext = null;
490: if (stateTail != null) {
491: stateTail.lruPrev = null;
492: } else {
493: stateHead = null;
494: }
495: }
496: }
497:
498: private void discardExcessQueries() {
499: for (; queryCount > maxQueries && queryTail != null; --queryCount) {
500: QueryEntry e = queryTail;
501: queryMap.remove(e);
502: queryTail = e.next;
503: e.next = null;
504: if (queryTail != null) {
505: queryTail.prev = null;
506: } else {
507: queryHead = null;
508: }
509: removeFromClassQueryLists(e);
510: }
511: }
512:
513: /**
514: * Remove e from the double linked LRU list and dec objectCount.
515: */
516: private void removeFromStateList(StateEntry e) {
517: if (e.lruPrev != null) {
518: e.lruPrev.lruNext = e.lruNext;
519: } else {
520: stateTail = e.lruNext;
521: }
522: if (e.lruNext != null) {
523: e.lruNext.lruPrev = e.lruPrev;
524: } else {
525: stateHead = e.lruPrev;
526: }
527: e.lruNext = e.lruPrev = null;
528: --objectCount;
529: }
530:
531: /**
532: * Add ps to the head of the double linked LRU list and inc objectCount.
533: * This will make it the most recently accessed object.
534: */
535: private void addToHeadOfStateList(StateEntry e) {
536: e.lruNext = null;
537: e.lruPrev = stateHead;
538: if (stateHead != null) {
539: stateHead.lruNext = e;
540: }
541: stateHead = e;
542: if (stateTail == null) {
543: stateTail = e;
544: }
545: ++objectCount;
546: }
547:
548: /**
549: * Add e to the double linked class state list for its class.
550: */
551: private void addToClassStateList(StateEntry e) {
552: int i = e.state.getClassIndex();
553: e.classPrev = classStateHead[i];
554: if (classStateHead[i] != null) {
555: classStateHead[i].classNext = e;
556: }
557: e.classNext = null;
558: classStateHead[i] = e;
559: }
560:
561: /**
562: * Remove e from the double linked class state list for its class.
563: */
564: private void removeFromClassStateList(StateEntry e) {
565: if (e.classPrev != null) {
566: e.classPrev.classNext = e.classNext;
567: } else {
568: classStateHead[e.state.getClassIndex()] = e.classNext;
569: }
570: if (e.classNext != null) {
571: e.classNext.classPrev = e.classPrev;
572: e.classNext = null;
573: }
574: e.classPrev = null;
575: }
576:
577: /**
578: * Remove cps from the double linked LRU list.
579: */
580: private void removeFromQueryList(QueryEntry e) {
581: if (e.prev != null) {
582: e.prev.next = e.next;
583: } else {
584: queryTail = e.next;
585: }
586: if (e.next != null) {
587: e.next.prev = e.prev;
588: } else {
589: queryHead = e.prev;
590: }
591: e.next = e.prev = null;
592: --queryCount;
593: }
594:
595: /**
596: * Add ps to the head of the double linked LRU list. This will make it
597: * the most recently accessed object.
598: */
599: private void addToHeadOfQueryList(QueryEntry e) {
600: e.next = null;
601: e.prev = queryHead;
602: if (queryHead != null) {
603: queryHead.next = e;
604: }
605: queryHead = e;
606: if (queryTail == null) {
607: queryTail = e;
608: }
609: ++queryCount;
610: }
611:
612: /**
613: * Add e to the class query lists of all of the classes that it depends
614: * on.
615: */
616: private void addToClassQueryLists(QueryEntry e) {
617: int[] indexes = e.cq.getClassIndexes();
618: QueryEntryNode sibling = null;
619: for (int i = 0; i < indexes.length; i++) {
620: int classIndex = indexes[i];
621: QueryEntryNode head = classQueryHead[classIndex];
622: QueryEntryNode n = new QueryEntryNode(e);
623: n.prev = head;
624: if (head != null) {
625: head.next = n;
626: }
627: if (sibling != null) {
628: sibling.nextSibling = n;
629: } else {
630: e.queryNodeTail = n;
631: }
632: classQueryHead[classIndex] = sibling = n;
633: }
634: }
635:
636: /**
637: * Remove e from the class query lists of all of the classes it depends
638: * on.
639: */
640: private void removeFromClassQueryLists(QueryEntry e) {
641: int i = 0;
642: int[] indexes = e.cq.getClassIndexes();
643: for (QueryEntryNode n = e.queryNodeTail; n != null; i++) {
644: QueryEntryNode nextSibling = n.nextSibling;
645: n.nextSibling = null;
646: if (n.prev != null) {
647: n.prev.next = n.next;
648: }
649: if (n.next != null) {
650: n.next.prev = n.prev;
651: } else { // n is current head of list
652: classQueryHead[indexes[i]] = n.prev;
653: }
654: n.next = n.prev = null;
655: n = nextSibling;
656: }
657: }
658:
659: /**
660: * Remove all queries for the class from the LRU query list, the query
661: * lists for all classes each query depends on and the queryMap itself.
662: */
663: private void removeQueriesForClass(int classIndex) {
664: for (QueryEntryNode n = classQueryHead[classIndex]; n != null;) {
665: removeFromQueryList(n.e);
666: QueryEntryNode prev = n.prev;
667: removeFromClassQueryLists(n.e);
668: queryMap.remove(n.e);
669: n = prev;
670: }
671: }
672:
673: public void dump(PrintStream out) {
674: out.println("stateMap.size() = " + stateMap.size());
675: out.println("objectCount = " + objectCount + ", maxObjects = "
676: + maxObjects);
677: int c = 0;
678: StateEntry p = null;
679: for (StateEntry e = stateTail; e != null; p = e, e = e.lruNext, ++c) {
680: asst(e.lruPrev == p);
681: }
682: out.println("LRU StateList length = " + c + " stateTail = "
683: + stateTail + " stateHead = " + stateHead);
684: HashSet oids = new HashSet();
685: for (Tx e = txTail; e != null; e = e.next) {
686: if (e.evicted != null) {
687: oids.addAll(Arrays.asList(e.evicted));
688: }
689: }
690: asst(stateHead == p);
691: asst(objectCount <= maxObjects);
692: asst(objectCount + oids.size() == stateMap.size());
693: out.println("--- Tx list ---");
694: c = 0;
695: int totEvictedCount = 0;
696: for (Tx e = txTail; e != null; e = e.next, ++c) {
697: out.println(e + " started " + e.started + " finished "
698: + e.finished + " evictedCount " + e.evictedCount);
699: totEvictedCount += e.evictedCount;
700: }
701: out.println("--- count " + c + " totEvictedCount "
702: + totEvictedCount);
703: }
704:
705: private void asst(boolean bool) {
706: if (!bool) {
707: throw BindingSupportImpl.getInstance().internal(
708: "assertion failed");
709: }
710: }
711:
712: public void addMetrics(List list) {
713: list.add(metricCacheSize);
714: list.add(metricCacheMaxSize);
715: list.add(metricCacheHit);
716: list.add(metricCacheMiss);
717: list.add(metricQueryCacheSize);
718: list.add(metricQueryCacheMaxSize);
719: list.add(metricQueryCacheHit);
720: list.add(metricQueryCacheMiss);
721: list.add(metricCacheFullPercent);
722: list.add(metricCacheHitPercent);
723: list.add(metricQueryCacheFullPercent);
724: list.add(metricQueryCacheHitPercent);
725: }
726:
727: public void sampleMetrics(int[][] buf, int pos) {
728: buf[metricCacheSize.getIndex()][pos] = objectCount;
729: buf[metricCacheMaxSize.getIndex()][pos] = maxObjects;
730: buf[metricCacheHit.getIndex()][pos] = hitCount;
731: buf[metricCacheMiss.getIndex()][pos] = missCount;
732: buf[metricQueryCacheSize.getIndex()][pos] = queryCount;
733: buf[metricQueryCacheMaxSize.getIndex()][pos] = maxQueries;
734: buf[metricQueryCacheHit.getIndex()][pos] = queryHitCount;
735: buf[metricQueryCacheMiss.getIndex()][pos] = queryMissCount;
736: }
737:
738: /**
739: * Info about a cache transaction.
740: */
741: private static final class Tx {
742: final long started;
743: Tx next;
744: boolean finished;
745: OID[] evicted;
746: int evictedCount;
747:
748: public Tx(long timestamp) {
749: this .started = timestamp;
750: }
751: }
752:
753: /**
754: * Stuff we associate with each State in stateMap. The state field is
755: * null if this is an eviction marker.
756: */
757: private static final class StateEntry {
758: long timestamp; // data read in a tx with started <= timestamp will
759: // not go in cache
760: OID oid;
761: State state;
762: StateEntry lruPrev, lruNext;
763: StateEntry classPrev, classNext;
764:
765: public StateEntry(long txId, OID oid, State state) {
766: this .timestamp = txId;
767: this .oid = oid;
768: this .state = state;
769: }
770: }
771:
772: /**
773: * This is the key and value for our query map. These form a double linked
774: * LRU list of query results.
775: */
776: private static final class QueryEntry {
777: final CompiledQuery cq;
778: final Object[] params;
779: final int hashCode;
780: long timestamp; // data read in a tx with started <= timestamp will
781: // not go in cache
782: CachedQueryResult res;
783: int resultCount;
784: QueryEntry prev, next;
785: QueryEntryNode queryNodeTail;
786:
787: // single linked list of our nodes on QueryEntryNode.nextSibling
788:
789: public QueryEntry(CompiledQuery cq, Object[] params) {
790: this .cq = cq;
791: this .params = params;
792: int hc = cq.hashCode();
793: if (params != null) {
794: hc = cq.hashCode();
795: for (int i = params.length - 1; i >= 0; i--) {
796: Object o = params[i];
797: if (o != null) {
798: hc = hc * 29 + o.hashCode();
799: }
800: }
801: }
802: hashCode = hc;
803: }
804:
805: public int hashCode() {
806: return hashCode;
807: }
808:
809: public boolean equals(Object o) {
810: QueryEntry k = (QueryEntry) o;
811: if (hashCode != k.hashCode) {
812: return false;
813: }
814: if (!cq.equals(k.cq)) {
815: return false;
816: }
817: if (params != null) {
818: for (int i = params.length - 1; i >= 0; i--) {
819: Object a = params[i];
820: Object b = k.params[i];
821: if (a == null) {
822: if (b != null)
823: return false;
824: } else if (b == null || !a.equals(b)) {
825: return false;
826: }
827: }
828: }
829: return true;
830: }
831: }
832:
833: /**
834: * Node in a double linked list of QueryEntry's. All the nodes for a
835: * single QueryEntry are also linked together starting from the
836: * QueryEntry.queryNodeTail. This makes it easy to remove all of the
837: * nodes for a QueryEntry.
838: */
839: private static final class QueryEntryNode {
840: QueryEntry e;
841: QueryEntryNode prev, next, nextSibling;
842:
843: public QueryEntryNode(QueryEntry e) {
844: this.e = e;
845: }
846:
847: }
848:
849: }
|