--- old/src/share/classes/java/util/Currency.java Tue Aug 28 14:38:26 2012 +++ new/src/share/classes/java/util/Currency.java Tue Aug 28 14:38:25 2012 @@ -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,13 @@ * 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 + * datestamp may be specified per currency entry if users need to specify a + * cutover date indicating when the new data comes into effect. The datestamp 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 date must be {@code 'yyyy-MM-dd-HH-mm-ss'}. For example, *

* * #Sample currency properties
@@ -69,6 +77,15 @@ *

* will supersede the currency data for Japan. * + *

+ * + * #Sample currency properties with cutover date
+ * JP=JPZ,999,0,2014-01-01-00-00-00 + *
+ *

+ * will supersede the currency data for Japan if Currency class is loaded after 1st + * January 2014 00:00:00 GMT. + * * @since 1.4 */ public final class Currency implements Serializable { @@ -100,7 +117,6 @@ private static ConcurrentMap instances = new ConcurrentHashMap<>(7); private static HashSet available; - // Class data: currency data obtained from currency.data file. // Purpose: // - determine valid country codes @@ -235,7 +251,9 @@ } Set 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}-\\d{2}-" + + "\\d{2}-\\d{2})?"); for (String key : keys) { replaceCurrencyData(propertiesPattern, key.toUpperCase(Locale.ROOT), @@ -645,32 +663,41 @@ * 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 GMT 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-01-00-00-00" has no effect unless + * GMT 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; + } + String code = m.group(1); int numeric = Integer.parseInt(m.group(2)); int fraction = Integer.parseInt(m.group(3)); @@ -695,6 +722,28 @@ 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-HH-mm-ss", Locale.US); + format.setTimeZone(TimeZone.getTimeZone("GMT")); + format.setLenient(false); + long time = format.parse(s.trim()).getTime(); + if (System.currentTimeMillis() - time >= 0L) + return true; + + return false; + } + + 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)) { --- old/test/java/util/Currency/PropertiesTest.java Tue Aug 28 14:38:28 2012 +++ new/test/java/util/Currency/PropertiesTest.java Tue Aug 28 14:38:27 2012 @@ -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(); @@ -117,8 +118,18 @@ Pattern.compile("([A-Z]{3})\\s*,\\s*(\\d{3})\\s*,\\s*([0-3])"); 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 +142,6 @@ // ignore this continue; } - Matcher mAfter = propertiesPattern.matcher(afterVal); mAfter.find(); @@ -164,4 +174,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-HH-mm-ss", Locale.US); + 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; + } } --- old/test/java/util/Currency/PropertiesTest.sh Tue Aug 28 14:38:30 2012 +++ new/test/java/util/Currency/PropertiesTest.sh Tue Aug 28 14:38:29 2012 @@ -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 --- old/test/java/util/Currency/currency.properties Tue Aug 28 14:38:33 2012 +++ new/test/java/util/Currency/currency.properties Tue Aug 28 14:38:32 2012 @@ -2,9 +2,18 @@ # 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-01-00-00-00 +CM=IED,111,2, 2004-01-01-00-70-00 +SB=EUR,111,2, 2099-01-01-00-00-00 ZZ = ZZZ , 999 , 3 +NO=EUR ,978 ,2, 2099-01-01-00-00-00 # invalid entries GB=123 FR=zzzzz.123 +DE=2099-01-01-00-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-01-00-00-00 +PE=EUR ,978 ,2, 20399-01-01-00-00-00