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 } else { 356 // old scheme - 1.0.* .. 4.8.* 357 String truncVersion = "ICU " + maj + "." + min; 358 if ((min % 2) == 1) { 359 milestoneOf = " (" + maj + "." + (min + 1) + "m" + micr + ")"; 360 truncVersion = "ICU " + (maj) + "." + (min + 1); 361 System.err.println(" .. " + milestoneOf + " is a milestone towards " + truncVersion); 362 } else if (micr == 0 && patch == 0) { 363 System.err.println(" .. " + milestoneOf + " is the release of " + truncVersion); 364 } else { 365 milestoneOf = " (update " + micr + "." + patch + ")"; 366 System.err.println(" .. " + milestoneOf + " is an update to " + truncVersion); 367 } 368 result = truncVersion; 369 } 370 if (whichVer.equals("new")) { 371 rightMilestone = milestoneOf; 372 } else { 373 leftMilestone = milestoneOf; 374 } 375 } 376 } 377 378 } 379 // dumpNode(defines,""); 380 } catch (Throwable t) { 381 t.printStackTrace(); 382 System.err.println( 383 "Warning: Couldn't get " + whichVer + " version from " + UVERSION + " - reverting to " + prevVer); 384 result = prevVer; 385 } 386 387 if (result != null) { 388 389 } 390 391 if (prevVer != null) { 392 if (result != null) { 393 if (!result.equals(prevVer)) { 394 System.err.println("Note: Detected " + result + " version but we'll use your requested --" 395 + whichVer + "ver " + prevVer); 396 result = prevVer; 397 if (!rightMilestone.isEmpty() && whichVer.equals("new")) { 398 System.err.println(" .. ignoring milestone indicator " + rightMilestone); 399 rightMilestone = ""; 400 } 401 if (!leftMilestone.isEmpty() && !whichVer.equals("new")) { 402 leftMilestone = ""; 403 } 404 } else { 405 System.err.println("Note: You don't need to use '--" + whichVer + "ver " + result 406 + "' anymore - we detected it correctly."); 407 } 408 } else { 409 System.err.println( 410 "Note: Didn't detect version so we'll use your requested --" + whichVer + "ver " + prevVer); 411 result = prevVer; 412 if (!rightMilestone.isEmpty() && whichVer.equals("new")) { 413 System.err.println(" .. ignoring milestone indicator " + rightMilestone); 414 rightMilestone = ""; 415 } 416 if (!leftMilestone.isEmpty() && !whichVer.equals("new")) { 417 leftMilestone = ""; 418 } 419 } 420 } 421 422 if (result == null) { 423 System.err.println("prevVer=" + prevVer); 424 System.err.println("Error: You'll need to use the option \"--" + whichVer 425 + "ver\" because we could not detect an ICU version in " + UVERSION); 426 throw new InternalError("Error: You'll need to use the option \"--" + whichVer 427 + "ver\" because we could not detect an ICU version in " + UVERSION); 428 } 429 430 return result; 431 } 432 printUsage()433 private static void printUsage() { 434 System.out.println("Usage: StableAPI option* target*"); 435 System.out.println(); 436 System.out.println("Options:"); 437 System.out.println(" --help Print this text"); 438 System.out.println(" --oldver Version of old version of ICU (optional)"); 439 System.out.println(" --olddir Directory that contains xml docs of old version"); 440 System.out.println(" --newver Version of new version of ICU (optional)"); 441 System.out.println(" --newdir Directory that contains xml docs of new version"); 442 System.out.println(" --cxslt XSLT file for C docs"); 443 System.out.println(" --cppxslt XSLT file for C++ docs"); 444 System.out.println(" --reportxslt XSLT file for report docs"); 445 System.out.println(" --resultfile Output file"); 446 System.exit(-1); 447 } 448 getAttr(Node node, String attrName)449 static String getAttr(Node node, String attrName) { 450 if (node.getAttributes() == null && node.getNodeType() == 3) { 451 // return "(text node 3)"; 452 return "(Node: " + node.toString() + " )"; 453 // return 454 // node.getFirstChild().getAttributes().getNamedItem(attrName).getNodeValue(); 455 } 456 457 try { 458 return node.getAttributes().getNamedItem(attrName).getNodeValue(); 459 } catch (NullPointerException npe) { 460 if (node.getAttributes() == null) { 461 throw new InternalError( 462 "[no attributes Can't get attr " + attrName + " out of node " + node.getNodeName() + ":" 463 + node.getNodeType() + ":" + node.getNodeValue() + "@" + node.getTextContent()); 464 } else if (node.getAttributes().getNamedItem(attrName) == null) { 465 return null; 466 // throw new InternalError("No attribute named: "+attrName); 467 } else { 468 System.err.println("Can't get attr " + attrName + ": " + npe.toString()); 469 } 470 npe.printStackTrace(); 471 throw new InternalError("Can't get attr " + attrName); 472 } 473 } 474 getAttr(NamedNodeMap attrList, String attrName)475 static String getAttr(NamedNodeMap attrList, String attrName) { 476 return attrList.getNamedItem(attrName).getNodeValue(); 477 } 478 479 static class Function implements Comparable<Function> { 480 public String prototype; 481 public String id; 482 public String status; 483 public String version; 484 public String file; 485 public String comparableName; 486 public String comparablePrototype; 487 equals(Function right)488 public boolean equals(Function right) { 489 return this.comparablePrototype.equals(right.comparablePrototype); 490 } 491 fromXml(Node n)492 static Function fromXml(Node n) { 493 Function f = new Function(); 494 f.prototype = getAttr(n, "prototype"); 495 496 if ("yes".equals(getAttr(n, "static")) && !f.prototype.contains("static")) { 497 f.prototype = "static ".concat(f.prototype); 498 } 499 500 f.id = getAttr(n, "id"); 501 f.status = getAttr(n, "status"); 502 f.version = trimICU(getAttr(n, "version")); 503 f.file = getAttr(n, "file"); 504 f.purifyPrototype(); 505 506 f.simplifyPrototype(); 507 508 f.comparablePrototype = f.prototype; 509 // Modify the prototype here, but don't display it to the user. ( Char16Ptr --> 510 // char16_t* etc ) 511 for (int i = 0; i < aliasList.length; i += 2) { 512 f.comparablePrototype = f.comparablePrototype.replaceAll(aliasList[i + 0], aliasList[i + 1]); 513 } 514 515 if (f.file == null) { 516 f.file = "{null}"; 517 } else { 518 f.file = Function.getBasename(f.file); 519 } 520 f.comparableName = f.comparableName(); 521 return f; 522 } 523 524 /** 525 * Convert string to basename. 526 * 527 * @param str 528 * @return 529 */ getBasename(String str)530 private static String getBasename(String str) { 531 int i = str.lastIndexOf("/"); 532 str = i == -1 ? str : str.substring(i + 1); 533 return str; 534 } 535 536 static private String replList[] = { "[ ]*\\([ ]*void[ ]*\\)", "() ", // (void) => () 537 "[ ]*,", ", ", // No spaces preceding commas. 538 "[ ]*\\*[ ]*", "* ", // No spaces preceding '*'. 539 "[ ]*=[ ]*0[ ]*$", "=0 ", // No spaces in " = 0". 540 "[ ]{2,}", " ", "\n", " " // Multiple spaces collapse to single. 541 }; 542 543 /** 544 * these are noted as deltas. 545 */ 546 static private String simplifyList[] = { 547 "[ ]*=[ ]*0[ ]*$", "",// remove pure virtual 548 // TODO: notify about this difference, separately 549 "[ ]*U_NOEXCEPT", "", // remove U_NOEXCEPT (this was fixed in Doxyfile, but fixing here so it is 550 // retroactive) 551 "[ ]*U_OVERRIDE", "", // remove U_OVERRIDE 552 // Simplify possibly-covariant functions to void* 553 "^([^\\* ]+)\\*(.*)::(clone|safeClone|cloneAsThawed|freeze|createBufferClone)\\((.*)", "void*$2::$3($4", 554 "\\s+$", "", // remove trailing spaces. 555 "^U_NAMESPACE_END ", "" // Bug in processing of uspoof.h 556 }; 557 558 /** 559 * This list is applied only for comparisons. The resulting string is NOT shown 560 * to the user. These should be ignored as far as changes go. func(UChar) === 561 * func(char16_t) 562 */ 563 static private String aliasList[] = { "UChar", "char16_t", "ConstChar16Ptr", "const char16_t*", "Char16Ptr", 564 "char16_t*", }; 565 566 /** 567 * Special cases: 568 * 569 * Remove the status attribute embedded in the C prototype 570 * 571 * Remove the virtual keyword in Cpp prototype 572 */ purifyPrototype()573 private void purifyPrototype() { 574 // refer to 'umachine.h' 575 String statusList[] = { "U_CAPI", "U_STABLE", "U_DRAFT", "U_DEPRECATED", "U_OBSOLETE", "U_INTERNAL", 576 "virtual", "U_EXPORT2", "U_I18N_API", "U_COMMON_API" }; 577 for (int i = 0; i < statusList.length; i++) { 578 String s = statusList[i]; 579 prototype = prototype.replaceAll(s, ""); 580 prototype = prototype.trim(); 581 } 582 583 for (int i = 0; i < replList.length; i += 2) { 584 prototype = prototype.replaceAll(replList[i + 0], replList[i + 1]); 585 } 586 587 prototype = prototype.trim(); 588 589 // Now, remove parameter names! 590 StringBuffer out = new StringBuffer(); 591 StringBuffer in = new StringBuffer(prototype); 592 int openParen = in.indexOf("("); 593 int closeParen = in.lastIndexOf(")"); 594 595 if (openParen == -1 || closeParen == -1) 596 return; // exit, malformed? 597 if (openParen + 1 == closeParen) 598 return; // exit: () 599 600 out.append(in, 0, openParen + 1); // prelude 601 602 for (int left = openParen + 1; left < closeParen;) { 603 int right = in.indexOf(",", left + 1); // right edge 604 if (right >= closeParen || right == -1) 605 right = closeParen; // found last comma 606 607 // System.err.println("Considering " + left + " / " + right + " - " + closeParen 608 // + " : " + in.substring(left, right)); 609 610 if (left == right) 611 continue; 612 613 // find variable name 614 int rightCh = right - 1; 615 if (rightCh == left) { // 1 ch- break 616 out.append(in, left, right); 617 continue; 618 } 619 // eat whitespace at right 620 int nameEndCh = rightCh; 621 while (nameEndCh > left && Character.isWhitespace(in.charAt(nameEndCh))) { 622 nameEndCh--; 623 } 624 int nameStartCh = nameEndCh; 625 while (nameStartCh > left && Character.isJavaIdentifierPart(in.charAt(nameStartCh))) { 626 nameStartCh--; 627 } 628 629 // now, did we find something to skip? 630 if (nameStartCh > left && nameEndCh > nameStartCh) { 631 out.append(in, left, nameStartCh + 1); 632 } else { 633 // pass through 634 out.append(in, left, right); 635 } 636 637 left = right; 638 } 639 640 out.append(in, closeParen, in.length()); // postlude 641 642 // Delete any doubled whitespace. 643 for (int p = 1; p < out.length(); p++) { 644 char prev = out.charAt(p - 1); 645 if (Character.isWhitespace(prev)) { 646 while (out.length() > p && (Character.isWhitespace(out.charAt(p)))) { 647 out.deleteCharAt(p); 648 } 649 if (out.length() > p) { 650 // any trailings to delete? 651 char curr = out.charAt(p); 652 if (curr == ',' || curr == ')' || curr == '*' || curr == '&') { // delete spaces before these. 653 out.deleteCharAt(--p); 654 continue; 655 } 656 } 657 } 658 } 659 660 // System.err.println(prototype+" -> " + out.toString()); 661 prototype = out.toString(); 662 } 663 simplifyPrototype()664 private void simplifyPrototype() { 665 if (prototype.startsWith("#define")) { 666 return; 667 } 668 final String prototype0 = prototype; 669 for (int i = 0; i < simplifyList.length; i += 2) { 670 prototype = prototype.replaceAll(simplifyList[i + 0], simplifyList[i + 1]); 671 } 672 if (!prototype0.equals(prototype)) { 673 addSimplification(prototype0, prototype); 674 } 675 } 676 677 /** 678 * @Override 679 */ compareTo(Function o)680 public int compareTo(Function o) { 681 return comparableName.compareTo(((Function) o).comparableName); 682 } 683 comparableName()684 public String comparableName() { 685 return file + "|" + comparablePrototype + "|" + status + "|" + version + "|" + id; 686 } 687 } 688 689 static class JoinedFunction implements Comparable<JoinedFunction> { 690 public String prototype; 691 public String leftRefId; 692 public String leftStatus; 693 public String leftVersion; 694 public String rightVersion; 695 public String leftFile; 696 public String rightRefId; 697 public String rightStatus; 698 public String rightFile; 699 700 public String comparableName; 701 fromLeftFun(Function left)702 static JoinedFunction fromLeftFun(Function left) { 703 JoinedFunction u = new JoinedFunction(); 704 u.prototype = left.prototype; 705 u.leftRefId = left.id; 706 u.leftStatus = left.status; 707 u.leftFile = left.file; 708 u.rightRefId = notFound; 709 // u.rightVersion = nul; 710 u.leftVersion = left.version; 711 u.rightStatus = notFound; 712 u.rightFile = notFound; 713 u.comparableName = left.comparableName; 714 return u; 715 } 716 fromRightFun(Function right)717 static JoinedFunction fromRightFun(Function right) { 718 JoinedFunction u = new JoinedFunction(); 719 u.prototype = right.prototype; 720 u.leftRefId = notFound; 721 u.leftStatus = notFound; 722 u.leftFile = notFound; 723 // u.leftVersion = nul; 724 u.rightVersion = right.version; 725 u.rightRefId = right.id; 726 u.rightStatus = right.status; 727 u.rightFile = right.file; 728 u.comparableName = right.comparableName; 729 return u; 730 } 731 fromTwoFun(Function left, Function right)732 static JoinedFunction fromTwoFun(Function left, Function right) { 733 if (!left.equals(right)) 734 throw new Error(); 735 JoinedFunction u = new JoinedFunction(); 736 u.prototype = left.prototype; 737 u.leftRefId = left.id; 738 u.leftStatus = left.status; 739 u.leftFile = left.file; 740 u.rightRefId = right.id; 741 u.rightStatus = right.status; 742 u.leftVersion = left.version; 743 u.rightVersion = right.version; 744 u.rightFile = right.file; 745 u.comparableName = left.comparableName + "+" + right.comparableName; 746 return u; 747 } 748 toXml(Document doc)749 Element toXml(Document doc) { 750 Element ele = doc.createElement("func"); 751 ele.setAttribute("prototype", formatCode(prototype)); 752 // ele.setAttribute("leftRefId", leftRefId); 753 754 ele.setAttribute("leftStatus", leftStatus); 755 // ele.setAttribute("rightRefId", rightRefId); 756 ele.setAttribute("rightStatus", rightStatus); 757 ele.setAttribute("leftVersion", leftVersion); 758 // ele.setAttribute("rightRefId", rightRefId); 759 ele.setAttribute("rightVersion", rightVersion); 760 761 // String f = rightRefId.equals(notFound) ? leftRefId : rightRefId; 762 // int tail = f.indexOf("_"); 763 // f = tail != -1 ? f.substring(0, tail) : f; 764 // f = f.startsWith("class") ? f.replaceFirst("class","") : f; 765 String f = rightFile.equals(notFound) ? leftFile : rightFile; 766 ele.setAttribute("file", f); 767 return ele; 768 } 769 compareTo(JoinedFunction o)770 public int compareTo(JoinedFunction o) { 771 return comparableName.compareTo(o.comparableName); 772 } 773 equals(Function right)774 public boolean equals(Function right) { 775 return this.prototype.equals(right.prototype); 776 } 777 } 778 779 TransformerFactory transFac = TransformerFactory.newInstance(); 780 makeTransformer(InputStream is, String name)781 Transformer makeTransformer(InputStream is, String name) { 782 if (is == null) { 783 throw new InternalError("No inputstream set for " + name); 784 } 785 System.err.println("Transforming from: " + name); 786 Transformer t; 787 try { 788 StreamSource ss = new StreamSource(is); 789 ss.setSystemId(new File(".")); 790 t = transFac.newTransformer(ss); 791 } catch (TransformerConfigurationException e) { 792 e.printStackTrace(); 793 throw new InternalError("Couldn't make transformer for " + name + " - " + e.getMessageAndLocation()); 794 } 795 if (t == null) { 796 throw new InternalError("Couldn't make transformer for " + name); 797 } 798 return t; 799 } 800 reportSelectedFun(Node joinedNode)801 private void reportSelectedFun(Node joinedNode) 802 throws TransformerException, ParserConfigurationException, SAXException, IOException { 803 Transformer report = makeTransformer(reportXslStream, RPTXSLT); 804 // report.setParameter("leftStatus", leftStatus); 805 report.setParameter("leftVer", leftVer); 806 // report.setParameter("rightStatus", rightStatus); 807 report.setParameter("ourYear", new Integer(new java.util.GregorianCalendar().get(java.util.Calendar.YEAR))); 808 report.setParameter("rightVer", rightVer); 809 report.setParameter("rightMilestone", rightMilestone); 810 report.setParameter("leftMilestone", leftMilestone); 811 report.setParameter("dateTime", new GregorianCalendar().getTime()); 812 report.setParameter("notFound", notFound); 813 814 DOMSource src = new DOMSource(joinedNode); 815 816 Result res = new StreamResult(resultFile); 817 // DOMResult res = new DOMResult(); 818 report.transform(src, res); 819 // dumpNode(res.getNode(),""); 820 } 821 getFullList(InputStream dumpXsltStream, String dumpXsltFile)822 private Set<JoinedFunction> getFullList(InputStream dumpXsltStream, String dumpXsltFile) 823 throws TransformerException, ParserConfigurationException, XPathExpressionException, SAXException, 824 IOException { 825 // prepare transformer 826 XPath xpath = XPathFactory.newInstance().newXPath(); 827 String expression = "/list"; 828 Transformer transformer = makeTransformer(dumpXsltStream, dumpXsltFile); 829 830 // InputSource leftSource = new InputSource(leftDir + "index.xml"); 831 DOMSource leftIndex = new DOMSource(getDocument(new File(leftDir, INDEX_XML))); 832 DOMResult leftResult = new DOMResult(); 833 transformer.setParameter(DOC_FOLDER, leftDir); 834 transformer.transform(leftIndex, leftResult); 835 836 // Node leftList = XPathAPI.selectSingleNode(leftResult.getNode(),"/list"); 837 Node leftList = (Node) xpath.evaluate(expression, leftResult.getNode(), XPathConstants.NODE); 838 if (leftList == null) { 839 // dumpNode(xsltSource.getNode()); 840 dumpNode(leftResult.getNode()); 841 // dumpNode(leftIndex.getNode()); 842 System.out.flush(); 843 System.err.flush(); 844 throw new InternalError("getFullList(" + dumpXsltFile.toString() + ") returned a null left " + expression); 845 } 846 847 xpath.reset(); // reuse 848 849 DOMSource rightIndex = new DOMSource(getDocument(new File(rightDir, INDEX_XML))); 850 DOMResult rightResult = new DOMResult(); 851 transformer.setParameter(DOC_FOLDER, rightDir); 852 System.err.println("Loading: " + dumpXsltFile.toString()); 853 transformer.transform(rightIndex, rightResult); 854 System.err.println(" .. loaded: " + dumpXsltFile.toString()); 855 Node rightList = (Node) xpath.evaluate(expression, rightResult.getNode(), XPathConstants.NODE); 856 if (rightList == null) { 857 throw new InternalError("getFullList(" + dumpXsltFile.toString() + ") returned a null right " + expression); 858 } 859 // dumpNode(rightList,""); 860 861 Set<Function> leftSet = nodeToSet(leftList); 862 Set<Function> rightSet = nodeToSet(rightList); 863 Set<JoinedFunction> joined = fullJoin(leftSet, rightSet); 864 return joined; 865 // joinedNode = setToNode(joined); 866 // dumpNode(joinedNode,""); 867 // return joinedNode; 868 } 869 870 /** 871 * @param node 872 * @return Set<Fun> 873 */ nodeToSet(Node node)874 private Set<Function> nodeToSet(Node node) { 875 Set<Function> s = new TreeSet<Function>(); 876 NodeList list = node.getChildNodes(); 877 for (int i = 0; i < list.getLength(); i++) { 878 Node n = list.item(i); 879 s.add(Function.fromXml(n)); 880 } 881 return s; 882 } 883 884 /** 885 * @param set Set<JoinedFun> 886 * @return 887 * @throws ParserConfigurationException 888 */ setToNode(Set<JoinedFunction> set)889 private Node setToNode(Set<JoinedFunction> set) throws ParserConfigurationException { 890 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 891 Document doc = dbf.newDocumentBuilder().newDocument(); 892 Element root = doc.createElement("list"); 893 doc.appendChild(root); 894 for (Iterator<JoinedFunction> iter = set.iterator(); iter.hasNext();) { 895 JoinedFunction fun = iter.next(); 896 root.appendChild(fun.toXml(doc)); 897 } 898 899 // add the 'changed' stuff 900 Element root2 = doc.createElement("simplifications"); 901 root.appendChild(root2); 902 { 903 for (String simplification : getChangedSimplifications()) { 904 Element subSimplification = doc.createElement("simplification"); 905 Element baseElement = doc.createElement("base"); 906 baseElement.appendChild(doc.createTextNode(simplification)); 907 subSimplification.appendChild(baseElement); 908 909 root2.appendChild(subSimplification); 910 911 for (String change : simplifications.get(simplification)) { 912 Element changeElement = doc.createElement("change"); 913 changeElement.appendChild(doc.createTextNode(change)); 914 subSimplification.appendChild(changeElement); 915 } 916 } 917 } 918 919 return doc; 920 } 921 922 /** 923 * full-join two Set on 'prototype' 924 * 925 * @param left Set<Fun> 926 * @param right Set<Fun> 927 * @return Set<JoinedFun> 928 */ fullJoin(Set<Function> left, Set<Function> right)929 private static Set<JoinedFunction> fullJoin(Set<Function> left, Set<Function> right) { 930 931 Set<JoinedFunction> joined = new TreeSet<JoinedFunction>(); // Set<JoinedFun> 932 Set<Function> common = new TreeSet<Function>(); // Set<Fun> 933 for (Iterator<Function> iter1 = left.iterator(); iter1.hasNext();) { 934 Function f1 = iter1.next(); 935 for (Iterator<Function> iter2 = right.iterator(); iter2.hasNext();) { 936 Function f2 = iter2.next(); 937 if (f1.equals(f2)) { 938 // should add left item to common set 939 // since we will remove common items with left set later 940 common.add(f1); 941 joined.add(JoinedFunction.fromTwoFun(f1, f2)); 942 right.remove(f2); 943 break; 944 } 945 } 946 } 947 948 for (Iterator<Function> iter = common.iterator(); iter.hasNext();) { 949 Function f = iter.next(); 950 left.remove(f); 951 } 952 953 for (Iterator<Function> iter = left.iterator(); iter.hasNext();) { 954 Function f = iter.next(); 955 joined.add(JoinedFunction.fromLeftFun(f)); 956 } 957 958 for (Iterator<Function> iter = right.iterator(); iter.hasNext();) { 959 Function f = iter.next(); 960 joined.add(JoinedFunction.fromRightFun(f)); 961 } 962 return joined; 963 } 964 dumpNode(Node n)965 private static void dumpNode(Node n) { 966 dumpNode(n, ""); 967 } 968 969 /** 970 * Dump out a node for debugging. Recursive fcn 971 * 972 * @param n 973 * @param pre 974 */ dumpNode(Node n, String pre)975 private static void dumpNode(Node n, String pre) { 976 String opre = pre; 977 pre += " "; 978 System.out.print(opre + "<" + n.getNodeName()); 979 // dump attribute 980 NamedNodeMap attr = n.getAttributes(); 981 if (attr != null) { 982 for (int i = 0; i < attr.getLength(); i++) { 983 System.out.print( 984 "\n" + pre + " " + attr.item(i).getNodeName() + "=\"" + attr.item(i).getNodeValue() + "\""); 985 } 986 } 987 System.out.println(">"); 988 989 // dump value 990 String v = pre + n.getNodeValue(); 991 if (n.getNodeType() == Node.TEXT_NODE) 992 System.out.println(v); 993 994 // dump sub nodes 995 NodeList nList = n.getChildNodes(); 996 for (int i = 0; i < nList.getLength(); i++) { 997 Node ln = nList.item(i); 998 dumpNode(ln, pre + " "); 999 } 1000 System.out.println(opre + "</" + n.getNodeName() + ">"); 1001 } 1002 1003 private static DocumentBuilder theBuilder = null; 1004 private static DocumentBuilderFactory dbf = null; 1005 getDocumentBuilder()1006 private synchronized static DocumentBuilder getDocumentBuilder() throws ParserConfigurationException { 1007 if (theBuilder == null) { 1008 dbf = DocumentBuilderFactory.newInstance(); 1009 theBuilder = dbf.newDocumentBuilder(); 1010 } 1011 return theBuilder; 1012 } 1013 getDocument(File file)1014 private static Document getDocument(File file) throws ParserConfigurationException, SAXException, IOException { 1015 FileInputStream fis = new FileInputStream(file); 1016 InputSource inputSource = new InputSource(fis); 1017 Document doc = getDocumentBuilder().parse(inputSource); 1018 return doc; 1019 } 1020 1021 static boolean tried = false; 1022 static Formatter aFormatter = null; 1023 1024 public interface Formatter { formatCode(String s)1025 public String formatCode(String s); 1026 } 1027 1028 public static String format_keywords[] = { "enum", "#define", "static" }; 1029 1030 /** 1031 * Attempt to use a pretty formatter 1032 * 1033 * @param prototype2 1034 * @return 1035 */ formatCode(String prototype2)1036 public static String formatCode(String prototype2) { 1037 if (!tried) { 1038 String theFormatter = StableAPI.class.getPackage().getName() + ".CodeFormatter"; 1039 try { 1040 @SuppressWarnings("unchecked") 1041 Class<Formatter> formatClass = (Class<Formatter>) Class.forName(theFormatter); 1042 aFormatter = (Formatter) formatClass.newInstance(); 1043 } catch (Exception e) { 1044 System.err.println("Note: Couldn't load " + theFormatter); 1045 aFormatter = new Formatter() { 1046 1047 public String formatCode(String s) { 1048 String str = HTMLSafe(s.trim()); 1049 for (String keyword : format_keywords) { 1050 if (str.startsWith(keyword)) { 1051 str = str.replaceFirst(keyword, "<tt>" + keyword + "</tt>"); 1052 } 1053 } 1054 return str; 1055 } 1056 1057 }; 1058 } 1059 tried = true; 1060 } 1061 if (aFormatter != null) { 1062 return aFormatter.formatCode(prototype2); 1063 } else { 1064 return HTMLSafe(prototype2); 1065 } 1066 } 1067 HTMLSafe(String s)1068 public static String HTMLSafe(String s) { 1069 if (s == null) 1070 return null; 1071 1072 return s.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """); 1073 } 1074 1075 } 1076