KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > fop > image > ImageFactory


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
18 /* $Id: ImageFactory.java 489314 2006-12-21 10:15:13Z spepping $ */
19
20 package org.apache.fop.image;
21
22 // Java
23
import java.io.InputStream JavaDoc;
24 import java.lang.ref.Reference JavaDoc;
25 import java.lang.ref.ReferenceQueue JavaDoc;
26 import java.lang.ref.SoftReference JavaDoc;
27 import java.lang.reflect.Constructor JavaDoc;
28 import java.util.ArrayList JavaDoc;
29 import java.util.Map JavaDoc;
30 import java.util.Set JavaDoc;
31 import java.util.Collections JavaDoc;
32 import java.util.HashMap JavaDoc;
33 import java.util.Iterator JavaDoc;
34 import java.util.List JavaDoc;
35 import java.util.Map.Entry;
36
37 import javax.xml.transform.Source JavaDoc;
38 import javax.xml.transform.stream.StreamSource JavaDoc;
39
40 import org.apache.commons.logging.Log;
41 import org.apache.commons.logging.LogFactory;
42
43 // FOP
44
import org.apache.fop.image.analyser.ImageReaderFactory;
45 import org.apache.fop.apps.FOUserAgent;
46 import org.apache.fop.datatypes.URISpecification;
47
48 /**
49  * Create FopImage objects (with a configuration file - not yet implemented).
50  * @author Eric SCHAEFFER
51  */

