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.

XMLResourceBundle.java 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  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. /* $Id$ */
  18. package org.apache.fop.util;
  19. import java.io.IOException;
  20. import java.io.InputStream;
  21. import java.security.AccessController;
  22. import java.security.PrivilegedAction;
  23. import java.util.Enumeration;
  24. import java.util.Hashtable;
  25. import java.util.Locale;
  26. import java.util.Map;
  27. import java.util.MissingResourceException;
  28. import java.util.Properties;
  29. import java.util.ResourceBundle;
  30. import java.util.Stack;
  31. import javax.xml.transform.Transformer;
  32. import javax.xml.transform.TransformerException;
  33. import javax.xml.transform.sax.SAXResult;
  34. import javax.xml.transform.sax.SAXTransformerFactory;
  35. import javax.xml.transform.stream.StreamSource;
  36. import org.xml.sax.Attributes;
  37. import org.xml.sax.SAXException;
  38. import org.xml.sax.helpers.DefaultHandler;
  39. import org.apache.xmlgraphics.util.QName;
  40. /**
  41. * This class is a ResourceBundle that loads its contents from XML files instead of properties
  42. * files (like PropertiesResourceBundle).
  43. * <p>
  44. * The XML format for this resource bundle implementation is the following
  45. * (the same as Apache Cocoon's XMLResourceBundle):
  46. * <pre>
  47. * &lt;catalogue xml:lang="en"&gt;
  48. * &lt;message key="key1"&gt;Message &lt;br/&gt; Value 1&lt;/message&gt;
  49. * &lt;message key="key2"&gt;Message &lt;br/&gt; Value 1&lt;/message&gt;
  50. * ...
  51. * &lt;/catalogue&gt;
  52. * </pre>
  53. */
  54. public class XMLResourceBundle extends ResourceBundle {
  55. //Note: Some code here has been copied and adapted from Apache Harmony!
  56. private Properties resources = new Properties();
  57. private Locale locale;
  58. private static SAXTransformerFactory tFactory
  59. = (SAXTransformerFactory)SAXTransformerFactory.newInstance();
  60. /**
  61. * Creates a resource bundle from an InputStream.
  62. * @param in the stream to read from
  63. * @throws IOException if an I/O error occurs
  64. */
  65. public XMLResourceBundle(InputStream in) throws IOException {
  66. try {
  67. Transformer transformer = tFactory.newTransformer();
  68. StreamSource src = new StreamSource(in);
  69. SAXResult res = new SAXResult(new CatalogueHandler());
  70. transformer.transform(src, res);
  71. } catch (TransformerException e) {
  72. throw new IOException("Error while parsing XML resource bundle: " + e.getMessage());
  73. }
  74. }
  75. /**
  76. * Gets a resource bundle using the specified base name, default locale, and class loader.
  77. * @param baseName the base name of the resource bundle, a fully qualified class name
  78. * @param loader the class loader from which to load the resource bundle
  79. * @return a resource bundle for the given base name and the default locale
  80. * @throws MissingResourceException if no resource bundle for the specified base name can be
  81. * found
  82. * @see java.util.ResourceBundle#getBundle(String)
  83. */
  84. public static ResourceBundle getXMLBundle(String baseName, ClassLoader loader)
  85. throws MissingResourceException {
  86. return getXMLBundle(baseName, Locale.getDefault(), loader);
  87. }
  88. /**
  89. * Gets a resource bundle using the specified base name, locale, and class loader.
  90. * @param baseName the base name of the resource bundle, a fully qualified class name
  91. * @param locale the locale for which a resource bundle is desired
  92. * @param loader the class loader from which to load the resource bundle
  93. * @return a resource bundle for the given base name and locale
  94. * @throws MissingResourceException if no resource bundle for the specified base name can be
  95. * found
  96. * @see java.util.ResourceBundle#getBundle(String, Locale, ClassLoader)
  97. */
  98. public static ResourceBundle getXMLBundle(String baseName, Locale locale, ClassLoader loader)
  99. throws MissingResourceException {
  100. if (loader == null) {
  101. throw new NullPointerException("loader must not be null");
  102. }
  103. if (baseName == null) {
  104. throw new NullPointerException("baseName must not be null");
  105. }
  106. assert locale != null;
  107. ResourceBundle bundle;
  108. if (!locale.equals(Locale.getDefault())) {
  109. bundle = handleGetXMLBundle(baseName, "_" + locale, false, loader);
  110. if (bundle != null) {
  111. return bundle;
  112. }
  113. }
  114. bundle = handleGetXMLBundle(baseName, "_" + Locale.getDefault(), true, loader);
  115. if (bundle != null) {
  116. return bundle;
  117. }
  118. throw new MissingResourceException(
  119. baseName + " (" + locale + ")", baseName + '_' + locale, null);
  120. }
  121. static class MissingBundle extends ResourceBundle {
  122. public Enumeration getKeys() {
  123. return null;
  124. }
  125. public Object handleGetObject(String name) {
  126. return null;
  127. }
  128. }
  129. private static final ResourceBundle MISSING = new MissingBundle();
  130. private static final ResourceBundle MISSINGBASE = new MissingBundle();
  131. private static Map cache = new java.util.WeakHashMap();
  132. //<Object, Hashtable<String, ResourceBundle>>
  133. private static ResourceBundle handleGetXMLBundle(String base, String locale,
  134. boolean loadBase, final ClassLoader loader) {
  135. XMLResourceBundle bundle = null;
  136. String bundleName = base + locale;
  137. Object cacheKey = loader != null ? (Object) loader : (Object) "null";
  138. Hashtable loaderCache; //<String, ResourceBundle>
  139. synchronized (cache) {
  140. loaderCache = (Hashtable)cache.get(cacheKey);
  141. if (loaderCache == null) {
  142. loaderCache = new Hashtable();
  143. cache.put(cacheKey, loaderCache);
  144. }
  145. }
  146. ResourceBundle result = (ResourceBundle)loaderCache.get(bundleName);
  147. if (result != null) {
  148. if (result == MISSINGBASE) {
  149. return null;
  150. }
  151. if (result == MISSING) {
  152. if (!loadBase) {
  153. return null;
  154. }
  155. String extension = strip(locale);
  156. if (extension == null) {
  157. return null;
  158. }
  159. return handleGetXMLBundle(base, extension, loadBase, loader);
  160. }
  161. return result;
  162. }
  163. final String fileName = bundleName.replace('.', '/') + ".xml";
  164. InputStream stream = (InputStream)AccessController
  165. .doPrivileged(new PrivilegedAction() {
  166. public Object run() {
  167. return loader == null
  168. ? ClassLoader.getSystemResourceAsStream(fileName)
  169. : loader.getResourceAsStream(fileName);
  170. }
  171. });
  172. if (stream != null) {
  173. try {
  174. try {
  175. bundle = new XMLResourceBundle(stream);
  176. } finally {
  177. stream.close();
  178. }
  179. bundle.setLocale(locale);
  180. } catch (IOException e) {
  181. throw new MissingResourceException(e.getMessage(), base, null);
  182. }
  183. }
  184. String extension = strip(locale);
  185. if (bundle != null) {
  186. if (extension != null) {
  187. ResourceBundle parent = handleGetXMLBundle(base, extension, true,
  188. loader);
  189. if (parent != null) {
  190. bundle.setParent(parent);
  191. }
  192. }
  193. loaderCache.put(bundleName, bundle);
  194. return bundle;
  195. }
  196. if (extension != null) {
  197. ResourceBundle fallback = handleGetXMLBundle(base, extension, loadBase, loader);
  198. if (fallback != null) {
  199. loaderCache.put(bundleName, fallback);
  200. return fallback;
  201. }
  202. }
  203. loaderCache.put(bundleName, loadBase ? MISSINGBASE : MISSING);
  204. return null;
  205. }
  206. private void setLocale(String name) {
  207. String language = "";
  208. String country = "";
  209. String variant = "";
  210. if (name.length() > 1) {
  211. int nextIndex = name.indexOf('_', 1);
  212. if (nextIndex == -1) {
  213. nextIndex = name.length();
  214. }
  215. language = name.substring(1, nextIndex);
  216. if (nextIndex + 1 < name.length()) {
  217. int index = nextIndex;
  218. nextIndex = name.indexOf('_', nextIndex + 1);
  219. if (nextIndex == -1) {
  220. nextIndex = name.length();
  221. }
  222. country = name.substring(index + 1, nextIndex);
  223. if (nextIndex + 1 < name.length()) {
  224. variant = name.substring(nextIndex + 1, name.length());
  225. }
  226. }
  227. }
  228. this.locale = new Locale(language, country, variant);
  229. }
  230. private static String strip(String name) {
  231. int index = name.lastIndexOf('_');
  232. if (index != -1) {
  233. return name.substring(0, index);
  234. }
  235. return null;
  236. }
  237. private Enumeration getLocalKeys() {
  238. return (Enumeration)resources.propertyNames();
  239. }
  240. /** {@inheritDoc} */
  241. public Locale getLocale() {
  242. return this.locale;
  243. }
  244. /** {@inheritDoc} */
  245. public Enumeration getKeys() {
  246. if (parent == null) {
  247. return getLocalKeys();
  248. }
  249. return new Enumeration() {
  250. private Enumeration local = getLocalKeys();
  251. private Enumeration pEnum = parent.getKeys();
  252. private Object nextElement;
  253. private boolean findNext() {
  254. if (nextElement != null) {
  255. return true;
  256. }
  257. while (pEnum.hasMoreElements()) {
  258. Object next = pEnum.nextElement();
  259. if (!resources.containsKey(next)) {
  260. nextElement = next;
  261. return true;
  262. }
  263. }
  264. return false;
  265. }
  266. public boolean hasMoreElements() {
  267. if (local.hasMoreElements()) {
  268. return true;
  269. }
  270. return findNext();
  271. }
  272. public Object nextElement() {
  273. if (local.hasMoreElements()) {
  274. return local.nextElement();
  275. }
  276. if (findNext()) {
  277. Object result = nextElement;
  278. nextElement = null;
  279. return result;
  280. }
  281. // Cause an exception
  282. return pEnum.nextElement();
  283. }
  284. };
  285. }
  286. /** {@inheritDoc} */
  287. protected Object handleGetObject(String key) {
  288. if (key == null) {
  289. throw new NullPointerException("key must not be null");
  290. }
  291. return resources.get(key);
  292. }
  293. /** {@inheritDoc} */
  294. public String toString() {
  295. return "XMLResourceBundle: " + getLocale();
  296. }
  297. private class CatalogueHandler extends DefaultHandler {
  298. private static final String CATALOGUE = "catalogue";
  299. private static final String MESSAGE = "message";
  300. private StringBuffer valueBuffer = new StringBuffer();
  301. private Stack elementStack = new Stack();
  302. private String currentKey;
  303. private boolean isOwnNamespace(String uri) {
  304. return ("".equals(uri));
  305. }
  306. private QName getParentElementName() {
  307. return (QName)elementStack.peek();
  308. }
  309. /** {@inheritDoc} */
  310. public void startElement(String uri, String localName, String qName,
  311. Attributes atts) throws SAXException {
  312. super.startElement(uri, localName, qName, atts);
  313. QName elementName = new QName(uri, qName);
  314. if (isOwnNamespace(uri)) {
  315. if (CATALOGUE.equals(localName)) {
  316. //nop
  317. } else if (MESSAGE.equals(localName)) {
  318. if (!CATALOGUE.equals(getParentElementName().getLocalName())) {
  319. throw new SAXException(MESSAGE + " must be a child of " + CATALOGUE);
  320. }
  321. this.currentKey = atts.getValue("key");
  322. } else {
  323. throw new SAXException("Invalid element name: " + elementName);
  324. }
  325. } else {
  326. //ignore
  327. }
  328. this.valueBuffer.setLength(0);
  329. elementStack.push(elementName);
  330. }
  331. /** {@inheritDoc} */
  332. public void endElement(String uri, String localName, String qName) throws SAXException {
  333. super.endElement(uri, localName, qName);
  334. elementStack.pop();
  335. if (isOwnNamespace(uri)) {
  336. if (CATALOGUE.equals(localName)) {
  337. //nop
  338. } else if (MESSAGE.equals(localName)) {
  339. if (this.currentKey == null) {
  340. throw new SAXException(
  341. "current key is null (attribute 'key' might be mistyped)");
  342. }
  343. resources.put(this.currentKey, this.valueBuffer.toString());
  344. this.currentKey = null;
  345. }
  346. } else {
  347. //ignore
  348. }
  349. this.valueBuffer.setLength(0);
  350. }
  351. /** {@inheritDoc} */
  352. public void characters(char[] ch, int start, int length) throws SAXException {
  353. super.characters(ch, start, length);
  354. valueBuffer.append(ch, start, length);
  355. }
  356. }
  357. }