1 // © 2017 and later: Unicode, Inc. and others. 2 // License & terms of use: http://www.unicode.org/copyright.html 3 /* 4 ********************************************************************** 5 * Copyright (C) 2016 and later: Unicode, Inc. and others. 6 * Copyright (c) 2006-2013, International Business Machines 7 * Corporation and others. All Rights Reserved. 8 ********************************************************************** 9 * Created on 2006-7-24 ? 10 * Moved from Java 1.4 to 1.5? API by srl 2009-01-16 11 */ 12 package com.ibm.icu.dev.tools.docs; 13 14 import java.io.File; 15 import java.io.FileInputStream; 16 import java.io.IOException; 17 import java.io.InputStream; 18 import java.util.GregorianCalendar; 19 import java.util.Iterator; 20 import java.util.Map; 21 import java.util.regex.*; 22 import java.util.Set; 23 import java.util.TreeMap; 24 import java.util.TreeSet; 25 26 import javax.xml.parsers.DocumentBuilder; 27 import javax.xml.parsers.DocumentBuilderFactory; 28 import javax.xml.parsers.ParserConfigurationException; 29 import javax.xml.transform.Result; 30 import javax.xml.transform.Transformer; 31 import javax.xml.transform.TransformerConfigurationException; 32 import javax.xml.transform.TransformerException; 33 import javax.xml.transform.TransformerFactory; 34 import javax.xml.transform.dom.DOMResult; 35 import javax.xml.transform.dom.DOMSource; 36 import javax.xml.transform.stream.StreamResult; 37 import javax.xml.transform.stream.StreamSource; 38 import javax.xml.xpath.XPath; 39 import javax.xml.xpath.XPathConstants; 40 import javax.xml.xpath.XPathExpressionException; 41 import javax.xml.xpath.XPathFactory; 42 43 import org.w3c.dom.Document; 44 import org.w3c.dom.Element; 45 import org.w3c.dom.NamedNodeMap; 46 import org.w3c.dom.Node; 47 import org.w3c.dom.NodeList; 48 import org.xml.sax.InputSource; 49 import org.xml.sax.SAXException; 50 51 /* 52 A utility to report the status change between two ICU releases 53 54 To use the utility 55 1. Generate the XML files 56 (put the two ICU releases on your machine ^_^ ) 57 (generate 'Doxygen' file on Windows platform with Cygwin's help) 58 Edit the generated 'Doxygen' file under ICU4C source directory 59 a) GENERATE_XML = YES 60 b) Sync the ALIASES definiation 61 (For example, copy the ALIASES defination from ICU 3.6 62 Doxygen file to ICU 3.4 Doxygen file.) 63 c) gerenate the XML files 64 2. Build the tool 65 Download Apache Xerces Java Parser 66 Build this file with the library 67 3. Edit the api-report-config.xml file & Change the file according your real configuration 68 4. Run the tool to generate the report. 69 */ 70 71 /** 72 * CLI tool to report the status change between two ICU releases 73 * @author Raymond Yang 74 */ 75 public class StableAPI { 76 77 private static final String DOC_FOLDER = "docFolder"; 78 private static final String INDEX_XML = "index.xml"; 79 private static final String ICU_SPACE_PREFIX = "ICU "; 80 private static final String INITIALIZER_XPATH = "initializer"; 81 private static final String NAME_XPATH = "name"; 82 private static final String UVERSIONA = "uvernum_8h.xml"; 83 private static final String UVERSIONB = "uversion_8h.xml"; 84 private static final String U_ICU_VERSION = "U_ICU_VERSION"; 85 /* ICU 4.4+ */ 86 private static final String ICU_VERSION_XPATHA = "/doxygen/compounddef[@id='uvernum_8h'][@kind='file']/sectiondef[@kind='define']"; 87 /* ICU <4.4 */ 88 private static final String ICU_VERSION_XPATHB = "/doxygen/compounddef[@id='uversion_8h'][@kind='file']/sectiondef[@kind='define']"; 89 private static String ICU_VERSION_XPATH = ICU_VERSION_XPATHA; 90 91 private String leftVer; 92 private File leftDir = null; 93 // private String leftStatus; 94 private String leftMilestone = ""; 95 96 private String rightVer; 97 private File rightDir = null; 98 // private String rightStatus; 99 private String rightMilestone = ""; 100 101 private InputStream dumpCppXsltStream = null; 102 private InputStream dumpCXsltStream = null; 103 private InputStream reportXslStream = null; 104 private static final String CXSLT = "dumpAllCFunc.xslt"; 105 private static final String CPPXSLT = "dumpAllCppFunc.xslt"; 106 private static final String RPTXSLT = "genReport.xslt"; 107 108 private File dumpCppXslt; 109 private File dumpCXslt; 110 private File reportXsl; 111 private File resultFile; 112 113 static Map<String, Set<String>> simplifications = new TreeMap<String, Set<String>>(); 114 addSimplification(String prototype0, String prototype)115 static void addSimplification(String prototype0, String prototype) { 116 Set<String> s = simplifications.get(prototype); 117 if (s == null) { 118 s = new TreeSet<String>(); 119 simplifications.put(prototype, s); 120 } 121 s.add(prototype0); 122 } 123 getChangedSimplifications()124 static Set<String> getChangedSimplifications() { 125 Set<String> output = new TreeSet<String>(); 126 for (Map.Entry<String, Set<String>> e : simplifications.entrySet()) { 127 if (e.getValue().size() > 1) { 128 output.add(e.getKey()); 129 } 130 } 131 return output; 132 } 133 134 final private static String notFound = "(missing)"; 135 main(String[] args)136 public static void main(String[] args) throws TransformerException, ParserConfigurationException, SAXException, 137 IOException, XPathExpressionException { 138 139 StableAPI t = new StableAPI(); 140 t.run(args); 141 } 142 run(String[] args)143 private void run(String[] args) throws XPathExpressionException, TransformerException, ParserConfigurationException, 144 SAXException, IOException { 145 this.parseArgs(args); 146 Set<JoinedFunction> full = new TreeSet<JoinedFunction>(); 147 148 System.err.println("Reading C++..."); 149 Set<JoinedFunction> setCpp = this.getFullList(dumpCppXsltStream, dumpCppXslt.getName()); 150 full.addAll(setCpp); 151 System.out.println("read " + setCpp.size() + " C++. Reading C:"); 152 153 Set<JoinedFunction> setC = this.getFullList(dumpCXsltStream, dumpCXslt.getName()); 154 full.addAll(setC); 155 156 System.out.println("read " + setC.size() + " C. Setting node:"); 157 158 Node fullList = this.setToNode(full); 159 // t.dumpNode(fullList,""); 160 161 System.out.println("Node set. Reporting:"); 162 163 this.reportSelectedFun(fullList); 164 System.out.println("Done. Please check " + this.resultFile); 165 166 Set<String> changedSimp = getChangedSimplifications(); 167 if (!changedSimp.isEmpty()) { 168 System.out.println("--- changed simplifications ---"); 169 for (String k : changedSimp) { 170 System.out.println(k); 171 for (String s : simplifications.get(k)) { 172 System.out.println("\t" + s); 173 } 174 } 175 } 176 } 177 parseArgs(String[] args)178 private void parseArgs(String[] args) { 179 for (int i = 0; i < args.length; i++) { 180 String arg = args[i]; 181 if (arg == null || arg.length() == 0) { 182 continue; 183 } 184 if (arg.equals("--help")) { 185 printUsage(); 186 } else if (arg.equals("--oldver")) { 187 leftVer = args[++i]; 188 } else if (arg.equals("--olddir")) { 189 leftDir = new File(args[++i]); 190 } else if (arg.equals("--newver")) { 191 rightVer = args[++i]; 192 } else if (arg.equals("--newdir")) { 193 rightDir = new File(args[++i]); 194 } else if (arg.equals("--cxslt")) { 195 dumpCXslt = new File(args[++i]); 196 } else if (arg.equals("--cppxslt")) { 197 dumpCppXslt = new File(args[++i]); 198 } else if (arg.equals("--reportxslt")) { 199 reportXsl = new File(args[++i]); 200 } else if (arg.equals("--resultfile")) { 201 resultFile = new File(args[++i]); 202 } else { 203 System.out.println("Unknown option: " + arg); 204 printUsage(); 205 } 206 } 207 208 dumpCppXsltStream = loadStream(CPPXSLT, "--cppxslt", dumpCppXslt); 209 dumpCXsltStream = loadStream(CXSLT, "--cxslt", dumpCXslt); 210 reportXslStream = loadStream(RPTXSLT, "--reportxslt", reportXsl); 211 212 leftVer = trimICU(setVer(leftVer, "old", leftDir)); 213 rightVer = trimICU(setVer(rightVer, "new", rightDir)); 214 } 215 216 @SuppressWarnings("resource") loadStream(String name, String argName, File argFile)217 private InputStream loadStream(String name, String argName, File argFile) { 218 InputStream stream = null; 219 if (argFile != null) { 220 try { 221 stream = new FileInputStream(argFile); 222 System.out.println("Loaded file " + argFile.getName()); 223 } catch (IOException ioe) { 224 throw new RuntimeException( 225 "Error: Could not load " + argName + " " + argFile.getPath() + " - " + ioe.toString(), ioe); 226 } 227 } else { 228 stream = StableAPI.class.getResourceAsStream(name); 229 if (stream == null) { 230 throw new InternalError("No resource found for " + StableAPI.class.getPackage().getName() + "/" + name 231 + " - use " + argName); 232 } else { 233 System.out.println("Loaded resource " + name); 234 } 235 } 236 return stream; 237 } 238 239 private static Set<String> warnSet = new TreeSet<String>(); 240 warn(String what)241 private static void warn(String what) { 242 if (!warnSet.contains(what)) { 243 System.out.println("Warning: " + what); 244 if (warnSet.isEmpty()) { 245 System.out.println(" (These warnings are only printed one time each.)"); 246 } 247 warnSet.add(what); 248 } 249 } 250 251 private static boolean didWarnSuperTrim = false; 252 trimICU(String ver)253 private static String trimICU(String ver) { 254 Matcher icuVersionMatcher = Pattern.compile("ICU *\\d+(\\.\\d+){0,2}").matcher(ver); 255 if (icuVersionMatcher.find()) { 256 return icuVersionMatcher.group(); 257 } else { 258 warn("@whatever not followed by ICU <version number>"); 259 return ""; 260 } 261 } 262 setVer(String prevVer, String whichVer, File dir)263 private String setVer(String prevVer, String whichVer, File dir) { 264 String UVERSION = UVERSIONA; 265 if (dir == null) { 266 System.out.println("--" + whichVer + "dir not set."); 267 printUsage(); /* exits */ 268 } else if (!dir.exists() || !dir.isDirectory()) { 269 System.out.println("--" + whichVer + "dir=" + dir.getName() + " does not exist or is not a directory."); 270 printUsage(); /* exits */ 271 } 272 String result = null; 273 // looking for: <name>U_ICU_VERSION</name> in uversion_8h.xml: 274 // <initializer>"3.8.1"</initializer> 275 try { 276 File verFile = new File(dir, UVERSION); 277 if (!verFile.exists()) { 278 UVERSION = UVERSIONB; 279 ICU_VERSION_XPATH = ICU_VERSION_XPATHB; 280 verFile = new File(dir, UVERSION); 281 } else { 282 ICU_VERSION_XPATH = ICU_VERSION_XPATHA; 283 } 284 Document doc = getDocument(verFile); 285 DOMSource uvernum_h = new DOMSource(doc); 286 XPath xpath = XPathFactory.newInstance().newXPath(); 287 288 Node defines = (Node) xpath.evaluate(ICU_VERSION_XPATH, uvernum_h.getNode(), XPathConstants.NODE); 289 290 if (defines == null) { 291 System.err.println("can't load from " + verFile.getName() + ":" + ICU_VERSION_XPATH); 292 } 293 294 NodeList nList = defines.getChildNodes(); 295 for (int i = 0; result == null && (i < nList.getLength()); i++) { 296 Node ln = nList.item(i); 297 if (!"memberdef".equals(ln.getNodeName())) { 298 continue; 299 } 300 Node name = (Node) xpath.evaluate(NAME_XPATH, ln, XPathConstants.NODE); 301 if (name == null) 302 continue; 303 304 // System.err.println("Gotta node: " + name); 305 306 Node nameVal = name.getFirstChild(); 307 if (nameVal == null) 308 nameVal = name; 309 310 String nameStr = nameVal.getNodeValue(); 311 if (nameStr == null) 312 continue; 313 314 // System.err.println("Gotta name: " + nameStr); 315 316 if (nameStr.trim().equals(U_ICU_VERSION)) { 317 Node initializer = (Node) xpath.evaluate(INITIALIZER_XPATH, ln, XPathConstants.NODE); 318 if (initializer == null) 319 System.err.println("initializer with no value"); 320 Node initVal = initializer.getFirstChild(); 321 // if(initVal==null) initVal = initializer; 322 String initStr = initVal.getNodeValue().trim().replaceAll("\"", ""); 323 result = ICU_SPACE_PREFIX + initStr; 324 System.err.println("Detected " + whichVer + " version: " + result); 325 326 String milestoneOf = ""; 327 328 // TODO: #1 use UVersionInfo. (this tool doesn't depend on ICU4J yet) 329 // #2 move this to a utility function: strip/"explain" an ICU version #. 330 if (result.startsWith("ICU ")) { 331 String vers[] = result.substring(4).split("\\."); 332 int maj = Integer.parseInt(vers[0]); 333 int min = vers.length > 1 ? Integer.parseInt(vers[1]) : 0; 334 int micr = vers.length > 2 ? Integer.parseInt(vers[2]) : 0; 335 int patch = vers.length > 3 ? Integer.parseInt(vers[3]) : 0; 336 System.err.println( 337 " == [" + vers.toString() + "] " + maj + " . " + min + " . " + micr + " . " + patch); 338 if (maj >= 49) { 339 // new scheme: 49 and following. 340 String truncVersion = "ICU " + maj; 341 if (min == 0) { 342 milestoneOf = " (m" + micr + ")"; 343 System.err.println(" .. " + milestoneOf + " is a milestone towards " + truncVersion); 344 } else if (min == 1) { 345 // Don't denote as milestone 346 result = "ICU " + (maj); 347 System.err.println(" .. " + milestoneOf + " is the release of " + truncVersion); 348 } else { 349 milestoneOf = " (update #" + (min - 1) + ": " + result.substring(4) + ")"; 350 result = "ICU " + (maj); 351 System.err.println(" .. " + milestoneOf + " is an update to " + truncVersion); 352 } 353 // always truncate to major # for comparing tags. 354 result = truncVersion; 355 if (maj >= 71) { 356 // Clear minor and micro version in API change report. 357 milestoneOf = ""; 358 } 359 } else { 360 // old scheme - 1.0.* .. 4.8.* 361 String truncVersion = "ICU " + maj + "." + min; 362 if ((min % 2) == 1) { 363 milestoneOf = " (" + maj + "." + (min + 1) + "m" + micr + ")"; 364 truncVersion = "ICU " + (maj) + "." + (min + 1); 365 System.err.println(" .. " + milestoneOf + " is a milestone towards " + truncVersion); 366 } else if (micr == 0 && patch == 0) { 367 System.err.println(" .. " + milestoneOf + " is the release of " + truncVersion); 368 } else { 369 milestoneOf = " (update " + micr + "." + patch + ")"; 370 System.err.println(" .. " + milestoneOf + " is an update to " + truncVersion); 371 } 372 result = truncVersion; 373 } 374 if (whichVer.equals("new")) { 375 rightMilestone = milestoneOf; 376 } else { 377 leftMilestone = milestoneOf; 378 } 379 } 380 } 381 382 } 383 // dumpNode(defines,""); 384 } catch (Throwable t) { 385 t.printStackTrace(); 386 System.err.println( 387 "Warning: Couldn't get " + whichVer + " version from " + UVERSION + " - reverting to " + prevVer); 388 result = prevVer; 389 } 390 391 if (result != null) { 392 393 } 394 395 if (prevVer != null) { 396 if (result != null) { 397 if (!result.equals(prevVer)) { 398 System.err.println("Note: Detected " + result + " version but we'll use your requested --" 399 + whichVer + "ver " + prevVer); 400 result = prevVer; 401 if (!rightMilestone.isEmpty() && whichVer.equals("new")) { 402 System.err.println(" .. ignoring milestone indicator " + rightMilestone); 403 rightMilestone = ""; 404 } 405 if (!leftMilestone.isEmpty() && !whichVer.equals("new")) { 406 leftMilestone = ""; 407 } 408 } else { 409 System.err.println("Note: You don't need to use '--" + whichVer + "ver " + result 410 + "' anymore - we detected it correctly."); 411 } 412 } else { 413 System.err.println( 414 "Note: Didn't detect version so we'll use your requested --" + whichVer + "ver " + prevVer); 415 result = prevVer; 416 if (!rightMilestone.isEmpty() && whichVer.equals("new")) { 417 System.err.println(" .. ignoring milestone indicator " + rightMilestone); 418 rightMilestone = ""; 419 } 420 if (!leftMilestone.isEmpty() && !whichVer.equals("new")) { 421 leftMilestone = ""; 422 } 423 } 424 } 425 426 if (result == null) { 427 System.err.println("prevVer=" + prevVer); 428 System.err.println("Error: You'll need to use the option \"--" + whichVer 429 + "ver\" because we could not detect an ICU version in " + UVERSION); 430 throw new InternalError("Error: You'll need to use the option \"--" + whichVer 431 + "ver\" because we could not detect an ICU version in " + UVERSION); 432 } 433 434 return result; 435 } 436 printUsage()437 private static void printUsage() { 438 System.out.println("Usage: StableAPI option* target*"); 439 System.out.println(); 440 System.out.println("Options:"); 441 System.out.println(" --help Print this text"); 442 System.out.println(" --oldver Version of old version of ICU (optional)"); 443 System.out.println(" --olddir Directory that contains xml docs of old version"); 444 System.out.println(" --newver Version of new version of ICU (optional)"); 445 System.out.println(" --newdir Directory that contains xml docs of new version"); 446 System.out.println(" --cxslt XSLT file for C docs"); 447 System.out.println(" --cppxslt XSLT file for C++ docs"); 448 System.out.println(" --reportxslt XSLT file for report docs"); 449 System.out.println(" --resultfile Output file"); 450 System.exit(-1); 451 } 452 getAttr(Node node, String attrName)453 static String getAttr(Node node, String attrName) { 454 if (node.getAttributes() == null && node.getNodeType() == 3) { 455 // return "(text node 3)"; 456 return "(Node: " + node.toString() + " )"; 457 // return 458 // node.getFirstChild().getAttributes().getNamedItem(attrName).getNodeValue(); 459 } 460 461 try { 462 return node.getAttributes().getNamedItem(attrName).getNodeValue(); 463 } catch (NullPointerException npe) { 464 if (node.getAttributes() == null) { 465 throw new InternalError( 466 "[no attributes Can't get attr " + attrName + " out of node " + node.getNodeName() + ":" 467 + node.getNodeType() + ":" + node.getNodeValue() + "@" + node.getTextContent()); 468 } else if (node.getAttributes().getNamedItem(attrName) == null) { 469 return null; 470 // throw new InternalError("No attribute named: "+attrName); 471 } else { 472 System.err.println("Can't get attr " + attrName + ": " + npe.toString()); 473 } 474 npe.printStackTrace(); 475 throw new InternalError("Can't get attr " + attrName); 476 } 477 } 478 getAttr(NamedNodeMap attrList, String attrName)479 static String getAttr(NamedNodeMap attrList, String attrName) { 480 return attrList.getNamedItem(attrName).getNodeValue(); 481 } 482 483 static class Function implements Comparable<Function> { 484 public String prototype; 485 public String id; 486 public String status; 487 public String version; 488 public String file; 489 public String comparableName; 490 public String comparablePrototype; 491 equals(Function right)492 public boolean equals(Function right) { 493 return this.comparablePrototype.equals(right.comparablePrototype); 494 } 495 fromXml(Node n)496 static Function fromXml(Node n) { 497 Function f = new Function(); 498 f.prototype = getAttr(n, "prototype"); 499 500 if ("yes".equals(getAttr(n, "static")) && !f.prototype.contains("static")) { 501 f.prototype = "static ".concat(f.prototype); 502 } 503 504 f.id = getAttr(n, "id"); 505 f.status = getAttr(n, "status"); 506 f.version = trimICU(getAttr(n, "version")); 507 f.file = getAttr(n, "file"); 508 f.purifyPrototype(); 509 510 f.simplifyPrototype(); 511 512 f.comparablePrototype = f.prototype; 513 // Modify the prototype here, but don't display it to the user. ( Char16Ptr --> 514 // char16_t* etc ) 515 for (int i = 0; i < aliasList.length; i += 2) { 516 f.comparablePrototype = f.comparablePrototype.replaceAll(aliasList[i + 0], aliasList[i + 1]); 517 } 518 519 if (f.file == null) { 520 f.file = "{null}"; 521 } else { 522 f.file = Function.getBasename(f.file); 523 } 524 f.comparableName = f.comparableName(); 525 return f; 526 } 527 528 /** 529 * Convert string to basename. 530 * 531 * @param str 532 * @return 533 */ getBasename(String str)534 private static String getBasename(String str) { 535 int i = str.lastIndexOf("/"); 536 str = i == -1 ? str : str.substring(i + 1); 537 return str; 538 } 539 540 static private String replList[] = { "[ ]*\\([ ]*void[ ]*\\)", "() ", // (void) => () 541 "[ ]*,", ", ", // No spaces preceding commas. 542 "[ ]*\\*[ ]*", "* ", // No spaces preceding '*'. 543 "[ ]*=[ ]*0[ ]*$", "=0 ", // No spaces in " = 0". 544 "[ ]{2,}", " ", "\n", " " // Multiple spaces collapse to single. 545 }; 546 547 /** 548 * these are noted as deltas. 549 */ 550 static private String simplifyList[] = { 551 "[ ]*=[ ]*0[ ]*$", "",// remove pure virtual 552 // TODO: notify about this difference, separately 553 "[ ]*U_NOEXCEPT", "", // remove U_NOEXCEPT (this was fixed in Doxyfile, but fixing here so it is 554 // retroactive) 555 "[ ]*(override|U_OVERRIDE)", "", // remove U_OVERRIDE and override 556 // Simplify possibly-covariant functions to void* 557 "^([^\\* ]+)\\*(.*)::(clone|safeClone|cloneAsThawed|freeze|createBufferClone)\\((.*)", "void*$2::$3($4", 558 "\\s+$", "", // remove trailing spaces. 559 "^U_NAMESPACE_END ", "", // Bug in processing of uspoof.h 560 "\\bUBool\\b", "bool" 561 }; 562 563 /** 564 * This list is applied only for comparisons. The resulting string is NOT shown 565 * to the user. These should be ignored as far as changes go. func(UChar) === 566 * func(char16_t) 567 */ 568 static private String aliasList[] = { "UChar", "char16_t", "ConstChar16Ptr", "const char16_t*", "Char16Ptr", 569 "char16_t*", }; 570 571 /** 572 * Special cases: 573 * 574 * Remove the status attribute embedded in the C prototype 575 * 576 * Remove the virtual keyword in Cpp prototype 577 */ purifyPrototype()578 private void purifyPrototype() { 579 // refer to 'umachine.h' 580 String statusList[] = { "U_CAPI", "U_STABLE", "U_DRAFT", "U_DEPRECATED", "U_OBSOLETE", "U_INTERNAL", 581 "virtual", "U_EXPORT2", "U_I18N_API", "U_COMMON_API" }; 582 for (int i = 0; i < statusList.length; i++) { 583 String s = statusList[i]; 584 prototype = prototype.replaceAll(s, ""); 585 prototype = prototype.trim(); 586 } 587 588 for (int i = 0; i < replList.length; i += 2) { 589 prototype = prototype.replaceAll(replList[i + 0], replList[i + 1]); 590 } 591 592 prototype = prototype.trim(); 593 594 // Now, remove parameter names! 595 StringBuffer out = new StringBuffer(); 596 StringBuffer in = new StringBuffer(prototype); 597 int openParen = in.indexOf("("); 598 int closeParen = in.lastIndexOf(")"); 599 600 if (openParen == -1 || closeParen == -1) 601 return; // exit, malformed? 602 if (openParen + 1 == closeParen) 603 return; // exit: () 604 605 out.append(in, 0, openParen + 1); // prelude 606 607 for (int left = openParen + 1; left < closeParen;) { 608 int right = in.indexOf(",", left + 1); // right edge 609 if (right >= closeParen || right == -1) 610 right = closeParen; // found last comma 611 612 // System.err.println("Considering " + left + " / " + right + " - " + closeParen 613 // + " : " + in.substring(left, right)); 614 615 if (left == right) 616 continue; 617 618 // find variable name 619 int rightCh = right - 1; 620 if (rightCh == left) { // 1 ch- break 621 out.append(in, left, right); 622 continue; 623 } 624 // eat whitespace at right 625 int nameEndCh = rightCh; 626 while (nameEndCh > left && Character.isWhitespace(in.charAt(nameEndCh))) { 627 nameEndCh--; 628 } 629 int nameStartCh = nameEndCh; 630 while (nameStartCh > left && Character.isJavaIdentifierPart(in.charAt(nameStartCh))) { 631 nameStartCh--; 632 } 633 634 // now, did we find something to skip? 635 if (nameStartCh > left && nameEndCh > nameStartCh) { 636 out.append(in, left, nameStartCh + 1); 637 } else { 638 // pass through 639 out.append(in, left, right); 640 } 641 642 left = right; 643 } 644 645 out.append(in, closeParen, in.length()); // postlude 646 647 // Delete any doubled whitespace. 648 for (int p = 1; p < out.length(); p++) { 649 char prev = out.charAt(p - 1); 650 if (Character.isWhitespace(prev)) { 651 while (out.length() > p && (Character.isWhitespace(out.charAt(p)))) { 652 out.deleteCharAt(p); 653 } 654 if (out.length() > p) { 655 // any trailings to delete? 656 char curr = out.charAt(p); 657 if (curr == ',' || curr == ')' || curr == '*' || curr == '&') { // delete spaces before these. 658 out.deleteCharAt(--p); 659 continue; 660 } 661 } 662 } 663 } 664 665 // System.err.println(prototype+" -> " + out.toString()); 666 prototype = out.toString(); 667 } 668 simplifyPrototype()669 private void simplifyPrototype() { 670 if (prototype.startsWith("#define")) { 671 return; 672 } 673 final String prototype0 = prototype; 674 for (int i = 0; i < simplifyList.length; i += 2) { 675 prototype = prototype.replaceAll(simplifyList[i + 0], simplifyList[i + 1]); 676 } 677 if (!prototype0.equals(prototype)) { 678 addSimplification(prototype0, prototype); 679 } 680 } 681 682 /** 683 * @Override 684 */ compareTo(Function o)685 public int compareTo(Function o) { 686 return comparableName.compareTo(((Function) o).comparableName); 687 } 688 comparableName()689 public String comparableName() { 690 return file + "|" + comparablePrototype + "|" + status + "|" + version + "|" + id; 691 } 692 } 693 694 static class JoinedFunction implements Comparable<JoinedFunction> { 695 public String prototype; 696 public String leftRefId; 697 public String leftStatus; 698 public String leftVersion; 699 public String rightVersion; 700 public String leftFile; 701 public String rightRefId; 702 public String rightStatus; 703 public String rightFile; 704 705 public String comparableName; 706 fromLeftFun(Function left)707 static JoinedFunction fromLeftFun(Function left) { 708 JoinedFunction u = new JoinedFunction(); 709 u.prototype = left.prototype; 710 u.leftRefId = left.id; 711 u.leftStatus = left.status; 712 u.leftFile = left.file; 713 u.rightRefId = notFound; 714 // u.rightVersion = nul; 715 u.leftVersion = left.version; 716 u.rightStatus = notFound; 717 u.rightFile = notFound; 718 u.comparableName = left.comparableName; 719 return u; 720 } 721 fromRightFun(Function right)722 static JoinedFunction fromRightFun(Function right) { 723 JoinedFunction u = new JoinedFunction(); 724 u.prototype = right.prototype; 725 u.leftRefId = notFound; 726 u.leftStatus = notFound; 727 u.leftFile = notFound; 728 // u.leftVersion = nul; 729 u.rightVersion = right.version; 730 u.rightRefId = right.id; 731 u.rightStatus = right.status; 732 u.rightFile = right.file; 733 u.comparableName = right.comparableName; 734 return u; 735 } 736 fromTwoFun(Function left, Function right)737 static JoinedFunction fromTwoFun(Function left, Function right) { 738 if (!left.equals(right)) 739 throw new Error(); 740 JoinedFunction u = new JoinedFunction(); 741 u.prototype = left.prototype; 742 u.leftRefId = left.id; 743 u.leftStatus = left.status; 744 u.leftFile = left.file; 745 u.rightRefId = right.id; 746 u.rightStatus = right.status; 747 u.leftVersion = left.version; 748 u.rightVersion = right.version; 749 u.rightFile = right.file; 750 u.comparableName = left.comparableName + "+" + right.comparableName; 751 return u; 752 } 753 toXml(Document doc)754 Element toXml(Document doc) { 755 Element ele = doc.createElement("func"); 756 ele.setAttribute("prototype", formatCode(prototype)); 757 // ele.setAttribute("leftRefId", leftRefId); 758 759 ele.setAttribute("leftStatus", leftStatus); 760 // ele.setAttribute("rightRefId", rightRefId); 761 ele.setAttribute("rightStatus", rightStatus); 762 ele.setAttribute("leftVersion", leftVersion); 763 // ele.setAttribute("rightRefId", rightRefId); 764 ele.setAttribute("rightVersion", rightVersion); 765 766 // String f = rightRefId.equals(notFound) ? leftRefId : rightRefId; 767 // int tail = f.indexOf("_"); 768 // f = tail != -1 ? f.substring(0, tail) : f; 769 // f = f.startsWith("class") ? f.replaceFirst("class","") : f; 770 String f = rightFile.equals(notFound) ? leftFile : rightFile; 771 ele.setAttribute("file", f); 772 return ele; 773 } 774 compareTo(JoinedFunction o)775 public int compareTo(JoinedFunction o) { 776 return comparableName.compareTo(o.comparableName); 777 } 778 equals(Function right)779 public boolean equals(Function right) { 780 return this.prototype.equals(right.prototype); 781 } 782 } 783 784 TransformerFactory transFac = TransformerFactory.newInstance(); 785 makeTransformer(InputStream is, String name)786 Transformer makeTransformer(InputStream is, String name) { 787 if (is == null) { 788 throw new InternalError("No inputstream set for " + name); 789 } 790 System.err.println("Transforming from: " + name); 791 Transformer t; 792 try { 793 StreamSource ss = new StreamSource(is); 794 ss.setSystemId(new File(".")); 795 t = transFac.newTransformer(ss); 796 } catch (TransformerConfigurationException e) { 797 e.printStackTrace(); 798 throw new InternalError("Couldn't make transformer for " + name + " - " + e.getMessageAndLocation()); 799 } 800 if (t == null) { 801 throw new InternalError("Couldn't make transformer for " + name); 802 } 803 return t; 804 } 805 reportSelectedFun(Node joinedNode)806 private void reportSelectedFun(Node joinedNode) 807 throws TransformerException, ParserConfigurationException, SAXException, IOException { 808 Transformer report = makeTransformer(reportXslStream, RPTXSLT); 809 // report.setParameter("leftStatus", leftStatus); 810 report.setParameter("leftVer", leftVer); 811 // report.setParameter("rightStatus", rightStatus); 812 report.setParameter("ourYear", new Integer(new java.util.GregorianCalendar().get(java.util.Calendar.YEAR))); 813 report.setParameter("rightVer", rightVer); 814 report.setParameter("rightMilestone", rightMilestone); 815 report.setParameter("leftMilestone", leftMilestone); 816 report.setParameter("dateTime", new GregorianCalendar().getTime()); 817 report.setParameter("notFound", notFound); 818 819 DOMSource src = new DOMSource(joinedNode); 820 821 Result res = new StreamResult(resultFile); 822 // DOMResult res = new DOMResult(); 823 report.transform(src, res); 824 // dumpNode(res.getNode(),""); 825 } 826 getFullList(InputStream dumpXsltStream, String dumpXsltFile)827 private Set<JoinedFunction> getFullList(InputStream dumpXsltStream, String dumpXsltFile) 828 throws TransformerException, ParserConfigurationException, XPathExpressionException, SAXException, 829 IOException { 830 // prepare transformer 831 XPath xpath = XPathFactory.newInstance().newXPath(); 832 String expression = "/list"; 833 Transformer transformer = makeTransformer(dumpXsltStream, dumpXsltFile); 834 835 // InputSource leftSource = new InputSource(leftDir + "index.xml"); 836 DOMSource leftIndex = new DOMSource(getDocument(new File(leftDir, INDEX_XML))); 837 DOMResult leftResult = new DOMResult(); 838 transformer.setParameter(DOC_FOLDER, leftDir); 839 transformer.transform(leftIndex, leftResult); 840 841 // Node leftList = XPathAPI.selectSingleNode(leftResult.getNode(),"/list"); 842 Node leftList = (Node) xpath.evaluate(expression, leftResult.getNode(), XPathConstants.NODE); 843 if (leftList == null) { 844 // dumpNode(xsltSource.getNode()); 845 dumpNode(leftResult.getNode()); 846 // dumpNode(leftIndex.getNode()); 847 System.out.flush(); 848 System.err.flush(); 849 throw new InternalError("getFullList(" + dumpXsltFile.toString() + ") returned a null left " + expression); 850 } 851 852 xpath.reset(); // reuse 853 854 DOMSource rightIndex = new DOMSource(getDocument(new File(rightDir, INDEX_XML))); 855 DOMResult rightResult = new DOMResult(); 856 transformer.setParameter(DOC_FOLDER, rightDir); 857 System.err.println("Loading: " + dumpXsltFile.toString()); 858 transformer.transform(rightIndex, rightResult); 859 System.err.println(" .. loaded: " + dumpXsltFile.toString()); 860 Node rightList = (Node) xpath.evaluate(expression, rightResult.getNode(), XPathConstants.NODE); 861 if (rightList == null) { 862 throw new InternalError("getFullList(" + dumpXsltFile.toString() + ") returned a null right " + expression); 863 } 864 // dumpNode(rightList,""); 865 866 Set<Function> leftSet = nodeToSet(leftList); 867 Set<Function> rightSet = nodeToSet(rightList); 868 Set<JoinedFunction> joined = fullJoin(leftSet, rightSet); 869 return joined; 870 // joinedNode = setToNode(joined); 871 // dumpNode(joinedNode,""); 872 // return joinedNode; 873 } 874 875 /** 876 * @param node 877 * @return Set<Fun> 878 */ nodeToSet(Node node)879 private Set<Function> nodeToSet(Node node) { 880 Set<Function> s = new TreeSet<Function>(); 881 NodeList list = node.getChildNodes(); 882 for (int i = 0; i < list.getLength(); i++) { 883 Node n = list.item(i); 884 s.add(Function.fromXml(n)); 885 } 886 return s; 887 } 888 889 /** 890 * @param set Set<JoinedFun> 891 * @return 892 * @throws ParserConfigurationException 893 */ setToNode(Set<JoinedFunction> set)894 private Node setToNode(Set<JoinedFunction> set) throws ParserConfigurationException { 895 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 896 Document doc = dbf.newDocumentBuilder().newDocument(); 897 Element root = doc.createElement("list"); 898 doc.appendChild(root); 899 for (Iterator<JoinedFunction> iter = set.iterator(); iter.hasNext();) { 900 JoinedFunction fun = iter.next(); 901 root.appendChild(fun.toXml(doc)); 902 } 903 904 // add the 'changed' stuff 905 Element root2 = doc.createElement("simplifications"); 906 root.appendChild(root2); 907 { 908 for (String simplification : getChangedSimplifications()) { 909 Element subSimplification = doc.createElement("simplification"); 910 Element baseElement = doc.createElement("base"); 911 baseElement.appendChild(doc.createTextNode(simplification)); 912 subSimplification.appendChild(baseElement); 913 914 root2.appendChild(subSimplification); 915 916 for (String change : simplifications.get(simplification)) { 917 Element changeElement = doc.createElement("change"); 918 changeElement.appendChild(doc.createTextNode(change)); 919 subSimplification.appendChild(changeElement); 920 } 921 } 922 } 923 924 return doc; 925 } 926 927 /** 928 * full-join two Set on 'prototype' 929 * 930 * @param left Set<Fun> 931 * @param right Set<Fun> 932 * @return Set<JoinedFun> 933 */ fullJoin(Set<Function> left, Set<Function> right)934 private static Set<JoinedFunction> fullJoin(Set<Function> left, Set<Function> right) { 935 936 Set<JoinedFunction> joined = new TreeSet<JoinedFunction>(); // Set<JoinedFun> 937 Set<Function> common = new TreeSet<Function>(); // Set<Fun> 938 for (Iterator<Function> iter1 = left.iterator(); iter1.hasNext();) { 939 Function f1 = iter1.next(); 940 for (Iterator<Function> iter2 = right.iterator(); iter2.hasNext();) { 941 Function f2 = iter2.next(); 942 if (f1.equals(f2)) { 943 // should add left item to common set 944 // since we will remove common items with left set later 945 common.add(f1); 946 joined.add(JoinedFunction.fromTwoFun(f1, f2)); 947 right.remove(f2); 948 break; 949 } 950 } 951 } 952 953 for (Iterator<Function> iter = common.iterator(); iter.hasNext();) { 954 Function f = iter.next(); 955 left.remove(f); 956 } 957 958 for (Iterator<Function> iter = left.iterator(); iter.hasNext();) { 959 Function f = iter.next(); 960 joined.add(JoinedFunction.fromLeftFun(f)); 961 } 962 963 for (Iterator<Function> iter = right.iterator(); iter.hasNext();) { 964 Function f = iter.next(); 965 joined.add(JoinedFunction.fromRightFun(f)); 966 } 967 return joined; 968 } 969 dumpNode(Node n)970 private static void dumpNode(Node n) { 971 dumpNode(n, ""); 972 } 973 974 /** 975 * Dump out a node for debugging. Recursive fcn 976 * 977 * @param n 978 * @param pre 979 */ dumpNode(Node n, String pre)980 private static void dumpNode(Node n, String pre) { 981 String opre = pre; 982 pre += " "; 983 System.out.print(opre + "<" + n.getNodeName()); 984 // dump attribute 985 NamedNodeMap attr = n.getAttributes(); 986 if (attr != null) { 987 for (int i = 0; i < attr.getLength(); i++) { 988 System.out.print( 989 "\n" + pre + " " + attr.item(i).getNodeName() + "=\"" + attr.item(i).getNodeValue() + "\""); 990 } 991 } 992 System.out.println(">"); 993 994 // dump value 995 String v = pre + n.getNodeValue(); 996 if (n.getNodeType() == Node.TEXT_NODE) 997 System.out.println(v); 998 999 // dump sub nodes 1000 NodeList nList = n.getChildNodes(); 1001 for (int i = 0; i < nList.getLength(); i++) { 1002 Node ln = nList.item(i); 1003 dumpNode(ln, pre + " "); 1004 } 1005 System.out.println(opre + "</" + n.getNodeName() + ">"); 1006 } 1007 1008 private static DocumentBuilder theBuilder = null; 1009 private static DocumentBuilderFactory dbf = null; 1010 getDocumentBuilder()1011 private synchronized static DocumentBuilder getDocumentBuilder() throws ParserConfigurationException { 1012 if (theBuilder == null) { 1013 dbf = DocumentBuilderFactory.newInstance(); 1014 theBuilder = dbf.newDocumentBuilder(); 1015 } 1016 return theBuilder; 1017 } 1018 getDocument(File file)1019 private static Document getDocument(File file) throws ParserConfigurationException, SAXException, IOException { 1020 FileInputStream fis = new FileInputStream(file); 1021 InputSource inputSource = new InputSource(fis); 1022 Document doc = getDocumentBuilder().parse(inputSource); 1023 return doc; 1024 } 1025 1026 static boolean tried = false; 1027 static Formatter aFormatter = null; 1028 1029 public interface Formatter { formatCode(String s)1030 public String formatCode(String s); 1031 } 1032 1033 public static String format_keywords[] = { "enum", "#define", "static" }; 1034 1035 /** 1036 * Attempt to use a pretty formatter 1037 * 1038 * @param prototype2 1039 * @return 1040 */ formatCode(String prototype2)1041 public static String formatCode(String prototype2) { 1042 if (!tried) { 1043 String theFormatter = StableAPI.class.getPackage().getName() + ".CodeFormatter"; 1044 try { 1045 @SuppressWarnings("unchecked") 1046 Class<Formatter> formatClass = (Class<Formatter>) Class.forName(theFormatter); 1047 aFormatter = (Formatter) formatClass.newInstance(); 1048 } catch (Exception e) { 1049 System.err.println("Note: Couldn't load " + theFormatter); 1050 aFormatter = new Formatter() { 1051 1052 public String formatCode(String s) { 1053 String str = HTMLSafe(s.trim()); 1054 for (String keyword : format_keywords) { 1055 if (str.startsWith(keyword)) { 1056 str = str.replaceFirst(keyword, "<tt>" + keyword + "</tt>"); 1057 } 1058 } 1059 return str; 1060 } 1061 1062 }; 1063 } 1064 tried = true; 1065 } 1066 if (aFormatter != null) { 1067 return aFormatter.formatCode(prototype2); 1068 } else { 1069 return HTMLSafe(prototype2); 1070 } 1071 } 1072 HTMLSafe(String s)1073 public static String HTMLSafe(String s) { 1074 if (s == null) 1075 return null; 1076 1077 return s.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """); 1078 } 1079 1080 } 1081