You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@cocoon.apache.org by vg...@apache.org on 2003/12/10 16:37:38 UTC

cvs commit: cocoon-2.1/src/java/org/apache/cocoon/i18n AbstractBundleFactory.java BundleFactory.java I18nUtils.java XMLResourceBundle.java XMLResourceBundleFactory.java package.html

vgritsenko    2003/12/10 07:37:38

  Modified:    src/java/org/apache/cocoon/transformation
                        I18nTransformer.java
               src/java/org/apache/cocoon/i18n AbstractBundleFactory.java
                        BundleFactory.java I18nUtils.java
                        XMLResourceBundle.java
                        XMLResourceBundleFactory.java package.html
  Log:
       Reworked XMLResourceBundle implementation. Now it supports only XML bundles with
       fixed syntax (same as for I18nTransformer dictionaries), and instead of XPath
       keys uses message keys (which is faster). Returned resources now are instances of
       ParamSaxBuffer class, and not DOM nodes.
  
       Reworked I18nTransformer implementation to use new version of XMLResourceBundle.
  
  Revision  Changes    Path
  1.18      +178 -195  cocoon-2.1/src/java/org/apache/cocoon/transformation/I18nTransformer.java
  
  Index: I18nTransformer.java
  ===================================================================
  RCS file: /home/cvs/cocoon-2.1/src/java/org/apache/cocoon/transformation/I18nTransformer.java,v
  retrieving revision 1.17
  retrieving revision 1.18
  diff -u -r1.17 -r1.18
  --- I18nTransformer.java	27 Nov 2003 18:40:03 -0000	1.17
  +++ I18nTransformer.java	10 Dec 2003 15:37:36 -0000	1.18
  @@ -77,7 +77,6 @@
   import org.apache.avalon.framework.configuration.Configuration;
   import org.apache.avalon.framework.configuration.ConfigurationException;
   import org.apache.avalon.framework.parameters.Parameters;
  -
   import org.apache.cocoon.ProcessingException;
   import org.apache.cocoon.caching.CacheableProcessingComponent;
   import org.apache.cocoon.components.treeprocessor.variables.PreparedVariableResolver;
  @@ -86,13 +85,9 @@
   import org.apache.cocoon.i18n.BundleFactory;
   import org.apache.cocoon.i18n.I18nUtils;
   import org.apache.cocoon.sitemap.PatternException;
  -import org.apache.cocoon.transformation.helpers.MirrorRecorder;
  -
  +import org.apache.cocoon.xml.ParamSaxBuffer;
  +import org.apache.cocoon.xml.SaxBuffer;
   import org.apache.excalibur.source.SourceValidity;
  -
  -import org.w3c.dom.Node;
  -import org.w3c.dom.NodeList;
  -
   import org.xml.sax.Attributes;
   import org.xml.sax.SAXException;
   import org.xml.sax.helpers.AttributesImpl;
  @@ -530,25 +525,34 @@
        */
       public static final String I18N_NUMBER_ELEMENT      = "number";
   
  -    /** currency element */
  +    /**
  +     * Currency element name 
  +     */
       public static final String I18N_CURRENCY_ELEMENT    = "currency";
   
  -    /** percent element */
  +    /**
  +     * Percent element name
  +     */
       public static final String I18N_PERCENT_ELEMENT     = "percent";
   
  -    /** integer currency element */
  -    public static final String I18N_INT_CURRENCY_ELEMENT
  -            = "int-currency";
  -
  -    /** currency without unit element */
  -    public static final String I18N_CURRENCY_NO_UNIT_ELEMENT =
  -            "currency-no-unit";
  -
  -    /** integer currency without unit element */
  -    public static final String I18N_INT_CURRENCY_NO_UNIT_ELEMENT =
  -            "int-currency-no-unit";
  +    /**
  +     * Integer currency element name
  +     */
  +    public static final String I18N_INT_CURRENCY_ELEMENT = "int-currency";
  +
  +    /**
  +     * Currency without unit element name
  +     */
  +    public static final String I18N_CURRENCY_NO_UNIT_ELEMENT = "currency-no-unit";
  +
  +    /**
  +     * Integer currency without unit element name
  +     */
  +    public static final String I18N_INT_CURRENCY_NO_UNIT_ELEMENT = "int-currency-no-unit";
   
  +    //
       // i18n general attributes
  +    //
   
       /**
        * This attribute is used with i18n:text element to indicate the key of
  @@ -570,7 +574,9 @@
        */
       public static final String I18N_ATTR_ATTRIBUTE          = "attr";
   
  +    //
       // i18n number and date formatting attributes
  +    //
   
       /**
        * This attribute is used with date and number formatting elements to
  @@ -666,14 +672,15 @@
        */
       public static final String I18N_CATALOGUE_ATTRIBUTE = "catalogue";
   
  +    //
       // Configuration parameters
  +    //
   
       /**
        * This configuration parameter specifies the default locale to be used.
        */
       public static final String I18N_LOCALE      = "locale";
   
  -
       /**
        * This configuration parameter specifies the message catalog name.
        */
  @@ -705,24 +712,16 @@
       public static final String I18N_CACHE_STARTUP       = "cache-at-startup";
   
       /**
  -     * This constant specifies the XPath prefix that will be used
  -     * to retrieve a value from a message catalogue. The resulting XPath is
  -     * looks like this:
  -     * <code>/catalogue/message[@key='key_value']</code>
  -     *<br/>
  -     * FIXME (KP): We need a more generic way of key interpretation: to use
  -     * XPath expression as keys, or keys with non-XML dictionaries.
  -     */
  -    public static final String I18N_CATALOGUE_PREFIX    = "/catalogue/message";
  -
  -    /**
        * <code>fraction-digits</code> attribute is used with
        * <code>i18:number</code> to
        * indicate the number of digits behind the fraction
        */
       public static final String I18N_FRACTION_DIGITS_ATTRIBUTE = "fraction-digits";
   
  +    //
       // States of the transformer
  +    //
  +    
       private static final int STATE_OUTSIDE                       = 0;
       private static final int STATE_INSIDE_TEXT                   = 10;
       private static final int STATE_INSIDE_PARAM                  = 20;
  @@ -735,7 +734,6 @@
       private static final int STATE_INSIDE_TIME                   = 62;
       private static final int STATE_INSIDE_NUMBER                 = 63;
   
  -
       // All date-time related parameter types and element names
       private static final Set dateTypes;
   
  @@ -773,30 +771,87 @@
       }
   
   
  -    // Component manager for this component
  +    /**
  +     * Component Manager
  +     */
       protected ComponentManager manager;
   
  +    /**
  +     * Source Resolver
  +     */
       private SourceResolver sourceResolver;
   
  +    //
  +    // i18n configuration variables
  +    //
  +
  +    /**
  +     * Default catalogue id
  +     */
  +    private String defaultCatalogueId;
  +
  +    /**
  +     * Default (global) untranslated message value
  +     */
  +    private String globalUntranslated;
  +
  +    /**
  +     * Current (local) untranslated message value
  +     */
  +    private String untranslated;
  +
  +    /**
  +     * SaxBuffer containing the contents of {@link #untranslated}.
  +     */
  +    private ParamSaxBuffer untranslatedRecorder;
  +
  +    /**
  +     * Cache at startup configuration parameter value
  +     */
  +    private boolean cacheAtStartup;
  +
  +    // Default catalogue
  +    private Bundle defaultCatalogue;
  +
  +    // All catalogues (hashed on catalgue id). The values are instances of CatalogueInfo.
  +    private Map catalogues = new HashMap();
  +
  +    // Dictionary loader factory
  +    protected BundleFactory factory;
  +
  +    //
  +    // Current state of the transformer
  +    //
  +    
       protected Map objectModel;
   
  -    // Current state of the transformer. The value is STATE_OUTSIDE by default.
  +    /**
  +     * Current state of the transformer. Default value is STATE_OUTSIDE.
  +     */
       private int current_state;
   
  -    // Previous state. Used in text translation inside params and translate
  -    // elements.
  +    /**
  +     * Previous state of the transformer.
  +     * Used in text translation inside params and translate elements.
  +     */
       private int prev_state;
   
  -    // Character data buffer, used to concat chunked character data
  +    /**
  +     * Character data buffer. used to concat chunked character data
  +     */
       private StringBuffer strBuffer;
   
  -    // The i18n:key attribute is stored for the current element.
  -    // If no translation found for the key then the character data of element is
  -    // used as default value.
  +    /**
  +     * The i18n:key attribute is stored for the current element.
  +     * If no translation found for the key then the character data of element is
  +     * used as default value.
  +     */
       private String current_key;
   
  -    // Contains the id of the current catalogue if it was explicitely mentioned
  -    // on an i18n:text element, otherwise it is null.
  +    /**
  +     * Contains the id of the current catalogue if it was explicitely mentioned
  +     * on an i18n:text element, otherwise it is null.
  +     */
       private String currentCatalogueId;
   
       // A flag for copying the node when doing in-place translation
  @@ -806,14 +861,14 @@
       // when doing in-place translation whitin i18n:choose
       private boolean translate_end;
   
  -    // Translated text
  -    private MirrorRecorder tr_text_recorder;
  +    // Translated text. Inside i:translate, collects character events.
  +    private ParamSaxBuffer tr_text_recorder;
   
  -    // Current "i:text" events
  -    private MirrorRecorder text_recorder;
  +    // Current "i18n:text" events
  +    private ParamSaxBuffer text_recorder;
   
       // Current parameter events
  -    private MirrorRecorder param_recorder;
  +    private SaxBuffer param_recorder;
   
       // Param count when not using i:param name="..."
       private int param_count;
  @@ -836,34 +891,6 @@
       // Date and number elements and params formatting attributes with values.
       private HashMap formattingParams;
   
  -    // Default catalogue
  -    private Bundle defaultCatalogue;
  -
  -    // All catalogues (hashed on catalgue id). The values are instances of CatalogueInfo.
  -    private Map catalogues = new HashMap();
  -
  -    // Dictionary loader factory
  -    protected BundleFactory factory;
  -
  -    //
  -    // i18n configuration variables
  -    //
  -
  -    // id of the default catalogue
  -    private String defaultCatalogueId;
  -
  -    // Untranslated message value
  -    private String untranslated;
  -
  -    /* A MirrorRecorder containing the contents of {@link #untranslated}. */
  -    private MirrorRecorder untranslatedRecorder;
  -
  -    // Cache at startup setting value
  -    private boolean cacheAtStartup;
  -
  -    // Helper variable used to hold the old untranslated value
  -    private String globalUntranslated;
  -
   
       /**
        * Returns the current locale setting of this transformer instance.
  @@ -952,9 +979,9 @@
           }
   
           // obtain default text to use for untranslated messages
  -        untranslated = conf.getChild(I18N_UNTRANSLATED).getValue(null);
  +        globalUntranslated = conf.getChild(I18N_UNTRANSLATED).getValue(null);
           if (getLogger().isDebugEnabled()) {
  -            getLogger().debug("Default untranslated text is '" + untranslated + "'");
  +            getLogger().debug("Default untranslated text is '" + globalUntranslated + "'");
           }
   
           // obtain config option, whether to cache messages at startup time
  @@ -977,34 +1004,19 @@
           this.objectModel = objectModel;
   
           try {
  -            // check parameters to see if anything has been locally overloaded
  -            String localUntranslated = null;
  -            String lc = null;
  -            String localDefaultCatalogueId = null;
  -
  -            if (parameters != null) {
  -                localUntranslated =
  -                        parameters.getParameter(I18N_UNTRANSLATED, null);
  -                lc = parameters.getParameter(I18N_LOCALE, null);
  -                localDefaultCatalogueId = parameters.getParameter(I18N_DEFAULT_CATALOGUE_ID, null);
  -            }
  -
  -            // if untranslated-text has been overridden, save the original
  -            // value so it can be restored when this object is recycled.
  -            if (localUntranslated != null) {
  -                globalUntranslated = untranslated;
  -                untranslated = localUntranslated;
  -            }
  -
  +            untranslated = parameters.getParameter(I18N_UNTRANSLATED, globalUntranslated);
               if (untranslated != null) {
  -                untranslatedRecorder = new MirrorRecorder();
  +                untranslatedRecorder = new ParamSaxBuffer();
                   untranslatedRecorder.characters(untranslated.toCharArray(), 0, untranslated.length());
               }
   
  +            String lc = parameters.getParameter(I18N_LOCALE, null);
  +            String localDefaultCatalogueId = parameters.getParameter(I18N_DEFAULT_CATALOGUE_ID, null);
  +
               // Get current locale
               Locale locale = I18nUtils.parseLocale(lc);
               if (getLogger().isDebugEnabled()) {
  -                getLogger().debug("Using locale " + locale.toString());
  +                getLogger().debug("Using locale '" + locale.toString() + "'");
               }
   
               // Initialize instance state variables
  @@ -1015,7 +1027,7 @@
               this.currentCatalogueId = null;
               this.translate_copy     = false;
               this.tr_text_recorder   = null;
  -            this.text_recorder      = new MirrorRecorder();
  +            this.text_recorder      = new ParamSaxBuffer();
               this.param_count        = 0;
               this.param_name         = null;
               this.param_value        = null;
  @@ -1192,7 +1204,7 @@
               }
   
               if (current_key != null) {
  -                tr_text_recorder = getMirrorRecorder(current_key, null);
  +                tr_text_recorder = getMessage(current_key, (ParamSaxBuffer)null);
               }
   
           } else if (I18N_TRANSLATE_ELEMENT.equals(name)) {
  @@ -1222,7 +1234,7 @@
                   param_name = String.valueOf(param_count++);
               }
   
  -            param_recorder = new MirrorRecorder();
  +            param_recorder = new SaxBuffer();
               setFormattingParams(attr);
               current_state = STATE_INSIDE_PARAM;
           } else if (I18N_CHOOSE_ELEMENT.equals(name)) {
  @@ -1440,28 +1452,25 @@
           }
       }
   
  -    private void i18nCharacters(String textValue)
  -    throws SAXException {
  -
  -        if (textValue == null) {
  -            return;
  -        }
  +    private void i18nCharacters(String textValue) throws SAXException {
           // Trim text values to avoid parsing errors.
           textValue = textValue.trim();
  -        if (textValue.length() == 0)
  +        if (textValue.length() == 0) {
               return;
  +        }
   
           if (getLogger().isDebugEnabled()) {
               getLogger().debug( "i18n message text = '" + textValue + "'" );
           }
   
  +        char[] ch = textValue.toCharArray();
           switch (current_state) {
               case STATE_INSIDE_TEXT:
  -                text_recorder.characters(textValue);
  +                text_recorder.characters(ch, 0, ch.length);
                   break;
   
               case STATE_INSIDE_PARAM:
  -                param_recorder.characters(textValue);
  +                param_recorder.characters(ch, 0, ch.length);
                   break;
   
               case STATE_INSIDE_WHEN:
  @@ -1471,9 +1480,9 @@
   
               case STATE_INSIDE_TRANSLATE:
                   if(tr_text_recorder == null) {
  -                    tr_text_recorder = new MirrorRecorder();
  +                    tr_text_recorder = new ParamSaxBuffer();
                   }
  -                tr_text_recorder.characters(textValue);
  +                tr_text_recorder.characters(ch, 0, ch.length);
                   break;
   
               case STATE_INSIDE_CHOOSE:
  @@ -1493,16 +1502,14 @@
                   break;
   
               default:
  -                throw new IllegalStateException(
  -                        this.getClass().getName()
  -                        + " developer's fault: characters not handled"
  -                        + "Current state: " + current_state);
  +                throw new IllegalStateException(getClass().getName() +
  +                                                " developer's fault: characters not handled. " +
  +                                                "Current state: " + current_state);
           }
       }
   
       // Translate all attributes that are listed in i18n:attr attribute
       private Attributes translateAttributes(String name, Attributes attr) {
  -
           if (attr == null) {
               return attr;
           }
  @@ -1533,15 +1540,13 @@
                       // check if the text2translate contains a colon, if so the text before
                       // the colon denotes a catalogue id
                       int colonPos = text2translate.indexOf(":");
  -                    String catalogueId = null;
  +                    String catalogueID = null;
                       if (colonPos != -1) {
  -                        catalogueId = text2translate.substring(0, colonPos);
  +                        catalogueID = text2translate.substring(0, colonPos);
                           text2translate = text2translate.substring(colonPos + 1, text2translate.length());
                       }
  -                    String result =
  -                            getString(text2translate, catalogueId, (untranslated == null)
  -                                                      ? text2translate
  -                                                      : untranslated);
  +                    String result = getString(catalogueID, text2translate,
  +                                              untranslated == null? text2translate : untranslated);
   
                       // set the translated value
                       if (result != null) {
  @@ -1568,18 +1573,18 @@
               case STATE_OUTSIDE:
                   if (tr_text_recorder == null) {
                       if (current_key == null) {
  -                        // Use the text as key
  -                        // Really not recommended for big strings, moreover if they
  -                        // include markup.
  -                        tr_text_recorder = getMirrorRecorder(text_recorder.text(), text_recorder);
  +                        // Use the text as key. Not recommended for large strings,
  +                        // especially if they include markup.
  +                        tr_text_recorder = getMessage(text_recorder.toString(), text_recorder);
                       } else {
  -                        // We have the key, but couldn't find a transltation
  +                        // We have the key, but couldn't find a translation
                           if (getLogger().isDebugEnabled()) {
  -                            getLogger().debug("translation not found for key " + current_key);
  +                            getLogger().debug("Translation not found for key '" + current_key + "'");
                           }
  -                        // use the untranslated-text only when the content of the i18n:text
  +                        
  +                        // Use the untranslated-text only when the content of the i18n:text
                           // element was empty
  -                        if (text_recorder.empty() && untranslatedRecorder != null) {
  +                        if (text_recorder.isEmpty() && untranslatedRecorder != null) {
                               tr_text_recorder = untranslatedRecorder;
                           } else {
                               tr_text_recorder = text_recorder;
  @@ -1588,7 +1593,7 @@
                   }
   
                   if (tr_text_recorder != null) {
  -                    tr_text_recorder.send(this.contentHandler);
  +                    tr_text_recorder.toSAX(this.contentHandler);
                   }
   
                   text_recorder.recycle();
  @@ -1599,10 +1604,12 @@
   
               case STATE_INSIDE_TRANSLATE:
                   if (tr_text_recorder == null) {
  -                    if (!text_recorder.empty()) {
  -                        tr_text_recorder = getMirrorRecorder(text_recorder.text(), text_recorder);
  -                        if (tr_text_recorder == text_recorder) // if the default value was returned
  -                            tr_text_recorder = (MirrorRecorder) text_recorder.clone();
  +                    if (!text_recorder.isEmpty()) {
  +                        tr_text_recorder = getMessage(text_recorder.toString(), text_recorder);
  +                        if (tr_text_recorder == text_recorder) {
  +                            // If the default value was returned, make a copy
  +                            tr_text_recorder = new ParamSaxBuffer(text_recorder);
  +                        }
                       }
                   }
   
  @@ -1610,11 +1617,11 @@
                   break;
   
               case STATE_INSIDE_PARAM:
  -                // We send the translated text to the param recorder,after trying to translate it.
  +                // We send the translated text to the param recorder, after trying to translate it.
                   // Remember you can't give a key when inside a param, that'll be nonsense!
                   // No need to clone. We just send the events.
  -                if (!text_recorder.empty()) {
  -                    getMirrorRecorder(text_recorder.text(), text_recorder).send(param_recorder);
  +                if (!text_recorder.isEmpty()) {
  +                    getMessage(text_recorder.toString(), text_recorder).toSAX(param_recorder);
                       text_recorder.recycle();
                   }
                   break;
  @@ -1664,16 +1671,17 @@
               return;
           }
   
  -        indexedParams.put(param_name, param_recorder.clone());
  +        indexedParams.put(param_name, param_recorder);
  +        param_recorder = null;
       }
   
       private void endTranslateElement() throws SAXException {
           if (tr_text_recorder != null) {
               if (getLogger().isDebugEnabled()) {
  -                getLogger().debug("End of translate with params");
  -                getLogger().debug("Fragment for substitution : " + tr_text_recorder.text());
  +                getLogger().debug("End of translate with params. " +
  +                                  "Fragment for substitution : " + tr_text_recorder);
               }
  -            tr_text_recorder.send(super.contentHandler, indexedParams);
  +            tr_text_recorder.toSAX(super.contentHandler, indexedParams);
               tr_text_recorder = null;
               text_recorder.recycle();
           }
  @@ -2012,20 +2020,20 @@
       //-- Dictionary handling routins
   
       /**
  -     * Helper method to retrieve a message Node from the dictionary.
  -     * A default value is returned if message is not found.
  +     * Helper method to retrieve a message from the dictionary.
        *
  -     * @param catalogueId if not null, this catalogue will be used instead of the default one.
  +     * @param catalogueID if not null, this catalogue will be used instead of the default one.
  +     * @return SaxBuffer containing message, or null if not found.
        */
  -    private Node getNode(String catalogueId, String key) {
  +    private ParamSaxBuffer getMessage(String catalogueID, String key) {
           try {
               Bundle catalogue = defaultCatalogue;
  -            if (catalogueId != null) {
  -                CatalogueInfo catalogueInfo = (CatalogueInfo)catalogues.get(catalogueId);
  +            if (catalogueID != null) {
  +                CatalogueInfo catalogueInfo = (CatalogueInfo)catalogues.get(catalogueID);
                   if (catalogueInfo == null) {
  -                    if (getLogger().isDebugEnabled()) {
  -                        getLogger().debug("Catalogue not found: " + catalogueId +
  -                                          ", could not translate key " + key);
  +                    if (getLogger().isWarnEnabled()) {
  +                        getLogger().warn("Catalogue not found: " + catalogueID +
  +                                         ", will not translate key " + key);
                       }
                       return null;
                   }
  @@ -2039,8 +2047,7 @@
                       return null;
                   }
               }
  -            return (Node)catalogue.getObject(I18N_CATALOGUE_PREFIX +
  -                                             "[@key='" + key + "']");
  +            return (ParamSaxBuffer)catalogue.getObject(key);
           } catch (MissingResourceException e)  {
               getLogger().debug("Untranslated key: '" + key + "'");
               return null;
  @@ -2054,55 +2061,30 @@
        *
        * @param catalogueId if not null, this catalogue will be used instead of the default one.
        */
  -    private String getString(String key, String catalogueId, String defaultValue) {
  -        final Node res = getNode(catalogueId, key);
  +    private String getString(String catalogueID, String key, String defaultValue) {
  +        final SaxBuffer res = getMessage(catalogueID, key);
           if (res == null) {
               return defaultValue;
           }
  -        return getTextValue(res);
  -    }
  -
  -    // Helper method to get the text value of the node.
  -    private static String getTextValue(Node node) {
  -        if (node == null) {
  -            return null;
  -        } 
  -
  -        NodeList list = node.getChildNodes();
  -        int listsize = list.getLength();
  -        Node item = null;
  -        StringBuffer itemValue = new StringBuffer();
  -        for (int i = 0; i < listsize; i++) {
  -            item = list.item(i);
  -            // only TEXT and CDATA nodes are processed
  -            if (item.getNodeType() == Node.TEXT_NODE
  -                    || item.getNodeType() == Node.CDATA_SECTION_NODE) {
  -
  -                itemValue.append(item.getNodeValue());
  -            }
  -        }
  -
  -        return itemValue.toString();
  +        return res.toString();
       }
   
       /**
  -     * Helper method to retrieve a message from the dictionary.
  +     * Helper method to retrieve a message from the current dictionary.
        * A default value is returned if message is not found.
  +     *
  +     * @return SaxBuffer containing message, or defaultValue if not found.
        */ 
  -    private MirrorRecorder getMirrorRecorder(String key, MirrorRecorder defaultValue) {
  -        Node value = getNode(currentCatalogueId, key);
  +    private ParamSaxBuffer getMessage(String key, ParamSaxBuffer defaultValue) {
  +        SaxBuffer value = getMessage(currentCatalogueId, key);
           if (value == null) {
               return defaultValue;
           }
   
  -        return new MirrorRecorder(value);
  +        return new ParamSaxBuffer(value);
       }
   
       public void recycle() {
  -        // restore untranslated-text if necessary
  -        if (globalUntranslated != null) {
  -            untranslated = globalUntranslated;
  -        }
           untranslatedRecorder = null;
   
           // clean up default catalogue
  @@ -2110,9 +2092,9 @@
           defaultCatalogue = null;
   
           // clean up the other catalogues
  -        Iterator catalogueIt = catalogues.values().iterator();
  -        while (catalogueIt.hasNext()) {
  -            CatalogueInfo catalogueInfo = (CatalogueInfo)catalogueIt.next();
  +        Iterator i = catalogues.values().iterator();
  +        while (i.hasNext()) {
  +            CatalogueInfo catalogueInfo = (CatalogueInfo)i.next();
               catalogueInfo.releaseCatalog();
           }
   
  @@ -2122,10 +2104,11 @@
       }
   
       public void dispose() {
  -        if (this.manager != null) {
  -            this.manager.release(this.factory);
  +        if (manager != null) {
  +            manager.release(this.factory);
           }
           factory = null;
  +        manager = null;
       }
   
       /**
  
  
  
  1.3       +11 -15    cocoon-2.1/src/java/org/apache/cocoon/i18n/AbstractBundleFactory.java
  
  Index: AbstractBundleFactory.java
  ===================================================================
  RCS file: /home/cvs/cocoon-2.1/src/java/org/apache/cocoon/i18n/AbstractBundleFactory.java,v
  retrieving revision 1.2
  retrieving revision 1.3
  diff -u -r1.2 -r1.3
  --- AbstractBundleFactory.java	16 Mar 2003 17:49:15 -0000	1.2
  +++ AbstractBundleFactory.java	10 Dec 2003 15:37:37 -0000	1.3
  @@ -67,16 +67,17 @@
    * @version CVS $Id$
    */
   public abstract class AbstractBundleFactory 
  -  extends AbstractLogEnabled
  -  implements BundleFactory, Composable, Configurable, Disposable, LogEnabled, ThreadSafe {
  +    extends AbstractLogEnabled
  +    implements BundleFactory, Composable, Configurable, Disposable, LogEnabled, ThreadSafe {
   
       /** Should we load bundles to cache on startup or not. */
  -    protected boolean cacheAtStartup = false;
  +    protected boolean cacheAtStartup;
   
       /** Root directory to all bundle names */
       protected String directory;
   
  -    protected ComponentManager manager = null;
  +    protected ComponentManager manager;
  +
   
       public void compose(ComponentManager manager) {
           this.manager = manager;
  @@ -88,7 +89,7 @@
        * @param configuration the configuration.
        */
       public void configure(Configuration configuration)
  -        throws ConfigurationException {
  +    throws ConfigurationException {
   
           this.cacheAtStartup =
               configuration.getChild(
  @@ -99,20 +100,15 @@
                   ConfigurationKeys.ROOT_DIRECTORY, true).getValue();
           } catch (ConfigurationException e) {
               if (getLogger().isWarnEnabled()) {
  -                getLogger().warn(
  -                    "Root directory not provided in configuration, "
  -                    + "using default (root)"
  -                );
  +                getLogger().warn("Root directory not provided in configuration, " +
  +                                 "using default (\"\")");
               }
               this.directory = "";
           }
   
           if (getLogger().isDebugEnabled()) {
  -            getLogger().debug(
  -                "Bundle Factory implementation configured with: cacheAtStartup = "
  -                + cacheAtStartup + ", directory = '" + directory + "'"
  -            );
  +            getLogger().debug("Bundle Factory implementation configured with: cacheAtStartup = " +
  +                              cacheAtStartup + ", directory = '" + directory + "'");
           }
       }
  -
   }
  
  
  
  1.4       +13 -7     cocoon-2.1/src/java/org/apache/cocoon/i18n/BundleFactory.java
  
  Index: BundleFactory.java
  ===================================================================
  RCS file: /home/cvs/cocoon-2.1/src/java/org/apache/cocoon/i18n/BundleFactory.java,v
  retrieving revision 1.3
  retrieving revision 1.4
  diff -u -r1.3 -r1.4
  --- BundleFactory.java	27 Nov 2003 02:55:28 -0000	1.3
  +++ BundleFactory.java	10 Dec 2003 15:37:37 -0000	1.4
  @@ -57,17 +57,23 @@
   import org.apache.avalon.framework.component.ComponentSelector;
   
   /**
  - * Bundle Factory realizations are responsible for loading and providing
  + * Bundle Factory implementations are responsible for loading and providing
    * particular types of resource bundles, implementors of Bundle interface.
    *
    * @author <a href="mailto:kpiroumian@apache.org">Konstantin Piroumian</a>
  + * @author <a href="mailto:vgritsenko@apache.org">Vadim Gritsenko</a>
    * @version CVS $Id$
    */
   public interface BundleFactory extends ComponentSelector {
   
  +    /**
  +     * Bundle factory ROLE name
  +     */
       String ROLE = BundleFactory.class.getName();
   
  -    /** Constants for configuration keys */
  +    /**
  +     * Constants for configuration keys
  +     */
       static class ConfigurationKeys {
           public static final String CACHE_AT_STARTUP = "cache-at-startup";
           public static final String ROOT_DIRECTORY = "catalogue-location";
  @@ -83,7 +89,7 @@
        * @return        the bundle
        * @exception     ComponentException if a bundle is not found
        */
  -    Component select(String base, String bundleName, String locale) throws ComponentException;
  +    Bundle select(String base, String bundleName, String locale) throws ComponentException;
   
       /**
        * Select a bundle based on the catalogue base location, bundle name,
  @@ -95,7 +101,7 @@
        * @return        the bundle
        * @exception     ComponentException if a bundle is not found
        */
  -    Component select(String base, String bundleName, Locale locale) throws ComponentException;
  +    Bundle select(String base, String bundleName, Locale locale) throws ComponentException;
   
       /**
        * Select a bundle based on the bundle name and the locale name from
  @@ -106,7 +112,7 @@
        * @return        the bundle
        * @exception     ComponentException if a bundle is not found
        */
  -    Component select(String bundleName, String locale) throws ComponentException;
  +    Bundle select(String bundleName, String locale) throws ComponentException;
   
       /**
        * Select a bundle based on the bundle name and the locale from
  @@ -117,5 +123,5 @@
        * @return        the bundle
        * @exception     ComponentException if a bundle is not found
        */
  -    Component select(String bundleName, Locale locale) throws ComponentException;
  +    Bundle select(String bundleName, Locale locale) throws ComponentException;
   }
  
  
  
  1.2       +5 -5      cocoon-2.1/src/java/org/apache/cocoon/i18n/I18nUtils.java
  
  Index: I18nUtils.java
  ===================================================================
  RCS file: /home/cvs/cocoon-2.1/src/java/org/apache/cocoon/i18n/I18nUtils.java,v
  retrieving revision 1.1
  retrieving revision 1.2
  diff -u -r1.1 -r1.2
  --- I18nUtils.java	9 Mar 2003 00:09:32 -0000	1.1
  +++ I18nUtils.java	10 Dec 2003 15:37:37 -0000	1.2
  @@ -65,6 +65,10 @@
       // Locale string delimiter
       private static final String LOCALE_DELIMITER = "_-@.";
   
  +    private I18nUtils() {
  +        // Disable instantiation
  +    }
  +
       /**
        * Parses given locale string to Locale object. If the string is null
        * then the given locale is returned.
  @@ -99,9 +103,5 @@
        */
       public static Locale parseLocale(String localeString) {
           return parseLocale(localeString, Locale.getDefault());
  -    }
  -
  -    // Hide constructor to prevent class instance creation
  -    private I18nUtils() {
       }
   }
  
  
  
  1.4       +248 -239  cocoon-2.1/src/java/org/apache/cocoon/i18n/XMLResourceBundle.java
  
  Index: XMLResourceBundle.java
  ===================================================================
  RCS file: /home/cvs/cocoon-2.1/src/java/org/apache/cocoon/i18n/XMLResourceBundle.java,v
  retrieving revision 1.3
  retrieving revision 1.4
  diff -u -r1.3 -r1.4
  --- XMLResourceBundle.java	27 Nov 2003 02:51:40 -0000	1.3
  +++ XMLResourceBundle.java	10 Dec 2003 15:37:37 -0000	1.4
  @@ -50,169 +50,298 @@
   package org.apache.cocoon.i18n;
   
   import java.io.IOException;
  -import java.util.Enumeration;
  +import java.net.MalformedURLException;
  +import java.util.Collections;
   import java.util.HashMap;
  -import java.util.Hashtable;
   import java.util.Locale;
   import java.util.Map;
  -import java.util.MissingResourceException;
  -import java.util.ResourceBundle;
  -
  -import javax.xml.parsers.DocumentBuilder;
  -import javax.xml.parsers.DocumentBuilderFactory;
  -import javax.xml.parsers.ParserConfigurationException;
  -
  -import org.apache.avalon.framework.component.ComponentException;
  -import org.apache.avalon.framework.component.ComponentManager;
  -import org.apache.avalon.framework.component.Component;
  -import org.apache.avalon.framework.logger.Logger;
  +import java.util.Set;
   
  +import org.apache.avalon.framework.logger.AbstractLogEnabled;
  +import org.apache.avalon.framework.service.ServiceException;
  +import org.apache.avalon.framework.service.ServiceManager;
  +import org.apache.avalon.framework.service.Serviceable;
  +import org.apache.cocoon.ProcessingException;
  +import org.apache.cocoon.components.source.SourceUtil;
  +import org.apache.cocoon.xml.ParamSaxBuffer;
   import org.apache.excalibur.source.Source;
  +import org.apache.excalibur.source.SourceNotFoundException;
   import org.apache.excalibur.source.SourceResolver;
  -import org.apache.excalibur.xml.xpath.XPathProcessor;
  -
  -import org.w3c.dom.Document;
  -import org.w3c.dom.NamedNodeMap;
  -import org.w3c.dom.Node;
  -import org.w3c.dom.NodeList;
  -
  -import org.xml.sax.InputSource;
  +import org.apache.excalibur.source.SourceValidity;
  +import org.xml.sax.Attributes;
  +import org.xml.sax.ContentHandler;
  +import org.xml.sax.Locator;
   import org.xml.sax.SAXException;
   
   /**
  - * Implementation of <code>Bundle</code> interface for XML resources. Represents a single XML message bundle.
  - *
  - * @author <a href="mailto:mengelhart@earthtrip.com">Mike Engelhart</a>
  - * @author <a href="mailto:neeme@one.lv">Neeme Praks</a>
  - * @author <a href="mailto:oleg@one.lv">Oleg Podolsky</a>
  - * @author <a href="mailto:mattam@netcourrier.com">Matthieu Sozeau</a>
  - * @author <a href="mailto:kpiroumian@apache.org">Konstantin Piroumian</a>
  + * Implementation of <code>Bundle</code> interface for XML resources. Represents a
  + * single XML message bundle.
  + * 
  + * XML format for this resource bundle implementation is the following:
  + * <pre>
  + * &lt;catalogue xml:lang="en"&gt;
  + *   &lt;message key="key1"&gt;Message &lt;br/&gt; Value 1&lt;/message&gt;
  + *   &lt;message key="key2"&gt;Message &lt;br/&gt; Value 1&lt;/message&gt;
  + *   ...
  + * &lt;/catalogue&gt;
  + * </pre>
  + * 
  + * Value can be any well formed XML snippet and it will be cached by the key specified
  + * in the attrbute <code>key</code>. Objects returned by this {@link Bundle} implementation
  + * are instances of the {@link ParamSaxBuffer} class.
  + * 
  + * @author <a href="mailto:dev@cocoon.apache.org">Apache Cocoon Team</a>
    * @version CVS $Id$
    */
  -public class XMLResourceBundle extends ResourceBundle
  -                               implements Bundle {
  +public class XMLResourceBundle extends AbstractLogEnabled
  +                               implements Bundle, Serviceable {
  +
  +    /**
  +     * Namespace for the Bundle markup
  +     */
  +    public static final String NS = "http://apache.org/cocoon/i18n/2.0";
  +    
  +    /**
  +     * XML bundle root element name
  +     */
  +    public static final String EL_CATALOGUE = "catalogue";
  +    
  +    /**
  +     * XML bundle message element name
  +     */
  +    public static final String EL_MESSAGE = "message";
  +    
  +    /**
  +     * XML bundle message element's key attribute name 
  +     */
  +    public static final String AT_KEY = "key";
  +    
  +    
  +    /**
  +     * Bundle name
  +     */
  +    private String name;
  +
  +    /**
  +     * Bundle validity
  +     */
  +    private SourceValidity validity;
   
  -    /** DOM factory */
  -    protected static final DocumentBuilderFactory docfactory = DocumentBuilderFactory.newInstance();
  +    /**
  +     * Locale of the bundle
  +     */
  +    private Locale locale;
   
  -    /** Logger */
  -    protected Logger logger;
  +    /**
  +     * Parent of the current bundle
  +     */
  +    protected Bundle parent;
   
  -    /** Cache for storing string values for existing XPaths */
  -    private Hashtable cache = new Hashtable();
  +    /**
  +     * Objects stored in the bundle
  +     */
  +    protected HashMap values;
  +    
  +    /**
  +     * Service Manager
  +     */
  +    protected ServiceManager manager;
   
  -    /** Cache for storing non-existing XPaths */
  -    private Map cacheNotFound = new HashMap();
  +    /**
  +     * Processes XML bundle file and creates map of values
  +     */
  +    private class SAXContentHandler implements ContentHandler {
  +        private Map values;
  +        private int state;
  +        private String namespace;
  +        private ParamSaxBuffer buffer; 
  +        
  +        public SAXContentHandler(Map values) {
  +            this.values = values;
  +        }
  +        
  +        public void setDocumentLocator(Locator arg0) {
  +            // Ignore
  +        }
   
  -    /** Bundle name */
  -    private String name = "";
  +        public void startDocument() throws SAXException {
  +            // Ignore
  +        }
   
  -    /** DOM-tree containing the bundle content */
  -    private Document doc;
  +        public void endDocument() throws SAXException {
  +            // Ignore
  +        }
   
  -    /** Locale of the bundle */
  -    private Locale locale;
  +        public void processingInstruction(String arg0, String arg1) throws SAXException {
  +            // Ignore
  +        }
   
  -    /** Parent of the current bundle */
  -    protected XMLResourceBundle parent = null;
  +        public void skippedEntity(String arg0) throws SAXException {
  +            // Ignore
  +        }
   
  -    /** Component Manager */
  -    protected ComponentManager manager = null;
  +        public void startElement(String ns, String localName, String qName, Attributes atts) throws SAXException {
  +            switch (state) {
  +                case 0:
  +                    // <i18n:catalogue>
  +                    if (!"".equals(ns) && !NS.equals(ns)) {
  +                        throw new SAXException("Root element <" + EL_CATALOGUE +
  +                                               "> must be non-namespaced or in i18n namespace.");
  +                    }
  +                    if (!EL_CATALOGUE.equals(localName)) {
  +                        throw new SAXException("Root element must be <" + EL_CATALOGUE + ">.");
  +                    }
  +                    this.namespace = ns;
  +                    state ++;
  +                    break;
  +                case 1:
  +                    // <i18n:message>
  +                    if (!EL_MESSAGE.equals(localName)) {
  +                        throw new SAXException("<" + EL_CATALOGUE + "> must contain <" +
  +                                               EL_MESSAGE + "> elements only.");
  +                    }
  +                    if (!this.namespace.equals(ns)) {
  +                        throw new SAXException("<" + EL_MESSAGE + "> element must be in '" +
  +                                               this.namespace + "' namespace.");
  +                    }
  +                    String key =  atts.getValue(AT_KEY);
  +                    if (key == null) {
  +                        throw new SAXException("<" + EL_MESSAGE + "> must have '" +
  +                                               AT_KEY + "' attribute.");
  +                    }
  +                    buffer = new ParamSaxBuffer();
  +                    values.put(key, buffer);
  +                    state ++;
  +                    break;
  +                case 2:
  +                    buffer.startElement(ns, localName, qName, atts);
  +                    break;
  +                default:
  +                    throw new SAXException("Internal error: Invalid state");
  +            }
  +        }
   
  -    /** XPath Processor */
  -    private XPathProcessor processor = null;
  +        public void endElement(String ns, String localName, String qName) throws SAXException {
  +            switch (state) {
  +                case 0:
  +                    break;
  +                case 1:
  +                    // </i18n:catalogue>
  +                    state --;
  +                    break;
  +                case 2:
  +                    if (this.namespace.equals(ns) && EL_MESSAGE.equals(localName)) {
  +                        // </i18n:message>
  +                        this.buffer = null;
  +                        state --;
  +                    } else {
  +                        buffer.endElement(ns, localName, qName);
  +                    }
  +                    break;
  +                default:
  +                    throw new SAXException("Internal error: Invalid state");
  +            }
  +        }
  +
  +        public void startPrefixMapping(String prefix, String uri) throws SAXException {
  +            if (buffer != null) {
  +                buffer.startPrefixMapping(prefix, uri);
  +            }
  +        }
  +
  +        public void endPrefixMapping(String prefix) throws SAXException {
  +            if (buffer != null) {
  +                buffer.endPrefixMapping(prefix);
  +            }
  +        }
   
  +        public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
  +            if (buffer != null) {
  +                buffer.ignorableWhitespace(ch, start, length);
  +            }
  +        }
   
  +        public void characters(char[] ch, int start, int length) throws SAXException {
  +            if (buffer != null) {
  +                buffer.characters(ch, start, length);
  +            }
  +        }
  +    }
  +    
       /**
        * Compose this instance
        *
  -     * @param manager The <code>ComponentManager</code> instance
  +     * @param manager The <code>ServiceManager</code> instance
        * @throws ComponentException if XPath processor is not found
        */
  -    public void compose(ComponentManager manager) throws ComponentException {
  +    public void service(ServiceManager manager) throws ServiceException {
           this.manager = manager;
  -        this.processor = (XPathProcessor) this.manager.lookup(XPathProcessor.ROLE);
       }
   
       /**
        * Implements Disposable interface for this class.
        */
       public void dispose() {
  -        this.manager.release((Component) this.processor);
  -        this.processor = null;
  -    }
  -
  -    /**
  -     * Implements LogEnabled interface for this class.
  -     *
  -     * @param logger the logger object
  -     */
  -    public void enableLogging(final Logger logger) {
  -        this.logger = logger;
  +        this.manager = null;
       }
   
       /**
        * Initalize the bundle
        *
        * @param name name of the bundle
  -     * @param fileName name of the XML source file
  +     * @param sourceURL source URL of the XML bundle
        * @param locale locale
        * @param parent parent bundle of this bundle
  -     * @param cacheAtStartup cache all the keys when constructing?
        *
        * @throws IOException if an IO error occurs while reading the file
  -     * @throws ParserConfigurationException if no parser is configured
  -     * @throws SAXException if an error occurs while parsing the file
  +     * @throws ProcessingException if an error occurs while loading the bundle
  +     * @throws SAXException if an error occurs while loading the bundle
        */
  -    public void init(String name, String fileName, Locale locale, XMLResourceBundle parent, boolean cacheAtStartup)
  -    throws IOException, ParserConfigurationException, SAXException {
  -        if (logger.isDebugEnabled()) {
  -            logger.debug("Constructing XMLResourceBundle: " + name + ", locale: " + locale);
  +    public void init(String name, String sourceURL, Locale locale, Bundle parent)
  +    throws IOException, ProcessingException, SAXException {
  +        if (getLogger().isDebugEnabled()) {
  +            getLogger().debug("Loading XML bundle: " + name + ", locale: " + locale);
           }
   
           this.name = name;
  -        this.doc = loadResourceBundle(fileName);
           this.locale = locale;
           this.parent = parent;
  -
  -        if (cacheAtStartup) {
  -            Node root = doc.getDocumentElement();
  -            cacheAll(root, "/" + root.getNodeName());
  -        }
  +        this.values = new HashMap();
  +        load(sourceURL);
       }
   
       /**
  -     * Load the DOM tree, based on the file name.
  -     *
  -     * @param fileName name of the XML source file
  +     * Load the XML bundle, based on the source URL.
        *
  +     * @param sourceURL source URL of the XML bundle
        * @return the DOM tree
        *
        * @exception IOException if an IO error occurs while reading the file
        * @exception ParserConfigurationException if no parser is configured
        * @exception SAXException if an error occurs while parsing the file
        */
  -    protected synchronized Document loadResourceBundle(String fileName)
  -    throws IOException, ParserConfigurationException, SAXException {
  +    protected void load(String sourceURL)
  +    throws IOException, ProcessingException, SAXException {
   
  -        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
  -        DocumentBuilder builder = factory.newDocumentBuilder();
           Source source = null;
           SourceResolver resolver = null;
  -
           try {
               resolver = (SourceResolver)manager.lookup(SourceResolver.ROLE);
  -            source = resolver.resolveURI(fileName);
  -            return builder.parse(new InputSource(source.getInputStream()));
  -        } catch (Exception e) {
  -            logger.warn("XMLResourceBundle: Non excalibur-source " + fileName, e);
  +            source = resolver.resolveURI(sourceURL);
  +            HashMap values = new HashMap();
  +            SourceUtil.toSAX(source, new SAXContentHandler(values));
  +            this.validity = source.getValidity();
  +            this.values = values;
  +        } catch (ServiceException e) {
  +            throw new ProcessingException("Can't lookup source resolver", e);
  +        } catch (MalformedURLException e) {
  +            throw new SourceNotFoundException("Invalid resource URL: " + sourceURL, e);
           } finally {
  -            resolver.release(source);
  +            if (source != null) {
  +                resolver.release(source);
  +            }
               manager.release(resolver);
           }
  -
  -        // Fallback try
  -        return builder.parse(fileName);
       }
   
       /**
  @@ -225,12 +354,12 @@
       }
   
       /**
  -     * Gets the source DOM tree of the bundle.
  +     * Gets the validity of the bundle.
        *
  -     * @return the DOM tree
  +     * @return the validity
        */
  -    public Document getDocument() {
  -        return this.doc;
  +    public SourceValidity getValidity() {
  +        return this.validity;
       }
   
       /**
  @@ -243,173 +372,53 @@
       }
   
       /**
  -     * Does the &quot;key-not-found-cache&quot; contain such key?
  -     *
  -     * @param key the key to the value to be returned
  -     *
  -     * @return true if contains, false otherwise
  -     */
  -    private boolean cacheNotFoundContains(String key) {
  -        return cacheNotFound.containsKey(key);
  -    }
  -
  -    /**
  -     * Cache the key and value in &quot;key-cache&quot;.
  -     *
  -     * @param key the key
  -     * @param value the value
  -     */
  -    private void cacheKey(String key, Node value) {
  -        cache.put(key, value);
  -    }
  -
  -    /**
  -     * Cache the key in &quot;key-not-found-cache&quot;.
  -     *
  -     * @param key the key
  -     */
  -    private void cacheNotFoundKey(String key) {
  -        cacheNotFound.put(key, "");
  -    }
  -
  -    /**
  -     * Gets the value by the key from the &quot;key-cache&quot;.
  -     *
  -     * @param key the key
  -     * @return the value
  -     */
  -    private Node getFromCache(String key) {
  -        Object value = cache.get(key);
  -        return (Node)value;
  -    }
  -
  -    /**
  -     * Steps through the bundle tree and stores all text element values in bundle's cache. Also stores attributes for
  -     * all element nodes.
  -     *
  -     * @param parent parent node, must be an element
  -     * @param pathToParent XPath to the parent node
  -     */
  -    private void cacheAll(Node parent, String pathToParent) {
  -        if (logger.isDebugEnabled()) {
  -            logger.debug("Caching all messages");
  -        }
  -
  -        NodeList children = parent.getChildNodes();
  -        int childnum = children.getLength();
  -
  -        for (int i = 0; i < childnum; i++) {
  -            Node child = children.item(i);
  -
  -            if (child.getNodeType() == Node.ELEMENT_NODE) {
  -                StringBuffer pathToChild = new StringBuffer(pathToParent).append('/').append(child.getNodeName());
  -
  -                NamedNodeMap attrs = child.getAttributes();
  -                if (attrs != null) {
  -                    int attrnum = attrs.getLength();
  -
  -                    for (int j = 0; j < attrnum; j++) {
  -                        Node temp = attrs.item(j);
  -
  -                        if (!temp.getNodeName().equalsIgnoreCase("xml:lang")) {
  -                            pathToChild.append("[@").append(temp.getNodeName()).append("='").append(temp.getNodeValue())
  -                                       .append("']");
  -                        }
  -                    }
  -                }
  -
  -                cacheKey(pathToChild.toString(), child);
  -            }
  -
  -            if (logger.isDebugEnabled()) {
  -                logger.debug("What we've cached: " + child.toString());
  -            }
  -        }
  -    }
  -
  -    /**
  -     * Get value by key.
  +     * Get Object value by key.
        *
        * @param key the key
        * @return the value
        */
  -    private Object _getObject(String key) {
  +    public Object getObject(String key) {
           if (key == null) {
               return null;
           }
   
  -        Node value = getFromCache(key);
  -        if (value == null && !cacheNotFoundContains(key)) {
  -            if (doc != null) {
  -                value = (Node)_getObject(this.doc.getDocumentElement(), key);
  -            }
  -
  -            if (value == null) {
  -                if (this.parent != null) {
  -                    value = (Node)this.parent._getObject(key);
  -                }
  -            }
  -
  -            if (value != null) {
  -                cacheKey(key, value);
  -            } else {
  -                cacheNotFoundKey(key);
  -            }
  +        Object value = values.get(key);
  +        if (value == null && this.parent != null) {
  +            value = this.parent.getObject(key);
           }
   
           return value;
       }
   
       /**
  -     * Get value by key from a concrete node.
  +     * Get String value by key.
        *
  -     * @param node the node
        * @param key the key
  -     *
        * @return the value
        */
  -    private Object _getObject(Node node, String key) {
  -        return _getNode(node, key);
  -    }
  +    public String getString(String key) {
  +        if (key == null) {
  +            return null;
  +        }
   
  -    /**
  -     * Get the node with the supplied XPath key, starting from concrete root node.
  -     *
  -     * @param rootNode the root node
  -     * @param key the key
  -     *
  -     * @return the node
  -     */
  -    private Node _getNode(Node rootNode, String key) {
  -        try {
  -            return this.processor.selectSingleNode(rootNode, key);
  -        } catch (Exception e) {
  -            logger.error("Error while locating resource with key: " + key, e);
  +        Object value = values.get(key);
  +        if (value != null) {
  +            return value.toString();
  +        }
  +        
  +        if(this.parent != null) {
  +            return this.parent.getString(key);
           }
   
           return null;
       }
   
       /**
  -     * Return an Object by key. Implementation of the ResourceBundle abstract method.
  -     *
  -     * @param key the key
  -     *
  -     * @return the object
  -     *
  -     * @throws MissingResourceException on error
  -     */
  -    protected Object handleGetObject(String key)
  -    throws MissingResourceException {
  -        return _getObject(key);
  -    }
  -
  -    /**
  -     * Return an enumeration of the keys. Implementation of the ResourceBundle abstract method.
  +     * Return a set of keys.
        *
        * @return the enumeration of keys
        */
  -    public Enumeration getKeys() {
  -        return cache.keys();
  +    public Set keySet() {
  +        return Collections.unmodifiableSet(values.keySet());
       }
   }
  
  
  
  1.7       +43 -39    cocoon-2.1/src/java/org/apache/cocoon/i18n/XMLResourceBundleFactory.java
  
  Index: XMLResourceBundleFactory.java
  ===================================================================
  RCS file: /home/cvs/cocoon-2.1/src/java/org/apache/cocoon/i18n/XMLResourceBundleFactory.java,v
  retrieving revision 1.6
  retrieving revision 1.7
  diff -u -r1.6 -r1.7
  --- XMLResourceBundleFactory.java	27 Nov 2003 18:40:03 -0000	1.6
  +++ XMLResourceBundleFactory.java	10 Dec 2003 15:37:37 -0000	1.7
  @@ -50,7 +50,6 @@
   */
   package org.apache.cocoon.i18n;
   
  -import java.io.FileNotFoundException;
   import java.util.HashMap;
   import java.util.Iterator;
   import java.util.Locale;
  @@ -59,18 +58,16 @@
   import org.apache.avalon.framework.activity.Disposable;
   import org.apache.avalon.framework.component.Component;
   import org.apache.avalon.framework.component.ComponentException;
  -import org.apache.avalon.framework.component.ComponentManager;
  -import org.apache.avalon.framework.component.Composable;
   import org.apache.avalon.framework.component.DefaultComponentSelector;
   import org.apache.avalon.framework.configuration.Configurable;
   import org.apache.avalon.framework.configuration.Configuration;
   import org.apache.avalon.framework.configuration.ConfigurationException;
   import org.apache.avalon.framework.logger.LogEnabled;
   import org.apache.avalon.framework.logger.Logger;
  +import org.apache.avalon.framework.service.ServiceManager;
  +import org.apache.avalon.framework.service.Serviceable;
   import org.apache.avalon.framework.thread.ThreadSafe;
  -
   import org.apache.excalibur.source.SourceNotFoundException;
  -
   import org.xml.sax.SAXParseException;
   
   /**
  @@ -84,25 +81,37 @@
    * @version CVS $Id$
    */
   public class XMLResourceBundleFactory extends DefaultComponentSelector
  -        implements BundleFactory, Composable, Configurable, Disposable, ThreadSafe, LogEnabled {
  +        implements BundleFactory, Serviceable, Configurable, Disposable, ThreadSafe, LogEnabled {
   
  -    /** Should we load bundles to cache on startup or not? */
  -    protected boolean cacheAtStartup = false;
  +    /**
  +     * Should we load bundles to cache on startup or not?
  +     */
  +    protected boolean cacheAtStartup;
   
  -    /** Root directory to all bundle names */
  +    /**
  +     * Root directory to all bundle names
  +     */
       protected String directory;
   
  -    /** Cache for the names of the bundles that were not found */
  -    protected Map cacheNotFound = new HashMap();
  +    /**
  +     * Cache for the names of the bundles that were not found
  +     */
  +    protected final Map cacheNotFound = new HashMap();
   
  -    /** The logger */
  +    /**
  +     * The logger
  +     */
       private Logger logger;
   
  -    /** Component Manager */
  -    protected ComponentManager manager = null;
  +    /**
  +     * Service Manager
  +     */
  +    protected ServiceManager manager = null;
   
   
  -    /** Default constructor. */
  +    /**
  +     * Default constructor
  +     */
       public XMLResourceBundleFactory() {
       }
   
  @@ -117,7 +126,7 @@
           return this.logger;
       }
   
  -    public void compose(ComponentManager manager) {
  +    public void service(ServiceManager manager) {
           this.manager = manager;
       }
   
  @@ -156,7 +165,6 @@
           }
       }
   
  -
       /**
        * Returns the root directory to all bundles.
        *
  @@ -175,7 +183,6 @@
           return cacheAtStartup;
       }
   
  -
       /**
        * Select a bundle based on the bundle name and the locale name.
        *
  @@ -184,7 +191,7 @@
        * @return        the bundle
        * @exception     ComponentException if a bundle is not found
        */
  -    public Component select(String name, String locale) throws ComponentException {
  +    public Bundle select(String name, String locale) throws ComponentException {
           return select(getDirectory(), name, locale);
       }
   
  @@ -196,7 +203,7 @@
        * @return        the bundle
        * @exception     ComponentException if a bundle is not found
        */
  -    public Component select(String name, Locale locale) throws ComponentException {
  +    public Bundle select(String name, Locale locale) throws ComponentException {
           return select(getDirectory(), name, locale);
       }
   
  @@ -210,7 +217,7 @@
        * @return        the bundle
        * @exception     ComponentException if a bundle is not found
        */
  -    public Component select(String directory, String name, String localeName) throws ComponentException {
  +    public Bundle select(String directory, String name, String localeName) throws ComponentException {
           return select(directory, name, new Locale(localeName, localeName));
       }
   
  @@ -224,8 +231,8 @@
        * @return        the bundle
        * @exception     ComponentException if a bundle is not found
        */
  -    public Component select(String directory, String name, Locale locale) throws ComponentException {
  -        Component bundle = _select(directory, name, locale, this.cacheAtStartup);
  +    public Bundle select(String directory, String name, Locale locale) throws ComponentException {
  +        Bundle bundle = _select(directory, name, locale);
           if (bundle == null) {
               throw new ComponentException(name, "Unable to locate resource: " + name);
           }
  @@ -241,7 +248,7 @@
        * @return                  the bundle
        */
       protected Component selectParent(String name, Locale locale) {
  -        return _select(getDirectory(), name, getParentLocale(locale), this.cacheAtStartup);
  +        return _select(getDirectory(), name, getParentLocale(locale));
       }
   
       /**
  @@ -253,14 +260,14 @@
        * @param cacheAtStartup    cache all the keys when constructing?
        * @return                  the bundle
        */
  -    private Component _select(String base, String name, Locale locale, boolean cacheAtStartup) {
  +    private Bundle _select(String base, String name, Locale locale) {
           String fileName = getFileName(base, name, locale);
           XMLResourceBundle bundle = (XMLResourceBundle)selectCached(fileName);
           if (bundle == null && !isNotFoundBundle(fileName)) {
               synchronized (this) {
                   bundle = (XMLResourceBundle)selectCached(fileName);
                   if (bundle == null && !isNotFoundBundle(fileName)) {
  -                    bundle = _loadBundle(name, fileName, locale, cacheAtStartup);
  +                    bundle = _loadBundle(name, fileName, locale);
   
                       while (bundle == null && locale != null && !locale.getLanguage().equals("")) {
                           if (getLogger().isDebugEnabled()) {
  @@ -268,13 +275,14 @@
                           }
                           locale = getParentLocale(locale);
                           String parentFileName = getFileName(base, name, locale);
  -                        bundle = _loadBundle(name, parentFileName, locale, cacheAtStartup);
  +                        bundle = _loadBundle(name, parentFileName, locale);
                       }
                       
                       updateCache(fileName, bundle);
                   }
               }
           }
  +        
           return bundle;
       }
   
  @@ -287,8 +295,7 @@
        * @param cacheAtStartup    cache all the keys when constructing?
        * @return                  the bundle, null if loading failed
        */
  -    private XMLResourceBundle _loadBundle(String name, String fileName,
  -                                          Locale locale, boolean cacheAtStartup) {
  +    private XMLResourceBundle _loadBundle(String name, String fileName, Locale locale) {
           if (getLogger().isDebugEnabled()) {
               getLogger().debug("Loading bundle: " + name + ", locale: " + locale +
                                 ", uri: " + fileName);
  @@ -301,18 +308,15 @@
                   parentBundle = (XMLResourceBundle)selectParent(name, locale);
               }
               bundle = new XMLResourceBundle();
  -            bundle.enableLogging(logger);
  -            bundle.compose(this.manager);
  -            bundle.init(name, fileName, locale, parentBundle, cacheAtStartup);
  +            bundle.enableLogging(this.logger);
  +            bundle.service(this.manager);
  +            bundle.init(name, fileName, locale, parentBundle);
               return bundle;
  -        } catch (FileNotFoundException fe) {
  -            getLogger().info("Resource not found: " + name + ", locale: " + locale +
  -                             ", bundleName: " + fileName + ". Exception: " + fe.getMessage());
           } catch (SourceNotFoundException e) {
               getLogger().info("Resource not found: " + name + ", locale: " + locale +
                                ", bundleName: " + fileName + ". Exception: " + e.getMessage());
  -        } catch (SAXParseException se) {
  -            getLogger().error("Incorrect resource format", se);
  +        } catch (SAXParseException e) {
  +            getLogger().error("Incorrect resource format", e);
           } catch (Exception e) {
               getLogger().error("Resource loading failed", e);
           }
  @@ -350,7 +354,7 @@
        * Maps a bundle name and locale to a full path in the filesystem.
        * If you need a different mapping, then just override this method.
        *
  -     * @param locale               the locale
  +     * @param locale            the locale
        * @return                  the parent locale
        */
       protected String getFileName(String base, String name, Locale locale) {
  
  
  
  1.2       +13 -12    cocoon-2.1/src/java/org/apache/cocoon/i18n/package.html
  
  Index: package.html
  ===================================================================
  RCS file: /home/cvs/cocoon-2.1/src/java/org/apache/cocoon/i18n/package.html,v
  retrieving revision 1.1
  retrieving revision 1.2
  diff -u -r1.1 -r1.2
  --- package.html	9 Mar 2003 00:09:32 -0000	1.1
  +++ package.html	10 Dec 2003 15:37:38 -0000	1.2
  @@ -1,14 +1,15 @@
   <html>
  -<head>
  - <title>Internationalization</title>
  -</head>
  -<body>
  - <h1>Internationalization support.</h1>
  - <p>
  - Internationalization bundle resource handling interfaces and default implementation for XML message catalogue. 
  - </p>
  - <p>
  - Other helper classes for i18n and localization.
  - </p>
  -</body>
  +  <head>
  +    <title>Internationalization</title>
  +  </head>
  +  <body>
  +    <h1>Internationalization support.</h1>
  +    <p>
  +      Internationalization bundle resource handling interfaces and default
  +      implementation for XML message catalogue. 
  +    </p>
  +    <p>
  +      Other helper classes for i18n and localization.
  +    </p>
  +  </body>
   </html>