Index: src/org/apache/fop/image/JAIImage.java
===================================================================
--- src/org/apache/fop/image/JAIImage.java (revision 216159)
+++ src/org/apache/fop/image/JAIImage.java (working copy)
@@ -51,23 +51,21 @@
package org.apache.fop.image;
// Java
-import java.net.URL;
-import java.io.InputStream;
-
-// AWT
+import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.IndexColorModel;
-import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
-// JAI
import javax.media.jai.JAI;
import javax.media.jai.RenderedOp;
-// Sun codec
-import com.sun.media.jai.codec.FileCacheSeekableStream;
-// FOP
+
import org.apache.fop.datatypes.ColorSpace;
-import org.apache.fop.pdf.PDFColor;
import org.apache.fop.image.analyser.ImageReader;
+import org.apache.fop.pdf.PDFColor;
+
+import com.sun.media.jai.codec.FileCacheSeekableStream;
/**
* FopImage object using JAI.
@@ -87,7 +85,13 @@
protected void loadImage() throws FopImageException {
try {
- InputStream inputStream = this.m_href.openStream();
+ // BUGFIX: do always close the inputstream (the garbage collector may do this
+ // too late when handling pdfs with many images!!)
+ InputStream inputStream=null;
+ try
+ {
+ inputStream = this.m_href.openStream();
+
/*
* BufferedInputStream inputStream = this.m_imageReader.getInputStream();
* inputStream.reset();
@@ -180,6 +184,21 @@
& 0xFF);
}
}
+ }
+ finally
+ {
+ if (inputStream!=null)
+ {
+ try
+ {
+ inputStream.close();
+ }
+ catch(IOException e)
+ {
+ // can be ignored
+ }
+ }
+ }
} catch (Exception ex) {
throw new FopImageException("Error while loading image "
Index: src/org/apache/fop/image/FopImageFactory.java
===================================================================
--- src/org/apache/fop/image/FopImageFactory.java (revision 216159)
+++ src/org/apache/fop/image/FopImageFactory.java (working copy)
@@ -51,28 +51,110 @@
package org.apache.fop.image;
// FOP
-import org.apache.fop.image.analyser.ImageReaderFactory;
-import org.apache.fop.image.analyser.ImageReader;
-import org.apache.fop.configuration.Configuration;
-import org.apache.fop.messaging.MessageHandler;
-
-// Java
import java.io.IOException;
import java.io.InputStream;
-import java.net.URL;
-import java.net.MalformedURLException;
import java.lang.reflect.Constructor;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
import java.util.Map;
+import org.apache.fop.configuration.Configuration;
+import org.apache.fop.image.analyser.ImageReader;
+import org.apache.fop.image.analyser.ImageReaderFactory;
+import org.apache.fop.messaging.MessageHandler;
+
/**
* create FopImage objects (with a configuration file - not yet implemented).
+ *
+ * This image cache does now support automatic clearing of the cache, if there
+ * are too many images inside the cache. This behaviour can be controlled by
+ * the following fop configuration parameters (stored e.g. with
+ * org.apache.fop.configuration.Configuration.put(param,value)
):
+ *
+ *
+ * - imagecache_max: the maximum count of image elements. If the cache exeeds
+ * this size, it will be shrinked to the imagecache_min size.
+ *
+ *
+ * Default: 100
+ *
+ *
+ *
+ * - imagecache_max_size: the maximum size in bytes of the image cache. If the cache exeeds
+ * this size, it will be shrinked to the imagecache_min_size size.
+ *
+ *
+ * Default: 2097152 (2Mbyte)
+ *
+ *
+ *
+ * -
+ *
- imagecache_min: the minimum count of image elements. If the cache exeeds
+ * the max size, it will be shrinked to this size.
+ *
+ *
+ * Default: 25
+ *
+ *
+ *
+ * - imagecache_min_size: the minimum size in bytes of the image cache. If the cache exeeds
+ * the max size, it will be shrinked to the imagecache_min_size size.
+ *
+ *
+ * Default: 1048576 (1MByte)
+ *
+ *
+ *
+ * -
+ *
- imagecache_debug: (true|false) if true, detailled cache information will be logged.
+ *
+ *
+ * Default: false
+ *
+ *
+ *
+ *
+ * Note (1): if the max parameter is 0, the cache will not be cleared automatically
+ * (same behaviour as before).
+ *
+ *
+ * Note (2): the parameters must be stored as strings, because the Configuration object
+ * returns Integer object as -1!
+ *
* @author Eric SCHAEFFER
*/
public class FopImageFactory {
+
+ // prevent instantiation
+ protected FopImageFactory()
+ {
// prevent instantiation
- protected FopImageFactory() {}
+ }
- private static Map m_urlMap = new java.util.HashMap();
+ // contains the keys which point to the cache entries
+ private static Map m_urlMap=new LinkedHashMap(16, 0.75f, true);
+
+ /**
+ * The minimum cache size.
+ */
+ private static int DEFAULT_MIN=25;
+
+ /**
+ * The maximum cache size.
+ */
+ private static int DEFAULT_MAX=100;
+
+ /**
+ * The mimimum image cache size in bytes.
+ */
+ private static long DEFAULT_MIN_SIZE=1048576;
+
+ /**
+ * The maximum image cache size in bytes.
+ */
+ private static long DEFAULT_MAX_SIZE=2097152;
/**
* The class name of the generic image handler.
@@ -112,9 +194,34 @@
}
// check if already created
- FopImage imageObject = (FopImage)m_urlMap.get(href);
- if (imageObject != null)
- return imageObject;
+ // use the image cache (the map will notice the access)
+ ImageCacheEntry cacheentry=(ImageCacheEntry)m_urlMap.get(href);
+ FopImage image=null;
+
+ String _debug=org.apache.fop.configuration.Configuration.getStringValue("imagecache_debug");
+ if (_debug==null)
+ _debug="false";
+ boolean imagecache_debug=_debug.toLowerCase().startsWith("t");
+ if (imagecache_debug)
+ {
+ StringBuffer sb=new StringBuffer();
+ for (Iterator iterator=m_urlMap.values().iterator();iterator.hasNext();)
+ {
+ ImageCacheEntry ce=(ImageCacheEntry)iterator.next();
+
+ sb.append(" * "+ce.getUrl()+"\n");
+ sb.append(" - image size: "+ce.getImageSize()+"\n");
+ }
+
+ if (cacheentry==null)
+ MessageHandler.log("Adding new image: "+href);
+ else
+ MessageHandler.log("Found image: "+href);
+ MessageHandler.log("\n"+sb.toString()+"\n");
+ }
+
+ if (cacheentry != null)
+ return cacheentry.getFopimage();
try {
// try url as complete first, this can cause
@@ -189,7 +296,10 @@
if (imgIS != null) {
try {
imgIS.close();
- } catch (IOException e) {}
+ } catch (IOException e)
+ {
+ // can't do anything about close() failing
+ }
}
}
if (imgReader == null)
@@ -264,7 +374,59 @@
+ "class " + imageClass.getName()
+ " doesn't implement org.apache.fop.image.FopImage interface");
}
- m_urlMap.put(href, imageInstance);
+
+ // create cache entry and check the map size (otherwise we will always get out of memory
+ // errors or too man open files errors when building large pdf files)
+ cacheentry=new ImageCacheEntry((FopImage)imageInstance,href);
+
+ int min=org.apache.fop.configuration.Configuration.getIntValue("imagecache_min");
+ if (min<0)
+ min=DEFAULT_MIN;
+ int max=org.apache.fop.configuration.Configuration.getIntValue("imagecache_max");
+ if (max<0)
+ max=DEFAULT_MAX;
+ long max_size=org.apache.fop.configuration.Configuration.getIntValue("imagecache_max_size");
+ if (max_size<0)
+ max_size=DEFAULT_MAX_SIZE;
+ long min_size=org.apache.fop.configuration.Configuration.getIntValue("imagecache_min_size");
+ if (min_size<0)
+ min_size=DEFAULT_MIN_SIZE;
+
+ // get image cache size
+ long cachesize=0;
+ for (Iterator it=m_urlMap.values().iterator();it.hasNext();)
+ {
+ ImageCacheEntry ce=(ImageCacheEntry)it.next();
+ if (ce.getImageSize()>=0)
+ cachesize+=ce.getImageSize();
+ }
+
+ if ((max!=0) &&
+ ((m_urlMap.size()>max) || (max_size<=cachesize)))
+ {
+ if (m_urlMap.size()>max)
+ MessageHandler.log("clearing fop image cache (from "+m_urlMap.size()+" elements to "+min+" elements)");
+ else
+ MessageHandler.log("clearing fop image cache (from "+cachesize+" byte to "+min_size+" byte)");
+
+ // cache size exceeded, shrink to min
+ Iterator entry_to_remove_it = m_urlMap.values().iterator();
+ do
+ {
+ ImageCacheEntry ce=(ImageCacheEntry)entry_to_remove_it.next();
+
+ if (imagecache_debug)
+ MessageHandler.log(" * removing "+ce.getUrl()+" ("+ce.getImageSize()+" byte) from cache");
+
+ cachesize-=ce.getImageSize();
+ entry_to_remove_it.remove();
+
+ } while((m_urlMap.size()>min) || (min_size<=cachesize));
+ }
+
+ // add to url map
+ m_urlMap.put(href,cacheentry);
+
return (FopImage)imageInstance;
}
@@ -298,5 +460,58 @@
public static synchronized void resetCache() {
m_urlMap.clear();
}
+
+ /**
+ * Returns the cached image count.
+ *
+ * @return the cached image count.
+ */
+ public static int getCacheSize()
+ {
+ return m_urlMap.size();
+ }
+
+ /**
+ * Set the map to use as the cache.
+ *
+ * This should be backed by a {@link LinkedHashMap} to get LRU behaviour when
+ * cleaning the cache.
+ *
+ * @param aMap The map to store url->ImageCacheEntry mappings.
+ */
+ public static synchronized void setCacheMap (Map aMap)
+ {
+ resetCache();
+ m_urlMap = aMap;
}
+ /*
+ public static void main (String[] args)
+ {
+ org.apache.fop.configuration.Configuration.put("imagecache_debug", "true");
+ //org.apache.fop.configuration.Configuration.put("imagecache_min", "10");
+ //org.apache.fop.configuration.Configuration.put("imagecache_max", "20");
+
+ try
+ {
+ for (int i = 0; i < 100; i++)
+ {
+ System.out.println (i+"...");
+ FopImage image = Make ("file:///tmp/fop/"+i+".jpg");
+ if ((i % 5) == 0)
+ {
+ // touch some images to check last access behaviour
+ if (i > 5)
+ Make ("file:///tmp/fop/"+(i-5)+".jpg");
+ if (i > 10)
+ Make ("file:///tmp/fop/"+(i-10)+".jpg");
+ }
+ }
+ }
+ catch (Throwable t)
+ {
+ t.printStackTrace();
+ }
+ }
+ */
+}
\ No newline at end of file
Index: src/org/apache/fop/image/BmpImage.java
===================================================================
--- src/org/apache/fop/image/BmpImage.java (revision 216159)
+++ src/org/apache/fop/image/BmpImage.java (working copy)
@@ -51,11 +51,10 @@
package org.apache.fop.image;
// Java
-import java.net.URL;
-import java.io.InputStream;
import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
-// FOP
import org.apache.fop.datatypes.ColorSpace;
import org.apache.fop.image.analyser.ImageReader;
@@ -81,6 +80,10 @@
int[] headermap = new int[54];
int filepos = 0;
InputStream file = null;
+ // BUGFIX: do always close the inputstream (the garbage collector may do this
+ // too late when handling pdfs with many images!!)
+ try
+ {
byte palette[] = null;
try {
file = this.m_href.openStream();
@@ -235,6 +238,21 @@
j++;
}
}
+ }
+ finally
+ {
+ if (file!=null)
+ {
+ try
+ {
+ file.close();
+ }
+ catch(IOException e)
+ {
+ // can be ignored
+ }
+ }
+ }
// This seems really strange to me, but I noticed that JimiImage hardcodes
// m_bitsPerPixel to 8. If I do not do this Acrobat is unable to read the resultant PDF,
Index: src/org/apache/fop/image/ImageCacheEntry.java
===================================================================
--- src/org/apache/fop/image/ImageCacheEntry.java (revision 0)
+++ src/org/apache/fop/image/ImageCacheEntry.java (revision 0)
@@ -0,0 +1,225 @@
+/*
+ * $Id: ImageArea.java,v 1.9.2.4 2003/09/20 21:48:19 vmote Exp $
+ * ============================================================================
+ * The Apache Software License, Version 1.1
+ * ============================================================================
+ *
+ * Copyright (C) 1999-2003 The Apache Software Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modifica-
+ * tion, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * 3. The end-user documentation included with the redistribution, if any, must
+ * include the following acknowledgment: "This product includes software
+ * developed by the Apache Software Foundation (http://www.apache.org/)."
+ * Alternately, this acknowledgment may appear in the software itself, if
+ * and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "FOP" and "Apache Software Foundation" must not be used to
+ * endorse or promote products derived from this software without prior
+ * written permission. For written permission, please contact
+ * apache@apache.org.
+ *
+ * 5. Products derived from this software may not be called "Apache", nor may
+ * "Apache" appear in their name, without prior written permission of the
+ * Apache Software Foundation.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * APACHE SOFTWARE FOUNDATION OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLU-
+ * DING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * ============================================================================
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * on behalf of the Apache Software Foundation and was originally created by
+ * Janko Nebel . For more information on the Apache
+ * Software Foundation, please see .
+ */
+package org.apache.fop.image;
+
+import org.apache.fop.messaging.MessageHandler;
+
+/**
+ * Stores one cached image of the image factory.
+ *
+ * This cache element offers access time and count members which allows
+ * the cache to be sorted by priority.
+ *
+ * @author Janko Nebel, Community4you GmbH http://www.community4you.de
+ * @see org.apache.fop.image.FopImageFactory
+ */
+class ImageCacheEntry implements Comparable
+{
+ /**
+ * The date of the last access.
+ */
+ private long lastaccess_time=0;
+
+ /**
+ * The access count.
+ */
+ private long access_count=0;
+
+ /**
+ * The image size.
+ */
+ private long image_size=0;
+
+ /**
+ * The stored fop image.
+ */
+ private FopImage fopimage=null;
+
+ /**
+ * The image url (key in url map).
+ */
+ private String url=null;
+
+ /**
+ * The constructor.
+ *
+ * @param fopimage the image.
+ * @param url the url (this is the key in the url map).
+ */
+ protected ImageCacheEntry(FopImage fopimage,String url)
+ {
+ lastaccess_time=System.currentTimeMillis();
+ access_count=0;
+ this.fopimage=fopimage;
+ this.url=url;
+ try
+ {
+ this.image_size=(fopimage.getHeight()*fopimage.getWidth()*fopimage.getBitsPerPixel())/8;
+ }
+ catch(FopImageException e)
+ {
+ MessageHandler.log(e.getMessage());
+ this.image_size=-1;
+ }
+ }
+
+ /**
+ * Compares one image cache entry with annother one.
+ *
+ * The compare algorithm is:
+ *
+ * - compare access_count (the element with the highest count wins)
+ *
+ * In future, it's also possible to compare the image size too.
+ *
+ * @param o the image cache entry to compare with.
+ * @return -1, if this entry is lower, 0 if both entries are equals or +1, if this
+ * entry is higher.
+ */
+ public int compareTo (Object o)
+ {
+ if (o==null)
+ return 1;
+
+ if (!(o instanceof ImageCacheEntry))
+ throw new IllegalArgumentException("image cache entries can only be compared with image cache" +
+ "entries");
+
+ ImageCacheEntry entry=(ImageCacheEntry)o;
+
+ // first step: compare last access
+ if (entry.lastaccess_time>lastaccess_time)
+ return -1;
+ else if (entry.lastaccess_timeimage_size)
+ return 1;
+ else if (entry.image_size 0) {
byte[] align = new byte[((iccStream.size()) % 8) + 8];
try {iccStream.write(align);} catch (Exception e) {
Index: src/org/apache/fop/image/JimiImage.java
===================================================================
--- src/org/apache/fop/image/JimiImage.java (revision 216159)
+++ src/org/apache/fop/image/JimiImage.java (working copy)
@@ -51,18 +51,18 @@
package org.apache.fop.image;
// Java
-import java.net.URL;
-import java.awt.image.ImageProducer;
import java.awt.image.ColorModel;
+import java.awt.image.ImageProducer;
import java.awt.image.IndexColorModel;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
-// Jimi
-import com.sun.jimi.core.Jimi;
-
-// FOP
import org.apache.fop.datatypes.ColorSpace;
-import org.apache.fop.pdf.PDFColor;
import org.apache.fop.image.analyser.ImageReader;
+import org.apache.fop.pdf.PDFColor;
+
+import com.sun.jimi.core.Jimi;
/**
* FopImage object for several images types, using Jimi.
@@ -94,7 +94,13 @@
protected void loadImage() throws FopImageException {
int[] tmpMap = null;
try {
- ImageProducer ip = Jimi.getImageProducer(this.m_href.openStream(),
+ // BUGFIX: do always close the inputstream (the garbage collector may do this
+ // too late when handling pdfs with many images!!)
+ InputStream inputStream=null;
+ try
+ {
+ inputStream=this.m_href.openStream();
+ ImageProducer ip = Jimi.getImageProducer(inputStream,
Jimi.SYNCHRONOUS
| Jimi.IN_MEMORY);
FopImageConsumer consumer = new FopImageConsumer(ip);
@@ -170,6 +176,21 @@
} else {
this.m_isTransparent = false;
}
+ }
+ finally
+ {
+ if (inputStream!=null)
+ {
+ try
+ {
+ inputStream.close();
+ }
+ catch(IOException e)
+ {
+ // can be ignored
+ }
+ }
+ }
} catch (Exception ex) {
throw new FopImageException("Error while loading image "
+ this.m_href.toString() + " : "
Index: src/org/apache/fop/image/TiffImage.java
===================================================================
--- src/org/apache/fop/image/TiffImage.java (revision 216159)
+++ src/org/apache/fop/image/TiffImage.java (working copy)
@@ -52,6 +52,7 @@
// Java
import java.net.URL;
+import java.io.IOException;
import java.io.InputStream;
// Sun codec
@@ -83,7 +84,13 @@
protected void loadImage() throws FopImageException {
try {
- InputStream inputStream = this.m_href.openStream();
+ InputStream inputStream = null;
+ // BUGFIX: do always close the inputstream (the garbage collector may do this
+ // too late when handling pdfs with many images!!)
+ try
+ {
+ inputStream=this.m_href.openStream();
+
/*
* BufferedInputStream inputStream = this.m_imageReader.getInputStream();
* inputStream.reset();
@@ -185,6 +192,21 @@
}
this.m_bitmaps = readBuf;
+ }
+ finally
+ {
+ if (inputStream!=null)
+ {
+ try
+ {
+ inputStream.close();
+ }
+ catch(IOException e)
+ {
+ // can be ignored
+ }
+ }
+ }
} catch (FopImageException fie) {
org.apache.fop.messaging.MessageHandler.logln("Reverting to TIFF image handling through JAI: "
+ fie.getMessage());