2 *******************************************************************************
3 * Copyright (C) 2004-2012, International Business Machines Corporation and *
4 * others. All Rights Reserved. *
5 *******************************************************************************
9 * Compare two API files (generated by GatherAPIData) and generate a report
13 * java -old: icu4j28.api.zip -new: icu4j30.api -html -out: icu4j_compare_28_30.html
16 * - make 'changed apis' smarter - detect method parameter or return type change
17 * for this, the sequential search through methods ordered by signature won't do.
18 * We need to gather all added and removed overloads for a method, and then
19 * compare all added against all removed in order to identify this kind of
23 package com.ibm.icu.dev.tool.docs;
25 import java.io.BufferedWriter;
26 import java.io.FileNotFoundException;
27 import java.io.FileOutputStream;
28 import java.io.OutputStream;
29 import java.io.OutputStreamWriter;
30 import java.io.PrintWriter;
31 import java.io.UnsupportedEncodingException;
32 import java.text.DateFormat;
33 import java.text.SimpleDateFormat;
34 import java.util.ArrayList;
35 import java.util.Collection;
36 import java.util.Comparator;
37 import java.util.Date;
38 import java.util.HashSet;
39 import java.util.Iterator;
41 import java.util.TreeSet;
43 public class ReportAPI {
49 TreeSet<APIInfo> added;
50 TreeSet<APIInfo> removed;
51 TreeSet<APIInfo> promotedStable;
52 TreeSet<APIInfo> promotedDraft;
53 TreeSet<APIInfo> obsoleted;
54 ArrayList<DeltaInfo> changed;
56 static final class DeltaInfo extends APIInfo {
60 DeltaInfo(APIInfo added, APIInfo removed) {
62 this.removed = removed;
65 public int getVal(int typ) {
66 return added.getVal(typ);
69 public String get(int typ, boolean brief) {
70 return added.get(typ, brief);
73 public void print(PrintWriter pw, boolean detail, boolean html) {
75 removed.print(pw, detail, html);
82 added.print(pw, detail, html);
86 public static void main(String[] args) {
87 String oldFile = null;
88 String newFile = null;
89 String outFile = null;
91 boolean internal = false;
92 for (int i = 0; i < args.length; ++i) {
94 if (arg.equals("-old:")) {
96 } else if (arg.equals("-new:")) {
98 } else if (arg.equals("-out:")) {
100 } else if (arg.equals("-html")) {
102 } else if (arg.equals("-internal")) {
107 new ReportAPI(oldFile, newFile, internal).writeReport(outFile, html, internal);
111 while the both are methods and the class and method names are the same, collect
112 overloads. when you hit a new method or class, compare the overloads
113 looking for the same # of params and simple param changes. ideally
114 there are just a few.
121 String am = a.getClassName() + "." + a.getName();
122 String rm = r.getClassName() + "." + r.getName();
123 int comp = am.compare(rm);
124 if (comp == 0 && a.isMethod() && r.isMethod())
128 ReportAPI(String oldFile, String newFile, boolean internal) {
129 this(APIData.read(oldFile, internal), APIData.read(newFile, internal));
132 ReportAPI(APIData oldData, APIData newData) {
133 this.oldData = oldData;
134 this.newData = newData;
136 removed = (TreeSet<APIInfo>)oldData.set.clone();
137 removed.removeAll(newData.set);
139 added = (TreeSet<APIInfo>)newData.set.clone();
140 added.removeAll(oldData.set);
142 changed = new ArrayList<DeltaInfo>();
143 Iterator<APIInfo> ai = added.iterator();
144 Iterator<APIInfo> ri = removed.iterator();
145 Comparator<APIInfo> c = APIInfo.changedComparator();
147 ArrayList<APIInfo> ams = new ArrayList<APIInfo>();
148 ArrayList<APIInfo> rms = new ArrayList<APIInfo>();
149 //PrintWriter outpw = new PrintWriter(System.out);
151 APIInfo a = null, r = null;
152 while ((a != null || ai.hasNext()) && (r != null || ri.hasNext())) {
153 if (a == null) a = ai.next();
154 if (r == null) r = ri.next();
156 String am = a.getClassName() + "." + a.getName();
157 String rm = r.getClassName() + "." + r.getName();
158 int comp = am.compareTo(rm);
159 if (comp == 0 && a.isMethod() && r.isMethod()) { // collect overloads
160 ams.add(a); a = null;
161 rms.add(r); r = null;
165 if (!ams.isEmpty()) {
166 // simplest case first
167 if (ams.size() == 1 && rms.size() == 1) {
168 changed.add(new DeltaInfo(ams.get(0), rms.get(0)));
170 // dang, what to do now?
171 // TODO: modify deltainfo to deal with lists of added and removed
177 int result = c.compare(a, r);
180 } else if (result > 0) {
183 changed.add(new DeltaInfo(a, r));
189 // now clean up added and removed by cleaning out the changed members
190 Iterator<DeltaInfo> ci = changed.iterator();
191 while (ci.hasNext()) {
192 DeltaInfo di = ci.next();
193 added.remove(di.added);
194 removed.remove(di.removed);
197 Set<APIInfo> tempAdded = new HashSet<APIInfo>();
198 tempAdded.addAll(newData.set);
199 tempAdded.removeAll(removed);
200 TreeSet<APIInfo> changedAdded = new TreeSet<APIInfo>(APIInfo.defaultComparator());
201 changedAdded.addAll(tempAdded);
203 Set<APIInfo> tempRemoved = new HashSet<APIInfo>();
204 tempRemoved.addAll(oldData.set);
205 tempRemoved.removeAll(added);
206 TreeSet<APIInfo> changedRemoved = new TreeSet<APIInfo>(APIInfo.defaultComparator());
207 changedRemoved.addAll(tempRemoved);
209 promotedStable = new TreeSet<APIInfo>(APIInfo.defaultComparator());
210 promotedDraft = new TreeSet<APIInfo>(APIInfo.defaultComparator());
211 obsoleted = new TreeSet<APIInfo>(APIInfo.defaultComparator());
212 ai = changedAdded.iterator();
213 ri = changedRemoved.iterator();
215 while ((a != null || ai.hasNext()) && (r != null || ri.hasNext())) {
216 if (a == null) a = ai.next();
217 if (r == null) r = ri.next();
218 int result = c.compare(a, r);
221 } else if (result > 0) {
224 int change = statusChange(a, r);
227 promotedStable.add(a);
229 promotedDraft.add(a);
231 } else if (change < 0) {
239 added = stripAndResort(added);
240 removed = stripAndResort(removed);
241 promotedStable = stripAndResort(promotedStable);
242 promotedDraft = stripAndResort(promotedDraft);
243 obsoleted = stripAndResort(obsoleted);
246 private int statusChange(APIInfo lhs, APIInfo rhs) { // new. old
247 for (int i = 0; i < APIInfo.NUM_TYPES; ++i) {
248 if (lhs.get(i, true).equals(rhs.get(i, true)) == (i == APIInfo.STA)) {
252 int lstatus = lhs.getVal(APIInfo.STA);
253 if (lstatus == APIInfo.STA_OBSOLETE
254 || lstatus == APIInfo.STA_DEPRECATED
255 || lstatus == APIInfo.STA_INTERNAL) {
261 private boolean writeReport(String outFile, boolean html, boolean internal) {
262 OutputStream os = System.out;
263 if (outFile != null) {
265 os = new FileOutputStream(outFile);
267 catch (FileNotFoundException e) {
268 RuntimeException re = new RuntimeException(e.getMessage());
274 PrintWriter pw = null;
276 pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(os, "UTF-8")));
278 catch (UnsupportedEncodingException e) {
279 throw new IllegalStateException(); // UTF-8 should always be supported
282 DateFormat fmt = new SimpleDateFormat("yyyy");
283 String year = fmt.format(new Date());
284 String title = "ICU4J API Comparison: " + oldData.name + " with " + newData.name;
285 String info = "Contents generated by ReportAPI tool on " + new Date().toString();
286 String copyright = "Copyright (C) " + year +
287 ", International Business Machines Corporation, All Rights Reserved.";
290 pw.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">");
291 pw.println("<html>");
292 pw.println("<head>");
293 pw.println("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">");
294 pw.println("<title>" + title + "</title>");
295 pw.println("<!-- Copyright " + year + ", IBM, All Rights Reserved. -->");
296 pw.println("</head>");
297 pw.println("<body>");
299 pw.println("<h1>" + title + "</h1>");
303 pw.println("<h2>Removed from " + oldData.name +"</h2>");
304 if (removed.size() > 0) {
305 printResults(removed, pw, true, false);
307 pw.println("<p>(no API removed)</p>");
313 pw.println("<h2>Withdrawn, Deprecated, or Obsoleted in " + newData.name + "</h2>");
315 pw.println("<h2>Deprecated or Obsoleted in " + newData.name + "</h2>");
317 if (obsoleted.size() > 0) {
318 printResults(obsoleted, pw, true, false);
320 pw.println("<p>(no API obsoleted)</p>");
325 pw.println("<h2>Changed in " + newData.name + " (old, new)</h2>");
326 if (changed.size() > 0) {
327 printResults(changed, pw, true, true);
329 pw.println("<p>(no API changed)</p>");
334 pw.println("<h2>Promoted to stable in " + newData.name + "</h2>");
335 if (promotedStable.size() > 0) {
336 printResults(promotedStable, pw, true, false);
338 pw.println("<p>(no API promoted to stable)</p>");
342 // APIs promoted from internal to draft is reported only when
343 // internal API check is enabled
346 pw.println("<h2>Promoted to draft in " + newData.name + "</h2>");
347 if (promotedDraft.size() > 0) {
348 printResults(promotedDraft, pw, true, false);
350 pw.println("<p>(no API promoted to draft)</p>");
356 pw.println("<h2>Added in " + newData.name + "</h2>");
357 if (added.size() > 0) {
358 printResults(added, pw, true, false);
360 pw.println("<p>(no API added)</p>");
364 pw.println("<p><i><font size=\"-1\">" + info + "<br/>" + copyright + "</font></i></p>");
365 pw.println("</body>");
366 pw.println("</html>");
372 pw.println("=== Removed from " + oldData.name + " ===");
373 if (removed.size() > 0) {
374 printResults(removed, pw, false, false);
376 pw.println("(no API removed)");
382 pw.println("=== Withdrawn, Deprecated, or Obsoleted in " + newData.name + " ===");
384 pw.println("=== Deprecated or Obsoleted in " + newData.name + " ===");
386 if (obsoleted.size() > 0) {
387 printResults(obsoleted, pw, false, false);
389 pw.println("(no API obsoleted)");
394 pw.println("=== Changed in " + newData.name + " (old, new) ===");
395 if (changed.size() > 0) {
396 printResults(changed, pw, false, true);
398 pw.println("(no API changed)");
403 pw.println("=== Promoted to stable in " + newData.name + " ===");
404 if (promotedStable.size() > 0) {
405 printResults(promotedStable, pw, false, false);
407 pw.println("(no API promoted to stable)");
413 pw.println("=== Promoted to draft in " + newData.name + " ===");
414 if (promotedDraft.size() > 0) {
415 printResults(promotedDraft, pw, false, false);
417 pw.println("(no API promoted to draft)");
423 pw.println("=== Added in " + newData.name + " ===");
424 if (added.size() > 0) {
425 printResults(added, pw, false, false);
427 pw.println("(no API added)");
431 pw.println("================");
433 pw.println(copyright);
440 private static void printResults(Collection<? extends APIInfo> c, PrintWriter pw, boolean html,
441 boolean isChangedAPIs) {
442 Iterator<? extends APIInfo> iter = c.iterator();
445 while (iter.hasNext()) {
446 APIInfo info = iter.next();
448 String packageName = info.getPackageName();
449 if (!packageName.equals(pack)) {
458 pw.println("<h3>Package " + packageName + "</h3>");
465 pw.println("Package " + packageName + ":");
473 if (!info.isClass()) {
474 String className = info.getClassName();
475 if (!className.equals(clas)) {
480 pw.println(className);
483 pw.println(className);
491 info.print(pw, isChangedAPIs, html);
494 info.println(pw, isChangedAPIs, html);
509 private static TreeSet<APIInfo> stripAndResort(TreeSet<APIInfo> t) {
511 TreeSet<APIInfo> r = new TreeSet<APIInfo>(APIInfo.classFirstComparator());
516 private static void stripClassInfo(Collection<APIInfo> c) {
517 // c is sorted with class info first
518 Iterator<? extends APIInfo> iter = c.iterator();
520 while (iter.hasNext()) {
521 APIInfo info = iter.next();
522 String className = info.getClassName();
524 if (cname.equals(className)) {
530 if (info.isClass()) {
531 cname = info.getName();