/* * 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.Reference; import java.lang.ref.ReferenceQueue; import java.lang.ref.SoftReference; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import freemarker.template.utility.UndeclaredThrowableException; /** * Soft cache storage is a cache storage that uses {@link SoftReference} objects to hold the objects it was passed, * therefore allows the garbage collector to purge the cache when it determines that it wants to free up memory. This * class is thread-safe to the extent that its underlying map is. The parameterless constructor uses a thread-safe map * since 2.3.24 or Java 5. * * @see freemarker.template.Configuration#setCacheStorage(CacheStorage) */ public class SoftCacheStorage implements ConcurrentCacheStorage, CacheStorageWithGetSize { private static final Method atomicRemove = getAtomicRemoveMethod(); private final ReferenceQueue queue = new ReferenceQueue(); private final Map map; private final boolean concurrent; /** * Creates an instance that uses a {@link ConcurrentMap} internally. */ public SoftCacheStorage() { this(new ConcurrentHashMap()); } /** * Returns true if the underlying Map is a {@code ConcurrentMap}. */ public boolean isConcurrent() { return concurrent; } public SoftCacheStorage(Map backingMap) { map = backingMap; this.concurrent = map instanceof ConcurrentMap; } public Object get(Object key) { processQueue(); Reference ref = (Reference) map.get(key); return ref == null ? null : ref.get(); } public void put(Object key, Object value) { processQueue(); map.put(key, new SoftValueReference(key, value, queue)); } public void remove(Object key) { processQueue(); map.remove(key); } public void clear() { map.clear(); processQueue(); } /** * Returns a close approximation of the number of cache entries. * * @since 2.3.21 */ public int getSize() { processQueue(); return map.size(); } private void processQueue() { for (; ; ) { SoftValueReference ref = (SoftValueReference) queue.poll(); if (ref == null) { return; } Object key = ref.getKey(); if (concurrent) { try { atomicRemove.invoke(map, new Object[] { key, ref }); } catch (IllegalAccessException e) { throw new UndeclaredThrowableException(e); } catch (InvocationTargetException e) { throw new UndeclaredThrowableException(e); } } else if (map.get(key) == ref) { map.remove(key); } } } private static final class SoftValueReference extends SoftReference { private final Object key; SoftValueReference(Object key, Object value, ReferenceQueue queue) { super(value, queue); this.key = key; } Object getKey() { return key; } } private static Method getAtomicRemoveMethod() { try { return Class.forName("java.util.concurrent.ConcurrentMap").getMethod("remove", new Class[] { Object.class, Object.class }); } catch (ClassNotFoundException e) { return null; } catch (NoSuchMethodException e) { throw new UndeclaredThrowableException(e); } } }