]> gitweb.fperrin.net Git - Dictionary.git/blob - jars/icu4j-52_1/tools/build/src/com/ibm/icu/dev/tool/docs/ReportAPI.java
Clean up imports.
[Dictionary.git] / jars / icu4j-52_1 / tools / build / src / com / ibm / icu / dev / tool / docs / ReportAPI.java
1 /**
2 *******************************************************************************
3 * Copyright (C) 2004-2012, International Business Machines Corporation and    *
4 * others. All Rights Reserved.                                                *
5 *******************************************************************************
6 */
7
8 /**
9  * Compare two API files (generated by GatherAPIData) and generate a report
10  * on the differences.
11  *
12  * Sample invocation:
13  * java -old: icu4j28.api.zip -new: icu4j30.api -html -out: icu4j_compare_28_30.html
14  *
15  * TODO:
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
20  *     change.
21  */
22
23 package com.ibm.icu.dev.tool.docs;
24
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;
40 import java.util.Set;
41 import java.util.TreeSet;
42
43 public class ReportAPI {
44     APIData oldData;
45     APIData newData;
46     boolean html;
47     String outputFile;
48
49     TreeSet<APIInfo> added;
50     TreeSet<APIInfo> removed;
51     TreeSet<APIInfo> promotedStable;
52     TreeSet<APIInfo> promotedDraft;
53     TreeSet<APIInfo> obsoleted;
54     ArrayList<DeltaInfo> changed;
55
56     static final class DeltaInfo extends APIInfo {
57         APIInfo added;
58         APIInfo removed;
59
60         DeltaInfo(APIInfo added, APIInfo removed) {
61             this.added = added;
62             this.removed = removed;
63         }
64
65         public int getVal(int typ) {
66             return added.getVal(typ);
67         }
68
69         public String get(int typ, boolean brief) {
70             return added.get(typ, brief);
71         }
72
73         public void print(PrintWriter pw, boolean detail, boolean html) {
74             pw.print("    ");
75             removed.print(pw, detail, html);
76             if (html) {
77                 pw.println("</br>");
78             } else {
79                 pw.println();
80                 pw.print("--> ");
81             }
82             added.print(pw, detail, html);
83         }
84     }
85
86     public static void main(String[] args) {
87         String oldFile = null;
88         String newFile = null;
89         String outFile = null;
90         boolean html = false;
91         boolean internal = false;
92         for (int i = 0; i < args.length; ++i) {
93             String arg = args[i];
94             if (arg.equals("-old:")) {
95                 oldFile = args[++i];
96             } else if (arg.equals("-new:")) {
97                 newFile = args[++i];
98             } else if (arg.equals("-out:")) {
99                 outFile = args[++i];
100             } else if (arg.equals("-html")) {
101                 html = true;
102             } else if (arg.equals("-internal")) {
103                 internal = true;
104             }
105         }
106
107         new ReportAPI(oldFile, newFile, internal).writeReport(outFile, html, internal);
108     }
109
110     /*
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.
115
116       String oldA = null;
117       String oldR = null;
118       if (!a.isMethod()) {
119       remove and continue
120       }
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())
125
126     */
127
128     ReportAPI(String oldFile, String newFile, boolean internal) {
129         this(APIData.read(oldFile, internal), APIData.read(newFile, internal));
130     }
131
132     ReportAPI(APIData oldData, APIData newData) {
133         this.oldData = oldData;
134         this.newData = newData;
135
136         removed = (TreeSet<APIInfo>)oldData.set.clone();
137         removed.removeAll(newData.set);
138
139         added = (TreeSet<APIInfo>)newData.set.clone();
140         added.removeAll(oldData.set);
141
142         changed = new ArrayList<DeltaInfo>();
143         Iterator<APIInfo> ai = added.iterator();
144         Iterator<APIInfo> ri = removed.iterator();
145         Comparator<APIInfo> c = APIInfo.changedComparator();
146
147         ArrayList<APIInfo> ams = new ArrayList<APIInfo>();
148         ArrayList<APIInfo> rms = new ArrayList<APIInfo>();
149         //PrintWriter outpw = new PrintWriter(System.out);
150
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();
155
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;
162                 continue;
163             }
164
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)));
169                 } else {
170                     // dang, what to do now?
171                     // TODO: modify deltainfo to deal with lists of added and removed
172                 }
173                 ams.clear();
174                 rms.clear();
175             }
176
177             int result = c.compare(a, r);
178             if (result < 0) {
179                 a = null;
180             } else if (result > 0) {
181                 r = null;
182             } else {
183                 changed.add(new DeltaInfo(a, r));
184                 a = null;
185                 r = null;
186             }
187         }
188
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);
195         }
196
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);
202
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);
208
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();
214         a = r = null;
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);
219             if (result < 0) {
220                 a = null;
221             } else if (result > 0) {
222                 r = null;
223             } else {
224                 int change = statusChange(a, r);
225                 if (change > 0) {
226                     if (a.isStable()) {
227                         promotedStable.add(a);
228                     } else {
229                         promotedDraft.add(a);
230                     }
231                 } else if (change < 0) {
232                     obsoleted.add(a);
233                 }
234                 a = null;
235                 r = null;
236             }
237         }
238
239         added = stripAndResort(added);
240         removed = stripAndResort(removed);
241         promotedStable = stripAndResort(promotedStable);
242         promotedDraft = stripAndResort(promotedDraft);
243         obsoleted = stripAndResort(obsoleted);
244     }
245
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)) {
249                 return 0;
250             }
251         }
252         int lstatus = lhs.getVal(APIInfo.STA);
253         if (lstatus == APIInfo.STA_OBSOLETE
254             || lstatus == APIInfo.STA_DEPRECATED
255             || lstatus == APIInfo.STA_INTERNAL) {
256             return -1;
257         }
258         return 1;
259     }
260
261     private boolean writeReport(String outFile, boolean html, boolean internal) {
262         OutputStream os = System.out;
263         if (outFile != null) {
264             try {
265                 os = new FileOutputStream(outFile);
266             }
267             catch (FileNotFoundException e) {
268                 RuntimeException re = new RuntimeException(e.getMessage());
269                 re.initCause(e);
270                 throw re;
271             }
272         }
273
274         PrintWriter pw = null;
275         try {
276             pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(os, "UTF-8")));
277         }
278         catch (UnsupportedEncodingException e) {
279             throw new IllegalStateException(); // UTF-8 should always be supported
280         }
281
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.";
288
289         if (html) {
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>");
298
299             pw.println("<h1>" + title + "</h1>");
300
301             pw.println();
302             pw.println("<hr/>");
303             pw.println("<h2>Removed from " + oldData.name +"</h2>");
304             if (removed.size() > 0) {
305                 printResults(removed, pw, true, false);
306             } else {
307                 pw.println("<p>(no API removed)</p>");
308             }
309
310             pw.println();
311             pw.println("<hr/>");
312             if (internal) {
313                 pw.println("<h2>Withdrawn, Deprecated, or Obsoleted in " + newData.name + "</h2>");
314             } else {
315                 pw.println("<h2>Deprecated or Obsoleted in " + newData.name + "</h2>");
316             }
317             if (obsoleted.size() > 0) {
318                 printResults(obsoleted, pw, true, false);
319             } else {
320                 pw.println("<p>(no API obsoleted)</p>");
321             }
322
323             pw.println();
324             pw.println("<hr/>");
325             pw.println("<h2>Changed in " + newData.name + " (old, new)</h2>");
326             if (changed.size() > 0) {
327                 printResults(changed, pw, true, true);
328             } else {
329                 pw.println("<p>(no API changed)</p>");
330             }
331
332             pw.println();
333             pw.println("<hr/>");
334             pw.println("<h2>Promoted to stable in " + newData.name + "</h2>");
335             if (promotedStable.size() > 0) {
336                 printResults(promotedStable, pw, true, false);
337             } else {
338                 pw.println("<p>(no API promoted to stable)</p>");
339             }
340
341             if (internal) {
342                 // APIs promoted from internal to draft is reported only when
343                 // internal API check is enabled
344                 pw.println();
345                 pw.println("<hr/>");
346                 pw.println("<h2>Promoted to draft in " + newData.name + "</h2>");
347                 if (promotedDraft.size() > 0) {
348                     printResults(promotedDraft, pw, true, false);
349                 } else {
350                     pw.println("<p>(no API promoted to draft)</p>");
351                 }
352             }
353
354             pw.println();
355             pw.println("<hr/>");
356             pw.println("<h2>Added in " + newData.name + "</h2>");
357             if (added.size() > 0) {
358                 printResults(added, pw, true, false);
359             } else {
360                 pw.println("<p>(no API added)</p>");
361             }
362
363             pw.println("<hr/>");
364             pw.println("<p><i><font size=\"-1\">" + info + "<br/>" + copyright + "</font></i></p>");
365             pw.println("</body>");
366             pw.println("</html>");
367         } else {
368             pw.println(title);
369             pw.println();
370             pw.println();
371
372             pw.println("=== Removed from " + oldData.name + " ===");
373             if (removed.size() > 0) {
374                 printResults(removed, pw, false, false);
375             } else {
376                 pw.println("(no API removed)");
377             }
378
379             pw.println();
380             pw.println();
381             if (internal) {
382                 pw.println("=== Withdrawn, Deprecated, or Obsoleted in " + newData.name + " ===");
383             } else {
384                 pw.println("=== Deprecated or Obsoleted in " + newData.name + " ===");
385             }
386             if (obsoleted.size() > 0) {
387                 printResults(obsoleted, pw, false, false);
388             } else {
389                 pw.println("(no API obsoleted)");
390             }
391
392             pw.println();
393             pw.println();
394             pw.println("=== Changed in " + newData.name + " (old, new) ===");
395             if (changed.size() > 0) {
396                 printResults(changed, pw, false, true);
397             } else {
398                 pw.println("(no API changed)");
399             }
400
401             pw.println();
402             pw.println();
403             pw.println("=== Promoted to stable in " + newData.name + " ===");
404             if (promotedStable.size() > 0) {
405                 printResults(promotedStable, pw, false, false);
406             } else {
407                 pw.println("(no API promoted to stable)");
408             }
409
410             if (internal) {
411                 pw.println();
412                 pw.println();
413                 pw.println("=== Promoted to draft in " + newData.name + " ===");
414                 if (promotedDraft.size() > 0) {
415                     printResults(promotedDraft, pw, false, false);
416                 } else {
417                     pw.println("(no API promoted to draft)");
418                 }
419             }
420
421             pw.println();
422             pw.println();
423             pw.println("=== Added in " + newData.name + " ===");
424             if (added.size() > 0) {
425                 printResults(added, pw, false, false);
426             } else {
427                 pw.println("(no API added)");
428             }
429
430             pw.println();
431             pw.println("================");
432             pw.println(info);
433             pw.println(copyright);
434         }
435         pw.close();
436
437         return false;
438     }
439
440     private static void printResults(Collection<? extends APIInfo> c, PrintWriter pw, boolean html,
441                                      boolean isChangedAPIs) {
442         Iterator<? extends APIInfo> iter = c.iterator();
443         String pack = null;
444         String clas = null;
445         while (iter.hasNext()) {
446             APIInfo info = iter.next();
447
448             String packageName = info.getPackageName();
449             if (!packageName.equals(pack)) {
450                 if (html) {
451                     if (clas != null) {
452                         pw.println("</ul>");
453                     }
454                     if (pack != null) {
455                         pw.println("</ul>");
456                     }
457                     pw.println();
458                     pw.println("<h3>Package " + packageName + "</h3>");
459                     pw.print("<ul>");
460                 } else {
461                     if (pack != null) {
462                         pw.println();
463                     }
464                     pw.println();
465                     pw.println("Package " + packageName + ":");
466                 }
467                 pw.println();
468
469                 pack = packageName;
470                 clas = null;
471             }
472
473             if (!info.isClass()) {
474                 String className = info.getClassName();
475                 if (!className.equals(clas)) {
476                     if (html) {
477                         if (clas != null) {
478                             pw.println("</ul>");
479                         }
480                         pw.println(className);
481                         pw.println("<ul>");
482                     } else {
483                         pw.println(className);
484                     }
485                     clas = className;
486                 }
487             }
488
489             if (html) {
490                 pw.print("<li>");
491                 info.print(pw, isChangedAPIs, html);
492                 pw.println("</li>");
493             } else {
494                 info.println(pw, isChangedAPIs, html);
495             }
496         }
497
498         if (html) {
499             if (clas != null) {
500                 pw.println("</ul>");
501             }
502             if (pack != null) {
503                 pw.println("</ul>");
504             }
505         }
506         pw.println();
507     }
508
509     private static TreeSet<APIInfo> stripAndResort(TreeSet<APIInfo> t) {
510         stripClassInfo(t);
511         TreeSet<APIInfo> r = new TreeSet<APIInfo>(APIInfo.classFirstComparator());
512         r.addAll(t);
513         return r;
514     }
515
516     private static void stripClassInfo(Collection<APIInfo> c) {
517         // c is sorted with class info first
518         Iterator<? extends APIInfo> iter = c.iterator();
519         String cname = null;
520         while (iter.hasNext()) {
521             APIInfo info = iter.next();
522             String className = info.getClassName();
523             if (cname != null) {
524                 if (cname.equals(className)) {
525                     iter.remove();
526                     continue;
527                 }
528                 cname = null;
529             }
530             if (info.isClass()) {
531                 cname = info.getName();
532             }
533         }
534     }
535 }