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 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509
  1. /*
  2. * Copyright 1999-2004 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$ */
  17. package org.apache.fop.image;
  18. // Java
  19. import java.io.IOException;
  20. import java.io.InputStream;
  21. import java.net.URL;
  22. import java.net.MalformedURLException;
  23. import java.lang.reflect.Constructor;
  24. import java.util.Map;
  25. import java.util.Set;
  26. import java.util.Collections;
  27. import java.util.Iterator;
  28. // Avalon
  29. import org.apache.avalon.framework.logger.Logger;
  30. // FOP
  31. import org.apache.fop.image.analyser.ImageReaderFactory;
  32. import org.apache.fop.apps.FOUserAgent;
  33. /**
  34. * Create FopImage objects (with a configuration file - not yet implemented).
  35. * @author Eric SCHAEFFER
  36. */
  37. public class ImageFactory {
  38. private static ImageFactory factory = new ImageFactory();
  39. private ImageCache cache = new ContextImageCache(true);
  40. private ImageFactory() {
  41. }
  42. /**
  43. * Get static image factory instance.
  44. *
  45. * @return the image factory instance
  46. */
  47. public static ImageFactory getInstance() {
  48. return factory;
  49. }
  50. /**
  51. * Get the url string from a wrapped url.
  52. *
  53. * @param href the input wrapped url
  54. * @return the raw url
  55. */
  56. public static String getURL(String href) {
  57. /*
  58. * According to section 5.11 a <uri-specification> is:
  59. * "url(" + URI + ")"
  60. * according to 7.28.7 a <uri-specification> is:
  61. * URI
  62. * So handle both.
  63. */
  64. href = href.trim();
  65. if (href.startsWith("url(") && (href.indexOf(")") != -1)) {
  66. href = href.substring(4, href.indexOf(")")).trim();
  67. if (href.startsWith("'") && href.endsWith("'")) {
  68. href = href.substring(1, href.length() - 1);
  69. } else if (href.startsWith("\"") && href.endsWith("\"")) {
  70. href = href.substring(1, href.length() - 1);
  71. }
  72. } else {
  73. // warn
  74. }
  75. return href;
  76. }
  77. /**
  78. * Get the image from the cache or load.
  79. * If this returns null then the image could not be loaded
  80. * due to an error. Messages should be logged.
  81. * Before calling this the getURL(url) must be used.
  82. *
  83. * @param url the url for the image
  84. * @param context the user agent context
  85. * @return the fop image instance
  86. */
  87. public FopImage getImage(String url, FOUserAgent context) {
  88. return cache.getImage(url, context);
  89. }
  90. /**
  91. * Release an image from the cache.
  92. * This can be used if the renderer has its own cache of
  93. * the image.
  94. * The image should then be put into the weak cache.
  95. *
  96. * @param url the url for the image
  97. * @param context the user agent context
  98. */
  99. public void releaseImage(String url, FOUserAgent context) {
  100. cache.releaseImage(url, context);
  101. }
  102. /**
  103. * Release the context and all images in the context.
  104. *
  105. * @param context the context to remove
  106. */
  107. public void removeContext(FOUserAgent context) {
  108. cache.removeContext(context);
  109. }
  110. /**
  111. * Create an FopImage objects.
  112. * @param href the url for the image
  113. * @param ua the user agent context
  114. * @return the fop image instance
  115. */
  116. public static FopImage loadImage(String href, FOUserAgent ua) {
  117. Logger log = ua.getLogger();
  118. InputStream in = openStream(href, ua);
  119. if (in == null) {
  120. return null;
  121. }
  122. // If not, check image type
  123. FopImage.ImageInfo imgInfo = null;
  124. try {
  125. imgInfo = ImageReaderFactory.make(
  126. href, in, ua);
  127. } catch (Exception e) {
  128. log.error("Error while recovering image information ("
  129. + href + ") : " + e.getMessage(), e);
  130. return null;
  131. }
  132. if (imgInfo == null) {
  133. try {
  134. in.close();
  135. in = null;
  136. } catch (Exception e) {
  137. log.debug("Error closing the InputStream for the image", e);
  138. }
  139. log.error("No ImageReader for this type of image ("
  140. + href + ")");
  141. return null;
  142. }
  143. // Associate mime-type to FopImage class
  144. String imgMimeType = imgInfo.mimeType;
  145. String imgClassName = getImageClassName(imgMimeType);
  146. if (imgClassName == null) {
  147. log.error("Unsupported image type ("
  148. + href + "): " + imgMimeType);
  149. return null;
  150. }
  151. // load the right image class
  152. // return new <FopImage implementing class>
  153. Object imageInstance = null;
  154. Class imageClass = null;
  155. try {
  156. imageClass = Class.forName(imgClassName);
  157. Class[] imageConstructorParameters = new Class[1];
  158. imageConstructorParameters[0] = org.apache.fop.image.FopImage.ImageInfo.class;
  159. Constructor imageConstructor =
  160. imageClass.getDeclaredConstructor(
  161. imageConstructorParameters);
  162. Object[] initArgs = new Object[1];
  163. initArgs[0] = imgInfo;
  164. imageInstance = imageConstructor.newInstance(initArgs);
  165. } catch (java.lang.reflect.InvocationTargetException ex) {
  166. Throwable t = ex.getTargetException();
  167. String msg;
  168. if (t != null) {
  169. msg = t.getMessage();
  170. } else {
  171. msg = ex.getMessage();
  172. }
  173. log.error("Error creating FopImage object ("
  174. + href + "): " + msg, (t == null) ? ex : t);
  175. return null;
  176. } catch (Exception ex) {
  177. log.error("Error creating FopImage object ("
  178. + href + "): " + ex.getMessage(), ex);
  179. return null;
  180. }
  181. if (!(imageInstance instanceof org.apache.fop.image.FopImage)) {
  182. log.error("Error creating FopImage object ("
  183. + href + "): " + "class "
  184. + imageClass.getName()
  185. + " doesn't implement org.apache.fop.image.FopImage interface");
  186. return null;
  187. }
  188. return (FopImage) imageInstance;
  189. }
  190. /**
  191. * Create an FopImage objects.
  192. * @param href image URL as a String
  193. * @param ua user agent
  194. * @return a new FopImage object
  195. */
  196. protected static InputStream openStream(String href, FOUserAgent ua) {
  197. Logger log = ua.getLogger();
  198. // Get the absolute URL
  199. URL absoluteURL = null;
  200. InputStream in = null;
  201. try {
  202. in = ua.getStream(href);
  203. } catch (IOException ioe) {
  204. log.error("Error while opening stream for ("
  205. + href + "): " + ioe.getMessage(), ioe);
  206. return null;
  207. }
  208. if (in == null) {
  209. try {
  210. // try url as complete first, this can cause
  211. // a problem with relative uri's if there is an
  212. // image relative to where fop is run and relative
  213. // to the base dir of the document
  214. try {
  215. absoluteURL = new URL(href);
  216. } catch (MalformedURLException mue) {
  217. // if the href contains only a path then file is assumed
  218. absoluteURL = new URL("file:" + href);
  219. }
  220. in = absoluteURL.openStream();
  221. } catch (MalformedURLException mfue) {
  222. log.error("Error with image URL: " + mfue.getMessage(), mfue);
  223. return null;
  224. } catch (Exception e) {
  225. // maybe relative
  226. if (ua.getBaseURL() == null) {
  227. log.error("Error with image URL: " + e.getMessage()
  228. + " and no base URL is specified", e);
  229. return null;
  230. }
  231. try {
  232. absoluteURL = new URL(ua.getBaseURL() + absoluteURL.getFile());
  233. } catch (MalformedURLException e_context) {
  234. // pb context url
  235. log.error("Invalid Image URL - error on relative URL: "
  236. + e_context.getMessage(), e_context);
  237. return null;
  238. }
  239. }
  240. } /* if (in == null) */
  241. try {
  242. if (in == null && absoluteURL != null) {
  243. in = absoluteURL.openStream();
  244. }
  245. if (in == null) {
  246. log.error("Could not resolve URI for image: " + href);
  247. return null;
  248. }
  249. //Decorate the InputStream with a BufferedInputStream
  250. return new java.io.BufferedInputStream(in);
  251. } catch (Exception e) {
  252. log.error("Error while opening stream for ("
  253. + href + "): " + e.getMessage(), e);
  254. return null;
  255. }
  256. }
  257. private static String getImageClassName(String imgMimeType) {
  258. String imgClassName = null;
  259. if ("image/gif".equals(imgMimeType)) {
  260. imgClassName = "org.apache.fop.image.GifImage";
  261. // imgClassName = "org.apache.fop.image.JAIImage";
  262. } else if ("image/jpeg".equals(imgMimeType)) {
  263. imgClassName = "org.apache.fop.image.JpegImage";
  264. // imgClassName = "org.apache.fop.image.JAIImage";
  265. } else if ("image/bmp".equals(imgMimeType)) {
  266. imgClassName = "org.apache.fop.image.BmpImage";
  267. // imgClassName = "org.apache.fop.image.JAIImage";
  268. } else if ("image/eps".equals(imgMimeType)) {
  269. imgClassName = "org.apache.fop.image.EPSImage";
  270. } else if ("image/png".equals(imgMimeType)) {
  271. imgClassName = "org.apache.fop.image.JimiImage";
  272. // imgClassName = "org.apache.fop.image.JAIImage";
  273. } else if ("image/tga".equals(imgMimeType)) {
  274. imgClassName = "org.apache.fop.image.JimiImage";
  275. // imgClassName = "org.apache.fop.image.JAIImage";
  276. } else if ("image/tiff".equals(imgMimeType)) {
  277. imgClassName = "org.apache.fop.image.JimiImage";
  278. // imgClassName = "org.apache.fop.image.JAIImage";
  279. } else if ("image/svg+xml".equals(imgMimeType)) {
  280. imgClassName = "org.apache.fop.image.XMLImage";
  281. } else if ("text/xml".equals(imgMimeType)) {
  282. imgClassName = "org.apache.fop.image.XMLImage";
  283. }
  284. return imgClassName;
  285. }
  286. }
  287. /**
  288. * Basic image cache.
  289. * This keeps track of invalid images.
  290. */
  291. class BasicImageCache implements ImageCache {
  292. private Set invalid = Collections.synchronizedSet(new java.util.HashSet());
  293. private Map contextStore = Collections.synchronizedMap(new java.util.HashMap());
  294. public FopImage getImage(String url, FOUserAgent context) {
  295. if (invalid.contains(url)) {
  296. return null;
  297. }
  298. return null;
  299. }
  300. public void releaseImage(String url, FOUserAgent context) {
  301. // do nothing
  302. }
  303. public void invalidateImage(String url, FOUserAgent context) {
  304. // cap size of invalid list
  305. if (invalid.size() > 100) {
  306. invalid.clear();
  307. }
  308. invalid.add(url);
  309. }
  310. public void removeContext(FOUserAgent context) {
  311. // do nothing
  312. }
  313. }
  314. /**
  315. * This is the context image cache.
  316. * This caches images on the basis of the given context.
  317. * Common images in different contexts are currently not handled.
  318. * There are two possiblities, each context handles its own images
  319. * and renderers can cache information or images are shared and
  320. * all information is retained.
  321. * Once a context is removed then all images are placed into a
  322. * weak hashmap so they may be garbage collected.
  323. */
  324. class ContextImageCache implements ImageCache {
  325. // if this cache is collective then images can be shared
  326. // among contexts, this implies that the base directory
  327. // is either the same or does not effect the images being
  328. // loaded
  329. private boolean collective;
  330. private Map contextStore = Collections.synchronizedMap(new java.util.HashMap());
  331. private Set invalid = null;
  332. private Map weakStore = null;
  333. public ContextImageCache(boolean col) {
  334. collective = col;
  335. if (collective) {
  336. weakStore = Collections.synchronizedMap(new java.util.WeakHashMap());
  337. invalid = Collections.synchronizedSet(new java.util.HashSet());
  338. }
  339. }
  340. // sync around lookups and puts
  341. // another sync around load for a particular image
  342. public FopImage getImage(String url, FOUserAgent context) {
  343. ImageLoader im = null;
  344. // this protects the finding or creating of a new
  345. // ImageLoader for multi threads
  346. synchronized (this) {
  347. if (collective && invalid.contains(url)) {
  348. return null;
  349. }
  350. Context con = (Context) contextStore.get(context);
  351. if (con == null) {
  352. con = new Context(context, collective);
  353. contextStore.put(context, con);
  354. } else {
  355. if (con.invalid(url)) {
  356. return null;
  357. }
  358. im = con.getImage(url);
  359. }
  360. if (im == null && collective) {
  361. Iterator i = contextStore.values().iterator();
  362. while (i.hasNext()) {
  363. Context c = (Context)i.next();
  364. if (c != con) {
  365. im = c.getImage(url);
  366. if (im != null) {
  367. break;
  368. }
  369. }
  370. }
  371. if (im == null) {
  372. im = (ImageLoader) weakStore.get(url);
  373. }
  374. }
  375. if (im != null) {
  376. con.putImage(url, im);
  377. } else {
  378. im = con.getImage(url, this);
  379. }
  380. }
  381. // the ImageLoader is synchronized so images with the
  382. // same url will not be loaded at the same time
  383. if (im != null) {
  384. return im.loadImage();
  385. }
  386. return null;
  387. }
  388. public void releaseImage(String url, FOUserAgent context) {
  389. Context con = (Context) contextStore.get(context);
  390. if (con != null) {
  391. if (collective) {
  392. ImageLoader im = con.getImage(url);
  393. weakStore.put(url, im);
  394. }
  395. con.releaseImage(url);
  396. }
  397. }
  398. public void invalidateImage(String url, FOUserAgent context) {
  399. if (collective) {
  400. // cap size of invalid list
  401. if (invalid.size() > 100) {
  402. invalid.clear();
  403. }
  404. invalid.add(url);
  405. }
  406. Context con = (Context) contextStore.get(context);
  407. if (con != null) {
  408. con.invalidateImage(url);
  409. }
  410. }
  411. public void removeContext(FOUserAgent context) {
  412. Context con = (Context) contextStore.get(context);
  413. if (con != null) {
  414. if (collective) {
  415. Map images = con.getImages();
  416. weakStore.putAll(images);
  417. }
  418. contextStore.remove(context);
  419. }
  420. }
  421. class Context {
  422. private Map images = Collections.synchronizedMap(new java.util.HashMap());
  423. private Set invalid = null;
  424. private FOUserAgent userAgent;
  425. public Context(FOUserAgent ua, boolean inv) {
  426. userAgent = ua;
  427. if (inv) {
  428. invalid = Collections.synchronizedSet(new java.util.HashSet());
  429. }
  430. }
  431. public ImageLoader getImage(String url, ImageCache c) {
  432. if (images.containsKey(url)) {
  433. return (ImageLoader) images.get(url);
  434. }
  435. ImageLoader loader = new ImageLoader(url, c, userAgent);
  436. images.put(url, loader);
  437. return loader;
  438. }
  439. public void putImage(String url, ImageLoader image) {
  440. images.put(url, image);
  441. }
  442. public ImageLoader getImage(String url) {
  443. return (ImageLoader) images.get(url);
  444. }
  445. public void releaseImage(String url) {
  446. images.remove(url);
  447. }
  448. public Map getImages() {
  449. return images;
  450. }
  451. public void invalidateImage(String url) {
  452. invalid.add(url);
  453. }
  454. public boolean invalid(String url) {
  455. return invalid.contains(url);
  456. }
  457. }
  458. }