import org.apache.fop.util.ContentHandlerFactory;
import org.apache.fop.util.ContentHandlerFactoryRegistry;
import org.apache.fop.util.ConversionUtils;
-import org.apache.fop.util.DefaultErrorListener;
import org.apache.fop.util.LanguageTags;
import org.apache.fop.util.XMLConstants;
import org.apache.fop.util.XMLUtil;
+import org.apache.fop.utils.DefaultErrorListener;
import static org.apache.fop.fo.Constants.FO_REGION_AFTER;
import static org.apache.fop.fo.Constants.FO_REGION_BEFORE;
import org.apache.fop.fo.pagination.Root;
import org.apache.fop.util.CharUtilities;
import org.apache.fop.util.ContentHandlerFactory;
-import org.apache.fop.util.text.AdvancedMessageFormat.Function;
+import org.apache.fop.utils.text.AdvancedMessageFormat.Function;
/**
* Base class for nodes in the XML tree
import org.apache.fop.util.ContentHandlerFactory;
import org.apache.fop.util.ContentHandlerFactoryRegistry;
import org.apache.fop.util.DOMBuilderContentHandlerFactory;
-import org.apache.fop.util.DefaultErrorListener;
import org.apache.fop.util.LanguageTags;
import org.apache.fop.util.XMLUtil;
+import org.apache.fop.utils.DefaultErrorListener;
/**
* This is a parser for the intermediate format XML which converts the intermediate file into
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import org.apache.fop.util.text.AdvancedMessageFormat.Part;
-import org.apache.fop.util.text.AdvancedMessageFormat.PartFactory;
+import org.apache.fop.utils.text.AdvancedMessageFormat;
+import org.apache.fop.utils.text.AdvancedMessageFormat.Part;
+import org.apache.fop.utils.text.AdvancedMessageFormat.PartFactory;
/**
* Defines a "choice" field part that works like {@link ChoiceFormat}.
import java.util.Map;
-import org.apache.fop.util.text.AdvancedMessageFormat.Part;
-import org.apache.fop.util.text.AdvancedMessageFormat.PartFactory;
+import org.apache.fop.utils.text.AdvancedMessageFormat;
+import org.apache.fop.utils.text.AdvancedMessageFormat.Part;
+import org.apache.fop.utils.text.AdvancedMessageFormat.PartFactory;
/**
* Defines an "equals" field part that can compare a field's string value against another string.
import org.apache.xmlgraphics.fonts.Glyphs;
-import org.apache.fop.util.text.AdvancedMessageFormat.Part;
-import org.apache.fop.util.text.AdvancedMessageFormat.PartFactory;
+import org.apache.fop.utils.text.AdvancedMessageFormat.Part;
+import org.apache.fop.utils.text.AdvancedMessageFormat.PartFactory;
/**
* Function formatting a character to a glyph name.
import java.util.Map;
-import org.apache.fop.util.text.AdvancedMessageFormat.Part;
-import org.apache.fop.util.text.AdvancedMessageFormat.PartFactory;
+import org.apache.fop.utils.text.AdvancedMessageFormat.Part;
+import org.apache.fop.utils.text.AdvancedMessageFormat.PartFactory;
/**
* Function formatting a number or character to a hex value.
import java.util.Map;
-import org.apache.fop.util.text.AdvancedMessageFormat.Part;
-import org.apache.fop.util.text.AdvancedMessageFormat.PartFactory;
+import org.apache.fop.utils.text.AdvancedMessageFormat;
+import org.apache.fop.utils.text.AdvancedMessageFormat.Part;
+import org.apache.fop.utils.text.AdvancedMessageFormat.PartFactory;
/**
* Defines an "if" field part that checks if field's value is true or false.
import org.xml.sax.Locator;
-import org.apache.fop.util.text.AdvancedMessageFormat.ObjectFormatter;
+import org.apache.fop.utils.text.AdvancedMessageFormat.ObjectFormatter;
/**
* Object formatter for the SAX Locator object.
+++ /dev/null
-org.apache.fop.fo.FONode$GatherContextInfoFunction
+++ /dev/null
-org.apache.fop.util.text.LocatorFormatter
+++ /dev/null
-org.apache.fop.util.text.IfFieldPart$Factory
-org.apache.fop.util.text.EqualsFieldPart$Factory
-org.apache.fop.util.text.ChoiceFieldPart$Factory
-org.apache.fop.util.text.HexFieldPart$Factory
-org.apache.fop.util.text.GlyphNameFieldPart$Factory
-org.apache.fop.events.EventFormatter$LookupFieldPartFactory
--- /dev/null
+org.apache.fop.fo.FONode$GatherContextInfoFunction
--- /dev/null
+org.apache.fop.util.text.LocatorFormatter
--- /dev/null
+org.apache.fop.util.text.IfFieldPart$Factory
+org.apache.fop.util.text.EqualsFieldPart$Factory
+org.apache.fop.util.text.ChoiceFieldPart$Factory
+org.apache.fop.util.text.HexFieldPart$Factory
+org.apache.fop.util.text.GlyphNameFieldPart$Factory
+org.apache.fop.events.EventFormatter$LookupFieldPartFactory
import static org.junit.Assert.assertEquals;
import org.apache.fop.events.model.EventSeverity;
-import org.apache.fop.util.text.AdvancedMessageFormat;
+import org.apache.fop.utils.text.AdvancedMessageFormat;
/**
* Tests for EventFormatter.
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
+import org.apache.fop.utils.XMLResourceBundle;
+
/**
* Tests for XMLResourceBundle.
*/
import org.apache.fop.configuration.Configurable;
import org.apache.fop.configuration.Configuration;
import org.apache.fop.configuration.ConfigurationException;
-import org.apache.fop.util.DefaultErrorListener;
+import org.apache.fop.utils.DefaultErrorListener;
/**
* BitmapProducer implementation that uses the PS or PDF renderer and an external converter
import org.apache.fop.configuration.Configurable;
import org.apache.fop.configuration.Configuration;
import org.apache.fop.configuration.ConfigurationException;
-import org.apache.fop.util.DefaultErrorListener;
+import org.apache.fop.utils.DefaultErrorListener;
/**
* BitmapProducer implementation that uses the Java2DRenderer to create bitmaps.
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-import org.apache.fop.util.XMLResourceBundle;
-import org.apache.fop.util.text.AdvancedMessageFormat;
-import org.apache.fop.util.text.AdvancedMessageFormat.Part;
-import org.apache.fop.util.text.AdvancedMessageFormat.PartFactory;
+import org.apache.fop.utils.XMLResourceBundle;
+import org.apache.fop.utils.text.AdvancedMessageFormat;
+import org.apache.fop.utils.text.AdvancedMessageFormat.Part;
+import org.apache.fop.utils.text.AdvancedMessageFormat.PartFactory;
/**
* Converts events into human-readable, localized messages.
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-import org.apache.fop.util.DefaultErrorListener;
+import org.apache.fop.utils.DefaultErrorListener;
/**
* This is a parser for the event model XML.
+++ /dev/null
-/*
- * 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 javax.xml.transform.ErrorListener;
-import javax.xml.transform.TransformerException;
-
-import org.apache.commons.logging.Log;
-
-/**
- * Standard ErrorListener implementation for in-FOP use. Some Xalan-J versions don't properly
- * re-throw exceptions.
- */
-public class DefaultErrorListener implements ErrorListener {
-
- private Log log;
-
- /**
- * Main constructor
- * @param log the log instance to send log events to
- */
- public DefaultErrorListener(Log log) {
- this.log = log;
- }
-
- /**
- * {@inheritDoc}
- */
- public void warning(TransformerException exc) {
- log.warn(exc.toString());
- }
-
- /**
- * {@inheritDoc}
- */
- public void error(TransformerException exc) throws TransformerException {
- throw exc;
- }
-
- /**
- * {@inheritDoc}
- */
- public void fatalError(TransformerException exc)
- throws TransformerException {
- throw exc;
- }
-
-}
+++ /dev/null
-/*
- * 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>
- * <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 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);
- }
-
- }
-
-}
+++ /dev/null
-/*
- * 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.text;
-
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.regex.Pattern;
-
-import org.apache.xmlgraphics.util.Service;
-
-
-/**
- * Formats messages based on a template and with a set of named parameters. This is similar to
- * {@link java.text.MessageFormat} but uses named parameters and supports conditional sub-groups.
- * <p>
- * Example:
- * </p>
- * <p><code>Missing field "{fieldName}"[ at location: {location}]!</code></p>
- * <ul>
- * <li>Curly brackets ("{}") are used for fields.</li>
- * <li>Square brackets ("[]") are used to delimit conditional sub-groups. A sub-group is
- * conditional when all fields inside the sub-group have a null value. In the case, everything
- * between the brackets is skipped.</li>
- * </ul>
- */
-public class AdvancedMessageFormat {
-
- /** Regex that matches "," but not "\," (escaped comma) */
- static final Pattern COMMA_SEPARATOR_REGEX = Pattern.compile("(?<!\\\\),");
-
- private static final Map<String, PartFactory> PART_FACTORIES
- = new java.util.HashMap<String, PartFactory>();
- private static final List<ObjectFormatter> OBJECT_FORMATTERS
- = new java.util.ArrayList<ObjectFormatter>();
- private static final Map<Object, Function> FUNCTIONS
- = new java.util.HashMap<Object, Function>();
-
- private CompositePart rootPart;
-
- static {
- Iterator<Object> iter;
- iter = Service.providers(PartFactory.class);
- while (iter.hasNext()) {
- PartFactory factory = (PartFactory)iter.next();
- PART_FACTORIES.put(factory.getFormat(), factory);
- }
- iter = Service.providers(ObjectFormatter.class);
- while (iter.hasNext()) {
- OBJECT_FORMATTERS.add((ObjectFormatter)iter.next());
- }
- iter = Service.providers(Function.class);
- while (iter.hasNext()) {
- Function function = (Function)iter.next();
- FUNCTIONS.put(function.getName(), function);
- }
- }
-
- /**
- * Construct a new message format.
- * @param pattern the message format pattern.
- */
- public AdvancedMessageFormat(CharSequence pattern) {
- parsePattern(pattern);
- }
-
- private void parsePattern(CharSequence pattern) {
- rootPart = new CompositePart(false);
- StringBuffer sb = new StringBuffer();
- parseInnerPattern(pattern, rootPart, sb, 0);
- }
-
- private int parseInnerPattern(CharSequence pattern, CompositePart parent,
- StringBuffer sb, int start) {
- assert sb.length() == 0;
- int i = start;
- int len = pattern.length();
- loop:
- while (i < len) {
- char ch = pattern.charAt(i);
- switch (ch) {
- case '{':
- if (sb.length() > 0) {
- parent.addChild(new TextPart(sb.toString()));
- sb.setLength(0);
- }
- i++;
- int nesting = 1;
- while (i < len) {
- ch = pattern.charAt(i);
- if (ch == '{') {
- nesting++;
- } else if (ch == '}') {
- nesting--;
- if (nesting == 0) {
- i++;
- break;
- }
- }
- sb.append(ch);
- i++;
- }
- parent.addChild(parseField(sb.toString()));
- sb.setLength(0);
- break;
- case ']':
- i++;
- break loop; //Current composite is finished
- case '[':
- if (sb.length() > 0) {
- parent.addChild(new TextPart(sb.toString()));
- sb.setLength(0);
- }
- i++;
- CompositePart composite = new CompositePart(true);
- parent.addChild(composite);
- i += parseInnerPattern(pattern, composite, sb, i);
- break;
- case '|':
- if (sb.length() > 0) {
- parent.addChild(new TextPart(sb.toString()));
- sb.setLength(0);
- }
- parent.newSection();
- i++;
- break;
- case '\\':
- if (i < len - 1) {
- i++;
- ch = pattern.charAt(i);
- }
- sb.append(ch);
- i++;
- break;
- default:
- sb.append(ch);
- i++;
- break;
- }
- }
- if (sb.length() > 0) {
- parent.addChild(new TextPart(sb.toString()));
- sb.setLength(0);
- }
- return i - start;
- }
-
- private Part parseField(String field) {
- String[] parts = COMMA_SEPARATOR_REGEX.split(field, 3);
- String fieldName = parts[0];
- if (parts.length == 1) {
- if (fieldName.startsWith("#")) {
- return new FunctionPart(fieldName.substring(1));
- } else {
- return new SimpleFieldPart(fieldName);
- }
- } else {
- String format = parts[1];
- PartFactory factory = PART_FACTORIES.get(format);
- if (factory == null) {
- throw new IllegalArgumentException(
- "No PartFactory available under the name: " + format);
- }
- if (parts.length == 2) {
- return factory.newPart(fieldName, null);
- } else {
- return factory.newPart(fieldName, parts[2]);
- }
- }
- }
-
- private static Function getFunction(String functionName) {
- return FUNCTIONS.get(functionName);
- }
-
- /**
- * Formats a message with the given parameters.
- * @param params a Map of named parameters (Contents: <String, Object>)
- * @return the formatted message
- */
- public String format(Map<String, Object> params) {
- StringBuffer sb = new StringBuffer();
- format(params, sb);
- return sb.toString();
- }
-
- /**
- * Formats a message with the given parameters.
- * @param params a Map of named parameters (Contents: <String, Object>)
- * @param target the target StringBuffer to write the formatted message to
- */
- public void format(Map<String, Object> params, StringBuffer target) {
- rootPart.write(target, params);
- }
-
- /**
- * Represents a message template part. This interface is implemented by various variants of
- * the single curly braces pattern ({field}, {field,if,yes,no} etc.).
- */
- public interface Part {
-
- /**
- * Writes the formatted part to a string buffer.
- * @param sb the target string buffer
- * @param params the parameters to work with
- */
- void write(StringBuffer sb, Map<String, Object> params);
-
- /**
- * Indicates whether there is any content that is generated by this message part.
- * @param params the parameters to work with
- * @return true if the part has content
- */
- boolean isGenerated(Map<String, Object> params);
- }
-
- /**
- * Implementations of this interface parse a field part and return message parts.
- */
- public interface PartFactory {
-
- /**
- * Creates a new part by parsing the values parameter to configure the part.
- * @param fieldName the field name
- * @param values the unparsed parameter values
- * @return the new message part
- */
- Part newPart(String fieldName, String values);
-
- /**
- * Returns the name of the message part format.
- * @return the name of the message part format
- */
- String getFormat();
- }
-
- /**
- * Implementations of this interface format certain objects to strings.
- */
- public interface ObjectFormatter {
-
- /**
- * Formats an object to a string and writes the result to a string buffer.
- * @param sb the target string buffer
- * @param obj the object to be formatted
- */
- void format(StringBuffer sb, Object obj);
-
- /**
- * Indicates whether a given object is supported.
- * @param obj the object
- * @return true if the object is supported by the formatter
- */
- boolean supportsObject(Object obj);
- }
-
- /**
- * Implementations of this interface do some computation based on the message parameters
- * given to it. Note: at the moment, this has to be done in a local-independent way since
- * there is no locale information.
- */
- public interface Function {
-
- /**
- * Executes the function.
- * @param params the message parameters
- * @return the function result
- */
- Object evaluate(Map<String, Object> params);
-
- /**
- * Returns the name of the function.
- * @return the name of the function
- */
- Object getName();
- }
-
- private static class TextPart implements Part {
-
- private String text;
-
- public TextPart(String text) {
- this.text = text;
- }
-
- public void write(StringBuffer sb, Map<String, Object> params) {
- sb.append(text);
- }
-
- public boolean isGenerated(Map<String, Object> params) {
- return true;
- }
-
- /** {@inheritDoc} */
- public String toString() {
- return this.text;
- }
- }
-
- private static class SimpleFieldPart implements Part {
-
- private String fieldName;
-
- public SimpleFieldPart(String fieldName) {
- this.fieldName = fieldName;
- }
-
- public void write(StringBuffer sb, Map<String, Object> params) {
- if (!params.containsKey(fieldName)) {
- throw new IllegalArgumentException(
- "Message pattern contains unsupported field name: " + fieldName);
- }
- Object obj = params.get(fieldName);
- formatObject(obj, sb);
- }
-
- public boolean isGenerated(Map<String, Object> params) {
- Object obj = params.get(fieldName);
- return obj != null;
- }
-
- /** {@inheritDoc} */
- public String toString() {
- return "{" + this.fieldName + "}";
- }
- }
-
- /**
- * Formats an object to a string and writes the result to a string buffer. This method
- * usually uses the object's <code>toString()</code> method unless there is an
- * {@link ObjectFormatter} that supports the object. {@link ObjectFormatter}s are registered
- * through the service provider mechanism defined by the JAR specification.
- * @param obj the object to be formatted
- * @param target the target string buffer
- */
- public static void formatObject(Object obj, StringBuffer target) {
- if (obj instanceof String) {
- target.append(obj);
- } else {
- boolean handled = false;
- for (ObjectFormatter formatter : OBJECT_FORMATTERS) {
- if (formatter.supportsObject(obj)) {
- formatter.format(target, obj);
- handled = true;
- break;
- }
- }
- if (!handled) {
- target.append(String.valueOf(obj));
- }
- }
- }
-
- private static class FunctionPart implements Part {
-
- private Function function;
-
- public FunctionPart(String functionName) {
- this.function = getFunction(functionName);
- if (this.function == null) {
- throw new IllegalArgumentException("Unknown function: " + functionName);
- }
- }
-
- public void write(StringBuffer sb, Map<String, Object> params) {
- Object obj = this.function.evaluate(params);
- formatObject(obj, sb);
- }
-
- public boolean isGenerated(Map<String, Object> params) {
- Object obj = this.function.evaluate(params);
- return obj != null;
- }
-
- /** {@inheritDoc} */
- public String toString() {
- return "{#" + this.function.getName() + "}";
- }
- }
-
- private static class CompositePart implements Part {
-
- protected List<Part> parts = new java.util.ArrayList<Part>();
- private boolean conditional;
- private boolean hasSections;
-
- public CompositePart(boolean conditional) {
- this.conditional = conditional;
- }
-
- private CompositePart(List<Part> parts) {
- this.parts.addAll(parts);
- this.conditional = true;
- }
-
- public void addChild(Part part) {
- if (part == null) {
- throw new NullPointerException("part must not be null");
- }
- if (hasSections) {
- CompositePart composite = (CompositePart) this.parts.get(this.parts.size() - 1);
- composite.addChild(part);
- } else {
- this.parts.add(part);
- }
- }
-
- public void newSection() {
- if (!hasSections) {
- List<Part> p = this.parts;
- //Dropping into a different mode...
- this.parts = new java.util.ArrayList<Part>();
- this.parts.add(new CompositePart(p));
- hasSections = true;
- }
- this.parts.add(new CompositePart(true));
- }
-
- public void write(StringBuffer sb, Map<String, Object> params) {
- if (hasSections) {
- for (Part part : this.parts) {
- if (part.isGenerated(params)) {
- part.write(sb, params);
- break;
- }
- }
- } else {
- if (isGenerated(params)) {
- for (Part part : this.parts) {
- part.write(sb, params);
- }
- }
- }
- }
-
- public boolean isGenerated(Map<String, Object> params) {
- if (hasSections) {
- for (Part part : this.parts) {
- if (part.isGenerated(params)) {
- return true;
- }
- }
- return false;
- } else {
- if (conditional) {
- for (Part part : this.parts) {
- if (!part.isGenerated(params)) {
- return false;
- }
- }
- }
- return true;
- }
- }
-
- /** {@inheritDoc} */
- public String toString() {
- return this.parts.toString();
- }
- }
-
-
- static String unescapeComma(String string) {
- return string.replaceAll("\\\\,", ",");
- }
-}
--- /dev/null
+/*
+ * 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.utils;
+
+import javax.xml.transform.ErrorListener;
+import javax.xml.transform.TransformerException;
+
+import org.apache.commons.logging.Log;
+
+/**
+ * Standard ErrorListener implementation for in-FOP use. Some Xalan-J versions don't properly
+ * re-throw exceptions.
+ */
+public class DefaultErrorListener implements ErrorListener {
+
+ private Log log;
+
+ /**
+ * Main constructor
+ * @param log the log instance to send log events to
+ */
+ public DefaultErrorListener(Log log) {
+ this.log = log;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void warning(TransformerException exc) {
+ log.warn(exc.toString());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void error(TransformerException exc) throws TransformerException {
+ throw exc;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void fatalError(TransformerException exc)
+ throws TransformerException {
+ throw exc;
+ }
+
+}
--- /dev/null
+/*
+ * 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.utils;
+
+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>
+ * <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 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);
+ }
+
+ }
+
+}
--- /dev/null
+/*
+ * 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.utils.text;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+import org.apache.xmlgraphics.util.Service;
+
+
+/**
+ * Formats messages based on a template and with a set of named parameters. This is similar to
+ * {@link java.text.MessageFormat} but uses named parameters and supports conditional sub-groups.
+ * <p>
+ * Example:
+ * </p>
+ * <p><code>Missing field "{fieldName}"[ at location: {location}]!</code></p>
+ * <ul>
+ * <li>Curly brackets ("{}") are used for fields.</li>
+ * <li>Square brackets ("[]") are used to delimit conditional sub-groups. A sub-group is
+ * conditional when all fields inside the sub-group have a null value. In the case, everything
+ * between the brackets is skipped.</li>
+ * </ul>
+ */
+public class AdvancedMessageFormat {
+
+ /** Regex that matches "," but not "\," (escaped comma) */
+ public static final Pattern COMMA_SEPARATOR_REGEX = Pattern.compile("(?<!\\\\),");
+
+ private static final Map<String, PartFactory> PART_FACTORIES
+ = new java.util.HashMap<String, PartFactory>();
+ private static final List<ObjectFormatter> OBJECT_FORMATTERS
+ = new java.util.ArrayList<ObjectFormatter>();
+ private static final Map<Object, Function> FUNCTIONS
+ = new java.util.HashMap<Object, Function>();
+
+ private CompositePart rootPart;
+
+ static {
+ Iterator<Object> iter;
+ iter = Service.providers(PartFactory.class);
+ while (iter.hasNext()) {
+ PartFactory factory = (PartFactory)iter.next();
+ PART_FACTORIES.put(factory.getFormat(), factory);
+ }
+ iter = Service.providers(ObjectFormatter.class);
+ while (iter.hasNext()) {
+ OBJECT_FORMATTERS.add((ObjectFormatter)iter.next());
+ }
+ iter = Service.providers(Function.class);
+ while (iter.hasNext()) {
+ Function function = (Function)iter.next();
+ FUNCTIONS.put(function.getName(), function);
+ }
+ }
+
+ /**
+ * Construct a new message format.
+ * @param pattern the message format pattern.
+ */
+ public AdvancedMessageFormat(CharSequence pattern) {
+ parsePattern(pattern);
+ }
+
+ private void parsePattern(CharSequence pattern) {
+ rootPart = new CompositePart(false);
+ StringBuffer sb = new StringBuffer();
+ parseInnerPattern(pattern, rootPart, sb, 0);
+ }
+
+ private int parseInnerPattern(CharSequence pattern, CompositePart parent,
+ StringBuffer sb, int start) {
+ assert sb.length() == 0;
+ int i = start;
+ int len = pattern.length();
+ loop:
+ while (i < len) {
+ char ch = pattern.charAt(i);
+ switch (ch) {
+ case '{':
+ if (sb.length() > 0) {
+ parent.addChild(new TextPart(sb.toString()));
+ sb.setLength(0);
+ }
+ i++;
+ int nesting = 1;
+ while (i < len) {
+ ch = pattern.charAt(i);
+ if (ch == '{') {
+ nesting++;
+ } else if (ch == '}') {
+ nesting--;
+ if (nesting == 0) {
+ i++;
+ break;
+ }
+ }
+ sb.append(ch);
+ i++;
+ }
+ parent.addChild(parseField(sb.toString()));
+ sb.setLength(0);
+ break;
+ case ']':
+ i++;
+ break loop; //Current composite is finished
+ case '[':
+ if (sb.length() > 0) {
+ parent.addChild(new TextPart(sb.toString()));
+ sb.setLength(0);
+ }
+ i++;
+ CompositePart composite = new CompositePart(true);
+ parent.addChild(composite);
+ i += parseInnerPattern(pattern, composite, sb, i);
+ break;
+ case '|':
+ if (sb.length() > 0) {
+ parent.addChild(new TextPart(sb.toString()));
+ sb.setLength(0);
+ }
+ parent.newSection();
+ i++;
+ break;
+ case '\\':
+ if (i < len - 1) {
+ i++;
+ ch = pattern.charAt(i);
+ }
+ sb.append(ch);
+ i++;
+ break;
+ default:
+ sb.append(ch);
+ i++;
+ break;
+ }
+ }
+ if (sb.length() > 0) {
+ parent.addChild(new TextPart(sb.toString()));
+ sb.setLength(0);
+ }
+ return i - start;
+ }
+
+ private Part parseField(String field) {
+ String[] parts = COMMA_SEPARATOR_REGEX.split(field, 3);
+ String fieldName = parts[0];
+ if (parts.length == 1) {
+ if (fieldName.startsWith("#")) {
+ return new FunctionPart(fieldName.substring(1));
+ } else {
+ return new SimpleFieldPart(fieldName);
+ }
+ } else {
+ String format = parts[1];
+ PartFactory factory = PART_FACTORIES.get(format);
+ if (factory == null) {
+ throw new IllegalArgumentException(
+ "No PartFactory available under the name: " + format);
+ }
+ if (parts.length == 2) {
+ return factory.newPart(fieldName, null);
+ } else {
+ return factory.newPart(fieldName, parts[2]);
+ }
+ }
+ }
+
+ private static Function getFunction(String functionName) {
+ return FUNCTIONS.get(functionName);
+ }
+
+ /**
+ * Formats a message with the given parameters.
+ * @param params a Map of named parameters (Contents: <String, Object>)
+ * @return the formatted message
+ */
+ public String format(Map<String, Object> params) {
+ StringBuffer sb = new StringBuffer();
+ format(params, sb);
+ return sb.toString();
+ }
+
+ /**
+ * Formats a message with the given parameters.
+ * @param params a Map of named parameters (Contents: <String, Object>)
+ * @param target the target StringBuffer to write the formatted message to
+ */
+ public void format(Map<String, Object> params, StringBuffer target) {
+ rootPart.write(target, params);
+ }
+
+ /**
+ * Represents a message template part. This interface is implemented by various variants of
+ * the single curly braces pattern ({field}, {field,if,yes,no} etc.).
+ */
+ public interface Part {
+
+ /**
+ * Writes the formatted part to a string buffer.
+ * @param sb the target string buffer
+ * @param params the parameters to work with
+ */
+ void write(StringBuffer sb, Map<String, Object> params);
+
+ /**
+ * Indicates whether there is any content that is generated by this message part.
+ * @param params the parameters to work with
+ * @return true if the part has content
+ */
+ boolean isGenerated(Map<String, Object> params);
+ }
+
+ /**
+ * Implementations of this interface parse a field part and return message parts.
+ */
+ public interface PartFactory {
+
+ /**
+ * Creates a new part by parsing the values parameter to configure the part.
+ * @param fieldName the field name
+ * @param values the unparsed parameter values
+ * @return the new message part
+ */
+ Part newPart(String fieldName, String values);
+
+ /**
+ * Returns the name of the message part format.
+ * @return the name of the message part format
+ */
+ String getFormat();
+ }
+
+ /**
+ * Implementations of this interface format certain objects to strings.
+ */
+ public interface ObjectFormatter {
+
+ /**
+ * Formats an object to a string and writes the result to a string buffer.
+ * @param sb the target string buffer
+ * @param obj the object to be formatted
+ */
+ void format(StringBuffer sb, Object obj);
+
+ /**
+ * Indicates whether a given object is supported.
+ * @param obj the object
+ * @return true if the object is supported by the formatter
+ */
+ boolean supportsObject(Object obj);
+ }
+
+ /**
+ * Implementations of this interface do some computation based on the message parameters
+ * given to it. Note: at the moment, this has to be done in a local-independent way since
+ * there is no locale information.
+ */
+ public interface Function {
+
+ /**
+ * Executes the function.
+ * @param params the message parameters
+ * @return the function result
+ */
+ Object evaluate(Map<String, Object> params);
+
+ /**
+ * Returns the name of the function.
+ * @return the name of the function
+ */
+ Object getName();
+ }
+
+ private static class TextPart implements Part {
+
+ private String text;
+
+ public TextPart(String text) {
+ this.text = text;
+ }
+
+ public void write(StringBuffer sb, Map<String, Object> params) {
+ sb.append(text);
+ }
+
+ public boolean isGenerated(Map<String, Object> params) {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ return this.text;
+ }
+ }
+
+ private static class SimpleFieldPart implements Part {
+
+ private String fieldName;
+
+ public SimpleFieldPart(String fieldName) {
+ this.fieldName = fieldName;
+ }
+
+ public void write(StringBuffer sb, Map<String, Object> params) {
+ if (!params.containsKey(fieldName)) {
+ throw new IllegalArgumentException(
+ "Message pattern contains unsupported field name: " + fieldName);
+ }
+ Object obj = params.get(fieldName);
+ formatObject(obj, sb);
+ }
+
+ public boolean isGenerated(Map<String, Object> params) {
+ Object obj = params.get(fieldName);
+ return obj != null;
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ return "{" + this.fieldName + "}";
+ }
+ }
+
+ /**
+ * Formats an object to a string and writes the result to a string buffer. This method
+ * usually uses the object's <code>toString()</code> method unless there is an
+ * {@link ObjectFormatter} that supports the object. {@link ObjectFormatter}s are registered
+ * through the service provider mechanism defined by the JAR specification.
+ * @param obj the object to be formatted
+ * @param target the target string buffer
+ */
+ public static void formatObject(Object obj, StringBuffer target) {
+ if (obj instanceof String) {
+ target.append(obj);
+ } else {
+ boolean handled = false;
+ for (ObjectFormatter formatter : OBJECT_FORMATTERS) {
+ if (formatter.supportsObject(obj)) {
+ formatter.format(target, obj);
+ handled = true;
+ break;
+ }
+ }
+ if (!handled) {
+ target.append(String.valueOf(obj));
+ }
+ }
+ }
+
+ private static class FunctionPart implements Part {
+
+ private Function function;
+
+ public FunctionPart(String functionName) {
+ this.function = getFunction(functionName);
+ if (this.function == null) {
+ throw new IllegalArgumentException("Unknown function: " + functionName);
+ }
+ }
+
+ public void write(StringBuffer sb, Map<String, Object> params) {
+ Object obj = this.function.evaluate(params);
+ formatObject(obj, sb);
+ }
+
+ public boolean isGenerated(Map<String, Object> params) {
+ Object obj = this.function.evaluate(params);
+ return obj != null;
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ return "{#" + this.function.getName() + "}";
+ }
+ }
+
+ private static class CompositePart implements Part {
+
+ protected List<Part> parts = new java.util.ArrayList<Part>();
+ private boolean conditional;
+ private boolean hasSections;
+
+ public CompositePart(boolean conditional) {
+ this.conditional = conditional;
+ }
+
+ private CompositePart(List<Part> parts) {
+ this.parts.addAll(parts);
+ this.conditional = true;
+ }
+
+ public void addChild(Part part) {
+ if (part == null) {
+ throw new NullPointerException("part must not be null");
+ }
+ if (hasSections) {
+ CompositePart composite = (CompositePart) this.parts.get(this.parts.size() - 1);
+ composite.addChild(part);
+ } else {
+ this.parts.add(part);
+ }
+ }
+
+ public void newSection() {
+ if (!hasSections) {
+ List<Part> p = this.parts;
+ //Dropping into a different mode...
+ this.parts = new java.util.ArrayList<Part>();
+ this.parts.add(new CompositePart(p));
+ hasSections = true;
+ }
+ this.parts.add(new CompositePart(true));
+ }
+
+ public void write(StringBuffer sb, Map<String, Object> params) {
+ if (hasSections) {
+ for (Part part : this.parts) {
+ if (part.isGenerated(params)) {
+ part.write(sb, params);
+ break;
+ }
+ }
+ } else {
+ if (isGenerated(params)) {
+ for (Part part : this.parts) {
+ part.write(sb, params);
+ }
+ }
+ }
+ }
+
+ public boolean isGenerated(Map<String, Object> params) {
+ if (hasSections) {
+ for (Part part : this.parts) {
+ if (part.isGenerated(params)) {
+ return true;
+ }
+ }
+ return false;
+ } else {
+ if (conditional) {
+ for (Part part : this.parts) {
+ if (!part.isGenerated(params)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ return this.parts.toString();
+ }
+ }
+
+
+ public static String unescapeComma(String string) {
+ return string.replaceAll("\\\\,", ",");
+ }
+}