blob: 59156f1e2d1d5fbd3cce2e1069a128fc8b8223b0 [file] [log] [blame]
/**
*******************************************************************************
* Copyright (C) 2004-2006, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
/**
* Compare two API files (generated by GatherAPIData) and generate a report
* on the differences.
*
* Sample invocation:
* java -old: icu4j28.api.zip -new: icu4j30.api -html -out: icu4j_compare_28_30.html
*
* TODO:
* - make 'changed apis' smarter - detect method parameter or return type change
* for this, the sequential search through methods ordered by signature won't do.
* We need to gather all added and removed overloads for a method, and then
* compare all added against all removed in order to identify this kind of
* change.
*/
package com.ibm.icu.dev.tool.docs;
import java.io.*;
import java.util.*;
import java.text.*;
public class ReportAPI {
APIData oldData;
APIData newData;
boolean html;
String outputFile;
TreeSet added;
TreeSet removed;
TreeSet promoted;
TreeSet obsoleted;
ArrayList changed;
static final class DeltaInfo extends APIInfo {
APIInfo added;
APIInfo removed;
DeltaInfo(APIInfo added, APIInfo removed) {
this.added = added;
this.removed = removed;
}
public int getVal(int typ) {
return added.getVal(typ);
}
public String get(int typ, boolean brief) {
return added.get(typ, brief);
}
public void print(PrintWriter pw, boolean detail, boolean html) {
pw.print(" ");
removed.print(pw, detail, html);
if (html) {
pw.println("</br>");
} else {
pw.println();
pw.print("--> ");
}
added.print(pw, detail, html);
}
}
public static void main(String[] args) {
String oldFile = null;
String newFile = null;
String outFile = null;
boolean html = false;
boolean internal = false;
for (int i = 0; i < args.length; ++i) {
String arg = args[i];
if (arg.equals("-old:")) {
oldFile = args[++i];
} else if (arg.equals("-new:")) {
newFile = args[++i];
} else if (arg.equals("-out:")) {
outFile = args[++i];
} else if (arg.equals("-html")) {
html = true;
} else if (arg.equals("-internal")) {
internal = true;
}
}
new ReportAPI(oldFile, newFile, internal).writeReport(outFile, html, internal);
}
/*
while the both are methods and the class and method names are the same, collect
overloads. when you hit a new method or class, compare the overloads
looking for the same # of params and simple param changes. ideally
there are just a few.
String oldA = null;
String oldR = null;
if (!a.isMethod()) {
remove and continue
}
String am = a.getClassName() + "." + a.getName();
String rm = r.getClassName() + "." + r.getName();
int comp = am.compare(rm);
if (comp == 0 && a.isMethod() && r.isMethod())
*/
ReportAPI(String oldFile, String newFile, boolean internal) {
this(APIData.read(oldFile, internal), APIData.read(newFile, internal));
}
ReportAPI(APIData oldData, APIData newData) {
this.oldData = oldData;
this.newData = newData;
removed = (TreeSet)oldData.set.clone();
removed.removeAll(newData.set);
added = (TreeSet)newData.set.clone();
added.removeAll(oldData.set);
changed = new ArrayList();
Iterator ai = added.iterator();
Iterator ri = removed.iterator();
Comparator c = APIInfo.changedComparator();
ArrayList ams = new ArrayList();
ArrayList rms = new ArrayList();
PrintWriter outpw = new PrintWriter(System.out);
APIInfo a = null, r = null;
while ((a != null || ai.hasNext()) && (r != null || ri.hasNext())) {
if (a == null) a = (APIInfo)ai.next();
if (r == null) r = (APIInfo)ri.next();
String am = a.getClassName() + "." + a.getName();
String rm = r.getClassName() + "." + r.getName();
int comp = am.compareTo(rm);
if (comp == 0 && a.isMethod() && r.isMethod()) { // collect overloads
ams.add(a); a = null;
rms.add(r); r = null;
continue;
}
if (!ams.isEmpty()) {
// simplest case first
if (ams.size() == 1 && rms.size() == 1) {
changed.add(new DeltaInfo((APIInfo)ams.get(0), (APIInfo)rms.get(0)));
} else {
// dang, what to do now?
// TODO: modify deltainfo to deal with lists of added and removed
}
ams.clear();
rms.clear();
}
int result = c.compare(a, r);
if (result < 0) {
a = null;
} else if (result > 0) {
r = null;
} else {
changed.add(new DeltaInfo(a, r));
a = null;
r = null;
}
}
// now clean up added and removed by cleaning out the changed members
Iterator ci = changed.iterator();
while (ci.hasNext()) {
DeltaInfo di = (DeltaInfo)ci.next();
added.remove(di.added);
removed.remove(di.removed);
}
Set tempAdded = new HashSet();
tempAdded.addAll(newData.set);
tempAdded.removeAll(removed);
TreeSet changedAdded = new TreeSet(APIInfo.defaultComparator());
changedAdded.addAll(tempAdded);
Set tempRemoved = new HashSet();
tempRemoved.addAll(oldData.set);
tempRemoved.removeAll(added);
TreeSet changedRemoved = new TreeSet(APIInfo.defaultComparator());
changedRemoved.addAll(tempRemoved);
promoted = new TreeSet(APIInfo.defaultComparator());
obsoleted = new TreeSet(APIInfo.defaultComparator());
ai = changedAdded.iterator();
ri = changedRemoved.iterator();
a = r = null;
while ((a != null || ai.hasNext()) && (r != null || ri.hasNext())) {
if (a == null) a = (APIInfo)ai.next();
if (r == null) r = (APIInfo)ri.next();
int result = c.compare(a, r);
if (result < 0) {
a = null;
} else if (result > 0) {
r = null;
} else {
int change = statusChange(a, r);
if (change > 0) {
promoted.add(a);
} else if (change < 0) {
obsoleted.add(a);
}
a = null;
r = null;
}
}
added = stripAndResort(added);
removed = stripAndResort(removed);
promoted = stripAndResort(promoted);
obsoleted = stripAndResort(obsoleted);
}
private int statusChange(APIInfo lhs, APIInfo rhs) { // new. old
for (int i = 0; i < APIInfo.NUM_TYPES; ++i) {
if (lhs.get(i, true).equals(rhs.get(i, true)) == (i == APIInfo.STA)) {
return 0;
}
}
int lstatus = lhs.getVal(APIInfo.STA);
if (lstatus == APIInfo.STA_OBSOLETE || lstatus == APIInfo.STA_DEPRECATED || lstatus == APIInfo.STA_INTERNAL) {
return -1;
}
return 1;
}
private boolean writeReport(String outFile, boolean html, boolean internal) {
OutputStream os = System.out;
if (outFile != null) {
try {
os = new FileOutputStream(outFile);
}
catch (FileNotFoundException e) {
RuntimeException re = new RuntimeException(e.getMessage());
re.initCause(e);
throw re;
}
}
PrintWriter pw = null;
try {
pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(os, "UTF-8")));
}
catch (UnsupportedEncodingException e) {
throw new IllegalStateException(); // UTF-8 should always be supported
}
DateFormat fmt = new SimpleDateFormat("yyyy");
String year = fmt.format(new Date());
String title = "ICU4J API Comparison: " + oldData.name + " with " + newData.name;
String info = "Contents generated by ReportAPI tool on " + new Date().toString();
String copyright = "Copyright (C) " + year + ", International Business Machines Corporation, All Rights Reserved.";
if (html) {
pw.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">");
pw.println("<html>");
pw.println("<head>");
pw.println("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">");
pw.println("<title>" + title + "</title>");
pw.println("<!-- Copyright " + year + ", IBM, All Rights Reserved. -->");
pw.println("</head>");
pw.println("<body>");
pw.println("<h1>" + title + "</h1>");
pw.println();
pw.println("<hr/>");
pw.println("<h2>Removed from " + oldData.name +"</h2>");
if (removed.size() > 0) {
printResults(removed, pw, true, false);
} else {
pw.println("<p>(no API removed)</p>");
}
pw.println();
pw.println("<hr/>");
if (internal) {
pw.println("<h2>Withdrawn, Deprecated, or Obsoleted in " + newData.name + "</h2>");
} else {
pw.println("<h2>Deprecated or Obsoleted in " + newData.name + "</h2>");
}
if (obsoleted.size() > 0) {
printResults(obsoleted, pw, true, false);
} else {
pw.println("<p>(no API obsoleted)</p>");
}
pw.println();
pw.println("<hr/>");
pw.println("<h2>Changed in " + newData.name + " (old, new)</h2>");
if (changed.size() > 0) {
printResults(changed, pw, true, true);
} else {
pw.println("<p>(no API changed)</p>");
}
pw.println();
pw.println("<hr/>");
pw.println("<h2>Promoted to stable in " + newData.name + "</h2>");
if (promoted.size() > 0) {
printResults(promoted, pw, true, false);
} else {
pw.println("<p>(no API promoted)</p>");
}
pw.println();
pw.println("<hr/>");
pw.println("<h2>Added in " + newData.name + "</h2>");
if (added.size() > 0) {
printResults(added, pw, true, false);
} else {
pw.println("<p>(no API added)</p>");
}
pw.println("<hr/>");
pw.println("<p><i><font size=\"-1\">" + info + "<br/>" + copyright + "</font></i></p>");
pw.println("</body>");
pw.println("</html>");
} else {
pw.println(title);
pw.println();
pw.println();
pw.println("=== Removed from " + oldData.name + " ===");
if (removed.size() > 0) {
printResults(removed, pw, false, false);
} else {
pw.println("(no API removed)");
}
pw.println();
pw.println();
if (internal) {
pw.println("=== Withdrawn, Deprecated, or Obsoleted in " + newData.name + " ===");
} else {
pw.println("=== Deprecated or Obsoleted in " + newData.name + " ===");
}
if (obsoleted.size() > 0) {
printResults(obsoleted, pw, false, false);
} else {
pw.println("(no API obsoleted)");
}
pw.println();
pw.println();
pw.println("=== Changed in " + newData.name + " (old, new) ===");
if (changed.size() > 0) {
printResults(changed, pw, false, true);
} else {
pw.println("(no API changed)");
}
pw.println();
pw.println();
pw.println("=== Promoted to stable in " + newData.name + " ===");
if (promoted.size() > 0) {
printResults(promoted, pw, false, false);
} else {
pw.println("(no API promoted)");
}
pw.println();
pw.println();
pw.println("=== Added in " + newData.name + " ===");
if (added.size() > 0) {
printResults(added, pw, false, false);
} else {
pw.println("(no API added)");
}
pw.println();
pw.println("================");
pw.println(info);
pw.println(copyright);
}
pw.close();
return false;
}
private static void printResults(Collection c, PrintWriter pw, boolean html, boolean isChangedAPIs) {
Iterator iter = c.iterator();
String pack = null;
String clas = null;
while (iter.hasNext()) {
APIInfo info = (APIInfo)iter.next();
String packageName = info.getPackageName();
if (!packageName.equals(pack)) {
if (html) {
if (clas != null) {
pw.println("</ul>");
}
if (pack != null) {
pw.println("</ul>");
}
pw.println();
pw.println("<h3>Package " + packageName + "</h3>");
pw.print("<ul>");
} else {
if (pack != null) {
pw.println();
}
pw.println();
pw.println("Package " + packageName + ":");
}
pw.println();
pack = packageName;
clas = null;
}
if (!info.isClass()) {
String className = info.getClassName();
if (!className.equals(clas)) {
if (html) {
if (clas != null) {
pw.println("</ul>");
}
pw.println(className);
pw.println("<ul>");
} else {
pw.println(className);
}
clas = className;
}
}
if (html) {
pw.print("<li>");
info.print(pw, isChangedAPIs, html);
pw.println("</li>");
} else {
info.println(pw, isChangedAPIs, html);
}
}
if (html) {
if (clas != null) {
pw.println("</ul>");
}
if (pack != null) {
pw.println("</ul>");
}
}
pw.println();
}
private static TreeSet stripAndResort(TreeSet t) {
stripClassInfo(t);
TreeSet r = new TreeSet(APIInfo.classFirstComparator());
r.addAll(t);
return r;
}
private static void stripClassInfo(Collection c) {
// c is sorted with class info first
Iterator iter = c.iterator();
String cname = null;
while (iter.hasNext()) {
APIInfo info = (APIInfo)iter.next();
String className = info.getClassName();
if (cname != null) {
if (cname.equals(className)) {
iter.remove();
continue;
}
cname = null;
}
if (info.isClass()) {
cname = info.getName();
}
}
}
}