• 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                             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("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;");
1078     }
1079 
1080 }
1081