123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550 |
- /*
- * 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.fonts;
-
- import java.io.BufferedInputStream;
- import java.io.File;
- import java.io.FileInputStream;
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.ObjectInputStream;
- import java.io.ObjectOutputStream;
- import java.io.OutputStream;
- import java.io.Serializable;
- import java.net.MalformedURLException;
- import java.net.URI;
- import java.net.URL;
- import java.net.URLConnection;
- import java.util.HashMap;
- import java.util.Map;
-
- import org.apache.commons.io.FileUtils;
- import org.apache.commons.io.IOUtils;
- import org.apache.commons.logging.Log;
- import org.apache.commons.logging.LogFactory;
-
- import org.apache.fop.apps.FOPException;
- import org.apache.fop.apps.io.InternalResourceResolver;
- import org.apache.fop.util.LogUtil;
-
- /**
- * Fop cache (currently only used for font info caching)
- */
- public final class FontCache implements Serializable {
-
- /**
- * Serialization Version UID. Change this value if you want to make sure the
- * user's cache file is purged after an update.
- */
- private static final long serialVersionUID = 605232520271754719L;
-
- /** logging instance */
- private static Log log = LogFactory.getLog(FontCache.class);
-
- /** FOP's user directory name */
- private static final String FOP_USER_DIR = ".fop";
-
- /** font cache file path */
- private static final String DEFAULT_CACHE_FILENAME = "fop-fonts.cache";
-
- /** has this cache been changed since it was last read? */
- private transient boolean changed = false;
-
- /** change lock */
- private final boolean[] changeLock = new boolean[1];
-
- /**
- * master mapping of font url -> font info. This needs to be a list, since a
- * TTC file may contain more than 1 font.
- */
- private Map<String, CachedFontFile> fontfileMap = null;
-
- /**
- * mapping of font url -> file modified date (for all fonts that have failed
- * to load)
- */
- private Map<String, Long> failedFontMap = null;
-
- /**
- * Default constructor
- */
- public FontCache() {
- //nop
- }
-
- private static File getUserHome() {
- return toDirectory(System.getProperty("user.home"));
- }
-
- private static File getTempDirectory() {
- return toDirectory(System.getProperty("java.io.tmpdir"));
- }
-
- private static File toDirectory(String path) {
- if (path != null) {
- File dir = new File(path);
- if (dir.exists()) {
- return dir;
- }
- }
- return null;
- }
-
- /**
- * Returns the default font cache file.
- *
- * @param forWriting
- * true if the user directory should be created
- * @return the default font cache file
- */
- public static File getDefaultCacheFile(boolean forWriting) {
- File userHome = getUserHome();
- if (userHome != null) {
- File fopUserDir = new File(userHome, FOP_USER_DIR);
- if (forWriting) {
- boolean writable = fopUserDir.canWrite();
- if (!fopUserDir.exists()) {
- writable = fopUserDir.mkdir();
- }
- if (!writable) {
- userHome = getTempDirectory();
- fopUserDir = new File(userHome, FOP_USER_DIR);
- fopUserDir.mkdir();
- }
- }
- return new File(fopUserDir, DEFAULT_CACHE_FILENAME);
- }
- return new File(FOP_USER_DIR);
- }
-
- /**
- * Reads the default font cache file and returns its contents.
- *
- * @return the font cache deserialized from the file (or null if no cache
- * file exists or if it could not be read)
- */
- public static FontCache load() {
- return loadFrom(getDefaultCacheFile(false));
- }
-
- /**
- * Reads a font cache file and returns its contents.
- *
- * @param cacheFile
- * the cache file
- * @return the font cache deserialized from the file (or null if no cache
- * file exists or if it could not be read)
- */
- public static FontCache loadFrom(File cacheFile) {
- if (cacheFile.exists()) {
- try {
- if (log.isTraceEnabled()) {
- log.trace("Loading font cache from "
- + cacheFile.getCanonicalPath());
- }
- InputStream in = new BufferedInputStream(new FileInputStream(cacheFile));
- ObjectInputStream oin = new ObjectInputStream(in);
- try {
- return (FontCache) oin.readObject();
- } finally {
- IOUtils.closeQuietly(oin);
- }
- } catch (ClassNotFoundException e) {
- // We don't really care about the exception since it's just a
- // cache file
- log.warn("Could not read font cache. Discarding font cache file. Reason: "
- + e.getMessage());
- } catch (IOException ioe) {
- // We don't really care about the exception since it's just a
- // cache file
- log.warn("I/O exception while reading font cache ("
- + ioe.getMessage() + "). Discarding font cache file.");
- try {
- cacheFile.delete();
- } catch (SecurityException ex) {
- log.warn("Failed to delete font cache file: "
- + cacheFile.getAbsolutePath());
- }
- }
- }
- return null;
- }
-
- /**
- * Writes the font cache to disk.
- *
- * @throws FOPException
- * fop exception
- */
- public void save() throws FOPException {
- saveTo(getDefaultCacheFile(true));
- }
-
- /**
- * Writes the font cache to disk.
- *
- * @param cacheFile
- * the file to write to
- * @throws FOPException
- * fop exception
- */
- public void saveTo(File cacheFile) throws FOPException {
- synchronized (changeLock) {
- if (changed) {
- try {
- log.trace("Writing font cache to " + cacheFile.getCanonicalPath());
- OutputStream out = new java.io.FileOutputStream(cacheFile);
- out = new java.io.BufferedOutputStream(out);
- ObjectOutputStream oout = new ObjectOutputStream(out);
- try {
- oout.writeObject(this);
- } finally {
- IOUtils.closeQuietly(oout);
- }
- } catch (IOException ioe) {
- LogUtil.handleException(log, ioe, true);
- }
- changed = false;
- log.trace("Cache file written.");
- }
- }
- }
-
- /**
- * creates a key given a font info for the font mapping
- *
- * @param fontInfo
- * font info
- * @return font cache key
- */
- protected static String getCacheKey(EmbedFontInfo fontInfo) {
- if (fontInfo != null) {
- URI embedFile = fontInfo.getEmbedURI();
- URI metricsFile = fontInfo.getMetricsURI();
- return (embedFile != null) ? embedFile.toASCIIString() : metricsFile.toASCIIString();
- }
- return null;
- }
-
- /**
- * cache has been updated since it was read
- *
- * @return if this cache has changed
- */
- public boolean hasChanged() {
- return this.changed;
- }
-
- /**
- * is this font in the cache?
- *
- * @param embedUrl
- * font info
- * @return boolean
- */
- public boolean containsFont(String embedUrl) {
- return (embedUrl != null && getFontFileMap().containsKey(embedUrl));
- }
-
- /**
- * is this font info in the cache?
- *
- * @param fontInfo
- * font info
- * @return font
- */
- public boolean containsFont(EmbedFontInfo fontInfo) {
- return (fontInfo != null && getFontFileMap().containsKey(
- getCacheKey(fontInfo)));
- }
-
- /**
- * Tries to identify a File instance from an array of URLs. If there's no
- * file URL in the array, the method returns null.
- *
- * @param urls
- * array of possible font urls
- * @return file font file
- */
- public static File getFileFromUrls(String[] urls) {
- for (int i = 0; i < urls.length; i++) {
- String urlStr = urls[i];
- if (urlStr != null) {
- File fontFile = null;
- if (urlStr.startsWith("file:")) {
- try {
- URL url = new URL(urlStr);
- fontFile = FileUtils.toFile(url);
- } catch (MalformedURLException mfue) {
- // do nothing
- }
- }
- if (fontFile == null) {
- fontFile = new File(urlStr);
- }
- if (fontFile.exists() && fontFile.canRead()) {
- return fontFile;
- }
- }
- }
- return null;
- }
-
- private Map<String, CachedFontFile> getFontFileMap() {
- if (fontfileMap == null) {
- fontfileMap = new HashMap<String, CachedFontFile>();
- }
- return fontfileMap;
- }
-
- /**
- * Adds a font info to cache
- *
- * @param fontInfo
- * font info
- */
- public void addFont(EmbedFontInfo fontInfo, InternalResourceResolver resourceResolver) {
- String cacheKey = getCacheKey(fontInfo);
- synchronized (changeLock) {
- CachedFontFile cachedFontFile;
- if (containsFont(cacheKey)) {
- cachedFontFile = getFontFileMap().get(cacheKey);
- if (!cachedFontFile.containsFont(fontInfo)) {
- cachedFontFile.put(fontInfo);
- }
- } else {
- // try and determine modified date
- URI fontUri = resourceResolver.resolveFromBase(fontInfo.getEmbedURI());
- File fontFile = new File(fontUri);
- long lastModified = fontFile.lastModified();
- cachedFontFile = new CachedFontFile(lastModified);
- if (log.isTraceEnabled()) {
- log.trace("Font added to cache: " + cacheKey);
- }
- cachedFontFile.put(fontInfo);
- getFontFileMap().put(cacheKey, cachedFontFile);
- changed = true;
- }
- }
- }
-
- /**
- * Returns a font from the cache.
- *
- * @param embedUrl
- * font info
- * @return CachedFontFile object
- */
- public CachedFontFile getFontFile(String embedUrl) {
- return containsFont(embedUrl) ? getFontFileMap().get(embedUrl) : null;
- }
-
- /**
- * Returns the EmbedFontInfo instances belonging to a font file. If the font
- * file was modified since it was cached the entry is removed and null is
- * returned.
- *
- * @param embedUrl
- * the font URL
- * @param lastModified
- * the last modified date/time of the font file
- * @return the EmbedFontInfo instances or null if there's no cached entry or
- * if it is outdated
- */
- public EmbedFontInfo[] getFontInfos(String embedUrl, long lastModified) {
- CachedFontFile cff = getFontFile(embedUrl);
- if (cff.lastModified() == lastModified) {
- return cff.getEmbedFontInfos();
- } else {
- removeFont(embedUrl);
- return null;
- }
- }
-
- /**
- * removes font from cache
- *
- * @param embedUrl
- * embed url
- */
- public void removeFont(String embedUrl) {
- synchronized (changeLock) {
- if (containsFont(embedUrl)) {
- if (log.isTraceEnabled()) {
- log.trace("Font removed from cache: " + embedUrl);
- }
- getFontFileMap().remove(embedUrl);
- changed = true;
- }
- }
- }
-
- /**
- * has this font previously failed to load?
- *
- * @param embedUrl
- * embed url
- * @param lastModified
- * last modified
- * @return whether this is a failed font
- */
- public boolean isFailedFont(String embedUrl, long lastModified) {
- synchronized (changeLock) {
- if (getFailedFontMap().containsKey(embedUrl)) {
- long failedLastModified = getFailedFontMap().get(
- embedUrl).longValue();
- if (lastModified != failedLastModified) {
- // this font has been changed so lets remove it
- // from failed font map for now
- getFailedFontMap().remove(embedUrl);
- changed = true;
- }
- return true;
- } else {
- return false;
- }
- }
- }
-
- /**
- * Registers a failed font with the cache
- *
- * @param embedUrl
- * embed url
- * @param lastModified
- * time last modified
- */
- public void registerFailedFont(String embedUrl, long lastModified) {
- synchronized (changeLock) {
- if (!getFailedFontMap().containsKey(embedUrl)) {
- getFailedFontMap().put(embedUrl, new Long(lastModified));
- changed = true;
- }
- }
- }
-
- private Map<String, Long> getFailedFontMap() {
- if (failedFontMap == null) {
- failedFontMap = new HashMap<String, Long>();
- }
- return failedFontMap;
- }
-
- /**
- * Clears font cache
- */
- public void clear() {
- synchronized (changeLock) {
- if (log.isTraceEnabled()) {
- log.trace("Font cache cleared.");
- }
- fontfileMap = null;
- failedFontMap = null;
- changed = true;
- }
- }
-
- /**
- * Retrieve the last modified date/time of a URL.
- *
- * @param url
- * the URL
- * @return the last modified date/time
- */
- public static long getLastModified(URI uri) {
- try {
- URL url = uri.toURL();
- URLConnection conn = url.openConnection();
- try {
- return conn.getLastModified();
- } finally {
- // An InputStream is created even if it's not accessed, but we
- // need to close it.
- IOUtils.closeQuietly(conn.getInputStream());
- }
- } catch (IOException e) {
- // Should never happen, because URL must be local
- log.debug("IOError: " + e.getMessage());
- return 0;
- }
- }
-
- private static class CachedFontFile implements Serializable {
- private static final long serialVersionUID = 4524237324330578883L;
-
- /** file modify date (if available) */
- private long lastModified = -1;
-
- private Map<String, EmbedFontInfo> filefontsMap = null;
-
- public CachedFontFile(long lastModified) {
- setLastModified(lastModified);
- }
-
- private Map<String, EmbedFontInfo> getFileFontsMap() {
- if (filefontsMap == null) {
- filefontsMap = new HashMap<String, EmbedFontInfo>();
- }
- return filefontsMap;
- }
-
- void put(EmbedFontInfo efi) {
- getFileFontsMap().put(efi.getPostScriptName(), efi);
- }
-
- public boolean containsFont(EmbedFontInfo efi) {
- return efi.getPostScriptName() != null
- && getFileFontsMap().containsKey(efi.getPostScriptName());
- }
-
- public EmbedFontInfo[] getEmbedFontInfos() {
- return getFileFontsMap().values().toArray(
- new EmbedFontInfo[getFileFontsMap().size()]);
- }
-
- /**
- * Gets the modified timestamp for font file (not always available)
- *
- * @return modified timestamp
- */
- public long lastModified() {
- return this.lastModified;
- }
-
- /**
- * Gets the modified timestamp for font file (used for the purposes of
- * font info caching)
- *
- * @param lastModified
- * modified font file timestamp
- */
- public void setLastModified(long lastModified) {
- this.lastModified = lastModified;
- }
-
- /**
- * @return string representation of this object {@inheritDoc}
- */
- public String toString() {
- return super.toString() + ", lastModified=" + lastModified;
- }
-
- }
- }
|