• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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>&quot;3.8.1&quot;</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("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;");
1073     }
1074 
1075 }
1076