Source Code Cross Referenced for I18nTransformer.java in  » Content-Management-System » apache-lenya-2.0 » org » apache » cocoon » transformation » Java Source Code / Java DocumentationJava Source Code and Java Documentation

Java Source Code / Java Documentation
1. 6.0 JDK Core
2. 6.0 JDK Modules
3. 6.0 JDK Modules com.sun
4. 6.0 JDK Modules com.sun.java
5. 6.0 JDK Modules sun
6. 6.0 JDK Platform
7. Ajax
8. Apache Harmony Java SE
9. Aspect oriented
10. Authentication Authorization
11. Blogger System
12. Build
13. Byte Code
14. Cache
15. Chart
16. Chat
17. Code Analyzer
18. Collaboration
19. Content Management System
20. Database Client
21. Database DBMS
22. Database JDBC Connection Pool
23. Database ORM
24. Development
25. EJB Server geronimo
26. EJB Server GlassFish
27. EJB Server JBoss 4.2.1
28. EJB Server resin 3.1.5
29. ERP CRM Financial
30. ESB
31. Forum
32. GIS
33. Graphic Library
34. Groupware
35. HTML Parser
36. IDE
37. IDE Eclipse
38. IDE Netbeans
39. Installer
40. Internationalization Localization
41. Inversion of Control
42. Issue Tracking
43. J2EE
44. JBoss
45. JMS
46. JMX
47. Library
48. Mail Clients
49. Net
50. Parser
51. PDF
52. Portal
53. Profiler
54. Project Management
55. Report
56. RSS RDF
57. Rule Engine
58. Science
59. Scripting
60. Search Engine
61. Security
62. Sevlet Container
63. Source Control
64. Swing Library
65. Template Engine
66. Test Coverage
67. Testing
68. UML
69. Web Crawler
70. Web Framework
71. Web Mail
72. Web Server
73. Web Services
74. Web Services apache cxf 2.0.1
75. Web Services AXIS2
76. Wiki Engine
77. Workflow Engines
78. XML
79. XML UI
Java
Java Tutorial
Java Open Source
Jar File Download
Java Articles
Java Products
Java by API
Photoshop Tutorials
Maya Tutorials
Flash Tutorials
3ds-Max Tutorials
Illustrator Tutorials
GIMP Tutorials
C# / C Sharp
C# / CSharp Tutorial
C# / CSharp Open Source
ASP.Net
ASP.NET Tutorial
JavaScript DHTML
JavaScript Tutorial
JavaScript Reference
HTML / CSS
HTML CSS Reference
C / ANSI-C
C Tutorial
C++
C++ Tutorial
Ruby
PHP
Python
Python Tutorial
Python Open Source
SQL Server / T-SQL
SQL Server / T-SQL Tutorial
Oracle PL / SQL
Oracle PL/SQL Tutorial
PostgreSQL
SQL / MySQL
MySQL Tutorial
VB.Net
VB.Net Tutorial
Flash / Flex / ActionScript
VBA / Excel / Access / Word
XML
XML Tutorial
Microsoft Office PowerPoint 2007 Tutorial
Microsoft Office Excel 2007 Tutorial
Microsoft Office Word 2007 Tutorial
Java Source Code / Java Documentation » Content Management System » apache lenya 2.0 » org.apache.cocoon.transformation 
Source Cross Referenced  Class Diagram Java Document (Java Doc) 


