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.

ImageFactory.java 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632
  1. /*
  2. * Copyright 1999-2005 The Apache Software Foundation.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. /* $Id: ImageFactory.java,v 1.7 2004/05/12 23:19:52 gmazza Exp $ */
  17. package org.apache.fop.image;
  18. // Java
  19. import java.io.InputStream;
  20. import java.lang.reflect.Constructor;
  21. import java.util.ArrayList;
  22. import java.util.Map;
  23. import java.util.Set;
  24. import java.util.Collections;
  25. import java.util.HashMap;
  26. import java.util.Iterator;
  27. import java.util.List;
  28. import javax.xml.transform.Source;
  29. import javax.xml.transform.stream.StreamSource;
  30. import org.apache.commons.logging.Log;
  31. import org.apache.commons.logging.LogFactory;
  32. // FOP
  33. import org.apache.fop.image.analyser.ImageReaderFactory;
  34. import org.apache.fop.apps.FOUserAgent;
  35. /**
  36. * Create FopImage objects (with a configuration file - not yet implemented).
  37. * @author Eric SCHAEFFER
  38. */
  39. public final class ImageFactory {
  40. /**
  41. * logging instance
  42. */
  43. protected static Log log = LogFactory.getLog(FopImage.class);
  44. private static ImageFactory factory = new ImageFactory();
  45. private HashMap imageMimeTypes = new HashMap();
  46. private ImageCache cache = new ContextImageCache(true);
  47. private ImageFactory() {
  48. /* @todo The mappings set up below of image mime types to implementing
  49. * classes should be made externally configurable
  50. */
  51. ImageProvider jaiImage = new ImageProvider("JAIImage", "org.apache.fop.image.JAIImage");
  52. ImageProvider jimiImage = new ImageProvider("JIMIImage", "org.apache.fop.image.JimiImage");
  53. ImageProvider imageIoImage = new ImageProvider(
  54. "ImageIOImage", "org.apache.fop.image.ImageIoImage");
  55. ImageProvider gifImage = new ImageProvider("GIFImage", "org.apache.fop.image.GifImage");
  56. ImageProvider jpegImage = new ImageProvider("JPEGImage", "org.apache.fop.image.JpegImage");
  57. ImageProvider bmpImage = new ImageProvider("BMPImage", "org.apache.fop.image.BmpImage");
  58. ImageProvider epsImage = new ImageProvider("EPSImage", "org.apache.fop.image.EPSImage");
  59. ImageProvider pngImage = new ImageProvider("PNGImage", "org.apache.fop.image.PNGImage");
  60. ImageProvider tiffImage = new ImageProvider("TIFFImage", "org.apache.fop.image.TIFFImage");
  61. ImageProvider xmlImage = new ImageProvider("XMLImage", "org.apache.fop.image.XMLImage");
  62. ImageMimeType imt = new ImageMimeType("image/gif");
  63. imageMimeTypes.put(imt.getMimeType(), imt);
  64. imt.addProvider(jaiImage);
  65. imt.addProvider(imageIoImage);
  66. imt.addProvider(jimiImage);
  67. imt.addProvider(gifImage);
  68. imt = new ImageMimeType("image/jpeg");
  69. imageMimeTypes.put(imt.getMimeType(), imt);
  70. imt.addProvider(jpegImage);
  71. imt = new ImageMimeType("image/bmp");
  72. imageMimeTypes.put(imt.getMimeType(), imt);
  73. imt.addProvider(bmpImage);
  74. imt = new ImageMimeType("image/eps");
  75. imageMimeTypes.put(imt.getMimeType(), imt);
  76. imt.addProvider(epsImage);
  77. imt = new ImageMimeType("image/png");
  78. imageMimeTypes.put(imt.getMimeType(), imt);
  79. imt.addProvider(pngImage);
  80. imt = new ImageMimeType("image/tga");
  81. imageMimeTypes.put(imt.getMimeType(), imt);
  82. imt.addProvider(jaiImage);
  83. imt.addProvider(imageIoImage);
  84. imt.addProvider(jimiImage);
  85. imt = new ImageMimeType("image/tiff");
  86. imageMimeTypes.put(imt.getMimeType(), imt);
  87. imt.addProvider(tiffImage);
  88. imt = new ImageMimeType("image/svg+xml");
  89. imageMimeTypes.put(imt.getMimeType(), imt);
  90. imt.addProvider(xmlImage);
  91. imt = new ImageMimeType("text/xml");
  92. imageMimeTypes.put(imt.getMimeType(), imt);
  93. imt.addProvider(xmlImage);
  94. }
  95. /**
  96. * Get static image factory instance.
  97. *
  98. * @return the image factory instance
  99. */
  100. public static ImageFactory getInstance() {
  101. return factory;
  102. }
  103. /**
  104. * Get the url string from a wrapped url.
  105. *
  106. * @param href the input wrapped url
  107. * @return the raw url
  108. */
  109. public static String getURL(String href) {
  110. /*
  111. * According to section 5.11 a <uri-specification> is:
  112. * "url(" + URI + ")"
  113. * according to 7.28.7 a <uri-specification> is:
  114. * URI
  115. * So handle both.
  116. */
  117. href = href.trim();
  118. if (href.startsWith("url(") && (href.indexOf(")") != -1)) {
  119. href = href.substring(4, href.indexOf(")")).trim();
  120. if (href.startsWith("'") && href.endsWith("'")) {
  121. href = href.substring(1, href.length() - 1);
  122. } else if (href.startsWith("\"") && href.endsWith("\"")) {
  123. href = href.substring(1, href.length() - 1);
  124. }
  125. } else {
  126. // warn
  127. }
  128. return href;
  129. }
  130. /**
  131. * Get the image from the cache or load.
  132. * If this returns null then the image could not be loaded
  133. * due to an error. Messages should be logged.
  134. * Before calling this the getURL(url) must be used.
  135. *
  136. * @param url the url for the image
  137. * @param context the user agent context
  138. * @return the fop image instance
  139. */
  140. public FopImage getImage(String url, FOUserAgent context) {
  141. return cache.getImage(url, context);
  142. }
  143. /**
  144. * Release an image from the cache.
  145. * This can be used if the renderer has its own cache of
  146. * the image.
  147. * The image should then be put into the weak cache.
  148. *
  149. * @param url the url for the image
  150. * @param context the user agent context
  151. */
  152. public void releaseImage(String url, FOUserAgent context) {
  153. cache.releaseImage(url, context);
  154. }
  155. /**
  156. * Release the context and all images in the context.
  157. *
  158. * @param context the context to remove
  159. */
  160. public void removeContext(FOUserAgent context) {
  161. cache.removeContext(context);
  162. }
  163. /**
  164. * Create an FopImage objects.
  165. * @param href the url for the image
  166. * @param ua the user agent context
  167. * @return the fop image instance
  168. */
  169. public FopImage loadImage(String href, FOUserAgent ua) {
  170. Source source = ua.resolveURI(href);
  171. if (source == null) {
  172. return null;
  173. }
  174. // Got a valid source, obtain an InputStream from it
  175. InputStream in = null;
  176. if (source instanceof StreamSource) {
  177. in = ((StreamSource)source).getInputStream();
  178. }
  179. if (in == null) {
  180. try {
  181. in = new java.net.URL(source.getSystemId()).openStream();
  182. } catch (Exception ex) {
  183. log.error("Unable to obtain stream from id '"
  184. + source.getSystemId() + "'");
  185. }
  186. }
  187. if (in == null) {
  188. return null;
  189. }
  190. //Make sure the InputStream is decorated with a BufferedInputStream
  191. if (!(in instanceof java.io.BufferedInputStream)) {
  192. in = new java.io.BufferedInputStream(in);
  193. }
  194. // Check image type
  195. FopImage.ImageInfo imgInfo = null;
  196. try {
  197. imgInfo = ImageReaderFactory.make(source.getSystemId(), in, ua);
  198. } catch (Exception e) {
  199. log.error("Error while recovering image information ("
  200. + href + ") : " + e.getMessage(), e);
  201. return null;
  202. }
  203. if (imgInfo == null) {
  204. try {
  205. in.close();
  206. in = null;
  207. } catch (Exception e) {
  208. log.debug("Error closing the InputStream for the image", e);
  209. }
  210. log.error("No ImageReader for this type of image ("
  211. + href + ")");
  212. return null;
  213. }
  214. // Associate mime-type to FopImage class
  215. String imgMimeType = imgInfo.mimeType;
  216. Class imageClass = getImageClass(imgMimeType);
  217. if (imageClass == null) {
  218. log.error("Unsupported image type ("
  219. + href + "): " + imgMimeType);
  220. return null;
  221. }
  222. // load the right image class
  223. // return new <FopImage implementing class>
  224. Object imageInstance = null;
  225. try {
  226. Class[] imageConstructorParameters = new Class[1];
  227. imageConstructorParameters[0] = org.apache.fop.image.FopImage.ImageInfo.class;
  228. Constructor imageConstructor = imageClass.getDeclaredConstructor(
  229. imageConstructorParameters);
  230. Object[] initArgs = new Object[1];
  231. initArgs[0] = imgInfo;
  232. imageInstance = imageConstructor.newInstance(initArgs);
  233. } catch (java.lang.reflect.InvocationTargetException ex) {
  234. Throwable t = ex.getTargetException();
  235. String msg;
  236. if (t != null) {
  237. msg = t.getMessage();
  238. } else {
  239. msg = ex.getMessage();
  240. }
  241. log.error("Error creating FopImage object ("
  242. + href + "): " + msg, (t == null) ? ex : t);
  243. return null;
  244. } catch (InstantiationException ie) {
  245. log.error("Error creating FopImage object ("
  246. + href + "): Could not instantiate " + imageClass.getName() + " instance");
  247. return null;
  248. } catch (Exception ex) {
  249. log.error("Error creating FopImage object ("
  250. + href + "): " + ex.getMessage(), ex);
  251. return null;
  252. }
  253. if (!(imageInstance instanceof org.apache.fop.image.FopImage)) {
  254. log.error("Error creating FopImage object ("
  255. + href + "): " + "class "
  256. + imageClass.getName()
  257. + " doesn't implement org.apache.fop.image.FopImage interface");
  258. return null;
  259. }
  260. return (FopImage) imageInstance;
  261. }
  262. private Class getImageClass(String imgMimeType) {
  263. ImageMimeType imt = (ImageMimeType)imageMimeTypes.get(imgMimeType);
  264. if (imt == null) {
  265. return null;
  266. }
  267. return imt.getFirstImplementingClass();
  268. }
  269. }
  270. /**
  271. * Basic image cache.
  272. * This keeps track of invalid images.
  273. */
  274. class BasicImageCache implements ImageCache {
  275. private Set invalid = Collections.synchronizedSet(new java.util.HashSet());
  276. //private Map contextStore = Collections.synchronizedMap(new java.util.HashMap());
  277. public FopImage getImage(String url, FOUserAgent context) {
  278. if (invalid.contains(url)) {
  279. return null;
  280. }
  281. return null;
  282. }
  283. public void releaseImage(String url, FOUserAgent context) {
  284. // do nothing
  285. }
  286. public void invalidateImage(String url, FOUserAgent context) {
  287. // cap size of invalid list
  288. if (invalid.size() > 100) {
  289. invalid.clear();
  290. }
  291. invalid.add(url);
  292. }
  293. public void removeContext(FOUserAgent context) {
  294. // do nothing
  295. }
  296. }
  297. /**
  298. * This is the context image cache.
  299. * This caches images on the basis of the given context.
  300. * Common images in different contexts are currently not handled.
  301. * There are two possiblities, each context handles its own images
  302. * and renderers can cache information or images are shared and
  303. * all information is retained.
  304. * Once a context is removed then all images are placed into a
  305. * weak hashmap so they may be garbage collected.
  306. */
  307. class ContextImageCache implements ImageCache {
  308. // if this cache is collective then images can be shared
  309. // among contexts, this implies that the base directory
  310. // is either the same or does not effect the images being
  311. // loaded
  312. private boolean collective;
  313. private Map contextStore = Collections.synchronizedMap(new java.util.HashMap());
  314. private Set invalid = null;
  315. private Map weakStore = null;
  316. public ContextImageCache(boolean col) {
  317. collective = col;
  318. if (collective) {
  319. weakStore = Collections.synchronizedMap(new java.util.WeakHashMap());
  320. invalid = Collections.synchronizedSet(new java.util.HashSet());
  321. }
  322. }
  323. // sync around lookups and puts
  324. // another sync around load for a particular image
  325. public FopImage getImage(String url, FOUserAgent context) {
  326. ImageLoader im = null;
  327. // this protects the finding or creating of a new
  328. // ImageLoader for multi threads
  329. synchronized (this) {
  330. if (collective && invalid.contains(url)) {
  331. return null;
  332. }
  333. Context con = (Context) contextStore.get(context);
  334. if (con == null) {
  335. con = new Context(context, collective);
  336. contextStore.put(context, con);
  337. } else {
  338. if (con.invalid(url)) {
  339. return null;
  340. }
  341. im = con.getImage(url);
  342. }
  343. if (im == null && collective) {
  344. Iterator i = contextStore.values().iterator();
  345. while (i.hasNext()) {
  346. Context c = (Context)i.next();
  347. if (c != con) {
  348. im = c.getImage(url);
  349. if (im != null) {
  350. break;
  351. }
  352. }
  353. }
  354. if (im == null) {
  355. im = (ImageLoader) weakStore.get(url);
  356. }
  357. }
  358. if (im != null) {
  359. con.putImage(url, im);
  360. } else {
  361. im = con.getImage(url, this);
  362. }
  363. }
  364. // the ImageLoader is synchronized so images with the
  365. // same url will not be loaded at the same time
  366. if (im != null) {
  367. return im.loadImage();
  368. }
  369. return null;
  370. }
  371. public void releaseImage(String url, FOUserAgent context) {
  372. Context con = (Context) contextStore.get(context);
  373. if (con != null) {
  374. if (collective) {
  375. ImageLoader im = con.getImage(url);
  376. weakStore.put(url, im);
  377. }
  378. con.releaseImage(url);
  379. }
  380. }
  381. public void invalidateImage(String url, FOUserAgent context) {
  382. if (collective) {
  383. // cap size of invalid list
  384. if (invalid.size() > 100) {
  385. invalid.clear();
  386. }
  387. invalid.add(url);
  388. }
  389. Context con = (Context) contextStore.get(context);
  390. if (con != null) {
  391. con.invalidateImage(url);
  392. }
  393. }
  394. public void removeContext(FOUserAgent context) {
  395. Context con = (Context) contextStore.get(context);
  396. if (con != null) {
  397. if (collective) {
  398. Map images = con.getImages();
  399. weakStore.putAll(images);
  400. }
  401. contextStore.remove(context);
  402. }
  403. }
  404. class Context {
  405. private Map images = Collections.synchronizedMap(new java.util.HashMap());
  406. private Set invalid = null;
  407. private FOUserAgent userAgent;
  408. public Context(FOUserAgent ua, boolean inv) {
  409. userAgent = ua;
  410. if (inv) {
  411. invalid = Collections.synchronizedSet(new java.util.HashSet());
  412. }
  413. }
  414. public ImageLoader getImage(String url, ImageCache c) {
  415. if (images.containsKey(url)) {
  416. return (ImageLoader) images.get(url);
  417. }
  418. ImageLoader loader = new ImageLoader(url, c, userAgent);
  419. images.put(url, loader);
  420. return loader;
  421. }
  422. public void putImage(String url, ImageLoader image) {
  423. images.put(url, image);
  424. }
  425. public ImageLoader getImage(String url) {
  426. return (ImageLoader) images.get(url);
  427. }
  428. public void releaseImage(String url) {
  429. images.remove(url);
  430. }
  431. public Map getImages() {
  432. return images;
  433. }
  434. public void invalidateImage(String url) {
  435. invalid.add(url);
  436. }
  437. public boolean invalid(String url) {
  438. return invalid.contains(url);
  439. }
  440. }
  441. }
  442. /**
  443. * Encapsulates a class of type FopImage by holding its class name.
  444. * This allows dynamic loading of the class at runtime.
  445. */
  446. class ImageProvider {
  447. private String name = null;
  448. private String className = null;
  449. private boolean checked = false;
  450. private Class clazz = null;
  451. /**
  452. * Creates an ImageProvider with a given name and implementing class.
  453. * The class name should refer to a class of type {@link FopImage}.
  454. * However, this is not checked on construction.
  455. * @param name The name of the provider
  456. * @param className The full class name of the class implementing this provider
  457. */
  458. public ImageProvider(String name, String className) {
  459. setName(name);
  460. setClassName(className);
  461. }
  462. /**
  463. * Returns the provider name.
  464. * @return The provider name
  465. */
  466. public String getName() {
  467. return name;
  468. }
  469. private void setName(String name) {
  470. this.name = name;
  471. }
  472. /**
  473. * Returns the implementing class name.
  474. * @return The implementing class name
  475. */
  476. public String getClassName() {
  477. return className;
  478. }
  479. private void setClassName(String className) {
  480. this.className = className;
  481. }
  482. /**
  483. * Returns the implementing class as a {@link Class} object.
  484. * @return The implementing class or null if it couldn't be loaded.
  485. */
  486. public Class getImplementingClass() {
  487. if (!checked) {
  488. try {
  489. clazz = Class.forName(getClassName());
  490. } catch (ClassNotFoundException cnfe) {
  491. //nop
  492. }
  493. checked = true;
  494. }
  495. return clazz;
  496. }
  497. }
  498. /**
  499. * Holds a mime type for a particular image format plus a list of
  500. * {@link ImageProvider} objects which support the particular image format.
  501. */
  502. class ImageMimeType {
  503. private String mimeType = null;
  504. private List providers = null;
  505. /**
  506. * Constructor for a particular mime type.
  507. * @param mimeType The mime type
  508. */
  509. public ImageMimeType(String mimeType) {
  510. setMimeType(mimeType);
  511. }
  512. /**
  513. * Returns the mime type.
  514. * @return The mime type
  515. */
  516. public String getMimeType() {
  517. return mimeType;
  518. }
  519. private void setMimeType(String mimeType) {
  520. this.mimeType = mimeType;
  521. }
  522. /**
  523. * Returns the class from the first available provider.
  524. * @return The first available class or null if none can be found
  525. */
  526. public Class getFirstImplementingClass() {
  527. if (providers == null) {
  528. return null;
  529. }
  530. for (Iterator it = providers.iterator(); it.hasNext();) {
  531. ImageProvider ip = (ImageProvider)it.next();
  532. Class clazz = ip.getImplementingClass();
  533. if (clazz != null) {
  534. return clazz;
  535. }
  536. }
  537. return null;
  538. }
  539. /**
  540. * Adds a new provider.
  541. * The provider is added to the end of the current provider list.
  542. * @param The new provider to add
  543. */
  544. public void addProvider(ImageProvider provider) {
  545. if (providers == null) {
  546. providers = new ArrayList(4); // Assume we only have a few providers
  547. }
  548. if (!providers.contains(provider)) {
  549. providers.add(provider);
  550. }
  551. }
  552. }