001: /*
002: * JBoss, Home of Professional Open Source.
003: * Copyright 2006, Red Hat Middleware LLC, and individual contributors
004: * as indicated by the @author tags. See the copyright.txt file in the
005: * distribution for a full listing of individual contributors.
006: *
007: * This is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU Lesser General Public License as
009: * published by the Free Software Foundation; either version 2.1 of
010: * the License, or (at your option) any later version.
011: *
012: * This software is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this software; if not, write to the Free
019: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021: */
022: package org.jboss.mq.server;
023:
024: import java.lang.ref.Reference;
025: import java.lang.ref.ReferenceQueue;
026: import java.util.HashMap;
027: import javax.jms.JMSException;
028: import javax.management.MBeanRegistration;
029: import javax.management.ObjectName;
030: import org.jboss.mq.DurableSubscriptionID;
031: import org.jboss.mq.SpyMessage;
032: import org.jboss.mq.pm.CacheStore;
033: import org.jboss.system.ServiceMBeanSupport;
034: import EDU.oswego.cs.dl.util.concurrent.SynchronizedLong;
035:
036: /**
037: * This class implements a Message cache so that larger amounts of messages
038: * can be processed without running out of memory. When memory starts getting tight
039: * it starts moving messages out of memory and into a file so that they can be recovered
040: * later.
041: *
042: * The locks should be obtained in the following order:<br>
043: * mr, the relevent message we are working with<br>
044: * lruCache, when maintaining the usage order
045: *
046: * @author <a href="mailto:hiram.chirino@jboss.org">Hiram Chirino</a>
047: * @author <a href="mailto:David.Maplesden@orion.co.nz">David Maplesden</a>
048: * @author <a href="mailto:pra@tim.se">Peter Antman</a>
049: * @author <a href="mailto:Adrian.Brock@HappeningTimes.com">Adrian Brock</a>
050: * @version $Revision: 57198 $
051: *
052: * @jmx.mbean name="jboss.mq:service=MessageCache"
053: * extends="org.jboss.system.ServiceMBean"
054: */
055: public class MessageCache extends ServiceMBeanSupport implements
056: MessageCacheMBean, MBeanRegistration, Runnable {
057: public static final long ONE_MEGABYTE = 1024L * 1000;
058: public static final long DEFAULT_HIGH_MEMORY_MARK = ONE_MEGABYTE * 50;
059: public static final long DEFAULT_MAX_MEMORY_MARK = ONE_MEGABYTE * 60;
060:
061: // The cached messages are orded in a LRU linked list
062: private LRUCache lruCache = new LRUCache();
063:
064: // Provides a Unique ID to MessageHanles
065: private SynchronizedLong messageCounter = new SynchronizedLong(0);
066: long cacheHits = 0;
067: long cacheMisses = 0;
068:
069: protected CacheStore cacheStore;
070: ObjectName cacheStoreName;
071:
072: private Thread referenceSoftner;
073:
074: private long highMemoryMark = DEFAULT_HIGH_MEMORY_MARK;
075: private long maxMemoryMark = DEFAULT_MAX_MEMORY_MARK;
076:
077: /** Whether to make soft references */
078: private boolean makeSoftReferences = true;
079:
080: /** The last time we softened */
081: private long lastSoften = 0L;
082:
083: /** soften no more than often */
084: private long softenNoMoreOftenThanMillis = 0L;
085:
086: /** soften at least every */
087: private long softenAtLeastEveryMillis = 0L;
088:
089: /** The length of time to wait before checking whether we should soften messages */
090: private long softenWaitMillis = 1000L;
091:
092: /** The minimum number of hard messages */
093: private int minimumHard = 1;
094:
095: /** The maximum number of hard messages */
096: private int maximumHard = 0;
097:
098: int softRefCacheSize = 0;
099: int totalCacheSize = 0;
100:
101: // Used to get notified when message are being deleted by GC
102: ReferenceQueue referenceQueue = new ReferenceQueue();
103:
104: // The historical number of softenings
105: long softenedSize = 0;
106:
107: // Check the soft reference depth
108: boolean checkSoftReferenceDepth = false;
109:
110: /**
111: * The <code>getInstance</code> method
112: *
113: * @return a <code>MessageCache</code> value
114: *
115: * @jmx.managed-attribute
116: */
117: public MessageCache getInstance() {
118: return this ;
119: }
120:
121: /**
122: * Adds a message to the cache.
123: */
124: public MessageReference add(SpyMessage message, BasicQueue queue,
125: int stored) throws javax.jms.JMSException {
126: DurableSubscriptionID id = message.header.durableSubscriberID;
127: return addInternal(message, queue, stored, id);
128: }
129:
130: /**
131: * Adds a message to the cache.
132: */
133: public MessageReference add(SpyMessage message, BasicQueue queue,
134: int stored, DurableSubscriptionID id)
135: throws javax.jms.JMSException {
136: return addInternal(message, queue, stored, id);
137: }
138:
139: /**
140: * Adds a message to the cache.
141: */
142: public MessageReference addInternal(SpyMessage message,
143: BasicQueue queue, int stored, DurableSubscriptionID id)
144: throws javax.jms.JMSException {
145: // Create the message reference
146: MessageReference mh = new MessageReference();
147: mh.init(this , messageCounter.increment(), message, queue, id);
148: mh.setStored(stored);
149:
150: // Add it to the cache
151: synchronized (mh) {
152: synchronized (lruCache) {
153: lruCache.addMostRecent(mh);
154: totalCacheSize++;
155: }
156: }
157: validateSoftReferenceDepth();
158:
159: return mh;
160: }
161:
162: /**
163: * removes a message from the cache
164: */
165: public void remove(MessageReference mr) throws JMSException {
166: // Remove if not done already
167: removeInternal(mr, true, true);
168: }
169:
170: /**
171: * removes a message from the cache without returning it to the pool
172: * used in two phase removes for joint cache/persistence
173: */
174: public void removeDelayed(MessageReference mr) throws JMSException {
175: // Remove from the cache
176: removeInternal(mr, true, false);
177: }
178:
179: /**
180: * removes a message from the cache but does not clear it,
181: * used in softening
182: */
183: void soften(MessageReference mr) throws JMSException {
184: // Remove from the cache
185: removeInternal(mr, false, false);
186:
187: if (makeSoftReferences)
188: softRefCacheSize++;
189: }
190:
191: /**
192: * removes a message from the cache
193: */
194: protected void removeInternal(MessageReference mr, boolean clear,
195: boolean reset) throws JMSException {
196: synchronized (mr) {
197: if (mr.stored != MessageReference.REMOVED) {
198: synchronized (lruCache) {
199: if (mr.hardReference != null) //If message is not hard, dont do lru stuff
200: lruCache.remove(mr);
201: if (clear)
202: totalCacheSize--;
203: }
204: if (clear)
205: mr.clear();
206: //Will remove it from storage if stored
207: }
208:
209: if (reset)
210: mr.reset();
211: //Return to the pool
212: }
213: }
214:
215: /**
216: * The strategy is that we keep the most recently used messages as
217: * Hard references. Then we make the older ones soft references. Making
218: * something a soft reference stores it to disk so we need to avoid making
219: * soft references if we can avoid it. But once it is made a soft reference does
220: * not mean that it is removed from memory. Depending on how agressive the JVM's
221: * GC is, it may stay around long enough for it to be used by a client doing a read,
222: * saving us read from the file system. If memory gets tight the GC will remove
223: * the soft references. What we want to do is make sure there are at least some
224: * soft references available so that the GC can reclaim memory.
225: * @see Runnable#run()
226: */
227: public void run() {
228: try {
229: while (true) {
230: // Get the next soft reference that was canned by the GC
231: Reference r = null;
232: if (checkSoftReferenceDepth)
233: r = referenceQueue.poll();
234: else
235: r = referenceQueue.remove(softenWaitMillis);
236: if (r != null) {
237: softRefCacheSize--;
238: // the GC will free a set of messages together, so we poll them
239: // all before we validate the soft reference depth.
240: while ((r = referenceQueue.poll()) != null) {
241: softRefCacheSize--;
242: }
243: if (log.isTraceEnabled())
244: log.trace("soft reference cache size is now: "
245: + softRefCacheSize);
246:
247: checkSoftReferenceDepth = true;
248: }
249:
250: long now = System.currentTimeMillis();
251:
252: // Don't try to soften too often
253: if (softenNoMoreOftenThanMillis > 0
254: && (now - lastSoften < softenNoMoreOftenThanMillis))
255: checkSoftReferenceDepth = false;
256:
257: // Is it a while since we last softened?
258: else if (softenAtLeastEveryMillis > 0
259: && (now - lastSoften > softenAtLeastEveryMillis))
260: checkSoftReferenceDepth = true;
261:
262: // Should we check for softening
263: if (checkSoftReferenceDepth) {
264: checkSoftReferenceDepth = validateSoftReferenceDepth();
265:
266: // Did the softening complete?
267: if (checkSoftReferenceDepth == false)
268: lastSoften = now;
269: }
270: }
271: } catch (InterruptedException e) {
272: // Signal to exit the thread.
273: } catch (Throwable t) {
274: log.error("Message Cache Thread Stopped: ", t);
275: }
276: log.debug("Thread exiting.");
277: }
278:
279: /**
280: * This method is in charge of determining if it time to convert some
281: * hard references over to soft references.
282: */
283: boolean validateSoftReferenceDepth() throws JMSException {
284: boolean trace = log.isTraceEnabled();
285:
286: // Loop until softening is not required or we find a message we can soften
287: while (getState() == ServiceMBeanSupport.STARTED) {
288: MessageReference messageToSoften = null;
289:
290: synchronized (lruCache) {
291: // howmany to change over to soft refs
292: int softenCount = 0;
293: int hardCount = getHardRefCacheSize();
294: int softCount = getSoftRefCacheSize();
295:
296: // Only soften down to a minimum
297: if (hardCount <= minimumHard)
298: return false;
299:
300: long currentMem = Runtime.getRuntime().totalMemory()
301: - Runtime.getRuntime().freeMemory();
302: if (currentMem > highMemoryMark) {
303: // we need to get more aggresive... how much?? lets get
304: // a mesurment from 0 to 1
305: float severity = ((float) (currentMem - highMemoryMark))
306: / (maxMemoryMark - highMemoryMark);
307: severity = Math.min(severity, 1.0F);
308: if (trace)
309: log.trace("Memory usage serverity=" + severity);
310: int totalMessageInMem = hardCount + softCount;
311: int howManyShouldBeSoft = (int) ((totalMessageInMem) * severity);
312: softenCount = howManyShouldBeSoft - softCount;
313: }
314:
315: // Are there too many messages in memory?
316: if (maximumHard > 0) {
317: int removeCount = hardCount - maximumHard;
318: if (removeCount > 0 && removeCount > softenCount)
319: softenCount = removeCount;
320: }
321:
322: // We can only do so much, somebody else is using all the memory?
323: if (softenCount > hardCount) {
324: if (trace)
325: log.trace("Soften count " + softenCount
326: + " greater than hard references "
327: + hardCount);
328: softenCount = hardCount;
329: }
330:
331: // Ignore soften counts of 1 since this will happen too often even
332: // if the serverity is low since it will round up.
333: if (softenCount > 1
334: || (maximumHard > 0 && hardCount > maximumHard)) {
335: if (trace)
336: log.trace("Need to soften " + softenCount
337: + " messages");
338: Node node = lruCache.getLeastRecent();
339: messageToSoften = (MessageReference) node.data;
340: }
341: }
342:
343: // No softening required
344: if (messageToSoften == null)
345: return false;
346:
347: synchronized (messageToSoften) {
348: // Soften unless it was removed
349: if (messageToSoften.messageCache != null
350: && messageToSoften.stored != MessageReference.REMOVED) {
351: messageToSoften.makeSoft();
352: if (messageToSoften.stored == MessageReference.STORED) {
353: softenedSize++;
354: return true;
355: } else if (messageToSoften.isPersistent()) {
356: // Avoid going into a cpu loop if there are persistent
357: // messages just about to be persisted
358: return false;
359: }
360: } else if (trace)
361: log.trace("not softening removed message "
362: + messageToSoften);
363: }
364: }
365: return false;
366: }
367:
368: /**
369: * This gets called when a MessageReference is de-referenced.
370: * We will pop it to the top of the RLU
371: */
372: void messageReferenceUsedEvent(MessageReference mh, boolean wasHard)
373: throws JMSException {
374: synchronized (mh) {
375: synchronized (lruCache) {
376: if (wasHard)
377: lruCache.makeMostRecent(mh);
378: else {
379: lruCache.addMostRecent(mh);
380: }
381: }
382: }
383:
384: if (wasHard == false)
385: checkSoftReferenceDepth = true;
386: }
387:
388: //////////////////////////////////////////////////////////////////////////////////
389: // Perisitence methods used by the MessageReference.
390: //////////////////////////////////////////////////////////////////////////////////
391: SpyMessage loadFromStorage(MessageReference mh) throws JMSException {
392: return cacheStore.loadFromStorage(mh);
393: }
394:
395: void saveToStorage(MessageReference mh, SpyMessage message)
396: throws JMSException {
397: cacheStore.saveToStorage(mh, message);
398: }
399:
400: void removeFromStorage(MessageReference mh) throws JMSException {
401: cacheStore.removeFromStorage(mh);
402: }
403:
404: //////////////////////////////////////////////////////////////////////////////////
405: //
406: // The following section deals the the JMX interface to manage the Cache
407: //
408: //////////////////////////////////////////////////////////////////////////////////
409:
410: /**
411: * This gets called to start the cache service. Synch. by start
412: */
413: protected void startService() throws Exception {
414: setupCacheStore();
415:
416: referenceSoftner = new Thread(this ,
417: "JBossMQ Cache Reference Softner");
418: referenceSoftner.setDaemon(true);
419: referenceSoftner.start();
420: }
421:
422: protected void setupCacheStore() throws Exception {
423: cacheStore = (CacheStore) getServer().getAttribute(
424: cacheStoreName, "Instance");
425: }
426:
427: /**
428: * This gets called to stop the cache service.
429: */
430: protected void stopService() {
431: synchronized (lruCache) {
432: referenceSoftner.interrupt();
433: referenceSoftner = null;
434: }
435: cacheStore = null;
436: }
437:
438: /**
439: * Gets the hardRefCacheSize
440: * @return Returns a int
441: *
442: * @jmx.managed-attribute
443: */
444: public int getHardRefCacheSize() {
445: synchronized (lruCache) {
446: return lruCache.size();
447: }
448: }
449:
450: /**
451: * The <code>getSoftenedSize</code> method
452: *
453: * @return a <code>long</code> value
454: *
455: * @jmx.managed-attribute
456: */
457: public long getSoftenedSize() {
458: return softenedSize;
459: }
460:
461: /**
462: * Gets the softRefCacheSize
463: * @return Returns a int
464: *
465: * @jmx.managed-attribute
466: */
467: public int getSoftRefCacheSize() {
468: return softRefCacheSize;
469: }
470:
471: /**
472: * Gets the totalCacheSize
473: * @return Returns a int
474: *
475: * @jmx.managed-attribute
476: */
477: public int getTotalCacheSize() {
478: return totalCacheSize;
479: }
480:
481: /**
482: * Gets the cacheMisses
483: * @return Returns a int
484: *
485: * @jmx.managed-attribute
486: */
487: public long getCacheMisses() {
488: return cacheMisses;
489: }
490:
491: /**
492: * Gets the cacheHits
493: * @return Returns a long
494: *
495: * @jmx.managed-attribute
496: */
497: public long getCacheHits() {
498: return cacheHits;
499: }
500:
501: /**
502: * Gets whether to make soft references
503: *
504: * @jmx.managed-attribute
505: * @return true when making soft references
506: */
507: public boolean getMakeSoftReferences() {
508: return makeSoftReferences;
509: }
510:
511: /**
512: * Sets whether to make soft references
513: *
514: * @jmx.managed-attribute
515: * @param true to make soft references
516: */
517: public void setMakeSoftReferences(boolean makeSoftReferences) {
518: this .makeSoftReferences = makeSoftReferences;
519: }
520:
521: /**
522: * Gets the minimum number of hard messages
523: *
524: * @jmx.managed-attribute
525: * @return the minimum number of hard messages
526: */
527: public int getMinimumHard() {
528: return minimumHard;
529: }
530:
531: /**
532: * Sets the minimum number of hard messages
533: *
534: * @jmx.managed-attribute
535: * @param minimumHard the minimum number of hard messages
536: */
537: public void setMinimumHard(int minimumHard) {
538: if (minimumHard < 1)
539: this .minimumHard = 1;
540: else
541: this .minimumHard = minimumHard;
542: }
543:
544: /**
545: * Gets the maximum number of hard messages
546: *
547: * @jmx.managed-attribute
548: * @return the minimum number of hard messages
549: */
550: public int getMaximumHard() {
551: return maximumHard;
552: }
553:
554: /**
555: * Sets the maximum number of hard messages
556: *
557: * @jmx.managed-attribute
558: * @param maximumHard the maximum number of hard messages
559: */
560: public void setMaximumHard(int maximumHard) {
561: if (maximumHard < 0)
562: this .maximumHard = 0;
563: else
564: this .maximumHard = maximumHard;
565: }
566:
567: /**
568: * Gets the length of time to wait before checking whether we should soften
569: *
570: * @jmx.managed-attribute
571: * @return the time to wait
572: */
573: public long getSoftenWaitMillis() {
574: return softenWaitMillis;
575: }
576:
577: /**
578: * Sets the length of time to wait before checking whether we should soften
579: *
580: * @jmx.managed-attribute
581: * @param millis the time to wait in millis
582: */
583: public void setSoftenWaitMillis(long millis) {
584: if (millis < 1000)
585: softenWaitMillis = 1000;
586: else
587: softenWaitMillis = millis;
588: }
589:
590: /**
591: * Gets the minimum length between softening checks
592: *
593: * @jmx.managed-attribute
594: * @return the time to wait
595: */
596: public long getSoftenNoMoreOftenThanMillis() {
597: return softenNoMoreOftenThanMillis;
598: }
599:
600: /**
601: * Sets the minimum length between softening checks
602: *
603: * @jmx.managed-attribute
604: * @param wait the time between checks
605: */
606: public void setSoftenNoMoreOftenThanMillis(long millis) {
607: if (millis < 0)
608: softenNoMoreOftenThanMillis = 0;
609: else
610: softenNoMoreOftenThanMillis = millis;
611: }
612:
613: /**
614: * Gets the maximum length between softening checks
615: *
616: * @jmx.managed-attribute
617: * @return the time
618: */
619: public long getSoftenAtLeastEveryMillis() {
620: return softenAtLeastEveryMillis;
621: }
622:
623: /**
624: * Sets the minimum length between softening checks
625: *
626: * @jmx.managed-attribute
627: * @param wait the time between checks
628: */
629: public void setSoftenAtLeastEveryMillis(long millis) {
630: if (millis < 0)
631: softenAtLeastEveryMillis = 0;
632: else
633: softenAtLeastEveryMillis = millis;
634: }
635:
636: /**
637: * Gets the highMemoryMark
638: * @return Returns a long
639: *
640: * @jmx.managed-attribute
641: */
642: public long getHighMemoryMark() {
643: return highMemoryMark / ONE_MEGABYTE;
644: }
645:
646: /**
647: * Sets the highMemoryMark
648: * @param highMemoryMark The highMemoryMark to set
649: *
650: * @jmx.managed-attribute
651: */
652: public void setHighMemoryMark(long highMemoryMark) {
653: if (highMemoryMark > 0)
654: this .highMemoryMark = highMemoryMark * ONE_MEGABYTE;
655: else
656: this .highMemoryMark = 0;
657: }
658:
659: /**
660: * Gets the maxMemoryMark
661: * @return Returns a long
662: *
663: * @jmx.managed-attribute
664: */
665: public long getMaxMemoryMark() {
666: return maxMemoryMark / ONE_MEGABYTE;
667: }
668:
669: /**
670: * Sets the maxMemoryMark
671: * @param maxMemoryMark The maxMemoryMark to set
672: *
673: * @jmx.managed-attribute
674: */
675: public void setMaxMemoryMark(long maxMemoryMark) {
676: if (maxMemoryMark > 0)
677: this .maxMemoryMark = maxMemoryMark * ONE_MEGABYTE;
678: else
679: this .maxMemoryMark = 0;
680: }
681:
682: /**
683: * Gets the CurrentMemoryUsage
684: * @return Returns a long
685: *
686: * @jmx.managed-attribute
687: */
688: public long getCurrentMemoryUsage() {
689: return (Runtime.getRuntime().totalMemory() - Runtime
690: .getRuntime().freeMemory())
691: / ONE_MEGABYTE;
692: }
693:
694: /**
695: * @see ServiceMBeanSupport#getName()
696: */
697: public String getName() {
698: return "MessageCache";
699: }
700:
701: /**
702: * @see MessageCacheMBean#setCacheStore(ObjectName)
703: *
704: * @jmx.managed-attribute
705: */
706: public void setCacheStore(ObjectName cacheStoreName) {
707: this .cacheStoreName = cacheStoreName;
708: }
709:
710: /**
711: * The <code>getCacheStore</code> method
712: *
713: * @return an <code>ObjectName</code> value
714: *
715: * @jmx.managed-attribute
716: */
717: public ObjectName getCacheStore() {
718: return cacheStoreName;
719: }
720:
721: /**
722: * This class implements a simple, efficient LRUCache. It is pretty much a
723: * cut down version of the code in org.jboss.pool.cache.LeastRecentlyUsedCache
724: */
725: class LRUCache {
726: int currentSize = 0;
727: //maps objects to their nodes
728: HashMap map = new HashMap();
729: Node mostRecent = null;
730: Node leastRecent = null;
731:
732: public void addMostRecent(Object o) {
733: Node newNode = new Node();
734: newNode.data = o;
735: //insert into map
736: Object oldNode = map.put(o, newNode);
737: if (oldNode != null) {
738: map.put(o, oldNode);
739: throw new RuntimeException("Can't add object '" + o
740: + "' to LRUCache that is already in cache.");
741: }
742: //insert into linked list
743: if (mostRecent == null) {
744: //first element
745: mostRecent = newNode;
746: leastRecent = newNode;
747: } else {
748: newNode.lessRecent = mostRecent;
749: mostRecent.moreRecent = newNode;
750: mostRecent = newNode;
751: }
752: ++currentSize;
753: }
754:
755: // Not used anywhere!!
756: public void addLeastRecent(Object o) {
757: Node newNode = new Node();
758: newNode.data = o;
759: //insert into map
760: Object oldNode = map.put(o, newNode);
761: if (oldNode != null) {
762: map.put(o, oldNode);
763: throw new RuntimeException("Can't add object '" + o
764: + "' to LRUCache that is already in cache.");
765: }
766: //insert into linked list
767: if (leastRecent == null) {
768: //first element
769: mostRecent = newNode;
770: leastRecent = newNode;
771: } else {
772: newNode.moreRecent = leastRecent;
773: leastRecent.lessRecent = newNode;
774: leastRecent = newNode;
775: }
776: ++currentSize;
777: }
778:
779: public void remove(Object o) {
780: //remove from map
781: Node node = (Node) map.remove(o);
782: if (node == null)
783: throw new RuntimeException("Can't remove object '" + o
784: + "' that is not in cache.");
785: //remove from linked list
786: Node more = node.moreRecent;
787: Node less = node.lessRecent;
788: if (more == null) { //means node is mostRecent
789: mostRecent = less;
790: if (mostRecent != null) {
791: mostRecent.moreRecent = null; //Mark it as beeing at the top of tree
792: }
793: } else {
794: more.lessRecent = less;
795: }
796: if (less == null) { //means node is leastRecent
797: leastRecent = more;
798: if (leastRecent != null) {
799: leastRecent.lessRecent = null; //Mark it last in tree
800: }
801: } else {
802: less.moreRecent = more;
803: }
804: --currentSize;
805: }
806:
807: public void makeMostRecent(Object o) {
808: //get node from map
809: Node node = (Node) map.get(o);
810: if (node == null)
811: throw new RuntimeException(
812: "Can't make most recent object '" + o
813: + "' that is not in cache.");
814: //reposition in linked list, first remove
815: Node more = node.moreRecent;
816: Node less = node.lessRecent;
817: if (more == null) //means node is mostRecent
818: return;
819: else
820: more.lessRecent = less;
821: if (less == null) //means node is leastRecent
822: leastRecent = more;
823: else
824: less.moreRecent = more;
825: //now add back in at most recent position
826: node.lessRecent = mostRecent;
827: node.moreRecent = null; //We are at the top
828: mostRecent.moreRecent = node;
829: mostRecent = node;
830: }
831:
832: public int size() {
833: return currentSize;
834: }
835:
836: public Node getMostRecent() {
837: return mostRecent;
838: }
839:
840: public Node getLeastRecent() {
841: return leastRecent;
842: }
843: }
844:
845: static class Node {
846: Node moreRecent = null;
847: Node lessRecent = null;
848: Object data = null;
849: }
850: }
|