1 /*
   2  * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.util.calendar;
  27 
  28 import java.io.File;
  29 import java.io.FileInputStream;
  30 import java.io.IOException;
  31 import java.security.AccessController;
  32 import java.security.PrivilegedActionException;
  33 import java.security.PrivilegedExceptionAction;
  34 import java.util.Properties;
  35 import java.util.TimeZone;
  36 import java.util.concurrent.ConcurrentHashMap;
  37 import java.util.concurrent.ConcurrentMap;
  38 
  39 /**
  40  * <code>CalendarSystem</code> is an abstract class that defines the
  41  * programming interface to deal with calendar date and time.
  42  *
  43  * <p><code>CalendarSystem</code> instances are singletons. For
  44  * example, there exists only one Gregorian calendar instance in the
  45  * Java runtime environment. A singleton instance can be obtained
  46  * calling one of the static factory methods.
  47  *
  48  * <h4>CalendarDate</h4>
  49  *
  50  * <p>For the methods in a <code>CalendarSystem</code> that manipulate
  51  * a <code>CalendarDate</code>, <code>CalendarDate</code>s that have
  52  * been created by the <code>CalendarSystem</code> must be
  53  * specified. Otherwise, the methods throw an exception. This is
  54  * because, for example, a Chinese calendar date can't be understood
  55  * by the Hebrew calendar system.
  56  *
  57  * <h4>Calendar names</h4>
  58  *
  59  * Each calendar system has a unique name to be identified. The Java
  60  * runtime in this release supports the following calendar systems.
  61  *
  62  * <pre>
  63  *  Name          Calendar System
  64  *  ---------------------------------------
  65  *  gregorian     Gregorian Calendar
  66  *  julian        Julian Calendar
  67  *  japanese      Japanese Imperial Calendar
  68  * </pre>
  69  *
  70  * @see CalendarDate
  71  * @author Masayoshi Okutsu
  72  * @since 1.5
  73  */
  74 
  75 public abstract class CalendarSystem {
  76 
  77     /////////////////////// Calendar Factory Methods /////////////////////////
  78 
  79     private volatile static boolean initialized = false;
  80 
  81     // Map of calendar names and calendar class names
  82     private static ConcurrentMap<String, String> names;
  83 
  84     // Map of calendar names and CalendarSystem instances
  85     private static ConcurrentMap<String,CalendarSystem> calendars;
  86 
  87     private static final String PACKAGE_NAME = "sun.util.calendar.";
  88 
  89     private static final String[] namePairs = {
  90         "gregorian", "Gregorian",
  91         "japanese", "LocalGregorianCalendar",
  92         "julian", "JulianCalendar",
  93         /*
  94         "hebrew", "HebrewCalendar",
  95         "iso8601", "ISOCalendar",
  96         "taiwanese", "LocalGregorianCalendar",
  97         "thaibuddhist", "LocalGregorianCalendar",
  98         */
  99     };
 100 
 101     private static void initNames() {
 102         ConcurrentMap<String,String> nameMap = new ConcurrentHashMap<>();
 103 
 104         // Associate a calendar name with its class name and the
 105         // calendar class name with its date class name.
 106         StringBuilder clName = new StringBuilder();
 107         for (int i = 0; i < namePairs.length; i += 2) {
 108             clName.setLength(0);
 109             String cl = clName.append(PACKAGE_NAME).append(namePairs[i+1]).toString();
 110             nameMap.put(namePairs[i], cl);
 111         }
 112         synchronized (CalendarSystem.class) {
 113             if (!initialized) {
 114                 names = nameMap;
 115                 calendars = new ConcurrentHashMap<>();
 116                 initialized = true;
 117             }
 118         }
 119     }
 120 
 121     private final static Gregorian GREGORIAN_INSTANCE = new Gregorian();
 122 
 123     /**
 124      * Returns the singleton instance of the <code>Gregorian</code>
 125      * calendar system.
 126      *
 127      * @return the <code>Gregorian</code> instance
 128      */
 129     public static Gregorian getGregorianCalendar() {
 130         return GREGORIAN_INSTANCE;
 131     }
 132 
 133     /**
 134      * Returns a <code>CalendarSystem</code> specified by the calendar
 135      * name. The calendar name has to be one of the supported calendar
 136      * names.
 137      *
 138      * @param calendarName the calendar name
 139      * @return the <code>CalendarSystem</code> specified by
 140      * <code>calendarName</code>, or null if there is no
 141      * <code>CalendarSystem</code> associated with the given calendar name.
 142      */
 143     public static CalendarSystem forName(String calendarName) {
 144         if ("gregorian".equals(calendarName)) {
 145             return GREGORIAN_INSTANCE;
 146         }
 147 
 148         if (!initialized) {
 149             initNames();
 150         }
 151 
 152         CalendarSystem cal = calendars.get(calendarName);
 153         if (cal != null) {
 154             return cal;
 155         }
 156 
 157         String className = names.get(calendarName);
 158         if (className == null) {
 159             return null; // Unknown calendar name
 160         }
 161 
 162         if (className.endsWith("LocalGregorianCalendar")) {
 163             // Create the specific kind of local Gregorian calendar system
 164             cal = LocalGregorianCalendar.getLocalGregorianCalendar(calendarName);
 165         } else {
 166             try {
 167                 Class<?> cl = Class.forName(className);
 168                 cal = (CalendarSystem) cl.newInstance();
 169             } catch (Exception e) {
 170                 throw new InternalError(e);
 171             }
 172         }
 173         if (cal == null) {
 174             return null;
 175         }
 176         CalendarSystem cs =  calendars.putIfAbsent(calendarName, cal);
 177         return (cs == null) ? cal : cs;
 178     }
 179 
 180     /**
 181      * Returns a {@link Properties} loaded from lib/calendars.properties.
 182      *
 183      * @return a {@link Properties} loaded from lib/calendars.properties
 184      * @throws IOException if an error occurred when reading from the input stream
 185      * @throws IllegalArgumentException if the input stream contains any malformed
 186      *                                  Unicode escape sequences
 187      */
 188     public static Properties getCalendarProperties() throws IOException {
 189         Properties calendarProps = null;
 190         try {
 191             String homeDir = AccessController.doPrivileged(
 192                 new sun.security.action.GetPropertyAction("java.home"));
 193             final String fname = homeDir + File.separator + "lib" + File.separator
 194                                  + "calendars.properties";
 195             calendarProps = AccessController.doPrivileged(new PrivilegedExceptionAction<Properties>() {
 196                 @Override
 197                 public Properties run() throws IOException {
 198                     Properties props = new Properties();
 199                     try (FileInputStream fis = new FileInputStream(fname)) {
 200                         props.load(fis);
 201                     }
 202                     return props;
 203                 }
 204             });
 205         } catch (PrivilegedActionException e) {
 206             Throwable cause = e.getCause();
 207             if (cause instanceof IOException) {
 208                 throw (IOException) cause;
 209             } else if (cause instanceof IllegalArgumentException) {
 210                 throw (IllegalArgumentException) cause;
 211             }
 212             // Should not happen
 213             throw new InternalError(cause);
 214         }
 215         return calendarProps;
 216     }
 217 
 218     //////////////////////////////// Calendar API //////////////////////////////////
 219 
 220     /**
 221      * Returns the name of this calendar system.
 222      */
 223     public abstract String getName();
 224 
 225     public abstract CalendarDate getCalendarDate();
 226 
 227     /**
 228      * Calculates calendar fields from the specified number of
 229      * milliseconds since the Epoch, January 1, 1970 00:00:00 UTC
 230      * (Gregorian). This method doesn't check overflow or underflow
 231      * when adjusting the millisecond value (representing UTC) with
 232      * the time zone offsets (i.e., the GMT offset and amount of
 233      * daylight saving).
 234      *
 235      * @param millis the offset value in milliseconds from January 1,
 236      * 1970 00:00:00 UTC (Gregorian).
 237      * @return a <code>CalendarDate</code> instance that contains the
 238      * calculated calendar field values.
 239      */
 240     public abstract CalendarDate getCalendarDate(long millis);
 241 
 242     public abstract CalendarDate getCalendarDate(long millis, CalendarDate date);
 243 
 244     public abstract CalendarDate getCalendarDate(long millis, TimeZone zone);
 245 
 246     /**
 247      * Constructs a <code>CalendarDate</code> that is specific to this
 248      * calendar system. All calendar fields have their initial
 249      * values. The {@link TimeZone#getDefault() default time zone} is
 250      * set to the instance.
 251      *
 252      * @return a <code>CalendarDate</code> instance that contains the initial
 253      * calendar field values.
 254      */
 255     public abstract CalendarDate newCalendarDate();
 256 
 257     public abstract CalendarDate newCalendarDate(TimeZone zone);
 258 
 259     /**
 260      * Returns the number of milliseconds since the Epoch, January 1,
 261      * 1970 00:00:00 UTC (Gregorian), represented by the specified
 262      * <code>CalendarDate</code>.
 263      *
 264      * @param date the <code>CalendarDate</code> from which the time
 265      * value is calculated
 266      * @return the number of milliseconds since the Epoch.
 267      */
 268     public abstract long getTime(CalendarDate date);
 269 
 270     /**
 271      * Returns the length in days of the specified year by
 272      * <code>date</code>. This method does not perform the
 273      * normalization with the specified <code>CalendarDate</code>. The
 274      * <code>CalendarDate</code> must be normalized to get a correct
 275      * value.
 276      */
 277     public abstract int getYearLength(CalendarDate date);
 278 
 279     /**
 280      * Returns the number of months of the specified year. This method
 281      * does not perform the normalization with the specified
 282      * <code>CalendarDate</code>. The <code>CalendarDate</code> must
 283      * be normalized to get a correct value.
 284      */
 285     public abstract int getYearLengthInMonths(CalendarDate date);
 286 
 287     /**
 288      * Returns the length in days of the month specified by the calendar
 289      * date. This method does not perform the normalization with the
 290      * specified calendar date. The <code>CalendarDate</code> must
 291      * be normalized to get a correct value.
 292      *
 293      * @param date the date from which the month value is obtained
 294      * @return the number of days in the month
 295      * @exception IllegalArgumentException if the specified calendar date
 296      * doesn't have a valid month value in this calendar system.
 297      */
 298     public abstract int getMonthLength(CalendarDate date); // no setter
 299 
 300     /**
 301      * Returns the length in days of a week in this calendar
 302      * system. If this calendar system has multiple radix weeks, this
 303      * method returns only one of them.
 304      */
 305     public abstract int getWeekLength();
 306 
 307     /**
 308      * Returns the <code>Era</code> designated by the era name that
 309      * has to be known to this calendar system. If no Era is
 310      * applicable to this calendar system, null is returned.
 311      *
 312      * @param eraName the name of the era
 313      * @return the <code>Era</code> designated by
 314      * <code>eraName</code>, or <code>null</code> if no Era is
 315      * applicable to this calendar system or the specified era name is
 316      * not known to this calendar system.
 317      */
 318     public abstract Era getEra(String eraName);
 319 
 320     /**
 321      * Returns valid <code>Era</code>s of this calendar system. The
 322      * return value is sorted in the descendant order. (i.e., the first
 323      * element of the returned array is the oldest era.) If no era is
 324      * applicable to this calendar system, <code>null</code> is returned.
 325      *
 326      * @return an array of valid <code>Era</code>s, or
 327      * <code>null</code> if no era is applicable to this calendar
 328      * system.
 329      */
 330     public abstract Era[] getEras();
 331 
 332     /**
 333      * @throws IllegalArgumentException if the specified era name is
 334      * unknown to this calendar system.
 335      * @see Era
 336      */
 337     public abstract void setEra(CalendarDate date, String eraName);
 338 
 339     /**
 340      * Returns a <code>CalendarDate</code> of the n-th day of week
 341      * which is on, after or before the specified date. For example, the
 342      * first Sunday in April 2002 (Gregorian) can be obtained as
 343      * below:
 344      *
 345      * <pre><code>
 346      * Gregorian cal = CalendarSystem.getGregorianCalendar();
 347      * CalendarDate date = cal.newCalendarDate();
 348      * date.setDate(2004, cal.APRIL, 1);
 349      * CalendarDate firstSun = cal.getNthDayOfWeek(1, cal.SUNDAY, date);
 350      * // firstSun represents April 4, 2004.
 351      * </code></pre>
 352      *
 353      * This method returns a new <code>CalendarDate</code> instance
 354      * and doesn't modify the original date.
 355      *
 356      * @param nth specifies the n-th one. A positive number specifies
 357      * <em>on or after</em> the <code>date</code>. A non-positive number
 358      * specifies <em>on or before</em> the <code>date</code>.
 359      * @param dayOfWeek the day of week
 360      * @param date the date
 361      * @return the date of the nth <code>dayOfWeek</code> after
 362      * or before the specified <code>CalendarDate</code>
 363      */
 364     public abstract CalendarDate getNthDayOfWeek(int nth, int dayOfWeek,
 365                                                  CalendarDate date);
 366 
 367     public abstract CalendarDate setTimeOfDay(CalendarDate date, int timeOfDay);
 368 
 369     /**
 370      * Checks whether the calendar fields specified by <code>date</code>
 371      * represents a valid date and time in this calendar system. If the
 372      * given date is valid, <code>date</code> is marked as <em>normalized</em>.
 373      *
 374      * @param date the <code>CalendarDate</code> to be validated
 375      * @return <code>true</code> if all the calendar fields are consistent,
 376      * otherwise, <code>false</code> is returned.
 377      * @exception NullPointerException if the specified
 378      * <code>date</code> is <code>null</code>
 379      */
 380     public abstract boolean validate(CalendarDate date);
 381 
 382     /**
 383      * Normalizes calendar fields in the specified
 384      * <code>date</code>. Also all {@link CalendarDate#FIELD_UNDEFINED
 385      * undefined} fields are set to correct values. The actual
 386      * normalization process is calendar system dependent.
 387      *
 388      * @param date the calendar date to be validated
 389      * @return <code>true</code> if all fields have been normalized;
 390      * <code>false</code> otherwise.
 391      * @exception NullPointerException if the specified
 392      * <code>date</code> is <code>null</code>
 393      */
 394     public abstract boolean normalize(CalendarDate date);
 395 }