DateFormatSymbols.java revision af18bc9973eca35fc0d4db6e240323f38c9d7a91
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved.
4 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5 *
6 * This code is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License version 2 only, as
8 * published by the Free Software Foundation.  Oracle designates this
9 * particular file as subject to the "Classpath" exception as provided
10 * by Oracle in the LICENSE file that accompanied this code.
11 *
12 * This code is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
15 * version 2 for more details (a copy is included in the LICENSE file that
16 * accompanied this code).
17 *
18 * You should have received a copy of the GNU General Public License version
19 * 2 along with this work; if not, write to the Free Software Foundation,
20 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
21 *
22 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
23 * or visit www.oracle.com if you need additional information or have any
24 * questions.
25 */
26
27/*
28 * (C) Copyright Taligent, Inc. 1996 - All Rights Reserved
29 * (C) Copyright IBM Corp. 1996 - All Rights Reserved
30 *
31 *   The original version of this source code and documentation is copyrighted
32 * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These
33 * materials are provided under terms of a License Agreement between Taligent
34 * and Sun. This technology is protected by multiple US and International
35 * patents. This notice and attribution to Taligent may not be removed.
36 *   Taligent is a registered trademark of Taligent, Inc.
37 *
38 */
39
40package java.text;
41
42import java.io.IOException;
43import java.io.ObjectInputStream;
44import java.io.ObjectOutputStream;
45import java.io.Serializable;
46import java.lang.ref.SoftReference;
47import java.text.spi.DateFormatSymbolsProvider;
48import java.util.Arrays;
49import java.util.Locale;
50import java.util.TimeZone;
51import java.util.concurrent.ConcurrentHashMap;
52import java.util.concurrent.ConcurrentMap;
53
54import libcore.icu.LocaleData;
55import libcore.icu.TimeZoneNames;
56import sun.util.LocaleServiceProviderPool;
57
58/**
59 * <code>DateFormatSymbols</code> is a public class for encapsulating
60 * localizable date-time formatting data, such as the names of the
61 * months, the names of the days of the week, and the time zone data.
62 * <code>SimpleDateFormat</code> uses
63 * <code>DateFormatSymbols</code> to encapsulate this information.
64 *
65 * <p>
66 * Typically you shouldn't use <code>DateFormatSymbols</code> directly.
67 * Rather, you are encouraged to create a date-time formatter with the
68 * <code>DateFormat</code> class's factory methods: <code>getTimeInstance</code>,
69 * <code>getDateInstance</code>, or <code>getDateTimeInstance</code>.
70 * These methods automatically create a <code>DateFormatSymbols</code> for
71 * the formatter so that you don't have to. After the
72 * formatter is created, you may modify its format pattern using the
73 * <code>setPattern</code> method. For more information about
74 * creating formatters using <code>DateFormat</code>'s factory methods,
75 * see {@link DateFormat}.
76 *
77 * <p>
78 * If you decide to create a date-time formatter with a specific
79 * format pattern for a specific locale, you can do so with:
80 * <blockquote>
81 * <pre>
82 * new SimpleDateFormat(aPattern, DateFormatSymbols.getInstance(aLocale)).
83 * </pre>
84 * </blockquote>
85 *
86 * <p>
87 * <code>DateFormatSymbols</code> objects are cloneable. When you obtain
88 * a <code>DateFormatSymbols</code> object, feel free to modify the
89 * date-time formatting data. For instance, you can replace the localized
90 * date-time format pattern characters with the ones that you feel easy
91 * to remember. Or you can change the representative cities
92 * to your favorite ones.
93 *
94 * <p>
95 * New <code>DateFormatSymbols</code> subclasses may be added to support
96 * <code>SimpleDateFormat</code> for date-time formatting for additional locales.
97
98 * @see          DateFormat
99 * @see          SimpleDateFormat
100 * @see          java.util.SimpleTimeZone
101 * @author       Chen-Lieh Huang
102 */
103public class DateFormatSymbols implements Serializable, Cloneable {
104
105    /**
106     * Construct a DateFormatSymbols object by loading format data from
107     * resources for the default {@link java.util.Locale.Category#FORMAT FORMAT}
108     * locale. This constructor can only
109     * construct instances for the locales supported by the Java
110     * runtime environment, not for those supported by installed
111     * {@link java.text.spi.DateFormatSymbolsProvider DateFormatSymbolsProvider}
112     * implementations. For full locale coverage, use the
113     * {@link #getInstance(Locale) getInstance} method.
114     * <p>This is equivalent to calling
115     * {@link #DateFormatSymbols(Locale)
116     *     DateFormatSymbols(Locale.getDefault(Locale.Category.FORMAT))}.
117     * @see #getInstance()
118     * @see java.util.Locale#getDefault(java.util.Locale.Category)
119     * @see java.util.Locale.Category#FORMAT
120     * @exception  java.util.MissingResourceException
121     *             if the resources for the default locale cannot be
122     *             found or cannot be loaded.
123     */
124    public DateFormatSymbols()
125    {
126        initializeData(Locale.getDefault(Locale.Category.FORMAT));
127    }
128
129    /**
130     * Construct a DateFormatSymbols object by loading format data from
131     * resources for the given locale. This constructor can only
132     * construct instances for the locales supported by the Java
133     * runtime environment, not for those supported by installed
134     * {@link java.text.spi.DateFormatSymbolsProvider DateFormatSymbolsProvider}
135     * implementations. For full locale coverage, use the
136     * {@link #getInstance(Locale) getInstance} method.
137     *
138     * @param locale the desired locale
139     * @see #getInstance(Locale)
140     * @exception  java.util.MissingResourceException
141     *             if the resources for the specified locale cannot be
142     *             found or cannot be loaded.
143     */
144    public DateFormatSymbols(Locale locale)
145    {
146        initializeData(locale);
147    }
148
149    /**
150     * Era strings. For example: "AD" and "BC".  An array of 2 strings,
151     * indexed by <code>Calendar.BC</code> and <code>Calendar.AD</code>.
152     * @serial
153     */
154    String eras[] = null;
155
156    /**
157     * Month strings. For example: "January", "February", etc.  An array
158     * of 13 strings (some calendars have 13 months), indexed by
159     * <code>Calendar.JANUARY</code>, <code>Calendar.FEBRUARY</code>, etc.
160     * @serial
161     */
162    String months[] = null;
163
164    /**
165     * Short month strings. For example: "Jan", "Feb", etc.  An array of
166     * 13 strings (some calendars have 13 months), indexed by
167     * <code>Calendar.JANUARY</code>, <code>Calendar.FEBRUARY</code>, etc.
168
169     * @serial
170     */
171    String shortMonths[] = null;
172
173    /**
174     * Weekday strings. For example: "Sunday", "Monday", etc.  An array
175     * of 8 strings, indexed by <code>Calendar.SUNDAY</code>,
176     * <code>Calendar.MONDAY</code>, etc.
177     * The element <code>weekdays[0]</code> is ignored.
178     * @serial
179     */
180    String weekdays[] = null;
181
182    /**
183     * Short weekday strings. For example: "Sun", "Mon", etc.  An array
184     * of 8 strings, indexed by <code>Calendar.SUNDAY</code>,
185     * <code>Calendar.MONDAY</code>, etc.
186     * The element <code>shortWeekdays[0]</code> is ignored.
187     * @serial
188     */
189    String shortWeekdays[] = null;
190
191    /**
192     * AM and PM strings. For example: "AM" and "PM".  An array of
193     * 2 strings, indexed by <code>Calendar.AM</code> and
194     * <code>Calendar.PM</code>.
195     * @serial
196     */
197    String ampms[] = null;
198
199    /**
200     * Localized names of time zones in this locale.  This is a
201     * two-dimensional array of strings of size <em>n</em> by <em>m</em>,
202     * where <em>m</em> is at least 5.  Each of the <em>n</em> rows is an
203     * entry containing the localized names for a single <code>TimeZone</code>.
204     * Each such row contains (with <code>i</code> ranging from
205     * 0..<em>n</em>-1):
206     * <ul>
207     * <li><code>zoneStrings[i][0]</code> - time zone ID</li>
208     * <li><code>zoneStrings[i][1]</code> - long name of zone in standard
209     * time</li>
210     * <li><code>zoneStrings[i][2]</code> - short name of zone in
211     * standard time</li>
212     * <li><code>zoneStrings[i][3]</code> - long name of zone in daylight
213     * saving time</li>
214     * <li><code>zoneStrings[i][4]</code> - short name of zone in daylight
215     * saving time</li>
216     * </ul>
217     * The zone ID is <em>not</em> localized; it's one of the valid IDs of
218     * the {@link java.util.TimeZone TimeZone} class that are not
219     * <a href="../java/util/TimeZone.html#CustomID">custom IDs</a>.
220     * All other entries are localized names.
221     * @see java.util.TimeZone
222     * @serial
223     */
224    String zoneStrings[][] = null;
225
226    /**
227     * Indicates that zoneStrings is set externally with setZoneStrings() method.
228     */
229    transient boolean isZoneStringsSet = false;
230
231    /**
232     * Unlocalized date-time pattern characters. For example: 'y', 'd', etc.
233     * All locales use the same these unlocalized pattern characters.
234     *
235     * Pretend to support 'L' and 'c' for now. It's meant for standalone weekday and
236     * month names, but we just use the non-standalone versions for now.
237     */
238    static final String  patternChars = "GyMdkHmsSEDFwWahKzZYuXLc";
239
240    static final int PATTERN_ERA                  =  0; // G
241    static final int PATTERN_YEAR                 =  1; // y
242    static final int PATTERN_MONTH                =  2; // M
243    static final int PATTERN_DAY_OF_MONTH         =  3; // d
244    static final int PATTERN_HOUR_OF_DAY1         =  4; // k
245    static final int PATTERN_HOUR_OF_DAY0         =  5; // H
246    static final int PATTERN_MINUTE               =  6; // m
247    static final int PATTERN_SECOND               =  7; // s
248    static final int PATTERN_MILLISECOND          =  8; // S
249    static final int PATTERN_DAY_OF_WEEK          =  9; // E
250    static final int PATTERN_DAY_OF_YEAR          = 10; // D
251    static final int PATTERN_DAY_OF_WEEK_IN_MONTH = 11; // F
252    static final int PATTERN_WEEK_OF_YEAR         = 12; // w
253    static final int PATTERN_WEEK_OF_MONTH        = 13; // W
254    static final int PATTERN_AM_PM                = 14; // a
255    static final int PATTERN_HOUR1                = 15; // h
256    static final int PATTERN_HOUR0                = 16; // K
257    static final int PATTERN_ZONE_NAME            = 17; // z
258    static final int PATTERN_ZONE_VALUE           = 18; // Z
259    static final int PATTERN_WEEK_YEAR            = 19; // Y
260    static final int PATTERN_ISO_DAY_OF_WEEK      = 20; // u
261    static final int PATTERN_ISO_ZONE             = 21; // X
262    static final int PATTERN_MONTH_STANDALONE     = 22; // L
263    static final int PATTERN_STANDALONE_DAY_OF_WEEK = 23; // c
264
265    /**
266     * Localized date-time pattern characters. For example, a locale may
267     * wish to use 'u' rather than 'y' to represent years in its date format
268     * pattern strings.
269     * This string must be exactly 18 characters long, with the index of
270     * the characters described by <code>DateFormat.ERA_FIELD</code>,
271     * <code>DateFormat.YEAR_FIELD</code>, etc.  Thus, if the string were
272     * "Xz...", then localized patterns would use 'X' for era and 'z' for year.
273     * @serial
274     */
275    String  localPatternChars = null;
276
277    /**
278     * The locale which is used for initializing this DateFormatSymbols object.
279     *
280     * @since 1.6
281     * @serial
282     */
283    Locale locale = null;
284
285    /* use serialVersionUID from JDK 1.1.4 for interoperability */
286    static final long serialVersionUID = -5987973545549424702L;
287
288    // the internal serial version which says which version was written
289    // - 0 (default) for version up to JDK 1.1.4
290    // - 1 Android version that contains a whole bunch of new fields.
291    static final int currentSerialVersion = 1;
292
293    /**
294     * The version of the serialized data on the stream.  Possible values:
295     * <ul>
296     * <li><b>0</b> or not present on stream: JDK 1.1.4.
297     * <li><b>1</b> Android:
298     * </ul>
299     * When streaming out this class, the most recent format
300     * and the highest allowable <code>serialVersionOnStream</code>
301     * is written.
302     * @serial
303     * @since JDK1.1.4
304     */
305    private int serialVersionOnStream = currentSerialVersion;
306
307    /**
308     * Tiny month strings; "J", "F", "M" etc.
309     *
310     * @serial
311     */
312    private String[] tinyMonths;
313
314    /**
315     * Tiny weekday strings: "M", "F", "W" etc.
316     *
317     * @serial
318     */
319    private String[] tinyWeekdays;
320
321    /**
322     * Standalone month strings; "January", "February", "March" etc.
323     *
324     * @serial
325     */
326    private String[] standAloneMonths;
327
328    /**
329     * Short standalone month strings: "Jan", "Feb", "Mar" etc.
330     *
331     * @serial
332     */
333    private String[] shortStandAloneMonths;
334
335    /**
336     * Tiny standalone month strings: "J", "F", "M" etc.
337     *
338     * @serial
339     */
340    private String[] tinyStandAloneMonths;
341
342    /**
343     * Standalone weekday strings; "Monday", "Tuesday", "Wednesday" etc.
344     *
345     * @serial
346     */
347    private String[] standAloneWeekdays;
348
349    /**
350     * Short standalone weekday strings; "Mon", "Tue", "Wed" etc.
351     *
352     * @serial
353     */
354    private String[] shortStandAloneWeekdays;
355
356    /**
357     * Tiny standalone weekday strings; "M", "T", "W" etc.
358     *
359     * @serial
360     */
361    private String[] tinyStandAloneWeekdays;
362
363    /**
364     * Returns an array of all locales for which the
365     * <code>getInstance</code> methods of this class can return
366     * localized instances.
367     * The returned array represents the union of locales supported by the
368     * Java runtime and by installed
369     * {@link java.text.spi.DateFormatSymbolsProvider DateFormatSymbolsProvider}
370     * implementations.  It must contain at least a <code>Locale</code>
371     * instance equal to {@link java.util.Locale#US Locale.US}.
372     *
373     * @return An array of locales for which localized
374     *         <code>DateFormatSymbols</code> instances are available.
375     * @since 1.6
376     */
377    public static Locale[] getAvailableLocales() {
378        LocaleServiceProviderPool pool=
379            LocaleServiceProviderPool.getPool(DateFormatSymbolsProvider.class);
380        return pool.getAvailableLocales();
381    }
382
383    /**
384     * Gets the <code>DateFormatSymbols</code> instance for the default
385     * locale.  This method provides access to <code>DateFormatSymbols</code>
386     * instances for locales supported by the Java runtime itself as well
387     * as for those supported by installed
388     * {@link java.text.spi.DateFormatSymbolsProvider DateFormatSymbolsProvider}
389     * implementations.
390     * <p>This is equivalent to calling {@link #getInstance(Locale)
391     *     getInstance(Locale.getDefault(Locale.Category.FORMAT))}.
392     * @see java.util.Locale#getDefault(java.util.Locale.Category)
393     * @see java.util.Locale.Category#FORMAT
394     * @return a <code>DateFormatSymbols</code> instance.
395     * @since 1.6
396     */
397    public static final DateFormatSymbols getInstance() {
398        return getInstance(Locale.getDefault(Locale.Category.FORMAT));
399    }
400
401    /**
402     * Gets the <code>DateFormatSymbols</code> instance for the specified
403     * locale.  This method provides access to <code>DateFormatSymbols</code>
404     * instances for locales supported by the Java runtime itself as well
405     * as for those supported by installed
406     * {@link java.text.spi.DateFormatSymbolsProvider DateFormatSymbolsProvider}
407     * implementations.
408     * @param locale the given locale.
409     * @return a <code>DateFormatSymbols</code> instance.
410     * @exception NullPointerException if <code>locale</code> is null
411     * @since 1.6
412     */
413    public static final DateFormatSymbols getInstance(Locale locale) {
414        DateFormatSymbols dfs = getProviderInstance(locale);
415        if (dfs != null) {
416            return dfs;
417        }
418        return (DateFormatSymbols) getCachedInstance(locale).clone();
419    }
420
421    /**
422     * Returns a DateFormatSymbols provided by a provider or found in
423     * the cache. Note that this method returns a cached instance,
424     * not its clone. Therefore, the instance should never be given to
425     * an application.
426     */
427    static final DateFormatSymbols getInstanceRef(Locale locale) {
428        DateFormatSymbols dfs = getProviderInstance(locale);
429        if (dfs != null) {
430            return dfs;
431        }
432        return getCachedInstance(locale);
433    }
434
435    private static DateFormatSymbols getProviderInstance(Locale locale) {
436        DateFormatSymbols providersInstance = null;
437
438        // Check whether a provider can provide an implementation that's closer
439        // to the requested locale than what the Java runtime itself can provide.
440        LocaleServiceProviderPool pool =
441            LocaleServiceProviderPool.getPool(DateFormatSymbolsProvider.class);
442        if (pool.hasProviders()) {
443            providersInstance = pool.getLocalizedObject(
444                                    DateFormatSymbolsGetter.INSTANCE, locale);
445        }
446        return providersInstance;
447    }
448
449    /**
450     * Returns a cached DateFormatSymbols if it's found in the
451     * cache. Otherwise, this method returns a newly cached instance
452     * for the given locale.
453     */
454    private static DateFormatSymbols getCachedInstance(Locale locale) {
455        SoftReference<DateFormatSymbols> ref = cachedInstances.get(locale);
456        DateFormatSymbols dfs = null;
457        if (ref == null || (dfs = ref.get()) == null) {
458            dfs = new DateFormatSymbols(locale);
459            ref = new SoftReference<DateFormatSymbols>(dfs);
460            SoftReference<DateFormatSymbols> x = cachedInstances.putIfAbsent(locale, ref);
461            if (x != null) {
462                DateFormatSymbols y = x.get();
463                if (y != null) {
464                    dfs = y;
465                } else {
466                    // Replace the empty SoftReference with ref.
467                    cachedInstances.put(locale, ref);
468                }
469            }
470        }
471        return dfs;
472    }
473
474    /**
475     * Gets era strings. For example: "AD" and "BC".
476     * @return the era strings.
477     */
478    public String[] getEras() {
479        return Arrays.copyOf(eras, eras.length);
480    }
481
482    /**
483     * Sets era strings. For example: "AD" and "BC".
484     * @param newEras the new era strings.
485     */
486    public void setEras(String[] newEras) {
487        eras = Arrays.copyOf(newEras, newEras.length);
488    }
489
490    /**
491     * Gets month strings. For example: "January", "February", etc.
492     * @return the month strings.
493     */
494    public String[] getMonths() {
495        return Arrays.copyOf(months, months.length);
496    }
497
498    /**
499     * Sets month strings. For example: "January", "February", etc.
500     * @param newMonths the new month strings.
501     */
502    public void setMonths(String[] newMonths) {
503        months = Arrays.copyOf(newMonths, newMonths.length);
504    }
505
506    /**
507     * Gets short month strings. For example: "Jan", "Feb", etc.
508     * @return the short month strings.
509     */
510    public String[] getShortMonths() {
511        return Arrays.copyOf(shortMonths, shortMonths.length);
512    }
513
514    /**
515     * Sets short month strings. For example: "Jan", "Feb", etc.
516     * @param newShortMonths the new short month strings.
517     */
518    public void setShortMonths(String[] newShortMonths) {
519        shortMonths = Arrays.copyOf(newShortMonths, newShortMonths.length);
520    }
521
522    /**
523     * Gets weekday strings. For example: "Sunday", "Monday", etc.
524     * @return the weekday strings. Use <code>Calendar.SUNDAY</code>,
525     * <code>Calendar.MONDAY</code>, etc. to index the result array.
526     */
527    public String[] getWeekdays() {
528        return Arrays.copyOf(weekdays, weekdays.length);
529    }
530
531    /**
532     * Sets weekday strings. For example: "Sunday", "Monday", etc.
533     * @param newWeekdays the new weekday strings. The array should
534     * be indexed by <code>Calendar.SUNDAY</code>,
535     * <code>Calendar.MONDAY</code>, etc.
536     */
537    public void setWeekdays(String[] newWeekdays) {
538        weekdays = Arrays.copyOf(newWeekdays, newWeekdays.length);
539    }
540
541    /**
542     * Gets short weekday strings. For example: "Sun", "Mon", etc.
543     * @return the short weekday strings. Use <code>Calendar.SUNDAY</code>,
544     * <code>Calendar.MONDAY</code>, etc. to index the result array.
545     */
546    public String[] getShortWeekdays() {
547        return Arrays.copyOf(shortWeekdays, shortWeekdays.length);
548    }
549
550    /**
551     * Sets short weekday strings. For example: "Sun", "Mon", etc.
552     * @param newShortWeekdays the new short weekday strings. The array should
553     * be indexed by <code>Calendar.SUNDAY</code>,
554     * <code>Calendar.MONDAY</code>, etc.
555     */
556    public void setShortWeekdays(String[] newShortWeekdays) {
557        shortWeekdays = Arrays.copyOf(newShortWeekdays, newShortWeekdays.length);
558    }
559
560    /**
561     * Gets ampm strings. For example: "AM" and "PM".
562     * @return the ampm strings.
563     */
564    public String[] getAmPmStrings() {
565        return Arrays.copyOf(ampms, ampms.length);
566    }
567
568    /**
569     * Sets ampm strings. For example: "AM" and "PM".
570     * @param newAmpms the new ampm strings.
571     */
572    public void setAmPmStrings(String[] newAmpms) {
573        ampms = Arrays.copyOf(newAmpms, newAmpms.length);
574    }
575
576    /**
577     * Gets time zone strings.  Use of this method is discouraged; use
578     * {@link java.util.TimeZone#getDisplayName() TimeZone.getDisplayName()}
579     * instead.
580     * <p>
581     * The value returned is a
582     * two-dimensional array of strings of size <em>n</em> by <em>m</em>,
583     * where <em>m</em> is at least 5.  Each of the <em>n</em> rows is an
584     * entry containing the localized names for a single <code>TimeZone</code>.
585     * Each such row contains (with <code>i</code> ranging from
586     * 0..<em>n</em>-1):
587     * <ul>
588     * <li><code>zoneStrings[i][0]</code> - time zone ID</li>
589     * <li><code>zoneStrings[i][1]</code> - long name of zone in standard
590     * time</li>
591     * <li><code>zoneStrings[i][2]</code> - short name of zone in
592     * standard time</li>
593     * <li><code>zoneStrings[i][3]</code> - long name of zone in daylight
594     * saving time</li>
595     * <li><code>zoneStrings[i][4]</code> - short name of zone in daylight
596     * saving time</li>
597     * </ul>
598     * The zone ID is <em>not</em> localized; it's one of the valid IDs of
599     * the {@link java.util.TimeZone TimeZone} class that are not
600     * <a href="../util/TimeZone.html#CustomID">custom IDs</a>.
601     * All other entries are localized names.  If a zone does not implement
602     * daylight saving time, the daylight saving time names should not be used.
603     * <p>
604     * If {@link #setZoneStrings(String[][]) setZoneStrings} has been called
605     * on this <code>DateFormatSymbols</code> instance, then the strings
606     * provided by that call are returned. Otherwise, the returned array
607     * contains names provided by the Java runtime and by installed
608     * {@link java.util.spi.TimeZoneNameProvider TimeZoneNameProvider}
609     * implementations.
610     *
611     * @return the time zone strings.
612     * @see #setZoneStrings(String[][])
613     */
614    public String[][] getZoneStrings() {
615        return getZoneStringsImpl(true);
616    }
617
618    /**
619     * Sets time zone strings.  The argument must be a
620     * two-dimensional array of strings of size <em>n</em> by <em>m</em>,
621     * where <em>m</em> is at least 5.  Each of the <em>n</em> rows is an
622     * entry containing the localized names for a single <code>TimeZone</code>.
623     * Each such row contains (with <code>i</code> ranging from
624     * 0..<em>n</em>-1):
625     * <ul>
626     * <li><code>zoneStrings[i][0]</code> - time zone ID</li>
627     * <li><code>zoneStrings[i][1]</code> - long name of zone in standard
628     * time</li>
629     * <li><code>zoneStrings[i][2]</code> - short name of zone in
630     * standard time</li>
631     * <li><code>zoneStrings[i][3]</code> - long name of zone in daylight
632     * saving time</li>
633     * <li><code>zoneStrings[i][4]</code> - short name of zone in daylight
634     * saving time</li>
635     * </ul>
636     * The zone ID is <em>not</em> localized; it's one of the valid IDs of
637     * the {@link java.util.TimeZone TimeZone} class that are not
638     * <a href="../util/TimeZone.html#CustomID">custom IDs</a>.
639     * All other entries are localized names.
640     *
641     * @param newZoneStrings the new time zone strings.
642     * @exception IllegalArgumentException if the length of any row in
643     *    <code>newZoneStrings</code> is less than 5
644     * @exception NullPointerException if <code>newZoneStrings</code> is null
645     * @see #getZoneStrings()
646     */
647    public void setZoneStrings(String[][] newZoneStrings) {
648        String[][] aCopy = new String[newZoneStrings.length][];
649        for (int i = 0; i < newZoneStrings.length; ++i) {
650            int len = newZoneStrings[i].length;
651            if (len < 5) {
652                throw new IllegalArgumentException();
653            }
654            aCopy[i] = Arrays.copyOf(newZoneStrings[i], len);
655        }
656        zoneStrings = aCopy;
657        isZoneStringsSet = true;
658    }
659
660    /**
661     * Gets localized date-time pattern characters. For example: 'u', 't', etc.
662     * @return the localized date-time pattern characters.
663     */
664    public String getLocalPatternChars() {
665        return localPatternChars;
666    }
667
668    /**
669     * Sets localized date-time pattern characters. For example: 'u', 't', etc.
670     * @param newLocalPatternChars the new localized date-time
671     * pattern characters.
672     */
673    public void setLocalPatternChars(String newLocalPatternChars) {
674        // Call toString() to throw an NPE in case the argument is null
675        localPatternChars = newLocalPatternChars.toString();
676    }
677
678    String[] getTinyMonths() {
679        return tinyMonths;
680    }
681
682    String[] getStandAloneMonths() {
683        return standAloneMonths;
684    }
685
686    String[] getShortStandAloneMonths() {
687        return shortStandAloneMonths;
688    }
689
690    String[] getTinyStandAloneMonths() {
691        return tinyStandAloneMonths;
692    }
693
694    String[] getTinyWeekdays() {
695        return tinyWeekdays;
696    }
697
698    String[] getStandAloneWeekdays() {
699        return standAloneWeekdays;
700    }
701
702    String[] getShortStandAloneWeekdays() {
703        return shortStandAloneWeekdays;
704    }
705
706    String[] getTinyStandAloneWeekdays() {
707        return tinyStandAloneWeekdays;
708    }
709
710    /**
711     * Overrides Cloneable
712     */
713    public Object clone()
714    {
715        try
716        {
717            DateFormatSymbols other = (DateFormatSymbols)super.clone();
718            copyMembers(this, other);
719            return other;
720        } catch (CloneNotSupportedException e) {
721            throw new InternalError(e);
722        }
723    }
724
725    /**
726     * Override hashCode.
727     * Generates a hash code for the DateFormatSymbols object.
728     */
729    @Override
730    public int hashCode() {
731        int hashcode = 0;
732        String[][] zoneStrings = getZoneStringsWrapper();
733        for (int index = 0; index < zoneStrings[0].length; ++index)
734            hashcode ^= zoneStrings[0][index].hashCode();
735        return hashcode;
736    }
737
738    /**
739     * Override equals
740     */
741    public boolean equals(Object obj)
742    {
743        if (this == obj) return true;
744        if (obj == null || getClass() != obj.getClass()) return false;
745        DateFormatSymbols that = (DateFormatSymbols) obj;
746        return (Arrays.equals(eras, that.eras)
747                && Arrays.equals(months, that.months)
748                && Arrays.equals(shortMonths, that.shortMonths)
749                && Arrays.equals(tinyMonths, that.tinyMonths)
750                && Arrays.equals(weekdays, that.weekdays)
751                && Arrays.equals(shortWeekdays, that.shortWeekdays)
752                && Arrays.equals(tinyWeekdays, that.tinyWeekdays)
753                && Arrays.equals(standAloneMonths, that.standAloneMonths)
754                && Arrays.equals(shortStandAloneMonths, that.shortStandAloneMonths)
755                && Arrays.equals(tinyStandAloneMonths, that.tinyStandAloneMonths)
756                && Arrays.equals(standAloneWeekdays, that.standAloneWeekdays)
757                && Arrays.equals(shortStandAloneWeekdays, that.shortStandAloneWeekdays)
758                && Arrays.equals(tinyStandAloneWeekdays, that.tinyStandAloneWeekdays)
759                && Arrays.equals(ampms, that.ampms)
760                && Arrays.deepEquals(getZoneStringsWrapper(), that.getZoneStringsWrapper())
761                && ((localPatternChars != null
762                  && localPatternChars.equals(that.localPatternChars))
763                 || (localPatternChars == null
764                  && that.localPatternChars == null)));
765    }
766
767    // =======================privates===============================
768
769    /**
770     * Useful constant for defining time zone offsets.
771     */
772    static final int millisPerHour = 60*60*1000;
773
774    /**
775     * Cache to hold DateFormatSymbols instances per Locale.
776     */
777    private static final ConcurrentMap<Locale, SoftReference<DateFormatSymbols>> cachedInstances
778        = new ConcurrentHashMap<Locale, SoftReference<DateFormatSymbols>>(3);
779
780    private transient int lastZoneIndex = 0;
781
782    private void initializeData(Locale desiredLocale) {
783        locale = desiredLocale;
784
785        // Copy values of a cached instance if any.
786        SoftReference<DateFormatSymbols> ref = cachedInstances.get(locale);
787        DateFormatSymbols dfs;
788        if (ref != null && (dfs = ref.get()) != null) {
789            copyMembers(dfs, this);
790            return;
791        }
792        locale = LocaleData.mapInvalidAndNullLocales(locale);
793        LocaleData localeData = LocaleData.get(locale);
794
795        eras = localeData.eras;
796
797        // Month names.
798        months = localeData.longMonthNames;
799        shortMonths = localeData.shortMonthNames;
800
801        ampms = localeData.amPm;
802        localPatternChars = patternChars;
803
804        // Weekdays.
805        weekdays = localeData.longWeekdayNames;
806        shortWeekdays = localeData.shortWeekdayNames;
807
808        initializeSupplementaryData(localeData);
809    }
810
811    private void initializeSupplementaryData(LocaleData localeData) {
812        // Tiny weekdays and months.
813        tinyMonths = localeData.tinyMonthNames;
814        tinyWeekdays = localeData.tinyWeekdayNames;
815
816        // Standalone month names.
817        standAloneMonths = localeData.longStandAloneMonthNames;
818        shortStandAloneMonths = localeData.shortStandAloneMonthNames;
819        tinyStandAloneMonths = localeData.tinyStandAloneMonthNames;
820
821        // Standalone weekdays.
822        standAloneWeekdays = localeData.longStandAloneWeekdayNames;
823        shortStandAloneWeekdays = localeData.shortStandAloneWeekdayNames;
824        tinyStandAloneWeekdays = localeData.tinyStandAloneWeekdayNames;
825    }
826
827    /**
828     * Package private: used by SimpleDateFormat
829     * Gets the index for the given time zone ID to obtain the time zone
830     * strings for formatting. The time zone ID is just for programmatic
831     * lookup. NOT LOCALIZED!!!
832     * @param ID the given time zone ID.
833     * @return the index of the given time zone ID.  Returns -1 if
834     * the given time zone ID can't be located in the DateFormatSymbols object.
835     * @see java.util.SimpleTimeZone
836     */
837    final int getZoneIndex(String ID) {
838        String[][] zoneStrings = getZoneStringsWrapper();
839
840        /*
841         * getZoneIndex has been re-written for performance reasons. instead of
842         * traversing the zoneStrings array every time, we cache the last used zone
843         * index
844         */
845        if (lastZoneIndex < zoneStrings.length && ID.equals(zoneStrings[lastZoneIndex][0])) {
846            return lastZoneIndex;
847        }
848
849        /* slow path, search entire list */
850        for (int index = 0; index < zoneStrings.length; index++) {
851            if (ID.equals(zoneStrings[index][0])) {
852                lastZoneIndex = index;
853                return index;
854            }
855        }
856
857        return -1;
858    }
859
860    /**
861     * Wrapper method to the getZoneStrings(), which is called from inside
862     * the java.text package and not to mutate the returned arrays, so that
863     * it does not need to create a defensive copy.
864     */
865    final String[][] getZoneStringsWrapper() {
866        if (isSubclassObject()) {
867            return getZoneStrings();
868        } else {
869            return getZoneStringsImpl(false);
870        }
871    }
872
873    private final synchronized String[][] internalZoneStrings() {
874        if (zoneStrings == null) {
875            zoneStrings = TimeZoneNames.getZoneStrings(locale);
876            // If icu4c doesn't have a name, our array contains a null. TimeZone.getDisplayName
877            // knows how to format GMT offsets (and, unlike icu4c, has accurate data). http://b/8128460.
878            for (String[] zone : zoneStrings) {
879                String id = zone[0];
880                if (zone[1] == null) {
881                    zone[1] =
882                        TimeZone.getTimeZone(id).getDisplayName(false, TimeZone.LONG, locale);
883                }
884                if (zone[2] == null) {
885                    zone[2] =
886                        TimeZone.getTimeZone(id).getDisplayName(false, TimeZone.SHORT, locale);
887                }
888                if (zone[3] == null) {
889                    zone[3] = TimeZone.getTimeZone(id).getDisplayName(true, TimeZone.LONG, locale);
890                }
891                if (zone[4] == null) {
892                    zone[4] =
893                        TimeZone.getTimeZone(id).getDisplayName(true, TimeZone.SHORT, locale);
894                }
895            }
896        }
897        return zoneStrings;
898    }
899
900    private final String[][] getZoneStringsImpl(boolean needsCopy) {
901        String[][] zoneStrings = internalZoneStrings();
902
903        if (!needsCopy) {
904            return zoneStrings;
905        }
906
907        int len = zoneStrings.length;
908        String[][] aCopy = new String[len][];
909        for (int i = 0; i < len; i++) {
910            aCopy[i] = Arrays.copyOf(zoneStrings[i], zoneStrings[i].length);
911        }
912        return aCopy;
913    }
914
915    private boolean isSubclassObject() {
916        return !getClass().getName().equals("java.text.DateFormatSymbols");
917    }
918
919    /**
920     * Clones all the data members from the source DateFormatSymbols to
921     * the target DateFormatSymbols. This is only for subclasses.
922     * @param src the source DateFormatSymbols.
923     * @param dst the target DateFormatSymbols.
924     */
925    private void copyMembers(DateFormatSymbols src, DateFormatSymbols dst)
926    {
927        dst.eras = Arrays.copyOf(src.eras, src.eras.length);
928        dst.months = Arrays.copyOf(src.months, src.months.length);
929        dst.shortMonths = Arrays.copyOf(src.shortMonths, src.shortMonths.length);
930        dst.weekdays = Arrays.copyOf(src.weekdays, src.weekdays.length);
931        dst.shortWeekdays = Arrays.copyOf(src.shortWeekdays, src.shortWeekdays.length);
932        dst.ampms = Arrays.copyOf(src.ampms, src.ampms.length);
933        if (src.zoneStrings != null) {
934            dst.zoneStrings = src.getZoneStringsImpl(true);
935        } else {
936            dst.zoneStrings = null;
937        }
938        dst.localPatternChars = src.localPatternChars;
939
940        dst.tinyMonths = src.tinyMonths;
941        dst.tinyWeekdays = src.tinyWeekdays;
942
943        dst.standAloneMonths = src.standAloneMonths;
944        dst.shortStandAloneMonths = src.shortStandAloneMonths;
945        dst.tinyStandAloneMonths = src.tinyStandAloneMonths;
946
947        dst.standAloneWeekdays = src.standAloneWeekdays;
948        dst.shortStandAloneWeekdays = src.shortStandAloneWeekdays;
949        dst.tinyStandAloneWeekdays = src.tinyStandAloneWeekdays;
950    }
951
952    private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
953        stream.defaultReadObject();
954
955        if (serialVersionOnStream < 1) {
956            LocaleData localeData = LocaleData.get(locale);
957            initializeSupplementaryData(localeData);
958        }
959
960        serialVersionOnStream = currentSerialVersion;
961    }
962
963    /**
964     * Write out the default serializable data, after ensuring the
965     * <code>zoneStrings</code> field is initialized in order to make
966     * sure the backward compatibility.
967     *
968     * @since 1.6
969     */
970    private void writeObject(ObjectOutputStream stream) throws IOException {
971        internalZoneStrings();
972        stream.defaultWriteObject();
973    }
974
975    /**
976     * Obtains a DateFormatSymbols instance from a DateFormatSymbolsProvider
977     * implementation.
978     */
979    private static class DateFormatSymbolsGetter
980        implements LocaleServiceProviderPool.LocalizedObjectGetter<DateFormatSymbolsProvider,
981                                                                   DateFormatSymbols> {
982        private static final DateFormatSymbolsGetter INSTANCE =
983            new DateFormatSymbolsGetter();
984
985        public DateFormatSymbols getObject(DateFormatSymbolsProvider dateFormatSymbolsProvider,
986                                Locale locale,
987                                String key,
988                                Object... params) {
989            assert params.length == 0;
990            return dateFormatSymbolsProvider.getInstance(locale);
991        }
992    }
993}
994