0001:        /*
0002:         * Licensed to the Apache Software Foundation (ASF) under one or more
0003:         * contributor license agreements.  See the NOTICE file distributed with
0004:         * this work for additional information regarding copyright ownership.
0005:         * The ASF licenses this file to You under the Apache License, Version 2.0
0006:         * (the "License"); you may not use this file except in compliance with
0007:         * the License.  You may obtain a copy of the License at
0008:         *
0009:         *      http://www.apache.org/licenses/LICENSE-2.0
0010:         *
0011:         * Unless required by applicable law or agreed to in writing, software
0012:         * distributed under the License is distributed on an "AS IS" BASIS,
0013:         * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014:         * See the License for the specific language governing permissions and
0015:         * limitations under the License.
0016:         */
0017:        package org.apache.cocoon.transformation;
0018:
0019:        import org.apache.avalon.framework.activity.Disposable;
0020:        import org.apache.avalon.framework.configuration.Configurable;
0021:        import org.apache.avalon.framework.configuration.Configuration;
0022:        import org.apache.avalon.framework.configuration.ConfigurationException;
0023:        import org.apache.avalon.framework.parameters.Parameters;
0024:        import org.apache.avalon.framework.service.ServiceException;
0025:        import org.apache.avalon.framework.service.ServiceManager;
0026:        import org.apache.avalon.framework.service.Serviceable;
0027:
0028:        import org.apache.cocoon.ProcessingException;
0029:        import org.apache.cocoon.caching.CacheableProcessingComponent;
0030:        import org.apache.cocoon.components.treeprocessor.variables.VariableExpressionTokenizer;
0031:        import org.apache.cocoon.components.treeprocessor.variables.VariableResolver;
0032:        import org.apache.cocoon.components.treeprocessor.variables.VariableResolverFactory;
0033:        import org.apache.cocoon.environment.SourceResolver;
0034:        import org.apache.cocoon.i18n.Bundle;
0035:        import org.apache.cocoon.i18n.BundleFactory;
0036:        import org.apache.cocoon.i18n.I18nUtils;
0037:        import org.apache.cocoon.sitemap.PatternException;
0038:        import org.apache.cocoon.xml.ParamSaxBuffer;
0039:        import org.apache.cocoon.xml.SaxBuffer;
0040:
0041:        import org.apache.excalibur.source.SourceValidity;
0042:        import org.xml.sax.Attributes;
0043:        import org.xml.sax.SAXException;
0044:        import org.xml.sax.helpers.AttributesImpl;
0045:
0046:        import java.io.IOException;
0047:        import java.text.DateFormat;
0048:        import java.text.DecimalFormat;
0049:        import java.text.DecimalFormatSymbols;
0050:        import java.text.NumberFormat;
0051:        import java.text.ParseException;
0052:        import java.text.SimpleDateFormat;
0053:        import java.util.Collections;
0054:        import java.util.Date;
0055:        import java.util.HashMap;
0056:        import java.util.HashSet;
0057:        import java.util.Iterator;
0058:        import java.util.Locale;
0059:        import java.util.Map;
0060:        import java.util.MissingResourceException;
0061:        import java.util.Set;
0062:        import java.util.StringTokenizer;
0063:
0064:        /**
0065:         * @cocoon.sitemap.component.documentation
0066:         * Internationalization transformer is used to transform i18n markup into text
0067:         * based on a particular locale.
0068:         *
0069:         * @cocoon.sitemap.component.name   i18n
0070:         * @cocoon.sitemap.component.documentation.caching TBD
0071:         * @cocoon.sitemap.component.logger sitemap.transformer.i18n
0072:         *
0073:         * <h3>I18n Transformer</h3>
0074:         * <p>The i18n transformer works by finding a translation for the user's locale
0075:         * in the configured catalogues. Locale is passed as parameter to the transformer,
0076:         * and it can be determined based on the request, session, or a cookie data by
0077:         * the {@link org.apache.cocoon.acting.LocaleAction}.</p>
0078:         *
0079:         * <p>For the passed local it then attempts to find a message catalogue that
0080:         * satisifies the locale, and uses it for for processing text replacement
0081:         * directed by i18n markup.</p>
0082:         *
0083:         * <p>Message catalogues are maintained in separate files, with a naming
0084:         * convention similar to that of {@link java.util.ResourceBundle}. I.e.
0085:         * <code>basename_locale</code>, where <i>basename</i> can be any name,
0086:         * and <i>locale</i> can be any locale specified using ISO 639/3166
0087:         * characters (eg. <code>en_AU</code>, <code>de_AT</code>, <code>es</code>).</p>
0088:         *
0089:         * <p><strong>NOTE:</strong> ISO 639 is not a stable standard; some of the
0090:         * language codes it defines (specifically, iw, ji, and in) have changed
0091:         * (see {@link java.util.Locale} for details).
0092:         *
0093:         * <h3>Message Catalogues</h3>
0094:         * <p>Catalogues are of the following format:
0095:         * <pre>
0096:         * &lt;?xml version="1.0"?&gt;
0097:         * &lt;!-- message catalogue file for locale ... --&gt;
0098:         * &lt;catalogue xml:lang=&quot;locale&quot;&gt;
0099:         *   &lt;message key="key"&gt;text &lt;i&gt;or&lt;/i&gt; markup&lt;/message&gt;
0100:         *   ....
0101:         * &lt;/catalogue&gt;
0102:         * </pre>
0103:         * Where <code>key</code> specifies a particular message for that
0104:         * language.
0105:         *
0106:         * <h3>Usage</h3>
0107:         * <p>Files to be translated contain the following markup:
0108:         * <pre>
0109:         * &lt;?xml version="1.0"?&gt;
0110:         * ... some text, translate &lt;i18n:text&gt;key&lt;/i18n:text&gt;
0111:         * </pre>
0112:         * At runtime, the i18n transformer will find a message catalogue for the
0113:         * user's locale, and will appropriately replace the text between the
0114:         * <code>&lt;i18n:text&gt;</code> markup, using the value between the tags as
0115:         * the lookup key.</p>
0116:         *
0117:         * <p>If the i18n transformer cannot find an appropriate message catalogue for
0118:         * the user's given locale, it will recursively try to locate a <i>parent</i>
0119:         * message catalogue, until a valid catalogue can be found.
0120:         * ie:
0121:         * <ul>
0122:         *  <li><strong>catalogue</strong>_<i>language</i>_<i>country</i>_<i>variant</i>.xml
0123:         *  <li><strong>catalogue</strong>_<i>language</i>_<i>country</i>.xml
0124:         *  <li><strong>catalogue</strong>_<i>language</i>.xml
0125:         *  <li><strong>catalogue</strong>.xml
0126:         * </ul>
0127:         * eg: Assuming a basename of <i>messages</i> and a locale of <i>en_AU</i>
0128:         * (no variant), the following search will occur:
0129:         * <ul>
0130:         *  <li><strong>messages</strong>_<i>en</i>_<i>AU</i>.xml
0131:         *  <li><strong>messages</strong>_<i>en</i>.xml
0132:         *  <li><strong>messages</strong>.xml
0133:         * </ul>
0134:         * This allows the developer to write a hierarchy of message catalogues,
0135:         * at each defining messages with increasing depth of variation.</p>
0136:         *
0137:         * <p>In addition, catalogues can be split across multiple locations. For example,
0138:         * there can be a default catalogue in one directory with a user or client specific
0139:         * catalogue in another directory. The catalogues will be searched in the order of
0140:         * the locations specified still following the locale ordering specified above.
0141:         * eg: Assuming a basename of <i>messages</i> and a locale of <i>en_AU</i>
0142:         * (no variant) and locations of <i>translations/client</i> and <i>translations</i>,
0143:         * the following search will occur:
0144:         * <ul>
0145:         *   <li><i>translations/client/</i><strong>messages</strong>_<i>en</i>_<i>AU</i>.xml
0146:         *   <li><i>translations/</i><strong>messages</strong>_<i>en</i>_<i>AU</i>.xml
0147:         *   <li><i>translations/client/</i><strong>messages</strong>_<i>en</i>.xml
0148:         *   <li><i>translations/</i><strong>messages</strong>_<i>en</i.xml
0149:         *   <li><i>translations/client/</i><strong>messages</strong>.xml
0150:         *   <li><i>translations/</i><strong>messages</strong>.xml
0151:         * </ul>
0152:         * </p>
0153:         *
0154:         * <p>The <code>i18n:text</code> element can optionally take an attribute
0155:         * <code>i18n:catalogue</code> to indicate which specific catalogue to use.
0156:         * The value of this attribute should be the id of the catalogue to use
0157:         * (see sitemap configuration).
0158:         *
0159:         * <h3>Sitemap Configuration</h3>
0160:         * <pre>
0161:         * &lt;map:transformer name="i18n"
0162:         *     src="org.apache.cocoon.transformation.I18nTransformer"&gt;
0163:         *
0164:         *     &lt;catalogues default="someId"&gt;
0165:         *       &lt;catalogue id="someId" name="messages" [location="translations"]&gt;
0166:         *         [&lt;location&gt;translations/client&lt;/location&gt;]
0167:         *         [&lt;location&gt;translations&lt;/location&gt;]
0168:         *       &lt;/catalogue&gt;
0169:         *       ...
0170:         *     &lt;/catalogues&gt;
0171:         *     &lt;untranslated-text&gt;untranslated&lt;/untranslated-text&gt;
0172:         *     &lt;preload&gt;en_US&lt;/preload&gt;
0173:         *     &lt;preload catalogue="someId"&gt;fr_CA&lt;/preload&gt;
0174:         * &lt;/map:transformer&gt;
0175:         * </pre>
0176:         * Where:
0177:         * <ul>
0178:         *  <li><strong>catalogues</strong>: container element in which the catalogues
0179:         *      are defined. It must have an attribute 'default' whose value is one
0180:         *      of the id's of the catalogue elements. (<i>mandatory</i>).
0181:         *  <li><strong>catalogue</strong>: specifies a catalogue. It takes 2 required
0182:         *      attributes: id (can be wathever you like) and name (base name of the catalogue).
0183:         *      The location (location of the message catalogue) is also required, but can be
0184:         *      specified either as an attribute or as one or more subelements, but not both.
0185:         *      If more than one location is specified the catalogues will be searched in the
0186:         *      order they appear in the configuration. The name and location can contain
0187:         *      references to inputmodules (same syntax as in other places in the
0188:         *      sitemap). They are resolved on each usage of the transformer, so they can
0189:         *      refer to e.g. request parameters. (<i>at least 1 catalogue
0190:         *      element required</i>).  After input module references are resolved the location
0191:         *      string can be the root of a URI. For example, specifying a location of
0192:         *      cocoon:/test with a name of messages and a locale of en_GB will cause the
0193:         *      sitemap to try to process cocoon:/test/messages_en_GB.xml,
0194:         *      cocoon:/test/messages_en.xml and cocoon:/test/messages.xml.
0195:         *  <li><strong>untranslated-text</strong>: text used for
0196:         *      untranslated keys (default is to output the key name).
0197:         *  <li><strong>preload</strong>: locale of the catalogue to preload. Will attempt
0198:         *      to resolve all configured catalogues for specified locale. If optional
0199:         *      <code>catalogue</code> attribute is present, will preload only specified
0200:         *      catalogue. Multiple <code>preload</code> elements can be specified.
0201:         * </ul>
0202:         *
0203:         * <h3>Pipeline Usage</h3>
0204:         * <p>To use the transformer in a pipeline, simply specify it in a particular
0205:         * transform, and pass locale parameter:
0206:         * <pre>
0207:         * &lt;map:match pattern="file"&gt;
0208:         *   &lt;map:generate src="file.xml"/&gt;
0209:         *   &lt;map:transform type="i18n"&gt;
0210:         *     &lt;map:parameter name="locale" value="..."/&gt;
0211:         *   &lt;/map:transform&gt;
0212:         *   &lt;map:serialize/&gt;
0213:         * &lt;/map:match&gt;
0214:         * </pre>
0215:         * You can use {@link org.apache.cocoon.acting.LocaleAction} or any other
0216:         * way to provide transformer with a locale.</p>
0217:         *
0218:         * <p>If in certain pipeline, you want to use a different catalogue as the
0219:         * default catalogue, you can do so by specifying a parameter called
0220:         * <strong>default-catalogue-id</strong>.
0221:         *
0222:         * <p>The <strong>untranslated-text</strong> can also be overridden at the
0223:         * pipeline level by specifying it as a parameter.</p>
0224:         *
0225:         *
0226:         * <h3>i18n markup</h3>
0227:         *
0228:         * <p>For date, time and number formatting use the following tags:
0229:         * <ul>
0230:         *  <li><strong>&lt;i18n:date/&gt;</strong> gives localized date.</li>
0231:         *  <li><strong>&lt;i18n:date-time/&gt;</strong> gives localized date and time.</li>
0232:         *  <li><strong>&lt;i18n:time/&gt;</strong> gives localized time.</li>
0233:         *  <li><strong>&lt;i18n:number/&gt;</strong> gives localized number.</li>
0234:         *  <li><strong>&lt;i18n:currency/&gt;</strong> gives localized currency.</li>
0235:         *  <li><strong>&lt;i18n:percent/&gt;</strong> gives localized percent.</li>
0236:         * </ul>
0237:         * Elements <code>date</code>, <code>date-time</code> and <code>time</code>
0238:         * accept <code>pattern</code> and <code>src-pattern</code> attribute, with
0239:         * values of:
0240:         * <ul>
0241:         *  <li><code>short</code>
0242:         *  <li><code>medium</code>
0243:         *  <li><code>long</code>
0244:         *  <li><code>full</code>
0245:         * </ul>
0246:         * See {@link java.text.DateFormat} for more info on these values.</p>
0247:         *
0248:         * <p>Elements <code>date</code>, <code>date-time</code>, <code>time</code> and
0249:         * <code>number</code>, a different <code>locale</code> and
0250:         * <code>source-locale</code> can be specified:
0251:         * <pre>
0252:         * &lt;i18n:date src-pattern="short" src-locale="en_US" locale="de_DE"&gt;
0253:         *   12/24/01
0254:         * &lt;/i18n:date&gt;
0255:         * </pre>
0256:         * Will result in 24.12.2001.</p>
0257:         *
0258:         * <p>A given real <code>pattern</code> and <code>src-pattern</code> (not
0259:         * keywords <code>short, medium, long, full</code>) overrides any value
0260:         * specified by <code>locale</code> and <code>src-locale</code> attributes.</p>
0261:         *
0262:         * <p>Future work coming:
0263:         * <ul>
0264:         *  <li>Introduce new &lt;get-locale/&gt; element
0265:         *  <li>Move all formatting routines to I18nUtils
0266:         * </ul>
0267:         *
0268:         * @author <a href="mailto:kpiroumian@apache.org">Konstantin Piroumian</a>
0269:         * @author <a href="mailto:mattam@netcourrier.com">Matthieu Sozeau</a>
0270:         * @author <a href="mailto:crafterm@apache.org">Marcus Crafter</a>
0271:         * @author <a href="mailto:Michael.Enke@wincor-nixdorf.com">Michael Enke</a>
0272:         * @version $Id: I18nTransformer.java 474832 2006-11-14 15:56:43Z vgritsenko $
0273:         */
0274:        public class I18nTransformer extends AbstractTransformer implements 
0275:                CacheableProcessingComponent, Serviceable, Configurable,
0276:                Disposable {
0277:
0278:            /**
0279:             * The namespace for i18n is "http://apache.org/cocoon/i18n/2.1".
0280:             */
0281:            public static final String I18N_NAMESPACE_URI = I18nUtils.NAMESPACE_URI;
0282:
0283:            /**
0284:             * The old namespace for i18n is "http://apache.org/cocoon/i18n/2.0".
0285:             */
0286:            public static final String I18N_OLD_NAMESPACE_URI = I18nUtils.OLD_NAMESPACE_URI;
0287:
0288:            //
0289:            // i18n elements
0290:            //
0291:
0292:            /**
0293:             * <code>i18n:text</code> element is used to translate any text, with
0294:             * or without markup. Example:
0295:             * <pre>
0296:             *   &lt;i18n:text&gt;
0297:             *     This is &lt;strong&gt;translated&lt;/strong&gt; string.
0298:             *   &lt;/i18n:text&gt;
0299:             * </pre>
0300:             */
0301:            public static final String I18N_TEXT_ELEMENT = "text";
0302:
0303:            /**
0304:             * <code>i18n:translate</code> element is used to translate text with
0305:             * parameter substitution. Example:
0306:             * <pre>
0307:             * &lt;i18n:translate&gt;
0308:             *   &lt;i18n:text&gt;This is translated string with {0} param&lt;/i18n:text&gt;
0309:             *   &lt;i18n:param&gt;1&lt;/i18n:param&gt;
0310:             * &lt;/i18n:translate&gt;
0311:             * </pre>
0312:             * The <code>i18n:text</code> fragment can include markup and parameters
0313:             * at any place. Also do parameters, which can include <code>i18n:text</code>,
0314:             * <code>i18n:date</code>, etc. elements (without keys only).
0315:             *
0316:             * @see #I18N_TEXT_ELEMENT
0317:             * @see #I18N_PARAM_ELEMENT
0318:             */
0319:            public static final String I18N_TRANSLATE_ELEMENT = "translate";
0320:
0321:            /**
0322:             * <code>i18n:choose</code> element is used to translate elements in-place.
0323:             * The first <code>i18n:when</code> element matching the current locale
0324:             * is selected and the others are discarded.
0325:             *
0326:             * <p>To specify what to do if no locale matched, simply add a node with
0327:             * <code>locale="*"</code>. <em>Note that this element must be the last
0328:             * child of &lt;i18n:choose&gt;.</em></p>
0329:             * <pre>
0330:             * &lt;i18n:choose&gt;
0331:             *   &lt;i18n:when locale="en"&gt;
0332:             *     Good Morning
0333:             *   &lt;/en&gt;
0334:             *   &lt;i18n:when locale="fr"&gt;
0335:             *     Bonjour
0336:             *   &lt;/jp&gt;
0337:             *   &lt;i18n:when locale="jp"&gt;
0338:             *     Aligato?
0339:             *   &lt;/jp&gt;
0340:             *   &lt;i18n:otherwise&gt;
0341:             *     Sorry, i don't know how to say hello in your language
0342:             *   &lt;/jp&gt;
0343:             * &lt;i18n:translate&gt;
0344:             * </pre>
0345:             * <p>You can include any markup within <code>i18n:when</code> elements,
0346:             * with the exception of other <code>i18n:*</code> elements.</p>
0347:             *
0348:             * @see #I18N_IF_ELEMENT
0349:             * @see #I18N_LOCALE_ATTRIBUTE
0350:             * @since 2.1
0351:             */
0352:            public static final String I18N_CHOOSE_ELEMENT = "choose";
0353:
0354:            /**
0355:             * <code>i18n:when</code> is used to test a locale.
0356:             * It can be used within <code>i18:choose</code> elements or alone.
0357:             * <em>Note: Using <code>locale="*"</code> here has no sense.</em>
0358:             * Example:
0359:             * <pre>
0360:             * &lt;greeting&gt;
0361:             *   &lt;i18n:when locale="en"&gt;Hello&lt;/i18n:when&gt;
0362:             *   &lt;i18n:when locale="fr"&gt;Bonjour&lt;/i18n:when&gt;
0363:             * &lt;/greeting&gt;
0364:             * </pre>
0365:             *
0366:             * @see #I18N_LOCALE_ATTRIBUTE
0367:             * @see #I18N_CHOOSE_ELEMENT
0368:             * @since 2.1
0369:             */
0370:            public static final String I18N_WHEN_ELEMENT = "when";
0371:
0372:            /**
0373:             * <code>i18n:if</code> is used to test a locale. Example:
0374:             * <pre>
0375:             * &lt;greeting&gt;
0376:             *   &lt;i18n:if locale="en"&gt;Hello&lt;/i18n:when&gt;
0377:             *   &lt;i18n:if locale="fr"&gt;Bonjour&lt;/i18n:when&gt;
0378:             * &lt;/greeting&gt;
0379:             * </pre>
0380:             *
0381:             * @see #I18N_LOCALE_ATTRIBUTE
0382:             * @see #I18N_CHOOSE_ELEMENT
0383:             * @see #I18N_WHEN_ELEMENT
0384:             * @since 2.1
0385:             */
0386:            public static final String I18N_IF_ELEMENT = "if";
0387:
0388:            /**
0389:             * <code>i18n:otherwise</code> is used to match any locale when
0390:             * no matching locale has been found inside an <code>i18n:choose</code>
0391:             * block.
0392:             *
0393:             * @see #I18N_CHOOSE_ELEMENT
0394:             * @see #I18N_WHEN_ELEMENT
0395:             * @since 2.1
0396:             */
0397:            public static final String I18N_OTHERWISE_ELEMENT = "otherwise";
0398:
0399:            /**
0400:             * <code>i18n:param</code> is used with i18n:translate to provide
0401:             * substitution params. The param can have <code>i18n:text</code> as
0402:             * its value to provide multilungual value. Parameters can have
0403:             * additional attributes to be used for formatting:
0404:             * <ul>
0405:             *   <li><code>type</code>: can be <code>date, date-time, time,
0406:             *   number, currency, currency-no-unit or percent</code>.
0407:             *   Used to format params before substitution.</li>
0408:             *   <li><code>value</code>: the value of the param. If no value is
0409:             *   specified then the text inside of the param element will be used.</li>
0410:             *   <li><code>locale</code>: used only with <code>number, date, time,
0411:             *   date-time</code> types and used to override the current locale to
0412:             *   format the given value.</li>
0413:             *   <li><code>src-locale</code>: used with <code>number, date, time,
0414:             *   date-time</code> types and specify the locale that should be used to
0415:             *   parse the given value.</li>
0416:             *   <li><code>pattern</code>: used with <code>number, date, time,
0417:             *   date-time</code> types and specify the pattern that should be used
0418:             *   to format the given value.</li>
0419:             *   <li><code>src-pattern</code>: used with <code>number, date, time,
0420:             *   date-time</code> types and specify the pattern that should be used
0421:             *   to parse the given value.</li>
0422:             * </ul>
0423:             *
0424:             * @see #I18N_TRANSLATE_ELEMENT
0425:             * @see #I18N_DATE_ELEMENT
0426:             * @see #I18N_TIME_ELEMENT
0427:             * @see #I18N_DATE_TIME_ELEMENT
0428:             * @see #I18N_NUMBER_ELEMENT
0429:             */
0430:            public static final String I18N_PARAM_ELEMENT = "param";
0431:
0432:            /**
0433:             * This attribute affects a name to the param that could be used
0434:             * for substitution.
0435:             *
0436:             * @since 2.1
0437:             */
0438:            public static final String I18N_PARAM_NAME_ATTRIBUTE = "name";
0439:
0440:            /**
0441:             * <code>i18n:date</code> is used to provide a localized date string.
0442:             * Allowed attributes are: <code>pattern, src-pattern, locale,
0443:             * src-locale</code>. Usage examples:
0444:             * <pre>
0445:             *  &lt;i18n:date src-pattern="short" src-locale="en_US" locale="de_DE"&gt;
0446:             *    12/24/01
0447:             *  &lt;/i18n:date&gt;
0448:             *
0449:             *  &lt;i18n:date pattern="dd/MM/yyyy" /&gt;
0450:             * </pre>
0451:             *
0452:             * If no value is specified then the current date will be used. E.g.:
0453:             * <pre>
0454:             *   &lt;i18n:date /&gt;
0455:             * </pre>
0456:             * Displays the current date formatted with default pattern for
0457:             * the current locale.
0458:             *
0459:             * @see #I18N_PARAM_ELEMENT
0460:             * @see #I18N_DATE_TIME_ELEMENT
0461:             * @see #I18N_TIME_ELEMENT
0462:             * @see #I18N_NUMBER_ELEMENT
0463:             */
0464:            public static final String I18N_DATE_ELEMENT = "date";
0465:
0466:            /**
0467:             * <code>i18n:date-time</code> is used to provide a localized date and
0468:             * time string. Allowed attributes are: <code>pattern, src-pattern,
0469:             * locale, src-locale</code>. Usage examples:
0470:             * <pre>
0471:             *  &lt;i18n:date-time src-pattern="short" src-locale="en_US" locale="de_DE"&gt;
0472:             *    12/24/01 1:00 AM
0473:             *  &lt;/i18n:date&gt;
0474:             *
0475:             *  &lt;i18n:date-time pattern="dd/MM/yyyy hh:mm" /&gt;
0476:             * </pre>
0477:             *
0478:             * If no value is specified then the current date and time will be used.
0479:             * E.g.:
0480:             * <pre>
0481:             *  &lt;i18n:date-time /&gt;
0482:             * </pre>
0483:             * Displays the current date formatted with default pattern for
0484:             * the current locale.
0485:             *
0486:             * @see #I18N_PARAM_ELEMENT
0487:             * @see #I18N_DATE_ELEMENT
0488:             * @see #I18N_TIME_ELEMENT
0489:             * @see #I18N_NUMBER_ELEMENT
0490:             */
0491:            public static final String I18N_DATE_TIME_ELEMENT = "date-time";
0492:
0493:            /**
0494:             * <code>i18n:time</code> is used to provide a localized time string.
0495:             * Allowed attributes are: <code>pattern, src-pattern, locale,
0496:             * src-locale</code>. Usage examples:
0497:             * <pre>
0498:             *  &lt;i18n:time src-pattern="short" src-locale="en_US" locale="de_DE"&gt;
0499:             *    1:00 AM
0500:             *  &lt;/i18n:time&gt;
0501:             *
0502:             * &lt;i18n:time pattern="hh:mm:ss" /&gt;
0503:             * </pre>
0504:             *
0505:             * If no value is specified then the current time will be used. E.g.:
0506:             * <pre>
0507:             *  &lt;i18n:time /&gt;
0508:             * </pre>
0509:             * Displays the current time formatted with default pattern for
0510:             * the current locale.
0511:             *
0512:             * @see #I18N_PARAM_ELEMENT
0513:             * @see #I18N_DATE_TIME_ELEMENT
0514:             * @see #I18N_DATE_ELEMENT
0515:             * @see #I18N_NUMBER_ELEMENT
0516:             */
0517:            public static final String I18N_TIME_ELEMENT = "time";
0518:
0519:            /**
0520:             * <code>i18n:number</code> is used to provide a localized number string.
0521:             * Allowed attributes are: <code>pattern, src-pattern, locale, src-locale,
0522:             * type</code>. Usage examples:
0523:             * <pre>
0524:             *  &lt;i18n:number src-pattern="short" src-locale="en_US" locale="de_DE"&gt;
0525:             *    1000.0
0526:             *  &lt;/i18n:number&gt;
0527:             *
0528:             * &lt;i18n:number type="currency" /&gt;
0529:             * </pre>
0530:             *
0531:             * If no value is specifies then 0 will be used.
0532:             *
0533:             * @see #I18N_PARAM_ELEMENT
0534:             * @see #I18N_DATE_TIME_ELEMENT
0535:             * @see #I18N_TIME_ELEMENT
0536:             * @see #I18N_DATE_ELEMENT
0537:             */
0538:            public static final String I18N_NUMBER_ELEMENT = "number";
0539:
0540:            /**
0541:             * Currency element name
0542:             */
0543:            public static final String I18N_CURRENCY_ELEMENT = "currency";
0544:
0545:            /**
0546:             * Percent element name
0547:             */
0548:            public static final String I18N_PERCENT_ELEMENT = "percent";
0549:
0550:            /**
0551:             * Integer currency element name
0552:             */
0553:            public static final String I18N_INT_CURRENCY_ELEMENT = "int-currency";
0554:
0555:            /**
0556:             * Currency without unit element name
0557:             */
0558:            public static final String I18N_CURRENCY_NO_UNIT_ELEMENT = "currency-no-unit";
0559:
0560:            /**
0561:             * Integer currency without unit element name
0562:             */
0563:            public static final String I18N_INT_CURRENCY_NO_UNIT_ELEMENT = "int-currency-no-unit";
0564:
0565:            //
0566:            // i18n general attributes
0567:            //
0568:
0569:            /**
0570:             * This attribute is used with i18n:text element to indicate the key of
0571:             * the according message. The character data of the element will be used
0572:             * if no message is found by this key. E.g.:
0573:             * <pre>
0574:             * &lt;i18n:text i18n:key="a_key"&gt;article_text1&lt;/i18n:text&gt;
0575:             * </pre>
0576:             */
0577:            public static final String I18N_KEY_ATTRIBUTE = "key";
0578:
0579:            /**
0580:             * This attribute is used with <strong>any</strong> element (even not i18n)
0581:             * to translate attribute values. Should contain whitespace separated
0582:             * attribute names that should be translated:
0583:             * <pre>
0584:             * &lt;para title="first" name="article" i18n:attr="title name"/&gt;
0585:             * </pre>
0586:             * Attribute value considered as key in message catalogue.
0587:             */
0588:            public static final String I18N_ATTR_ATTRIBUTE = "attr";
0589:
0590:            /**
0591:             * This attribute is used with <strong>any</strong> element (even not i18n)
0592:             * to evaluate attribute values. Should contain whitespace separated
0593:             * attribute names that should be evaluated:
0594:             * <pre>
0595:             * &lt;para title="first" name="{one} {two}" i18n:attr="name"/&gt;
0596:             * </pre>
0597:             * Attribute value considered as expression containing text and catalogue
0598:             * keys in curly braces.
0599:             */
0600:            public static final String I18N_EXPR_ATTRIBUTE = "expr";
0601:
0602:            //
0603:            // i18n number and date formatting attributes
0604:            //
0605:
0606:            /**
0607:             * This attribute is used with date and number formatting elements to
0608:             * indicate the pattern that should be used to parse the element value.
0609:             *
0610:             * @see #I18N_PARAM_ELEMENT
0611:             * @see #I18N_DATE_TIME_ELEMENT
0612:             * @see #I18N_DATE_ELEMENT
0613:             * @see #I18N_TIME_ELEMENT
0614:             * @see #I18N_NUMBER_ELEMENT
0615:             */
0616:            public static final String I18N_SRC_PATTERN_ATTRIBUTE = "src-pattern";
0617:
0618:            /**
0619:             * This attribute is used with date and number formatting elements to
0620:             * indicate the pattern that should be used to format the element value.
0621:             *
0622:             * @see #I18N_PARAM_ELEMENT
0623:             * @see #I18N_DATE_TIME_ELEMENT
0624:             * @see #I18N_DATE_ELEMENT
0625:             * @see #I18N_TIME_ELEMENT
0626:             * @see #I18N_NUMBER_ELEMENT
0627:             */
0628:            public static final String I18N_PATTERN_ATTRIBUTE = "pattern";
0629:
0630:            /**
0631:             * This attribute is used with date and number formatting elements to
0632:             * indicate the locale that should be used to format the element value.
0633:             * Also used for in-place translations.
0634:             *
0635:             * @see #I18N_PARAM_ELEMENT
0636:             * @see #I18N_DATE_TIME_ELEMENT
0637:             * @see #I18N_DATE_ELEMENT
0638:             * @see #I18N_TIME_ELEMENT
0639:             * @see #I18N_NUMBER_ELEMENT
0640:             * @see #I18N_WHEN_ELEMENT
0641:             */
0642:            public static final String I18N_LOCALE_ATTRIBUTE = "locale";
0643:
0644:            /**
0645:             * This attribute is used with date and number formatting elements to
0646:             * indicate the locale that should be used to parse the element value.
0647:             *
0648:             * @see #I18N_PARAM_ELEMENT
0649:             * @see #I18N_DATE_TIME_ELEMENT
0650:             * @see #I18N_DATE_ELEMENT
0651:             * @see #I18N_TIME_ELEMENT
0652:             * @see #I18N_NUMBER_ELEMENT
0653:             */
0654:            public static final String I18N_SRC_LOCALE_ATTRIBUTE = "src-locale";
0655:
0656:            /**
0657:             * This attribute is used with date and number formatting elements to
0658:             * indicate the value that should be parsed and formatted. If value
0659:             * attribute is not used then the character data of the element will be used.
0660:             *
0661:             * @see #I18N_PARAM_ELEMENT
0662:             * @see #I18N_DATE_TIME_ELEMENT
0663:             * @see #I18N_DATE_ELEMENT
0664:             * @see #I18N_TIME_ELEMENT
0665:             * @see #I18N_NUMBER_ELEMENT
0666:             */
0667:            public static final String I18N_VALUE_ATTRIBUTE = "value";
0668:
0669:            /**
0670:             * This attribute is used with <code>i18:param</code> to
0671:             * indicate the parameter type: <code>date, time, date-time</code> or
0672:             * <code>number, currency, percent, int-currency, currency-no-unit,
0673:             * int-currency-no-unit</code>.
0674:             * Also used with <code>i18:translate</code> to indicate inplace
0675:             * translations: <code>inplace</code>
0676:             * @deprecated since 2.1. Use nested tags instead, e.g.:
0677:             * &lt;i18n:param&gt;&lt;i18n:date/&gt;&lt;/i18n:param&gt;
0678:             */
0679:            public static final String I18N_TYPE_ATTRIBUTE = "type";
0680:
0681:            /**
0682:             * This attribute is used to specify a different locale for the
0683:             * currency. When specified, this locale will be combined with
0684:             * the "normal" locale: e.g. the seperator symbols are taken from
0685:             * the normal locale but the currency symbol and possition will
0686:             * be taken from the currency locale.
0687:             * This enables to see a currency formatted for Euro but with US
0688:             * grouping and decimal char.
0689:             */
0690:            public static final String CURRENCY_LOCALE_ATTRIBUTE = "currency";
0691:
0692:            /**
0693:             * This attribute can be used on <code>i18n:text</code> to indicate the catalogue
0694:             * from which the key should be retrieved. This attribute is optional,
0695:             * if it is not mentioned the default catalogue is used.
0696:             */
0697:            public static final String I18N_CATALOGUE_ATTRIBUTE = "catalogue";
0698:
0699:            //
0700:            // Configuration parameters
0701:            //
0702:
0703:            /**
0704:             * This configuration parameter specifies the default locale to be used.
0705:             */
0706:            public static final String I18N_LOCALE = "locale";
0707:
0708:            /**
0709:             * This configuration parameter specifies the id of the catalogue to be used as
0710:             * default catalogue, allowing to redefine the default catalogue on the pipeline
0711:             * level.
0712:             */
0713:            public static final String I18N_DEFAULT_CATALOGUE_ID = "default-catalogue-id";
0714:
0715:            /**
0716:             * This configuration parameter specifies the message that should be
0717:             * displayed in case of a not translated text (message not found).
0718:             */
0719:            public static final String I18N_UNTRANSLATED = "untranslated-text";
0720:
0721:            /**
0722:             * This configuration parameter specifies locale for which catalogues should
0723:             * be preloaded.
0724:             */
0725:            public static final String I18N_PRELOAD = "preload";
0726:
0727:            /**
0728:             * <code>fraction-digits</code> attribute is used with
0729:             * <code>i18:number</code> to
0730:             * indicate the number of digits behind the fraction
0731:             */
0732:            public static final String I18N_FRACTION_DIGITS_ATTRIBUTE = "fraction-digits";
0733:
0734:            //
0735:            // States of the transformer
0736:            //
0737:
0738:            private static final int STATE_OUTSIDE = 0;
0739:            private static final int STATE_INSIDE_TEXT = 10;
0740:            private static final int STATE_INSIDE_PARAM = 20;
0741:            private static final int STATE_INSIDE_TRANSLATE = 30;
0742:            private static final int STATE_INSIDE_CHOOSE = 50;
0743:            private static final int STATE_INSIDE_WHEN = 51;
0744:            private static final int STATE_INSIDE_OTHERWISE = 52;
0745:            private static final int STATE_INSIDE_DATE = 60;
0746:            private static final int STATE_INSIDE_DATE_TIME = 61;
0747:            private static final int STATE_INSIDE_TIME = 62;
0748:            private static final int STATE_INSIDE_NUMBER = 63;
0749:
0750:            // All date-time related parameter types and element names
0751:            private static final Set dateTypes;
0752:
0753:            // All number related parameter types and element names
0754:            private static final Set numberTypes;
0755:
0756:            // Date pattern types map: short, medium, long, full
0757:            private static final Map datePatterns;
0758:
0759:            static {
0760:                // initialize date types set
0761:                HashSet set = new HashSet(5);
0762:                set.add(I18N_DATE_ELEMENT);
0763:                set.add(I18N_TIME_ELEMENT);
0764:                set.add(I18N_DATE_TIME_ELEMENT);
0765:                dateTypes = Collections.unmodifiableSet(set);
0766:
0767:                // initialize number types set
0768:                set = new HashSet(9);
0769:                set.add(I18N_NUMBER_ELEMENT);
0770:                set.add(I18N_PERCENT_ELEMENT);
0771:                set.add(I18N_CURRENCY_ELEMENT);
0772:                set.add(I18N_INT_CURRENCY_ELEMENT);
0773:                set.add(I18N_CURRENCY_NO_UNIT_ELEMENT);
0774:                set.add(I18N_INT_CURRENCY_NO_UNIT_ELEMENT);
0775:                numberTypes = Collections.unmodifiableSet(set);
0776:
0777:                // Initialize date patterns map
0778:                Map map = new HashMap(7);
0779:                map.put("SHORT", new Integer(DateFormat.SHORT));
0780:                map.put("MEDIUM", new Integer(DateFormat.MEDIUM));
0781:                map.put("LONG", new Integer(DateFormat.LONG));
0782:                map.put("FULL", new Integer(DateFormat.FULL));
0783:                datePatterns = Collections.unmodifiableMap(map);
0784:            }
0785:
0786:            //
0787:            // Global configuration variables
0788:            //
0789:
0790:            /**
0791:             * Component (service) manager
0792:             */
0793:            protected ServiceManager manager;
0794:
0795:            /**
0796:             * Message bundle loader factory component (service)
0797:             */
0798:            protected BundleFactory factory;
0799:
0800:            /**
0801:             * All catalogues (keyed by catalogue id). The values are instances
0802:             * of {@link CatalogueInfo}.
0803:             */
0804:            private Map catalogues;
0805:
0806:            /**
0807:             * Default (global) catalogue
0808:             */
0809:            private CatalogueInfo defaultCatalogue;
0810:
0811:            /**
0812:             * Default (global) untranslated message value
0813:             */
0814:            private String defaultUntranslated;
0815:
0816:            //
0817:            // Local configuration variables
0818:            //
0819:
0820:            protected Map objectModel;
0821:
0822:            /**
0823:             * Locale
0824:             */
0825:            protected Locale locale;
0826:
0827:            /**
0828:             * Catalogue (local)
0829:             */
0830:            private CatalogueInfo catalogue;
0831:
0832:            /**
0833:             * Current (local) untranslated message value
0834:             */
0835:            private String untranslated;
0836:
0837:            /**
0838:             * {@link SaxBuffer} containing the contents of {@link #untranslated}.
0839:             */
0840:            private ParamSaxBuffer untranslatedRecorder;
0841:
0842:            //
0843:            // Current state of the transformer
0844:            //
0845:
0846:            /**
0847:             * Current state of the transformer. Default value is STATE_OUTSIDE.
0848:             */
0849:            private int current_state;
0850:
0851:            /**
0852:             * Previous state of the transformer.
0853:             * Used in text translation inside params and translate elements.
0854:             */
0855:            private int prev_state;
0856:
0857:            /**
0858:             * The i18n:key attribute is stored for the current element.
0859:             * If no translation found for the key then the character data of element is
0860:             * used as default value.
0861:             */
0862:            private String currentKey;
0863:
0864:            /**
0865:             * Contains the id of the current catalogue if it was explicitely mentioned
0866:             * on an i18n:text element, otherwise it is null.
0867:             */
0868:            private String currentCatalogueId;
0869:
0870:            /**
0871:             * Character data buffer. used to concat chunked character data
0872:             */
0873:            private StringBuffer strBuffer;
0874:
0875:            /**
0876:             * A flag for copying the node when doing in-place translation
0877:             */
0878:            private boolean translate_copy;
0879:
0880:            // A flag for copying the _GOOD_ node and not others
0881:            // when doing in-place translation within i18n:choose
0882:            private boolean translate_end;
0883:
0884:            // Translated text. Inside i18n:translate, collects character events.
0885:            private ParamSaxBuffer tr_text_recorder;
0886:
0887:            // Current "i18n:text" events
0888:            private ParamSaxBuffer text_recorder;
0889:
0890:            // Current parameter events
0891:            private SaxBuffer param_recorder;
0892:
0893:            // Param count when not using i18n:param name="..."
0894:            private int param_count;
0895:
0896:            // Param name attribute for substitution.
0897:            private String param_name;
0898:
0899:            // i18n:param's hashmap for substitution
0900:            private HashMap indexedParams;
0901:
0902:            // Current parameter value (translated or not)
0903:            private String param_value;
0904:
0905:            // Date and number elements and params formatting attributes with values.
0906:            private HashMap formattingParams;
0907:
0908:            /**
0909:             * Returns the current locale setting of this transformer instance.
0910:             * @return current Locale object
0911:             */
0912:            public Locale getLocale() {
0913:                return this .locale;
0914:            }
0915:
0916:            /**
0917:             * Implemenation of CacheableProcessingComponents.
0918:             * Generates unique key for the current locale.
0919:             */
0920:            public java.io.Serializable getKey() {
0921:                // TODO: Key should be composed out of used catalogues locations, and locale.
0922:                //       Right now it is hardcoded only to default catalogue location.
0923:                StringBuffer key = new StringBuffer();
0924:                if (catalogue != null) {
0925:                    key.append(catalogue.getLocation()[0]);
0926:                }
0927:                key.append("?");
0928:                if (locale != null) {
0929:                    key.append(locale.getLanguage());
0930:                    key.append("_");
0931:                    key.append(locale.getCountry());
0932:                    key.append("_");
0933:                    key.append(locale.getVariant());
0934:                }
0935:                return key.toString();
0936:            }
0937:
0938:            /**
0939:             * Implementation of CacheableProcessingComponent.
0940:             * Generates validity object for this transformer or <code>null</code>
0941:             * if this instance is not cacheable.
0942:             */
0943:            public SourceValidity getValidity() {
0944:                // FIXME (KP): Cache validity should be generated by
0945:                // Bundle implementations.
0946:                return org.apache.excalibur.source.impl.validity.NOPValidity.SHARED_INSTANCE;
0947:            }
0948:
0949:            /**
0950:             * Look up the {@link BundleFactory} to be used.
0951:             */
0952:            public void service(ServiceManager manager) throws ServiceException {
0953:                this .manager = manager;
0954:                try {
0955:                    this .factory = (BundleFactory) manager
0956:                            .lookup(BundleFactory.ROLE);
0957:                } catch (ServiceException e) {
0958:                    getLogger().debug(
0959:                            "Failed to lookup <" + BundleFactory.ROLE + ">", e);
0960:                    throw e;
0961:                }
0962:            }
0963:
0964:            /**
0965:             * Implementation of Configurable interface.
0966:             * Configure this transformer.
0967:             */
0968:            public void configure(Configuration conf)
0969:                    throws ConfigurationException {
0970:                // Read in the config options from the transformer definition
0971:                Configuration cataloguesConf = conf.getChild("catalogues",
0972:                        false);
0973:                if (cataloguesConf == null) {
0974:                    throw new ConfigurationException(
0975:                            "Required <catalogues> configuration is missing.",
0976:                            conf);
0977:                }
0978:
0979:                // new configuration style
0980:                Configuration[] catalogueConfs = cataloguesConf
0981:                        .getChildren("catalogue");
0982:                catalogues = new HashMap(catalogueConfs.length + 3);
0983:                for (int i = 0; i < catalogueConfs.length; i++) {
0984:                    String id = catalogueConfs[i].getAttribute("id");
0985:                    String name = catalogueConfs[i].getAttribute("name");
0986:
0987:                    String[] locations;
0988:                    String location = catalogueConfs[i].getAttribute(
0989:                            "location", null);
0990:                    Configuration[] locationConf = catalogueConfs[i]
0991:                            .getChildren("location");
0992:                    if (location != null) {
0993:                        if (locationConf.length > 0) {
0994:                            String msg = "Location attribute cannot be "
0995:                                    + "specified with location elements";
0996:                            getLogger().error(msg);
0997:                            throw new ConfigurationException(msg,
0998:                                    catalogueConfs[i]);
0999:                        }
1000:
1001:                        if (getLogger().isDebugEnabled()) {
1002:                            getLogger().debug(
1003:                                    "name=" + name + ", location=" + location);
1004:                        }
1005:                        locations = new String[1];
1006:                        locations[0] = location;
1007:                    } else {
1008:                        if (locationConf.length == 0) {
1009:                            String msg = "A location attribute or location "
1010:                                    + "elements must be specified";
1011:                            getLogger().error(msg);
1012:                            throw new ConfigurationException(msg,
1013:                                    catalogueConfs[i]);
1014:                        }
1015:
1016:                        locations = new String[locationConf.length];
1017:                        for (int j = 0; j < locationConf.length; ++j) {
1018:                            locations[j] = locationConf[j].getValue();
1019:                            if (getLogger().isDebugEnabled()) {
1020:                                getLogger().debug(
1021:                                        "name=" + name + ", location="
1022:                                                + locations[j]);
1023:                            }
1024:                        }
1025:                    }
1026:
1027:                    CatalogueInfo catalogueInfo;
1028:                    try {
1029:                        catalogueInfo = new CatalogueInfo(name, locations);
1030:                    } catch (PatternException e) {
1031:                        throw new ConfigurationException(
1032:                                "Error in name or location attribute on catalogue "
1033:                                        + "element with id " + id,
1034:                                catalogueConfs[i], e);
1035:                    }
1036:                    catalogues.put(id, catalogueInfo);
1037:                }
1038:
1039:                String defaultCatalogueId = cataloguesConf
1040:                        .getAttribute("default");
1041:                defaultCatalogue = (CatalogueInfo) catalogues
1042:                        .get(defaultCatalogueId);
1043:                if (defaultCatalogue == null) {
1044:                    throw new ConfigurationException("Default catalogue id '"
1045:                            + defaultCatalogueId
1046:                            + "' denotes a nonexisting catalogue",
1047:                            cataloguesConf);
1048:                }
1049:
1050:                // Obtain default text to use for untranslated messages
1051:                defaultUntranslated = conf.getChild(I18N_UNTRANSLATED)
1052:                        .getValue(null);
1053:                if (getLogger().isDebugEnabled()) {
1054:                    getLogger().debug(
1055:                            "Default untranslated text is '"
1056:                                    + defaultUntranslated + "'");
1057:                }
1058:
1059:                // Preload specified catalogues (if any)
1060:                Configuration[] preloadConfs = conf.getChildren(I18N_PRELOAD);
1061:                for (int i = 0; i < preloadConfs.length; i++) {
1062:                    String localeStr = preloadConfs[i].getValue();
1063:                    this .locale = I18nUtils.parseLocale(localeStr);
1064:
1065:                    String id = preloadConfs[i].getAttribute("catalogue", null);
1066:                    if (id != null) {
1067:                        CatalogueInfo catalogueInfo = (CatalogueInfo) catalogues
1068:                                .get(id);
1069:                        if (catalogueInfo == null) {
1070:                            throw new ConfigurationException(
1071:                                    "Invalid catalogue id '" + id
1072:                                            + "' in preload element.",
1073:                                    preloadConfs[i]);
1074:                        }
1075:
1076:                        try {
1077:                            catalogueInfo.getCatalogue();
1078:                        } finally {
1079:                            catalogueInfo.releaseCatalog();
1080:                        }
1081:                    } else {
1082:                        for (Iterator j = catalogues.values().iterator(); j
1083:                                .hasNext();) {
1084:                            CatalogueInfo catalogueInfo = (CatalogueInfo) j
1085:                                    .next();
1086:                            try {
1087:                                catalogueInfo.getCatalogue();
1088:                            } finally {
1089:                                catalogueInfo.releaseCatalog();
1090:                            }
1091:                        }
1092:                    }
1093:                }
1094:                this .locale = null;
1095:            }
1096:
1097:            /**
1098:             * Setup current instance of transformer.
1099:             */
1100:            public void setup(SourceResolver resolver, Map objectModel,
1101:                    String source, Parameters parameters)
1102:                    throws ProcessingException, SAXException, IOException {
1103:
1104:                this .objectModel = objectModel;
1105:
1106:                untranslated = parameters.getParameter(I18N_UNTRANSLATED,
1107:                        defaultUntranslated);
1108:                if (untranslated != null) {
1109:                    untranslatedRecorder = new ParamSaxBuffer();
1110:                    untranslatedRecorder.characters(untranslated.toCharArray(),
1111:                            0, untranslated.length());
1112:                }
1113:
1114:                // Get current locale
1115:                String lc = parameters.getParameter(I18N_LOCALE, null);
1116:                Locale locale = I18nUtils.parseLocale(lc);
1117:                if (getLogger().isDebugEnabled()) {
1118:                    getLogger().debug("Using locale '" + locale + "'");
1119:                }
1120:
1121:                // Initialize instance state variables
1122:                this .locale = locale;
1123:                this .current_state = STATE_OUTSIDE;
1124:                this .prev_state = STATE_OUTSIDE;
1125:                this .currentKey = null;
1126:                this .currentCatalogueId = null;
1127:                this .translate_copy = false;
1128:                this .tr_text_recorder = null;
1129:                this .text_recorder = new ParamSaxBuffer();
1130:                this .param_count = 0;
1131:                this .param_name = null;
1132:                this .param_value = null;
1133:                this .param_recorder = null;
1134:                this .indexedParams = new HashMap(3);
1135:                this .formattingParams = null;
1136:                this .strBuffer = null;
1137:
1138:                // give the catalogue variable its value -- first look if it's locally overridden
1139:                // and otherwise use the component-wide defaults.
1140:                String catalogueId = parameters.getParameter(
1141:                        I18N_DEFAULT_CATALOGUE_ID, null);
1142:                if (catalogueId != null) {
1143:                    CatalogueInfo catalogueInfo = (CatalogueInfo) catalogues
1144:                            .get(catalogueId);
1145:                    if (catalogueInfo == null) {
1146:                        throw new ProcessingException("I18nTransformer: '"
1147:                                + catalogueId
1148:                                + "' is not an existing catalogue id.");
1149:                    }
1150:                    catalogue = catalogueInfo;
1151:                } else {
1152:                    catalogue = defaultCatalogue;
1153:                }
1154:
1155:                if (getLogger().isDebugEnabled()) {
1156:                    getLogger().debug(
1157:                            "Default catalogue is " + catalogue.getName());
1158:                }
1159:            }
1160:
1161:            //
1162:            // Standard SAX event handlers
1163:            //
1164:
1165:            public void startElement(String uri, String name, String raw,
1166:                    Attributes attr) throws SAXException {
1167:
1168:                // Handle previously buffered characters
1169:                if (current_state != STATE_OUTSIDE && strBuffer != null) {
1170:                    i18nCharacters(strBuffer.toString());
1171:                    strBuffer = null;
1172:                }
1173:
1174:                // Process start element event
1175:                if (I18nUtils.matchesI18nNamespace(uri)) {
1176:                    if (getLogger().isDebugEnabled()) {
1177:                        getLogger().debug("Starting i18n element: " + name);
1178:                    }
1179:                    startI18NElement(name, attr);
1180:                } else {
1181:                    // We have a non i18n element event
1182:                    if (current_state == STATE_OUTSIDE) {
1183:                        super .startElement(uri, name, raw, translateAttributes(
1184:                                name, attr));
1185:                    } else if (current_state == STATE_INSIDE_PARAM) {
1186:                        param_recorder.startElement(uri, name, raw, attr);
1187:                    } else if (current_state == STATE_INSIDE_TEXT) {
1188:                        text_recorder.startElement(uri, name, raw, attr);
1189:                    } else if ((current_state == STATE_INSIDE_WHEN || current_state == STATE_INSIDE_OTHERWISE)
1190:                            && translate_copy) {
1191:
1192:                        super .startElement(uri, name, raw, attr);
1193:                    }
1194:                }
1195:            }
1196:
1197:            public void endElement(String uri, String name, String raw)
1198:                    throws SAXException {
1199:
1200:                // Handle previously buffered characters
1201:                if (current_state != STATE_OUTSIDE && strBuffer != null) {
1202:                    i18nCharacters(strBuffer.toString());
1203:                    strBuffer = null;
1204:                }
1205:
1206:                if (I18nUtils.matchesI18nNamespace(uri)) {
1207:                    endI18NElement(name);
1208:                } else if (current_state == STATE_INSIDE_PARAM) {
1209:                    param_recorder.endElement(uri, name, raw);
1210:                } else if (current_state == STATE_INSIDE_TEXT) {
1211:                    text_recorder.endElement(uri, name, raw);
1212:                } else if (current_state == STATE_INSIDE_CHOOSE
1213:                        || (current_state == STATE_INSIDE_WHEN || current_state == STATE_INSIDE_OTHERWISE)
1214:                        && !translate_copy) {
1215:
1216:                    // Output nothing
1217:                } else {
1218:                    super .endElement(uri, name, raw);
1219:                }
1220:            }
1221:
1222:            public void characters(char[] ch, int start, int len)
1223:                    throws SAXException {
1224:
1225:                if (current_state == STATE_OUTSIDE
1226:                        || ((current_state == STATE_INSIDE_WHEN || current_state == STATE_INSIDE_OTHERWISE) && translate_copy)) {
1227:
1228:                    super .characters(ch, start, len);
1229:                } else {
1230:                    // Perform buffering to prevent chunked character data
1231:                    if (strBuffer == null) {
1232:                        strBuffer = new StringBuffer();
1233:                    }
1234:                    strBuffer.append(ch, start, len);
1235:                }
1236:            }
1237:
1238:            //
1239:            // i18n specific event handlers
1240:            //
1241:
1242:            private void startI18NElement(String name, Attributes attr)
1243:                    throws SAXException {
1244:
1245:                if (getLogger().isDebugEnabled()) {
1246:                    getLogger().debug("Start i18n element: " + name);
1247:                }
1248:
1249:                if (I18N_TEXT_ELEMENT.equals(name)) {
1250:                    if (current_state != STATE_OUTSIDE
1251:                            && current_state != STATE_INSIDE_PARAM
1252:                            && current_state != STATE_INSIDE_TRANSLATE) {
1253:
1254:                        throw new SAXException(
1255:                                getClass().getName()
1256:                                        + ": nested i18n:text elements are not allowed."
1257:                                        + " Current state: " + current_state);
1258:                    }
1259:
1260:                    prev_state = current_state;
1261:                    current_state = STATE_INSIDE_TEXT;
1262:
1263:                    currentKey = attr.getValue("", I18N_KEY_ATTRIBUTE);
1264:                    if (currentKey == null) {
1265:                        // Try the namespaced attribute
1266:                        currentKey = attr.getValue(I18N_NAMESPACE_URI,
1267:                                I18N_KEY_ATTRIBUTE);
1268:                        if (currentKey == null) {
1269:                            // Try the old namespace
1270:                            currentKey = attr.getValue(I18N_OLD_NAMESPACE_URI,
1271:                                    I18N_KEY_ATTRIBUTE);
1272:                        }
1273:                    }
1274:
1275:                    currentCatalogueId = attr.getValue("",
1276:                            I18N_CATALOGUE_ATTRIBUTE);
1277:                    if (currentCatalogueId == null) {
1278:                        // Try the namespaced attribute
1279:                        currentCatalogueId = attr.getValue(I18N_NAMESPACE_URI,
1280:                                I18N_CATALOGUE_ATTRIBUTE);
1281:                    }
1282:
1283:                    if (prev_state != STATE_INSIDE_PARAM) {
1284:                        tr_text_recorder = null;
1285:                    }
1286:
1287:                    if (currentKey != null) {
1288:                        tr_text_recorder = getMessage(currentKey,
1289:                                (ParamSaxBuffer) null);
1290:                    }
1291:
1292:                } else if (I18N_TRANSLATE_ELEMENT.equals(name)) {
1293:                    if (current_state != STATE_OUTSIDE) {
1294:                        throw new SAXException(
1295:                                getClass().getName()
1296:                                        + ": i18n:translate element must be used "
1297:                                        + "outside of other i18n elements. Current state: "
1298:                                        + current_state);
1299:                    }
1300:
1301:                    prev_state = current_state;
1302:                    current_state = STATE_INSIDE_TRANSLATE;
1303:                } else if (I18N_PARAM_ELEMENT.equals(name)) {
1304:                    if (current_state != STATE_INSIDE_TRANSLATE) {
1305:                        throw new SAXException(
1306:                                getClass().getName()
1307:                                        + ": i18n:param element can be used only inside "
1308:                                        + "i18n:translate element. Current state: "
1309:                                        + current_state);
1310:                    }
1311:
1312:                    param_name = attr.getValue(I18N_PARAM_NAME_ATTRIBUTE);
1313:                    if (param_name == null) {
1314:                        param_name = String.valueOf(param_count++);
1315:                    }
1316:
1317:                    param_recorder = new SaxBuffer();
1318:                    setFormattingParams(attr);
1319:                    current_state = STATE_INSIDE_PARAM;
1320:                } else if (I18N_CHOOSE_ELEMENT.equals(name)) {
1321:                    if (current_state != STATE_OUTSIDE) {
1322:                        throw new SAXException(getClass().getName()
1323:                                + ": i18n:choose elements cannot be used"
1324:                                + "inside of other i18n elements.");
1325:                    }
1326:
1327:                    translate_copy = false;
1328:                    translate_end = false;
1329:                    prev_state = current_state;
1330:                    current_state = STATE_INSIDE_CHOOSE;
1331:                } else if (I18N_WHEN_ELEMENT.equals(name)
1332:                        || I18N_IF_ELEMENT.equals(name)) {
1333:
1334:                    if (I18N_WHEN_ELEMENT.equals(name)
1335:                            && current_state != STATE_INSIDE_CHOOSE) {
1336:                        throw new SAXException(getClass().getName()
1337:                                + ": i18n:when elements are can be used only"
1338:                                + "inside of i18n:choose elements.");
1339:                    }
1340:
1341:                    if (I18N_IF_ELEMENT.equals(name)
1342:                            && current_state != STATE_OUTSIDE) {
1343:                        throw new SAXException(getClass().getName()
1344:                                + ": i18n:if elements cannot be nested.");
1345:                    }
1346:
1347:                    String locale = attr.getValue(I18N_LOCALE_ATTRIBUTE);
1348:                    if (locale == null)
1349:                        throw new SAXException(
1350:                                getClass().getName()
1351:                                        + ": i18n:"
1352:                                        + name
1353:                                        + " element cannot be used without 'locale' attribute.");
1354:
1355:                    if ((!translate_end && current_state == STATE_INSIDE_CHOOSE)
1356:                            || current_state == STATE_OUTSIDE) {
1357:
1358:                        // Perform soft locale matching
1359:                        if (this .locale.toString().startsWith(locale)) {
1360:                            if (getLogger().isDebugEnabled()) {
1361:                                getLogger().debug("Locale matching: " + locale);
1362:                            }
1363:                            translate_copy = true;
1364:                        }
1365:                    }
1366:
1367:                    prev_state = current_state;
1368:                    current_state = STATE_INSIDE_WHEN;
1369:
1370:                } else if (I18N_OTHERWISE_ELEMENT.equals(name)) {
1371:                    if (current_state != STATE_INSIDE_CHOOSE) {
1372:                        throw new SAXException(getClass().getName()
1373:                                + ": i18n:otherwise elements are not allowed "
1374:                                + "only inside i18n:choose.");
1375:                    }
1376:
1377:                    getLogger().debug("Matching any locale");
1378:                    if (!translate_end) {
1379:                        translate_copy = true;
1380:                    }
1381:
1382:                    prev_state = current_state;
1383:                    current_state = STATE_INSIDE_OTHERWISE;
1384:
1385:                } else if (I18N_DATE_ELEMENT.equals(name)) {
1386:                    if (current_state != STATE_OUTSIDE
1387:                            && current_state != STATE_INSIDE_TEXT
1388:                            && current_state != STATE_INSIDE_PARAM) {
1389:                        throw new SAXException(getClass().getName()
1390:                                + ": i18n:date elements are not allowed "
1391:                                + "inside of other i18n elements.");
1392:                    }
1393:
1394:                    setFormattingParams(attr);
1395:                    prev_state = current_state;
1396:                    current_state = STATE_INSIDE_DATE;
1397:                } else if (I18N_DATE_TIME_ELEMENT.equals(name)) {
1398:                    if (current_state != STATE_OUTSIDE
1399:                            && current_state != STATE_INSIDE_TEXT
1400:                            && current_state != STATE_INSIDE_PARAM) {
1401:                        throw new SAXException(getClass().getName()
1402:                                + ": i18n:date-time elements are not allowed "
1403:                                + "inside of other i18n elements.");
1404:                    }
1405:
1406:                    setFormattingParams(attr);
1407:                    prev_state = current_state;
1408:                    current_state = STATE_INSIDE_DATE_TIME;
1409:                } else if (I18N_TIME_ELEMENT.equals(name)) {
1410:                    if (current_state != STATE_OUTSIDE
1411:                            && current_state != STATE_INSIDE_TEXT
1412:                            && current_state != STATE_INSIDE_PARAM) {
1413:                        throw new SAXException(getClass().getName()
1414:                                + ": i18n:date elements are not allowed "
1415:                                + "inside of other i18n elements.");
1416:                    }
1417:
1418:                    setFormattingParams(attr);
1419:                    prev_state = current_state;
1420:                    current_state = STATE_INSIDE_TIME;
1421:                } else if (I18N_NUMBER_ELEMENT.equals(name)) {
1422:                    if (current_state != STATE_OUTSIDE
1423:                            && current_state != STATE_INSIDE_TEXT
1424:                            && current_state != STATE_INSIDE_PARAM) {
1425:                        throw new SAXException(getClass().getName()
1426:                                + ": i18n:number elements are not allowed "
1427:                                + "inside of other i18n elements.");
1428:                    }
1429:
1430:                    setFormattingParams(attr);
1431:                    prev_state = current_state;
1432:                    current_state = STATE_INSIDE_NUMBER;
1433:                }
1434:            }
1435:
1436:            // Get all possible i18n formatting attribute values and store in a Map
1437:            private void setFormattingParams(Attributes attr) {
1438:                // average number of attributes is 3
1439:                formattingParams = new HashMap(3);
1440:
1441:                String attr_value = attr.getValue(I18N_SRC_PATTERN_ATTRIBUTE);
1442:                if (attr_value != null) {
1443:                    formattingParams
1444:                            .put(I18N_SRC_PATTERN_ATTRIBUTE, attr_value);
1445:                }
1446:
1447:                attr_value = attr.getValue(I18N_PATTERN_ATTRIBUTE);
1448:                if (attr_value != null) {
1449:                    formattingParams.put(I18N_PATTERN_ATTRIBUTE, attr_value);
1450:                }
1451:
1452:                attr_value = attr.getValue(I18N_VALUE_ATTRIBUTE);
1453:                if (attr_value != null) {
1454:                    formattingParams.put(I18N_VALUE_ATTRIBUTE, attr_value);
1455:                }
1456:
1457:                attr_value = attr.getValue(I18N_LOCALE_ATTRIBUTE);
1458:                if (attr_value != null) {
1459:                    formattingParams.put(I18N_LOCALE_ATTRIBUTE, attr_value);
1460:                }
1461:
1462:                attr_value = attr.getValue(CURRENCY_LOCALE_ATTRIBUTE);
1463:                if (attr_value != null) {
1464:                    formattingParams.put(CURRENCY_LOCALE_ATTRIBUTE, attr_value);
1465:                }
1466:
1467:                attr_value = attr.getValue(I18N_SRC_LOCALE_ATTRIBUTE);
1468:                if (attr_value != null) {
1469:                    formattingParams.put(I18N_SRC_LOCALE_ATTRIBUTE, attr_value);
1470:                }
1471:
1472:                attr_value = attr.getValue(I18N_TYPE_ATTRIBUTE);
1473:                if (attr_value != null) {
1474:                    formattingParams.put(I18N_TYPE_ATTRIBUTE, attr_value);
1475:                }
1476:
1477:                attr_value = attr.getValue(I18N_FRACTION_DIGITS_ATTRIBUTE);
1478:                if (attr_value != null) {
1479:                    formattingParams.put(I18N_FRACTION_DIGITS_ATTRIBUTE,
1480:                            attr_value);
1481:                }
1482:            }
1483:
1484:            private void endI18NElement(String name) throws SAXException {
1485:                if (getLogger().isDebugEnabled()) {
1486:                    getLogger().debug("End i18n element: " + name);
1487:                }
1488:
1489:                switch (current_state) {
1490:                case STATE_INSIDE_TEXT:
1491:                    endTextElement();
1492:                    break;
1493:
1494:                case STATE_INSIDE_TRANSLATE:
1495:                    endTranslateElement();
1496:                    break;
1497:
1498:                case STATE_INSIDE_CHOOSE:
1499:                    endChooseElement();
1500:                    break;
1501:
1502:                case STATE_INSIDE_WHEN:
1503:                case STATE_INSIDE_OTHERWISE:
1504:                    endWhenElement();
1505:                    break;
1506:
1507:                case STATE_INSIDE_PARAM:
1508:                    endParamElement();
1509:                    break;
1510:
1511:                case STATE_INSIDE_DATE:
1512:                case STATE_INSIDE_DATE_TIME:
1513:                case STATE_INSIDE_TIME:
1514:                    endDate_TimeElement();
1515:                    break;
1516:
1517:                case STATE_INSIDE_NUMBER:
1518:                    endNumberElement();
1519:                    break;
1520:                }
1521:            }
1522:
1523:            private void i18nCharacters(String textValue) throws SAXException {
1524:                if (getLogger().isDebugEnabled()) {
1525:                    getLogger()
1526:                            .debug("i18n message text = '" + textValue + "'");
1527:                }
1528:
1529:                SaxBuffer buffer;
1530:                switch (current_state) {
1531:                case STATE_INSIDE_TEXT:
1532:                    buffer = text_recorder;
1533:                    break;
1534:
1535:                case STATE_INSIDE_PARAM:
1536:                    buffer = param_recorder;
1537:                    break;
1538:
1539:                case STATE_INSIDE_WHEN:
1540:                case STATE_INSIDE_OTHERWISE:
1541:                    // Previously handeld to avoid the String() conversion.
1542:                    return;
1543:
1544:                case STATE_INSIDE_TRANSLATE:
1545:                    if (tr_text_recorder == null) {
1546:                        tr_text_recorder = new ParamSaxBuffer();
1547:                    }
1548:                    buffer = tr_text_recorder;
1549:                    break;
1550:
1551:                case STATE_INSIDE_CHOOSE:
1552:                    // No characters allowed. Send an exception ?
1553:                    if (getLogger().isDebugEnabled()) {
1554:                        textValue = textValue.trim();
1555:                        if (textValue.length() > 0) {
1556:                            getLogger().debug(
1557:                                    "No characters allowed inside <i18n:choose> tag. Received: "
1558:                                            + textValue);
1559:                        }
1560:                    }
1561:                    return;
1562:
1563:                case STATE_INSIDE_DATE:
1564:                case STATE_INSIDE_DATE_TIME:
1565:                case STATE_INSIDE_TIME:
1566:                case STATE_INSIDE_NUMBER:
1567:                    // Trim text values to avoid parsing errors.
1568:                    textValue = textValue.trim();
1569:                    if (textValue.length() > 0) {
1570:                        if (formattingParams.get(I18N_VALUE_ATTRIBUTE) == null) {
1571:                            formattingParams.put(I18N_VALUE_ATTRIBUTE,
1572:                                    textValue);
1573:                        } else {
1574:                            // ignore the text inside of date element
1575:                        }
1576:                    }
1577:                    return;
1578:
1579:                default:
1580:                    throw new IllegalStateException(getClass().getName()
1581:                            + " developer's fault: characters not handled. "
1582:                            + "Current state: " + current_state);
1583:                }
1584:
1585:                char[] ch = textValue.toCharArray();
1586:                buffer.characters(ch, 0, ch.length);
1587:            }
1588:
1589:            // Translate all attributes that are listed in i18n:attr attribute
1590:            private Attributes translateAttributes(final String element,
1591:                    Attributes attr) throws SAXException {
1592:                if (attr == null) {
1593:                    return null;
1594:                }
1595:
1596:                AttributesImpl tempAttr = null;
1597:
1598:                // Translate all attributes from i18n:attr="name1 name2 ..."
1599:                // using their values as keys.
1600:                int attrIndex = attr.getIndex(I18N_NAMESPACE_URI,
1601:                        I18N_ATTR_ATTRIBUTE);
1602:                if (attrIndex == -1) {
1603:                    // Try the old namespace
1604:                    attrIndex = attr.getIndex(I18N_OLD_NAMESPACE_URI,
1605:                            I18N_ATTR_ATTRIBUTE);
1606:                }
1607:
1608:                if (attrIndex != -1) {
1609:                    StringTokenizer st = new StringTokenizer(attr
1610:                            .getValue(attrIndex));
1611:
1612:                    // Make a copy which we are going to modify
1613:                    tempAttr = new AttributesImpl(attr);
1614:                    // Remove the i18n:attr attribute - we don't need it anymore
1615:                    tempAttr.removeAttribute(attrIndex);
1616:
1617:                    // Iterate through listed attributes and translate them
1618:                    while (st.hasMoreElements()) {
1619:                        final String name = st.nextToken();
1620:
1621:                        int index = tempAttr.getIndex(name);
1622:                        if (index == -1) {
1623:                            getLogger().warn(
1624:                                    "Attribute " + name
1625:                                            + " not found in element <"
1626:                                            + element + ">");
1627:                            continue;
1628:                        }
1629:
1630:                        String value = translateAttribute(element, name,
1631:                                tempAttr.getValue(index));
1632:                        if (value != null) {
1633:                            // Set the translated value. If null, do nothing.
1634:                            tempAttr.setValue(index, value);
1635:                        }
1636:                    }
1637:
1638:                    attr = tempAttr;
1639:                }
1640:
1641:                // Translate all attributes from i18n:expr="name1 name2 ..."
1642:                // using their values as keys.
1643:                attrIndex = attr.getIndex(I18N_NAMESPACE_URI,
1644:                        I18N_EXPR_ATTRIBUTE);
1645:                if (attrIndex != -1) {
1646:                    StringTokenizer st = new StringTokenizer(attr
1647:                            .getValue(attrIndex));
1648:
1649:                    if (tempAttr == null) {
1650:                        tempAttr = new AttributesImpl(attr);
1651:                    }
1652:                    tempAttr.removeAttribute(attrIndex);
1653:
1654:                    // Iterate through listed attributes and evaluate them
1655:                    while (st.hasMoreElements()) {
1656:                        final String name = st.nextToken();
1657:
1658:                        int index = tempAttr.getIndex(name);
1659:                        if (index == -1) {
1660:                            getLogger().warn(
1661:                                    "Attribute " + name
1662:                                            + " not found in element <"
1663:                                            + element + ">");
1664:                            continue;
1665:                        }
1666:
1667:                        final StringBuffer translated = new StringBuffer();
1668:
1669:                        // Evaluate {..} expression
1670:                        VariableExpressionTokenizer.TokenReciever tr = new VariableExpressionTokenizer.TokenReciever() {
1671:                            private String catalogueName;
1672:
1673:                            public void addToken(int type, String value) {
1674:                                if (type == MODULE) {
1675:                                    this .catalogueName = value;
1676:                                } else if (type == VARIABLE) {
1677:                                    translated.append(translateAttribute(
1678:                                            element, name, value));
1679:                                } else if (type == TEXT) {
1680:                                    if (this .catalogueName != null) {
1681:                                        translated.append(translateAttribute(
1682:                                                element, name,
1683:                                                this .catalogueName + ":"
1684:                                                        + value));
1685:                                        this .catalogueName = null;
1686:                                    } else if (value != null) {
1687:                                        translated.append(value);
1688:                                    }
1689:                                }
1690:                            }
1691:                        };
1692:
1693:                        try {
1694:                            VariableExpressionTokenizer.tokenize(tempAttr
1695:                                    .getValue(index), tr);
1696:                        } catch (PatternException e) {
1697:                            throw new SAXException(e);
1698:                        }
1699:
1700:                        // Set the translated value.
1701:                        tempAttr.setValue(index, translated.toString());
1702:                    }
1703:
1704:                    attr = tempAttr;
1705:                }
1706:
1707:                // nothing to translate, just return
1708:                return attr;
1709:            }
1710:
1711:            /**
1712:             * Translate attribute value.
1713:             * Value can be prefixed with catalogue ID and semicolon.
1714:             * @return Translated text, untranslated text, or null.
1715:             */
1716:            private String translateAttribute(String element, String name,
1717:                    String key) {
1718:                // Check if the key contains a colon, if so the text before
1719:                // the colon denotes a catalogue ID.
1720:                int colonPos = key.indexOf(":");
1721:                String catalogueID = null;
1722:                if (colonPos != -1) {
1723:                    catalogueID = key.substring(0, colonPos);
1724:                    key = key.substring(colonPos + 1, key.length());
1725:                }
1726:
1727:                final SaxBuffer text = getMessage(catalogueID, key);
1728:                if (text == null) {
1729:                    getLogger().warn(
1730:                            "Translation not found for attribute " + name
1731:                                    + " in element <" + element + ">");
1732:                    return untranslated;
1733:                }
1734:                return text.toString();
1735:            }
1736:
1737:            private void endTextElement() throws SAXException {
1738:                switch (prev_state) {
1739:                case STATE_OUTSIDE:
1740:                    if (tr_text_recorder == null) {
1741:                        if (currentKey == null) {
1742:                            // Use the text as key. Not recommended for large strings,
1743:                            // especially if they include markup.
1744:                            tr_text_recorder = getMessage(text_recorder
1745:                                    .toString(), text_recorder);
1746:                        } else {
1747:                            // We have the key, but couldn't find a translation
1748:                            if (getLogger().isDebugEnabled()) {
1749:                                getLogger().debug(
1750:                                        "Translation not found for key '"
1751:                                                + currentKey + "'");
1752:                            }
1753:
1754:                            // Use the untranslated-text only when the content of the i18n:text
1755:                            // element was empty
1756:                            if (text_recorder.isEmpty()
1757:                                    && untranslatedRecorder != null) {
1758:                                tr_text_recorder = untranslatedRecorder;
1759:                            } else {
1760:                                tr_text_recorder = text_recorder;
1761:                            }
1762:                        }
1763:                    }
1764:
1765:                    if (tr_text_recorder != null) {
1766:                        tr_text_recorder.toSAX(this .contentHandler);
1767:                    }
1768:
1769:                    text_recorder.recycle();
1770:                    tr_text_recorder = null;
1771:                    currentKey = null;
1772:                    currentCatalogueId = null;
1773:                    break;
1774:
1775:                case STATE_INSIDE_TRANSLATE:
1776:                    if (tr_text_recorder == null) {
1777:                        if (!text_recorder.isEmpty()) {
1778:                            tr_text_recorder = getMessage(text_recorder
1779:                                    .toString(), text_recorder);
1780:                            if (tr_text_recorder == text_recorder) {
1781:                                // If the default value was returned, make a copy
1782:                                tr_text_recorder = new ParamSaxBuffer(
1783:                                        text_recorder);
1784:                            }
1785:                        }
1786:                    }
1787:
1788:                    text_recorder.recycle();
1789:                    break;
1790:
1791:                case STATE_INSIDE_PARAM:
1792:                    // We send the translated text to the param recorder, after trying to translate it.
1793:                    // Remember you can't give a key when inside a param, that'll be nonsense!
1794:                    // No need to clone. We just send the events.
1795:                    if (!text_recorder.isEmpty()) {
1796:                        getMessage(text_recorder.toString(), text_recorder)
1797:                                .toSAX(param_recorder);
1798:                        text_recorder.recycle();
1799:                    }
1800:                    break;
1801:                }
1802:
1803:                current_state = prev_state;
1804:                prev_state = STATE_OUTSIDE;
1805:            }
1806:
1807:            // Process substitution parameter
1808:            private void endParamElement() throws SAXException {
1809:                String paramType = (String) formattingParams
1810:                        .get(I18N_TYPE_ATTRIBUTE);
1811:                if (paramType != null) {
1812:                    // We have a typed parameter
1813:
1814:                    if (getLogger().isDebugEnabled()) {
1815:                        getLogger().debug("Param type: " + paramType);
1816:                    }
1817:                    if (formattingParams.get(I18N_VALUE_ATTRIBUTE) == null
1818:                            && param_value != null) {
1819:                        if (getLogger().isDebugEnabled()) {
1820:                            getLogger()
1821:                                    .debug("Put param value: " + param_value);
1822:                        }
1823:                        formattingParams.put(I18N_VALUE_ATTRIBUTE, param_value);
1824:                    }
1825:
1826:                    // Check if we have a date or a number parameter
1827:                    if (dateTypes.contains(paramType)) {
1828:                        if (getLogger().isDebugEnabled()) {
1829:                            getLogger().debug(
1830:                                    "Formatting date_time param: "
1831:                                            + formattingParams);
1832:                        }
1833:                        param_value = formatDate_Time(formattingParams);
1834:                    } else if (numberTypes.contains(paramType)) {
1835:                        if (getLogger().isDebugEnabled()) {
1836:                            getLogger().debug(
1837:                                    "Formatting number param: "
1838:                                            + formattingParams);
1839:                        }
1840:                        param_value = formatNumber(formattingParams);
1841:                    }
1842:                    if (getLogger().isDebugEnabled()) {
1843:                        getLogger().debug(
1844:                                "Added substitution param: " + param_value);
1845:                    }
1846:                }
1847:
1848:                param_value = null;
1849:                current_state = STATE_INSIDE_TRANSLATE;
1850:
1851:                if (param_recorder == null) {
1852:                    return;
1853:                }
1854:
1855:                indexedParams.put(param_name, param_recorder);
1856:                param_recorder = null;
1857:            }
1858:
1859:            private void endTranslateElement() throws SAXException {
1860:                if (tr_text_recorder != null) {
1861:                    if (getLogger().isDebugEnabled()) {
1862:                        getLogger().debug(
1863:                                "End of translate with params. "
1864:                                        + "Fragment for substitution : "
1865:                                        + tr_text_recorder);
1866:                    }
1867:                    tr_text_recorder.toSAX(super .contentHandler, indexedParams);
1868:                    tr_text_recorder = null;
1869:                    text_recorder.recycle();
1870:                }
1871:
1872:                indexedParams.clear();
1873:                param_count = 0;
1874:                current_state = STATE_OUTSIDE;
1875:            }
1876:
1877:            private void endChooseElement() {
1878:                current_state = STATE_OUTSIDE;
1879:            }
1880:
1881:            private void endWhenElement() {
1882:                current_state = prev_state;
1883:                if (translate_copy) {
1884:                    translate_copy = false;
1885:                    translate_end = true;
1886:                }
1887:            }
1888:
1889:            private void endDate_TimeElement() throws SAXException {
1890:                String result = formatDate_Time(formattingParams);
1891:                switch (prev_state) {
1892:                case STATE_OUTSIDE:
1893:                    super .contentHandler.characters(result.toCharArray(), 0,
1894:                            result.length());
1895:                    break;
1896:                case STATE_INSIDE_PARAM:
1897:                    param_recorder.characters(result.toCharArray(), 0, result
1898:                            .length());
1899:                    break;
1900:                case STATE_INSIDE_TEXT:
1901:                    text_recorder.characters(result.toCharArray(), 0, result
1902:                            .length());
1903:                    break;
1904:                }
1905:                current_state = prev_state;
1906:            }
1907:
1908:            // Helper method: creates Locale object from a string value in a map
1909:            private Locale getLocale(Map params, String attribute) {
1910:                // the specific locale value
1911:                String lc = (String) params.get(attribute);
1912:                return I18nUtils.parseLocale(lc, this .locale);
1913:            }
1914:
1915:            private String formatDate_Time(Map params) throws SAXException {
1916:                // Check that we have not null params
1917:                if (params == null) {
1918:                    throw new IllegalArgumentException("Nothing to format");
1919:                }
1920:
1921:                // Formatters
1922:                SimpleDateFormat to_fmt;
1923:                SimpleDateFormat from_fmt;
1924:
1925:                // Date formatting styles
1926:                int srcStyle = DateFormat.DEFAULT;
1927:                int style = DateFormat.DEFAULT;
1928:
1929:                // Date formatting patterns
1930:                boolean realPattern = false;
1931:                boolean realSrcPattern = false;
1932:
1933:                // From locale
1934:                Locale srcLoc = getLocale(params, I18N_SRC_LOCALE_ATTRIBUTE);
1935:                // To locale
1936:                Locale loc = getLocale(params, I18N_LOCALE_ATTRIBUTE);
1937:
1938:                // From pattern
1939:                String srcPattern = (String) params
1940:                        .get(I18N_SRC_PATTERN_ATTRIBUTE);
1941:                // To pattern
1942:                String pattern = (String) params.get(I18N_PATTERN_ATTRIBUTE);
1943:                // The date value
1944:                String value = (String) params.get(I18N_VALUE_ATTRIBUTE);
1945:
1946:                // A src-pattern attribute is present
1947:                if (srcPattern != null) {
1948:                    // Check if we have a real pattern
1949:                    Integer patternValue = (Integer) datePatterns
1950:                            .get(srcPattern.toUpperCase());
1951:                    if (patternValue != null) {
1952:                        srcStyle = patternValue.intValue();
1953:                    } else {
1954:                        realSrcPattern = true;
1955:                    }
1956:                }
1957:
1958:                // A pattern attribute is present
1959:                if (pattern != null) {
1960:                    Integer patternValue = (Integer) datePatterns.get(pattern
1961:                            .toUpperCase());
1962:                    if (patternValue != null) {
1963:                        style = patternValue.intValue();
1964:                    } else {
1965:                        realPattern = true;
1966:                    }
1967:                }
1968:
1969:                // If we are inside of a typed param
1970:                String paramType = (String) formattingParams
1971:                        .get(I18N_TYPE_ATTRIBUTE);
1972:
1973:                // Initializing date formatters
1974:                if (current_state == STATE_INSIDE_DATE
1975:                        || I18N_DATE_ELEMENT.equals(paramType)) {
1976:
1977:                    to_fmt = (SimpleDateFormat) DateFormat.getDateInstance(
1978:                            style, loc);
1979:                    from_fmt = (SimpleDateFormat) DateFormat.getDateInstance(
1980:                            srcStyle, srcLoc);
1981:                } else if (current_state == STATE_INSIDE_DATE_TIME
1982:                        || I18N_DATE_TIME_ELEMENT.equals(paramType)) {
1983:                    to_fmt = (SimpleDateFormat) DateFormat.getDateTimeInstance(
1984:                            style, style, loc);
1985:                    from_fmt = (SimpleDateFormat) DateFormat
1986:                            .getDateTimeInstance(srcStyle, srcStyle, srcLoc);
1987:                } else {
1988:                    // STATE_INSIDE_TIME or param type='time'
1989:                    to_fmt = (SimpleDateFormat) DateFormat.getTimeInstance(
1990:                            style, loc);
1991:                    from_fmt = (SimpleDateFormat) DateFormat.getTimeInstance(
1992:                            srcStyle, srcLoc);
1993:                }
1994:
1995:                // parsed date object
1996:                Date dateValue;
1997:
1998:                // pattern overwrites locale format
1999:                if (realSrcPattern) {
2000:                    from_fmt.applyPattern(srcPattern);
2001:                }
2002:
2003:                if (realPattern) {
2004:                    to_fmt.applyPattern(pattern);
2005:                }
2006:
2007:                // get current date and time by default
2008:                if (value == null) {
2009:                    dateValue = new Date();
2010:                } else {
2011:                    try {
2012:                        dateValue = from_fmt.parse(value);
2013:                    } catch (ParseException pe) {
2014:                        throw new SAXException(this .getClass().getName()
2015:                                + "i18n:date - parsing error.", pe);
2016:                    }
2017:                }
2018:
2019:                // we have all necessary data here: do formatting.
2020:                if (getLogger().isDebugEnabled()) {
2021:                    getLogger().debug(
2022:                            "### Formatting date: " + dateValue
2023:                                    + " with localized pattern "
2024:                                    + to_fmt.toLocalizedPattern()
2025:                                    + " for locale: " + locale);
2026:                }
2027:                return to_fmt.format(dateValue);
2028:            }
2029:
2030:            private void endNumberElement() throws SAXException {
2031:                String result = formatNumber(formattingParams);
2032:                switch (prev_state) {
2033:                case STATE_OUTSIDE:
2034:                    super .contentHandler.characters(result.toCharArray(), 0,
2035:                            result.length());
2036:                    break;
2037:                case STATE_INSIDE_PARAM:
2038:                    param_recorder.characters(result.toCharArray(), 0, result
2039:                            .length());
2040:                    break;
2041:                case STATE_INSIDE_TEXT:
2042:                    text_recorder.characters(result.toCharArray(), 0, result
2043:                            .length());
2044:                    break;
2045:                }
2046:                current_state = prev_state;
2047:            }
2048:
2049:            private String formatNumber(Map params) throws SAXException {
2050:                if (params == null) {
2051:                    throw new SAXException(this .getClass().getName()
2052:                            + ": i18n:number - error in element attributes.");
2053:                }
2054:
2055:                // from pattern
2056:                String srcPattern = (String) params
2057:                        .get(I18N_SRC_PATTERN_ATTRIBUTE);
2058:                // to pattern
2059:                String pattern = (String) params.get(I18N_PATTERN_ATTRIBUTE);
2060:                // the number value
2061:                String value = (String) params.get(I18N_VALUE_ATTRIBUTE);
2062:
2063:                if (value == null)
2064:                    return "";
2065:                // type
2066:                String type = (String) params.get(I18N_TYPE_ATTRIBUTE);
2067:
2068:                // fraction-digits
2069:                int fractionDigits = -1;
2070:                try {
2071:                    String fd = (String) params
2072:                            .get(I18N_FRACTION_DIGITS_ATTRIBUTE);
2073:                    if (fd != null)
2074:                        fractionDigits = Integer.parseInt(fd);
2075:                } catch (NumberFormatException nfe) {
2076:                    getLogger().warn(
2077:                            "Error in number format with fraction-digits", nfe);
2078:                }
2079:
2080:                // parsed number
2081:                Number numberValue;
2082:
2083:                // locale, may be switched locale
2084:                Locale loc = getLocale(params, I18N_LOCALE_ATTRIBUTE);
2085:                Locale srcLoc = getLocale(params, I18N_SRC_LOCALE_ATTRIBUTE);
2086:                // currency locale
2087:                Locale currencyLoc = getLocale(params,
2088:                        CURRENCY_LOCALE_ATTRIBUTE);
2089:                // decimal and grouping locale
2090:                Locale dgLoc = null;
2091:                if (currencyLoc != null) {
2092:                    // the reasoning here is: if there is a currency locale, then start from that
2093:                    // one but take certain properties (like decimal and grouping seperation symbols)
2094:                    // from the default locale (this happens further on).
2095:                    dgLoc = loc;
2096:                    loc = currencyLoc;
2097:                }
2098:
2099:                // src format
2100:                DecimalFormat from_fmt = (DecimalFormat) NumberFormat
2101:                        .getInstance(srcLoc);
2102:                int int_currency = 0;
2103:
2104:                // src-pattern overwrites locale format
2105:                if (srcPattern != null) {
2106:                    from_fmt.applyPattern(srcPattern);
2107:                }
2108:
2109:                // to format
2110:                DecimalFormat to_fmt;
2111:                char dec = from_fmt.getDecimalFormatSymbols()
2112:                        .getDecimalSeparator();
2113:                int decAt = 0;
2114:                boolean appendDec = false;
2115:
2116:                if (type == null || type.equals(I18N_NUMBER_ELEMENT)) {
2117:                    to_fmt = (DecimalFormat) NumberFormat.getInstance(loc);
2118:                    to_fmt.setMaximumFractionDigits(309);
2119:                    for (int i = value.length() - 1; i >= 0
2120:                            && value.charAt(i) != dec; i--, decAt++) {
2121:                    }
2122:
2123:                    if (decAt < value.length())
2124:                        to_fmt.setMinimumFractionDigits(decAt);
2125:                    decAt = 0;
2126:                    for (int i = 0; i < value.length()
2127:                            && value.charAt(i) != dec; i++) {
2128:                        if (Character.isDigit(value.charAt(i))) {
2129:                            decAt++;
2130:                        }
2131:                    }
2132:
2133:                    to_fmt.setMinimumIntegerDigits(decAt);
2134:                    if (value.charAt(value.length() - 1) == dec) {
2135:                        appendDec = true;
2136:                    }
2137:                } else if (type.equals(I18N_CURRENCY_ELEMENT)) {
2138:                    to_fmt = (DecimalFormat) NumberFormat
2139:                            .getCurrencyInstance(loc);
2140:                } else if (type.equals(I18N_INT_CURRENCY_ELEMENT)) {
2141:                    to_fmt = (DecimalFormat) NumberFormat
2142:                            .getCurrencyInstance(loc);
2143:                    int_currency = 1;
2144:                    for (int i = 0; i < to_fmt.getMaximumFractionDigits(); i++) {
2145:                        int_currency *= 10;
2146:                    }
2147:                } else if (type.equals(I18N_CURRENCY_NO_UNIT_ELEMENT)) {
2148:                    DecimalFormat tmp = (DecimalFormat) NumberFormat
2149:                            .getCurrencyInstance(loc);
2150:                    to_fmt = (DecimalFormat) NumberFormat.getInstance(loc);
2151:                    to_fmt.setMinimumFractionDigits(tmp
2152:                            .getMinimumFractionDigits());
2153:                    to_fmt.setMaximumFractionDigits(tmp
2154:                            .getMaximumFractionDigits());
2155:                } else if (type.equals(I18N_INT_CURRENCY_NO_UNIT_ELEMENT)) {
2156:                    DecimalFormat tmp = (DecimalFormat) NumberFormat
2157:                            .getCurrencyInstance(loc);
2158:                    int_currency = 1;
2159:                    for (int i = 0; i < tmp.getMaximumFractionDigits(); i++)
2160:                        int_currency *= 10;
2161:                    to_fmt = (DecimalFormat) NumberFormat.getInstance(loc);
2162:                    to_fmt.setMinimumFractionDigits(tmp
2163:                            .getMinimumFractionDigits());
2164:                    to_fmt.setMaximumFractionDigits(tmp
2165:                            .getMaximumFractionDigits());
2166:                } else if (type.equals(I18N_PERCENT_ELEMENT)) {
2167:                    to_fmt = (DecimalFormat) NumberFormat
2168:                            .getPercentInstance(loc);
2169:                } else {
2170:                    throw new SAXException("&lt;i18n:number>: unknown type: "
2171:                            + type);
2172:                }
2173:
2174:                if (fractionDigits > -1) {
2175:                    to_fmt.setMinimumFractionDigits(fractionDigits);
2176:                    to_fmt.setMaximumFractionDigits(fractionDigits);
2177:                }
2178:
2179:                if (dgLoc != null) {
2180:                    DecimalFormat df = (DecimalFormat) NumberFormat
2181:                            .getCurrencyInstance(dgLoc);
2182:                    DecimalFormatSymbols dfsNew = df.getDecimalFormatSymbols();
2183:                    DecimalFormatSymbols dfsOrig = to_fmt
2184:                            .getDecimalFormatSymbols();
2185:                    dfsOrig.setDecimalSeparator(dfsNew.getDecimalSeparator());
2186:                    dfsOrig.setMonetaryDecimalSeparator(dfsNew
2187:                            .getMonetaryDecimalSeparator());
2188:                    dfsOrig.setGroupingSeparator(dfsNew.getGroupingSeparator());
2189:                    to_fmt.setDecimalFormatSymbols(dfsOrig);
2190:                }
2191:
2192:                // pattern overwrites locale format
2193:                if (pattern != null) {
2194:                    to_fmt.applyPattern(pattern);
2195:                }
2196:
2197:                try {
2198:                    numberValue = from_fmt.parse(value);
2199:                    if (int_currency > 0) {
2200:                        numberValue = new Double(numberValue.doubleValue()
2201:                                / int_currency);
2202:                    } else {
2203:                        // what?
2204:                    }
2205:                } catch (ParseException pe) {
2206:                    throw new SAXException(this .getClass().getName()
2207:                            + "i18n:number - parsing error.", pe);
2208:                }
2209:
2210:                // we have all necessary data here: do formatting.
2211:                String result = to_fmt.format(numberValue);
2212:                if (appendDec)
2213:                    result = result + dec;
2214:                if (getLogger().isDebugEnabled()) {
2215:                    getLogger().debug("i18n:number result: " + result);
2216:                }
2217:                return result;
2218:            }
2219:
2220:            //-- Dictionary handling routines
2221:
2222:            /**
2223:             * Helper method to retrieve a message from the dictionary.
2224:             *
2225:             * @param catalogueID if not null, this catalogue will be used instead of the default one.
2226:             * @return SaxBuffer containing message, or null if not found.
2227:             */
2228:            protected ParamSaxBuffer getMessage(String catalogueID, String key) {
2229:                if (getLogger().isDebugEnabled()) {
2230:                    getLogger().debug(
2231:                            "Getting key " + key + " from catalogue "
2232:                                    + catalogueID);
2233:                }
2234:
2235:                CatalogueInfo catalogue = this .catalogue;
2236:                if (catalogueID != null) {
2237:                    catalogue = (CatalogueInfo) catalogues.get(catalogueID);
2238:                    if (catalogue == null) {
2239:                        if (getLogger().isWarnEnabled()) {
2240:                            getLogger()
2241:                                    .warn(
2242:                                            "Catalogue not found: "
2243:                                                    + catalogueID
2244:                                                    + ", will not translate key "
2245:                                                    + key);
2246:                        }
2247:                        return null;
2248:                    }
2249:                }
2250:
2251:                Bundle bundle = catalogue.getCatalogue();
2252:                if (bundle == null) {
2253:                    // Can't translate
2254:                    getLogger().debug("Untranslated key: '" + key + "'");
2255:                    return null;
2256:                }
2257:
2258:                try {
2259:                    return (ParamSaxBuffer) bundle.getObject(key);
2260:                } catch (MissingResourceException e) {
2261:                    getLogger().debug("Untranslated key: '" + key + "'");
2262:                }
2263:
2264:                return null;
2265:            }
2266:
2267:            /**
2268:             * Helper method to retrieve a message from the current dictionary.
2269:             * A default value is returned if message is not found.
2270:             *
2271:             * @return SaxBuffer containing message, or defaultValue if not found.
2272:             */
2273:            private ParamSaxBuffer getMessage(String key,
2274:                    ParamSaxBuffer defaultValue) {
2275:                SaxBuffer value = getMessage(currentCatalogueId, key);
2276:                if (value == null) {
2277:                    return defaultValue;
2278:                }
2279:
2280:                return new ParamSaxBuffer(value);
2281:            }
2282:
2283:            public void recycle() {
2284:                this .untranslatedRecorder = null;
2285:                this .catalogue = null;
2286:                this .objectModel = null;
2287:
2288:                // Release catalogues which were selected for current locale
2289:                Iterator i = catalogues.values().iterator();
2290:                while (i.hasNext()) {
2291:                    CatalogueInfo catalogueInfo = (CatalogueInfo) i.next();
2292:                    catalogueInfo.releaseCatalog();
2293:                }
2294:
2295:                super .recycle();
2296:            }
2297:
2298:            public void dispose() {
2299:                if (manager != null) {
2300:                    manager.release(factory);
2301:                }
2302:                factory = null;
2303:                manager = null;
2304:                catalogues = null;
2305:            }
2306:
2307:            /**
2308:             * Holds information about one catalogue. The location and name of the catalogue
2309:             * can contain references to input modules, and are resolved upon each transformer
2310:             * usage. It is important that releaseCatalog is called when the transformer is recycled.
2311:             */
2312:            public final class CatalogueInfo {
2313:                VariableResolver name;
2314:                VariableResolver[] locations;
2315:                String resolvedName;
2316:                String[] resolvedLocations;
2317:                Bundle catalogue;
2318:
2319:                public CatalogueInfo(String name, String[] locations)
2320:                        throws PatternException {
2321:                    this .name = VariableResolverFactory.getResolver(name,
2322:                            manager);
2323:                    this .locations = new VariableResolver[locations.length];
2324:                    for (int i = 0; i < locations.length; ++i) {
2325:                        this .locations[i] = VariableResolverFactory
2326:                                .getResolver(locations[i], manager);
2327:                    }
2328:                }
2329:
2330:                public String getName() {
2331:                    try {
2332:                        if (resolvedName == null) {
2333:                            resolve();
2334:                        }
2335:                    } catch (Exception e) {
2336:                        // Ignore the error for now
2337:                    }
2338:                    return resolvedName;
2339:                }
2340:
2341:                public String[] getLocation() {
2342:                    try {
2343:                        if (resolvedName == null) {
2344:                            resolve();
2345:                        }
2346:                    } catch (Exception e) {
2347:                        // Ignore the error for now
2348:                    }
2349:                    return resolvedLocations;
2350:                }
2351:
2352:                private void resolve() throws Exception {
2353:                    if (resolvedLocations == null) {
2354:                        resolvedLocations = new String[locations.length];
2355:                        for (int i = 0; i < resolvedLocations.length; ++i) {
2356:                            resolvedLocations[i] = locations[i].resolve(null,
2357:                                    objectModel);
2358:                        }
2359:                    }
2360:                    if (resolvedName == null) {
2361:                        resolvedName = name.resolve(null, objectModel);
2362:                    }
2363:                }
2364:
2365:                public Bundle getCatalogue() {
2366:                    if (catalogue == null) {
2367:                        try {
2368:                            resolve();
2369:                            catalogue = factory.select(resolvedLocations,
2370:                                    resolvedName, locale);
2371:                        } catch (Exception e) {
2372:                            getLogger().error(
2373:                                    "Error obtaining catalogue '" + getName()
2374:                                            + "' from  <" + getLocation()
2375:                                            + "> for locale " + locale, e);
2376:                        }
2377:                    }
2378:
2379:                    return catalogue;
2380:                }
2381:
2382:                public void releaseCatalog() {
2383:                    if (catalogue != null) {
2384:                        factory.release(catalogue);
2385:                    }
2386:                    catalogue = null;
2387:                    resolvedName = null;
2388:                    resolvedLocations = null;
2389:                }
2390:            }
2391:        }
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.