/*
 * 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.
 */

/* $Id: PropertyCache.java 1761019 2016-09-16 10:43:45Z ssteiner $ */

package org.apache.fop.fo.properties;

import java.lang.ref.WeakReference;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

Thread-safe cache that minimizes the memory requirements by fetching an instance from the cache that is equal to the given one. Internally the instances are stored in WeakReferences in order to be reclaimed when they are no longer referenced.
Type parameters:
  • <T> – The type of values that are cached
/** * Thread-safe cache that minimizes the memory requirements by fetching an instance from the cache * that is equal to the given one. Internally the instances are stored in WeakReferences in order to * be reclaimed when they are no longer referenced. * @param <T> The type of values that are cached */
public final class PropertyCache<T> { private static final Log LOG = LogFactory.getLog(PropertyCache.class);
Determines if the cache is used based on the value of the system property org.apache.fop.fo.properties.use-cache
/** * Determines if the cache is used based on the value of the system property * org.apache.fop.fo.properties.use-cache */
private final boolean useCache;
The underlying map that stores WeakReferences to the cached entries. The map keys are the hashCode of the cached entries. The map values are a WeakRefence to the cached entries. When two cached entries have the same hash code, the last one is kept but this should be an exception case (otherwise the hashCode() method of T needs to be fixed).
/** * The underlying map that stores WeakReferences to the cached entries. The map keys are the * hashCode of the cached entries. The map values are a WeakRefence to the cached entries. When * two cached entries have the same hash code, the last one is kept but this should be an * exception case (otherwise the hashCode() method of T needs to be fixed). */
private final ConcurrentMap<Integer, WeakReference<T>> map;
Counts the number of entries put in the map in order to periodically check and remove the entries whose referents have been reclaimed.
/** * Counts the number of entries put in the map in order to periodically check and remove the * entries whose referents have been reclaimed. */
private final AtomicInteger putCounter;
Lock to prevent concurrent cleanup of the map.
/** * Lock to prevent concurrent cleanup of the map. */
private final Lock cleanupLock; private final AtomicInteger hashCodeCollisionCounter;
Creates a new cache. The "org.apache.fop.fo.properties.use-cache" system property is used to determine whether properties should actually be cached or not. If not, then the fetch(Object) method will simply return its argument. To enable the cache, set this property to "true" (case insensitive).
/** * Creates a new cache. The "org.apache.fop.fo.properties.use-cache" system * property is used to determine whether properties should actually be * cached or not. If not, then the {@link #fetch(Object)} method will simply * return its argument. To enable the cache, set this property to "true" * (case insensitive). */
public PropertyCache() { boolean useCache; try { useCache = Boolean.valueOf( System.getProperty("org.apache.fop.fo.properties.use-cache", "true")); } catch (SecurityException e) { useCache = true; LOG.info("Unable to access org.apache.fop.fo.properties.use-cache" + " due to security restriction; defaulting to 'true'."); } if (useCache) { this.map = new ConcurrentHashMap<Integer, WeakReference<T>>(); this.putCounter = new AtomicInteger(); this.cleanupLock = new ReentrantLock(); this.hashCodeCollisionCounter = new AtomicInteger(); } else { this.map = null; this.putCounter = null; this.cleanupLock = null; this.hashCodeCollisionCounter = null; } this.useCache = useCache; }
Returns a cached version of the given object. If the object is not yet in the cache, it will be added and then returned.
Params:
  • obj – an object
Returns:a cached version of the object
/** * Returns a cached version of the given object. If the object is not yet in * the cache, it will be added and then returned. * * @param obj an object * @return a cached version of the object */
public T fetch(T obj) { if (!this.useCache) { return obj; } if (obj == null) { return null; } Integer hashCode = obj.hashCode(); WeakReference<T> weakRef = map.get(hashCode); if (weakRef == null) { weakRef = map.putIfAbsent(hashCode, new WeakReference<T>(obj)); attemptCleanup(); if (weakRef == null) { return obj; } // else another thread added a value, continue. } T cached = weakRef.get(); if (cached != null) { if (eq(cached, obj)) { return cached; } else { /* * Log a message when obj.getClass() does not implement correctly the equals() or * hashCode() method. It is expected that only very few objects will have the * same hashCode but will not be equal. */ if ((hashCodeCollisionCounter.incrementAndGet() % 10) == 0) { LOG.info(hashCodeCollisionCounter.get() + " hashCode() collisions for " + obj.getClass().getName()); } } } // Adds a new or replaces an existing entry with obj that has the same hash code map.put(hashCode, new WeakReference<T>(obj)); attemptCleanup(); return obj; /* * Another thread might add first. We could check this using map.replace() instead of * map.put() and then recursively call fetch(obj). But if in the meantime, garbage * collection kicks in, we might end up with a StackOverflowException. Not caching an entry * is tolerable, after all it's configurable. */ } private void attemptCleanup() { if ((putCounter.incrementAndGet() % 10000) != 0) { return; } // Lock as there is no need for concurrent cleanup and protect us, on JDK5, from // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6312056 if (cleanupLock.tryLock()) { try { cleanReclaimedMapEntries(); } finally { cleanupLock.unlock(); } } } private void cleanReclaimedMapEntries() { Iterator<Map.Entry<Integer, WeakReference<T>>> iterator = map.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry<Integer, WeakReference<T>> entry = iterator.next(); WeakReference<T> weakRef = entry.getValue(); T r = weakRef.get(); if (r == null) { iterator.remove(); } } } private boolean eq(Object p, Object q) { return (p == q || p.equals(q)); } }