001/* DateFormatSymbols.java -- Format over a range of numbers
002   Copyright (C) 1998, 1999, 2000, 2001, 2003, 2005, 2006  Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038
039package java.text;
040
041import gnu.java.locale.LocaleHelper;
042
043import java.io.IOException;
044
045import java.text.spi.DateFormatSymbolsProvider;
046
047import java.util.ArrayList;
048import java.util.Arrays;
049import java.util.HashMap;
050import java.util.List;
051import java.util.Locale;
052import java.util.Map;
053import java.util.MissingResourceException;
054import java.util.Properties;
055import java.util.ResourceBundle;
056import java.util.ServiceLoader;
057import java.util.TimeZone;
058
059import java.util.concurrent.ConcurrentMap;
060import java.util.concurrent.ConcurrentHashMap;
061
062import java.util.regex.Pattern;
063
064import java.util.spi.TimeZoneNameProvider;
065
066/**
067 * This class acts as container for locale specific date/time formatting
068 * information such as the days of the week and the months of the year.
069 *
070 * @author Per Bothner (bothner@cygnus.com)
071 * @author Andrew John Hughes (gnu_andrew@member.fsf.org)
072 * @date October 24, 1998.
073 */
074/* Written using "Java Class Libraries", 2nd edition, ISBN 0-201-31002-3.
075 * Status:  Believed complete and correct.
076 */
077public class DateFormatSymbols implements java.io.Serializable, Cloneable
078{
079  /**
080   * The set of properties for obtaining the metazone data.
081   */
082  private static transient final Properties properties;
083
084  /**
085   * Reads in the properties.
086   */
087  static
088  {
089    properties = new Properties();
090    try
091      {
092        properties.load(DateFormatSymbols.class.getResourceAsStream("metazones.properties"));
093      }
094    catch (IOException exception)
095      {
096        System.out.println("Failed to load weeks resource: " + exception);
097      }
098  }
099
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}