52 public final class ImageFactory {
53
54     /**
55      * logging instance
56      */

57     protected static Log log = LogFactory.getLog(FopImage.class);
58     
59     private HashMap JavaDoc imageMimeTypes = new HashMap JavaDoc();
60     
61     private ImageCache cache = new ContextImageCache(true);
62
63     /**
64      * Main constructor for the ImageFactory.
65      */

66     public ImageFactory() {
67         /* @todo The mappings set up below of image mime types to implementing
68          * classes should be made externally configurable
69          */

70         ImageProvider jaiImage = new ImageProvider("JAIImage", "org.apache.fop.image.JAIImage");
71         ImageProvider jimiImage = new ImageProvider("JIMIImage", "org.apache.fop.image.JimiImage");
72         ImageProvider imageIoImage = new ImageProvider(
73                 "ImageIOImage", "org.apache.fop.image.ImageIOImage");
74         ImageProvider gifImage = new ImageProvider("GIFImage", "org.apache.fop.image.GifImage");
75         ImageProvider jpegImage = new ImageProvider("JPEGImage", "org.apache.fop.image.JpegImage");
76         ImageProvider jpegImageIOImage = new ImageProvider(
77                 "JPEGImage", "org.apache.fop.image.JpegImageIOImage");
78         ImageProvider bmpImage = new ImageProvider("BMPImage", "org.apache.fop.image.BmpImage");
79         ImageProvider epsImage = new ImageProvider("EPSImage", "org.apache.fop.image.EPSImage");
80         ImageProvider pngImage = new ImageProvider("PNGImage", "org.apache.fop.image.PNGImage");
81         ImageProvider tiffImage = new ImageProvider("TIFFImage", "org.apache.fop.image.TIFFImage");
82         ImageProvider xmlImage = new ImageProvider("XMLImage", "org.apache.fop.image.XMLImage");
83         ImageProvider emfImage = new ImageProvider("EMFImage", "org.apache.fop.image.EmfImage");
84         
85         ImageMimeType imt = new ImageMimeType("image/gif");
86         imageMimeTypes.put(imt.getMimeType(), imt);
87         imt.addProvider(imageIoImage);
88         imt.addProvider(jaiImage);
89         imt.addProvider(jimiImage);
90         imt.addProvider(gifImage);
91
92         imt = new ImageMimeType("image/jpeg");
93         imageMimeTypes.put(imt.getMimeType(), imt);
94         imt.addProvider(jpegImageIOImage);
95         imt.addProvider(jpegImage);
96
97         imt = new ImageMimeType("image/bmp");
98         imageMimeTypes.put(imt.getMimeType(), imt);
99         imt.addProvider(bmpImage);
100
101         imt = new ImageMimeType("image/eps");
102         imageMimeTypes.put(imt.getMimeType(), imt);
103         imt.addProvider(epsImage);
104
105         imt = new ImageMimeType("image/png");
106         imageMimeTypes.put(imt.getMimeType(), imt);
107         imt.addProvider(pngImage);
108         //Image I/O is faster and more memory-efficient than own codec for PNG
109
imt.addProvider(imageIoImage);
110
111         imt = new ImageMimeType("image/tga");
112         imageMimeTypes.put(imt.getMimeType(), imt);
113         imt.addProvider(jaiImage);
114         imt.addProvider(imageIoImage);
115         imt.addProvider(jimiImage);
116
117         imt = new ImageMimeType("image/tiff");
118         imageMimeTypes.put(imt.getMimeType(), imt);
119         imt.addProvider(tiffImage); //Slower but supports CCITT embedding
120
imt.addProvider(imageIoImage); //Fast but doesn't support CCITT embedding
121
imt.addProvider(jaiImage); //Fast but doesn't support CCITT embedding
122

123         imt = new ImageMimeType("image/svg+xml");
124         imageMimeTypes.put(imt.getMimeType(), imt);
125         imt.addProvider(xmlImage);
126
127         imt = new ImageMimeType("text/xml");
128         imageMimeTypes.put(imt.getMimeType(), imt);
129         imt.addProvider(xmlImage);
130         
131         imt = new ImageMimeType("image/emf");
132         imageMimeTypes.put(imt.getMimeType(), imt);
133         imt.addProvider(emfImage);
134     }
135
136     /**
137      * Get the url string from a wrapped url.
138      *
139      * @param href the input wrapped url
140      * @return the raw url
141      */

142     public static String JavaDoc getURL(String JavaDoc href) {
143         return URISpecification.getURL(href);
144     }
145
146     /**
147      * Get the image from the cache or load.
148      * If this returns null then the image could not be loaded
149      * due to an error. Messages should be logged.
150      * Before calling this the getURL(url) must be used.
151      *
152      * @param url the url for the image
153      * @param context the user agent context
154      * @return the fop image instance
155      */

156     public FopImage getImage(String JavaDoc url, FOUserAgent context) {
157         return cache.getImage(url, context);
158     }
159
160     /**
161      * Release an image from the cache.
162      * This can be used if the renderer has its own cache of
163      * the image.
164      * The image should then be put into the weak cache.
165      *
166      * @param url the url for the image
167      * @param context the user agent context
168      */

169     public void releaseImage(String JavaDoc url, FOUserAgent context) {
170         cache.releaseImage(url, context);
171     }
172
173     /**
174      * Release the context and all images in the context.
175      *
176      * @param context the context to remove
177      */

178     public void removeContext(FOUserAgent context) {
179         cache.removeContext(context);
180     }
181
182     /**
183      * Create an FopImage objects.
184      * @param href the url for the image
185      * @param ua the user agent context
186      * @return the fop image instance
187      */

188     public FopImage loadImage(String JavaDoc href, FOUserAgent ua) {
189
190         Source JavaDoc source = ua.resolveURI(href);
191         if (source == null) {
192             return null;
193         }
194         
195         // Got a valid source, obtain an InputStream from it
196
InputStream JavaDoc in = null;
197         if (source instanceof StreamSource JavaDoc) {
198             in = ((StreamSource JavaDoc)source).getInputStream();
199         }
200         if (in == null) {
201             try {
202                 in = new java.net.URL JavaDoc(source.getSystemId()).openStream();
203             } catch (Exception JavaDoc ex) {
204                 log.error("Unable to obtain stream from id '"
205                     + source.getSystemId() + "'");
206             }
207         }
208         if (in == null) {
209             return null;
210         }
211
212         //Make sure the InputStream is decorated with a BufferedInputStream
213
if (!(in instanceof java.io.BufferedInputStream JavaDoc)) {
214             in = new java.io.BufferedInputStream JavaDoc(in);
215         }
216
217         // Check image type
218
FopImage.ImageInfo imgInfo = null;
219         try {
220             imgInfo = ImageReaderFactory.make(source.getSystemId(), in, ua);
221         } catch (Exception JavaDoc e) {
222             log.error("Error while recovering image information ("
223                     + href + ") : " + e.getMessage(), e);
224             return null;
225         }
226         if (imgInfo == null) {
227             try {
228                 in.close();
229                 in = null;
230             } catch (Exception JavaDoc e) {
231                 log.debug("Error closing the InputStream for the image", e);
232             }
233             log.error("No ImageReader for this type of image (" + href + ")");
234             return null;
235         }
236         // Associate mime-type to FopImage class
237
String JavaDoc imgMimeType = imgInfo.mimeType;
238         Class JavaDoc imageClass = getImageClass(imgMimeType);
239         if (imageClass == null) {
240             log.error("Unsupported image type (" + href + "): " + imgMimeType);
241             return null;
242         } else {
243             if (log.isDebugEnabled()) {
244                 log.debug("Loading " + imgMimeType + " with " + imageClass.getName()
245                         + ": " + href);
246             }
247         }
248
249         // load the right image class
250
// return new <FopImage implementing class>
251
Object JavaDoc imageInstance = null;
252         try {
253             Class JavaDoc[] imageConstructorParameters = new Class JavaDoc[1];
254             imageConstructorParameters[0] = org.apache.fop.image.FopImage.ImageInfo.class;
255             Constructor JavaDoc imageConstructor = imageClass.getDeclaredConstructor(
256                     imageConstructorParameters);
257             Object JavaDoc[] initArgs = new Object JavaDoc[1];
258             initArgs[0] = imgInfo;
259             imageInstance = imageConstructor.newInstance(initArgs);
260         } catch (java.lang.reflect.InvocationTargetException JavaDoc ex) {
261             Throwable JavaDoc t = ex.getTargetException();
262             String JavaDoc msg;
263             if (t != null) {
264                 msg = t.getMessage();
265             } else {
266                 msg = ex.getMessage();
267             }
268             log.error("Error creating FopImage object ("
269                     + href + "): " + msg, (t == null) ? ex : t);
270             return null;
271         } catch (InstantiationException JavaDoc ie) {
272             log.error("Error creating FopImage object ("
273                     + href + "): Could not instantiate " + imageClass.getName() + " instance");
274             return null;
275         } catch (Exception JavaDoc ex) {
276             log.error("Error creating FopImage object ("
277                     + href + "): " + ex.getMessage(), ex);
278             return null;
279         }
280         if (!(imageInstance instanceof org.apache.fop.image.FopImage)) {
281             log.error("Error creating FopImage object (" + href + "): " + "class "
282                     + imageClass.getName()
283                     + " doesn't implement org.apache.fop.image.FopImage interface");
284             return null;
285         }
286         return (FopImage) imageInstance;
287     }
288
289     private Class JavaDoc getImageClass(String JavaDoc imgMimeType) {
290         ImageMimeType imt = (ImageMimeType)imageMimeTypes.get(imgMimeType);
291         if (imt == null) {
292             return null;
293         }
294         return imt.getFirstImplementingClass();
295     }
296     
297     /**
298      * Forces all the image caches to be cleared. This should normally only be used in
299      * testing environments. If you happen to think that you need to call this yourself
300      * in a production environment, please notify the development team so we can look
301      * into the issue. A call like this shouldn't be necessary anymore like it may have
302      * been with FOP 0.20.5.
303      */

304     public void clearCaches() {
305         cache.clearAll();
306     }
307 }
308
309 /**
310  * Basic image cache.
311  * This keeps track of invalid images.
312  */

