1/*
2 *******************************************************************************
3 * Copyright (C) 2008, International Business Machines Corporation and         *
4 * others. All Rights Reserved.                                                *
5 *******************************************************************************
6 */
7package com.ibm.icu.impl.jdkadapter;
8
9import java.util.HashMap;
10import java.util.Locale;
11import java.util.Map;
12import java.util.TimeZone;
13
14import com.ibm.icu.impl.icuadapter.TimeZoneJDK;
15import com.ibm.icu.text.DateFormatSymbols;
16import com.ibm.icu.util.Calendar;
17
18/**
19 * CalendarICU is an adapter class which wraps ICU4J Calendar and
20 * implements java.util.Calendar APIs.
21 */
22public class CalendarICU extends java.util.Calendar {
23
24    private static final long serialVersionUID = -8641226371713600671L;
25
26    private Calendar fIcuCal;
27
28    private CalendarICU(Calendar icuCal) {
29        fIcuCal = icuCal;
30        init();
31    }
32
33    public static java.util.Calendar wrap(Calendar icuCal) {
34        return new CalendarICU(icuCal);
35    }
36
37    public Calendar unwrap() {
38        sync();
39        return fIcuCal;
40    }
41
42    @Override
43    public void add(int field, int amount) {
44        sync();
45        fIcuCal.add(field, amount);
46    }
47
48    // Note:    We do not need to override followings.  These methods
49    //          call int compareTo(Calendar anotherCalendar) and we
50    //          override the method.
51    //public boolean after(Object when)
52    //public boolean before(Object when)
53
54    // Note:    Jeez!  These methods are final and we cannot override them.
55    //          We do not want to rewrite ICU Calendar implementation classes
56    //          as subclasses of java.util.Calendar.  This adapter class
57    //          wraps an ICU Calendar instance and the calendar calculation
58    //          is actually done independently from java.util.Calendar
59    //          implementation.  Thus, we need to monitor the status of
60    //          superclass fields in some methods and call ICU Calendar's
61    //          clear if superclass clear update the status of superclass's
62    //          calendar fields.  See private void sync().
63    //public void clear()
64    //public void clear(int field)
65
66    @Override
67    public Object clone() {
68        sync();
69        CalendarICU other = (CalendarICU)super.clone();
70        other.fIcuCal = (Calendar)fIcuCal.clone();
71        return other;
72    }
73
74    public int compareTo(Calendar anotherCalendar)  {
75        sync();
76        long thisMillis = getTimeInMillis();
77        long otherMillis = anotherCalendar.getTimeInMillis();
78        return thisMillis > otherMillis ? 1 : (thisMillis == otherMillis ? 0 : -1);
79    }
80
81    // Note:    These methods are supposed to be implemented by java.util.Calendar
82    //          subclasses.  But we actually use a instance of ICU Calendar
83    //          for all calendar calculation, we do nothing here.
84    @Override
85    protected void complete() {}
86    @Override
87    protected void computeFields() {}
88    @Override
89    protected void computeTime() {}
90
91    @Override
92    public boolean equals(Object obj) {
93        if (obj instanceof CalendarICU) {
94            sync();
95            return ((CalendarICU)obj).fIcuCal.equals(fIcuCal);
96        }
97        return false;
98    }
99
100    @Override
101    public int get(int field) {
102        sync();
103        return fIcuCal.get(field);
104    }
105
106    @Override
107    public int getActualMaximum(int field) {
108        return fIcuCal.getActualMaximum(field);
109    }
110
111    @Override
112    public int getActualMinimum(int field) {
113        return fIcuCal.getActualMinimum(field);
114    }
115
116    @Override
117    public String getDisplayName(int field, int style, Locale locale) {
118        if (field < 0 || field >= FIELD_COUNT || (style != SHORT && style != LONG && style != ALL_STYLES)) {
119            throw new IllegalArgumentException("Bad field or style.");
120        }
121        DateFormatSymbols dfs = DateFormatSymbols.getInstance(locale);
122        String[] array = getFieldStrings(field, style, dfs);
123        if (array != null) {
124            int fieldVal = get(field);
125            if (fieldVal < array.length) {
126                return array[fieldVal];
127            }
128        }
129        return null;
130    }
131
132    @Override
133    public Map<String,Integer> getDisplayNames(int field, int style, Locale locale) {
134        if (field < 0 || field >= FIELD_COUNT || (style != SHORT && style != LONG && style != ALL_STYLES)) {
135            throw new IllegalArgumentException("Bad field or style.");
136        }
137        DateFormatSymbols dfs = DateFormatSymbols.getInstance(locale);
138        if (style != ALL_STYLES) {
139            return getFieldStringsMap(field, style, dfs);
140        }
141
142        Map<String,Integer> result = getFieldStringsMap(field, SHORT, dfs);
143        if (result == null) {
144            return null;
145        }
146        if (field == MONTH || field == DAY_OF_WEEK) {
147            Map<String,Integer> longMap = getFieldStringsMap(field, LONG, dfs);
148            if (longMap != null) {
149                result.putAll(longMap);
150            }
151        }
152        return result;
153    }
154
155    @Override
156    public int getGreatestMinimum(int field) {
157        return fIcuCal.getGreatestMinimum(field);
158    }
159
160    @Override
161    public int getLeastMaximum(int field) {
162        return fIcuCal.getLeastMaximum(field);
163    }
164
165    @Override
166    public int getMaximum(int field) {
167        return fIcuCal.getMaximum(field);
168    }
169
170    @Override
171    public int getMinimalDaysInFirstWeek() {
172        return fIcuCal.getMinimalDaysInFirstWeek();
173    }
174
175    @Override
176    public int getMinimum(int field) {
177        return fIcuCal.getMinimum(field);
178    }
179
180    // Note:    getTime() calls getTimeInMillis()
181    //public Date getTime()
182
183    @Override
184    public long getTimeInMillis() {
185        sync();
186        return fIcuCal.getTimeInMillis();
187    }
188
189    @Override
190    public TimeZone getTimeZone() {
191        return TimeZoneICU.wrap(fIcuCal.getTimeZone());
192    }
193
194    @Override
195    public int hashCode() {
196        sync();
197        return fIcuCal.hashCode();
198    }
199
200    //protected int internalGet(int field)
201
202    @Override
203    public boolean isLenient() {
204        return fIcuCal.isLenient();
205    }
206
207    //public boolean isSet(int field)
208
209    @Override
210    public void roll(int field, boolean up) {
211        sync();
212        fIcuCal.roll(field, up);
213    }
214
215    @Override
216    public void roll(int field, int amount) {
217        sync();
218        fIcuCal.roll(field, amount);
219    }
220
221    @Override
222    public void set(int field, int value) {
223        sync();
224        fIcuCal.set(field, value);
225    }
226
227    // Note:    These set methods call set(int field, int value) for each field.
228    //          These are final, so we cannot override them, but we override
229    //          set(int field, int value), so the superclass implementations
230    //          still work as we want.
231    //public void set(int year, int month, int date)
232    //public void set(int year, int month, int date, int hourOfDay, int minute)
233    //public void set(int year, int month, int date, int hourOfDay, int minute, int second)
234
235    @Override
236    public void setFirstDayOfWeek(int value) {
237        fIcuCal.setFirstDayOfWeek(value);
238    }
239
240    @Override
241    public void setLenient(boolean lenient) {
242        fIcuCal.setLenient(lenient);
243    }
244
245    @Override
246    public void setMinimalDaysInFirstWeek(int value) {
247        fIcuCal.setMinimalDaysInFirstWeek(value);
248    }
249
250    // Note:    This method calls setTimeInMillis(long millis).
251    //          This method is final, so we cannot override it, but we
252    //          override setTimeInMillis(long millis), so the superclass
253    //          implementation still works as we want.
254    //public void setTime(Date date)
255
256    @Override
257    public void setTimeInMillis(long millis) {
258        fIcuCal.setTimeInMillis(millis);
259    }
260
261    @Override
262    public void setTimeZone(TimeZone value) {
263        fIcuCal.setTimeZone(TimeZoneJDK.wrap(value));
264    }
265
266    @Override
267    public String toString() {
268        sync();
269        return "CalendarICU: " + fIcuCal.toString();
270    }
271
272    private void sync() {
273        // Check if clear is called for each JDK Calendar field.
274        // If it was, then call clear for the field in the wrapped
275        // ICU Calendar.
276        for (int i = 0; i < isSet.length; i++) {
277            if (!isSet[i]) {
278                isSet[i] = true;
279                try {
280                    fIcuCal.clear(i);
281                } catch (ArrayIndexOutOfBoundsException e) {
282                    // More fields in JDK calendar, which is unlikely
283                }
284            }
285        }
286    }
287
288    private void init() {
289        // Mark "set" for all fields, so we can detect the invocation of
290        // clear() later.
291        for (int i = 0; i < isSet.length; i++) {
292            isSet[i] = true;
293        }
294    }
295
296    private static String[] getFieldStrings(int field, int style, DateFormatSymbols dfs) {
297        String[] result = null;
298        switch (field) {
299        case AM_PM:
300            result = dfs.getAmPmStrings();
301            break;
302        case DAY_OF_WEEK:
303            result = (style == LONG) ? dfs.getWeekdays() : dfs.getShortWeekdays();
304            break;
305        case ERA:
306            //result = (style == LONG) ? dfs.getEraNames() : dfs.getEras();
307            result = dfs.getEras();
308            break;
309        case MONTH:
310            result = (style == LONG) ? dfs.getMonths() : dfs.getShortMonths();
311            break;
312        }
313        return result;
314    }
315
316    private static Map<String,Integer> getFieldStringsMap(int field, int style, DateFormatSymbols dfs) {
317        String[] strings = getFieldStrings(field, style, dfs);
318        if (strings == null) {
319            return null;
320        }
321        Map<String,Integer> res = new HashMap<String,Integer>();
322        for (int i = 0; i < strings.length; i++) {
323            if (strings[i].length() != 0) {
324                res.put(strings[i], Integer.valueOf(i));
325            }
326        }
327        return res;
328    }
329}
330