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.spi.TimeZoneNameProvider;
060
061/**
062 * This class acts as container for locale specific date/time formatting
063 * information such as the days of the week and the months of the year.
064 *
065 * @author Per Bothner (bothner@cygnus.com)
066 * @author Andrew John Hughes (gnu_andrew@member.fsf.org)
067 * @date October 24, 1998.
068 */
069/* Written using "Java Class Libraries", 2nd edition, ISBN 0-201-31002-3.
070 * Status:  Believed complete and correct.
071 */
072public class DateFormatSymbols implements java.io.Serializable, Cloneable
073{
074  String[] ampms;
075  String[] eras;
076  private String localPatternChars;
077  String[] months;
078  String[] shortMonths;
079  String[] shortWeekdays;
080  String[] weekdays;
081
082  /**
083   * The set of properties for obtaining the metazone data.
084   */
085  private static transient final Properties properties;
086
087  /**
088   * Reads in the properties.
089   */
090  static
091  {
092    properties = new Properties();
093    try
094      {
095        properties.load(DateFormatSymbols.class.getResourceAsStream("metazones.properties"));
096      }
097    catch (IOException exception)
098      {
099        System.out.println("Failed to load weeks resource: " + exception);
100      }
101  }
102
103  /**
104   * The timezone strings supplied by the runtime.
105   */
106  private String[][] runtimeZoneStrings;
107
108  /**
109   * Custom timezone strings supplied by {@link #setZoneStrings()}.
110   */
111  private String[][] zoneStrings;
112
113  private static final long serialVersionUID = -5987973545549424702L;
114
115  // The order of these prefixes must be the same as in DateFormat
116  private static final String[] formatPrefixes =
117  {
118    "full", "long", "medium", "short"
119  };
120
121  // These are each arrays with a value for SHORT, MEDIUM, LONG, FULL,
122  // and DEFAULT (constants defined in java.text.DateFormat).  While
123  // not part of the official spec, we need a way to get at locale-specific
124  // default formatting patterns.  They are declared package scope so
125  // as to be easily accessible where needed (DateFormat, SimpleDateFormat).
126  transient String[] dateFormats;
127  transient String[] timeFormats;
128
129  /**
130   * Compiles a string array for a property using data from each of the locales in the
131   * hierarchy as necessary.
132   *
133   * @param bundles the locale hierarchy, starting with the most specific.
134   * @param name the name of the property.
135   * @param size the size the array should be when complete.
136   * @return a completed string array.
137   */
138  private static String[] getStringArray(List<ResourceBundle> bundles, String name, int size)
139  {
140    return getStringArray(bundles, name, size, null);
141  }
142
143  /**
144   * Compiles a string array for a property using data from each of the locales in the
145   * hierarchy as necessary.  If non-null, the fallback array is also used for "sideways"
146   * inheritance (e.g. if there is no short name for a month, the long name is used rather
147   * than the empty string).
148   *
149   * @param bundles the locale hierarchy, starting with the most specific.
150   * @param name the name of the property.
151   * @param size the size the array should be when complete.
152   * @param fallback an array of long name fallback strings for data with both long and short names.
153   * @return a completed string array.
154   */
155  private static String[] getStringArray(List<ResourceBundle> bundles, String name, int size,
156                                         String[] fallback)
157  {
158    String[] data = new String[size];
159    Arrays.fill(data, "");
160    // Populate array with data from each locale back to the root, starting with the most specific
161    for (int a = 0; a < bundles.size(); ++a)
162      {
163        String localeData = bundles.get(a).getString(name);
164        String[] array = localeData.split("\u00ae", size);
165        for (int b = 0; b < data.length; ++b)
166          {
167            if (array.length > b && array[b] != null && data[b].isEmpty() && !array[b].isEmpty())
168              data[b] = array[b];
169          }
170      }
171    // Replace any remaining empty strings with data from the fallback array, if non-null
172    if (fallback != null && fallback.length == size)
173      {
174        for (int a = 0; a < data.length; ++a)
175          {
176            if (data[a].isEmpty() && fallback[a] != null && !fallback[a].isEmpty())
177              data[a] = fallback[a];
178          }
179      }
180    return data;
181  }
182
183  private String[][] getZoneStrings(ResourceBundle res, Locale locale)
184  {
185    List<String[]> allZones = new ArrayList<String[]>();
186    try
187      {
188        Map<String,String[]> systemZones = new HashMap<String,String[]>();
189        while (true)
190          {
191            int index = 0;
192            String country = locale.getCountry();
193            String data = res.getString("zoneStrings");
194            String[] zones = data.split("\u00a9");
195            for (int a = 0; a < zones.length; ++a)
196              {
197                String[] strings = zones[a].split("\u00ae");
198                String type = properties.getProperty(strings[0] + "." + country);
199                if (type == null)
200                  type = properties.getProperty(strings[0] + ".DEFAULT");
201                if (type != null)
202                  strings[0] = type;
203                if (strings.length < 5)
204                  {
205                    String[] newStrings = new String[5];
206                    System.arraycopy(strings, 0, newStrings, 0, strings.length);
207                    for (int b = strings.length; b < newStrings.length; ++b)
208                      newStrings[b] = "";
209                    strings = newStrings;
210                  }
211                String[] existing = systemZones.get(strings[0]);
212                if (existing != null && existing.length > 1)
213                  {
214                    for (int b = 1; b < existing.length; ++b)
215                      if (!existing[b].equals(""))
216                        strings[b] = existing[b];
217                  }
218                systemZones.put(strings[0], strings);
219              }
220            if (res.getLocale() == Locale.ROOT)
221              break;
222            else
223              res = ResourceBundle.getBundle("gnu.java.locale.LocaleInformation",
224                                             LocaleHelper.getFallbackLocale(res.getLocale()),
225                                             ClassLoader.getSystemClassLoader());
226          }
227        /* Final sanity check for missing values */
228        for (String[] zstrings : systemZones.values())
229          {
230            if (zstrings[1].equals("") && zstrings[2].equals(""))
231              {
232                for (Map.Entry<Object,Object> entry : properties.entrySet())
233                  {
234                    String val = (String) entry.getValue();
235                    if (val.equals(zstrings[0]))
236                      {
237                        String key = (String) entry.getKey();
238                        String metazone = key.substring(0, key.indexOf("."));
239                        String type = properties.getProperty(metazone + "." + locale.getCountry());
240                        if (type == null)
241                          type = properties.getProperty(metazone + ".DEFAULT");
242                        if (type != null)
243                          {
244                            String[] ostrings = systemZones.get(type);
245                            zstrings[1] = ostrings[1];
246                            zstrings[2] = ostrings[2];
247                          }
248                      }
249                  }
250              }
251          }
252        allZones.addAll(systemZones.values());
253      }
254    catch (MissingResourceException e)
255      {
256        /* This means runtime support for the locale
257         * is not available, so we just include providers. */
258      }
259    for (TimeZoneNameProvider p :
260           ServiceLoader.load(TimeZoneNameProvider.class))
261      {
262        for (Locale loc : p.getAvailableLocales())
263          {
264            if (loc.equals(locale))
265              {
266                for (String id : TimeZone.getAvailableIDs())
267                  {
268                    String[] z = new String[5];
269                    z[0] = id;
270                    z[1] = p.getDisplayName(id, false,
271                                            TimeZone.LONG,
272                                            locale);
273                    z[2] = p.getDisplayName(id, false,
274                                            TimeZone.SHORT,
275                                            locale);
276                    z[3] = p.getDisplayName(id, true,
277                                            TimeZone.LONG,
278                                            locale);
279                    z[4] = p.getDisplayName(id, true,
280                                            TimeZone.SHORT,
281                                            locale);
282                    allZones.add(z);
283                  }
284                break;
285              }
286          }
287      }
288    return allZones.toArray(new String[allZones.size()][]);
289  }
290
291  private String[] formatsForKey(ResourceBundle res, String key)
292  {
293    String[] values = new String[formatPrefixes.length];
294
295    for (int i = 0; i < formatPrefixes.length; i++)
296      values[i] = res.getString(formatPrefixes[i] + key);
297
298    return values;
299  }
300
301  /**
302   * This method initializes a new instance of <code>DateFormatSymbols</code>
303   * by loading the date format information for the specified locale.
304   * This constructor only obtains instances using the runtime's resources;
305   * to also include {@link java.text.spi.DateFormatSymbolsProvider} instances,
306   * call {@link #getInstance(java.util.Locale)} instead.
307   *
308   * @param locale The locale for which date formatting symbols should
309   *               be loaded.
310   * @throws MissingResourceException if the resources for the specified
311   *                                  locale could not be found or loaded.
312   * @see #getInstance(java.util.Locale)
313   */
314  public DateFormatSymbols (Locale locale)
315    throws MissingResourceException
316  {
317    ClassLoader ldr = ClassLoader.getSystemClassLoader();
318    List<ResourceBundle> bundles = new ArrayList<ResourceBundle>();
319    ResourceBundle res
320      = ResourceBundle.getBundle("gnu.java.locale.LocaleInformation", locale, ldr);
321    bundles.add(res);
322    Locale resLocale = res.getLocale();
323    while (resLocale != Locale.ROOT)
324      {
325        res = ResourceBundle.getBundle("gnu.java.locale.LocaleInformation",
326                                       LocaleHelper.getFallbackLocale(resLocale), ldr);
327        bundles.add(res);
328        resLocale = res.getLocale();
329      }
330    ampms = getStringArray(bundles, "ampms", 2);
331    eras = getStringArray(bundles, "eras", 2);
332    localPatternChars = res.getString("localPatternChars");
333    months = getStringArray(bundles, "months", 13);
334    shortMonths = getStringArray(bundles, "shortMonths", 13, months);
335    weekdays = getStringArray(bundles, "weekdays", 8);
336    shortWeekdays = getStringArray(bundles, "shortWeekdays", 8, weekdays);
337    dateFormats = formatsForKey(res, "DateFormat");
338    timeFormats = formatsForKey(res, "TimeFormat");
339    runtimeZoneStrings = getZoneStrings(res, locale);
340  }
341
342  /**
343   * This method loads the format symbol information for the default
344   * locale. This constructor only obtains instances using the runtime's resources;
345   * to also include {@link java.text.spi.DateFormatSymbolsProvider} instances,
346   * call {@link #getInstance()} instead.
347   *
348   * @throws MissingResourceException if the resources for the default
349   *                                  locale could not be found or loaded.
350   * @see #getInstance()
351   */
352  public DateFormatSymbols()
353    throws MissingResourceException
354  {
355    this (Locale.getDefault());
356  }
357
358  /**
359   * This method returns the list of strings used for displaying AM or PM.
360   * This is a two element <code>String</code> array indexed by
361   * <code>Calendar.AM</code> and <code>Calendar.PM</code>
362   *
363   * @return The list of AM/PM display strings.
364   */
365  public String[] getAmPmStrings()
366  {
367    return ampms;
368  }
369
370  /**
371    * This method returns the list of strings used for displaying eras
372    * (e.g., "BC" and "AD").  This is a two element <code>String</code>
373    * array indexed by <code>Calendar.BC</code> and <code>Calendar.AD</code>.
374    *
375    * @return The list of era disply strings.
376    */
377  public String[] getEras()
378  {
379    return eras;
380  }
381
382  /**
383    * This method returns the pattern character information for this
384    * object.  This is an 18 character string that contains the characters
385    * that are used in creating the date formatting strings in
386    * <code>SimpleDateFormat</code>.   The following are the character
387    * positions in the string and which format character they correspond
388    * to (the character in parentheses is the default value in the US English
389    * locale):
390    * <p>
391    * <ul>
392    * <li>0 - era (G)</li>
393    * <li>1 - year (y)</li>
394    * <li>2 - month (M)</li>
395    * <li>3 - day of month (d)</li>
396    * <li>4 - hour out of 12, from 1-12 (h)</li>
397    * <li>5 - hour out of 24, from 0-23 (H)</li>
398    * <li>6 - minute (m)</li>
399    * <li>7 - second (s)</li>
400    * <li>8 - millisecond (S)</li>
401    * <li>9 - date of week (E)</li>
402    * <li>10 - date of year (D)</li>
403    * <li>11 - day of week in month, eg. "4th Thur in Nov" (F)</li>
404    * <li>12 - week in year (w)</li>
405    * <li>13 - week in month (W)</li>
406    * <li>14 - am/pm (a)</li>
407    * <li>15 - hour out of 24, from 1-24 (k)</li>
408    * <li>16 - hour out of 12, from 0-11 (K)</li>
409    * <li>17 - time zone (z)</li>
410    * </ul>
411    *
412    * @return The format patter characters
413    */
414  public String getLocalPatternChars()
415  {
416    return localPatternChars;
417  }
418
419  /**
420   * This method returns the list of strings used for displaying month
421   * names (e.g., "January" and "February").  This is a thirteen element
422   * string array indexed by <code>Calendar.JANUARY</code> through
423   * <code>Calendar.UNDECEMBER</code>.  Note that there are thirteen
424   * elements because some calendars have thriteen months.
425   *
426   * @return The list of month display strings.
427   */
428  public String[] getMonths ()
429  {
430    return months;
431  }
432
433  /**
434   * This method returns the list of strings used for displaying abbreviated
435   * month names (e.g., "Jan" and "Feb").  This is a thirteen element
436   * <code>String</code> array indexed by <code>Calendar.JANUARY</code>
437   * through <code>Calendar.UNDECEMBER</code>.  Note that there are thirteen
438   * elements because some calendars have thirteen months.
439   *
440   * @return The list of abbreviated month display strings.
441   */
442  public String[] getShortMonths ()
443  {
444    return shortMonths;
445  }
446
447  /**
448   * This method returns the list of strings used for displaying abbreviated
449   * weekday names (e.g., "Sun" and "Mon").  This is an eight element
450   * <code>String</code> array indexed by <code>Calendar.SUNDAY</code>
451   * through <code>Calendar.SATURDAY</code>.  Note that the first element
452   * of this array is ignored.
453   *
454   * @return This list of abbreviated weekday display strings.
455   */
456  public String[] getShortWeekdays ()
457  {
458    return shortWeekdays;
459  }
460
461  /**
462   * This method returns the list of strings used for displaying weekday
463   * names (e.g., "Sunday" and "Monday").  This is an eight element
464   * <code>String</code> array indexed by <code>Calendar.SUNDAY</code>
465   * through <code>Calendar.SATURDAY</code>.  Note that the first element
466   * of this array is ignored.
467   *
468   * @return This list of weekday display strings.
469   */
470  public String[] getWeekdays ()
471  {
472    return weekdays;
473  }
474
475  /**
476   * This method returns this list of localized timezone display strings.
477   * This is a two dimensional <code>String</code> array where each row in
478   * the array contains five values:
479   * <P>
480   * <ul>
481   * <li>0 - The non-localized time zone id string.</li>
482   * <li>1 - The long name of the time zone (standard time).</li>
483   * <li>2 - The short name of the time zone (standard time).</li>
484   * <li>3 - The long name of the time zone (daylight savings time).</li>
485   * <li>4 - the short name of the time zone (daylight savings time).</li>
486   * </ul>
487   * <p>
488   * If {@link #setZoneStrings(String[][])} has been called, then the value
489   * passed to this will be returned.  Otherwise the returned array contains
490   * zone names provided by the runtime environment and any
491   * {@link java.util.spi.TimeZoneProvider} instances.
492   * </p>
493   *
494   * @return The list of time zone display strings.
495   * @see #setZoneStrings(String[][])
496   */
497  public String[][] getZoneStrings()
498  {
499    if (zoneStrings != null)
500      return zoneStrings;
501    return runtimeZoneStrings;
502  }
503
504  /**
505   * This method sets the list of strings used to display AM/PM values to
506   * the specified list.
507   * This is a two element <code>String</code> array indexed by
508   * <code>Calendar.AM</code> and <code>Calendar.PM</code>
509   *
510   * @param value The new list of AM/PM display strings.
511   */
512  public void setAmPmStrings (String[] value)
513  {
514    if(value==null)
515      throw new NullPointerException();
516    ampms = value;
517  }
518
519  /**
520   * This method sets the list of strings used to display time eras to
521   * to the specified list.
522   * This is a two element <code>String</code>
523   * array indexed by <code>Calendar.BC</code> and <code>Calendar.AD</code>.
524   *
525   * @param labels The new list of era display strings.
526   */
527  public void setEras (String[] labels)
528  {
529    if(labels==null)
530      throw new NullPointerException();
531    eras = labels;
532  }
533
534  /**
535    * This method sets the list of characters used to specific date/time
536    * formatting strings.
537    * This is an 18 character string that contains the characters
538    * that are used in creating the date formatting strings in
539    * <code>SimpleDateFormat</code>.   The following are the character
540    * positions in the string and which format character they correspond
541    * to (the character in parentheses is the default value in the US English
542    * locale):
543    * <p>
544    * <ul>
545    * <li>0 - era (G)</li>
546    * <li>1 - year (y)</li>
547    * <li>2 - month (M)</li>
548    * <li>3 - day of month (d)</li>
549    * <li>4 - hour out of 12, from 1-12 (h)</li>
550    * <li>5 - hour out of 24, from 0-23 (H)</li>
551    * <li>6 - minute (m)</li>
552    * <li>7 - second (s)</li>
553    * <li>8 - millisecond (S)</li>
554    * <li>9 - date of week (E)</li>
555    * <li>10 - date of year (D)</li>
556    * <li>11 - day of week in month, eg. "4th Thur in Nov" (F)</li>
557    * <li>12 - week in year (w)</li>
558    * <li>13 - week in month (W)</li>
559    * <li>14 - am/pm (a)</li>
560    * <li>15 - hour out of 24, from 1-24 (k)</li>
561    * <li>16 - hour out of 12, from 0-11 (K)</li>
562    * <li>17 - time zone (z)</li>
563    * </ul>
564    *
565    * @param chars The new format pattern characters
566    */
567  public void setLocalPatternChars (String chars)
568  {
569    if(chars==null)
570      throw new NullPointerException();
571    localPatternChars = chars;
572  }
573
574  /**
575    * This method sets the list of strings used to display month names.
576    * This is a thirteen element
577    * string array indexed by <code>Calendar.JANUARY</code> through
578    * <code>Calendar.UNDECEMBER</code>.  Note that there are thirteen
579    * elements because some calendars have thriteen months.
580    *
581    * @param labels The list of month display strings.
582    */
583  public void setMonths (String[] labels)
584  {
585    if(labels==null)
586      throw new NullPointerException();
587    months = labels;
588  }
589
590  /**
591   * This method sets the list of strings used to display abbreviated month
592   * names.
593   * This is a thirteen element
594   * <code>String</code> array indexed by <code>Calendar.JANUARY</code>
595   * through <code>Calendar.UNDECEMBER</code>.  Note that there are thirteen
596   * elements because some calendars have thirteen months.
597   *
598   * @param labels The new list of abbreviated month display strings.
599   */
600  public void setShortMonths (String[] labels)
601  {
602    if(labels==null)
603      throw new NullPointerException();
604    shortMonths = labels;
605  }
606
607  /**
608   * This method sets the list of strings used to display abbreviated
609   * weekday names.
610   * This is an eight element
611   * <code>String</code> array indexed by <code>Calendar.SUNDAY</code>
612   * through <code>Calendar.SATURDAY</code>.  Note that the first element
613   * of this array is ignored.
614   *
615   * @param labels This list of abbreviated weekday display strings.
616   */
617  public void setShortWeekdays (String[] labels)
618  {
619    if(labels==null)
620      throw new NullPointerException();
621    shortWeekdays = labels;
622  }
623
624  /**
625   * This method sets the list of strings used to display weekday names.
626   * This is an eight element
627   * <code>String</code> array indexed by <code>Calendar.SUNDAY</code>
628   * through <code>Calendar.SATURDAY</code>.  Note that the first element
629   * of this array is ignored.
630   *
631   * @param labels This list of weekday display strings.
632   */
633  public void setWeekdays (String[] labels)
634  {
635    if(labels==null)
636      throw new NullPointerException();
637    weekdays = labels;
638  }
639
640  /**
641   * This method sets the list of display strings for time zones.
642   * This is a two dimensional <code>String</code> array where each row in
643   * the array contains five values:
644   * <P>
645   * <ul>
646   * <li>0 - The non-localized time zone id string.</li>
647   * <li>1 - The long name of the time zone (standard time).</li>
648   * <li>2 - The short name of the time zone (standard time).</li>
649   * <li>3 - The long name of the time zone (daylight savings time).</li>
650   * <li>4 - the short name of the time zone (daylight savings time).</li>
651   * </ul>
652   *
653   * @params zones The list of time zone display strings.
654   */
655  public void setZoneStrings (String[][] zones)
656  {
657    if(zones==null)
658      throw new NullPointerException();
659    zoneStrings = zones;
660  }
661
662  /* Does a "deep" equality test - recurses into arrays. */
663  private static boolean equals (Object x, Object y)
664  {
665    if (x == y)
666      return true;
667    if (x == null || y == null)
668      return false;
669    if (! (x instanceof Object[]) || ! (y instanceof Object[]))
670      return x.equals(y);
671    Object[] xa = (Object[]) x;
672    Object[] ya = (Object[]) y;
673    if (xa.length != ya.length)
674      return false;
675    for (int i = xa.length;  --i >= 0; )
676      {
677        if (! equals(xa[i], ya[i]))
678          return false;
679      }
680    return true;
681  }
682
683  private static int hashCode (Object x)
684  {
685    if (x == null)
686      return 0;
687    if (! (x instanceof Object[]))
688      return x.hashCode();
689    Object[] xa = (Object[]) x;
690    int hash = 0;
691    for (int i = 0;  i < xa.length;  i++)
692      hash = 37 * hashCode(xa[i]);
693    return hash;
694  }
695
696  /**
697   * This method tests a specified object for equality against this object.
698   * This will be true if and only if the specified object:
699   * <p>
700   * <ul>
701   * <li> Is not <code>null</code>.</li>
702   * <li> Is an instance of <code>DateFormatSymbols</code>.</li>
703   * <li> Contains identical formatting symbols to this object.</li>
704   * </ul>
705   *
706   * @param obj The <code>Object</code> to test for equality against.
707   *
708   * @return <code>true</code> if the specified object is equal to this one,
709   * <code>false</code> otherwise.
710   */
711  public boolean equals (Object obj)
712  {
713    if (! (obj instanceof DateFormatSymbols))
714      return false;
715    DateFormatSymbols other = (DateFormatSymbols) obj;
716    return (equals(ampms, other.ampms)
717            && equals(eras, other.eras)
718            && equals(localPatternChars, other.localPatternChars)
719            && equals(months, other.months)
720            && equals(shortMonths, other.shortMonths)
721            && equals(shortWeekdays, other.shortWeekdays)
722            && equals(weekdays, other.weekdays)
723            && equals(zoneStrings, other.zoneStrings));
724  }
725
726  /**
727   * Returns a new copy of this object.
728   *
729   * @return A copy of this object
730   */
731  public Object clone ()
732  {
733    try
734      {
735        return super.clone ();
736      }
737    catch (CloneNotSupportedException e)
738      {
739        return null;
740      }
741  }
742
743  /**
744   * This method returns a hash value for this object.
745   *
746   * @return A hash value for this object.
747   */
748  public int hashCode ()
749  {
750    return (hashCode(ampms)
751            ^ hashCode(eras)
752            ^ hashCode(localPatternChars)
753            ^ hashCode(months)
754            ^ hashCode(shortMonths)
755            ^ hashCode(shortWeekdays)
756            ^ hashCode(weekdays)
757            ^ hashCode(zoneStrings));
758  }
759
760  /**
761   * Returns a {@link DateFormatSymbols} instance for the
762   * default locale obtained from either the runtime itself
763   * or one of the installed
764   * {@link java.text.spi.DateFormatSymbolsProvider} instances.
765   * This is equivalent to calling
766   * <code>getInstance(Locale.getDefault())</code>.
767   *
768   * @return a {@link DateFormatSymbols} instance for the default
769   *         locale.
770   * @since 1.6
771   */
772  public static final DateFormatSymbols getInstance()
773  {
774    return getInstance(Locale.getDefault());
775  }
776
777  /**
778   * Returns a {@link DateFormatSymbols} instance for the
779   * specified locale obtained from either the runtime itself
780   * or one of the installed
781   * {@link java.text.spi.DateFormatSymbolsProvider} instances.
782   *
783   * @param locale the locale for which an instance should be
784   *               returned.
785   * @return a {@link DateFormatSymbols} instance for the specified
786   *         locale.
787   * @throws NullPointerException if <code>locale</code> is
788   *                              <code>null</code>.
789   * @since 1.6
790   */
791  public static final DateFormatSymbols getInstance(Locale locale)
792  {
793    try
794      {
795        DateFormatSymbols syms = new DateFormatSymbols(locale);
796        return syms;
797      }
798    catch (MissingResourceException e)
799      {
800        /* This means runtime support for the locale
801         * is not available, so we check providers. */
802      }
803    for (DateFormatSymbolsProvider p :
804           ServiceLoader.load(DateFormatSymbolsProvider.class))
805      {
806        for (Locale loc : p.getAvailableLocales())
807          {
808            if (loc.equals(locale))
809              {
810                DateFormatSymbols syms = p.getInstance(locale);
811                if (syms != null)
812                  return syms;
813                break;
814              }
815          }
816      }
817    return getInstance(LocaleHelper.getFallbackLocale(locale));
818  }
819
820}