jdk/src/share/classes/java/time/chrono/HijrahChronology.java
changeset 17474 8c100beabcc0
parent 16852 60207b2b4b42
child 19030 32f129cb6351
--- a/jdk/src/share/classes/java/time/chrono/HijrahChronology.java	Wed May 15 15:01:59 2013 +0100
+++ b/jdk/src/share/classes/java/time/chrono/HijrahChronology.java	Wed May 15 07:48:57 2013 -0700
@@ -133,9 +133,10 @@
  *      Chronology chrono = Chronology.ofLocale(locale);
  * </pre>
  *
- * <h3>Specification for implementors</h3>
+ * @implSpec
  * This class is immutable and thread-safe.
- * <h3>Implementation Note for Hijrah Calendar Variant Configuration</h3>
+ *
+ * @implNote
  * Each Hijrah variant is configured individually. Each variant is defined by a
  * property resource that defines the {@code ID}, the {@code calendar type},
  * the start of the calendar, the alignment with the
@@ -230,6 +231,11 @@
      */
     public static final HijrahChronology INSTANCE;
     /**
+     * Flag to indicate the initialization of configuration data is complete.
+     * @see #checkCalendarInit()
+     */
+    private volatile boolean initComplete;
+    /**
      * Array of epoch days indexed by Hijrah Epoch month.
      * Computed by {@link #loadCalendarData}.
      */
