|
|
@@ -0,0 +1,391 @@ |
|
|
|
/* |
|
|
|
* 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; |
|
|
|
|
|
|
|
/** |
|
|
|
* This class is a ResourceBundle that loads its contents from XML files instead of properties |
|
|
|
* files (like PropertiesResourceBunde). |
|
|
|
* <p> |
|
|
|
* The XML format for this resource bundle implementation is the following |
|
|
|
* (the same as Apache Cocoon's XMLResourceBundle): |
|
|
|
* <pre> |
|
|
|
* <catalogue xml:lang="en"> |
|
|
|
* <message key="key1">Message <br/> Value 1</message> |
|
|
|
* <message key="key2">Message <br/> Value 1</message> |
|
|
|
* ... |
|
|
|
* </catalogue> |
|
|
|
* </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 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); |
|
|
|
} |
|
|
|
|
|
|
|
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); |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |