2 *******************************************************************************
\r
3 * Copyright (C) 2004-2010, International Business Machines Corporation and *
\r
4 * others. All Rights Reserved. *
\r
5 *******************************************************************************
\r
9 * Compare two API files (generated by GatherAPIData) and generate a report
\r
10 * on the differences.
\r
12 * Sample invocation:
\r
13 * java -old: icu4j28.api.zip -new: icu4j30.api -html -out: icu4j_compare_28_30.html
\r
16 * - make 'changed apis' smarter - detect method parameter or return type change
\r
17 * for this, the sequential search through methods ordered by signature won't do.
\r
18 * We need to gather all added and removed overloads for a method, and then
\r
19 * compare all added against all removed in order to identify this kind of
\r
23 package com.ibm.icu.dev.tool.docs;
\r
25 import java.io.BufferedWriter;
\r
26 import java.io.FileNotFoundException;
\r
27 import java.io.FileOutputStream;
\r
28 import java.io.OutputStream;
\r
29 import java.io.OutputStreamWriter;
\r
30 import java.io.PrintWriter;
\r
31 import java.io.UnsupportedEncodingException;
\r
32 import java.text.DateFormat;
\r
33 import java.text.SimpleDateFormat;
\r
34 import java.util.ArrayList;
\r
35 import java.util.Collection;
\r
36 import java.util.Comparator;
\r
37 import java.util.Date;
\r
38 import java.util.HashSet;
\r
39 import java.util.Iterator;
\r
40 import java.util.Set;
\r
41 import java.util.TreeSet;
\r
43 public class ReportAPI {
\r
49 TreeSet<APIInfo> added;
\r
50 TreeSet<APIInfo> removed;
\r
51 TreeSet<APIInfo> promoted;
\r
52 TreeSet<APIInfo> obsoleted;
\r
53 ArrayList<DeltaInfo> changed;
\r
55 static final class DeltaInfo extends APIInfo {
\r
59 DeltaInfo(APIInfo added, APIInfo removed) {
\r
61 this.removed = removed;
\r
64 public int getVal(int typ) {
\r
65 return added.getVal(typ);
\r
68 public String get(int typ, boolean brief) {
\r
69 return added.get(typ, brief);
\r
72 public void print(PrintWriter pw, boolean detail, boolean html) {
\r
74 removed.print(pw, detail, html);
\r
76 pw.println("</br>");
\r
81 added.print(pw, detail, html);
\r
85 public static void main(String[] args) {
\r
86 String oldFile = null;
\r
87 String newFile = null;
\r
88 String outFile = null;
\r
89 boolean html = false;
\r
90 boolean internal = false;
\r
91 for (int i = 0; i < args.length; ++i) {
\r
92 String arg = args[i];
\r
93 if (arg.equals("-old:")) {
\r
94 oldFile = args[++i];
\r
95 } else if (arg.equals("-new:")) {
\r
96 newFile = args[++i];
\r
97 } else if (arg.equals("-out:")) {
\r
98 outFile = args[++i];
\r
99 } else if (arg.equals("-html")) {
\r
101 } else if (arg.equals("-internal")) {
\r
106 new ReportAPI(oldFile, newFile, internal).writeReport(outFile, html, internal);
\r
110 while the both are methods and the class and method names are the same, collect
\r
111 overloads. when you hit a new method or class, compare the overloads
\r
112 looking for the same # of params and simple param changes. ideally
\r
113 there are just a few.
\r
115 String oldA = null;
\r
116 String oldR = null;
\r
117 if (!a.isMethod()) {
\r
118 remove and continue
\r
120 String am = a.getClassName() + "." + a.getName();
\r
121 String rm = r.getClassName() + "." + r.getName();
\r
122 int comp = am.compare(rm);
\r
123 if (comp == 0 && a.isMethod() && r.isMethod())
\r
127 ReportAPI(String oldFile, String newFile, boolean internal) {
\r
128 this(APIData.read(oldFile, internal), APIData.read(newFile, internal));
\r
131 ReportAPI(APIData oldData, APIData newData) {
\r
132 this.oldData = oldData;
\r
133 this.newData = newData;
\r
135 removed = (TreeSet<APIInfo>)oldData.set.clone();
\r
136 removed.removeAll(newData.set);
\r
138 added = (TreeSet<APIInfo>)newData.set.clone();
\r
139 added.removeAll(oldData.set);
\r
141 changed = new ArrayList<DeltaInfo>();
\r
142 Iterator<APIInfo> ai = added.iterator();
\r
143 Iterator<APIInfo> ri = removed.iterator();
\r
144 Comparator<APIInfo> c = APIInfo.changedComparator();
\r
146 ArrayList<APIInfo> ams = new ArrayList<APIInfo>();
\r
147 ArrayList<APIInfo> rms = new ArrayList<APIInfo>();
\r
148 //PrintWriter outpw = new PrintWriter(System.out);
\r
150 APIInfo a = null, r = null;
\r
151 while ((a != null || ai.hasNext()) && (r != null || ri.hasNext())) {
\r
152 if (a == null) a = ai.next();
\r
153 if (r == null) r = ri.next();
\r
155 String am = a.getClassName() + "." + a.getName();
\r
156 String rm = r.getClassName() + "." + r.getName();
\r
157 int comp = am.compareTo(rm);
\r
158 if (comp == 0 && a.isMethod() && r.isMethod()) { // collect overloads
\r
159 ams.add(a); a = null;
\r
160 rms.add(r); r = null;
\r
164 if (!ams.isEmpty()) {
\r
165 // simplest case first
\r
166 if (ams.size() == 1 && rms.size() == 1) {
\r
167 changed.add(new DeltaInfo(ams.get(0), rms.get(0)));
\r
169 // dang, what to do now?
\r
170 // TODO: modify deltainfo to deal with lists of added and removed
\r
176 int result = c.compare(a, r);
\r
179 } else if (result > 0) {
\r
182 changed.add(new DeltaInfo(a, r));
\r
188 // now clean up added and removed by cleaning out the changed members
\r
189 Iterator<DeltaInfo> ci = changed.iterator();
\r
190 while (ci.hasNext()) {
\r
191 DeltaInfo di = ci.next();
\r
192 added.remove(di.added);
\r
193 removed.remove(di.removed);
\r
196 Set<APIInfo> tempAdded = new HashSet<APIInfo>();
\r
197 tempAdded.addAll(newData.set);
\r
198 tempAdded.removeAll(removed);
\r
199 TreeSet<APIInfo> changedAdded = new TreeSet<APIInfo>(APIInfo.defaultComparator());
\r
200 changedAdded.addAll(tempAdded);
\r
202 Set<APIInfo> tempRemoved = new HashSet<APIInfo>();
\r
203 tempRemoved.addAll(oldData.set);
\r
204 tempRemoved.removeAll(added);
\r
205 TreeSet<APIInfo> changedRemoved = new TreeSet<APIInfo>(APIInfo.defaultComparator());
\r
206 changedRemoved.addAll(tempRemoved);
\r
208 promoted = new TreeSet<APIInfo>(APIInfo.defaultComparator());
\r
209 obsoleted = new TreeSet<APIInfo>(APIInfo.defaultComparator());
\r
210 ai = changedAdded.iterator();
\r
211 ri = changedRemoved.iterator();
\r
213 while ((a != null || ai.hasNext()) && (r != null || ri.hasNext())) {
\r
214 if (a == null) a = ai.next();
\r
215 if (r == null) r = ri.next();
\r
216 int result = c.compare(a, r);
\r
219 } else if (result > 0) {
\r
222 int change = statusChange(a, r);
\r
225 } else if (change < 0) {
\r
233 added = stripAndResort(added);
\r
234 removed = stripAndResort(removed);
\r
235 promoted = stripAndResort(promoted);
\r
236 obsoleted = stripAndResort(obsoleted);
\r
239 private int statusChange(APIInfo lhs, APIInfo rhs) { // new. old
\r
240 for (int i = 0; i < APIInfo.NUM_TYPES; ++i) {
\r
241 if (lhs.get(i, true).equals(rhs.get(i, true)) == (i == APIInfo.STA)) {
\r
245 int lstatus = lhs.getVal(APIInfo.STA);
\r
246 if (lstatus == APIInfo.STA_OBSOLETE
\r
247 || lstatus == APIInfo.STA_DEPRECATED
\r
248 || lstatus == APIInfo.STA_INTERNAL) {
\r
254 private boolean writeReport(String outFile, boolean html, boolean internal) {
\r
255 OutputStream os = System.out;
\r
256 if (outFile != null) {
\r
258 os = new FileOutputStream(outFile);
\r
260 catch (FileNotFoundException e) {
\r
261 RuntimeException re = new RuntimeException(e.getMessage());
\r
267 PrintWriter pw = null;
\r
269 pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(os, "UTF-8")));
\r
271 catch (UnsupportedEncodingException e) {
\r
272 throw new IllegalStateException(); // UTF-8 should always be supported
\r
275 DateFormat fmt = new SimpleDateFormat("yyyy");
\r
276 String year = fmt.format(new Date());
\r
277 String title = "ICU4J API Comparison: " + oldData.name + " with " + newData.name;
\r
278 String info = "Contents generated by ReportAPI tool on " + new Date().toString();
\r
279 String copyright = "Copyright (C) " + year +
\r
280 ", International Business Machines Corporation, All Rights Reserved.";
\r
283 pw.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">");
\r
284 pw.println("<html>");
\r
285 pw.println("<head>");
\r
286 pw.println("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">");
\r
287 pw.println("<title>" + title + "</title>");
\r
288 pw.println("<!-- Copyright " + year + ", IBM, All Rights Reserved. -->");
\r
289 pw.println("</head>");
\r
290 pw.println("<body>");
\r
292 pw.println("<h1>" + title + "</h1>");
\r
295 pw.println("<hr/>");
\r
296 pw.println("<h2>Removed from " + oldData.name +"</h2>");
\r
297 if (removed.size() > 0) {
\r
298 printResults(removed, pw, true, false);
\r
300 pw.println("<p>(no API removed)</p>");
\r
304 pw.println("<hr/>");
\r
306 pw.println("<h2>Withdrawn, Deprecated, or Obsoleted in " + newData.name + "</h2>");
\r
308 pw.println("<h2>Deprecated or Obsoleted in " + newData.name + "</h2>");
\r
310 if (obsoleted.size() > 0) {
\r
311 printResults(obsoleted, pw, true, false);
\r
313 pw.println("<p>(no API obsoleted)</p>");
\r
317 pw.println("<hr/>");
\r
318 pw.println("<h2>Changed in " + newData.name + " (old, new)</h2>");
\r
319 if (changed.size() > 0) {
\r
320 printResults(changed, pw, true, true);
\r
322 pw.println("<p>(no API changed)</p>");
\r
326 pw.println("<hr/>");
\r
327 pw.println("<h2>Promoted to stable in " + newData.name + "</h2>");
\r
328 if (promoted.size() > 0) {
\r
329 printResults(promoted, pw, true, false);
\r
331 pw.println("<p>(no API promoted)</p>");
\r
335 pw.println("<hr/>");
\r
336 pw.println("<h2>Added in " + newData.name + "</h2>");
\r
337 if (added.size() > 0) {
\r
338 printResults(added, pw, true, false);
\r
340 pw.println("<p>(no API added)</p>");
\r
343 pw.println("<hr/>");
\r
344 pw.println("<p><i><font size=\"-1\">" + info + "<br/>" + copyright + "</font></i></p>");
\r
345 pw.println("</body>");
\r
346 pw.println("</html>");
\r
352 pw.println("=== Removed from " + oldData.name + " ===");
\r
353 if (removed.size() > 0) {
\r
354 printResults(removed, pw, false, false);
\r
356 pw.println("(no API removed)");
\r
362 pw.println("=== Withdrawn, Deprecated, or Obsoleted in " + newData.name + " ===");
\r
364 pw.println("=== Deprecated or Obsoleted in " + newData.name + " ===");
\r
366 if (obsoleted.size() > 0) {
\r
367 printResults(obsoleted, pw, false, false);
\r
369 pw.println("(no API obsoleted)");
\r
374 pw.println("=== Changed in " + newData.name + " (old, new) ===");
\r
375 if (changed.size() > 0) {
\r
376 printResults(changed, pw, false, true);
\r
378 pw.println("(no API changed)");
\r
383 pw.println("=== Promoted to stable in " + newData.name + " ===");
\r
384 if (promoted.size() > 0) {
\r
385 printResults(promoted, pw, false, false);
\r
387 pw.println("(no API promoted)");
\r
392 pw.println("=== Added in " + newData.name + " ===");
\r
393 if (added.size() > 0) {
\r
394 printResults(added, pw, false, false);
\r
396 pw.println("(no API added)");
\r
400 pw.println("================");
\r
402 pw.println(copyright);
\r
409 private static void printResults(Collection<? extends APIInfo> c, PrintWriter pw, boolean html,
\r
410 boolean isChangedAPIs) {
\r
411 Iterator<? extends APIInfo> iter = c.iterator();
\r
412 String pack = null;
\r
413 String clas = null;
\r
414 while (iter.hasNext()) {
\r
415 APIInfo info = iter.next();
\r
417 String packageName = info.getPackageName();
\r
418 if (!packageName.equals(pack)) {
\r
420 if (clas != null) {
\r
421 pw.println("</ul>");
\r
423 if (pack != null) {
\r
424 pw.println("</ul>");
\r
427 pw.println("<h3>Package " + packageName + "</h3>");
\r
430 if (pack != null) {
\r
434 pw.println("Package " + packageName + ":");
\r
438 pack = packageName;
\r
442 if (!info.isClass()) {
\r
443 String className = info.getClassName();
\r
444 if (!className.equals(clas)) {
\r
446 if (clas != null) {
\r
447 pw.println("</ul>");
\r
449 pw.println(className);
\r
450 pw.println("<ul>");
\r
452 pw.println(className);
\r
460 info.print(pw, isChangedAPIs, html);
\r
461 pw.println("</li>");
\r
463 info.println(pw, isChangedAPIs, html);
\r
468 if (clas != null) {
\r
469 pw.println("</ul>");
\r
471 if (pack != null) {
\r
472 pw.println("</ul>");
\r
478 private static TreeSet<APIInfo> stripAndResort(TreeSet<APIInfo> t) {
\r
480 TreeSet<APIInfo> r = new TreeSet<APIInfo>(APIInfo.classFirstComparator());
\r
485 private static void stripClassInfo(Collection<APIInfo> c) {
\r
486 // c is sorted with class info first
\r
487 Iterator<? extends APIInfo> iter = c.iterator();
\r
488 String cname = null;
\r
489 while (iter.hasNext()) {
\r
490 APIInfo info = iter.next();
\r
491 String className = info.getClassName();
\r
492 if (cname != null) {
\r
493 if (cname.equals(className)) {
\r
499 if (info.isClass()) {
\r
500 cname = info.getName();
\r