7180362: RFE: Implement date cutover functionality for currency.properties file
authorcoffeys
Fri, 07 Sep 2012 21:22:37 +0100
changeset 13790 5b29e3921008
parent 13789 639c51fe428c
child 13793 07980df92cb0
7180362: RFE: Implement date cutover functionality for currency.properties file Reviewed-by: naoto
jdk/src/share/classes/java/util/Currency.java
jdk/test/java/util/Currency/PropertiesTest.java
jdk/test/java/util/Currency/PropertiesTest.sh
jdk/test/java/util/Currency/currency.properties
--- a/jdk/src/share/classes/java/util/Currency.java	Fri Sep 07 12:49:04 2012 -0400
+++ b/jdk/src/share/classes/java/util/Currency.java	Fri Sep 07 21:22:37 2012 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2000, 2011, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 2012, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -34,6 +34,8 @@
 import java.io.Serializable;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.regex.Pattern;
@@ -60,7 +62,14 @@
  * and the ISO 4217 currency data respectively.  The value part consists of
  * three ISO 4217 values of a currency, i.e., an alphabetic code, a numeric
  * code, and a minor unit.  Those three ISO 4217 values are separated by commas.
- * The lines which start with '#'s are considered comment lines.  For example,
+ * The lines which start with '#'s are considered comment lines. An optional UTC
+ * timestamp may be specified per currency entry if users need to specify a
+ * cutover date indicating when the new data comes into effect. The timestamp is
+ * appended to the end of the currency properties and uses a comma as a separator.
+ * If a UTC datestamp is present and valid, the JRE will only use the new currency
+ * properties if the current UTC date is later than the date specified at class
+ * loading time. The format of the timestamp must be of ISO 8601 format :
+ * {@code 'yyyy-MM-dd'T'HH:mm:ss'}. For example,
  * <p>
  * <code>
  * #Sample currency properties<br>
@@ -69,6 +78,20 @@
  * <p>
  * will supersede the currency data for Japan.
  *
+ * <p>
+ * <code>
+ * #Sample currency properties with cutover date<br>
+ * JP=JPZ,999,0,2014-01-01T00:00:00
+ * </code>
+ * <p>
+ * will supersede the currency data for Japan if {@code Currency} class is loaded after
+ * 1st January 2014 00:00:00 GMT.
+ * <p>
+ * Where syntactically malformed entries are encountered, the entry is ignored
+ * and the remainder of entries in file are processed. For instances where duplicate
+ * country code entries exist, the behavior of the Currency information for that
+ * {@code Currency} is undefined and the remainder of entries in file are processed.
+ *
  * @since 1.4
  */
 public final class Currency implements Serializable {
@@ -100,7 +123,6 @@
     private static ConcurrentMap<String, Currency> instances = new ConcurrentHashMap<>(7);
     private static HashSet<Currency> available;
 
-
     // Class data: currency data obtained from currency.data file.
     // Purpose:
     // - determine valid country codes
@@ -235,7 +257,9 @@
                         }
                         Set<String> keys = props.stringPropertyNames();
                         Pattern propertiesPattern =