313 class BasicImageCache implements ImageCache {
314
315     private Set JavaDoc invalid = Collections.synchronizedSet(new java.util.HashSet JavaDoc());
316     //private Map contextStore = Collections.synchronizedMap(new java.util.HashMap());
317

318     public FopImage getImage(String JavaDoc url, FOUserAgent context) {
319         if (invalid.contains(url)) {
320             return null;
321         }
322         //TODO Doesn't seem to be fully implemented. Do we need it at all? Not referenced.
323
return null;
324     }
325
326     public void releaseImage(String JavaDoc url, FOUserAgent context) {
327         // do nothing
328
}
329
330     public void invalidateImage(String JavaDoc url, FOUserAgent context) {
331         // cap size of invalid list
332
if (invalid.size() > 100) {
333             invalid.clear();
334         }
335         invalid.add(url);
336     }
337
338     public void removeContext(FOUserAgent context) {
339         // do nothing
340
}
341
342     /** @see org.apache.fop.image.ImageCache#clearAll() */
343     public void clearAll() {
344         invalid.clear();
345     }
346     
347 }
348
349 /**
350  * This is the context image cache.
351  * This caches images on the basis of the given context.
352  * Common images in different contexts are currently not handled.
353  * There are two possiblities, each context handles its own images
354  * and renderers can cache information or images are shared and
355  * all information is retained.
356  * Once a context is removed then all images are placed into a
357  * weak hashmap so they may be garbage collected.
358  */

