aboutsummaryrefslogtreecommitdiffstats
path: root/src/java/org/apache/fop/util/XMLResourceBundle.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/java/org/apache/fop/util/XMLResourceBundle.java')
-rw-r--r--src/java/org/apache/fop/util/XMLResourceBundle.java398
1 files changed, 398 insertions, 0 deletions
diff --git a/src/java/org/apache/fop/util/XMLResourceBundle.java b/src/java/org/apache/fop/util/XMLResourceBundle.java
new file mode 100644
index 000000000..1b320816b
--- /dev/null
+++ b/src/java/org/apache/fop/util/XMLResourceBundle.java
@@ -0,0 +1,398 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Locale;
+import java.util.Map;
+import java.util.MissingResourceException;
+import java.util.Properties;
+import java.util.ResourceBundle;
+import java.util.Stack;
+
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.sax.SAXResult;
+import javax.xml.transform.sax.SAXTransformerFactory;
+import javax.xml.transform.stream.StreamSource;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import org.apache.xmlgraphics.util.QName;
+
+/**
+ * This class is a ResourceBundle that loads its contents from XML files instead of properties
+ * files (like PropertiesResourceBundle).
+ * <p>
+ * The XML format for this resource bundle implementation is the following
+ * (the same as Apache Cocoon's XMLResourceBundle):
+ * <pre>
+ * &lt;catalogue xml:lang="en"&gt;
+ * &lt;message key="key1"&gt;Message &lt;br/&gt; Value 1&lt;/message&gt;
+ * &lt;message key="key2"&gt;Message &lt;br/&gt; Value 1&lt;/message&gt;
+ * ...
+ * &lt;/catalogue&gt;
+ * </pre>
+ */
+public class XMLResourceBundle extends ResourceBundle {
+
+ //Note: Some code here has been copied and adapted from Apache Harmony!
+
+ private Properties resources = new Properties();
+
+ private Locale locale;
+
+ private static SAXTransformerFactory tFactory
+ = (SAXTransformerFactory)SAXTransformerFactory.newInstance();
+
+ /**
+ * Creates a resource bundle from an InputStream.
+ * @param in the stream to read from
+ * @throws IOException if an I/O error occurs
+ */
+ public XMLResourceBundle(InputStream in) throws IOException {
+ try {
+ Transformer transformer = tFactory.newTransformer();
+ StreamSource src = new StreamSource(in);
+ SAXResult res = new SAXResult(new CatalogueHandler());
+ transformer.transform(src, res);
+ } catch (TransformerException e) {
+ throw new IOException("Error while parsing XML resource bundle: " + e.getMessage());
+ }
+ }
+
+ /**
+ * Gets a resource bundle using the specified base name, default locale, and class loader.
+ * @param baseName the base name of the resource bundle, a fully qualified class name
+ * @param loader the class loader from which to load the resource bundle
+ * @return a resource bundle for the given base name and the default locale
+ * @throws MissingResourceException if no resource bundle for the specified base name can be
+ * found
+ * @see java.util.ResourceBundle#getBundle(String)
+ */
+ public static ResourceBundle getXMLBundle(String baseName, ClassLoader loader)
+ throws MissingResourceException {
+ return getXMLBundle(baseName, Locale.getDefault(), loader);
+ }
+
+ /**
+ * Gets a resource bundle using the specified base name, locale, and class loader.
+ * @param baseName the base name of the resource bundle, a fully qualified class name
+ * @param locale the locale for which a resource bundle is desired
+ * @param loader the class loader from which to load the resource bundle
+ * @return a resource bundle for the given base name and locale
+ * @throws MissingResourceException if no resource bundle for the specified base name can be
+ * found
+ * @see java.util.ResourceBundle#getBundle(String, Locale, ClassLoader)
+ */
+ public static ResourceBundle getXMLBundle(String baseName, Locale locale, ClassLoader loader)
+ throws MissingResourceException {
+ if (loader == null) {
+ throw new NullPointerException("loader must not be null");
+ }
+ if (baseName == null) {
+ throw new NullPointerException("baseName must not be null");
+ }
+
+ ResourceBundle bundle;
+ if (!locale.equals(Locale.getDefault())) {
+ bundle = handleGetXMLBundle(baseName, "_" + locale, false, loader);
+ if (bundle != null) {
+ return bundle;
+ }
+ }
+ bundle = handleGetXMLBundle(baseName, "_" + Locale.getDefault(), true, loader);
+ if (bundle != null) {
+ return bundle;
+ }
+ throw new MissingResourceException(
+ baseName + " (" + locale + ")", baseName + '_' + locale, null);
+ }
+
+ static class MissingBundle extends ResourceBundle {
+ public Enumeration getKeys() {
+ return null;
+ }
+
+ public Object handleGetObject(String name) {
+ return null;
+ }
+ }
+
+ private static final ResourceBundle MISSING = new MissingBundle();
+ private static final ResourceBundle MISSINGBASE = new MissingBundle();
+
+ private static Map cache = new java.util.WeakHashMap();
+ //<Object, Hashtable<String, ResourceBundle>>
+
+ private static ResourceBundle handleGetXMLBundle(String base, String locale,
+ boolean loadBase, final ClassLoader loader) {
+ XMLResourceBundle bundle = null;
+ String bundleName = base + locale;
+ Object cacheKey = loader != null ? (Object) loader : (Object) "null";
+ Hashtable loaderCache; //<String, ResourceBundle>
+ synchronized (cache) {
+ loaderCache = (Hashtable)cache.get(cacheKey);
+ if (loaderCache == null) {
+ loaderCache = new Hashtable();
+ cache.put(cacheKey, loaderCache);
+ }
+ }
+ ResourceBundle result = (ResourceBundle)loaderCache.get(bundleName);
+ if (result != null) {
+ if (result == MISSINGBASE) {
+ return null;
+ }
+ if (result == MISSING) {
+ if (!loadBase) {
+ return null;
+ }
+ String extension = strip(locale);
+ if (extension == null) {
+ return null;
+ }
+ return handleGetXMLBundle(base, extension, loadBase, loader);
+ }
+ return result;
+ }
+
+ final String fileName = bundleName.replace('.', '/') + ".xml";
+ InputStream stream = (InputStream)AccessController
+ .doPrivileged(new PrivilegedAction() {
+ public Object run() {
+ return loader == null
+ ? ClassLoader.getSystemResourceAsStream(fileName)
+ : loader.getResourceAsStream(fileName);
+ }
+ });
+ if (stream != null) {
+ try {
+ try {
+ bundle = new XMLResourceBundle(stream);
+ } finally {
+ stream.close();
+ }
+ bundle.setLocale(locale);
+ } catch (IOException e) {
+ throw new MissingResourceException(e.getMessage(), base, null);
+ }
+ }
+
+ String extension = strip(locale);
+ if (bundle != null) {
+ if (extension != null) {
+ ResourceBundle parent = handleGetXMLBundle(base, extension, true,
+ loader);
+ if (parent != null) {
+ bundle.setParent(parent);
+ }
+ }
+ loaderCache.put(bundleName, bundle);
+ return bundle;
+ }
+
+ if (extension != null) {
+ ResourceBundle fallback = handleGetXMLBundle(base, extension, loadBase, loader);
+ if (fallback != null) {
+ loaderCache.put(bundleName, fallback);
+ return fallback;
+ }
+ }
+ loaderCache.put(bundleName, loadBase ? MISSINGBASE : MISSING);
+ return null;
+ }
+
+ private void setLocale(String name) {
+ String language = "", country = "", variant = "";
+ if (name.length() > 1) {
+ int nextIndex = name.indexOf('_', 1);
+ if (nextIndex == -1) {
+ nextIndex = name.length();
+ }
+ language = name.substring(1, nextIndex);
+ if (nextIndex + 1 < name.length()) {
+ int index = nextIndex;
+ nextIndex = name.indexOf('_', nextIndex + 1);
+ if (nextIndex == -1) {
+ nextIndex = name.length();
+ }
+ country = name.substring(index + 1, nextIndex);
+ if (nextIndex + 1 < name.length()) {
+ variant = name.substring(nextIndex + 1, name.length());
+ }
+ }
+ }
+ this.locale = new Locale(language, country, variant);
+ }
+
+ private static String strip(String name) {
+ int index = name.lastIndexOf('_');
+ if (index != -1) {
+ return name.substring(0, index);
+ }
+ return null;
+ }
+
+ private Enumeration getLocalKeys() {
+ return (Enumeration)resources.propertyNames();
+ }
+
+ /** {@inheritDoc} */
+ public Locale getLocale() {
+ return this.locale;
+ }
+
+ /** {@inheritDoc} */
+ public Enumeration getKeys() {
+ if (parent == null) {
+ return getLocalKeys();
+ }
+ return new Enumeration() {
+ private Enumeration local = getLocalKeys();
+ private Enumeration pEnum = parent.getKeys();
+
+ private Object nextElement;
+
+ private boolean findNext() {
+ if (nextElement != null) {
+ return true;
+ }
+ while (pEnum.hasMoreElements()) {
+ Object next = pEnum.nextElement();
+ if (!resources.containsKey(next)) {
+ nextElement = next;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public boolean hasMoreElements() {
+ if (local.hasMoreElements()) {
+ return true;
+ }
+ return findNext();
+ }
+
+ public Object nextElement() {
+ if (local.hasMoreElements()) {
+ return local.nextElement();
+ }
+ if (findNext()) {
+ Object result = nextElement;
+ nextElement = null;
+ return result;
+ }
+ // Cause an exception
+ return pEnum.nextElement();
+ }
+ };
+ }
+
+ /** {@inheritDoc} */
+ protected Object handleGetObject(String key) {
+ if (key == null) {
+ throw new NullPointerException("key must not be null");
+ }
+ return resources.get(key);
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ return "XMLResourceBundle: " + getLocale();
+ }
+
+ private class CatalogueHandler extends DefaultHandler {
+
+ private static final String CATALOGUE = "catalogue";
+ private static final String MESSAGE = "message";
+
+ private StringBuffer valueBuffer = new StringBuffer();
+ private Stack elementStack = new Stack();
+ private String currentKey = null;
+
+ private boolean isOwnNamespace(String uri) {
+ return ("".equals(uri));
+ }
+
+ private QName getParentElementName() {
+ return (QName)elementStack.peek();
+ }
+
+ /** {@inheritDoc} */
+ public void startElement(String uri, String localName, String qName,
+ Attributes atts) throws SAXException {
+ super.startElement(uri, localName, qName, atts);
+ QName elementName = new QName(uri, qName);
+ if (isOwnNamespace(uri)) {
+ if (CATALOGUE.equals(localName)) {
+ //nop
+ } else if (MESSAGE.equals(localName)) {
+ if (!CATALOGUE.equals(getParentElementName().getLocalName())) {
+ throw new SAXException(MESSAGE + " must be a child of " + CATALOGUE);
+ }
+ this.currentKey = atts.getValue("key");
+ } else {
+ throw new SAXException("Invalid element name: " + elementName);
+ }
+ } else {
+ //ignore
+ }
+ this.valueBuffer.setLength(0);
+ elementStack.push(elementName);
+ }
+
+ /** {@inheritDoc} */
+ public void endElement(String uri, String localName, String qName) throws SAXException {
+ super.endElement(uri, localName, qName);
+ elementStack.pop();
+ if (isOwnNamespace(uri)) {
+ if (CATALOGUE.equals(localName)) {
+ //nop
+ } else if (MESSAGE.equals(localName)) {
+ if (this.currentKey == null) {
+ throw new SAXException(
+ "current key is null (attribute 'key' might be mistyped)");
+ }
+ resources.put(this.currentKey, this.valueBuffer.toString());
+ this.currentKey = null;
+ }
+ } else {
+ //ignore
+ }
+ this.valueBuffer.setLength(0);
+ }
+
+ /** {@inheritDoc} */
+ public void characters(char[] ch, int start, int length) throws SAXException {
+ super.characters(ch, start, length);
+ valueBuffer.append(ch, start, length);
+ }
+
+ }
+
+}