| // © 2017 and later: Unicode, Inc. and others. |
| // License & terms of use: http://www.unicode.org/copyright.html |
| /* |
| ********************************************************************** |
| * Copyright (C) 2016 and later: Unicode, Inc. and others. |
| * Copyright (c) 2006-2013, International Business Machines |
| * Corporation and others. All Rights Reserved. |
| ********************************************************************** |
| * Created on 2006-7-24 ? |
| * Moved from Java 1.4 to 1.5? API by srl 2009-01-16 |
| */ |
| package com.ibm.icu.dev.tools.docs; |
| |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.util.GregorianCalendar; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.regex.*; |
| import java.util.Set; |
| import java.util.TreeMap; |
| import java.util.TreeSet; |
| |
| import javax.xml.parsers.DocumentBuilder; |
| import javax.xml.parsers.DocumentBuilderFactory; |
| import javax.xml.parsers.ParserConfigurationException; |
| import javax.xml.transform.Result; |
| import javax.xml.transform.Transformer; |
| import javax.xml.transform.TransformerConfigurationException; |
| import javax.xml.transform.TransformerException; |
| import javax.xml.transform.TransformerFactory; |
| import javax.xml.transform.dom.DOMResult; |
| import javax.xml.transform.dom.DOMSource; |
| import javax.xml.transform.stream.StreamResult; |
| import javax.xml.transform.stream.StreamSource; |
| import javax.xml.xpath.XPath; |
| import javax.xml.xpath.XPathConstants; |
| import javax.xml.xpath.XPathExpressionException; |
| import javax.xml.xpath.XPathFactory; |
| |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.NamedNodeMap; |
| import org.w3c.dom.Node; |
| import org.w3c.dom.NodeList; |
| import org.xml.sax.InputSource; |
| import org.xml.sax.SAXException; |
| |
| /* |
| A utility to report the status change between two ICU releases |
| |
| To use the utility |
| 1. Generate the XML files |
| (put the two ICU releases on your machine ^_^ ) |
| (generate 'Doxygen' file on Windows platform with Cygwin's help) |
| Edit the generated 'Doxygen' file under ICU4C source directory |
| a) GENERATE_XML = YES |
| b) Sync the ALIASES definiation |
| (For example, copy the ALIASES defination from ICU 3.6 |
| Doxygen file to ICU 3.4 Doxygen file.) |
| c) gerenate the XML files |
| 2. Build the tool |
| Download Apache Xerces Java Parser |
| Build this file with the library |
| 3. Edit the api-report-config.xml file & Change the file according your real configuration |
| 4. Run the tool to generate the report. |
| */ |
| |
| /** |
| * CLI tool to report the status change between two ICU releases |
| * @author Raymond Yang |
| */ |
| public class StableAPI { |
| |
| private static final String DOC_FOLDER = "docFolder"; |
| private static final String INDEX_XML = "index.xml"; |
| private static final String ICU_SPACE_PREFIX = "ICU "; |
| private static final String INITIALIZER_XPATH = "initializer"; |
| private static final String NAME_XPATH = "name"; |
| private static final String UVERSIONA = "uvernum_8h.xml"; |
| private static final String UVERSIONB = "uversion_8h.xml"; |
| private static final String U_ICU_VERSION = "U_ICU_VERSION"; |
| /* ICU 4.4+ */ |
| private static final String ICU_VERSION_XPATHA = "/doxygen/compounddef[@id='uvernum_8h'][@kind='file']/sectiondef[@kind='define']"; |
| /* ICU <4.4 */ |
| private static final String ICU_VERSION_XPATHB = "/doxygen/compounddef[@id='uversion_8h'][@kind='file']/sectiondef[@kind='define']"; |
| private static String ICU_VERSION_XPATH = ICU_VERSION_XPATHA; |
| |
| private String leftVer; |
| private File leftDir = null; |
| // private String leftStatus; |
| private String leftMilestone = ""; |
| |
| private String rightVer; |
| private File rightDir = null; |
| // private String rightStatus; |
| private String rightMilestone = ""; |
| |
| private InputStream dumpCppXsltStream = null; |
| private InputStream dumpCXsltStream = null; |
| private InputStream reportXslStream = null; |
| private static final String CXSLT = "dumpAllCFunc.xslt"; |
| private static final String CPPXSLT = "dumpAllCppFunc.xslt"; |
| private static final String RPTXSLT = "genReport.xslt"; |
| |
| private File dumpCppXslt; |
| private File dumpCXslt; |
| private File reportXsl; |
| private File resultFile; |
| |
| static Map<String, Set<String>> simplifications = new TreeMap<String, Set<String>>(); |
| |
| static void addSimplification(String prototype0, String prototype) { |
| Set<String> s = simplifications.get(prototype); |
| if (s == null) { |
| s = new TreeSet<String>(); |
| simplifications.put(prototype, s); |
| } |
| s.add(prototype0); |
| } |
| |
| static Set<String> getChangedSimplifications() { |
| Set<String> output = new TreeSet<String>(); |
| for (Map.Entry<String, Set<String>> e : simplifications.entrySet()) { |
| if (e.getValue().size() > 1) { |
| output.add(e.getKey()); |
| } |
| } |
| return output; |
| } |
| |
| final private static String notFound = "(missing)"; |
| |
| public static void main(String[] args) throws TransformerException, ParserConfigurationException, SAXException, |
| IOException, XPathExpressionException { |
| |
| StableAPI t = new StableAPI(); |
| t.run(args); |
| } |
| |
| private void run(String[] args) throws XPathExpressionException, TransformerException, ParserConfigurationException, |
| SAXException, IOException { |
| this.parseArgs(args); |
| Set<JoinedFunction> full = new TreeSet<JoinedFunction>(); |
| |
| System.err.println("Reading C++..."); |
| Set<JoinedFunction> setCpp = this.getFullList(dumpCppXsltStream, dumpCppXslt.getName()); |
| full.addAll(setCpp); |
| System.out.println("read " + setCpp.size() + " C++. Reading C:"); |
| |
| Set<JoinedFunction> setC = this.getFullList(dumpCXsltStream, dumpCXslt.getName()); |
| full.addAll(setC); |
| |
| System.out.println("read " + setC.size() + " C. Setting node:"); |
| |
| Node fullList = this.setToNode(full); |
| // t.dumpNode(fullList,""); |
| |
| System.out.println("Node set. Reporting:"); |
| |
| this.reportSelectedFun(fullList); |
| System.out.println("Done. Please check " + this.resultFile); |
| |
| Set<String> changedSimp = getChangedSimplifications(); |
| if (!changedSimp.isEmpty()) { |
| System.out.println("--- changed simplifications ---"); |
| for (String k : changedSimp) { |
| System.out.println(k); |
| for (String s : simplifications.get(k)) { |
| System.out.println("\t" + s); |
| } |
| } |
| } |
| } |
| |
| private void parseArgs(String[] args) { |
| for (int i = 0; i < args.length; i++) { |
| String arg = args[i]; |
| if (arg == null || arg.length() == 0) { |
| continue; |
| } |
| if (arg.equals("--help")) { |
| printUsage(); |
| } else if (arg.equals("--oldver")) { |
| leftVer = args[++i]; |
| } else if (arg.equals("--olddir")) { |
| leftDir = new File(args[++i]); |
| } else if (arg.equals("--newver")) { |
| rightVer = args[++i]; |
| } else if (arg.equals("--newdir")) { |
| rightDir = new File(args[++i]); |
| } else if (arg.equals("--cxslt")) { |
| dumpCXslt = new File(args[++i]); |
| } else if (arg.equals("--cppxslt")) { |
| dumpCppXslt = new File(args[++i]); |
| } else if (arg.equals("--reportxslt")) { |
| reportXsl = new File(args[++i]); |
| } else if (arg.equals("--resultfile")) { |
| resultFile = new File(args[++i]); |
| } else { |
| System.out.println("Unknown option: " + arg); |
| printUsage(); |
| } |
| } |
| |
| dumpCppXsltStream = loadStream(CPPXSLT, "--cppxslt", dumpCppXslt); |
| dumpCXsltStream = loadStream(CXSLT, "--cxslt", dumpCXslt); |
| reportXslStream = loadStream(RPTXSLT, "--reportxslt", reportXsl); |
| |
| leftVer = trimICU(setVer(leftVer, "old", leftDir)); |
| rightVer = trimICU(setVer(rightVer, "new", rightDir)); |
| } |
| |
| @SuppressWarnings("resource") |
| private InputStream loadStream(String name, String argName, File argFile) { |
| InputStream stream = null; |
| if (argFile != null) { |
| try { |
| stream = new FileInputStream(argFile); |
| System.out.println("Loaded file " + argFile.getName()); |
| } catch (IOException ioe) { |
| throw new RuntimeException( |
| "Error: Could not load " + argName + " " + argFile.getPath() + " - " + ioe.toString(), ioe); |
| } |
| } else { |
| stream = StableAPI.class.getResourceAsStream(name); |
| if (stream == null) { |
| throw new InternalError("No resource found for " + StableAPI.class.getPackage().getName() + "/" + name |
| + " - use " + argName); |
| } else { |
| System.out.println("Loaded resource " + name); |
| } |
| } |
| return stream; |
| } |
| |
| private static Set<String> warnSet = new TreeSet<String>(); |
| |
| private static void warn(String what) { |
| if (!warnSet.contains(what)) { |
| System.out.println("Warning: " + what); |
| if (warnSet.isEmpty()) { |
| System.out.println(" (These warnings are only printed one time each.)"); |
| } |
| warnSet.add(what); |
| } |
| } |
| |
| private static boolean didWarnSuperTrim = false; |
| |
| private static String trimICU(String ver) { |
| Matcher icuVersionMatcher = Pattern.compile("ICU *\\d+(\\.\\d+){0,2}").matcher(ver); |
| if (icuVersionMatcher.find()) { |
| return icuVersionMatcher.group(); |
| } else { |
| warn("@whatever not followed by ICU <version number>"); |
| return ""; |
| } |
| } |
| |
| private String setVer(String prevVer, String whichVer, File dir) { |
| String UVERSION = UVERSIONA; |
| if (dir == null) { |
| System.out.println("--" + whichVer + "dir not set."); |
| printUsage(); /* exits */ |
| } else if (!dir.exists() || !dir.isDirectory()) { |
| System.out.println("--" + whichVer + "dir=" + dir.getName() + " does not exist or is not a directory."); |
| printUsage(); /* exits */ |
| } |
| String result = null; |
| // looking for: <name>U_ICU_VERSION</name> in uversion_8h.xml: |
| // <initializer>"3.8.1"</initializer> |
| try { |
| File verFile = new File(dir, UVERSION); |
| if (!verFile.exists()) { |
| UVERSION = UVERSIONB; |
| ICU_VERSION_XPATH = ICU_VERSION_XPATHB; |
| verFile = new File(dir, UVERSION); |
| } else { |
| ICU_VERSION_XPATH = ICU_VERSION_XPATHA; |
| } |
| Document doc = getDocument(verFile); |
| DOMSource uvernum_h = new DOMSource(doc); |
| XPath xpath = XPathFactory.newInstance().newXPath(); |
| |
| Node defines = (Node) xpath.evaluate(ICU_VERSION_XPATH, uvernum_h.getNode(), XPathConstants.NODE); |
| |
| if (defines == null) { |
| System.err.println("can't load from " + verFile.getName() + ":" + ICU_VERSION_XPATH); |
| } |
| |
| NodeList nList = defines.getChildNodes(); |
| for (int i = 0; result == null && (i < nList.getLength()); i++) { |
| Node ln = nList.item(i); |
| if (!"memberdef".equals(ln.getNodeName())) { |
| continue; |
| } |
| Node name = (Node) xpath.evaluate(NAME_XPATH, ln, XPathConstants.NODE); |
| if (name == null) |
| continue; |
| |
| // System.err.println("Gotta node: " + name); |
| |
| Node nameVal = name.getFirstChild(); |
| if (nameVal == null) |
| nameVal = name; |
| |
| String nameStr = nameVal.getNodeValue(); |
| if (nameStr == null) |
| continue; |
| |
| // System.err.println("Gotta name: " + nameStr); |
| |
| if (nameStr.trim().equals(U_ICU_VERSION)) { |
| Node initializer = (Node) xpath.evaluate(INITIALIZER_XPATH, ln, XPathConstants.NODE); |
| if (initializer == null) |
| System.err.println("initializer with no value"); |
| Node initVal = initializer.getFirstChild(); |
| // if(initVal==null) initVal = initializer; |
| String initStr = initVal.getNodeValue().trim().replaceAll("\"", ""); |
| result = ICU_SPACE_PREFIX + initStr; |
| System.err.println("Detected " + whichVer + " version: " + result); |
| |
| String milestoneOf = ""; |
| |
| // TODO: #1 use UVersionInfo. (this tool doesn't depend on ICU4J yet) |
| // #2 move this to a utility function: strip/"explain" an ICU version #. |
| if (result.startsWith("ICU ")) { |
| String vers[] = result.substring(4).split("\\."); |
| int maj = Integer.parseInt(vers[0]); |
| int min = vers.length > 1 ? Integer.parseInt(vers[1]) : 0; |
| int micr = vers.length > 2 ? Integer.parseInt(vers[2]) : 0; |
| int patch = vers.length > 3 ? Integer.parseInt(vers[3]) : 0; |
| System.err.println( |
| " == [" + vers.toString() + "] " + maj + " . " + min + " . " + micr + " . " + patch); |
| if (maj >= 49) { |
| // new scheme: 49 and following. |
| String truncVersion = "ICU " + maj; |
| if (min == 0) { |
| milestoneOf = " (m" + micr + ")"; |
| System.err.println(" .. " + milestoneOf + " is a milestone towards " + truncVersion); |
| } else if (min == 1) { |
| // Don't denote as milestone |
| result = "ICU " + (maj); |
| System.err.println(" .. " + milestoneOf + " is the release of " + truncVersion); |
| } else { |
| milestoneOf = " (update #" + (min - 1) + ": " + result.substring(4) + ")"; |
| result = "ICU " + (maj); |
| System.err.println(" .. " + milestoneOf + " is an update to " + truncVersion); |
| } |
| // always truncate to major # for comparing tags. |
| result = truncVersion; |
| } else { |
| // old scheme - 1.0.* .. 4.8.* |
| String truncVersion = "ICU " + maj + "." + min; |
| if ((min % 2) == 1) { |
| milestoneOf = " (" + maj + "." + (min + 1) + "m" + micr + ")"; |
| truncVersion = "ICU " + (maj) + "." + (min + 1); |
| System.err.println(" .. " + milestoneOf + " is a milestone towards " + truncVersion); |
| } else if (micr == 0 && patch == 0) { |
| System.err.println(" .. " + milestoneOf + " is the release of " + truncVersion); |
| } else { |
| milestoneOf = " (update " + micr + "." + patch + ")"; |
| System.err.println(" .. " + milestoneOf + " is an update to " + truncVersion); |
| } |
| result = truncVersion; |
| } |
| if (whichVer.equals("new")) { |
| rightMilestone = milestoneOf; |
| } else { |
| leftMilestone = milestoneOf; |
| } |
| } |
| } |
| |
| } |
| // dumpNode(defines,""); |
| } catch (Throwable t) { |
| t.printStackTrace(); |
| System.err.println( |
| "Warning: Couldn't get " + whichVer + " version from " + UVERSION + " - reverting to " + prevVer); |
| result = prevVer; |
| } |
| |
| if (result != null) { |
| |
| } |
| |
| if (prevVer != null) { |
| if (result != null) { |
| if (!result.equals(prevVer)) { |
| System.err.println("Note: Detected " + result + " version but we'll use your requested --" |
| + whichVer + "ver " + prevVer); |
| result = prevVer; |
| if (!rightMilestone.isEmpty() && whichVer.equals("new")) { |
| System.err.println(" .. ignoring milestone indicator " + rightMilestone); |
| rightMilestone = ""; |
| } |
| if (!leftMilestone.isEmpty() && !whichVer.equals("new")) { |
| leftMilestone = ""; |
| } |
| } else { |
| System.err.println("Note: You don't need to use '--" + whichVer + "ver " + result |
| + "' anymore - we detected it correctly."); |
| } |
| } else { |
| System.err.println( |
| "Note: Didn't detect version so we'll use your requested --" + whichVer + "ver " + prevVer); |
| result = prevVer; |
| if (!rightMilestone.isEmpty() && whichVer.equals("new")) { |
| System.err.println(" .. ignoring milestone indicator " + rightMilestone); |
| rightMilestone = ""; |
| } |
| if (!leftMilestone.isEmpty() && !whichVer.equals("new")) { |
| leftMilestone = ""; |
| } |
| } |
| } |
| |
| if (result == null) { |
| System.err.println("prevVer=" + prevVer); |
| System.err.println("Error: You'll need to use the option \"--" + whichVer |
| + "ver\" because we could not detect an ICU version in " + UVERSION); |
| throw new InternalError("Error: You'll need to use the option \"--" + whichVer |
| + "ver\" because we could not detect an ICU version in " + UVERSION); |
| } |
| |
| return result; |
| } |
| |
| private static void printUsage() { |
| System.out.println("Usage: StableAPI option* target*"); |
| System.out.println(); |
| System.out.println("Options:"); |
| System.out.println(" --help Print this text"); |
| System.out.println(" --oldver Version of old version of ICU (optional)"); |
| System.out.println(" --olddir Directory that contains xml docs of old version"); |
| System.out.println(" --newver Version of new version of ICU (optional)"); |
| System.out.println(" --newdir Directory that contains xml docs of new version"); |
| System.out.println(" --cxslt XSLT file for C docs"); |
| System.out.println(" --cppxslt XSLT file for C++ docs"); |
| System.out.println(" --reportxslt XSLT file for report docs"); |
| System.out.println(" --resultfile Output file"); |
| System.exit(-1); |
| } |
| |
| static String getAttr(Node node, String attrName) { |
| if (node.getAttributes() == null && node.getNodeType() == 3) { |
| // return "(text node 3)"; |
| return "(Node: " + node.toString() + " )"; |
| // return |
| // node.getFirstChild().getAttributes().getNamedItem(attrName).getNodeValue(); |
| } |
| |
| try { |
| return node.getAttributes().getNamedItem(attrName).getNodeValue(); |
| } catch (NullPointerException npe) { |
| if (node.getAttributes() == null) { |
| throw new InternalError( |
| "[no attributes Can't get attr " + attrName + " out of node " + node.getNodeName() + ":" |
| + node.getNodeType() + ":" + node.getNodeValue() + "@" + node.getTextContent()); |
| } else if (node.getAttributes().getNamedItem(attrName) == null) { |
| return null; |
| // throw new InternalError("No attribute named: "+attrName); |
| } else { |
| System.err.println("Can't get attr " + attrName + ": " + npe.toString()); |
| } |
| npe.printStackTrace(); |
| throw new InternalError("Can't get attr " + attrName); |
| } |
| } |
| |
| static String getAttr(NamedNodeMap attrList, String attrName) { |
| return attrList.getNamedItem(attrName).getNodeValue(); |
| } |
| |
| static class Function implements Comparable<Function> { |
| public String prototype; |
| public String id; |
| public String status; |
| public String version; |
| public String file; |
| public String comparableName; |
| public String comparablePrototype; |
| |
| public boolean equals(Function right) { |
| return this.comparablePrototype.equals(right.comparablePrototype); |
| } |
| |
| static Function fromXml(Node n) { |
| Function f = new Function(); |
| f.prototype = getAttr(n, "prototype"); |
| |
| if ("yes".equals(getAttr(n, "static")) && !f.prototype.contains("static")) { |
| f.prototype = "static ".concat(f.prototype); |
| } |
| |
| f.id = getAttr(n, "id"); |
| f.status = getAttr(n, "status"); |
| f.version = trimICU(getAttr(n, "version")); |
| f.file = getAttr(n, "file"); |
| f.purifyPrototype(); |
| |
| f.simplifyPrototype(); |
| |
| f.comparablePrototype = f.prototype; |
| // Modify the prototype here, but don't display it to the user. ( Char16Ptr --> |
| // char16_t* etc ) |
| for (int i = 0; i < aliasList.length; i += 2) { |
| f.comparablePrototype = f.comparablePrototype.replaceAll(aliasList[i + 0], aliasList[i + 1]); |
| } |
| |
| if (f.file == null) { |
| f.file = "{null}"; |
| } else { |
| f.file = Function.getBasename(f.file); |
| } |
| f.comparableName = f.comparableName(); |
| return f; |
| } |
| |
| /** |
| * Convert string to basename. |
| * |
| * @param str |
| * @return |
| */ |
| private static String getBasename(String str) { |
| int i = str.lastIndexOf("/"); |
| str = i == -1 ? str : str.substring(i + 1); |
| return str; |
| } |
| |
| static private String replList[] = { "[ ]*\\([ ]*void[ ]*\\)", "() ", // (void) => () |
| "[ ]*,", ", ", // No spaces preceding commas. |
| "[ ]*\\*[ ]*", "* ", // No spaces preceding '*'. |
| "[ ]*=[ ]*0[ ]*$", "=0 ", // No spaces in " = 0". |
| "[ ]{2,}", " ", "\n", " " // Multiple spaces collapse to single. |
| }; |
| |
| /** |
| * these are noted as deltas. |
| */ |
| static private String simplifyList[] = { |
| "[ ]*=[ ]*0[ ]*$", "",// remove pure virtual |
| // TODO: notify about this difference, separately |
| "[ ]*U_NOEXCEPT", "", // remove U_NOEXCEPT (this was fixed in Doxyfile, but fixing here so it is |
| // retroactive) |
| "[ ]*U_OVERRIDE", "", // remove U_OVERRIDE |
| // Simplify possibly-covariant functions to void* |
| "^([^\\* ]+)\\*(.*)::(clone|safeClone|cloneAsThawed|freeze|createBufferClone)\\((.*)", "void*$2::$3($4", |
| "\\s+$", "", // remove trailing spaces. |
| "^U_NAMESPACE_END ", "" // Bug in processing of uspoof.h |
| }; |
| |
| /** |
| * This list is applied only for comparisons. The resulting string is NOT shown |
| * to the user. These should be ignored as far as changes go. func(UChar) === |
| * func(char16_t) |
| */ |
| static private String aliasList[] = { "UChar", "char16_t", "ConstChar16Ptr", "const char16_t*", "Char16Ptr", |
| "char16_t*", }; |
| |
| /** |
| * Special cases: |
| * |
| * Remove the status attribute embedded in the C prototype |
| * |
| * Remove the virtual keyword in Cpp prototype |
| */ |
| private void purifyPrototype() { |
| // refer to 'umachine.h' |
| String statusList[] = { "U_CAPI", "U_STABLE", "U_DRAFT", "U_DEPRECATED", "U_OBSOLETE", "U_INTERNAL", |
| "virtual", "U_EXPORT2", "U_I18N_API", "U_COMMON_API" }; |
| for (int i = 0; i < statusList.length; i++) { |
| String s = statusList[i]; |
| prototype = prototype.replaceAll(s, ""); |
| prototype = prototype.trim(); |
| } |
| |
| for (int i = 0; i < replList.length; i += 2) { |
| prototype = prototype.replaceAll(replList[i + 0], replList[i + 1]); |
| } |
| |
| prototype = prototype.trim(); |
| |
| // Now, remove parameter names! |
| StringBuffer out = new StringBuffer(); |
| StringBuffer in = new StringBuffer(prototype); |
| int openParen = in.indexOf("("); |
| int closeParen = in.lastIndexOf(")"); |
| |
| if (openParen == -1 || closeParen == -1) |
| return; // exit, malformed? |
| if (openParen + 1 == closeParen) |
| return; // exit: () |
| |
| out.append(in, 0, openParen + 1); // prelude |
| |
| for (int left = openParen + 1; left < closeParen;) { |
| int right = in.indexOf(",", left + 1); // right edge |
| if (right >= closeParen || right == -1) |
| right = closeParen; // found last comma |
| |
| // System.err.println("Considering " + left + " / " + right + " - " + closeParen |
| // + " : " + in.substring(left, right)); |
| |
| if (left == right) |
| continue; |
| |
| // find variable name |
| int rightCh = right - 1; |
| if (rightCh == left) { // 1 ch- break |
| out.append(in, left, right); |
| continue; |
| } |
| // eat whitespace at right |
| int nameEndCh = rightCh; |
| while (nameEndCh > left && Character.isWhitespace(in.charAt(nameEndCh))) { |
| nameEndCh--; |
| } |
| int nameStartCh = nameEndCh; |
| while (nameStartCh > left && Character.isJavaIdentifierPart(in.charAt(nameStartCh))) { |
| nameStartCh--; |
| } |
| |
| // now, did we find something to skip? |
| if (nameStartCh > left && nameEndCh > nameStartCh) { |
| out.append(in, left, nameStartCh + 1); |
| } else { |
| // pass through |
| out.append(in, left, right); |
| } |
| |
| left = right; |
| } |
| |
| out.append(in, closeParen, in.length()); // postlude |
| |
| // Delete any doubled whitespace. |
| for (int p = 1; p < out.length(); p++) { |
| char prev = out.charAt(p - 1); |
| if (Character.isWhitespace(prev)) { |
| while (out.length() > p && (Character.isWhitespace(out.charAt(p)))) { |
| out.deleteCharAt(p); |
| } |
| if (out.length() > p) { |
| // any trailings to delete? |
| char curr = out.charAt(p); |
| if (curr == ',' || curr == ')' || curr == '*' || curr == '&') { // delete spaces before these. |
| out.deleteCharAt(--p); |
| continue; |
| } |
| } |
| } |
| } |
| |
| // System.err.println(prototype+" -> " + out.toString()); |
| prototype = out.toString(); |
| } |
| |
| private void simplifyPrototype() { |
| if (prototype.startsWith("#define")) { |
| return; |
| } |
| final String prototype0 = prototype; |
| for (int i = 0; i < simplifyList.length; i += 2) { |
| prototype = prototype.replaceAll(simplifyList[i + 0], simplifyList[i + 1]); |
| } |
| if (!prototype0.equals(prototype)) { |
| addSimplification(prototype0, prototype); |
| } |
| } |
| |
| /** |
| * @Override |
| */ |
| public int compareTo(Function o) { |
| return comparableName.compareTo(((Function) o).comparableName); |
| } |
| |
| public String comparableName() { |
| return file + "|" + comparablePrototype + "|" + status + "|" + version + "|" + id; |
| } |
| } |
| |
| static class JoinedFunction implements Comparable<JoinedFunction> { |
| public String prototype; |
| public String leftRefId; |
| public String leftStatus; |
| public String leftVersion; |
| public String rightVersion; |
| public String leftFile; |
| public String rightRefId; |
| public String rightStatus; |
| public String rightFile; |
| |
| public String comparableName; |
| |
| static JoinedFunction fromLeftFun(Function left) { |
| JoinedFunction u = new JoinedFunction(); |
| u.prototype = left.prototype; |
| u.leftRefId = left.id; |
| u.leftStatus = left.status; |
| u.leftFile = left.file; |
| u.rightRefId = notFound; |
| // u.rightVersion = nul; |
| u.leftVersion = left.version; |
| u.rightStatus = notFound; |
| u.rightFile = notFound; |
| u.comparableName = left.comparableName; |
| return u; |
| } |
| |
| static JoinedFunction fromRightFun(Function right) { |
| JoinedFunction u = new JoinedFunction(); |
| u.prototype = right.prototype; |
| u.leftRefId = notFound; |
| u.leftStatus = notFound; |
| u.leftFile = notFound; |
| // u.leftVersion = nul; |
| u.rightVersion = right.version; |
| u.rightRefId = right.id; |
| u.rightStatus = right.status; |
| u.rightFile = right.file; |
| u.comparableName = right.comparableName; |
| return u; |
| } |
| |
| static JoinedFunction fromTwoFun(Function left, Function right) { |
| if (!left.equals(right)) |
| throw new Error(); |
| JoinedFunction u = new JoinedFunction(); |
| u.prototype = left.prototype; |
| u.leftRefId = left.id; |
| u.leftStatus = left.status; |
| u.leftFile = left.file; |
| u.rightRefId = right.id; |
| u.rightStatus = right.status; |
| u.leftVersion = left.version; |
| u.rightVersion = right.version; |
| u.rightFile = right.file; |
| u.comparableName = left.comparableName + "+" + right.comparableName; |
| return u; |
| } |
| |
| Element toXml(Document doc) { |
| Element ele = doc.createElement("func"); |
| ele.setAttribute("prototype", formatCode(prototype)); |
| // ele.setAttribute("leftRefId", leftRefId); |
| |
| ele.setAttribute("leftStatus", leftStatus); |
| // ele.setAttribute("rightRefId", rightRefId); |
| ele.setAttribute("rightStatus", rightStatus); |
| ele.setAttribute("leftVersion", leftVersion); |
| // ele.setAttribute("rightRefId", rightRefId); |
| ele.setAttribute("rightVersion", rightVersion); |
| |
| // String f = rightRefId.equals(notFound) ? leftRefId : rightRefId; |
| // int tail = f.indexOf("_"); |
| // f = tail != -1 ? f.substring(0, tail) : f; |
| // f = f.startsWith("class") ? f.replaceFirst("class","") : f; |
| String f = rightFile.equals(notFound) ? leftFile : rightFile; |
| ele.setAttribute("file", f); |
| return ele; |
| } |
| |
| public int compareTo(JoinedFunction o) { |
| return comparableName.compareTo(o.comparableName); |
| } |
| |
| public boolean equals(Function right) { |
| return this.prototype.equals(right.prototype); |
| } |
| } |
| |
| TransformerFactory transFac = TransformerFactory.newInstance(); |
| |
| Transformer makeTransformer(InputStream is, String name) { |
| if (is == null) { |
| throw new InternalError("No inputstream set for " + name); |
| } |
| System.err.println("Transforming from: " + name); |
| Transformer t; |
| try { |
| StreamSource ss = new StreamSource(is); |
| ss.setSystemId(new File(".")); |
| t = transFac.newTransformer(ss); |
| } catch (TransformerConfigurationException e) { |
| e.printStackTrace(); |
| throw new InternalError("Couldn't make transformer for " + name + " - " + e.getMessageAndLocation()); |
| } |
| if (t == null) { |
| throw new InternalError("Couldn't make transformer for " + name); |
| } |
| return t; |
| } |
| |
| private void reportSelectedFun(Node joinedNode) |
| throws TransformerException, ParserConfigurationException, SAXException, IOException { |
| Transformer report = makeTransformer(reportXslStream, RPTXSLT); |
| // report.setParameter("leftStatus", leftStatus); |
| report.setParameter("leftVer", leftVer); |
| // report.setParameter("rightStatus", rightStatus); |
| report.setParameter("ourYear", new Integer(new java.util.GregorianCalendar().get(java.util.Calendar.YEAR))); |
| report.setParameter("rightVer", rightVer); |
| report.setParameter("rightMilestone", rightMilestone); |
| report.setParameter("leftMilestone", leftMilestone); |
| report.setParameter("dateTime", new GregorianCalendar().getTime()); |
| report.setParameter("notFound", notFound); |
| |
| DOMSource src = new DOMSource(joinedNode); |
| |
| Result res = new StreamResult(resultFile); |
| // DOMResult res = new DOMResult(); |
| report.transform(src, res); |
| // dumpNode(res.getNode(),""); |
| } |
| |
| private Set<JoinedFunction> getFullList(InputStream dumpXsltStream, String dumpXsltFile) |
| throws TransformerException, ParserConfigurationException, XPathExpressionException, SAXException, |
| IOException { |
| // prepare transformer |
| XPath xpath = XPathFactory.newInstance().newXPath(); |
| String expression = "/list"; |
| Transformer transformer = makeTransformer(dumpXsltStream, dumpXsltFile); |
| |
| // InputSource leftSource = new InputSource(leftDir + "index.xml"); |
| DOMSource leftIndex = new DOMSource(getDocument(new File(leftDir, INDEX_XML))); |
| DOMResult leftResult = new DOMResult(); |
| transformer.setParameter(DOC_FOLDER, leftDir); |
| transformer.transform(leftIndex, leftResult); |
| |
| // Node leftList = XPathAPI.selectSingleNode(leftResult.getNode(),"/list"); |
| Node leftList = (Node) xpath.evaluate(expression, leftResult.getNode(), XPathConstants.NODE); |
| if (leftList == null) { |
| // dumpNode(xsltSource.getNode()); |
| dumpNode(leftResult.getNode()); |
| // dumpNode(leftIndex.getNode()); |
| System.out.flush(); |
| System.err.flush(); |
| throw new InternalError("getFullList(" + dumpXsltFile.toString() + ") returned a null left " + expression); |
| } |
| |
| xpath.reset(); // reuse |
| |
| DOMSource rightIndex = new DOMSource(getDocument(new File(rightDir, INDEX_XML))); |
| DOMResult rightResult = new DOMResult(); |
| transformer.setParameter(DOC_FOLDER, rightDir); |
| System.err.println("Loading: " + dumpXsltFile.toString()); |
| transformer.transform(rightIndex, rightResult); |
| System.err.println(" .. loaded: " + dumpXsltFile.toString()); |
| Node rightList = (Node) xpath.evaluate(expression, rightResult.getNode(), XPathConstants.NODE); |
| if (rightList == null) { |
| throw new InternalError("getFullList(" + dumpXsltFile.toString() + ") returned a null right " + expression); |
| } |
| // dumpNode(rightList,""); |
| |
| Set<Function> leftSet = nodeToSet(leftList); |
| Set<Function> rightSet = nodeToSet(rightList); |
| Set<JoinedFunction> joined = fullJoin(leftSet, rightSet); |
| return joined; |
| // joinedNode = setToNode(joined); |
| // dumpNode(joinedNode,""); |
| // return joinedNode; |
| } |
| |
| /** |
| * @param node |
| * @return Set<Fun> |
| */ |
| private Set<Function> nodeToSet(Node node) { |
| Set<Function> s = new TreeSet<Function>(); |
| NodeList list = node.getChildNodes(); |
| for (int i = 0; i < list.getLength(); i++) { |
| Node n = list.item(i); |
| s.add(Function.fromXml(n)); |
| } |
| return s; |
| } |
| |
| /** |
| * @param set Set<JoinedFun> |
| * @return |
| * @throws ParserConfigurationException |
| */ |
| private Node setToNode(Set<JoinedFunction> set) throws ParserConfigurationException { |
| DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); |
| Document doc = dbf.newDocumentBuilder().newDocument(); |
| Element root = doc.createElement("list"); |
| doc.appendChild(root); |
| for (Iterator<JoinedFunction> iter = set.iterator(); iter.hasNext();) { |
| JoinedFunction fun = iter.next(); |
| root.appendChild(fun.toXml(doc)); |
| } |
| |
| // add the 'changed' stuff |
| Element root2 = doc.createElement("simplifications"); |
| root.appendChild(root2); |
| { |
| for (String simplification : getChangedSimplifications()) { |
| Element subSimplification = doc.createElement("simplification"); |
| Element baseElement = doc.createElement("base"); |
| baseElement.appendChild(doc.createTextNode(simplification)); |
| subSimplification.appendChild(baseElement); |
| |
| root2.appendChild(subSimplification); |
| |
| for (String change : simplifications.get(simplification)) { |
| Element changeElement = doc.createElement("change"); |
| changeElement.appendChild(doc.createTextNode(change)); |
| subSimplification.appendChild(changeElement); |
| } |
| } |
| } |
| |
| return doc; |
| } |
| |
| /** |
| * full-join two Set on 'prototype' |
| * |
| * @param left Set<Fun> |
| * @param right Set<Fun> |
| * @return Set<JoinedFun> |
| */ |
| private static Set<JoinedFunction> fullJoin(Set<Function> left, Set<Function> right) { |
| |
| Set<JoinedFunction> joined = new TreeSet<JoinedFunction>(); // Set<JoinedFun> |
| Set<Function> common = new TreeSet<Function>(); // Set<Fun> |
| for (Iterator<Function> iter1 = left.iterator(); iter1.hasNext();) { |
| Function f1 = iter1.next(); |
| for (Iterator<Function> iter2 = right.iterator(); iter2.hasNext();) { |
| Function f2 = iter2.next(); |
| if (f1.equals(f2)) { |
| // should add left item to common set |
| // since we will remove common items with left set later |
| common.add(f1); |
| joined.add(JoinedFunction.fromTwoFun(f1, f2)); |
| right.remove(f2); |
| break; |
| } |
| } |
| } |
| |
| for (Iterator<Function> iter = common.iterator(); iter.hasNext();) { |
| Function f = iter.next(); |
| left.remove(f); |
| } |
| |
| for (Iterator<Function> iter = left.iterator(); iter.hasNext();) { |
| Function f = iter.next(); |
| joined.add(JoinedFunction.fromLeftFun(f)); |
| } |
| |
| for (Iterator<Function> iter = right.iterator(); iter.hasNext();) { |
| Function f = iter.next(); |
| joined.add(JoinedFunction.fromRightFun(f)); |
| } |
| return joined; |
| } |
| |
| private static void dumpNode(Node n) { |
| dumpNode(n, ""); |
| } |
| |
| /** |
| * Dump out a node for debugging. Recursive fcn |
| * |
| * @param n |
| * @param pre |
| */ |
| private static void dumpNode(Node n, String pre) { |
| String opre = pre; |
| pre += " "; |
| System.out.print(opre + "<" + n.getNodeName()); |
| // dump attribute |
| NamedNodeMap attr = n.getAttributes(); |
| if (attr != null) { |
| for (int i = 0; i < attr.getLength(); i++) { |
| System.out.print( |
| "\n" + pre + " " + attr.item(i).getNodeName() + "=\"" + attr.item(i).getNodeValue() + "\""); |
| } |
| } |
| System.out.println(">"); |
| |
| // dump value |
| String v = pre + n.getNodeValue(); |
| if (n.getNodeType() == Node.TEXT_NODE) |
| System.out.println(v); |
| |
| // dump sub nodes |
| NodeList nList = n.getChildNodes(); |
| for (int i = 0; i < nList.getLength(); i++) { |
| Node ln = nList.item(i); |
| dumpNode(ln, pre + " "); |
| } |
| System.out.println(opre + "</" + n.getNodeName() + ">"); |
| } |
| |
| private static DocumentBuilder theBuilder = null; |
| private static DocumentBuilderFactory dbf = null; |
| |
| private synchronized static DocumentBuilder getDocumentBuilder() throws ParserConfigurationException { |
| if (theBuilder == null) { |
| dbf = DocumentBuilderFactory.newInstance(); |
| theBuilder = dbf.newDocumentBuilder(); |
| } |
| return theBuilder; |
| } |
| |
| private static Document getDocument(File file) throws ParserConfigurationException, SAXException, IOException { |
| FileInputStream fis = new FileInputStream(file); |
| InputSource inputSource = new InputSource(fis); |
| Document doc = getDocumentBuilder().parse(inputSource); |
| return doc; |
| } |
| |
| static boolean tried = false; |
| static Formatter aFormatter = null; |
| |
| public interface Formatter { |
| public String formatCode(String s); |
| } |
| |
| public static String format_keywords[] = { "enum", "#define", "static" }; |
| |
| /** |
| * Attempt to use a pretty formatter |
| * |
| * @param prototype2 |
| * @return |
| */ |
| public static String formatCode(String prototype2) { |
| if (!tried) { |
| String theFormatter = StableAPI.class.getPackage().getName() + ".CodeFormatter"; |
| try { |
| @SuppressWarnings("unchecked") |
| Class<Formatter> formatClass = (Class<Formatter>) Class.forName(theFormatter); |
| aFormatter = (Formatter) formatClass.newInstance(); |
| } catch (Exception e) { |
| System.err.println("Note: Couldn't load " + theFormatter); |
| aFormatter = new Formatter() { |
| |
| public String formatCode(String s) { |
| String str = HTMLSafe(s.trim()); |
| for (String keyword : format_keywords) { |
| if (str.startsWith(keyword)) { |
| str = str.replaceFirst(keyword, "<tt>" + keyword + "</tt>"); |
| } |
| } |
| return str; |
| } |
| |
| }; |
| } |
| tried = true; |
| } |
| if (aFormatter != null) { |
| return aFormatter.formatCode(prototype2); |
| } else { |
| return HTMLSafe(prototype2); |
| } |
| } |
| |
| public static String HTMLSafe(String s) { |
| if (s == null) |
| return null; |
| |
| return s.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """); |
| } |
| |
| } |