@@ -285,7 +291,8 @@
     private static final String PROP_TYPE_SUFFIX = ".type";
 
     /**
-     * Name data.
+     * Static initialization of the predefined calendars found in the
+     * lib/calendars.properties file.
      */
     static {
         try {
@@ -299,8 +306,7 @@
             // Register it by its aliases
             Chronology.registerChrono(INSTANCE, "Hijrah");
             Chronology.registerChrono(INSTANCE, "islamic");
-
-        } catch (Exception ex) {
+        } catch (DateTimeException ex) {
             // Absence of Hijrah calendar is fatal to initializing this class.
             PlatformLogger logger = PlatformLogger.getLogger("java.time.chrono");
             logger.severe("Unable to initialize Hijrah calendar: Hijrah-umalqura", ex);
@@ -327,7 +333,7 @@
                     // Create and register the variant
                     HijrahChronology chrono = new HijrahChronology(id);
                     Chronology.registerChrono(chrono);
-                } catch (Exception ex) {
+                } catch (DateTimeException ex) {
                     // Log error and continue
                     PlatformLogger logger = PlatformLogger.getLogger("java.time.chrono");
                     logger.severe("Unable to initialize Hijrah calendar: " + id, ex);
@@ -343,22 +349,39 @@
      * The property names are {@code "calendar.hijrah." + id}
      * and  {@code "calendar.hijrah." + id + ".type"}
      * @param id the id of the calendar
-     * @throws Exception if the resource can not be accessed or
-     *    the format is invalid
+     * @throws DateTimeException if the calendar type is missing from the properties file.
+     * @throws IllegalArgumentException if the id is empty
      */
-    private HijrahChronology(String id) throws Exception {
+    private HijrahChronology(String id) throws DateTimeException {
         if (id.isEmpty()) {
             throw new IllegalArgumentException("calendar id is empty");
         }
+        String propName = PROP_PREFIX + id + PROP_TYPE_SUFFIX;
+        String calType = calendarProperties.getProperty(propName);
+        if (calType == null || calType.isEmpty()) {
+            throw new DateTimeException("calendarType is missing or empty for: " + propName);
+        }
         this.typeId = id;
-        this.calendarType = calendarProperties.getProperty(PROP_PREFIX + id + PROP_TYPE_SUFFIX);
+        this.calendarType = calType;
+    }
 
-        try {
-            String resource = calendarProperties.getProperty(PROP_PREFIX + id);
-            Objects.requireNonNull(resource, "Resource missing for calendar");
-            loadCalendarData(resource);
-        } catch (Exception ex) {
-            throw new Exception("Unable to initialize HijrahCalendar: " + id, ex);
+    /**
+     * Check and ensure that the calendar data has been initialized.
+     * The initialization check is performed at the boundary between
+     * public and package methods.  If a public calls another public method
+     * a check is not necessary in the caller.
+     * The constructors of HijrahDate call {@link #getEpochDay} or
+     * {@link #getHijrahDateInfo} so every call from HijrahDate to a
+     * HijrahChronology via package private methods has been checked.
+     *
+     * @throws DateTimeException if the calendar data configuration is
+     *     malformed or IOExceptions occur loading the data
+     */
+    private void checkCalendarInit() {
+        // Keep this short so it can be inlined for performance
+        if (initComplete == false) {
+            loadCalendarData();
+            initComplete = true;
         }
     }
 
@@ -509,6 +532,7 @@
     //-----------------------------------------------------------------------
     @Override
     public boolean isLeapYear(long prolepticYear) {
+        checkCalendarInit();
         int epochMonth = yearToEpochMonth((int) prolepticYear);
         if (epochMonth < 0 || epochMonth > maxEpochDay) {
             throw new DateTimeException("Hijrah date out of range");
@@ -543,6 +567,7 @@
     //-----------------------------------------------------------------------
     @Override
     public ValueRange range(ChronoField field) {
+        checkCalendarInit();
         if (field instanceof ChronoField) {
             ChronoField f = field;
             switch (f) {
@@ -595,6 +620,7 @@
      * @return int[0] = YEAR, int[1] = MONTH, int[2] = DATE
      */
     int[] getHijrahDateInfo(int epochDay) {
+        checkCalendarInit();    // ensure that the chronology is initialized
         if (epochDay < minEpochDay || epochDay >= maxEpochDay) {
             throw new DateTimeException("Hijrah date out of range");
         }
@@ -621,6 +647,7 @@
      * @return the epoch day
      */
     long getEpochDay(int prolepticYear, int monthOfYear, int dayOfMonth) {
+        checkCalendarInit();    // ensure that the chronology is initialized
         checkValidMonth(monthOfYear);
         int epochMonth = yearToEpochMonth(prolepticYear) + (monthOfYear - 1);
         if (epochMonth < 0 || epochMonth >= hijrahEpochMonthStartDays.length) {
@@ -846,84 +873,90 @@
     }
 
     /**
-     * Loads and processes the Hijrah calendar properties file.
+     * Loads and processes the Hijrah calendar properties file for this calendarType.
      * The starting Hijrah date and the corresponding ISO date are
      * extracted and used to calculate the epochDate offset.
      * The version number is identified and ignored.
      * Everything else is the data for a year with containing the length of each
      * of 12 months.
      *
-     * @param resourceName  containing the properties defining the calendar, not null
-     * @throws IllegalArgumentException  if any of the values are malformed
-     * @throws NumberFormatException  if numbers, including properties that should
-     *      be years are invalid
-     * @throws IOException  if access to the property resource fails.
+     * @throws DateTimeException if initialization of the calendar data from the
+     *     resource fails
      */
-    private void loadCalendarData(String resourceName)  throws Exception {
-        Properties props = readConfigProperties(resourceName);
+    private void loadCalendarData() {
+        try {
+            String resourceName = calendarProperties.getProperty(PROP_PREFIX + typeId);
+            Objects.requireNonNull(resourceName, "Resource missing for calendar: " + PROP_PREFIX + typeId);
+            Properties props = readConfigProperties(resourceName);
 
-        Map<Integer, int[]> years = new HashMap<>();
-        int minYear = Integer.MAX_VALUE;
-        int maxYear = Integer.MIN_VALUE;
-        String id = null;
-        String type = null;
-        String version = null;
-        int isoStart = 0;
-        for (Map.Entry<Object, Object> entry : props.entrySet()) {
-            String key = (String) entry.getKey();
-            switch (key) {
-                case KEY_ID:
-                    id = (String)entry.getValue();
-                    break;
-                case KEY_TYPE:
-                    type = (String)entry.getValue();
-                    break;
-                case KEY_VERSION:
-                    version = (String)entry.getValue();
-                    break;
-                case KEY_ISO_START: {
-                    int[] ymd = parseYMD((String) entry.getValue());
-                    isoStart = (int) LocalDate.of(ymd[0], ymd[1], ymd[2]).toEpochDay();
-                    break;
+            Map<Integer, int[]> years = new HashMap<>();
+            int minYear = Integer.MAX_VALUE;
+            int maxYear = Integer.MIN_VALUE;
+            String id = null;
+            String type = null;
+            String version = null;
+            int isoStart = 0;
+            for (Map.Entry<Object, Object> entry : props.entrySet()) {
+                String key = (String) entry.getKey();
+                switch (key) {
+                    case KEY_ID:
+                        id = (String)entry.getValue();
+                        break;
+                    case KEY_TYPE:
+                        type = (String)entry.getValue();
+                        break;
+                    case KEY_VERSION:
+                        version = (String)entry.getValue();
+                        break;
+                    case KEY_ISO_START: {
+                        int[] ymd = parseYMD((String) entry.getValue());
+                        isoStart = (int) LocalDate.of(ymd[0], ymd[1], ymd[2]).toEpochDay();
+                        break;
+                    }
+                    default:
+                        try {
+                            // Everything else is either a year or invalid
+                            int year = Integer.valueOf(key);
+                            int[] months = parseMonths((String) entry.getValue());
+                            years.put(year, months);
+                            maxYear = Math.max(maxYear, year);
+                            minYear = Math.min(minYear, year);
+                        } catch (NumberFormatException nfe) {
+                            throw new IllegalArgumentException("bad key: " + key);
+                        }
                 }
-                default:
-                    try {
-                        // Everything else is either a year or invalid
-                        int year = Integer.valueOf(key);
-                        int[] months = parseMonths((String) entry.getValue());
-                        years.put(year, months);
-                        maxYear = Math.max(maxYear, year);
-                        minYear = Math.min(minYear, year);
-                    } catch (NumberFormatException nfe) {
-                        throw new IllegalArgumentException("bad key: " + key);
-                    }
+            }
+
+            if (!getId().equals(id)) {
+                throw new IllegalArgumentException("Configuration is for a different calendar: " + id);
             }
-        }
+            if (!getCalendarType().equals(type)) {
+                throw new IllegalArgumentException("Configuration is for a different calendar type: " + type);
+            }
+            if (version == null || version.isEmpty()) {
+                throw new IllegalArgumentException("Configuration does not contain a version");
+            }
+            if (isoStart == 0) {
+                throw new IllegalArgumentException("Configuration does not contain a ISO start date");
+            }
 
-        if (!getId().equals(id)) {
-            throw new IllegalArgumentException("Configuration is for a different calendar: " + id);
-        }
-        if (!getCalendarType().equals(type)) {
-            throw new IllegalArgumentException("Configuration is for a different calendar type: " + type);
-        }
-        if (version == null || version.isEmpty()) {
-            throw new IllegalArgumentException("Configuration does not contain a version");
-        }
-        if (isoStart == 0) {
-            throw new IllegalArgumentException("Configuration does not contain a ISO start date");
-        }
+            // Now create and validate the array of epochDays indexed by epochMonth
+            hijrahStartEpochMonth = minYear * 12;
+            minEpochDay = isoStart;
+            hijrahEpochMonthStartDays = createEpochMonths(minEpochDay, minYear, maxYear, years);
+            maxEpochDay = hijrahEpochMonthStartDays[hijrahEpochMonthStartDays.length - 1];
 
-        // Now create and validate the array of epochDays indexed by epochMonth
-        hijrahStartEpochMonth = minYear * 12;
-        minEpochDay = isoStart;
-        hijrahEpochMonthStartDays = createEpochMonths(minEpochDay, minYear, maxYear, years);
-        maxEpochDay = hijrahEpochMonthStartDays[hijrahEpochMonthStartDays.length - 1];
-
-        // Compute the min and max year length in days.
-        for (int year = minYear; year < maxYear; year++) {
-            int length = getYearLength(year);
-            minYearLength = Math.min(minYearLength, length);
-            maxYearLength = Math.max(maxYearLength, length);
+            // Compute the min and max year length in days.
+            for (int year = minYear; year < maxYear; year++) {
+                int length = getYearLength(year);
+                minYearLength = Math.min(minYearLength, length);
+                maxYearLength = Math.max(maxYearLength, length);
+            }
+        } catch (Exception ex) {
+            // Log error and throw a DateTimeException
+            PlatformLogger logger = PlatformLogger.getLogger("java.time.chrono");
+            logger.severe("Unable to initialize Hijrah calendar proxy: " + typeId, ex);
+            throw new DateTimeException("Unable to initialize HijrahCalendar: " + typeId, ex);
         }
     }