359 class ContextImageCache implements ImageCache {
360
361     // if this cache is collective then images can be shared
362
// among contexts, this implies that the base directory
363
// is either the same or does not effect the images being
364
// loaded
365
private boolean collective;
366     private Map JavaDoc contextStore = Collections.synchronizedMap(new java.util.HashMap JavaDoc());
367     private Set JavaDoc invalid = null;
368     private Map JavaDoc refStore = null;
369     private ReferenceQueue JavaDoc refQueue = new ReferenceQueue JavaDoc();
370
371     public ContextImageCache(boolean col) {
372         collective = col;
373         if (collective) {
374             refStore = Collections.synchronizedMap(new java.util.HashMap JavaDoc());
375             invalid = Collections.synchronizedSet(new java.util.HashSet JavaDoc());
376         }
377     }
378
379     // sync around lookups and puts
380
// another sync around load for a particular image
381
public FopImage getImage(String JavaDoc url, FOUserAgent context) {
382         ImageLoader im = null;
383         // this protects the finding or creating of a new
384
// ImageLoader for multi threads
385
synchronized (this) {
386             if (collective && invalid.contains(url)) {
387                 return null;
388             }
389             Context con = (Context) contextStore.get(context);
390             if (con == null) {
391                 con = new Context(context, collective);
392                 contextStore.put(context, con);
393             } else {
394                 if (con.invalid(url)) {
395                     return null;
396                 }
397                 im = con.getImage(url);
398             }
399             if (im == null && collective) {
400                 Iterator JavaDoc i = contextStore.values().iterator();
401                 while (i.hasNext()) {
402                     Context c = (Context)i.next();
403                     if (c != con) {
404                         im = c.getImage(url);
405                         if (im != null) {
406                             break;
407                         }
408                     }
409                 }
410                 if (im == null) {
411                     Reference JavaDoc ref = (Reference JavaDoc)refStore.get(url);
412                     if (ref != null) {
413                         im = (ImageLoader) ref.get();
414                         if (im == null) {
415                             //Remove key if its value has been garbage collected
416
refStore.remove(url);
417                         }
418                     }
419                 }
420             }
421
422             if (im != null) {
423                 con.putImage(url, im);
424             } else {
425                 im = con.getImage(url, this);
426             }
427         }
428
429         // the ImageLoader is synchronized so images with the
430
// same url will not be loaded at the same time
431
if (im != null) {
432             return im.loadImage();
433         }
434         return null;
435     }
436
437     public void releaseImage(String JavaDoc url, FOUserAgent context) {
438         Context con = (Context) contextStore.get(context);
439         if (con != null) {
440             if (collective) {
441                 ImageLoader im = con.getImage(url);
442                 refStore.put(url, wrapInReference(im, url));
443             }
444             con.releaseImage(url);
445         }
446     }
447
448     public void invalidateImage(String JavaDoc url, FOUserAgent context) {
449         if (collective) {
450             // cap size of invalid list
451
if (invalid.size() > 100) {
452                 invalid.clear();
453             }
454             invalid.add(url);
455         }
456         Context con = (Context) contextStore.get(context);
457         if (con != null) {
458             con.invalidateImage(url);
459         }
460     }
461
462     private Reference JavaDoc wrapInReference(Object JavaDoc obj, Object JavaDoc key) {
463         return new SoftReferenceWithKey(obj, key, refQueue);
464     }
465     
466     private static class SoftReferenceWithKey extends SoftReference JavaDoc {
467         
468         private Object JavaDoc key;
469         
470         public SoftReferenceWithKey(Object JavaDoc referent, Object JavaDoc key, ReferenceQueue JavaDoc q) {
471             super(referent, q);
472             this.key = key;
473         }
474     }
475     
476     public void removeContext(FOUserAgent context) {
477         Context con = (Context) contextStore.get(context);
478         if (con != null) {
479             if (collective) {
480                 Map JavaDoc images = con.getImages();
481                 Iterator JavaDoc iter = images.entrySet().iterator();
482                 while (iter.hasNext()) {
483                     Entry entry = (Entry)iter.next();
484                     refStore.put(entry.getKey(),
485                             wrapInReference(entry.getValue(), entry.getKey()));
486                 }
487             }
488             contextStore.remove(context);
489         }
490         //House-keeping (remove cleared references)
491
checkReferenceQueue();
492     }
493     
494     /**
495      * Checks the reference queue if any references have been cleared and removes them from the
496      * cache.
497      */

498     private void checkReferenceQueue() {
499         SoftReferenceWithKey ref;
500         while ((ref = (SoftReferenceWithKey)refQueue.poll()) != null) {
501             refStore.remove(ref.key);
502         }
503     }
504
505     class Context {
506         private Map JavaDoc images = Collections.synchronizedMap(new java.util.HashMap JavaDoc());
507         private Set JavaDoc invalid = null;
508         private FOUserAgent userAgent;
509
510         public Context(FOUserAgent ua, boolean inv) {
511             userAgent = ua;
512             if (inv) {
513                 invalid = Collections.synchronizedSet(new java.util.HashSet JavaDoc());
514             }
515         }
516
517         public ImageLoader getImage(String JavaDoc url, ImageCache c) {
518             if (images.containsKey(url)) {
519                 return (ImageLoader) images.get(url);
520             }
521             ImageLoader loader = new ImageLoader(url, c, userAgent);
522             images.put(url, loader);
523             return loader;
524         }
525
526         public void putImage(String JavaDoc url, ImageLoader image) {
527             images.put(url, image);
528         }
529
530         public ImageLoader getImage(String JavaDoc url) {
531             return (ImageLoader) images.get(url);
532         }
533
534         public void releaseImage(String JavaDoc url) {
535             images.remove(url);
536         }
537
538         public Map JavaDoc getImages() {
539             return images;
540         }
541
542         public void invalidateImage(String JavaDoc url) {
543             invalid.add(url);
544         }
545
546         public boolean invalid(String JavaDoc url) {
547             return invalid.contains(url);
548         }
549
550     }
551
552     /** @see org.apache.fop.image.ImageCache#clearAll() */
553     public void clearAll() {
554         this.refStore.clear();
555         this.invalid.clear();
556         //The context-sensitive caches are not cleared so there are no negative side-effects
557
//in a multi-threaded environment. Not that it's a good idea to use this method at
558
//all except in testing environments. If such a calls is necessary in normal environments
559
//we need to check on memory leaks!
560
}
561
562 }
563
564 /**
565  * Encapsulates a class of type FopImage by holding its class name.
566  * This allows dynamic loading of the class at runtime.
567  */

