Source for java.text.DateFormatSymbols

   1: /* DateFormatSymbols.java -- Format over a range of numbers
   2:    Copyright (C) 1998, 1999, 2000, 2001, 2003, 2005, 2006  Free Software Foundation, Inc.
   3: 
   4: This file is part of GNU Classpath.
   5: 
   6: GNU Classpath is free software; you can redistribute it and/or modify
   7: it under the terms of the GNU General Public License as published by
   8: the Free Software Foundation; either version 2, or (at your option)
   9: any later version.
  10: 
  11: GNU Classpath is distributed in the hope that it will be useful, but
  12: WITHOUT ANY WARRANTY; without even the implied warranty of
  13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14: General Public License for more details.
  15: 
  16: You should have received a copy of the GNU General Public License
  17: along with GNU Classpath; see the file COPYING.  If not, write to the
  18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  19: 02110-1301 USA.
  20: 
  21: Linking this library statically or dynamically with other modules is
  22: making a combined work based on this library.  Thus, the terms and
  23: conditions of the GNU General Public License cover the whole
  24: combination.
  25: 
  26: As a special exception, the copyright holders of this library give you
  27: permission to link this library with independent modules to produce an
  28: executable, regardless of the license terms of these independent
  29: modules, and to copy and distribute the resulting executable under
  30: terms of your choice, provided that you also meet, for each linked
  31: independent module, the terms and conditions of the license of that
  32: module.  An independent module is a module which is not derived from
  33: or based on this library.  If you modify this library, you may extend
  34: this exception to your version of the library, but you are not
  35: obligated to do so.  If you do not wish to do so, delete this
  36: exception statement from your version. */
  37: 
  38: 
  39: package java.text;
  40: 
  41: import gnu.java.locale.LocaleHelper;
  42: 
  43: import java.io.IOException;
  44: 
  45: import java.text.spi.DateFormatSymbolsProvider;
  46: 
  47: import java.util.ArrayList;
  48: import java.util.Arrays;
  49: import java.util.HashMap;
  50: import java.util.List;
  51: import java.util.Locale;
  52: import java.util.Map;
  53: import java.util.MissingResourceException;
  54: import java.util.Properties;
  55: import java.util.ResourceBundle;
  56: import java.util.ServiceLoader;
  57: import java.util.TimeZone;
  58: 
  59: import java.util.concurrent.ConcurrentMap;
  60: import java.util.concurrent.ConcurrentHashMap;
  61: 
  62: import java.util.regex.Pattern;
  63: 
  64: import java.util.spi.TimeZoneNameProvider;
  65: 
  66: /**
  67:  * This class acts as container for locale specific date/time formatting
  68:  * information such as the days of the week and the months of the year.
  69:  *
  70:  * @author Per Bothner (bothner@cygnus.com)
  71:  * @author Andrew John Hughes (gnu_andrew@member.fsf.org)
  72:  * @date October 24, 1998.
  73:  */
  74: /* Written using "Java Class Libraries", 2nd edition, ISBN 0-201-31002-3.
  75:  * Status:  Believed complete and correct.
  76:  */
  77: public class DateFormatSymbols implements java.io.Serializable, Cloneable
  78: {
  79:   /**
  80:    * The set of properties for obtaining the metazone data.
  81:    */
  82:   private static transient final Properties properties;
  83: 
  84:   /**
  85:    * Reads in the properties.
  86:    */
  87:   static
  88:   {
  89:     properties = new Properties();
  90:     try
  91:       {
  92:         properties.load(DateFormatSymbols.class.getResourceAsStream("metazones.properties"));
  93:       }
  94:     catch (IOException exception)
  95:       {
  96:         System.out.println("Failed to load weeks resource: " + exception);
  97:       }
  98:   }
  99: 
 100:   private static final Pattern ZONE_SEP = Pattern.compile("\u00a9");
 101: 
 102:   private static final Pattern FIELD_SEP = Pattern.compile("\u00ae");
 103: 
 104:   /**
 105:    * Class for storing DateFormatSymbols data parsed from the property files.
 106:    */
 107:   private static class DFSData
 108:   {
 109:     private String[] ampms;
 110:     private String[] eras;
 111:     private String localPatternChars;
 112:     private String[] months;
 113:     private String[] shortMonths;
 114:     private String[] weekdays;
 115:     private String[] shortWeekdays;
 116:     private String[] dateFormats;
 117:     private String[] timeFormats;
 118:     private String[][] runtimeZoneStrings;
 119: 
 120:     /**
 121:      * Construct a new instance with the parsed data.
 122:      *
 123:      * @param ampms strings for "am" and "pm".
 124:      * @param eras strings for calendar eras.
 125:      * @param localPatternChars localised pattern characters.
 126:      * @param months strings for the months of the year.
 127:      * @param shortMonths short strings for the months of the year.
 128:      * @param weekdays strings for the days of the week.
 129:      * @param shortWeekdays short strings for the days of the week.
 130:      * @param dateFormats localised date formats.
 131:      * @param timeFormats localised time formats.
 132:      * @param runtimeZoneStrings localised time zone names.
 133:      */
 134:     public DFSData(String[] ampms, String[] eras, String localPatternChars,
 135:                    String[] months, String[] shortMonths, String[] weekdays,
 136:                    String[] shortWeekdays, String[] dateFormats,
 137:                    String[] timeFormats, String[][] runtimeZoneStrings)
 138:     {
 139:       this.ampms = ampms;
 140:       this.eras = eras;
 141:       this.localPatternChars = localPatternChars;
 142:       this.months = months;
 143:       this.shortMonths = shortMonths;
 144:       this.weekdays = weekdays;
 145:       this.shortWeekdays = shortWeekdays;
 146:       this.dateFormats = dateFormats;
 147:       this.timeFormats = timeFormats;
 148:       this.runtimeZoneStrings = runtimeZoneStrings;
 149:     }
 150: 
 151:     /**
 152:      * Accessor for the AM/PM data.
 153:      *
 154:      * @return the AM/PM strings.
 155:      */
 156:     public String[] getAMPMs()
 157:     {
 158:       return ampms.clone();
 159:     }
 160: 
 161:     /**
 162:      * Accessor for the era data.
 163:      *
 164:      * @return the era strings.
 165:      */
 166:     public String[] getEras()
 167:     {
 168:       return eras.clone();
 169:     }
 170: 
 171:     /**
 172:      * Accessor for the local pattern characters.
 173:      *
 174:      * @return the local pattern characters.
 175:      */
 176:     public String getLocalPatternChars()
 177:     {
 178:       return localPatternChars;
 179:     }
 180: 
 181:     /**
 182:      * Accessor for the months of the year (long form).
 183:      *
 184:      * @return the months of the year (long form).
 185:      */
 186:     public String[] getMonths()
 187:     {
 188:       return months.clone();
 189:     }
 190: 
 191:     /**
 192:      * Accessor for the months of the year (short form).
 193:      *
 194:      * @return the months of the year (short form).
 195:      */
 196:     public String[] getShortMonths()
 197:     {
 198:       return shortMonths.clone();
 199:     }
 200: 
 201:     /**
 202:      * Accessor for the days of the week (long form).
 203:      *
 204:      * @return the days of the week (long form).
 205:      */
 206:     public String[] getWeekdays()
 207:     {
 208:       return weekdays.clone();
 209:     }
 210: 
 211:     /**
 212:      * Accessor for the days of the week (short form).
 213:      *
 214:      * @return the days of the week (short form).
 215:      */
 216:     public String[] getShortWeekdays()
 217:     {
 218:       return shortWeekdays.clone();
 219:     }
 220: 
 221:     /**
 222:      * Accessor for the date formats.
 223:      *
 224:      * @return the date formats.
 225:      */
 226:     public String[] getDateFormats()
 227:     {
 228:       return dateFormats.clone();
 229:     }
 230: 
 231:     /**
 232:      * Accessor for the time formats.
 233:      *
 234:      * @return the time formats.
 235:      */
 236:     public String[] getTimeFormats()
 237:     {
 238:       return timeFormats.clone();
 239:     }
 240: 
 241:     /**
 242:      * Accessor for the zone strings.
 243:      *
 244:      * @return the zone strings.
 245:      */
 246:     public String[][] getZoneStrings()
 247:     {
 248:       // Perform a deep clone so subarrays aren't modifiable
 249:       String[][] clone = runtimeZoneStrings.clone();
 250:       for (int a = 0; a < clone.length; ++a)
 251:         clone[a] = runtimeZoneStrings[a].clone();
 252:       return clone;
 253:     }
 254: 
 255:   }
 256: 
 257:   private static final ConcurrentMap<Locale, DFSData> dataCache = new ConcurrentHashMap<Locale, DFSData>();
 258: 
 259:   String[] ampms;
 260:   String[] eras;
 261:   private String localPatternChars;
 262:   String[] months;
 263:   String[] shortMonths;
 264:   String[] shortWeekdays;
 265:   String[] weekdays;
 266: 
 267:   /**
 268:    * The timezone strings supplied by the runtime.
 269:    */
 270:   private String[][] runtimeZoneStrings;
 271: 
 272:   /**
 273:    * Custom timezone strings supplied by {@link #setZoneStrings()}.
 274:    */
 275:   private String[][] zoneStrings;
 276: 
 277:   private static final long serialVersionUID = -5987973545549424702L;
 278: 
 279:   // The order of these prefixes must be the same as in DateFormat
 280:   private static final String[] formatPrefixes =
 281:   {
 282:     "full", "long", "medium", "short"
 283:   };
 284: 
 285:   // These are each arrays with a value for SHORT, MEDIUM, LONG, FULL,
 286:   // and DEFAULT (constants defined in java.text.DateFormat).  While
 287:   // not part of the official spec, we need a way to get at locale-specific
 288:   // default formatting patterns.  They are declared package scope so
 289:   // as to be easily accessible where needed (DateFormat, SimpleDateFormat).
 290:   transient String[] dateFormats;
 291:   transient String[] timeFormats;
 292: 
 293:   /**
 294:    * Compiles a string array for a property using data from each of the locales in the
 295:    * hierarchy as necessary.
 296:    *
 297:    * @param bundles the locale hierarchy, starting with the most specific.
 298:    * @param name the name of the property.
 299:    * @param size the size the array should be when complete.
 300:    * @return a completed string array.
 301:    */
 302:   private static String[] getStringArray(List<ResourceBundle> bundles, String name, int size)
 303:   {
 304:     return getStringArray(bundles, name, size, null);
 305:   }
 306: 
 307:   /**
 308:    * Compiles a string array for a property using data from each of the locales in the
 309:    * hierarchy as necessary.  If non-null, the fallback array is also used for "sideways"
 310:    * inheritance (e.g. if there is no short name for a month, the long name is used rather
 311:    * than the empty string).
 312:    *
 313:    * @param bundles the locale hierarchy, starting with the most specific.
 314:    * @param name the name of the property.
 315:    * @param size the size the array should be when complete.
 316:    * @param fallback an array of long name fallback strings for data with both long and short names.
 317:    * @return a completed string array.
 318:    */
 319:   private static String[] getStringArray(List<ResourceBundle> bundles, String name, int size,
 320:                                          String[] fallback)
 321:   {
 322:     String[] data = new String[size];
 323:     Arrays.fill(data, "");
 324:     // Populate array with data from each locale back to the root, starting with the most specific
 325:     for (int a = 0; a < bundles.size(); ++a)
 326:       {
 327:         String localeData = bundles.get(a).getString(name);
 328:         String[] array = FIELD_SEP.split(localeData, size);
 329:         for (int b = 0; b < data.length; ++b)
 330:           {
 331:             if (array.length > b && array[b] != null && data[b].isEmpty() && !array[b].isEmpty())
 332:               data[b] = array[b];
 333:           }
 334:       }
 335:     // Replace any remaining empty strings with data from the fallback array, if non-null
 336:     if (fallback != null && fallback.length == size)
 337:       {
 338:         for (int a = 0; a < data.length; ++a)
 339:           {
 340:             if (data[a].isEmpty() && fallback[a] != null && !fallback[a].isEmpty())
 341:               data[a] = fallback[a];
 342:           }
 343:       }
 344:     return data;
 345:   }
 346: 
 347:   private static String[][] getZoneStrings(List<ResourceBundle> bundles, Locale locale)
 348:   {
 349:     List<String[]> allZones = new ArrayList<String[]>();
 350:     try
 351:       {
 352:         Map<String,String[]> systemZones = new HashMap<String,String[]>();
 353:         for (ResourceBundle bundle : bundles)
 354:           {
 355:             String country = locale.getCountry();
 356:             String data = bundle.getString("zoneStrings");
 357:             String[] zones = ZONE_SEP.split(data);
 358:             for (int a = 0; a < zones.length; ++a)
 359:               {
 360:                 String[] strings = FIELD_SEP.split(zones[a]);
 361:                 String type = properties.getProperty(strings[0] + "." + country);
 362:                 if (type == null)
 363:                   type = properties.getProperty(strings[0] + ".DEFAULT");
 364:                 if (type != null)
 365:                   strings[0] = type;
 366:                 if (strings.length < 5)
 367:                   {
 368:                     String[] newStrings = new String[5];
 369:                     System.arraycopy(strings, 0, newStrings, 0, strings.length);
 370:                     for (int b = strings.length; b < newStrings.length; ++b)
 371:                       newStrings[b] = "";
 372:                     strings = newStrings;
 373:                   }
 374:                 String[] existing = systemZones.get(strings[0]);
 375:                 if (existing != null && existing.length > 1)
 376:                   {
 377:                     for (int b = 1; b < existing.length; ++b)
 378:                       if (!existing[b].equals(""))
 379:                         strings[b] = existing[b];
 380:                   }
 381:                 systemZones.put(strings[0], strings);
 382:               }
 383:           }
 384:         /* Final sanity check for missing values */
 385:         for (String[] zstrings : systemZones.values())
 386:           {
 387:             if (zstrings[1].equals("") && zstrings[2].equals(""))
 388:               {
 389:                 for (Map.Entry<Object,Object> entry : properties.entrySet())
 390:                   {
 391:                     String val = (String) entry.getValue();
 392:                     if (val.equals(zstrings[0]))
 393:                       {
 394:                         String key = (String) entry.getKey();
 395:                         String metazone = key.substring(0, key.indexOf("."));
 396:                         String type = properties.getProperty(metazone + "." + locale.getCountry());
 397:                         if (type == null)
 398:                           type = properties.getProperty(metazone + ".DEFAULT");
 399:                         if (type != null)
 400:                           {
 401:                             String[] ostrings = systemZones.get(type);
 402:                             zstrings[1] = ostrings[1];
 403:                             zstrings[2] = ostrings[2];
 404:                           }
 405:                       }
 406:                   }
 407:               }
 408:           }
 409:         allZones.addAll(systemZones.values());
 410:       }
 411:     catch (MissingResourceException e)
 412:       {
 413:         /* This means runtime support for the locale
 414:          * is not available, so we just include providers. */
 415:       }
 416:     for (TimeZoneNameProvider p :
 417:            ServiceLoader.load(TimeZoneNameProvider.class))
 418:       {
 419:         for (Locale loc : p.getAvailableLocales())
 420:           {
 421:             if (loc.equals(locale))
 422:               {
 423:                 for (String id : TimeZone.getAvailableIDs())
 424:                   {
 425:                     String[] z = new String[5];
 426:                     z[0] = id;
 427:                     z[1] = p.getDisplayName(id, false,
 428:                                             TimeZone.LONG,
 429:                                             locale);
 430:                     z[2] = p.getDisplayName(id, false,
 431:                                             TimeZone.SHORT,
 432:                                             locale);
 433:                     z[3] = p.getDisplayName(id, true,
 434:                                             TimeZone.LONG,
 435:                                             locale);
 436:                     z[4] = p.getDisplayName(id, true,
 437:                                             TimeZone.SHORT,
 438:                                             locale);
 439:                     allZones.add(z);
 440:                   }
 441:                 break;
 442:               }
 443:           }
 444:       }
 445:     return allZones.toArray(new String[allZones.size()][]);
 446:   }
 447: 
 448:   /**
 449:    * Retrieve the date or time formats for a specific key e.g.
 450:    * asking for "DateFormat" will return an array containing the
 451:    * full, long, medium and short date formats localised for
 452:    * the locales in the specified bundle.
 453:    *
 454:    * @param bundles the stack of bundles to check, most-specific first.
 455:    * @param key the type of format to retrieve.
 456:    * @param an array of localised strings for each format prefix.
 457:    */
 458:   private static String[] formatsForKey(List<ResourceBundle> bundles, String key)
 459:   {
 460:     String[] values = new String[formatPrefixes.length];
 461: 
 462:     for (int i = 0; i < formatPrefixes.length; i++)
 463:       values[i] = getString(bundles, formatPrefixes[i] + key);
 464: 
 465:     return values;
 466:   }
 467: 
 468:   /**
 469:    * Simple wrapper around extracting a {@code String} from a
 470:    * {@code ResourceBundle}.  Keep searching less-specific locales
 471:    * until a non-null non-empty value is found.
 472:    *
 473:    * @param bundles the stack of bundles to check, most-specific first.
 474:    * @param key the key of the value to retrieve.
 475:    * @return the first non-null non-empty String found or the last
 476:    *         retrieved if one isn't found.
 477:    */
 478:   private static String getString(List<ResourceBundle> bundles, String key)
 479:   {
 480:     String val = null;
 481:     for (ResourceBundle bundle : bundles)
 482:       {
 483:         val = bundle.getString(key);
 484:         if (val != null && !val.isEmpty())
 485:           return val;
 486:       }
 487:     return val;
 488:   }
 489: 
 490:   /**
 491:    * Retrieves the locale data from the property files and constructs a
 492:    * {@code DFSData} instance for it.
 493:    *
 494:    * @param the locale for which data should be retrieved.
 495:    * @return the parsed data.
 496:    * @throws MissingResourceException if the resources for the specified
 497:    *                                  locale could not be found or loaded.
 498:    */
 499:   private static DFSData retrieveData(Locale locale)
 500:     throws MissingResourceException
 501:   {
 502:     DFSData data = dataCache.get(locale);
 503:     if (data == null)
 504:       {
 505:         ClassLoader ldr = ClassLoader.getSystemClassLoader();
 506:         List<ResourceBundle> bundles = new ArrayList<ResourceBundle>();
 507:         ResourceBundle res
 508:           = ResourceBundle.getBundle("gnu.java.locale.LocaleInformation", locale, ldr);
 509:         bundles.add(res);
 510:         Locale resLocale = res.getLocale();
 511:         while (resLocale != Locale.ROOT)
 512:           {
 513:             res = ResourceBundle.getBundle("gnu.java.locale.LocaleInformation",
 514:                                            LocaleHelper.getFallbackLocale(resLocale), ldr);
 515:             bundles.add(res);
 516:             resLocale = res.getLocale();
 517:           }
 518:         String[] lMonths = getStringArray(bundles, "months", 13);
 519:         String[] lWeekdays = getStringArray(bundles, "weekdays", 8);
 520:         data = new DFSData(getStringArray(bundles, "ampms", 2),
 521:                            getStringArray(bundles, "eras", 2),
 522:                            getString(bundles, "localPatternChars"),
 523:                            lMonths, getStringArray(bundles, "shortMonths", 13, lMonths),
 524:                            lWeekdays, getStringArray(bundles, "shortWeekdays", 8, lWeekdays),
 525:                            formatsForKey(bundles, "DateFormat"),
 526:                            formatsForKey(bundles, "TimeFormat"),
 527:                            getZoneStrings(bundles, locale));
 528:         DFSData cachedData = dataCache.putIfAbsent(locale, data);
 529:         // Use the earlier version if another thread beat us to it.
 530:         if (cachedData != null)
 531:           data = cachedData;
 532:       }
 533:     return data;
 534:   }
 535: 
 536:   /**
 537:    * This method initializes a new instance of <code>DateFormatSymbols</code>
 538:    * by loading the date format information for the specified locale.
 539:    * This constructor only obtains instances using the runtime's resources;
 540:    * to also include {@link java.text.spi.DateFormatSymbolsProvider} instances,
 541:    * call {@link #getInstance(java.util.Locale)} instead.
 542:    *
 543:    * @param locale The locale for which date formatting symbols should
 544:    *               be loaded.
 545:    * @throws MissingResourceException if the resources for the specified
 546:    *                                  locale could not be found or loaded.
 547:    * @see #getInstance(java.util.Locale)
 548:    */
 549:   public DateFormatSymbols (Locale locale)
 550:     throws MissingResourceException
 551:   {
 552:     DFSData data = retrieveData(locale);
 553:     ampms = data.getAMPMs();
 554:     eras = data.getEras();
 555:     localPatternChars = data.getLocalPatternChars();
 556:     months = data.getMonths();
 557:     shortMonths = data.getShortMonths();
 558:     weekdays = data.getWeekdays();
 559:     shortWeekdays = data.getShortWeekdays();
 560:     dateFormats = data.getDateFormats();
 561:     timeFormats = data.getTimeFormats();
 562:     runtimeZoneStrings = data.getZoneStrings();
 563:   }
 564: 
 565:   /**
 566:    * This method loads the format symbol information for the default
 567:    * locale. This constructor only obtains instances using the runtime's resources;
 568:    * to also include {@link java.text.spi.DateFormatSymbolsProvider} instances,
 569:    * call {@link #getInstance()} instead.
 570:    *
 571:    * @throws MissingResourceException if the resources for the default
 572:    *                                  locale could not be found or loaded.
 573:    * @see #getInstance()
 574:    */
 575:   public DateFormatSymbols()
 576:     throws MissingResourceException
 577:   {
 578:     this (Locale.getDefault());
 579:   }
 580: 
 581:   /**
 582:    * This method returns the list of strings used for displaying AM or PM.
 583:    * This is a two element <code>String</code> array indexed by
 584:    * <code>Calendar.AM</code> and <code>Calendar.PM</code>
 585:    *
 586:    * @return The list of AM/PM display strings.
 587:    */
 588:   public String[] getAmPmStrings()
 589:   {
 590:     return ampms;
 591:   }
 592: 
 593:   /**
 594:     * This method returns the list of strings used for displaying eras
 595:     * (e.g., "BC" and "AD").  This is a two element <code>String</code>
 596:     * array indexed by <code>Calendar.BC</code> and <code>Calendar.AD</code>.
 597:     *
 598:     * @return The list of era disply strings.
 599:     */
 600:   public String[] getEras()
 601:   {
 602:     return eras;
 603:   }
 604: 
 605:   /**
 606:     * This method returns the pattern character information for this
 607:     * object.  This is an 18 character string that contains the characters
 608:     * that are used in creating the date formatting strings in
 609:     * <code>SimpleDateFormat</code>.   The following are the character
 610:     * positions in the string and which format character they correspond
 611:     * to (the character in parentheses is the default value in the US English
 612:     * locale):
 613:     * <p>
 614:     * <ul>
 615:     * <li>0 - era (G)</li>
 616:     * <li>1 - year (y)</li>
 617:     * <li>2 - month (M)</li>
 618:     * <li>3 - day of month (d)</li>
 619:     * <li>4 - hour out of 12, from 1-12 (h)</li>
 620:     * <li>5 - hour out of 24, from 0-23 (H)</li>
 621:     * <li>6 - minute (m)</li>
 622:     * <li>7 - second (s)</li>
 623:     * <li>8 - millisecond (S)</li>
 624:     * <li>9 - date of week (E)</li>
 625:     * <li>10 - date of year (D)</li>
 626:     * <li>11 - day of week in month, eg. "4th Thur in Nov" (F)</li>
 627:     * <li>12 - week in year (w)</li>
 628:     * <li>13 - week in month (W)</li>
 629:     * <li>14 - am/pm (a)</li>
 630:     * <li>15 - hour out of 24, from 1-24 (k)</li>
 631:     * <li>16 - hour out of 12, from 0-11 (K)</li>
 632:     * <li>17 - time zone (z)</li>
 633:     * </ul>
 634:     *
 635:     * @return The format patter characters
 636:     */
 637:   public String getLocalPatternChars()
 638:   {
 639:     return localPatternChars;
 640:   }
 641: 
 642:   /**
 643:    * This method returns the list of strings used for displaying month
 644:    * names (e.g., "January" and "February").  This is a thirteen element
 645:    * string array indexed by <code>Calendar.JANUARY</code> through
 646:    * <code>Calendar.UNDECEMBER</code>.  Note that there are thirteen
 647:    * elements because some calendars have thriteen months.
 648:    *
 649:    * @return The list of month display strings.
 650:    */
 651:   public String[] getMonths ()
 652:   {
 653:     return months;
 654:   }
 655: 
 656:   /**
 657:    * This method returns the list of strings used for displaying abbreviated
 658:    * month names (e.g., "Jan" and "Feb").  This is a thirteen element
 659:    * <code>String</code> array indexed by <code>Calendar.JANUARY</code>
 660:    * through <code>Calendar.UNDECEMBER</code>.  Note that there are thirteen
 661:    * elements because some calendars have thirteen months.
 662:    *
 663:    * @return The list of abbreviated month display strings.
 664:    */
 665:   public String[] getShortMonths ()
 666:   {
 667:     return shortMonths;
 668:   }
 669: 
 670:   /**
 671:    * This method returns the list of strings used for displaying abbreviated
 672:    * weekday names (e.g., "Sun" and "Mon").  This is an eight element
 673:    * <code>String</code> array indexed by <code>Calendar.SUNDAY</code>
 674:    * through <code>Calendar.SATURDAY</code>.  Note that the first element
 675:    * of this array is ignored.
 676:    *
 677:    * @return This list of abbreviated weekday display strings.
 678:    */
 679:   public String[] getShortWeekdays ()
 680:   {
 681:     return shortWeekdays;
 682:   }
 683: 
 684:   /**
 685:    * This method returns the list of strings used for displaying weekday
 686:    * names (e.g., "Sunday" and "Monday").  This is an eight element
 687:    * <code>String</code> array indexed by <code>Calendar.SUNDAY</code>
 688:    * through <code>Calendar.SATURDAY</code>.  Note that the first element
 689:    * of this array is ignored.
 690:    *
 691:    * @return This list of weekday display strings.
 692:    */
 693:   public String[] getWeekdays ()
 694:   {
 695:     return weekdays;
 696:   }
 697: 
 698:   /**
 699:    * This method returns this list of localized timezone display strings.
 700:    * This is a two dimensional <code>String</code> array where each row in
 701:    * the array contains five values:
 702:    * <P>
 703:    * <ul>
 704:    * <li>0 - The non-localized time zone id string.</li>
 705:    * <li>1 - The long name of the time zone (standard time).</li>
 706:    * <li>2 - The short name of the time zone (standard time).</li>
 707:    * <li>3 - The long name of the time zone (daylight savings time).</li>
 708:    * <li>4 - the short name of the time zone (daylight savings time).</li>
 709:    * </ul>
 710:    * <p>
 711:    * If {@link #setZoneStrings(String[][])} has been called, then the value
 712:    * passed to this will be returned.  Otherwise the returned array contains
 713:    * zone names provided by the runtime environment and any
 714:    * {@link java.util.spi.TimeZoneProvider} instances.
 715:    * </p>
 716:    *
 717:    * @return The list of time zone display strings.
 718:    * @see #setZoneStrings(String[][])
 719:    */
 720:   public String[][] getZoneStrings()
 721:   {
 722:     if (zoneStrings != null)
 723:       return zoneStrings;
 724:     return runtimeZoneStrings;
 725:   }
 726: 
 727:   /**
 728:    * This method sets the list of strings used to display AM/PM values to
 729:    * the specified list.
 730:    * This is a two element <code>String</code> array indexed by
 731:    * <code>Calendar.AM</code> and <code>Calendar.PM</code>
 732:    *
 733:    * @param value The new list of AM/PM display strings.
 734:    */
 735:   public void setAmPmStrings (String[] value)
 736:   {
 737:     if(value==null)
 738:       throw new NullPointerException();
 739:     ampms = value;
 740:   }
 741: 
 742:   /**
 743:    * This method sets the list of strings used to display time eras to
 744:    * to the specified list.
 745:    * This is a two element <code>String</code>
 746:    * array indexed by <code>Calendar.BC</code> and <code>Calendar.AD</code>.
 747:    *
 748:    * @param labels The new list of era display strings.
 749:    */
 750:   public void setEras (String[] labels)
 751:   {
 752:     if(labels==null)
 753:       throw new NullPointerException();
 754:     eras = labels;
 755:   }
 756: 
 757:   /**
 758:     * This method sets the list of characters used to specific date/time
 759:     * formatting strings.
 760:     * This is an 18 character string that contains the characters
 761:     * that are used in creating the date formatting strings in
 762:     * <code>SimpleDateFormat</code>.   The following are the character
 763:     * positions in the string and which format character they correspond
 764:     * to (the character in parentheses is the default value in the US English
 765:     * locale):
 766:     * <p>
 767:     * <ul>
 768:     * <li>0 - era (G)</li>
 769:     * <li>1 - year (y)</li>
 770:     * <li>2 - month (M)</li>
 771:     * <li>3 - day of month (d)</li>
 772:     * <li>4 - hour out of 12, from 1-12 (h)</li>
 773:     * <li>5 - hour out of 24, from 0-23 (H)</li>
 774:     * <li>6 - minute (m)</li>
 775:     * <li>7 - second (s)</li>
 776:     * <li>8 - millisecond (S)</li>
 777:     * <li>9 - date of week (E)</li>
 778:     * <li>10 - date of year (D)</li>
 779:     * <li>11 - day of week in month, eg. "4th Thur in Nov" (F)</li>
 780:     * <li>12 - week in year (w)</li>
 781:     * <li>13 - week in month (W)</li>
 782:     * <li>14 - am/pm (a)</li>
 783:     * <li>15 - hour out of 24, from 1-24 (k)</li>
 784:     * <li>16 - hour out of 12, from 0-11 (K)</li>
 785:     * <li>17 - time zone (z)</li>
 786:     * </ul>
 787:     *
 788:     * @param chars The new format pattern characters
 789:     */
 790:   public void setLocalPatternChars (String chars)
 791:   {
 792:     if(chars==null)
 793:       throw new NullPointerException();
 794:     localPatternChars = chars;
 795:   }
 796: 
 797:   /**
 798:     * This method sets the list of strings used to display month names.
 799:     * This is a thirteen element
 800:     * string array indexed by <code>Calendar.JANUARY</code> through
 801:     * <code>Calendar.UNDECEMBER</code>.  Note that there are thirteen
 802:     * elements because some calendars have thriteen months.
 803:     *
 804:     * @param labels The list of month display strings.
 805:     */
 806:   public void setMonths (String[] labels)
 807:   {
 808:     if(labels==null)
 809:       throw new NullPointerException();
 810:     months = labels;
 811:   }
 812: 
 813:   /**
 814:    * This method sets the list of strings used to display abbreviated month
 815:    * names.
 816:    * This is a thirteen element
 817:    * <code>String</code> array indexed by <code>Calendar.JANUARY</code>
 818:    * through <code>Calendar.UNDECEMBER</code>.  Note that there are thirteen
 819:    * elements because some calendars have thirteen months.
 820:    *
 821:    * @param labels The new list of abbreviated month display strings.
 822:    */
 823:   public void setShortMonths (String[] labels)
 824:   {
 825:     if(labels==null)
 826:       throw new NullPointerException();
 827:     shortMonths = labels;
 828:   }
 829: 
 830:   /**
 831:    * This method sets the list of strings used to display abbreviated
 832:    * weekday names.
 833:    * This is an eight element
 834:    * <code>String</code> array indexed by <code>Calendar.SUNDAY</code>
 835:    * through <code>Calendar.SATURDAY</code>.  Note that the first element
 836:    * of this array is ignored.
 837:    *
 838:    * @param labels This list of abbreviated weekday display strings.
 839:    */
 840:   public void setShortWeekdays (String[] labels)
 841:   {
 842:     if(labels==null)
 843:       throw new NullPointerException();
 844:     shortWeekdays = labels;
 845:   }
 846: 
 847:   /**
 848:    * This method sets the list of strings used to display weekday names.
 849:    * This is an eight element
 850:    * <code>String</code> array indexed by <code>Calendar.SUNDAY</code>
 851:    * through <code>Calendar.SATURDAY</code>.  Note that the first element
 852:    * of this array is ignored.
 853:    *
 854:    * @param labels This list of weekday display strings.
 855:    */
 856:   public void setWeekdays (String[] labels)
 857:   {
 858:     if(labels==null)
 859:       throw new NullPointerException();
 860:     weekdays = labels;
 861:   }
 862: 
 863:   /**
 864:    * This method sets the list of display strings for time zones.
 865:    * This is a two dimensional <code>String</code> array where each row in
 866:    * the array contains five values:
 867:    * <P>
 868:    * <ul>
 869:    * <li>0 - The non-localized time zone id string.</li>
 870:    * <li>1 - The long name of the time zone (standard time).</li>
 871:    * <li>2 - The short name of the time zone (standard time).</li>
 872:    * <li>3 - The long name of the time zone (daylight savings time).</li>
 873:    * <li>4 - the short name of the time zone (daylight savings time).</li>
 874:    * </ul>
 875:    *
 876:    * @params zones The list of time zone display strings.
 877:    */
 878:   public void setZoneStrings (String[][] zones)
 879:   {
 880:     if(zones==null)
 881:       throw new NullPointerException();
 882:     zoneStrings = zones;
 883:   }
 884: 
 885:   /* Does a "deep" equality test - recurses into arrays. */
 886:   private static boolean equals (Object x, Object y)
 887:   {
 888:     if (x == y)
 889:       return true;
 890:     if (x == null || y == null)
 891:       return false;
 892:     if (! (x instanceof Object[]) || ! (y instanceof Object[]))
 893:       return x.equals(y);
 894:     Object[] xa = (Object[]) x;
 895:     Object[] ya = (Object[]) y;
 896:     if (xa.length != ya.length)
 897:       return false;
 898:     for (int i = xa.length;  --i >= 0; )
 899:       {
 900:         if (! equals(xa[i], ya[i]))
 901:           return false;
 902:       }
 903:     return true;
 904:   }
 905: 
 906:   private static int hashCode (Object x)
 907:   {
 908:     if (x == null)
 909:       return 0;
 910:     if (! (x instanceof Object[]))
 911:       return x.hashCode();
 912:     Object[] xa = (Object[]) x;
 913:     int hash = 0;
 914:     for (int i = 0;  i < xa.length;  i++)
 915:       hash = 37 * hashCode(xa[i]);
 916:     return hash;
 917:   }
 918: 
 919:   /**
 920:    * This method tests a specified object for equality against this object.
 921:    * This will be true if and only if the specified object:
 922:    * <p>
 923:    * <ul>
 924:    * <li> Is not <code>null</code>.</li>
 925:    * <li> Is an instance of <code>DateFormatSymbols</code>.</li>
 926:    * <li> Contains identical formatting symbols to this object.</li>
 927:    * </ul>
 928:    *
 929:    * @param obj The <code>Object</code> to test for equality against.
 930:    *
 931:    * @return <code>true</code> if the specified object is equal to this one,
 932:    * <code>false</code> otherwise.
 933:    */
 934:   public boolean equals (Object obj)
 935:   {
 936:     if (! (obj instanceof DateFormatSymbols))
 937:       return false;
 938:     DateFormatSymbols other = (DateFormatSymbols) obj;
 939:     return (equals(ampms, other.ampms)
 940:             && equals(eras, other.eras)
 941:             && equals(localPatternChars, other.localPatternChars)
 942:             && equals(months, other.months)
 943:             && equals(shortMonths, other.shortMonths)
 944:             && equals(shortWeekdays, other.shortWeekdays)
 945:             && equals(weekdays, other.weekdays)
 946:             && equals(zoneStrings, other.zoneStrings));
 947:   }
 948: 
 949:   /**
 950:    * Returns a new copy of this object.
 951:    *
 952:    * @return A copy of this object
 953:    */
 954:   public Object clone ()
 955:   {
 956:     try
 957:       {
 958:         return super.clone ();
 959:       }
 960:     catch (CloneNotSupportedException e)
 961:       {
 962:         return null;
 963:       }
 964:   }
 965: 
 966:   /**
 967:    * This method returns a hash value for this object.
 968:    *
 969:    * @return A hash value for this object.
 970:    */
 971:   public int hashCode ()
 972:   {
 973:     return (hashCode(ampms)
 974:             ^ hashCode(eras)
 975:             ^ hashCode(localPatternChars)
 976:             ^ hashCode(months)
 977:             ^ hashCode(shortMonths)
 978:             ^ hashCode(shortWeekdays)
 979:             ^ hashCode(weekdays)
 980:             ^ hashCode(zoneStrings));
 981:   }
 982: 
 983:   /**
 984:    * Returns a {@link DateFormatSymbols} instance for the
 985:    * default locale obtained from either the runtime itself
 986:    * or one of the installed
 987:    * {@link java.text.spi.DateFormatSymbolsProvider} instances.
 988:    * This is equivalent to calling
 989:    * <code>getInstance(Locale.getDefault())</code>.
 990:    *
 991:    * @return a {@link DateFormatSymbols} instance for the default
 992:    *         locale.
 993:    * @since 1.6
 994:    */
 995:   public static final DateFormatSymbols getInstance()
 996:   {
 997:     return getInstance(Locale.getDefault());
 998:   }
 999: 
1000:   /**
1001:    * Returns a {@link DateFormatSymbols} instance for the
1002:    * specified locale obtained from either the runtime itself
1003:    * or one of the installed
1004:    * {@link java.text.spi.DateFormatSymbolsProvider} instances.
1005:    *
1006:    * @param locale the locale for which an instance should be
1007:    *               returned.
1008:    * @return a {@link DateFormatSymbols} instance for the specified
1009:    *         locale.
1010:    * @throws NullPointerException if <code>locale</code> is
1011:    *                              <code>null</code>.
1012:    * @since 1.6
1013:    */
1014:   public static final DateFormatSymbols getInstance(Locale locale)
1015:   {
1016:     try
1017:       {
1018:         DateFormatSymbols syms = new DateFormatSymbols(locale);
1019:         return syms;
1020:       }
1021:     catch (MissingResourceException e)
1022:       {
1023:         /* This means runtime support for the locale
1024:          * is not available, so we check providers. */
1025:       }
1026:     for (DateFormatSymbolsProvider p :
1027:            ServiceLoader.load(DateFormatSymbolsProvider.class))
1028:       {
1029:         for (Locale loc : p.getAvailableLocales())
1030:           {
1031:             if (loc.equals(locale))
1032:               {
1033:                 DateFormatSymbols syms = p.getInstance(locale);
1034:                 if (syms != null)
1035:                   return syms;
1036:                 break;
1037:               }
1038:           }
1039:       }
1040:     return getInstance(LocaleHelper.getFallbackLocale(locale));
1041:   }
1042: 
1043: }