blob: 80aec2d2f0d682732af9a5208358003bd2d19f99 [file] [log] [blame]
/*
* 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");
}
assert locale != 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 = "";
String country = "";
String 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;
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);
}
}
}