Coverage Report - org.apache.myfaces.application._LocaleUtils
 
Classes in this File Line Coverage Branch Coverage Complexity
_LocaleUtils
0%
0/94
0%
0/98
7.9
_LocaleUtils$SyncAvoid
0%
0/5
N/A
7.9
 
 1  
 /*
 2  
  * Licensed to the Apache Software Foundation (ASF) under one
 3  
  * or more contributor license agreements.  See the NOTICE file
 4  
  * distributed with this work for additional information
 5  
  * regarding copyright ownership.  The ASF licenses this file
 6  
  * to you under the Apache License, Version 2.0 (the
 7  
  * "License"); you may not use this file except in compliance
 8  
  * with the License.  You may obtain a copy of the License at
 9  
  *
 10  
  *   http://www.apache.org/licenses/LICENSE-2.0
 11  
  *
 12  
  * Unless required by applicable law or agreed to in writing,
 13  
  * software distributed under the License is distributed on an
 14  
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 15  
  * KIND, either express or implied.  See the License for the
 16  
  * specific language governing permissions and limitations
 17  
  * under the License.
 18  
  */
 19  
 package org.apache.myfaces.application;
 20  
 
 21  
 import java.util.ArrayList;
 22  
 import java.util.Arrays;
 23  
 import java.util.Collections;
 24  
 import java.util.HashSet;
 25  
 import java.util.List;
 26  
 import java.util.Locale;
 27  
 import java.util.Set;
 28  
 import java.util.concurrent.ConcurrentHashMap;
 29  
 import java.util.concurrent.ConcurrentMap;
 30  
 
 31  
 /**
 32  
  * <p>Operations to assist when working with a {@link Locale}.</p>
 33  
  *
 34  
  * <p>This class tries to handle {@code null} input gracefully.
 35  
  * An exception will not be thrown for a {@code null} input.
 36  
  * Each method documents its behaviour in more detail.</p>
 37  
  *
 38  
  * NOTE: This a copy of commons lang LocaleUtils, to use it inside MyFaces 
 39  
  *
 40  
  * @since 2.2
 41  
  * @version $Id$
 42  
  */
 43  
 class _LocaleUtils
 44  
 {
 45  
 
 46  
     /** Concurrent map of language locales by country. */
 47  0
     private static final ConcurrentMap<String, List<Locale>> LANGUAGES_BY_COUNTRY =
 48  
             new ConcurrentHashMap<String, List<Locale>>();
 49  
 
 50  
     /** Concurrent map of country locales by language. */
 51  0
     private static final ConcurrentMap<String, List<Locale>> COUNTRIES_BY_LANGUAGE =
 52  
             new ConcurrentHashMap<String, List<Locale>>();
 53  
 
 54  
     /**
 55  
      * <p>{@code _LocaleUtils} instances should NOT be constructed in standard programming.
 56  
      * Instead, the class should be used as {@code _LocaleUtils.toLocale("en_GB");}.</p>
 57  
      *
 58  
      * <p>This constructor is public to permit tools that require a JavaBean instance
 59  
      * to operate.</p>
 60  
      */
 61  
     public _LocaleUtils()
 62  
     {
 63  0
         super();
 64  0
     }
 65  
 
 66  
     //-----------------------------------------------------------------------
 67  
 
 68  
     /**
 69  
      * <p>Converts a String to a Locale.</p>
 70  
      *
 71  
      * <p>This method takes the string format of a locale and creates the
 72  
      * locale object from it.</p>
 73  
      *
 74  
      * <pre>
 75  
      *   _LocaleUtils.toLocale("en")         = new Locale("en", "")
 76  
      *   _LocaleUtils.toLocale("en_GB")      = new Locale("en", "GB")
 77  
      *   _LocaleUtils.toLocale("en_GB_xxx")  = new Locale("en", "GB", "xxx")   (#)
 78  
      * </pre>
 79  
      *
 80  
      * <p>(#) The behaviour of the JDK variant constructor changed between JDK1.3 and JDK1.4.
 81  
      * In JDK1.3, the constructor upper cases the variant, in JDK1.4, it doesn't.
 82  
      * Thus, the result from getVariant() may vary depending on your JDK.</p>
 83  
      *
 84  
      * <p>This method validates the input strictly.
 85  
      * The language code must be lowercase.
 86  
      * The country code must be uppercase.
 87  
      * The separator must be an underscore.
 88  
      * The length must be correct.
 89  
      * </p>
 90  
      *
 91  
      * @param str  the locale String to convert, null returns null
 92  
      * @return a Locale, null if null input
 93  
      * @throws IllegalArgumentException if the string is an invalid format
 94  
      */
 95  
     public static Locale toLocale(String str)
 96  
     {
 97  0
         if (str == null)
 98  
         {
 99  0
             return null;
 100  
         }
 101  0
         int len = str.length();
 102  0
         if (len != 2 && len != 5 && len < 7)
 103  
         {
 104  0
             throw new IllegalArgumentException("Invalid locale format: " + str);
 105  
         }
 106  0
         char ch0 = str.charAt(0);
 107  0
         char ch1 = str.charAt(1);
 108  0
         if (ch0 < 'a' || ch0 > 'z' || ch1 < 'a' || ch1 > 'z')
 109  
         {
 110  0
             throw new IllegalArgumentException("Invalid locale format: " + str);
 111  
         }
 112  0
         if (len == 2)
 113  
         {
 114  0
             return new Locale(str, "");
 115  
         }
 116  
         else
 117  
         {
 118  0
             if (str.charAt(2) != '_')
 119  
             {
 120  0
                 throw new IllegalArgumentException("Invalid locale format: " + str);
 121  
             }
 122  0
             char ch3 = str.charAt(3);
 123  0
             if (ch3 == '_')
 124  
             {
 125  0
                 return new Locale(str.substring(0, 2), "", str.substring(4));
 126  
             }
 127  0
             char ch4 = str.charAt(4);
 128  0
             if (ch3 < 'A' || ch3 > 'Z' || ch4 < 'A' || ch4 > 'Z')
 129  
             {
 130  0
                 throw new IllegalArgumentException("Invalid locale format: " + str);
 131  
             }
 132  0
             if (len == 5)
 133  
             {
 134  0
                 return new Locale(str.substring(0, 2), str.substring(3, 5));
 135  
             }
 136  
             else
 137  
             {
 138  0
                 if (str.charAt(5) != '_')
 139  
                 {
 140  0
                     throw new IllegalArgumentException("Invalid locale format: " + str);
 141  
                 }
 142  0
                 return new Locale(str.substring(0, 2), str.substring(3, 5), str.substring(6));
 143  
             }
 144  
         }
 145  
     }
 146  
     
 147  
     /**
 148  
      * Same as toLocale(), but return null when it cannot derive a valid Locale object.
 149  
      * 
 150  
      * @param str
 151  
      * @return 
 152  
      */
 153  
     public static Locale deriveLocale(String str)
 154  
     {
 155  0
         if (str == null)
 156  
         {
 157  0
             return null;
 158  
         }
 159  0
         int len = str.length();
 160  0
         if (len != 2 && len != 5 && len < 7)
 161  
         {
 162  0
             return null;
 163  
         }
 164  0
         char ch0 = str.charAt(0);
 165  0
         char ch1 = str.charAt(1);
 166  0
         if (ch0 < 'a' || ch0 > 'z' || ch1 < 'a' || ch1 > 'z')
 167  
         {
 168  0
             return null;
 169  
         }
 170  0
         if (len == 2)
 171  
         {
 172  0
             return new Locale(str, "");
 173  
         }
 174  
         else
 175  
         {
 176  0
             if (str.charAt(2) != '_')
 177  
             {
 178  0
                 return null;
 179  
             }
 180  0
             char ch3 = str.charAt(3);
 181  0
             if (ch3 == '_')
 182  
             {
 183  0
                 return new Locale(str.substring(0, 2), "", str.substring(4));
 184  
             }
 185  0
             char ch4 = str.charAt(4);
 186  0
             if (ch3 < 'A' || ch3 > 'Z' || ch4 < 'A' || ch4 > 'Z')
 187  
             {
 188  0
                 return null;
 189  
             }
 190  0
             if (len == 5)
 191  
             {
 192  0
                 return new Locale(str.substring(0, 2), str.substring(3, 5));
 193  
             }
 194  
             else
 195  
             {
 196  0
                 if (str.charAt(5) != '_')
 197  
                 {
 198  0
                     return null;
 199  
                 }
 200  0
                 return new Locale(str.substring(0, 2), str.substring(3, 5), str.substring(6));
 201  
             }
 202  
         }
 203  
     }
 204  
 
 205  
     //-----------------------------------------------------------------------
 206  
 
 207  
     /**
 208  
      * <p>Obtains the list of locales to search through when performing
 209  
      * a locale search.</p>
 210  
      *
 211  
      * <pre>
 212  
      * localeLookupList(Locale("fr","CA","xxx"))
 213  
      *   = [Locale("fr","CA","xxx"), Locale("fr","CA"), Locale("fr")]
 214  
      * </pre>
 215  
      *
 216  
      * @param locale  the locale to start from
 217  
      * @return the unmodifiable list of Locale objects, 0 being locale, not null
 218  
      */
 219  
     public static List<Locale> localeLookupList(Locale locale)
 220  
     {
 221  0
         return localeLookupList(locale, locale);
 222  
     }
 223  
 
 224  
     //-----------------------------------------------------------------------
 225  
 
 226  
     /**
 227  
      * <p>Obtains the list of locales to search through when performing
 228  
      * a locale search.</p>
 229  
      *
 230  
      * <pre>
 231  
      * localeLookupList(Locale("fr", "CA", "xxx"), Locale("en"))
 232  
      *   = [Locale("fr","CA","xxx"), Locale("fr","CA"), Locale("fr"), Locale("en"]
 233  
      * </pre>
 234  
      *
 235  
      * <p>The result list begins with the most specific locale, then the
 236  
      * next more general and so on, finishing with the default locale.
 237  
      * The list will never contain the same locale twice.</p>
 238  
      *
 239  
      * @param locale  the locale to start from, null returns empty list
 240  
      * @param defaultLocale  the default locale to use if no other is found
 241  
      * @return the unmodifiable list of Locale objects, 0 being locale, not null
 242  
      */
 243  
     public static List<Locale> localeLookupList(Locale locale, Locale defaultLocale)
 244  
     {
 245  0
         List<Locale> list = new ArrayList<Locale>(4);
 246  0
         if (locale != null)
 247  
         {
 248  0
             list.add(locale);
 249  0
             if (locale.getVariant().length() > 0)
 250  
             {
 251  0
                 list.add(new Locale(locale.getLanguage(), locale.getCountry()));
 252  
             }
 253  0
             if (locale.getCountry().length() > 0)
 254  
             {
 255  0
                 list.add(new Locale(locale.getLanguage(), ""));
 256  
             }
 257  0
             if (!list.contains(defaultLocale))
 258  
             {
 259  0
                 list.add(defaultLocale);
 260  
             }
 261  
         }
 262  0
         return Collections.unmodifiableList(list);
 263  
     }
 264  
 
 265  
     //-----------------------------------------------------------------------
 266  
 
 267  
     /**
 268  
      * <p>Obtains an unmodifiable list of installed locales.</p>
 269  
      *
 270  
      * <p>This method is a wrapper around {@link Locale#getAvailableLocales()}.
 271  
      * It is more efficient, as the JDK method must create a new array each
 272  
      * time it is called.</p>
 273  
      *
 274  
      * @return the unmodifiable list of available locales
 275  
      */
 276  
     public static List<Locale> availableLocaleList()
 277  
     {
 278  0
         return SyncAvoid.AVAILABLE_LOCALE_LIST;
 279  
     }
 280  
 
 281  
     //-----------------------------------------------------------------------
 282  
 
 283  
     /**
 284  
      * <p>Obtains an unmodifiable set of installed locales.</p>
 285  
      *
 286  
      * <p>This method is a wrapper around {@link Locale#getAvailableLocales()}.
 287  
      * It is more efficient, as the JDK method must create a new array each
 288  
      * time it is called.</p>
 289  
      *
 290  
      * @return the unmodifiable set of available locales
 291  
      */
 292  
     public static Set<Locale> availableLocaleSet()
 293  
     {
 294  0
         return SyncAvoid.AVAILABLE_LOCALE_SET;
 295  
     }
 296  
 
 297  
     //-----------------------------------------------------------------------
 298  
 
 299  
     /**
 300  
      * <p>Checks if the locale specified is in the list of available locales.</p>
 301  
      *
 302  
      * @param locale the Locale object to check if it is available
 303  
      * @return true if the locale is a known locale
 304  
      */
 305  
     public static boolean isAvailableLocale(Locale locale)
 306  
     {
 307  0
         return availableLocaleList().contains(locale);
 308  
     }
 309  
 
 310  
     //-----------------------------------------------------------------------
 311  
 
 312  
     /**
 313  
      * <p>Obtains the list of languages supported for a given country.</p>
 314  
      *
 315  
      * <p>This method takes a country code and searches to find the
 316  
      * languages available for that country. Variant locales are removed.</p>
 317  
      *
 318  
      * @param countryCode  the 2 letter country code, null returns empty
 319  
      * @return an unmodifiable List of Locale objects, not null
 320  
      */
 321  
     public static List<Locale> languagesByCountry(String countryCode)
 322  
     {
 323  0
         if (countryCode == null)
 324  
         {
 325  0
             return Collections.emptyList();
 326  
         }
 327  0
         List<Locale> langs = LANGUAGES_BY_COUNTRY.get(countryCode);
 328  0
         if (langs == null)
 329  
         {
 330  0
             langs = new ArrayList<Locale>();
 331  0
             List<Locale> locales = availableLocaleList();
 332  0
             for (int i = 0; i < locales.size(); i++)
 333  
             {
 334  0
                 Locale locale = locales.get(i);
 335  0
                 if (countryCode.equals(locale.getCountry()) &&
 336  
                         locale.getVariant().length() == 0)
 337  
                 {
 338  0
                     langs.add(locale);
 339  
                 }
 340  
             }
 341  0
             langs = Collections.unmodifiableList(langs);
 342  0
             LANGUAGES_BY_COUNTRY.putIfAbsent(countryCode, langs);
 343  0
             langs = LANGUAGES_BY_COUNTRY.get(countryCode);
 344  
         }
 345  0
         return langs;
 346  
     }
 347  
 
 348  
     //-----------------------------------------------------------------------
 349  
 
 350  
     /**
 351  
      * <p>Obtains the list of countries supported for a given language.</p>
 352  
      *
 353  
      * <p>This method takes a language code and searches to find the
 354  
      * countries available for that language. Variant locales are removed.</p>
 355  
      *
 356  
      * @param languageCode  the 2 letter language code, null returns empty
 357  
      * @return an unmodifiable List of Locale objects, not null
 358  
      */
 359  
     public static List<Locale> countriesByLanguage(String languageCode)
 360  
     {
 361  0
         if (languageCode == null)
 362  
         {
 363  0
             return Collections.emptyList();
 364  
         }
 365  0
         List<Locale> countries = COUNTRIES_BY_LANGUAGE.get(languageCode);
 366  0
         if (countries == null)
 367  
         {
 368  0
             countries = new ArrayList<Locale>();
 369  0
             List<Locale> locales = availableLocaleList();
 370  0
             for (int i = 0; i < locales.size(); i++)
 371  
             {
 372  0
                 Locale locale = locales.get(i);
 373  0
                 if (languageCode.equals(locale.getLanguage()) &&
 374  
                         locale.getCountry().length() != 0 &&
 375  
                         locale.getVariant().length() == 0)
 376  
                 {
 377  0
                     countries.add(locale);
 378  
                 }
 379  
             }
 380  0
             countries = Collections.unmodifiableList(countries);
 381  0
             COUNTRIES_BY_LANGUAGE.putIfAbsent(languageCode, countries);
 382  0
             countries = COUNTRIES_BY_LANGUAGE.get(languageCode);
 383  
         }
 384  0
         return countries;
 385  
     }
 386  
 
 387  
     //-----------------------------------------------------------------------
 388  
     // class to avoid synchronization
 389  0
     static class SyncAvoid
 390  
     {
 391  
         /** Unmodifiable list of available locales. */
 392  
         private static final List<Locale> AVAILABLE_LOCALE_LIST;
 393  
         /** Unmodifiable set of available locales. */
 394  
         private static final Set<Locale> AVAILABLE_LOCALE_SET;
 395  
 
 396  
         static
 397  
         {
 398  0
             List<Locale> list = new ArrayList<Locale>(Arrays.asList(Locale.getAvailableLocales()));  // extra safe
 399  0
             AVAILABLE_LOCALE_LIST = Collections.unmodifiableList(list);
 400  0
             AVAILABLE_LOCALE_SET = Collections.unmodifiableSet(new HashSet<Locale>(availableLocaleList()));
 401  0
         }
 402  
     }
 403  
 
 404  
 }