123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390 |
- /*
- * 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$ */
-
- package org.apache.fop.fo.properties;
-
- import java.lang.ref.ReferenceQueue;
- import java.lang.ref.WeakReference;
-
- /**
- * Dedicated cache, meant for storing canonical instances
- * of property-related classes.
- * The public access points are overloaded <code>fetch()</code> methods
- * that each correspond to a cached type.
- * It is designed especially to be used concurrently by multiple threads,
- * drawing heavily upon the principles behind Java 1.5's
- * <code>ConcurrentHashMap</code>.
- */
- public final class PropertyCache {
-
- private static final int SEGMENT_COUNT = 32; //0x20
- private static final int INITIAL_BUCKET_COUNT = SEGMENT_COUNT;
-
- /** bitmask to apply to the hash to get to the
- * corresponding cache segment */
- private static final int SEGMENT_MASK = SEGMENT_COUNT - 1; //0x1F
- /**
- * Indicates whether the cache should be used at all
- * Can be controlled by the system property:
- * org.apache.fop.fo.properties.use-cache
- */
- private final boolean useCache;
-
- /** the segments array (length = 32) */
- private CacheSegment[] segments = new CacheSegment[SEGMENT_COUNT];
- /** the table of hash-buckets */
- private CacheEntry[] table = new CacheEntry[INITIAL_BUCKET_COUNT];
-
- private Class runtimeType;
-
- private boolean[] votesForRehash = new boolean[SEGMENT_COUNT];
-
- /* same hash function as used by java.util.HashMap */
- private static int hash(Object x) {
- return hash(x.hashCode());
- }
-
- private static int hash(int hashCode) {
- int h = hashCode;
- h += ~(h << 9);
- h ^= (h >>> 14);
- h += (h << 4);
- h ^= (h >>> 10);
- return h;
- }
-
- /* shortcut function */
- private static boolean eq(Object p, Object q) {
- return (p == q || (p != null && p.equals(q)));
- }
-
- /* Class modeling a cached entry */
- private static class CacheEntry extends WeakReference {
- private volatile CacheEntry nextEntry;
- private final int hash;
-
- /* main constructor */
- public CacheEntry(Object p, CacheEntry nextEntry, ReferenceQueue refQueue) {
- super(p, refQueue);
- this.nextEntry = nextEntry;
- this.hash = hash(p);
- }
-
- /* main constructor */
- public CacheEntry(Object p, CacheEntry nextEntry) {
- super(p);
- this.nextEntry = nextEntry;
- this.hash = hash(p);
- }
-
- }
-
- /* Wrapper objects to synchronize on */
- private static class CacheSegment {
- private int count = 0;
- }
-
- private void cleanSegment(int segmentIndex) {
- CacheSegment segment = segments[segmentIndex];
-
- int oldCount = segment.count;
-
- /* clean all buckets in this segment */
- for (int bucketIndex = segmentIndex;
- bucketIndex < table.length;
- bucketIndex += SEGMENT_COUNT) {
- CacheEntry prev = null;
- CacheEntry entry = table[bucketIndex];
- if (entry == null) {
- continue;
- }
- do {
- if (entry.get() == null) {
- if (prev == null) {
- table[bucketIndex] = entry.nextEntry;
- } else {
- prev.nextEntry = entry.nextEntry;
- }
- segment.count--;
- assert segment.count >= 0;
- } else {
- prev = entry;
- }
- entry = entry.nextEntry;
- } while (entry != null);
- }
-
- synchronized (votesForRehash) {
- if (oldCount > segment.count) {
- votesForRehash[segmentIndex] = false;
- return;
- }
- /* cleanup had no effect */
- if (!votesForRehash[segmentIndex]) {
- /* first time for this segment */
- votesForRehash[segmentIndex] = true;
- int voteCount = 0;
- for (int i = SEGMENT_MASK + 1; --i >= 0;) {
- if (votesForRehash[i]) {
- voteCount++;
- }
- }
- if (voteCount > SEGMENT_MASK / 4) {
- rehash(SEGMENT_MASK);
- /* reset votes */
- for (int i = SEGMENT_MASK + 1; --i >= 0;) {
- votesForRehash[i] = false;
- }
-
- }
- }
- }
- }
-
- /*
- * Puts a new instance in the cache.
- * If the total number of entries for the corresponding
- * segment exceeds twice the amount of hash-buckets, a
- * cleanup will be performed to try and remove obsolete
- * entries.
- */
- private void put(Object o) {
-
- int hash = hash(o);
- int segmentIndex = hash & SEGMENT_MASK;
- CacheSegment segment = segments[segmentIndex];
-
- synchronized (segment) {
- int index = hash & (table.length - 1);
- CacheEntry entry = table[index];
-
- if (entry == null) {
- entry = new CacheEntry(o, null);
- table[index] = entry;
- segment.count++;
- } else {
- Object p = entry.get();
- if (eq(p, o)) {
- return;
- } else {
- CacheEntry newEntry = new CacheEntry(o, entry);
- table[index] = newEntry;
- segment.count++;
- }
- }
-
- if (segment.count > (2 * table.length)) {
- cleanSegment(segmentIndex);
- }
- }
- }
-
-
- /* Gets a cached instance. Returns null if not found */
- private Object get(Object o) {
-
- int hash = hash(o);
- int index = hash & (table.length - 1);
-
- CacheEntry entry = table[index];
- Object q;
-
- /* try non-synched first */
- for (CacheEntry e = entry; e != null; e = e.nextEntry) {
- if (e.hash == hash
- && (q = e.get()) != null
- && eq(q, o)) {
- return q;
- }
- }
-
- /* retry synched, only if the above attempt did not succeed,
- * as another thread may, in the meantime, have added a
- * corresponding entry */
- CacheSegment segment = segments[hash & SEGMENT_MASK];
- synchronized (segment) {
- entry = table[index];
- for (CacheEntry e = entry; e != null; e = e.nextEntry) {
- if (e.hash == hash
- && (q = e.get()) != null
- && eq(q, o)) {
- return q;
- }
- }
- }
- return null;
- }
-
- /*
- * Recursively acquires locks on all 32 segments,
- * extends the cache and redistributes the entries.
- *
- */
- private void rehash(int index) {
-
- CacheSegment seg = segments[index];
- synchronized (seg) {
- if (index > 0) {
- /* need to recursively acquire locks on all segments */
- rehash(index - 1);
- } else {
- /* double the amount of buckets */
- int newLength = table.length << 1;
- if (newLength > 0) { //no overflow?
- /* reset segment counts */
- for (int i = segments.length; --i >= 0;) {
- segments[i].count = 0;
- }
-
- CacheEntry[] newTable = new CacheEntry[newLength];
-
- int hash, idx;
- Object o;
- newLength--;
- for (int i = table.length; --i >= 0;) {
- for (CacheEntry c = table[i]; c != null; c = c.nextEntry) {
- if ((o = c.get()) != null) {
- hash = c.hash;
- idx = hash & newLength;
- newTable[idx] = new CacheEntry(o, newTable[idx]);
- segments[hash & SEGMENT_MASK].count++;
- }
- }
- }
- table = newTable;
- }
- }
- }
- }
-
- /**
- * Default constructor.
- *
- * @param c Runtime type of the objects that will be stored in the cache
- */
- public PropertyCache(Class c) {
- this.useCache = Boolean.valueOf(System.getProperty(
- "org.apache.fop.fo.properties.use-cache", "true")
- ).booleanValue();
- if (useCache) {
- for (int i = SEGMENT_MASK + 1; --i >= 0;) {
- segments[i] = new CacheSegment();
- }
- }
- this.runtimeType = c;
- }
-
- /**
- * Generic fetch() method.
- * Checks if the given <code>Object</code> is present in the cache -
- * if so, returns a reference to the cached instance.
- * Otherwise the given object is added to the cache and returned.
- *
- * @param obj the Object to check for
- * @return the cached instance
- */
- private Object fetch(Object obj) {
- if (!this.useCache) {
- return obj;
- }
-
- if (obj == null) {
- return null;
- }
-
- Object cacheEntry = get(obj);
- if (cacheEntry != null) {
- return cacheEntry;
- }
- put(obj);
- return obj;
- }
-
- /**
- * Checks if the given {@link Property} is present in the cache -
- * if so, returns a reference to the cached instance.
- * Otherwise the given object is added to the cache and returned.
- *
- * @param prop the Property instance to check for
- * @return the cached instance
- */
- public Property fetch(Property prop) {
-
- return (Property) fetch((Object) prop);
- }
-
- /**
- * Checks if the given {@link CommonHyphenation} is present in the cache -
- * if so, returns a reference to the cached instance.
- * Otherwise the given object is added to the cache and returned.
- *
- * @param chy the CommonHyphenation instance to check for
- * @return the cached instance
- */
- public CommonHyphenation fetch(CommonHyphenation chy) {
-
- return (CommonHyphenation) fetch((Object) chy);
- }
-
- /**
- * Checks if the given {@link CommonFont} is present in the cache -
- * if so, returns a reference to the cached instance.
- * Otherwise the given object is added to the cache and returned.
- *
- * @param cf the CommonFont instance to check for
- * @return the cached instance
- */
- public CommonFont fetch(CommonFont cf) {
-
- return (CommonFont) fetch((Object) cf);
- }
-
- /**
- * Checks if the given {@link CommonBorderPaddingBackground} is present in the cache -
- * if so, returns a reference to the cached instance.
- * Otherwise the given object is added to the cache and returned.
- *
- * @param cbpb the CommonBorderPaddingBackground instance to check for
- * @return the cached instance
- */
- public CommonBorderPaddingBackground fetch(CommonBorderPaddingBackground cbpb) {
-
- return (CommonBorderPaddingBackground) fetch((Object) cbpb);
- }
-
- /**
- * Checks if the given {@link CommonBorderPaddingBackground.BorderInfo} is present
- * in the cache - if so, returns a reference to the cached instance.
- * Otherwise the given object is added to the cache and returned.
- *
- * @param bi the BorderInfo instance to check for
- * @return the cached instance
- */
- public CommonBorderPaddingBackground.BorderInfo fetch(
- CommonBorderPaddingBackground.BorderInfo bi) {
- return (CommonBorderPaddingBackground.BorderInfo) fetch((Object) bi);
- }
-
- /** {@inheritDoc} */
- public String toString() {
- return super.toString() + "[runtimeType=" + this.runtimeType + "]";
- }
-
-
- }
|