blob: 86a92cefee84f1b8eb9d6e08c5c8cc64ae9fe2e0 [file] [log] [blame]
/*
**********************************************************************
* Copyright (c) 2006-2010, 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.Set;
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;
import com.sun.xml.internal.fastinfoset.stax.events.XMLConstants;
/**
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.
* @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 rightVer;
private File rightDir = null;
// private String rightStatus;
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;
private String milestoneOf = "";
final private static String nul = "None";
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, CPPXSLT);
full.addAll(setCpp);
System.out.println("read "+setCpp.size() +" C++. Reading C:");
Set<JoinedFunction> setC = this.getFullList(dumpCXsltStream, CXSLT);
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);
}
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));
}
private InputStream loadStream(String name, String argName, File argFile) {
InputStream stream = null;
if(argFile != null) {
try {
stream = new FileInputStream(argFile);
} 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) {
final String ICU_ = ICU_SPACE_PREFIX;
final String ICU = "ICU";
if(ver != null) { // trim everything before the 'ICU...'
ver = ver.trim();
int icuidx = ver.lastIndexOf(ICU_);
int icuidx1 = ver.lastIndexOf(ICU);
if(icuidx>=0) {
ver = ver.substring(icuidx+ICU_.length()).trim();
} else if(icuidx1>=0) {
warn("trimming: '" + ver + "'");
ver = ver.substring(icuidx1+ICU.length()).trim();
} else {
int n;
for(n=ver.length()-1;n>0 && ((ver.charAt(n)=='.') || Character.isDigit(ver.charAt(n))) ;n--)
;
warn("Trimming extraneous 'version' text: '" + ver + "'");
if(!didWarnSuperTrim) {
didWarnSuperTrim = true;
warn("Please ONLY use: '@whatever ICU X.Y.Z'");
}
if(n>0) {
ver = ver.substring(n+1).trim();
}
}
}
return ver;
}
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>&quot;3.8.1&quot;</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);
if(whichVer.equals("new") && result.startsWith("ICU ")) {
String vers[] = result.substring(4).split("\\.");
int maj = Integer.parseInt(vers[0]);
int min = Integer.parseInt(vers[1]);
if((min%2)==1) {
milestoneOf = " ("+result+")";
result = "ICU "+(maj)+"."+(min+1);
System.err.println(" .. " + milestoneOf + " is a milestone towards " + result);
}
}
}
}
//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(!milestoneOf.isEmpty()&&whichVer.equals("new")) {
System.err.println(" .. ignoring milestone indicator " + milestoneOf);
milestoneOf = "";
}
} 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(!milestoneOf.isEmpty()&&whichVer.equals("new")) {
System.err.println(" .. ignoring milestone indicator " + milestoneOf);
milestoneOf = "";
}
}
}
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 boolean equals(Function right){
return this.prototype.equals(right.prototype);
}
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();
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;
}
/**
* 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"};
for (int i = 0; i < statusList.length; i++) {
String s = statusList[i];
prototype = prototype.replaceAll(s,"");
prototype = prototype.trim();
}
String replList[] = { "[ ]*\\([ ]*void[ ]*\\)[ ]*", "()",
" , ", ", ",
};
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 Element toXml(Document doc){
// Element ele = doc.createElement("func");
// ele.setAttribute("prototype", prototype);
// ele.setAttribute("id", id);
// ele.setAttribute("status", status);
// return ele;
// }
public int compareTo(Function o) {
return comparableName.compareTo(((Function)o).comparableName);
}
public String comparableName() {
return file+"|"+prototype+"|"+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 = nul;
// u.rightVersion = nul;
u.leftVersion = left.version;
u.rightStatus = nul;
u.rightFile = nul;
u.comparableName = left.comparableName;
return u;
}
static JoinedFunction fromRightFun(Function right){
JoinedFunction u = new JoinedFunction();
u.prototype = right.prototype;
u.leftRefId = nul;
u.leftStatus = nul;
u.leftFile = nul;
// 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(nul) ? 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(nul) ? 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 {
// // check the prolog
// if(is.markSupported()) try {
// is.mark(100);
// int b = is.read();
// is.reset();
// System.err.println("Read byte: == " + Integer.toHexString(b));
// } catch(Throwable th) {
// System.err.println(" ( couldn't read a byte: " + th.toString()+ " )");
// } else {
// System.err.println(" ( couldn't set mark)");
// }
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", milestoneOf);
report.setParameter("dateTime", new GregorianCalendar().getTime());
report.setParameter("nul", nul);
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("abdToLoad "+dumpXsltFile.toString());
transformer.transform(rightIndex, rightResult);
System.err.println("dnlToLoad "+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));
}
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();
// if (f1.prototype.matches(".*Transliterator::.*")){
// System.err.println("left: " + f1.prototype);
// System.err.println("left: " + f1.status);
// }
for (Iterator<Function> iter2 = right.iterator(); iter2.hasNext();) {
Function f2 = iter2.next();
// if ( f1.prototype.matches(".*filteredTransliterate.*")
// && f2.prototype.matches(".*filteredTransliterate.*")){
// System.err.println("right: " + f2.prototype);
// System.err.println("right: " + f2.status);
// System.err.println(f1.prototype.equals(f2.prototype));
// System.err.println(f1.prototype.getBytes()[0]);
// System.err.println(f2.prototype.getBytes()[0]);
// }
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("&","&amp;")
.replaceAll("<","&lt;")
.replaceAll(">","&gt;")
.replaceAll("\"","&quot;");
}
}