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.

FontCache.java 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  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.fonts;
  19. import java.io.File;
  20. import java.io.IOException;
  21. import java.io.InputStream;
  22. import java.io.ObjectInputStream;
  23. import java.io.ObjectOutputStream;
  24. import java.io.OutputStream;
  25. import java.io.Serializable;
  26. import java.util.Map;
  27. import org.apache.commons.io.IOUtils;
  28. import org.apache.commons.logging.Log;
  29. import org.apache.commons.logging.LogFactory;
  30. import org.apache.fop.apps.FOPException;
  31. import org.apache.fop.util.LogUtil;
  32. /**
  33. * Fop cache (currently only used for font info caching)
  34. */
  35. public final class FontCache implements Serializable {
  36. /** Serialization Version UID */
  37. private static final long serialVersionUID = 605232520271754717L;
  38. /** logging instance */
  39. private static Log log = LogFactory.getLog(FontCache.class);
  40. /** FOP's user directory name */
  41. private static final String FOP_USER_DIR = ".fop";
  42. /** font cache file path */
  43. private static final String DEFAULT_CACHE_FILENAME = "fop-fonts.cache";
  44. /** has this cache been changed since it was last read? */
  45. private transient boolean changed = false;
  46. /** change lock */
  47. private transient Object changeLock = new Object();
  48. /** master mapping of font url -> font info */
  49. private Map fontMap = new java.util.HashMap();
  50. /** mapping of font url -> file modified date */
  51. private Map failedFontMap = new java.util.HashMap();
  52. /**
  53. * Default constructor
  54. */
  55. public FontCache() {
  56. //nop
  57. }
  58. private void readObject(java.io.ObjectInputStream in)
  59. throws IOException, ClassNotFoundException {
  60. in.defaultReadObject();
  61. this.changeLock = new Object(); //Initialize transient field
  62. }
  63. private static File getUserHome() {
  64. String s = System.getProperty("user.home");
  65. if (s != null) {
  66. File userDir = new File(s);
  67. if (userDir.exists()) {
  68. return userDir;
  69. }
  70. }
  71. return null;
  72. }
  73. /**
  74. * Returns the default font cache file.
  75. * @param forWriting true if the user directory should be created
  76. * @return the default font cache file
  77. */
  78. public static File getDefaultCacheFile(boolean forWriting) {
  79. File userHome = getUserHome();
  80. if (userHome != null) {
  81. File fopUserDir = new File(userHome, FOP_USER_DIR);
  82. if (forWriting) {
  83. fopUserDir.mkdir();
  84. }
  85. return new File(fopUserDir, DEFAULT_CACHE_FILENAME);
  86. }
  87. return new File(FOP_USER_DIR);
  88. }
  89. /**
  90. * Reads the default font cache file and returns its contents.
  91. * @return the font cache deserialized from the file (or null if no cache file exists or if
  92. * it could not be read)
  93. */
  94. public static FontCache load() {
  95. return loadFrom(getDefaultCacheFile(false));
  96. }
  97. /**
  98. * Reads a font cache file and returns its contents.
  99. * @param cacheFile the cache file
  100. * @return the font cache deserialized from the file (or null if no cache file exists or if
  101. * it could not be read)
  102. */
  103. public static FontCache loadFrom(File cacheFile) {
  104. if (cacheFile.exists()) {
  105. try {
  106. if (log.isTraceEnabled()) {
  107. log.trace("Loading font cache from " + cacheFile.getCanonicalPath());
  108. }
  109. InputStream in = new java.io.FileInputStream(cacheFile);
  110. in = new java.io.BufferedInputStream(in);
  111. ObjectInputStream oin = new ObjectInputStream(in);
  112. try {
  113. return (FontCache)oin.readObject();
  114. } finally {
  115. IOUtils.closeQuietly(oin);
  116. }
  117. } catch (ClassNotFoundException e) {
  118. //We don't really care about the exception since it's just a cache file
  119. log.warn("Could not read font cache. Discarding font cache file. Reason: "
  120. + e.getMessage());
  121. } catch (IOException ioe) {
  122. //We don't really care about the exception since it's just a cache file
  123. log.warn("I/O exception while reading font cache (" + ioe.getMessage()
  124. + "). Discarding font cache file.");
  125. }
  126. }
  127. return null;
  128. }
  129. /**
  130. * Writes the font cache to disk.
  131. * @throws FOPException fop exception
  132. */
  133. public void save() throws FOPException {
  134. saveTo(getDefaultCacheFile(true));
  135. }
  136. /**
  137. * Writes the font cache to disk.
  138. * @param cacheFile the file to write to
  139. * @throws FOPException fop exception
  140. */
  141. public void saveTo(File cacheFile) throws FOPException {
  142. synchronized (changeLock) {
  143. if (changed) {
  144. try {
  145. if (log.isTraceEnabled()) {
  146. log.trace("Writing font cache to " + cacheFile.getCanonicalPath());
  147. }
  148. OutputStream out = new java.io.FileOutputStream(cacheFile);
  149. out = new java.io.BufferedOutputStream(out);
  150. ObjectOutputStream oout = new ObjectOutputStream(out);
  151. try {
  152. oout.writeObject(this);
  153. } finally {
  154. IOUtils.closeQuietly(oout);
  155. }
  156. } catch (IOException ioe) {
  157. LogUtil.handleException(log, ioe, true);
  158. }
  159. changed = false;
  160. log.trace("Cache file written.");
  161. }
  162. }
  163. }
  164. /**
  165. * creates a key given a font info for the font mapping
  166. * @param fontInfo font info
  167. * @return font cache key
  168. */
  169. protected static String getCacheKey(EmbedFontInfo fontInfo) {
  170. if (fontInfo != null) {
  171. String embedFile = fontInfo.getEmbedFile();
  172. String metricsFile = fontInfo.getMetricsFile();
  173. return (embedFile != null) ? embedFile : metricsFile;
  174. }
  175. return null;
  176. }
  177. /**
  178. * cache has been updated since it was read
  179. * @return if this cache has changed
  180. */
  181. public boolean hasChanged() {
  182. return this.changed;
  183. }
  184. /**
  185. * is this font in the cache?
  186. * @param embedUrl font info
  187. * @return boolean
  188. */
  189. public boolean containsFont(String embedUrl) {
  190. if (embedUrl != null) {
  191. return fontMap.containsKey(embedUrl);
  192. }
  193. return false;
  194. }
  195. /**
  196. * is this font info in the cache?
  197. * @param fontInfo font info
  198. * @return font
  199. */
  200. public boolean containsFont(EmbedFontInfo fontInfo) {
  201. if (fontInfo != null) {
  202. return fontMap.containsKey(getCacheKey(fontInfo));
  203. }
  204. return false;
  205. }
  206. /**
  207. * adds a font info to cache
  208. * @param fontInfo font info
  209. */
  210. public void addFont(EmbedFontInfo fontInfo) {
  211. String cacheKey = getCacheKey(fontInfo);
  212. synchronized (changeLock) {
  213. if (!containsFont(cacheKey)) {
  214. if (log.isTraceEnabled()) {
  215. log.trace("Font added to cache: " + cacheKey);
  216. }
  217. if (fontInfo instanceof CachedFontInfo) {
  218. fontMap.put(cacheKey, fontInfo);
  219. } else {
  220. fontMap.put(cacheKey, new CachedFontInfo(fontInfo));
  221. }
  222. changed = true;
  223. }
  224. }
  225. }
  226. /**
  227. * returns a font from the cache
  228. * @param embedUrl font info
  229. * @return boolean
  230. */
  231. public CachedFontInfo getFont(String embedUrl) {
  232. if (containsFont(embedUrl)) {
  233. return (CachedFontInfo)fontMap.get(embedUrl);
  234. }
  235. return null;
  236. }
  237. /**
  238. * removes font from cache
  239. * @param embedUrl embed url
  240. */
  241. public void removeFont(String embedUrl) {
  242. synchronized (changeLock) {
  243. if (containsFont(embedUrl)) {
  244. if (log.isTraceEnabled()) {
  245. log.trace("Font removed from cache: " + embedUrl);
  246. }
  247. fontMap.remove(embedUrl);
  248. changed = true;
  249. }
  250. }
  251. }
  252. /**
  253. * has this font previously failed to load?
  254. * @param embedUrl embed url
  255. * @param lastModified last modified
  256. * @return whether this is a failed font
  257. */
  258. public boolean isFailedFont(String embedUrl, long lastModified) {
  259. if (failedFontMap.containsKey(embedUrl)) {
  260. synchronized (changeLock) {
  261. long failedLastModified = ((Long)failedFontMap.get(embedUrl)).longValue();
  262. if (lastModified != failedLastModified) {
  263. // this font has been changed so lets remove it
  264. // from failed font map for now
  265. failedFontMap.remove(embedUrl);
  266. changed = true;
  267. }
  268. }
  269. return true;
  270. }
  271. return false;
  272. }
  273. /**
  274. * registers a failed font with the cache
  275. * @param embedUrl embed url
  276. * @param lastModified time last modified
  277. */
  278. public void registerFailedFont(String embedUrl, long lastModified) {
  279. synchronized (changeLock) {
  280. if (!failedFontMap.containsKey(embedUrl)) {
  281. failedFontMap.put(embedUrl, new Long(lastModified));
  282. changed = true;
  283. }
  284. }
  285. }
  286. /**
  287. * Clears font cache
  288. */
  289. public void clear() {
  290. synchronized (changeLock) {
  291. if (log.isTraceEnabled()) {
  292. log.trace("Font cache cleared.");
  293. }
  294. fontMap.clear();
  295. failedFontMap.clear();
  296. changed = true;
  297. }
  298. }
  299. }