1 /* 2 ****************************************************************************** 3 * Copyright (C) 2004-2013, International Business Machines Corporation and * 4 * others. All Rights Reserved. * 5 ****************************************************************************** 6 */ 7 package org.unicode.cldr.tool; 8 9 import java.io.BufferedReader; 10 import java.io.File; 11 import java.io.IOException; 12 import java.io.PrintWriter; 13 import java.util.ArrayList; 14 import java.util.Arrays; 15 import java.util.Collection; 16 import java.util.Comparator; 17 import java.util.HashMap; 18 import java.util.HashSet; 19 import java.util.Iterator; 20 import java.util.LinkedHashMap; 21 import java.util.List; 22 import java.util.Map; 23 import java.util.Map.Entry; 24 import java.util.Set; 25 import java.util.TreeMap; 26 import java.util.TreeSet; 27 import java.util.regex.Matcher; 28 import java.util.regex.Pattern; 29 30 import org.unicode.cldr.draft.FileUtilities; 31 import org.unicode.cldr.util.CLDRConfig; 32 import org.unicode.cldr.util.CLDRFile; 33 import org.unicode.cldr.util.CLDRPaths; 34 import org.unicode.cldr.util.CldrUtility; 35 import org.unicode.cldr.util.Factory; 36 import org.unicode.cldr.util.Iso639Data; 37 import org.unicode.cldr.util.IsoCurrencyParser; 38 import org.unicode.cldr.util.IsoCurrencyParser.Data; 39 import org.unicode.cldr.util.IsoRegionData; 40 import org.unicode.cldr.util.Level; 41 import org.unicode.cldr.util.Log; 42 import org.unicode.cldr.util.Organization; 43 import org.unicode.cldr.util.Pair; 44 import org.unicode.cldr.util.PatternCache; 45 import org.unicode.cldr.util.StandardCodes; 46 import org.unicode.cldr.util.SupplementalDataInfo; 47 import org.unicode.cldr.util.Tabber; 48 import org.unicode.cldr.util.TimezoneFormatter; 49 import org.unicode.cldr.util.UnicodeSetPrettyPrinter; 50 import org.unicode.cldr.util.XPathParts; 51 import org.unicode.cldr.util.props.ICUPropertyFactory; 52 53 import com.google.common.base.Joiner; 54 import com.google.common.collect.ImmutableMap; 55 import com.ibm.icu.dev.util.UnicodeMap; 56 import com.ibm.icu.dev.util.UnicodeMapIterator; 57 import com.ibm.icu.impl.Relation; 58 import com.ibm.icu.impl.Row; 59 import com.ibm.icu.impl.Row.R2; 60 import com.ibm.icu.impl.Row.R3; 61 import com.ibm.icu.text.Collator; 62 import com.ibm.icu.text.NumberFormat; 63 import com.ibm.icu.text.RuleBasedCollator; 64 import com.ibm.icu.text.Transform; 65 import com.ibm.icu.text.UnicodeSet; 66 import com.ibm.icu.util.ULocale; 67 68 /** 69 * Simple program to count the amount of data in CLDR. Internal Use. 70 */ 71 public class CountItems { 72 73 private static final Collator ROOT_PRIMARY_COLLATOR = Collator.getInstance(ULocale.ROOT) 74 .setStrength2(Collator.PRIMARY); 75 76 static final String needsTranslationString = "America/Buenos_Aires " // America/Rio_Branco 77 + " America/Manaus America/Belem " 78 + " America/Campo_Grande America/Sao_Paulo " 79 + " Australia/Perth Australia/Darwin Australia/Brisbane Australia/Adelaide Australia/Sydney Australia/Hobart " 80 + " America/Vancouver America/Edmonton America/Regina America/Winnipeg America/Toronto America/Halifax America/St_Johns " 81 + " Asia/Jakarta " 82 + " America/Tijuana America/Hermosillo America/Chihuahua America/Mexico_City " 83 + " Europe/Moscow Europe/Kaliningrad Europe/Moscow Asia/Yekaterinburg Asia/Novosibirsk Asia/Yakutsk Asia/Vladivostok" 84 + " Pacific/Honolulu America/Indiana/Indianapolis America/Anchorage " 85 + " America/Los_Angeles America/Phoenix America/Denver America/Chicago America/Indianapolis" 86 + " America/New_York"; 87 88 static final ImmutableMap<String, String> country_map = ImmutableMap.<String, String>builder() 89 .put("AQ", "http://www.worldtimezone.com/time-antarctica24.php") 90 .put("AR", "http://www.worldtimezone.com/time-south-america24.php") 91 .put("AU", "http://www.worldtimezone.com/time-australia24.php") 92 .put("BR", "http://www.worldtimezone.com/time-south-america24.php") 93 .put("CA", "http://www.worldtimezone.com/time-canada24.php") 94 .put("CD", "http://www.worldtimezone.com/time-africa24.php") 95 .put("CL", "http://www.worldtimezone.com/time-south-america24.php") 96 .put("CN", "http://www.worldtimezone.com/time-cis24.php") 97 .put("EC", "http://www.worldtimezone.com/time-south-america24.php") 98 .put("ES", "http://www.worldtimezone.com/time-europe24.php") 99 .put("FM", "http://www.worldtimezone.com/time-oceania24.php") 100 .put("GL", "http://www.worldtimezone.com/index24.php") 101 .put("ID", "http://www.worldtimezone.com/time-asia24.php") 102 .put("KI", "http://www.worldtimezone.com/time-oceania24.php") 103 .put("KZ", "http://www.worldtimezone.com/time-cis24.php") 104 .put("MH", "http://www.worldtimezone.com/time-oceania24.php") 105 .put("MN", "http://www.worldtimezone.com/time-cis24.php") 106 .put("MX", "http://www.worldtimezone.com/index24.php") 107 .put("MY", "http://www.worldtimezone.com/time-asia24.php") 108 .put("NZ", "http://www.worldtimezone.com/time-oceania24.php") 109 .put("PF", "http://www.worldtimezone.com/time-oceania24.php") 110 .put("PT", "http://www.worldtimezone.com/time-europe24.php") 111 .put("RU", "http://www.worldtimezone.com/time-russia24.php") 112 .put("SJ", "http://www.worldtimezone.com/index24.php") 113 .put("UA", "http://www.worldtimezone.com/time-cis24.php") 114 .put("UM", "http://www.worldtimezone.com/time-oceania24.php") 115 .put("US", "http://www.worldtimezone.com/time-usa24.php") 116 .put("UZ", "http://www.worldtimezone.com/time-cis24.php") 117 .build(); 118 119 /** 120 * Count the data. 121 * 122 * @throws IOException 123 */ main(String[] args)124 public static void main(String[] args) throws Exception { 125 double deltaTime = System.currentTimeMillis(); 126 try { 127 String methodName = System.getProperty("method"); 128 if (methodName != null) { 129 CldrUtility.callMethod(methodName, CountItems.class); 130 } else { 131 ShowZoneEquivalences.getZoneEquivalences(); 132 } 133 } finally { 134 deltaTime = System.currentTimeMillis() - deltaTime; 135 System.out.println("Elapsed: " + deltaTime / 1000.0 + " seconds"); 136 System.out.println("Done"); 137 } 138 } 139 subheader(PrintWriter out, Tabber tabber)140 static void subheader(PrintWriter out, Tabber tabber) { 141 // out.println("<tr><td colspan='6' class='gap'> </td></tr>"); 142 out.println(tabber.process("Cnty" + "\t" + "Grp" + "\t" + "ZoneID" + "\t" 143 + "Formatted ID" + "\t" + "MaxOffset" + "\t" + "MinOffset")); 144 } 145 146 /** 147 * 148 */ getPatternBlocks()149 private static void getPatternBlocks() { 150 UnicodeSet patterns = new UnicodeSet("[:pattern_syntax:]"); 151 UnicodeSet unassigned = new UnicodeSet("[:unassigned:]"); 152 UnicodeSet punassigned = new UnicodeSet(patterns).retainAll(unassigned); 153 UnicodeMap<String> blocks = ICUPropertyFactory.make().getProperty("block") 154 .getUnicodeMap(); 155 blocks.setMissing("<Reserved-Block>"); 156 // blocks.composeWith(new UnicodeMap().putAll(new UnicodeSet(patterns).retainAll(unassigned),"<reserved>"), 157 // new UnicodeMap.Composer() { 158 // public Object compose(int codePoint, Object a, Object b) { 159 // if (a == null) { 160 // return b; 161 // } 162 // if (b == null) { 163 // return a; 164 // } 165 // return a.toString() + " " + b.toString(); 166 // }}); 167 for (UnicodeMapIterator<String> it = new UnicodeMapIterator<>(blocks); it 168 .nextRange();) { 169 UnicodeSet range = new UnicodeSet(it.codepoint, it.codepointEnd); 170 boolean hasPat = range.containsSome(patterns); 171 String prefix = !hasPat ? "Not-Syntax" 172 : !range.containsSome(unassigned) ? "Closed" : !range 173 .containsSome(punassigned) ? "Closed2" : "Open"; 174 175 boolean show = (prefix.equals("Open") || prefix.equals("Closed2")); 176 177 if (show) 178 System.out.println(); 179 System.out.println(prefix + "\t" + range + "\t" + it.value); 180 if (show) { 181 System.out.println(new UnicodeMap<String>().putAll(unassigned, "<reserved>") 182 .putAll(punassigned, "<reserved-for-syntax>").setMissing( 183 "<assigned>") 184 .putAll(range.complement(), null)); 185 } 186 } 187 } 188 189 /** 190 * @throws IOException 191 * 192 */ showExemplars()193 private static void showExemplars() throws IOException { 194 PrintWriter out = FileUtilities.openUTF8Writer(CLDRPaths.GEN_DIRECTORY, "fixed_exemplars.txt"); 195 Factory cldrFactory = Factory.make(CLDRPaths.MAIN_DIRECTORY, ".*"); 196 Set<String> locales = cldrFactory.getAvailable(); 197 for (Iterator<String> it = locales.iterator(); it.hasNext();) { 198 System.out.print('.'); 199 String locale = it.next(); 200 CLDRFile cldrfile = cldrFactory.make(locale, false); 201 String v = cldrfile 202 .getStringValue("//ldml/characters/exemplarCharacters"); 203 if (v == null) 204 continue; 205 UnicodeSet exemplars = new UnicodeSet(v); 206 if (exemplars.size() != 0 && exemplars.size() < 500) { 207 Collator col = Collator.getInstance(new ULocale(locale)); 208 Collator spaceCol = Collator.getInstance(new ULocale(locale)); 209 spaceCol.setStrength(Collator.PRIMARY); 210 out.println(locale + ":\t\u200E" + v + '\u200E'); 211 String fixed = new UnicodeSetPrettyPrinter() 212 .setOrdering(col != null ? col : Collator.getInstance(ULocale.ROOT)) 213 .setSpaceComparator(spaceCol != null ? spaceCol : ROOT_PRIMARY_COLLATOR) 214 .setCompressRanges(true) 215 .format(exemplars); 216 out.println(" =>\t\u200E" + fixed + '\u200E'); 217 218 verifyEquality(exemplars, new UnicodeSet(fixed)); 219 out.flush(); 220 } 221 } 222 out.close(); 223 } 224 225 /** 226 * 227 */ verifyEquality(UnicodeSet exemplars, UnicodeSet others)228 private static void verifyEquality(UnicodeSet exemplars, UnicodeSet others) { 229 if (others.equals(exemplars)) 230 return; 231 System.out.println("FAIL\ta-b\t" 232 + new UnicodeSet(exemplars).removeAll(others)); 233 System.out.println("\tb-a\t" + new UnicodeSet(others).removeAll(exemplars)); 234 } 235 236 /** 237 * 238 */ generateSupplementalCurrencyItems()239 public static void generateSupplementalCurrencyItems() { 240 IsoCurrencyParser isoCurrencyParser = IsoCurrencyParser.getInstance(); 241 Relation<String, Data> codeList = isoCurrencyParser.getCodeList(); 242 Map<String, String> numericTocurrencyCode = new TreeMap<>(); 243 StringBuffer list = new StringBuffer(); 244 245 for (Iterator<String> it = codeList.keySet().iterator(); it.hasNext();) { 246 String currencyCode = it.next(); 247 int numericCode = -1; 248 Set<Data> dataSet = codeList.getAll(currencyCode); 249 boolean first = true; 250 for (Data data : dataSet) { 251 if (first) { 252 first = false; 253 } 254 numericCode = data.getNumericCode(); 255 } 256 257 String strNumCode = "" + numericCode; 258 String otherCode = numericTocurrencyCode.get(strNumCode); 259 if (otherCode != null) { 260 System.out.println("Warning: duplicate code " + otherCode + 261 "for " + numericCode); 262 } 263 numericTocurrencyCode.put(strNumCode, currencyCode); 264 if (list.length() != 0) 265 list.append(" "); 266 String currencyLine = "<currencyCodes type=" + "\"" + currencyCode + 267 "\"" + " numeric=" + "\"" + numericCode + "\"/>"; 268 list.append(currencyLine); 269 System.out.println(currencyLine); 270 271 } 272 System.out.println(); 273 274 } 275 276 /** 277 * 278 */ generateCurrencyItems()279 public static void generateCurrencyItems() { 280 IsoCurrencyParser isoCurrencyParser = IsoCurrencyParser.getInstance(); 281 Relation<String, Data> codeList = isoCurrencyParser.getCodeList(); 282 StringBuffer list = new StringBuffer(); 283 for (Iterator<String> it = codeList.keySet().iterator(); it.hasNext();) { 284 // String lastField = (String) it.next(); 285 // String zone = (String) fullMap.get(lastField); 286 String currencyCode = it.next(); 287 Set<Data> dataSet = codeList.getAll(currencyCode); 288 boolean first = true; 289 for (Data data : dataSet) { 290 if (first) { 291 System.out.print(currencyCode); 292 first = false; 293 } 294 System.out.println("\t" + data); 295 } 296 297 if (list.length() != 0) 298 list.append(" "); 299 list.append(currencyCode); 300 301 } 302 System.out.println(); 303 String sep = CldrUtility.LINE_SEPARATOR + "\t\t\t\t"; 304 // "((?:[-+_A-Za-z0-9]+[/])+[A-Za-z0-9])[-+_A-Za-z0-9]*" 305 String broken = CldrUtility.breakLines(list.toString(), sep, PatternCache.get( 306 "([A-Z])[A-Z][A-Z]").matcher(""), 80); 307 assert (list.toString().equals(broken.replace(sep, " "))); 308 //System.out.println("\t\t\t<variable id=\"$currency\" type=\"choice\">" 309 // + broken + CldrUtility.LINE_SEPARATOR + "\t\t\t</variable>"); 310 Set<String> isoTextFileCodes = StandardCodes.make().getAvailableCodes( 311 "currency"); 312 Set<String> temp = new TreeSet<>(codeList.keySet()); 313 temp.removeAll(isoTextFileCodes); 314 if (temp.size() != 0) { 315 throw new IllegalArgumentException("Missing from ISO4217.txt file: " + temp); 316 } 317 } 318 genSupplementalZoneData()319 public static void genSupplementalZoneData() throws IOException { 320 genSupplementalZoneData(false); 321 } 322 genSupplementalZoneData(boolean skipUnaliased)323 public static void genSupplementalZoneData(boolean skipUnaliased) 324 throws IOException { 325 RuleBasedCollator col = (RuleBasedCollator) Collator.getInstance(); 326 col.setNumericCollation(true); 327 StandardCodes sc = StandardCodes.make(); 328 Map<String, String> zone_country = sc.getZoneToCounty(); 329 Map<String, Set<String>> country_zone = sc.getCountryToZoneSet(); 330 Factory cldrFactory = Factory.make(CLDRPaths.MAIN_DIRECTORY, ".*"); 331 CLDRFile english = cldrFactory.make("en", true); 332 333 writeZonePrettyPath(col, zone_country, english); 334 writeMetazonePrettyPath(); 335 336 Map<String, String> old_new = sc.getZoneLinkold_new(); 337 Map<String, Set<String>> new_old = new TreeMap<>(); 338 339 for (Iterator<String> it = old_new.keySet().iterator(); it.hasNext();) { 340 String old = it.next(); 341 String newOne = old_new.get(old); 342 Set<String> oldSet = new_old.get(newOne); 343 if (oldSet == null) 344 new_old.put(newOne, oldSet = new TreeSet<>()); 345 oldSet.add(old); 346 } 347 Map<String, String> fullMap = new TreeMap<>(col); 348 for (Iterator<String> it = zone_country.keySet().iterator(); it.hasNext();) { 349 String zone = it.next(); 350 String defaultName = TimezoneFormatter.getFallbackName(zone); 351 Object already = fullMap.get(defaultName); 352 if (already != null) 353 System.out.println("CONFLICT: " + already + ", " + zone); 354 fullMap.put(defaultName, zone); 355 } 356 // fullSet.addAll(zone_country.keySet()); 357 // fullSet.addAll(new_old.keySet()); 358 359 System.out 360 .println("<!-- Generated by org.unicode.cldr.tool.CountItems -->"); 361 System.out.println("<supplementalData>"); 362 System.out.println("\t<timezoneData>"); 363 System.out.println(); 364 365 Set<String> multizone = new TreeSet<>(); 366 for (Iterator<String> it = country_zone.keySet().iterator(); it.hasNext();) { 367 String country = it.next(); 368 Set<String> zones = country_zone.get(country); 369 if (zones != null && zones.size() != 1) 370 multizone.add(country); 371 } 372 373 System.out.println("\t\t<zoneFormatting multizone=\"" 374 + toString(multizone, " ") + "\"" + " tzidVersion=\"" 375 + sc.getZoneVersion() + "\"" + ">"); 376 377 Set<String> orderedSet = new TreeSet<>(col); 378 orderedSet.addAll(zone_country.keySet()); 379 orderedSet.addAll(sc.getDeprecatedZoneIDs()); 380 StringBuffer tzid = new StringBuffer(); 381 382 for (Iterator<String> it = orderedSet.iterator(); it.hasNext();) { 383 // String lastField = (String) it.next(); 384 // String zone = (String) fullMap.get(lastField); 385 String zone = it.next(); 386 if (tzid.length() != 0) 387 tzid.append(' '); 388 tzid.append(zone); 389 390 String country = zone_country.get(zone); 391 if (country == null) 392 continue; // skip deprecated 393 394 Set<String> aliases = new_old.get(zone); 395 if (aliases != null) { 396 aliases = new TreeSet<>(aliases); 397 aliases.remove(zone); 398 } 399 if (skipUnaliased) 400 if (aliases == null || aliases.size() == 0) 401 continue; 402 403 System.out.println("\t\t\t<zoneItem" 404 + " type=\"" 405 + zone 406 + "\"" 407 + " territory=\"" 408 + country 409 + "\"" 410 + (aliases != null && aliases.size() > 0 ? " aliases=\"" 411 + toString(aliases, " ") + "\"" : "") 412 + "/>"); 413 } 414 415 System.out.println("\t\t</zoneFormatting>"); 416 System.out.println(); 417 System.out.println("\t</timezoneData>"); 418 System.out.println("</supplementalData>"); 419 System.out.println(); 420 String sep = CldrUtility.LINE_SEPARATOR + "\t\t\t\t"; 421 // "((?:[-+_A-Za-z0-9]+[/])+[A-Za-z0-9])[-+_A-Za-z0-9]*" 422 String broken = CldrUtility.breakLines(tzid, sep, PatternCache.get( 423 "((?:[-+_A-Za-z0-9]+[/])+[-+_A-Za-z0-9])[-+_A-Za-z0-9]*").matcher(""), 424 80); 425 assert (tzid.toString().equals(broken.replace(sep, " "))); 426 System.out.println("\t\t\t<variable id=\"$tzid\" type=\"choice\">" + broken 427 + CldrUtility.LINE_SEPARATOR + "\t\t\t</variable>"); 428 } 429 writeMetazonePrettyPath()430 public static void writeMetazonePrettyPath() { 431 CLDRConfig testInfo = ToolConfig.getToolInstance(); 432 Map<String, Map<String, String>> map = testInfo.getSupplementalDataInfo().getMetazoneToRegionToZone(); 433 Map zoneToCountry = StandardCodes.make().getZoneToCounty(); 434 Set<Pair<String, String>> results = new TreeSet<>(); 435 Map<String, String> countryToContinent = getCountryToContinent(testInfo.getSupplementalDataInfo(), 436 testInfo.getEnglish()); 437 438 for (String metazone : map.keySet()) { 439 Map<String, String> regionToZone = map.get(metazone); 440 String zone = regionToZone.get("001"); 441 if (zone == null) { 442 throw new IllegalArgumentException("Missing 001 for metazone " + metazone); 443 } 444 String continent = zone.split("/")[0]; 445 446 final Object country = zoneToCountry.get(zone); 447 results.add(new Pair<>(continent + "\t" + country + "\t" + countryToContinent.get(country) 448 + "\t" + metazone, metazone)); 449 } 450 for (Pair<String, String> line : results) { 451 System.out.println("'" + line.getSecond() + "'\t>\t'\t" + line.getFirst() + "\t'"); 452 } 453 } 454 getCountryToContinent(SupplementalDataInfo supplementalDataInfo, CLDRFile english)455 private static Map<String, String> getCountryToContinent(SupplementalDataInfo supplementalDataInfo, CLDRFile english) { 456 Relation<String, String> countryToContinent = Relation.of(new TreeMap<String, Set<String>>(), TreeSet.class); 457 Set<String> continents = new HashSet<>(Arrays.asList("002", "019", "142", "150", "009")); 458 // note: we don't need more than 3 levels 459 for (String continent : continents) { 460 final Set<String> subcontinents = supplementalDataInfo.getContained(continent); 461 countryToContinent.putAll(subcontinents, continent); 462 for (String subcontinent : subcontinents) { 463 if (subcontinent.equals("EU")) continue; 464 final Set<String> countries = supplementalDataInfo.getContained(subcontinent); 465 countryToContinent.putAll(countries, continent); 466 } 467 } 468 // convert to map 469 Map<String, String> results = new TreeMap<>(); 470 for (String item : countryToContinent.keySet()) { 471 final Set<String> containees = countryToContinent.getAll(item); 472 if (containees.size() != 1) { 473 throw new IllegalArgumentException(item + "\t" + containees); 474 } 475 results.put(item, english.getName(CLDRFile.TERRITORY_NAME, containees.iterator().next())); 476 } 477 return results; 478 } 479 writeZonePrettyPath(RuleBasedCollator col, Map<String, String> zone_country, CLDRFile english)480 private static void writeZonePrettyPath(RuleBasedCollator col, Map<String, String> zone_country, 481 CLDRFile english) throws IOException { 482 System.out.println("Writing zonePrettyPath"); 483 Set<String> masked = new HashSet<>(); 484 Map<String, String> zoneNew_Old = new TreeMap<>(col); 485 String lastZone = "XXX"; 486 for (String zone : new TreeSet<>(zone_country.keySet())) { 487 String[] parts = zone.split("/"); 488 String newPrefix = zone_country.get(zone); // english.getName("tzid", zone_country.get(zone), 489 // false).replace(' ', '_'); 490 if (newPrefix.equals("001")) { 491 newPrefix = "ZZ"; 492 } 493 parts[0] = newPrefix; 494 String newName; 495 if (parts.length > 2) { 496 System.out.println("\tMultifield: " + zone); 497 if (parts.length == 3 && parts[1].equals("Argentina")) { 498 newName = parts[0] + "/" + parts[1]; 499 } else { 500 newName = CldrUtility.join(parts, "/"); 501 } 502 } else { 503 newName = CldrUtility.join(parts, "/"); 504 } 505 zoneNew_Old.put(newName, zone); 506 if (zone.startsWith(lastZone)) { 507 masked.add(zone); // find "masked items" and do them first. 508 } else { 509 lastZone = zone; 510 } 511 } 512 513 Log.setLog(CLDRPaths.GEN_DIRECTORY + "/supplemental/prettyPathZone.txt"); 514 String lastCountry = ""; 515 for (int i = 0; i < 2; ++i) { 516 Set<String> orderedList = zoneNew_Old.keySet(); 517 if (i == 0) { 518 Log 519 .println("# Short IDs for zone names: country code + last part of TZID"); 520 Log 521 .println("# First are items that would be masked, and are moved forwards and sorted in reverse order"); 522 Log.println(); 523 //Comparator c; 524 Set<String> temp = new TreeSet<>(new ReverseComparator<>(col)); 525 temp.addAll(orderedList); 526 orderedList = temp; 527 } else { 528 Log.println(); 529 Log.println("# Normal items, sorted by country code"); 530 Log.println(); 531 } 532 533 // do masked items first 534 535 for (String newName : orderedList) { 536 String oldName = zoneNew_Old.get(newName); 537 if (masked.contains(oldName) != (i == 0)) { 538 continue; 539 } 540 String newCountry = newName.split("/")[0]; 541 if (!newCountry.equals(lastCountry)) { 542 Log.println("# " + newCountry + "\t" 543 + english.getName("territory", newCountry)); 544 lastCountry = newCountry; 545 } 546 Log.println("\t'" + oldName + "'\t>\t'" + newName + "';"); 547 } 548 } 549 Log.close(); 550 System.out.println("Done Writing zonePrettyPath"); 551 } 552 553 public static class ReverseComparator<T> implements Comparator<T> { 554 Comparator<T> other; 555 ReverseComparator(Comparator<T> other)556 public ReverseComparator(Comparator<T> other) { 557 this.other = other; 558 } 559 560 @Override compare(T o1, T o2)561 public int compare(T o1, T o2) { 562 return other.compare(o2, o1); 563 } 564 } 565 getSubtagVariables2()566 public static void getSubtagVariables2() throws IOException { 567 Log.setLogNoBOM(CLDRPaths.GEN_DIRECTORY + "/supplemental", "supplementalMetadata.xml"); 568 BufferedReader oldFile = FileUtilities.openUTF8Reader(CLDRPaths.SUPPLEMENTAL_DIRECTORY, "supplementalMetadata.xml"); 569 CldrUtility.copyUpTo(oldFile, PatternCache.get("\\s*<!-- start of data generated with CountItems.*"), 570 Log.getLog(), true); 571 572 Map<String, String> variableSubstitutions = getVariables(VariableType.partial); 573 for (Entry<String, String> type : variableSubstitutions.entrySet()) { 574 Log.println(type.getValue()); 575 } 576 577 // String sep = CldrUtility.LINE_SEPARATOR + "\t\t\t"; 578 // String broken = CldrUtility.breakLines(CldrUtility.join(defaultLocaleContent," "), sep, 579 // PatternCache.get("(\\S)\\S*").matcher(""), 80); 580 // 581 // Log.println("\t\t<defaultContent locales=\"" + broken + "\""); 582 // Log.println("\t\t/>"); 583 584 // Log.println("</supplementalData>"); 585 CldrUtility.copyUpTo(oldFile, PatternCache.get("\\s<!-- end of data generated by CountItems.*"), null, true); 586 CldrUtility.copyUpTo(oldFile, null, Log.getLog(), true); 587 588 Log.close(); 589 oldFile.close(); 590 } 591 592 static final SupplementalDataInfo supplementalData = SupplementalDataInfo 593 .getInstance(CLDRPaths.SUPPLEMENTAL_DIRECTORY); 594 static final StandardCodes sc = StandardCodes.make(); 595 getSubtagVariables()596 public static void getSubtagVariables() { 597 // This section no longer necessary, as it has been replaced by the new attributeValueValidity.xml 598 // 599 // System.out.println("Validity variables"); 600 // System.out.println("Cut/paste into supplementalMetadata.xml under the line"); 601 // System.out.println("<!-- start of data generated with CountItems tool ..."); 602 // Map<String, String> variableSubstitutions = getVariables(VariableType.partial); 603 604 // for (Entry<String, String> type : variableSubstitutions.entrySet()) { 605 // System.out.println(type.getValue()); 606 // } 607 // System.out.println("<!-- end of Validity variables generated with CountItems tool ..."); 608 // System.out.println(); 609 System.out.println("Language aliases"); 610 System.out.println("Cut/paste into supplementalMetadata.xml under the line"); 611 System.out.println("<!-- start of data generated with CountItems tool ..."); 612 613 Map<String, Map<String, String>> languageReplacement = StandardCodes.getLStreg().get("language"); 614 Map<String, Map<String, R2<List<String>, String>>> localeAliasInfo = supplementalData.getLocaleAliasInfo(); 615 Map<String, R2<List<String>, String>> languageAliasInfo = localeAliasInfo.get("language"); 616 617 Set<String> available = Iso639Data.getAvailable(); 618 // <languageAlias type="aju" replacement="jrb"/> <!-- Moroccan Judeo-Arabic ⇒ Judeo-Arabic --> 619 Set<String> bad3letter = new HashSet<>(); 620 for (String lang : available) { 621 if (lang.length() != 2) continue; 622 String target = lang; 623 Map<String, String> lstregData = languageReplacement.get(lang); 624 if (lstregData == null) { 625 throw new IllegalArgumentException("illegal language code"); 626 } else { 627 String replacement = lstregData.get("Preferred-Value"); 628 if (replacement != null) { 629 target = replacement; 630 } 631 } 632 String alpha3 = Iso639Data.toAlpha3(lang); 633 bad3letter.add(alpha3); 634 String targetAliased; 635 if (languageAliasInfo.containsKey(target)) { 636 targetAliased = Joiner.on(" ").join(languageAliasInfo.get(target).get0()); 637 } else { 638 targetAliased = target; 639 } 640 System.out.println("\t\t\t<languageAlias type=\"" + alpha3 + "\" replacement=\"" + targetAliased 641 + "\" reason=\"overlong\"/> <!-- " + 642 Iso639Data.getNames(target) + " -->"); 643 } 644 System.out.println("\t\t\t<!-- Bibliographic -->"); 645 TreeMap<String, String> sorted = new TreeMap<>(); 646 for (String hasBiblio : Iso639Data.hasBiblio3()) { 647 String biblio = Iso639Data.toBiblio3(hasBiblio); 648 sorted.put(biblio, hasBiblio); 649 } 650 for (Entry<String, String> entry : sorted.entrySet()) { 651 String biblio = entry.getKey(); 652 String hasBiblio = entry.getValue(); 653 System.out.println("\t\t\t<languageAlias type=\"" + biblio + "\" replacement=\"" + hasBiblio 654 + "\" reason=\"bibliographic\"/> <!-- " + 655 Iso639Data.getNames(hasBiblio) + " -->"); 656 } 657 System.out.println("<!-- end of Language alises generated with CountItems tool ..."); 658 659 Set<String> encompassed = Iso639Data.getEncompassed(); 660 Set<String> macros = Iso639Data.getMacros(); 661 Map<String, String> encompassed_macro = new HashMap<>(); 662 for (Entry<String, R2<List<String>, String>> typeAndData : languageAliasInfo.entrySet()) { 663 String type = typeAndData.getKey(); 664 R2<List<String>, String> data = typeAndData.getValue(); 665 List<String> replacements = data.get0(); 666 if (!encompassed.contains(type)) continue; 667 if (replacements == null || replacements.size() != 1) continue; 668 String replacement = replacements.get(0); 669 if (macros.contains(replacement)) { 670 // we have a match, encompassed => replacement 671 encompassed_macro.put(type, replacement); 672 } 673 } 674 Set<String> missing = new TreeSet<>(); 675 missing.addAll(macros); 676 missing.remove("no"); 677 missing.remove("sh"); 678 679 missing.removeAll(encompassed_macro.values()); 680 if (missing.size() != 0) { 681 for (String missingMacro : missing) { 682 System.err.println("ERROR: Missing <languageAlias type=\"" + "???" + "\" replacement=\"" + missingMacro 683 + "\"/> <!-- ??? => " + 684 Iso639Data.getNames(missingMacro) + " -->"); 685 System.out.println("\tOptions for ???:"); 686 for (String enc : Iso639Data.getEncompassedForMacro(missingMacro)) { 687 System.out.println("\t" + enc + "\t// " + Iso639Data.getNames(enc)); 688 } 689 } 690 } 691 // verify that every macro language has a encompassed mapping to it 692 // and remember those codes 693 694 // verify that nobody contains a bad code 695 696 for (Entry<String, R2<List<String>, String>> typeAndData : languageAliasInfo.entrySet()) { 697 String type = typeAndData.getKey(); 698 List<String> replacements = typeAndData.getValue().get0(); 699 if (replacements == null) continue; 700 for (String replacement : replacements) { 701 if (bad3letter.contains(replacement)) { 702 System.err.println("ERROR: Replacement(s) for type=\"" + type + 703 "\" contains " + replacement + ", which should be: " + Iso639Data.fromAlpha3(replacement)); 704 } 705 } 706 } 707 708 // get the bad ISO codes 709 710 Factory cldrFactory = Factory.make(CLDRPaths.MAIN_DIRECTORY, ".*"); 711 CLDRFile english = cldrFactory.make("en", true); 712 713 Set<String> territories = new TreeSet<>(); 714 Relation<String, String> containers = supplementalData.getTerritoryToContained(); 715 for (String region : sc.getAvailableCodes("territory")) { 716 if (containers.containsKey(region)) continue; 717 territories.add(region); 718 } 719 System.out.println(); 720 System.out.println("Territory aliases"); 721 System.out.println("Cut/paste into supplementalMetadata.xml under the line"); 722 System.out.println("<!-- start of data generated with CountItems tool ..."); 723 //final Map<String, R2<List<String>, String>> territoryAliasInfo = localeAliasInfo.get("territory"); 724 725 addRegions(english, territories, "alpha3", "EA,EU,IC".split(","), new Transform<String, String>() { 726 @Override 727 public String transform(String region) { 728 return IsoRegionData.get_alpha3(region); 729 } 730 }); 731 addRegions(english, territories, "numeric", "AC,CP,DG,EA,EU,IC,TA".split(","), new Transform<String, String>() { 732 @Override 733 public String transform(String region) { 734 return IsoRegionData.getNumeric(region); 735 } 736 }); 737 System.out.println("<!-- end of Territory alises generated with CountItems tool ..."); 738 System.out.println(); 739 System.out.println("Deprecated codes check (informational)"); 740 // check that all deprecated codes are in fact deprecated 741 Map<String, Map<String, Map<String, String>>> fullData = StandardCodes.getLStreg(); 742 743 checkCodes("language", sc, localeAliasInfo, fullData); 744 checkCodes("script", sc, localeAliasInfo, fullData); 745 checkCodes("territory", sc, localeAliasInfo, fullData); 746 System.out.println("End of Deprecated codes check..."); 747 748 // generate mapping equivalences 749 // { "aar", "aar", "aa" }, // Afar 750 // b, t, bcp47 751 System.out.println(); 752 System.out.println("Mapping equivalences - (Informational only...)"); 753 System.out.println("{ bib , tech , bcp47 }"); 754 755 Set<R3<String, String, String>> rows = new TreeSet<>(); 756 for (String lang : Iso639Data.getAvailable()) { 757 String bib = Iso639Data.toBiblio3(lang); 758 String tech = Iso639Data.toAlpha3(lang); 759 R3<String, String, String> row = Row.of(tech, bib, lang); 760 rows.add(row); 761 } 762 for (R3<String, String, String> row : rows) { 763 String tech = row.get0(); 764 String bib = row.get1(); 765 String lang = row.get2(); 766 String name = Iso639Data.getNames(lang).iterator().next(); // english.getName(lang); 767 if ((bib != null && !lang.equals(bib)) || (tech != null && !lang.equals(tech))) { 768 System.out.println(" { \"" + bib + "\", \"" + tech + "\", \"" + lang + "\" }, // " + name); 769 } 770 } 771 System.out.println("End of Mapping equivalences..."); 772 773 // generate the codeMappings 774 // <codeMappings> 775 // <territoryCodes type="CS" numeric="891" alpha3="SCG" fips10="YI"/> 776 777 System.out.println(); 778 System.out.println("Code Mappings"); 779 System.out.println("Cut/paste into supplementaData.xml under the line"); 780 System.out.println("<!-- start of data generated with CountItems tool ..."); 781 List<String> warnings = new ArrayList<>(); 782 territories.add("QO"); 783 territories.add("EU"); 784 // territories.add("MF"); 785 //Map<String, R2<List<String>, String>> territoryAliases = supplementalData.getLocaleAliasInfo().get("territory"); 786 Relation<String, String> numeric2region = Relation.of(new HashMap<String, Set<String>>(), TreeSet.class); 787 Relation<String, String> alpha32region = Relation.of(new HashMap<String, Set<String>>(), TreeSet.class); 788 for (String region : territories) { 789 String numeric = IsoRegionData.getNumeric(region); 790 String alpha3 = IsoRegionData.get_alpha3(region); 791 numeric2region.put(numeric, region); 792 alpha32region.put(alpha3, region); 793 } 794 795 System.out.println(" <codeMappings>"); 796 797 for (String region : territories) { 798 String numeric = IsoRegionData.getNumeric(region); 799 String alpha3 = IsoRegionData.get_alpha3(region); 800 String fips10 = IsoRegionData.get_fips10(region); 801 System.out.println(" <territoryCodes" 802 + " type=\"" + region + "\"" 803 + (numeric == null ? "" : " numeric=\"" + numeric + "\"") 804 + (alpha3 == null ? "" : " alpha3=\"" + alpha3 + "\"") 805 + (fips10 == null || fips10.equals(region) ? "" : " fips10=\"" + fips10 + "\"") 806 + "/>"); 807 } 808 System.out.println(" </codeMappings>"); 809 System.out.println("<!-- end of Code Mappings generated with CountItems tool ..."); 810 System.out.println(Joiner.on(CldrUtility.LINE_SEPARATOR).join(warnings)); 811 } 812 813 enum VariableType { 814 full, partial 815 } 816 getVariables(VariableType variableType)817 public static Map<String, String> getVariables(VariableType variableType) { 818 String sep = CldrUtility.LINE_SEPARATOR + "\t\t\t\t"; 819 Map<String, String> variableSubstitutions = new LinkedHashMap<>(); 820 for (String type : new String[] { "legacy", "territory", "script", "variant" }) { 821 Set<String> i; 822 i = (variableType == VariableType.full || type.equals("legacy")) ? sc.getAvailableCodes(type) : sc.getGoodAvailableCodes(type); 823 addVariable(variableSubstitutions, type, i, sep); 824 } 825 826 Relation<String, String> bcp47Keys = supplementalData.getBcp47Keys(); 827 Relation<R2<String, String>, String> aliases = supplementalData.getBcp47Aliases(); 828 for (String key : bcp47Keys.keySet()) { 829 Set<String> keyAliases = aliases.getAll(Row.of(key, "")); 830 Set<String> rawsubtypes = bcp47Keys.getAll(key); 831 TreeSet<String> subtypes = new TreeSet<>(); 832 for (String subtype : rawsubtypes) { 833 Set<String> keySubtypeAliases = aliases.getAll(Row.of(key, subtype)); 834 if (keySubtypeAliases != null) { 835 subtypes.addAll(keySubtypeAliases); 836 } 837 } 838 subtypes.addAll(rawsubtypes); 839 String alias = (keyAliases == null ? key : keyAliases.iterator().next()) + "_XXX"; 840 addVariable(variableSubstitutions, alias, subtypes, sep); 841 } 842 return variableSubstitutions; 843 } 844 845 private static final Pattern BreakerPattern = PatternCache.get("([-_A-Za-z0-9])[-/+_A-Za-z0-9]*"); 846 addVariable(Map<String, String> variableSubstitutions, String type, Set<String> sinput, String sep)847 private static void addVariable(Map<String, String> variableSubstitutions, String type, Set<String> sinput, 848 String sep) { 849 TreeSet<String> s = new TreeSet<>(ROOT_PRIMARY_COLLATOR); 850 s.addAll(sinput); 851 852 StringBuffer b = new StringBuffer(); 853 for (String code : s) { 854 if (b.length() != 0) 855 b.append(' '); 856 b.append(code); 857 } 858 // "((?:[-+_A-Za-z0-9]+[/])+[A-Za-z0-9])[-+_A-Za-z0-9]*" 859 String broken = CldrUtility.breakLines(b, sep, BreakerPattern.matcher(""), 80); 860 assert (b.toString().equals(broken.replace(sep, " "))); 861 variableSubstitutions.put(type, "\t\t\t<variable id=\"$" + type 862 + "\" type=\"choice\">" + broken + CldrUtility.LINE_SEPARATOR + "\t\t\t</variable>"); 863 } 864 checkCodes(String type, StandardCodes sc, Map<String, Map<String, R2<List<String>, String>>> localeAliasInfo, Map<String, Map<String, Map<String, String>>> fullData)865 private static void checkCodes(String type, StandardCodes sc, 866 Map<String, Map<String, R2<List<String>, String>>> localeAliasInfo, 867 Map<String, Map<String, Map<String, String>>> fullData) { 868 Map<String, Map<String, String>> typeData = fullData.get("territory".equals(type) ? "region" : type); 869 Map<String, R2<List<String>, String>> aliasInfo = localeAliasInfo.get(type); 870 for (String code : sc.getAvailableCodes(type)) { 871 Map<String, String> subdata = typeData.get(code); 872 String deprecated = subdata.get("Deprecated"); 873 if (deprecated == null) continue; 874 String replacement = subdata.get("Preferred-Value"); 875 R2<List<String>, String> supplementalReplacements = aliasInfo.get(code); 876 if (supplementalReplacements == null) { 877 System.out.println("Deprecated in LSTR, but not in supplementalData: " + type + "\t" + code + "\t" 878 + replacement); 879 } 880 } 881 } 882 addRegions(CLDRFile english, Set<String> availableCodes, String codeType, String[] exceptions, Transform<String, String> trans)883 private static void addRegions(CLDRFile english, Set<String> availableCodes, String codeType, String[] exceptions, 884 Transform<String, String> trans) { 885 Set<String> missingRegions = new TreeSet<>(); 886 Set<String> exceptionSet = new HashSet<>(Arrays.asList(exceptions)); 887 List<String> duplicateDestroyer = new ArrayList<>(); 888 for (String region : availableCodes) { 889 890 if (exceptionSet.contains(region)) continue; 891 String alpha3 = trans.transform(region); 892 if (alpha3 == null) { 893 missingRegions.add(region); 894 continue; 895 } 896 Map<String, R2<List<String>, String>> territoryAliasInfo = supplementalData.getLocaleAliasInfo().get("territory"); 897 String result; 898 if (territoryAliasInfo.containsKey(region)) { 899 result = Joiner.on(" ").join(territoryAliasInfo.get(region).get0()); 900 } else { 901 result = region; 902 } 903 String name = english.getName(CLDRFile.TERRITORY_NAME, result); 904 if (!(duplicateDestroyer.contains(alpha3 + result + name))) { 905 duplicateDestroyer.add(alpha3 + result + name); 906 System.out.println("\t\t\t<territoryAlias type=\"" + alpha3 + "\" replacement=\"" + result 907 + "\" reason=\"overlong\"/> <!-- " + name + " -->"); 908 } 909 } 910 for (String region : missingRegions) { 911 String name = english.getName(CLDRFile.TERRITORY_NAME, region); 912 System.err.println("ERROR: Missing " + codeType + " code for " + region + "\t" + name); 913 } 914 } 915 916 /** 917 * 918 */ toString(Collection aliases, String separator)919 private static String toString(Collection aliases, String separator) { 920 StringBuffer result = new StringBuffer(); 921 boolean first = true; 922 for (Iterator<Object> it = aliases.iterator(); it.hasNext();) { 923 Object item = it.next(); 924 if (first) 925 first = false; 926 else 927 result.append(separator); 928 result.append(item); 929 } 930 return result.toString(); 931 } 932 showZoneInfo()933 public static void showZoneInfo() throws IOException { 934 StandardCodes sc = StandardCodes.make(); 935 Map<String, String> m = sc.getZoneLinkold_new(); 936 int i = 0; 937 System.out.println("/* Generated by org.unicode.cldr.tool.CountItems */"); 938 System.out.println(); 939 i = 0; 940 System.out.println("/* zoneID, canonical zoneID */"); 941 for (Iterator<String> it = m.keySet().iterator(); it.hasNext();) { 942 String old = it.next(); 943 String newOne = m.get(old); 944 System.out.println("{\"" + old + "\", \"" + newOne + "\"},"); 945 ++i; 946 } 947 System.out.println("/* Total: " + i + " */"); 948 949 System.out.println(); 950 i = 0; 951 System.out.println("/* All canonical zoneIDs */"); 952 for (Iterator<String> it = sc.getZoneData().keySet().iterator(); it.hasNext();) { 953 String old = it.next(); 954 System.out.println("\"" + old + "\","); 955 ++i; 956 } 957 System.out.println("/* Total: " + i + " */"); 958 959 Factory mainCldrFactory = Factory.make(CLDRPaths.COMMON_DIRECTORY + "main" 960 + File.separator, ".*"); 961 CLDRFile desiredLocaleFile = mainCldrFactory.make("root", true); 962 String temp = desiredLocaleFile 963 .getFullXPath("//ldml/dates/timeZoneNames/singleCountries"); 964 XPathParts parts = XPathParts.getFrozenInstance(temp); 965 String singleCountriesList = parts.findAttributes("singleCountries").get("list"); 966 Set<String> singleCountriesSet = new TreeSet<>(CldrUtility.splitList(singleCountriesList, ' ')); 967 968 Map<String, String> zone_countries = StandardCodes.make().getZoneToCounty(); 969 Map<String, Set<String>> countries_zoneSet = StandardCodes.make().getCountryToZoneSet(); 970 System.out.println(); 971 i = 0; 972 System.out.println("/* zoneID, country, isSingle */"); 973 for (Iterator<String> it = zone_countries.keySet().iterator(); it.hasNext();) { 974 String old = it.next(); 975 String newOne = zone_countries.get(old); 976 Set<String> s = countries_zoneSet.get(newOne); 977 String isSingle = (s != null && s.size() == 1 || singleCountriesSet 978 .contains(old)) ? "T" : "F"; 979 System.out.println("{\"" + old + "\", \"" + newOne + "\", \"" + isSingle 980 + "\"},"); 981 ++i; 982 } 983 System.out.println("/* Total: " + i + " */"); 984 985 if (true) 986 return; 987 988 Factory cldrFactory = Factory.make(CLDRPaths.MAIN_DIRECTORY, ".*"); 989 Map<Organization, Map<String, Level>> platform_locale_status = StandardCodes.make().getLocaleTypes(); 990 Map<String, Level> onlyLocales = platform_locale_status.get(Organization.ibm); 991 Set<String> locales = onlyLocales.keySet(); 992 CLDRFile english = cldrFactory.make("en", true); 993 for (Iterator<String> it = locales.iterator(); it.hasNext();) { 994 String locale = it.next(); 995 System.out.println(locale + "\t" + english.getName(locale) + "\t" 996 + onlyLocales.get(locale)); 997 } 998 } 999 1000 static final NumberFormat decimal = NumberFormat.getNumberInstance(); 1001 static { 1002 decimal.setGroupingUsed(true); 1003 } 1004 countItems()1005 public static void countItems() { 1006 // CLDRKey.main(new String[]{"-mde.*"}); 1007 String dir = CldrUtility.getProperty("source", CLDRPaths.MAIN_DIRECTORY); 1008 Factory cldrFactory = Factory.make(dir, ".*"); 1009 countItems(cldrFactory, false); 1010 } 1011 1012 /** 1013 * @param cldrFactory 1014 * @param resolved 1015 */ countItems(Factory cldrFactory, boolean resolved)1016 private static int countItems(Factory cldrFactory, boolean resolved) { 1017 int count = 0; 1018 int resolvedCount = 0; 1019 Set<String> locales = cldrFactory.getAvailable(); 1020 Set<String> keys = new HashSet<>(); 1021 Set<String> values = new HashSet<>(); 1022 Set<String> fullpaths = new HashSet<>(); 1023 Matcher alt = CLDRFile.ALT_PROPOSED_PATTERN.matcher(""); 1024 1025 Set<String> temp = new HashSet<>(); 1026 for (Iterator<String> it = locales.iterator(); it.hasNext();) { 1027 String locale = it.next(); 1028 if (CLDRFile.isSupplementalName(locale)) 1029 continue; 1030 CLDRFile item = cldrFactory.make(locale, false); 1031 1032 temp.clear(); 1033 for (Iterator<String> it2 = item.iterator(); it2.hasNext();) { 1034 String path = it2.next(); 1035 if (alt.reset(path).matches()) { 1036 continue; 1037 } 1038 temp.add(path); 1039 keys.add(path); 1040 values.add(item.getStringValue(path)); 1041 fullpaths.add(item.getFullXPath(path)); 1042 } 1043 int current = temp.size(); 1044 1045 CLDRFile itemResolved = cldrFactory.make(locale, true); 1046 temp.clear(); 1047 itemResolved.forEach(temp::add); 1048 int resolvedCurrent = temp.size(); 1049 1050 System.out.println(locale + "\tPlain:\t" + current + "\tResolved:\t" 1051 + resolvedCurrent + "\tUnique Paths:\t" + keys.size() 1052 + "\tUnique Values:\t" + values.size() + "\tUnique Full Paths:\t" 1053 + fullpaths.size()); 1054 count += current; 1055 resolvedCount += resolvedCurrent; 1056 } 1057 System.out.println("Total Items\t" + decimal.format(count)); 1058 System.out 1059 .println("Total Resolved Items\t" + decimal.format(resolvedCount)); 1060 System.out.println("Unique Paths\t" + decimal.format(keys.size())); 1061 System.out.println("Unique Values\t" + decimal.format(values.size())); 1062 System.out 1063 .println("Unique Full Paths\t" + decimal.format(fullpaths.size())); 1064 return count; 1065 } 1066 1067 } 1068