]> gitweb.fperrin.net Git - Dictionary.git/blob - jars/icu4j-4_4_2-src/tools/build/src/com/ibm/icu/dev/tool/docs/ReportAPI.java
go
[Dictionary.git] / jars / icu4j-4_4_2-src / tools / build / src / com / ibm / icu / dev / tool / docs / ReportAPI.java
1 /**\r
2 *******************************************************************************\r
3 * Copyright (C) 2004-2010, International Business Machines Corporation and    *\r
4 * others. All Rights Reserved.                                                *\r
5 *******************************************************************************\r
6 */\r
7 \r
8 /**\r
9  * Compare two API files (generated by GatherAPIData) and generate a report\r
10  * on the differences.\r
11  *\r
12  * Sample invocation:\r
13  * java -old: icu4j28.api.zip -new: icu4j30.api -html -out: icu4j_compare_28_30.html\r
14  *\r
15  * TODO:\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
20  *     change.\r
21  */\r
22 \r
23 package com.ibm.icu.dev.tool.docs;\r
24 \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
42 \r
43 public class ReportAPI {\r
44     APIData oldData;\r
45     APIData newData;\r
46     boolean html;\r
47     String outputFile;\r
48 \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
54 \r
55     static final class DeltaInfo extends APIInfo {\r
56         APIInfo added;\r
57         APIInfo removed;\r
58 \r
59         DeltaInfo(APIInfo added, APIInfo removed) {\r
60             this.added = added;\r
61             this.removed = removed;\r
62         }\r
63 \r
64         public int getVal(int typ) {\r
65             return added.getVal(typ);\r
66         }\r
67 \r
68         public String get(int typ, boolean brief) {\r
69             return added.get(typ, brief);\r
70         }\r
71 \r
72         public void print(PrintWriter pw, boolean detail, boolean html) {\r
73             pw.print("    ");\r
74             removed.print(pw, detail, html);\r
75             if (html) {\r
76                 pw.println("</br>");\r
77             } else {\r
78                 pw.println();\r
79                 pw.print("--> ");\r
80             }\r
81             added.print(pw, detail, html);\r
82         }\r
83     }\r
84 \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
100                 html = true;\r
101             } else if (arg.equals("-internal")) {\r
102                 internal = true;\r
103             }\r
104         }\r
105 \r
106         new ReportAPI(oldFile, newFile, internal).writeReport(outFile, html, internal);\r
107     }\r
108 \r
109     /*\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
114 \r
115       String oldA = null;\r
116       String oldR = null;\r
117       if (!a.isMethod()) {\r
118       remove and continue\r
119       }\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
124 \r
125     */\r
126 \r
127     ReportAPI(String oldFile, String newFile, boolean internal) {\r
128         this(APIData.read(oldFile, internal), APIData.read(newFile, internal));\r
129     }\r
130 \r
131     ReportAPI(APIData oldData, APIData newData) {\r
132         this.oldData = oldData;\r
133         this.newData = newData;\r
134 \r
135         removed = (TreeSet<APIInfo>)oldData.set.clone();\r
136         removed.removeAll(newData.set);\r
137 \r
138         added = (TreeSet<APIInfo>)newData.set.clone();\r
139         added.removeAll(oldData.set);\r
140 \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
145 \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
149 \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
154 \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
161                 continue;\r
162             }\r
163 \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
168                 } else {\r
169                     // dang, what to do now?\r
170                     // TODO: modify deltainfo to deal with lists of added and removed\r
171                 }\r
172                 ams.clear();\r
173                 rms.clear();\r
174             }\r
175 \r
176             int result = c.compare(a, r);\r
177             if (result < 0) {\r
178                 a = null;\r
179             } else if (result > 0) {\r
180                 r = null;\r
181             } else {\r
182                 changed.add(new DeltaInfo(a, r));\r
183                 a = null;\r
184                 r = null;\r
185             }\r
186         }\r
187 \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
194         }\r
195 \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
201 \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
207 \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
212         a = r = null;\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
217             if (result < 0) {\r
218                 a = null;\r
219             } else if (result > 0) {\r
220                 r = null;\r
221             } else {\r
222                 int change = statusChange(a, r);\r
223                 if (change > 0) {\r
224                     promoted.add(a);\r
225                 } else if (change < 0) {\r
226                     obsoleted.add(a);\r
227                 }\r
228                 a = null;\r
229                 r = null;\r
230             }\r
231         }\r
232 \r
233         added = stripAndResort(added);\r
234         removed = stripAndResort(removed);\r
235         promoted = stripAndResort(promoted);\r
236         obsoleted = stripAndResort(obsoleted);\r
237     }\r
238 \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
242                 return 0;\r
243             }\r
244         }\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
249             return -1;\r
250         }\r
251         return 1;\r
252     }\r
253 \r
254     private boolean writeReport(String outFile, boolean html, boolean internal) {\r
255         OutputStream os = System.out;\r
256         if (outFile != null) {\r
257             try {\r
258                 os = new FileOutputStream(outFile);\r
259             }\r
260             catch (FileNotFoundException e) {\r
261                 RuntimeException re = new RuntimeException(e.getMessage());\r
262                 re.initCause(e);\r
263                 throw re;\r
264             }\r
265         }\r
266 \r
267         PrintWriter pw = null;\r
268         try {\r
269             pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(os, "UTF-8")));\r
270         }\r
271         catch (UnsupportedEncodingException e) {\r
272             throw new IllegalStateException(); // UTF-8 should always be supported\r
273         }\r
274 \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
281 \r
282         if (html) {\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
291 \r
292             pw.println("<h1>" + title + "</h1>");\r
293 \r
294             pw.println();\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
299             } else {\r
300                 pw.println("<p>(no API removed)</p>");\r
301             }\r
302 \r
303             pw.println();\r
304             pw.println("<hr/>");\r
305             if (internal) {\r
306                 pw.println("<h2>Withdrawn, Deprecated, or Obsoleted in " + newData.name + "</h2>");\r
307             } else {\r
308                 pw.println("<h2>Deprecated or Obsoleted in " + newData.name + "</h2>");\r
309             }\r
310             if (obsoleted.size() > 0) {\r
311                 printResults(obsoleted, pw, true, false);\r
312             } else {\r
313                 pw.println("<p>(no API obsoleted)</p>");\r
314             }\r
315 \r
316             pw.println();\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
321             } else {\r
322                 pw.println("<p>(no API changed)</p>");\r
323             }\r
324 \r
325             pw.println();\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
330             } else {\r
331                 pw.println("<p>(no API promoted)</p>");\r
332             }\r
333 \r
334             pw.println();\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
339             } else {\r
340                 pw.println("<p>(no API added)</p>");\r
341             }\r
342 \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
347         } else {\r
348             pw.println(title);\r
349             pw.println();\r
350             pw.println();\r
351 \r
352             pw.println("=== Removed from " + oldData.name + " ===");\r
353             if (removed.size() > 0) {\r
354                 printResults(removed, pw, false, false);\r
355             } else {\r
356                 pw.println("(no API removed)");\r
357             }\r
358 \r
359             pw.println();\r
360             pw.println();\r
361             if (internal) {\r
362                 pw.println("=== Withdrawn, Deprecated, or Obsoleted in " + newData.name + " ===");\r
363             } else {\r
364                 pw.println("=== Deprecated or Obsoleted in " + newData.name + " ===");\r
365             }\r
366             if (obsoleted.size() > 0) {\r
367                 printResults(obsoleted, pw, false, false);\r
368             } else {\r
369                 pw.println("(no API obsoleted)");\r
370             }\r
371 \r
372             pw.println();\r
373             pw.println();\r
374             pw.println("=== Changed in " + newData.name + " (old, new) ===");\r
375             if (changed.size() > 0) {\r
376                 printResults(changed, pw, false, true);\r
377             } else {\r
378                 pw.println("(no API changed)");\r
379             }\r
380 \r
381             pw.println();\r
382             pw.println();\r
383             pw.println("=== Promoted to stable in " + newData.name + " ===");\r
384             if (promoted.size() > 0) {\r
385                 printResults(promoted, pw, false, false);\r
386             } else {\r
387                 pw.println("(no API promoted)");\r
388             }\r
389 \r
390             pw.println();\r
391             pw.println();\r
392             pw.println("=== Added in " + newData.name + " ===");\r
393             if (added.size() > 0) {\r
394                 printResults(added, pw, false, false);\r
395             } else {\r
396                 pw.println("(no API added)");\r
397             }\r
398 \r
399             pw.println();\r
400             pw.println("================");\r
401             pw.println(info);\r
402             pw.println(copyright);\r
403         }\r
404         pw.close();\r
405 \r
406         return false;\r
407     }\r
408 \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
416 \r
417             String packageName = info.getPackageName();\r
418             if (!packageName.equals(pack)) {\r
419                 if (html) {\r
420                     if (clas != null) {\r
421                         pw.println("</ul>");\r
422                     }\r
423                     if (pack != null) {\r
424                         pw.println("</ul>");\r
425                     }\r
426                     pw.println();\r
427                     pw.println("<h3>Package " + packageName + "</h3>");\r
428                     pw.print("<ul>");\r
429                 } else {\r
430                     if (pack != null) {\r
431                         pw.println();\r
432                     }\r
433                     pw.println();\r
434                     pw.println("Package " + packageName + ":");\r
435                 }\r
436                 pw.println();\r
437 \r
438                 pack = packageName;\r
439                 clas = null;\r
440             }\r
441 \r
442             if (!info.isClass()) {\r
443                 String className = info.getClassName();\r
444                 if (!className.equals(clas)) {\r
445                     if (html) {\r
446                         if (clas != null) {\r
447                             pw.println("</ul>");\r
448                         }\r
449                         pw.println(className);\r
450                         pw.println("<ul>");\r
451                     } else {\r
452                         pw.println(className);\r
453                     }\r
454                     clas = className;\r
455                 }\r
456             }\r
457 \r
458             if (html) {\r
459                 pw.print("<li>");\r
460                 info.print(pw, isChangedAPIs, html);\r
461                 pw.println("</li>");\r
462             } else {\r
463                 info.println(pw, isChangedAPIs, html);\r
464             }\r
465         }\r
466 \r
467         if (html) {\r
468             if (clas != null) {\r
469                 pw.println("</ul>");\r
470             }\r
471             if (pack != null) {\r
472                 pw.println("</ul>");\r
473             }\r
474         }\r
475         pw.println();\r
476     }\r
477 \r
478     private static TreeSet<APIInfo> stripAndResort(TreeSet<APIInfo> t) {\r
479         stripClassInfo(t);\r
480         TreeSet<APIInfo> r = new TreeSet<APIInfo>(APIInfo.classFirstComparator());\r
481         r.addAll(t);\r
482         return r;\r
483     }\r
484 \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
494                     iter.remove();\r
495                     continue;\r
496                 }\r
497                 cname = null;\r
498             }\r
499             if (info.isClass()) {\r
500                 cname = info.getName();\r
501             }\r
502         }\r
503     }\r
504 }\r