568 class ImageProvider {
569
570     private String JavaDoc name = null;
571
572     private String JavaDoc className = null;
573
574     private boolean checked = false;
575
576     private Class JavaDoc clazz = null;
577
578     /**
579      * Creates an ImageProvider with a given name and implementing class.
580      * The class name should refer to a class of type {@link FopImage}.
581      * However, this is not checked on construction.
582      * @param name The name of the provider
583      * @param className The full class name of the class implementing this provider
584      */

585     public ImageProvider(String JavaDoc name, String JavaDoc className) {
586         setName(name);
587         setClassName(className);
588     }
589
590     /**
591      * Returns the provider name.
592      * @return The provider name
593      */

594     public String JavaDoc getName() {
595         return name;
596     }
597
598     private void setName(String JavaDoc name) {
599         this.name = name;
600     }
601
602     /**
603      * Returns the implementing class name.
604      * @return The implementing class name
605      */

606     public String JavaDoc getClassName() {
607         return className;
608     }
609
610     private void setClassName(String JavaDoc className) {
611         this.className = className;
612     }
613
614     /**
615      * Returns the implementing class as a {@link Class} object.
616      * @return The implementing class or null if it couldn't be loaded.
617      */

618     public Class JavaDoc getImplementingClass() {
619         if (!checked) {
620             try {
621                 clazz = Class.forName(getClassName());
622             } catch (ClassNotFoundException JavaDoc cnfe) {
623                 //nop
624
} catch (LinkageError JavaDoc le) {
625                 // This can happen if fop was build with support for a
626
// particular provider (e.g. a binary fop distribution)
627
// but the required support files (e.g. jai, jimi) are not
628
// available in the current runtime environment.
629
ImageFactory.log.debug("Image support provider " + getName()
630                     + " could not be loaded. If " + getName() + " should be"
631                     + " available please make sure all required external libraries"
632                     + " are on the classpath.");
633             }
634             checked = true;
635         }
636         return clazz;
637     }
638 }
639
640 /**
641  * Holds a mime type for a particular image format plus a list of
642  * {@link ImageProvider} objects which support the particular image format.
643  */

