| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package freemarker.cache; |
| |
| import java.lang.ref.ReferenceQueue; |
| import java.lang.ref.SoftReference; |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| /** |
| * A cache storage that implements a two-level Most Recently Used cache. In the |
| * first level, items are strongly referenced up to the specified maximum. When |
| * the maximum is exceeded, the least recently used item is moved into the |
| * second level cache, where they are softly referenced, up to another |
| * specified maximum. When the second level maximum is also exceeded, the least |
| * recently used item is discarded altogether. This cache storage is a |
| * generalization of both {@link StrongCacheStorage} and |
| * {@link SoftCacheStorage} - the effect of both of them can be achieved by |
| * setting one maximum to zero and the other to the largest positive integer. |
| * On the other hand, if you wish to use this storage in a strong-only mode, or |
| * in a soft-only mode, you might consider using {@link StrongCacheStorage} or |
| * {@link SoftCacheStorage} instead, as they can be used by |
| * {@link TemplateCache} concurrently without any synchronization on a 5.0 or |
| * later JRE. |
| * |
| * <p>This class is <em>NOT</em> thread-safe. If it's accessed from multiple |
| * threads concurrently, proper synchronization must be provided by the callers. |
| * Note that {@link TemplateCache}, the natural user of this class provides the |
| * necessary synchronizations when it uses the class. |
| * Also you might consider whether you need this sort of a mixed storage at all |
| * in your solution, as in most cases SoftCacheStorage can also be sufficient. |
| * SoftCacheStorage will use Java soft references, and they already use access |
| * timestamps internally to bias the garbage collector against clearing |
| * recently used references, so you can get reasonably good (and |
| * memory-sensitive) most-recently-used caching through |
| * {@link SoftCacheStorage} as well. |
| * |
| * @see freemarker.template.Configuration#setCacheStorage(CacheStorage) |
| */ |
| public class MruCacheStorage implements CacheStorageWithGetSize { |
| private final MruEntry strongHead = new MruEntry(); |
| private final MruEntry softHead = new MruEntry(); |
| { |
| softHead.linkAfter(strongHead); |
| } |
| private final Map map = new HashMap(); |
| private final ReferenceQueue refQueue = new ReferenceQueue(); |
| private final int strongSizeLimit; |
| private final int softSizeLimit; |
| private int strongSize = 0; |
| private int softSize = 0; |
| |
| /** |
| * Creates a new MRU cache storage with specified maximum cache sizes. Each |
| * cache size can vary between 0 and {@link Integer#MAX_VALUE}. |
| * @param strongSizeLimit the maximum number of strongly referenced templates; when exceeded, the entry used |
| * the least recently will be moved into the soft cache. |
| * @param softSizeLimit the maximum number of softly referenced templates; when exceeded, the entry used |
| * the least recently will be discarded. |
| */ |
| public MruCacheStorage(int strongSizeLimit, int softSizeLimit) { |
| if (strongSizeLimit < 0) throw new IllegalArgumentException("strongSizeLimit < 0"); |
| if (softSizeLimit < 0) throw new IllegalArgumentException("softSizeLimit < 0"); |
| this.strongSizeLimit = strongSizeLimit; |
| this.softSizeLimit = softSizeLimit; |
| } |
| |
| public Object get(Object key) { |
| removeClearedReferences(); |
| MruEntry entry = (MruEntry) map.get(key); |
| if (entry == null) { |
| return null; |
| } |
| relinkEntryAfterStrongHead(entry, null); |
| Object value = entry.getValue(); |
| if (value instanceof MruReference) { |
| // This can only happen with strongSizeLimit == 0 |
| return ((MruReference) value).get(); |
| } |
| return value; |
| } |
| |
| public void put(Object key, Object value) { |
| removeClearedReferences(); |
| MruEntry entry = (MruEntry) map.get(key); |
| if (entry == null) { |
| entry = new MruEntry(key, value); |
| map.put(key, entry); |
| linkAfterStrongHead(entry); |
| } else { |
| relinkEntryAfterStrongHead(entry, value); |
| } |
| |
| } |
| |
| public void remove(Object key) { |
| removeClearedReferences(); |
| removeInternal(key); |
| } |
| |
| private void removeInternal(Object key) { |
| MruEntry entry = (MruEntry) map.remove(key); |
| if (entry != null) { |
| unlinkEntryAndInspectIfSoft(entry); |
| } |
| } |
| |
| public void clear() { |
| strongHead.makeHead(); |
| softHead.linkAfter(strongHead); |
| map.clear(); |
| strongSize = softSize = 0; |
| // Quick refQueue processing |
| while (refQueue.poll() != null); |
| } |
| |
| private void relinkEntryAfterStrongHead(MruEntry entry, Object newValue) { |
| if (unlinkEntryAndInspectIfSoft(entry) && newValue == null) { |
| // Turn soft reference into strong reference, unless is was cleared |
| MruReference mref = (MruReference) entry.getValue(); |
| Object strongValue = mref.get(); |
| if (strongValue != null) { |
| entry.setValue(strongValue); |
| linkAfterStrongHead(entry); |
| } else { |
| map.remove(mref.getKey()); |
| } |
| } else { |
| if (newValue != null) { |
| entry.setValue(newValue); |
| } |
| linkAfterStrongHead(entry); |
| } |
| } |
| |
| private void linkAfterStrongHead(MruEntry entry) { |
| entry.linkAfter(strongHead); |
| if (strongSize == strongSizeLimit) { |
| // softHead.previous is LRU strong entry |
| MruEntry lruStrong = softHead.getPrevious(); |
| // Attila: This is equaivalent to strongSizeLimit != 0 |
| // DD: But entry.linkAfter(strongHead) was just executed above, so |
| // lruStrong != strongHead is true even if strongSizeLimit == 0. |
| if (lruStrong != strongHead) { |
| lruStrong.unlink(); |
| if (softSizeLimit > 0) { |
| lruStrong.linkAfter(softHead); |
| lruStrong.setValue(new MruReference(lruStrong, refQueue)); |
| if (softSize == softSizeLimit) { |
| // List is circular, so strongHead.previous is LRU soft entry |
| MruEntry lruSoft = strongHead.getPrevious(); |
| lruSoft.unlink(); |
| map.remove(lruSoft.getKey()); |
| } else { |
| ++softSize; |
| } |
| } else { |
| map.remove(lruStrong.getKey()); |
| } |
| } |
| } else { |
| ++strongSize; |
| } |
| } |
| |
| private boolean unlinkEntryAndInspectIfSoft(MruEntry entry) { |
| entry.unlink(); |
| if (entry.getValue() instanceof MruReference) { |
| --softSize; |
| return true; |
| } else { |
| --strongSize; |
| return false; |
| } |
| } |
| |
| private void removeClearedReferences() { |
| for (; ; ) { |
| MruReference ref = (MruReference) refQueue.poll(); |
| if (ref == null) { |
| break; |
| } |
| removeInternal(ref.getKey()); |
| } |
| } |
| |
| /** |
| * Returns the configured upper limit of the number of strong cache entries. |
| * |
| * @since 2.3.21 |
| */ |
| public int getStrongSizeLimit() { |
| return strongSizeLimit; |
| } |
| |
| /** |
| * Returns the configured upper limit of the number of soft cache entries. |
| * |
| * @since 2.3.21 |
| */ |
| public int getSoftSizeLimit() { |
| return softSizeLimit; |
| } |
| |
| /** |
| * Returns the <em>current</em> number of strong cache entries. |
| * |
| * @see #getStrongSizeLimit() |
| * @since 2.3.21 |
| */ |
| public int getStrongSize() { |
| return strongSize; |
| } |
| |
| /** |
| * Returns a close approximation of the <em>current</em> number of soft cache entries. |
| * |
| * @see #getSoftSizeLimit() |
| * @since 2.3.21 |
| */ |
| public int getSoftSize() { |
| removeClearedReferences(); |
| return softSize; |
| } |
| |
| /** |
| * Returns a close approximation of the current number of cache entries. |
| * |
| * @see #getStrongSize() |
| * @see #getSoftSize() |
| * @since 2.3.21 |
| */ |
| public int getSize() { |
| return getSoftSize() + getStrongSize(); |
| } |
| |
| private static final class MruEntry { |
| private MruEntry prev; |
| private MruEntry next; |
| private final Object key; |
| private Object value; |
| |
| /** |
| * Used solely to construct the head element |
| */ |
| MruEntry() { |
| makeHead(); |
| key = value = null; |
| } |
| |
| MruEntry(Object key, Object value) { |
| this.key = key; |
| this.value = value; |
| } |
| |
| Object getKey() { |
| return key; |
| } |
| |
| Object getValue() { |
| return value; |
| } |
| |
| void setValue(Object value) { |
| this.value = value; |
| } |
| |
| MruEntry getPrevious() { |
| return prev; |
| } |
| |
| void linkAfter(MruEntry entry) { |
| next = entry.next; |
| entry.next = this; |
| prev = entry; |
| next.prev = this; |
| } |
| |
| void unlink() { |
| next.prev = prev; |
| prev.next = next; |
| prev = null; |
| next = null; |
| } |
| |
| void makeHead() { |
| prev = next = this; |
| } |
| } |
| |
| private static class MruReference extends SoftReference { |
| private final Object key; |
| |
| MruReference(MruEntry entry, ReferenceQueue queue) { |
| super(entry.getValue(), queue); |
| this.key = entry.getKey(); |
| } |
| |
| Object getKey() { |
| return key; |
| } |
| } |
| |
| |
| } |