1 /*
   2  * Copyright (c) 2007, 2018, 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.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 import java.io.*;
  25 import java.text.*;
  26 import java.util.*;
  27 import java.util.regex.*;
  28 import java.util.stream.Collectors;
  29 
  30 public class PropertiesTest {
  31     public static void main(String[] args) throws Exception {
  32         if (args.length == 2 && args[0].equals("-d")) {
  33             dump(args[1]);
  34         } else if (args.length == 4 && args[0].equals("-c")) {
  35             compare(args[1], args[2], args[3]);
  36         } else if (args.length == 1 && args[0].equals("bug7102969")) {
  37             bug7102969();
  38         } else if (args.length == 1 && args[0].equals("bug8157138")) {
  39             bug8157138();
  40         } else if (args.length == 1 && args[0].equals("bug8190904")) {
  41             bug8190904();
  42         } else {
  43             System.err.println("Usage:  java PropertiesTest -d <dumpfile>");
  44             System.err.println("        java PropertiesTest -c <beforedump> <afterdump> <propsfile>");
  45             System.err.println("        java PropertiesTest bug[JBS bug id number] e.g. bug7102969");
  46             System.exit(-1);
  47         }
  48     }
  49 
  50     private static void dump(String outfile) {
  51         File f = new File(outfile);
  52         PrintWriter pw;
  53         try {
  54             f.createNewFile();
  55             pw = new PrintWriter(f);
  56         } catch (Exception fnfe) {
  57             throw new RuntimeException(fnfe);
  58         }
  59         for (char c1 = 'A'; c1 <= 'Z'; c1++) {
  60             for (char c2 = 'A'; c2 <= 'Z'; c2++) {
  61                 String ctry = new StringBuilder().append(c1).append(c2).toString();
  62                 try {
  63                     Currency c = Currency.getInstance(new Locale("", ctry));
  64                     if (c != null) {
  65                         pw.printf(Locale.ROOT, "%s=%s,%03d,%1d\n",
  66                             ctry,
  67                             c.getCurrencyCode(),
  68                             c.getNumericCode(),
  69                             c.getDefaultFractionDigits());
  70                     }
  71                 } catch (IllegalArgumentException iae) {
  72                     // invalid country code
  73                     continue;
  74                 }
  75             }
  76         }
  77         pw.flush();
  78         pw.close();
  79     }
  80 
  81     private static void compare(String beforeFile, String afterFile, String propsFile)
  82         throws IOException
  83     {
  84         // load file contents
  85         Properties before = new Properties();
  86         try (Reader reader = new FileReader(beforeFile)) {
  87             before.load(reader);
  88         }
  89         Properties after = new Properties();
  90         try (Reader reader = new FileReader(afterFile)) {
  91             after.load(reader);
  92         }
  93 
  94         // remove the same contents from the 'after' properties
  95         Set<String> keys = before.stringPropertyNames();
  96         for (String key: keys) {
  97             String beforeVal = before.getProperty(key);
  98             String afterVal = after.getProperty(key);
  99             System.out.printf("Removing country: %s. before: %s, after: %s", key, beforeVal, afterVal);
 100             if (beforeVal.equals(afterVal)) {
 101                 after.remove(key);
 102                 System.out.printf(" --- removed\n");
 103             } else {
 104                 System.out.printf(" --- NOT removed\n");
 105             }
 106         }
 107 
 108         // now look at the currency.properties
 109         Properties p = new Properties();
 110         try (Reader reader = new FileReader(propsFile)) {
 111             p.load(reader);
 112         }
 113 
 114         // test each replacements
 115         keys = p.stringPropertyNames();
 116         Pattern propertiesPattern =
 117             Pattern.compile("([A-Z]{3})\\s*,\\s*(\\d{3})\\s*,\\s*" +
 118                 "(\\d+)\\s*,?\\s*(\\d{4}-\\d{2}-\\d{2}T\\d{2}:" +
 119                 "\\d{2}:\\d{2})?");
 120         for (String key: keys) {
 121             String val = p.getProperty(key);
 122             try {
 123                 if (val.chars().map(c -> c == ',' ? 1 : 0).sum() >= 3
 124                         && !isPastCutoverDate(val)) {
 125                     System.out.println("Skipping " + key + " since date is in future");
 126                     continue; // skip since date in future (no effect)
 127                 }
 128             } catch (ParseException pe) {
 129                 // swallow - currency class should not honour this value
 130                 continue;
 131             }
 132             String afterVal = after.getProperty(key);
 133             System.out.printf("Testing key: %s, val: %s... ", key, val);
 134             System.out.println("AfterVal is : " + afterVal);
 135 
 136             if (afterVal == null) {
 137                 System.out.println("Testing key " + key + " is ignored"
 138                         + " because of the inconsistent numeric code and/or"
 139                         + " dfd for the given currency code: "+val);
 140                 continue;
 141             }
 142 
 143             Matcher m = propertiesPattern.matcher(val.toUpperCase(Locale.ROOT));
 144             if (!m.find()) {
 145                 // format is not recognized.
 146                 System.out.printf("Format is not recognized.\n");
 147                 if (afterVal != null) {
 148                     throw new RuntimeException("Currency data replacement for "+key+" failed: It was incorrectly altered to "+afterVal);
 149                 }
 150 
 151                 // ignore this
 152                 continue;
 153             }
 154 
 155             String code = m.group(1);
 156             int numeric = Integer.parseInt(m.group(2));
 157             int fraction = Integer.parseInt(m.group(3));
 158             if (fraction > 9) {
 159                 System.out.println("Skipping since the fraction is greater than 9");
 160                 continue;
 161             }
 162 
 163             Matcher mAfter = propertiesPattern.matcher(afterVal);
 164             mAfter.find();
 165 
 166             String codeAfter = mAfter.group(1);
 167             int numericAfter = Integer.parseInt(mAfter.group(2));
 168             int fractionAfter = Integer.parseInt(mAfter.group(3));
 169             if (code.equals(codeAfter) &&
 170                 (numeric == numericAfter)&&
 171                 (fraction == fractionAfter)) {
 172                 after.remove(key);
 173             } else {
 174                 throw new RuntimeException("Currency data replacement for "+key+" failed: actual: (alphacode: "+codeAfter+", numcode: "+numericAfter+", fraction: "+fractionAfter+"), expected:  (alphacode: "+code+", numcode: "+numeric+", fraction: "+fraction+")");
 175             }
 176             System.out.printf("Success!\n");
 177         }
 178         if (!after.isEmpty()) {
 179 
 180             keys = after.stringPropertyNames();
 181             for (String key : keys) {
 182                 String modified = after.getProperty(key);
 183                 if(!p.containsValue(modified)) {
 184                     throw new RuntimeException("Unnecessary modification was"
 185                             + " made to county: "+ key + " with currency value:"
 186                                     + " " + modified);
 187                 } else {
 188                     System.out.println(key + " modified by an entry in"
 189                             + " currency.properties with currency value "
 190                             + modified);
 191                 }
 192             }
 193         }
 194     }
 195 
 196     private static void bug7102969() {
 197         // check the correct overriding of special case entries
 198         Currency cur = Currency.getInstance(new Locale("", "JP"));
 199         if (!cur.getCurrencyCode().equals("ABC")) {
 200             throw new RuntimeException("[Expected: ABC as currency code of JP, found: "
 201                     + cur.getCurrencyCode() + "]");
 202         }
 203 
 204         /* check if the currency instance is returned by
 205          * getAvailableCurrencies() method
 206          */
 207         if (!Currency.getAvailableCurrencies().contains(cur)) {
 208             throw new RuntimeException("[The Currency instance ["
 209                     + cur.getCurrencyCode() + ", "
 210                     + cur.getNumericCode() + ", "
 211                     + cur.getDefaultFractionDigits()
 212                     + "] is not available in the currencies list]");
 213         }
 214 
 215     }
 216 
 217     private static void bug8157138() {
 218 
 219         /* check the currencies which exist only as a special case are
 220          * accessible i.e. it should not throw IllegalArgumentException
 221          */
 222         try {
 223             Currency.getInstance("MAD");
 224         } catch (IllegalArgumentException ex) {
 225             throw new RuntimeException("Test Failed: "
 226                     + "special case currency instance MAD not found"
 227                     + " via Currency.getInstance(\"MAD\")");
 228         }
 229 
 230         try {
 231             Currency.getInstance("ABC");
 232         } catch (IllegalArgumentException ex) {
 233             throw new RuntimeException("Test Failed: "
 234                     + "special case currency instance ABC not found"
 235                     + " via Currency.getInstance(\"ABC\")");
 236         }
 237 
 238         /* check the currency value is returned by getAvailableCurrencies()
 239          * method
 240         */
 241         List<Currency> list = Currency.getAvailableCurrencies().stream()
 242                 .filter(cur -> cur.getCurrencyCode().equals("MAD"))
 243                 .collect(Collectors.toList());
 244 
 245         if (list.isEmpty()) {
 246             throw new RuntimeException("Test Failed: "
 247                     + "special case currency instance MAD not found"
 248                     + " in Currency.getAvailableCurrencies() list");
 249         }
 250 
 251         list = Currency.getAvailableCurrencies().stream()
 252                 .filter(cur -> cur.getCurrencyCode().equals("ABC"))
 253                 .collect(Collectors.toList());
 254 
 255         if (list.isEmpty()) {
 256             throw new RuntimeException("Test Failed: "
 257                     + "special case currency instance ABC not found"
 258                     + " in Currency.getAvailableCurrencies() list");
 259         }
 260 
 261     }
 262 
 263     private static void bug8190904() {
 264         // should throw IllegalArgumentException as currency code
 265         // does not exist as valid ISO 4217 code and failed to load
 266         // from currency.properties file because of inconsistent numeric/dfd
 267         try {
 268             Currency.getInstance("MCC");
 269             throw new RuntimeException("[FAILED: Should throw"
 270                     + " IllegalArgumentException for invalid currency code]");
 271         } catch (IllegalArgumentException ex) {
 272             // expected to throw IllegalArgumentException
 273         }
 274 
 275         // should keep the XOF instance as XOF,952,0, as the XOF entries in
 276         // currency.properties IT=XOF,952,1, XY=XOF,955,0 are ignored because
 277         // of inconsistency in numeric code and/or dfd
 278         checkCurrencyInstance("XOF", 952, 0);
 279         // property entry "AS=USD,841,2" should change all occurences
 280         // of USD with USD,841,2
 281         checkCurrencyInstance("USD", 841, 2);
 282     }
 283 
 284     /**
 285      * Test the numeric code and fraction of the Currency instance obtained
 286      * by given currencyCode, with the expected numericCode and fraction
 287      */
 288     private static void checkCurrencyInstance(String currencyCode,
 289             int numericCode, int fraction) {
 290         Currency cur = Currency.getInstance(currencyCode);
 291         if (cur.getNumericCode() != numericCode
 292                 || cur.getDefaultFractionDigits() != fraction) {
 293             throw new RuntimeException("[FAILED: Incorrect numeric code or"
 294                     + " dfd for currency code: " + currencyCode + "]");
 295         }
 296     }
 297 
 298     private static boolean isPastCutoverDate(String s)
 299             throws IndexOutOfBoundsException, NullPointerException, ParseException {
 300         String dateString = s.substring(s.lastIndexOf(',')+1, s.length()).trim();
 301         SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.ROOT);
 302         format.setTimeZone(TimeZone.getTimeZone("GMT"));
 303         format.setLenient(false);
 304 
 305         long time = format.parse(dateString).getTime();
 306         if (System.currentTimeMillis() - time >= 0L) {
 307             return true;
 308         } else {
 309             return false;
 310         }
 311     }
 312 
 313 }