644 class ImageMimeType {
645
646     private String JavaDoc mimeType = null;
647
648     private List JavaDoc providers = null;
649
650     /**
651      * Constructor for a particular mime type.
652      * @param mimeType The mime type
653      */

654     public ImageMimeType(String JavaDoc mimeType) {
655         setMimeType(mimeType);
656     }
657
658     /**
659      * Returns the mime type.
660      * @return The mime type
661      */

662     public String JavaDoc getMimeType() {
663         return mimeType;
664     }
665
666     private void setMimeType(String JavaDoc mimeType) {
667         this.mimeType = mimeType;
668     }
669
670     /**
671      * Returns the class from the first available provider.
672      * @return The first available class or null if none can be found
673      */

674     public Class JavaDoc getFirstImplementingClass() {
675         if (providers == null) {
676             return null;
677         }
678         for (Iterator JavaDoc it = providers.iterator(); it.hasNext();) {
679             ImageProvider ip = (ImageProvider)it.next();
680             Class JavaDoc clazz = ip.getImplementingClass();
681             if (clazz != null) {
682                 return clazz;
683             }
684         }
685         return null;
686     }
687
688     /**
689      * Adds a new provider.
690      * The provider is added to the end of the current provider list.
691      * @param The new provider to add
692      */

693     public void addProvider(ImageProvider provider) {
694         if (providers == null) {
695             providers = new ArrayList JavaDoc(4); // Assume we only have a few providers
696
}
697         if (!providers.contains(provider)) {
698             providers.add(provider);
699         }
700     }
701 }
702
703
Popular Tags