Coverage Report - org.apache.commons.resources.impl.CollectionResourcesBase
 
Classes in this File Line Coverage Branch Coverage Complexity
CollectionResourcesBase
0%
0/101
0%
0/54
3.455
 
 1  
 /*
 2  
  * $Id: CollectionResourcesBase.java 354330 2005-12-06 06:05:19Z niallp $
 3  
  * $Revision: 354330 $
 4  
  * $Date: 2005-12-06 06:05:19 +0000 (Tue, 06 Dec 2005) $
 5  
  *
 6  
  * ====================================================================
 7  
  *
 8  
  *  Copyright 2003-2005 The Apache Software Foundation
 9  
  *
 10  
  *  Licensed under the Apache License, Version 2.0 (the "License");
 11  
  *  you may not use this file except in compliance with the License.
 12  
  *  You may obtain a copy of the License at
 13  
  *
 14  
  *      http://www.apache.org/licenses/LICENSE-2.0
 15  
  *
 16  
  *  Unless required by applicable law or agreed to in writing, software
 17  
  *  distributed under the License is distributed on an "AS IS" BASIS,
 18  
  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 19  
  *  See the License for the specific language governing permissions and
 20  
  *  limitations under the License.
 21  
  *
 22  
  */
 23  
 
 24  
 package org.apache.commons.resources.impl;
 25  
 
 26  
 import java.util.ArrayList;
 27  
 import java.util.HashMap;
 28  
 import java.util.HashSet;
 29  
 import java.util.Iterator;
 30  
 import java.util.List;
 31  
 import java.util.Locale;
 32  
 import java.util.Map;
 33  
 import java.util.Set;
 34  
 
 35  
 import org.apache.commons.logging.Log;
 36  
 import org.apache.commons.logging.LogFactory;
 37  
 import org.apache.commons.resources.ResourcesException;
 38  
 import org.apache.commons.resources.ResourcesKeyException;
 39  
 
 40  
 /**
 41  
  * <p>Abstract base classes for 
 42  
  * {@link org.apache.commons.resources.Resources} implementations that
 43  
  * store their name-value mappings for each supported <code>Locale</code>
 44  
  * in a URL-accessible resource file with a common base URL.  Subclasses
 45  
  * need only override <code>loadLocale()</code> to manage the details of
 46  
  * loading the name-value mappings for a particular Locale.</p>
 47  
  */
 48  
 public abstract class CollectionResourcesBase extends ResourcesBase {
 49  
 
 50  
     /**
 51  
      * <p>The logging instance for this class.</p>
 52  
      */
 53  0
     private transient Log log =
 54  0
         LogFactory.getLog(CollectionResourcesBase.class);
 55  
 
 56  
     /**
 57  
      * <p>Create a new {@link org.apache.commons.resources.Resources} instance with the specified
 58  
      * logical name and base URL.</p>
 59  
      *
 60  
      * @param name Logical name of the new instance
 61  
      * @param base Base URL of the resource files that contain the per-Locale
 62  
      *  name-value mappings for this {@link org.apache.commons.resources.Resources} instance
 63  
      */
 64  
     public CollectionResourcesBase(String name, String base) {
 65  0
         super(name);
 66  0
         this.base = base;
 67  0
     }
 68  
 
 69  
 
 70  
     // ----------------------------------------------------- Instance Variables
 71  
 
 72  
 
 73  
     /**
 74  
      * <p>The base URL for the per-Locale resources files containing the
 75  
      * name-value mappings for this {@link org.apache.commons.resources.Resources} instance.</p>
 76  
      */
 77  0
     private String base = null;
 78  
 
 79  
 
 80  
     /**
 81  
      * <p>The default <code>Locale</code> to use when no <code>Locale</code>
 82  
      * is specified by the caller.</p>
 83  
      */
 84  0
     private Locale defaultLocale = Locale.getDefault();
 85  
 
 86  
 
 87  
     /**
 88  
      * <p>The previously calculated <code>Locale</code> lists returned
 89  
      * by <code>getLocaleList()</code>, keyed by <code>Locale</code>.</p>
 90  
      */
 91  0
     private Map lists = new HashMap();
 92  
 
 93  
 
 94  
     /**
 95  
      * <p>The previously calculated name-value mappings <code>Map</code>s
 96  
      * returned by <code>getLocaleMap()</code>, keyed by <code>Locale</code>.
 97  
      * </p>
 98  
      */
 99  0
     private Map maps = new HashMap();
 100  
 
 101  
 
 102  
     // ------------------------------------------------------------- Properties
 103  
 
 104  
 
 105  
     /**
 106  
      * Set the default locale.
 107  
      * @param defaultLocale The default Locale.
 108  
      */
 109  
     public void setDefaultLocale(Locale defaultLocale) {
 110  0
         this.defaultLocale = defaultLocale;
 111  0
     }
 112  
 
 113  
     /**
 114  
      * Return the default locale.
 115  
      * @return The default Locale.
 116  
      */
 117  
     public Locale getDefaultLocale() {
 118  0
         return defaultLocale;
 119  
     }
 120  
 
 121  
     /**
 122  
      * @see org.apache.commons.resources.impl.ResourcesBase#getKeys()
 123  
      */
 124  
     public Iterator getKeys() {
 125  
 
 126  0
         synchronized (maps) {
 127  
 
 128  0
             Set results = new HashSet();
 129  0
             Iterator locales = maps.keySet().iterator();
 130  0
             while (locales.hasNext()) {
 131  0
                 Locale locale = (Locale) locales.next();
 132  0
                 Map map = (Map) maps.get(locale);
 133  0
                 results.addAll(map.keySet());
 134  0
             }
 135  0
             return (results.iterator());
 136  
 
 137  0
         }
 138  
 
 139  
 
 140  
 
 141  
 
 142  
     }
 143  
 
 144  
 
 145  
     // ---------------------------------------------- Content Retrieval Methods
 146  
 
 147  
 
 148  
     /**
 149  
      * <p>Return the content for the specified <code>key</code> as an
 150  
      * Object, localized based on the specified <code>locale</code>.
 151  
      * </p>
 152  
      *
 153  
      * @param key Identifier for the requested content
 154  
      * @param locale Locale with which to localize retrieval,
 155  
      *  or <code>null</code> for the default Locale
 156  
      * @return The content for the specified key.
 157  
      *
 158  
      * @exception ResourcesException if an error occurs retrieving or
 159  
      *  returning the requested content
 160  
      * @exception ResourcesKeyException if the no value for the specified
 161  
      *  key was found, and <code>isReturnNull()</code> returns
 162  
      *  <code>false</code>
 163  
      */
 164  
     public Object getObject(String key, Locale locale) {
 165  
 
 166  0
         if (getLog().isTraceEnabled()) {
 167  0
             getLog().trace("Retrieving message for key '" + key + "' and locale '"
 168  
                       + locale + "'");
 169  
         }
 170  
 
 171  0
         if (locale == null) {
 172  0
             locale = defaultLocale;
 173  
         }
 174  
 
 175  
         // Prepare local variables we will need
 176  0
         List list = getLocaleList(locale);
 177  0
         int n = list.size();
 178  
 
 179  
         // Search through the Locale hierarchy for this resource key
 180  0
         for (int i = 0; i < n; i++) {
 181  0
             Map map = getLocaleMap((Locale) list.get(i));
 182  0
             if (map.containsKey(key)) {
 183  0
                 Object object  = map.get(key);
 184  0
                 if (getLog().isTraceEnabled()) {
 185  0
                     getLog().trace("Retrieved object for key '" + key + 
 186  
                                    "' and locale '" + locale +
 187  
                                    "' is '" + object + "'");
 188  
                 }
 189  0
                 return object;
 190  
             }
 191  
         }
 192  
 
 193  0
         if (getLog().isTraceEnabled()) {
 194  0
             getLog().trace("No message found for key '" + key + 
 195  
                            "' and locale '" + locale + "'");
 196  
         }
 197  
 
 198  
         // No value for this key was located in the entire hierarchy
 199  0
         if (isReturnNull()) {
 200  0
             return (null);
 201  
         } else {
 202  0
             throw new ResourcesKeyException(key);
 203  
         }
 204  
 
 205  
     }
 206  
 
 207  
 
 208  
     // ------------------------------------------------------ Lifecycle Methods
 209  
 
 210  
 
 211  
     /**
 212  
      * <p>This method must be called when the manager of this resource
 213  
      * decides that it's no longer needed.  After this method is called,
 214  
      * no further calls to any of the <code>getXxx()</code> methods are
 215  
      * allowed.</p>
 216  
      *
 217  
      * @exception ResourcesException if an error occurs during finalization
 218  
      */
 219  
     public void destroy() {
 220  
 
 221  0
         synchronized (lists) {
 222  0
             lists.clear();
 223  0
         }
 224  0
         synchronized (maps) {
 225  0
             maps.clear();
 226  0
         }
 227  
 
 228  0
     }
 229  
 
 230  
 
 231  
     // ------------------------------------------------------ Protected Methods
 232  
 
 233  
 
 234  
     /**
 235  
      * <p>Return a <code>List</code> of Locales that should be searched
 236  
      * when locating resources for the specified Locale.  The returned
 237  
      * list will start with the specified Locale itself, followed by Locales
 238  
      * that do not specify variant, country, or language modifiers.  For
 239  
      * example, if you pass in a Locale for the <code>en_US_POSIX</code>
 240  
      * combination, the returned list will have Locale instances for
 241  
      * the following country/language/variant combinations:</p>
 242  
      * <ul>
 243  
      * <li><code>en_US_POSIX</code></li>
 244  
      * <li><code>en_US</code></li>
 245  
      * <li><code>en</code></li>
 246  
      * <li>(zero-length country, language, and variant)</li>
 247  
      * </ul>
 248  
      *
 249  
      * <p>The search order calculated by this method makes it easy for
 250  
      * {@link org.apache.commons.resources.Resources} implementations to implement hierarchical search
 251  
      * strategies similar to that employed by the standard Java class
 252  
      * <code>java.util.ResourceBundle</code>.</p>
 253  
      *
 254  
      * @param locale Locale on which to base the list calculation
 255  
      * @return A List of locales.
 256  
      */
 257  
     protected List getLocaleList(Locale locale) {
 258  
 
 259  0
         synchronized (lists) {
 260  
 
 261  
             // Optimized lookup of any previously cached Map for this Locale
 262  0
             List list = (List) lists.get(locale);
 263  0
             if (list != null) {
 264  0
                 return (list);
 265  
             }
 266  
 
 267  
             // Calculate, cache, and return the list for this Locale
 268  0
             list = new ArrayList();
 269  0
             String language = locale.getLanguage();
 270  0
             int languageLength = language.length();
 271  0
             String country = locale.getCountry();
 272  0
             int countryLength = country.length();
 273  0
             String variant = locale.getVariant();
 274  0
             int variantLength = variant.length();
 275  
 
 276  0
             list.add(locale);
 277  0
             if (variantLength > 0) {
 278  0
                 list.add(new Locale(language, country, ""));
 279  
             }
 280  0
             if ((countryLength > 0) && (languageLength > 0)) {
 281  0
                 list.add(new Locale(language, "", ""));
 282  
             }
 283  0
             if ((languageLength > 0) || (countryLength > 0)) {
 284  0
                 list.add(new Locale("", "", ""));
 285  
             }
 286  0
             lists.put(locale, list);
 287  0
             return (list);
 288  
 
 289  0
         }
 290  
 
 291  
     }
 292  
 
 293  
 
 294  
     /**
 295  
      * <p>Return the <code>Map</code> to be used to resolve name-value
 296  
      * mappings for the specified <code>Locale</code>.  Caching is utilized
 297  
      * to ensure that a call to <code>getLocaleMap(base,locale)</code>
 298  
      * occurs only once per <code>Locale</code>.</p>
 299  
      *
 300  
      * @param locale Locale for which to return a name-value mappings Map
 301  
      * @return A name-value Map for the specified locale.
 302  
      */
 303  
     protected Map getLocaleMap(Locale locale) {
 304  
 
 305  0
         synchronized (maps) {
 306  
 
 307  
             // Optimized lookup of any previously cached Map for this Locale
 308  0
             Map map = (Map) maps.get(locale);
 309  0
             if (map != null) {
 310  0
                 return (map);
 311  
             }
 312  
 
 313  
             // Calculate, cache, and return the map for this Locale
 314  0
             map = getLocaleMap(base, locale);
 315  0
             maps.put(locale, map);
 316  0
             return (map);
 317  
 
 318  0
         }
 319  
 
 320  
     }
 321  
 
 322  
 
 323  
     /**
 324  
      * <p>Return a <code>Map</code> containing the name-value mappings for
 325  
      * the specified base URL and requested <code>Locale</code>, if there
 326  
      * are any.  If there are no defined mappings for the specified
 327  
      * <code>Locale</code>, return an empty <code>Map</code> instead.</p>
 328  
      *
 329  
      * <p>Concrete subclasses must override this method to perform the
 330  
      * appropriate lookup.  A typical implementation will construct an
 331  
      * absolute URL based on the specified base URL and <code>Locale</code>,
 332  
      * retrieve the specified resource file (if any), and parse it into
 333  
      * a <code>Map</code> structure.</p>
 334  
      *
 335  
      * <p>Caching of previously retrieved <code>Map</code>s (if any) should
 336  
      * be performed by callers of this method.  Therefore, this method should
 337  
      * always attempt to retrieve the specified resource and load it
 338  
      * appropriately.</p>
 339  
      *
 340  
      * @param baseUrl Base URL of the resource files for this 
 341  
      * {@link org.apache.commons.resources.Resources} instance
 342  
      * @param locale <code>Locale</code> for which name-value mappings
 343  
      *  are requested
 344  
      * @return A name-value Map for the specified URL and locale.
 345  
      */
 346  
     protected abstract Map getLocaleMap(String baseUrl, Locale locale);
 347  
 
 348  
 
 349  
     /**
 350  
      * <p>Return the <code>Locale</code>-specific suffix for the specified
 351  
      * <code>Locale</code>.  If the specified <code>Locale</code> has
 352  
      * zero-length language and country components, the returned suffix
 353  
      * will also have zero length.  Otherwise, it will contain an
 354  
      * underscore character followed by the non-zero-length language,
 355  
      * country, and variant properties (separated by underscore characters).
 356  
      *
 357  
      * @param locale <code>Locale</code> for which a suffix string
 358  
      *  is requested
 359  
      * @return The locale specific suffix.
 360  
      */
 361  
     protected String getLocaleSuffix(Locale locale) {
 362  
 
 363  0
         if (locale == null) {
 364  0
             locale = defaultLocale;
 365  
         }
 366  0
         String language = locale.getLanguage();
 367  0
         if (language == null) {
 368  0
             language = "";
 369  
         }
 370  0
         String country = locale.getCountry();
 371  0
         if (country == null) {
 372  0
             country = "";
 373  
         }
 374  0
         if ((language.length() < 1) && (country.length() < 1)) {
 375  0
             return ("");
 376  
         }
 377  0
         StringBuffer sb = new StringBuffer();
 378  0
         if (language.length() > 0) {
 379  0
             sb.append('_');
 380  0
             sb.append(language.toLowerCase());
 381  
         }
 382  0
         if (country.length() > 0) {
 383  0
             sb.append('_');
 384  0
             sb.append(country.toUpperCase());
 385  
         }
 386  0
         String variant = locale.getVariant();
 387  0
         if ((variant != null) && (variant.length() > 0)) {
 388  0
             sb.append('_');
 389  0
             sb.append(variant);
 390  
         }
 391  0
         return (sb.toString());
 392  
 
 393  
     }
 394  
 
 395  
     /**
 396  
      * Accessor method for Log instance.
 397  
      *
 398  
      * The Log instance variable is transient and
 399  
      * accessing it through this method ensures it
 400  
      * is re-initialized when this instance is
 401  
      * de-serialized.
 402  
      *
 403  
      * @return The Log instance.
 404  
      */
 405  
     private Log getLog() {
 406  0
         if (log == null) {
 407  0
             log =  LogFactory.getLog(CollectionResourcesBase.class);
 408  
         }
 409  0
         return log;
 410  
     }
 411  
 
 412  
 }