You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

PropertyCache.java 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one or more
  3. * contributor license agreements. See the NOTICE file distributed with
  4. * this work for additional information regarding copyright ownership.
  5. * The ASF licenses this file to You under the Apache License, Version 2.0
  6. * (the "License"); you may not use this file except in compliance with
  7. * the License. You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. /* $Id$ */
  18. package org.apache.fop.fo.properties;
  19. import java.lang.ref.ReferenceQueue;
  20. import java.lang.ref.WeakReference;
  21. /**
  22. * Dedicated cache, meant for storing canonical instances
  23. * of property-related classes.
  24. * The public access points are overloaded <code>fetch()</code> methods
  25. * that each correspond to a cached type.
  26. * It is designed especially to be used concurrently by multiple threads,
  27. * drawing heavily upon the principles behind Java 1.5's
  28. * <code>ConcurrentHashMap</code>.
  29. */
  30. public final class PropertyCache {
  31. private static final int SEGMENT_COUNT = 32; //0x20
  32. private static final int INITIAL_BUCKET_COUNT = SEGMENT_COUNT;
  33. /** bitmask to apply to the hash to get to the
  34. * corresponding cache segment */
  35. private static final int SEGMENT_MASK = SEGMENT_COUNT - 1; //0x1F
  36. /**
  37. * Indicates whether the cache should be used at all
  38. * Can be controlled by the system property:
  39. * org.apache.fop.fo.properties.use-cache
  40. */
  41. private final boolean useCache;
  42. /** the segments array (length = 32) */
  43. private CacheSegment[] segments = new CacheSegment[SEGMENT_COUNT];
  44. /** the table of hash-buckets */
  45. private CacheEntry[] table = new CacheEntry[INITIAL_BUCKET_COUNT];
  46. private Class runtimeType;
  47. private boolean[] votesForRehash = new boolean[SEGMENT_COUNT];
  48. /* same hash function as used by java.util.HashMap */
  49. private static int hash(Object x) {
  50. return hash(x.hashCode());
  51. }
  52. private static int hash(int hashCode) {
  53. int h = hashCode;
  54. h += ~(h << 9);
  55. h ^= (h >>> 14);
  56. h += (h << 4);
  57. h ^= (h >>> 10);
  58. return h;
  59. }
  60. /* shortcut function */
  61. private static boolean eq(Object p, Object q) {
  62. return (p == q || (p != null && p.equals(q)));
  63. }
  64. /* Class modeling a cached entry */
  65. private static class CacheEntry extends WeakReference {
  66. private volatile CacheEntry nextEntry;
  67. private final int hash;
  68. /* main constructor */
  69. public CacheEntry(Object p, CacheEntry nextEntry, ReferenceQueue refQueue) {
  70. super(p, refQueue);
  71. this.nextEntry = nextEntry;
  72. this.hash = hash(p);
  73. }
  74. /* main constructor */
  75. public CacheEntry(Object p, CacheEntry nextEntry) {
  76. super(p);
  77. this.nextEntry = nextEntry;
  78. this.hash = hash(p);
  79. }
  80. }
  81. /* Wrapper objects to synchronize on */
  82. private static class CacheSegment {
  83. private int count = 0;
  84. }
  85. private void cleanSegment(int segmentIndex) {
  86. CacheSegment segment = segments[segmentIndex];
  87. int oldCount = segment.count;
  88. /* clean all buckets in this segment */
  89. for (int bucketIndex = segmentIndex;
  90. bucketIndex < table.length;
  91. bucketIndex += SEGMENT_COUNT) {
  92. CacheEntry prev = null;
  93. CacheEntry entry = table[bucketIndex];
  94. if (entry == null) {
  95. continue;
  96. }
  97. do {
  98. if (entry.get() == null) {
  99. if (prev == null) {
  100. table[bucketIndex] = entry.nextEntry;
  101. } else {
  102. prev.nextEntry = entry.nextEntry;
  103. }
  104. segment.count--;
  105. assert segment.count >= 0;
  106. } else {
  107. prev = entry;
  108. }
  109. entry = entry.nextEntry;
  110. } while (entry != null);
  111. }
  112. synchronized (votesForRehash) {
  113. if (oldCount > segment.count) {
  114. votesForRehash[segmentIndex] = false;
  115. return;
  116. }
  117. /* cleanup had no effect */
  118. if (!votesForRehash[segmentIndex]) {
  119. /* first time for this segment */
  120. votesForRehash[segmentIndex] = true;
  121. int voteCount = 0;
  122. for (int i = SEGMENT_MASK + 1; --i >= 0;) {
  123. if (votesForRehash[i]) {
  124. voteCount++;
  125. }
  126. }
  127. if (voteCount > SEGMENT_MASK / 4) {
  128. rehash(SEGMENT_MASK);
  129. /* reset votes */
  130. for (int i = SEGMENT_MASK + 1; --i >= 0;) {
  131. votesForRehash[i] = false;
  132. }
  133. }
  134. }
  135. }
  136. }
  137. /*
  138. * Puts a new instance in the cache.
  139. * If the total number of entries for the corresponding
  140. * segment exceeds twice the amount of hash-buckets, a
  141. * cleanup will be performed to try and remove obsolete
  142. * entries.
  143. */
  144. private void put(Object o) {
  145. int hash = hash(o);
  146. int segmentIndex = hash & SEGMENT_MASK;
  147. CacheSegment segment = segments[segmentIndex];
  148. synchronized (segment) {
  149. int index = hash & (table.length - 1);
  150. CacheEntry entry = table[index];
  151. if (entry == null) {
  152. entry = new CacheEntry(o, null);
  153. table[index] = entry;
  154. segment.count++;
  155. } else {
  156. Object p = entry.get();
  157. if (eq(p, o)) {
  158. return;
  159. } else {
  160. CacheEntry newEntry = new CacheEntry(o, entry);
  161. table[index] = newEntry;
  162. segment.count++;
  163. }
  164. }
  165. if (segment.count > (2 * table.length)) {
  166. cleanSegment(segmentIndex);
  167. }
  168. }
  169. }
  170. /* Gets a cached instance. Returns null if not found */
  171. private Object get(Object o) {
  172. int hash = hash(o);
  173. int index = hash & (table.length - 1);
  174. CacheEntry entry = table[index];
  175. Object q;
  176. /* try non-synched first */
  177. for (CacheEntry e = entry; e != null; e = e.nextEntry) {
  178. if (e.hash == hash
  179. && (q = e.get()) != null
  180. && eq(q, o)) {
  181. return q;
  182. }
  183. }
  184. /* retry synched, only if the above attempt did not succeed,
  185. * as another thread may, in the meantime, have added a
  186. * corresponding entry */
  187. CacheSegment segment = segments[hash & SEGMENT_MASK];
  188. synchronized (segment) {
  189. entry = table[index];
  190. for (CacheEntry e = entry; e != null; e = e.nextEntry) {
  191. if (e.hash == hash
  192. && (q = e.get()) != null
  193. && eq(q, o)) {
  194. return q;
  195. }
  196. }
  197. }
  198. return null;
  199. }
  200. /*
  201. * Recursively acquires locks on all 32 segments,
  202. * extends the cache and redistributes the entries.
  203. *
  204. */
  205. private void rehash(int index) {
  206. CacheSegment seg = segments[index];
  207. synchronized (seg) {
  208. if (index > 0) {
  209. /* need to recursively acquire locks on all segments */
  210. rehash(index - 1);
  211. } else {
  212. /* double the amount of buckets */
  213. int newLength = table.length << 1;
  214. if (newLength > 0) { //no overflow?
  215. /* reset segment counts */
  216. for (int i = segments.length; --i >= 0;) {
  217. segments[i].count = 0;
  218. }
  219. CacheEntry[] newTable = new CacheEntry[newLength];
  220. int hash, idx;
  221. Object o;
  222. newLength--;
  223. for (int i = table.length; --i >= 0;) {
  224. for (CacheEntry c = table[i]; c != null; c = c.nextEntry) {
  225. if ((o = c.get()) != null) {
  226. hash = c.hash;
  227. idx = hash & newLength;
  228. newTable[idx] = new CacheEntry(o, newTable[idx]);
  229. segments[hash & SEGMENT_MASK].count++;
  230. }
  231. }
  232. }
  233. table = newTable;
  234. }
  235. }
  236. }
  237. }
  238. /**
  239. * Default constructor.
  240. *
  241. * @param c Runtime type of the objects that will be stored in the cache
  242. */
  243. public PropertyCache(Class c) {
  244. this.useCache = Boolean.valueOf(System.getProperty(
  245. "org.apache.fop.fo.properties.use-cache", "true")
  246. ).booleanValue();
  247. if (useCache) {
  248. for (int i = SEGMENT_MASK + 1; --i >= 0;) {
  249. segments[i] = new CacheSegment();
  250. }
  251. }
  252. this.runtimeType = c;
  253. }
  254. /**
  255. * Generic fetch() method.
  256. * Checks if the given <code>Object</code> is present in the cache -
  257. * if so, returns a reference to the cached instance.
  258. * Otherwise the given object is added to the cache and returned.
  259. *
  260. * @param obj the Object to check for
  261. * @return the cached instance
  262. */
  263. private Object fetch(Object obj) {
  264. if (!this.useCache) {
  265. return obj;
  266. }
  267. if (obj == null) {
  268. return null;
  269. }
  270. Object cacheEntry = get(obj);
  271. if (cacheEntry != null) {
  272. return cacheEntry;
  273. }
  274. put(obj);
  275. return obj;
  276. }
  277. /**
  278. * Checks if the given {@link Property} is present in the cache -
  279. * if so, returns a reference to the cached instance.
  280. * Otherwise the given object is added to the cache and returned.
  281. *
  282. * @param prop the Property instance to check for
  283. * @return the cached instance
  284. */
  285. public Property fetch(Property prop) {
  286. return (Property) fetch((Object) prop);
  287. }
  288. /**
  289. * Checks if the given {@link CommonHyphenation} is present in the cache -
  290. * if so, returns a reference to the cached instance.
  291. * Otherwise the given object is added to the cache and returned.
  292. *
  293. * @param chy the CommonHyphenation instance to check for
  294. * @return the cached instance
  295. */
  296. public CommonHyphenation fetch(CommonHyphenation chy) {
  297. return (CommonHyphenation) fetch((Object) chy);
  298. }
  299. /**
  300. * Checks if the given {@link CommonFont} is present in the cache -
  301. * if so, returns a reference to the cached instance.
  302. * Otherwise the given object is added to the cache and returned.
  303. *
  304. * @param cf the CommonFont instance to check for
  305. * @return the cached instance
  306. */
  307. public CommonFont fetch(CommonFont cf) {
  308. return (CommonFont) fetch((Object) cf);
  309. }
  310. /**
  311. * Checks if the given {@link CommonBorderPaddingBackground} is present in the cache -
  312. * if so, returns a reference to the cached instance.
  313. * Otherwise the given object is added to the cache and returned.
  314. *
  315. * @param cbpb the CommonBorderPaddingBackground instance to check for
  316. * @return the cached instance
  317. */
  318. public CommonBorderPaddingBackground fetch(CommonBorderPaddingBackground cbpb) {
  319. return (CommonBorderPaddingBackground) fetch((Object) cbpb);
  320. }
  321. /**
  322. * Checks if the given {@link CommonBorderPaddingBackground.BorderInfo} is present
  323. * in the cache - if so, returns a reference to the cached instance.
  324. * Otherwise the given object is added to the cache and returned.
  325. *
  326. * @param bi the BorderInfo instance to check for
  327. * @return the cached instance
  328. */
  329. public CommonBorderPaddingBackground.BorderInfo fetch(
  330. CommonBorderPaddingBackground.BorderInfo bi) {
  331. return (CommonBorderPaddingBackground.BorderInfo) fetch((Object) bi);
  332. }
  333. /** {@inheritDoc} */
  334. public String toString() {
  335. return super.toString() + "[runtimeType=" + this.runtimeType + "]";
  336. }
  337. }