-                            Pattern.compile("([A-Z]{3})\\s*,\\s*(\\d{3})\\s*,\\s*([0-3])");
+                            Pattern.compile("([A-Z]{3})\\s*,\\s*(\\d{3})\\s*,\\s*" +
+                                "([0-3])\\s*,?\\s*(\\d{4}-\\d{2}-\\d{2}T\\d{2}:" +
+                                "\\d{2}:\\d{2})?");
                         for (String key : keys) {
                            replaceCurrencyData(propertiesPattern,
                                key.toUpperCase(Locale.ROOT),
@@ -645,29 +669,38 @@
      *    consists of "three-letter alphabet code", "three-digit numeric code",
      *    and "one-digit (0,1,2, or 3) default fraction digit".
      *    For example, "JPZ,392,0".
-     * @throws
+     *    An optional UTC date can be appended to the string (comma separated)
+     *    to allow a currency change take effect after date specified.
+     *    For example, "JP=JPZ,999,0,2014-01-01T00:00:00" has no effect unless
+     *    UTC time is past 1st January 2014 00:00:00 GMT.
      */
     private static void replaceCurrencyData(Pattern pattern, String ctry, String curdata) {
 
         if (ctry.length() != 2) {
             // ignore invalid country code
-            String message = new StringBuilder()
-                .append("The entry in currency.properties for ")
-                .append(ctry).append(" is ignored because of the invalid country code.")
-                .toString();
-            info(message, null);
+            info("currency.properties entry for " + ctry +
+                    " is ignored because of the invalid country code.", null);
             return;
         }
 
         Matcher m = pattern.matcher(curdata);
-        if (!m.find()) {
+        if (!m.find() || (m.group(4) == null && countOccurrences(curdata, ',') >= 3)) {
             // format is not recognized.  ignore the data
-            String message = new StringBuilder()
-                .append("The entry in currency.properties for ")
-                .append(ctry)
-                .append(" is ignored because the value format is not recognized.")
-                .toString();
-            info(message, null);
+            // if group(4) date string is null and we've 4 values, bad date value
+            info("currency.properties entry for " + ctry +
+                    " ignored because the value format is not recognized.", null);
+            return;
+        }
+
+        try {
+            if (m.group(4) != null && !isPastCutoverDate(m.group(4))) {
+                info("currency.properties entry for " + ctry +
+                        " ignored since cutover date has not passed :" + curdata, null);
+                return;
+            }
+        } catch (IndexOutOfBoundsException | NullPointerException | ParseException ex) {
+            info("currency.properties entry for " + ctry +
+                        " ignored since exception encountered :" + ex.getMessage(), null);
             return;
         }
 
@@ -695,6 +728,26 @@
         setMainTableEntry(ctry.charAt(0), ctry.charAt(1), entry);
     }
 
+    private static boolean isPastCutoverDate(String s)
+            throws IndexOutOfBoundsException, NullPointerException, ParseException {
+        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.ROOT);
+        format.setTimeZone(TimeZone.getTimeZone("UTC"));
+        format.setLenient(false);
+        long time = format.parse(s.trim()).getTime();
+        return System.currentTimeMillis() > time;
+
+    }
+
+    private static int countOccurrences(String value, char match) {
+        int count = 0;
+        for (char c : value.toCharArray()) {
+            if (c == match) {
+               ++count;
+            }
+        }
+        return count;
+    }
+
     private static void info(String message, Throwable t) {
         PlatformLogger logger = PlatformLogger.getLogger("java.util.Currency");
         if (logger.isLoggable(PlatformLogger.INFO)) {
--- a/jdk/test/java/util/Currency/PropertiesTest.java	Fri Sep 07 12:49:04 2012 -0400
+++ b/jdk/test/java/util/Currency/PropertiesTest.java	Fri Sep 07 21:22:37 2012 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2007, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2007, 2012, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -22,11 +22,12 @@
  */
 
 import java.io.*;
+import java.text.*;
 import java.util.*;
 import java.util.regex.*;
 
 public class PropertiesTest {
-    public static void main(String[] s) {
+    public static void main(String[] s) throws Exception {
         for (int i = 0; i < s.length; i ++) {
             if ("-d".equals(s[i])) {
                 i++;
@@ -76,7 +77,7 @@
         pw.close();
     }
 
-    private static void compare(String beforeFile, String afterFile) {
+    private static void compare(String beforeFile, String afterFile) throws Exception {
         // load file contents
         Properties before = new Properties();
         Properties after = new Properties();
@@ -114,11 +115,23 @@
         // test each replacements
         keys = p.stringPropertyNames();
         Pattern propertiesPattern =
-            Pattern.compile("([A-Z]{3})\\s*,\\s*(\\d{3})\\s*,\\s*([0-3])");
+            Pattern.compile("([A-Z]{3})\\s*,\\s*(\\d{3})\\s*,\\s*" +
+                "([0-3])\\s*,?\\s*(\\d{4}-\\d{2}-\\d{2}T\\d{2}:" +
+                "\\d{2}:\\d{2})?");
         for (String key: keys) {
             String val = p.getProperty(key);
+            try {
+                if (countOccurrences(val, ',') == 3 && !isPastCutoverDate(val)) {
+                    System.out.println("Skipping since date is in future");
+                    continue; // skip since date in future (no effect)
+                }
+            } catch (ParseException pe) {
+                // swallow - currency class should not honour this value
+                continue;
+            }
             String afterVal = after.getProperty(key);
             System.out.printf("Testing key: %s, val: %s... ", key, val);
+            System.out.println("AfterVal is : " + afterVal);
 
             Matcher m = propertiesPattern.matcher(val.toUpperCase(Locale.ROOT));
             if (!m.find()) {
@@ -131,7 +144,6 @@
                 // ignore this
                 continue;
             }
-
             Matcher mAfter = propertiesPattern.matcher(afterVal);
             mAfter.find();
 
@@ -164,4 +176,29 @@
             throw new RuntimeException(sb.toString());
         }
     }
+
+    private static boolean isPastCutoverDate(String s)
+            throws IndexOutOfBoundsException, NullPointerException, ParseException {
+        String dateString = s.substring(s.lastIndexOf(',')+1, s.length()).trim();
+        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.ROOT);
+        format.setTimeZone(TimeZone.getTimeZone("GMT"));
+        format.setLenient(false);
+
+        long time = format.parse(dateString).getTime();
+        if (System.currentTimeMillis() - time >= 0L) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    private static int countOccurrences(String value, char match) {
+        int count = 0;
+        for (char c : value.toCharArray()) {
+            if (c == match) {
+               ++count;
+            }
+        }
+        return count;
+    }
 }
--- a/jdk/test/java/util/Currency/PropertiesTest.sh	Fri Sep 07 12:49:04 2012 -0400
+++ b/jdk/test/java/util/Currency/PropertiesTest.sh	Fri Sep 07 21:22:37 2012 +0100
@@ -1,7 +1,7 @@
 #!/bin/sh
 #
 # @test
-# @bug 6332666
+# @bug 6332666 7180362
 # @summary tests the capability of replacing the currency data with user
 #     specified currency properties file
 # @build PropertiesTest
--- a/jdk/test/java/util/Currency/currency.properties	Fri Sep 07 12:49:04 2012 -0400
+++ b/jdk/test/java/util/Currency/currency.properties	Fri Sep 07 21:22:37 2012 +0100
@@ -2,9 +2,19 @@
 # Test data for replacing the currency data
 #
 JP=JPZ,123,2
-US=euR,978,2
+ES=ESD,877,2
+US=euR,978,2,2001-01-01T00:00:00
+CM=IED,111,2, 2004-01-01T00:70:00
+SB=EUR,111,2, 2099-01-01T00:00:00
 ZZ	=	ZZZ	,	999	,	3
+NO=EUR   ,978  ,2,  2099-01-01T00:00:00
 
 # invalid entries
 GB=123
 FR=zzzzz.123
+DE=2009-01-01T00:00:00,EUR,111,2
+IE=euR,111,2,#testcomment
+=euR,111,2, 2099-01-01-00-00-00
+FM=DED,194,2,eeee-01-01T00:00:00
+PE=EUR   ,978  ,2,  20399-01-01T00:00:00
+MX=SSS,493,2,2001-01-01-00-00-00