2 *******************************************************************************
\r
3 * Copyright (C) 2005-2007, International Business Machines Corporation and *
\r
4 * others. All Rights Reserved. *
\r
5 *******************************************************************************
\r
9 package com.ibm.icu.dev.tool.docs;
\r
12 import java.lang.reflect.*;
\r
16 * Compare ICU4J and JDK APIS.
\r
18 * TODO: compare protected APIs. Reflection on Class allows you
\r
19 * to either get all inherited methods with public access, or get methods
\r
20 * on the particular class with any access, but no way to get all
\r
21 * inherited methods with any access. Go figure.
\r
23 public class ICUJDKCompare {
\r
24 static final boolean DEBUG = false;
\r
27 private static final String kSrcPrefix = "java.";
\r
28 private static final String kTrgPrefix = "com.ibm.icu.";
\r
29 private static final String[] kPairInfo = {
\r
30 "lang.Character/UCharacter",
\r
31 "lang.Character$UnicodeBlock/UCharacter$UnicodeBlock",
\r
32 "text.BreakIterator",
\r
35 "text.DateFormatSymbols",
\r
36 "text.DecimalFormat",
\r
37 "text.DecimalFormatSymbols",
\r
38 "text.Format/UFormat",
\r
39 "text.MessageFormat",
\r
40 "text.NumberFormat",
\r
41 "text.SimpleDateFormat",
\r
44 "util.GregorianCalendar",
\r
45 "util.SimpleTimeZone",
\r
47 "util.Locale/ULocale",
\r
48 "util.ResourceBundle/UResourceBundle",
\r
51 private static final String[] kIgnore = new String[] {
\r
52 "lang.Character <init> charValue compareTo MAX_VALUE MIN_VALUE TYPE",
\r
53 "lang.Character$UnicodeBlock SURROGATES_AREA",
\r
54 "util.Calendar FIELD_COUNT",
\r
55 "util.GregorianCalendar FIELD_COUNT",
\r
56 "util.SimpleTimeZone STANDARD_TIME UTC_TIME WALL_TIME",
\r
59 private PrintWriter pw;
\r
60 private String srcPrefix;
\r
61 private String trgPrefix;
\r
62 private Class[] classPairs;
\r
63 private String[] namePairs;
\r
64 private String[] ignore;
\r
65 private boolean swap;
\r
66 //private boolean signature;
\r
68 // call System.exit with non-zero if there were some missing APIs
\r
69 public static void main(String[] args) {
\r
70 System.exit(doMain(args));
\r
73 // return non-zero if there were some missing APIs
\r
74 public static int doMain(String[] args) {
\r
75 ICUJDKCompare p = new ICUJDKCompare();
\r
76 p.setOutputWriter(new PrintWriter(System.out));
\r
82 public ICUJDKCompare setOutputWriter(PrintWriter pw) {
\r
87 public ICUJDKCompare setSrcPrefix(String srcPrefix) {
\r
88 this.srcPrefix = srcPrefix;
\r
92 public ICUJDKCompare setTrgPrefix(String trgPrefix) {
\r
93 this.trgPrefix = trgPrefix;
\r
97 public ICUJDKCompare setClassPairs(Class[] classPairs) {
\r
98 this.classPairs = classPairs;
\r
102 public ICUJDKCompare setNamePairs(String[] namePairs) {
\r
103 this.namePairs = namePairs;
\r
107 public ICUJDKCompare setIgnore(String[] ignore) {
\r
108 this.ignore = ignore;
\r
112 public ICUJDKCompare setSwap(boolean swap) {
\r
117 public ICUJDKCompare setup(String[] args) {
\r
118 String namelist = null;
\r
119 String ignorelist = null;
\r
120 for (int i = 0; i < args.length; ++i) {
\r
121 String arg = args[i];
\r
122 if (arg.equals("-swap")) {
\r
124 } else if (arg.equals("-srcPrefix:")) {
\r
125 srcPrefix = args[++i];
\r
126 if (!srcPrefix.endsWith(".")) {
\r
129 } else if (arg.equals("-trgPrefix:")) {
\r
130 trgPrefix = args[++i];
\r
131 if (!trgPrefix.endsWith(".")) {
\r
134 } else if (arg.equals("-names:")) {
\r
135 namelist = args[++i];
\r
136 } else if (arg.equals("-ignore:")) {
\r
137 ignorelist = args[++i];
\r
139 System.err.println("unrecognized argument: " + arg);
\r
140 throw new IllegalStateException();
\r
144 if (ignorelist != null) {
\r
145 if (ignorelist.charAt(0) == '@') { // a file containing ignoreinfo
\r
147 ArrayList nl = new ArrayList();
\r
148 File f = new File(namelist.substring(1));
\r
149 FileInputStream fis = new FileInputStream(f);
\r
150 InputStreamReader isr = new InputStreamReader(fis);
\r
151 BufferedReader br = new BufferedReader(isr);
\r
152 String line = null;
\r
153 while (null != (line = br.readLine())) {
\r
156 ignore = (String[])nl.toArray(new String[nl.size()]);
\r
158 catch (Exception e) {
\r
159 System.err.println(e);
\r
160 throw new IllegalStateException();
\r
162 } else { // a list of ignoreinfo separated by semicolons
\r
163 ignore = ignorelist.split("\\s*;\\s*");
\r
167 if (namelist != null) {
\r
168 String[] names = null;
\r
169 if (namelist.charAt(0) == '@') { // a file
\r
171 ArrayList nl = new ArrayList();
\r
172 File f = new File(namelist.substring(1));
\r
173 FileInputStream fis = new FileInputStream(f);
\r
174 InputStreamReader isr = new InputStreamReader(fis);
\r
175 BufferedReader br = new BufferedReader(isr);
\r
176 String line = null;
\r
177 while (null != (line = br.readLine())) {
\r
180 names = (String[])nl.toArray(new String[nl.size()]);
\r
182 catch (Exception e) {
\r
183 System.err.println(e);
\r
184 throw new IllegalStateException();
\r
186 } else { // a list of names separated by semicolons
\r
187 names = namelist.split("\\s*;\\s*");
\r
190 processPairInfo(names);
\r
198 private void processPairInfo(String[] names) {
\r
199 ArrayList cl = new ArrayList();
\r
200 ArrayList nl = new ArrayList();
\r
201 for (int i = 0; i < names.length; ++i) {
\r
202 String name = names[i];
\r
203 String srcName = srcPrefix;
\r
204 String trgName = trgPrefix;
\r
206 int n = name.indexOf('/');
\r
211 String srcSuffix = name.substring(0, n).trim();
\r
212 String trgSuffix = name.substring(n+1).trim();
\r
213 int jx = srcSuffix.length()+1;
\r
214 int ix = trgSuffix.length()+1;
\r
216 jx = srcSuffix.lastIndexOf('.', jx-1);
\r
217 ix = trgSuffix.lastIndexOf('.', ix-1);
\r
219 srcName += srcSuffix;
\r
220 trgName += srcSuffix.substring(0, jx+1) + trgSuffix;
\r
224 Class jc = Class.forName(srcName);
\r
225 Class ic = Class.forName(trgName);
\r
228 nl.add(ic.getName());
\r
229 nl.add(jc.getName());
\r
231 catch (Exception e) {
\r
232 if (DEBUG) System.err.println("can't load class: " + e.getMessage());
\r
235 classPairs = (Class[])cl.toArray(new Class[cl.size()]);
\r
236 namePairs = (String[])nl.toArray(new String[nl.size()]);
\r
239 private void println(String s) {
\r
240 if (pw != null) pw.println(s);
\r
243 private void flush() {
\r
244 if (pw != null) pw.flush();
\r
247 public int process() {
\r
249 if (srcPrefix == null) {
\r
250 srcPrefix = kSrcPrefix;
\r
253 if (trgPrefix == null) {
\r
254 trgPrefix = kTrgPrefix;
\r
257 if (classPairs == null) {
\r
258 processPairInfo(kPairInfo);
\r
261 if (ignore == null) {
\r
265 println("ICU and Java API Comparison");
\r
266 String ICU_VERSION = "unknown";
\r
268 Class cls = Class.forName("com.ibm.icu.util.VersionInfo");
\r
269 Field fld = cls.getField("ICU_VERSION");
\r
270 ICU_VERSION = fld.get(null).toString();
\r
272 catch (Exception e) {
\r
273 if (DEBUG) System.err.println("can't get VersionInfo: " + e.getMessage());
\r
275 println("ICU Version " + ICU_VERSION);
\r
276 println("JDK Version " + System.getProperty("java.version"));
\r
278 int errorCount = 0;
\r
279 for (int i = 0; i < classPairs.length; i += 2) {
\r
282 errorCount += compare(classPairs[i+1], classPairs[i]);
\r
284 errorCount += compare(classPairs[i], classPairs[i+1]);
\r
287 catch (Exception e) {
\r
288 System.err.println("exception: " + e);
\r
289 System.err.println("between " + namePairs[i] + " and " + namePairs[i+1]);
\r
290 e.printStackTrace();
\r
297 static class MorC {
\r
298 private Method mref;
\r
299 private Constructor cref;
\r
305 MorC(Constructor c) {
\r
309 int getModifiers() {
\r
310 return mref == null ? cref.getModifiers() : mref.getModifiers();
\r
313 Class getReturnType() {
\r
314 return mref == null ? void.class : mref.getReturnType();
\r
317 Class[] getParameterTypes() {
\r
318 return mref == null ? cref.getParameterTypes() : mref.getParameterTypes();
\r
322 return mref == null ? "<init>" : mref.getName();
\r
325 String getSignature() {
\r
326 return mref == null ? cref.toString() : mref.toString();
\r
330 private int compare(Class class1, Class class2) throws Exception {
\r
331 String n1 = class1.getName();
\r
332 String n2 = class2.getName();
\r
334 println("\ncompare " + n1 + " <> " + n2);
\r
336 MorC[] conss1 = getMorCArray(class1.getConstructors());
\r
337 MorC[] conss2 = getMorCArray(class2.getConstructors());
\r
339 Map cmap1 = getMethodMap(conss1);
\r
340 Map cmap2 = getMethodMap(conss2);
\r
342 MorC[] meths1 = getMorCArray(class1.getMethods());
\r
343 MorC[] meths2 = getMorCArray(class2.getMethods());
\r
345 Map map1 = getMethodMap(meths1);
\r
346 Map map2 = getMethodMap(meths2);
\r
348 Field[] fields1 = class1.getFields();
\r
349 Field[] fields2 = class2.getFields();
\r
351 Set set1 = getFieldSet(fields1);
\r
352 Set set2 = getFieldSet(fields2);
\r
354 Map diffConss = diffMethodMaps(cmap2, cmap1);
\r
355 Map diffMeths = diffMethodMaps(map2, map1);
\r
356 Set diffFields = diffFieldSets(set2, set1);
\r
358 diffConss = removeIgnored(n2, diffConss);
\r
359 diffMeths = removeIgnored(n2, diffMeths);
\r
360 diffFields = removeIgnored(n2, diffFields);
\r
362 int result = diffConss.size() + diffMeths.size() + diffFields.size();
\r
363 if (result > 0 && pw != null) {
\r
364 pw.println("Public API in " + n2 + " but not in " + n1);
\r
365 if (diffConss.size() > 0) {
\r
366 pw.println("CONSTRUCTORS");
\r
367 dumpMethodMap(diffConss, pw);
\r
369 if (diffMeths.size() > 0) {
\r
370 pw.println("METHODS");
\r
371 dumpMethodMap(diffMeths, pw);
\r
373 if (diffFields.size() > 0) {
\r
374 pw.println("FIELDS");
\r
375 dumpFieldSet(diffFields, pw);
\r
384 final class MethodRecord {
\r
387 MethodRecord(MorC m) {
\r
388 overrides = new MorC[] { m };
\r
391 MethodRecord(MorC[] ms) {
\r
395 MethodRecord copy() {
\r
396 return new MethodRecord((MorC[])overrides.clone());
\r
400 for (int i = 0; i < overrides.length; ++i) {
\r
401 if (overrides[i] == null) {
\r
405 return overrides.length;
\r
409 MorC[] temp = new MorC[overrides.length + 1];
\r
410 for (int i = 0; i < overrides.length; ++i) {
\r
411 temp[i] = overrides[i];
\r
413 temp[overrides.length] = m;
\r
417 void remove(int index) {
\r
419 while (overrides[i] != null && i < overrides.length-1) {
\r
420 overrides[i] = overrides[i+1];
\r
423 overrides[i] = null;
\r
426 // if a call to a method can be handled by a call to t, remove the
\r
427 // method from our list, and return true
\r
428 boolean removeOverridden(MorC t) {
\r
429 boolean result = false;
\r
431 while (i < overrides.length) {
\r
432 MorC m = overrides[i];
\r
436 if (handles(t, m)) {
\r
446 // remove all methods handled by any method of mr
\r
447 boolean removeOverridden(MethodRecord mr) {
\r
448 boolean result = false;
\r
449 for (int i = 0; i < mr.overrides.length; ++i) {
\r
450 MorC t = mr.overrides[i];
\r
452 // this shouldn't happen, as the target record should not have been modified
\r
453 throw new IllegalStateException();
\r
455 if (removeOverridden(t)) {
\r
462 void debugmsg(MorC t, MorC m, String msg) {
\r
463 StringBuffer buf = new StringBuffer();
\r
464 buf.append(t.getName());
\r
471 System.out.println(buf.toString());
\r
474 boolean handles(MorC t, MorC m) {
\r
475 // relevant modifiers must match
\r
476 if ((t.getModifiers() & MOD_MASK) != (m.getModifiers() & MOD_MASK)) {
\r
477 if (DEBUG) debugmsg(t, m, "modifier mismatch");
\r
481 Class tr = pairClassEquivalent(t.getReturnType());
\r
482 Class mr = pairClassEquivalent(m.getReturnType());
\r
483 if (!assignableFrom(mr, tr)) { // t return type must be same or narrower than m
\r
484 if (DEBUG) debugmsg(t, m, "return value mismatch");
\r
487 Class[] tts = t.getParameterTypes();
\r
488 Class[] mts = m.getParameterTypes();
\r
489 if (tts.length != mts.length) {
\r
490 if (DEBUG) debugmsg(t, m, "param count mismatch");
\r
494 for (int i = 0; i < tts.length; ++i) {
\r
495 Class tc = pairClassEquivalent(tts[i]);
\r
496 Class mc = pairClassEquivalent(mts[i]);
\r
497 if (!assignableFrom(tc, mc)) { // m param must be same or narrower than t
\r
498 if (DEBUG) debugmsg(t, m, "parameter " + i + " mismatch, " +
\r
499 tts[i].getName() + " not assignable from " + mts[i].getName());
\r
506 public void toString(MorC m, StringBuffer buf) {
\r
507 int mod = m.getModifiers();
\r
509 buf.append(Modifier.toString(mod) + " ");
\r
511 buf.append(nameOf(m.getReturnType()));
\r
513 buf.append(m.getName());
\r
515 Class[] ptypes = m.getParameterTypes();
\r
516 for (int j = 0; j < ptypes.length; ++j) {
\r
520 buf.append(nameOf(ptypes[j]));
\r
525 public String toString() {
\r
526 StringBuffer buf = new StringBuffer();
\r
527 buf.append(overrides[0].getName());
\r
528 for (int i = 0; i < overrides.length; ++i) {
\r
529 MorC m = overrides[i];
\r
536 return buf.toString();
\r
540 public static String nameOf(Class c) {
\r
542 return nameOf(c.getComponentType()) + "[]";
\r
544 String name = c.getName();
\r
545 return name.substring(name.lastIndexOf('.') + 1);
\r
548 static MorC[] getMorCArray(Constructor[] cons) {
\r
549 MorC[] result = new MorC[cons.length];
\r
550 for (int i = 0 ; i < cons.length; ++i) {
\r
551 result[i] = new MorC(cons[i]);
\r
556 static MorC[] getMorCArray(Method[] meths) {
\r
557 MorC[] result = new MorC[meths.length];
\r
558 for (int i = 0 ; i < meths.length; ++i) {
\r
559 result[i] = new MorC(meths[i]);
\r
564 private Map getMethodMap(MorC[] meths) {
\r
565 Map result = new TreeMap();
\r
566 for (int i = 0; i < meths.length; ++i) {
\r
568 String key = m.getName();
\r
569 MethodRecord mr = (MethodRecord)result.get(key);
\r
571 mr = new MethodRecord(m);
\r
572 result.put(key, mr);
\r
580 private void dumpMethodMap(Map m, PrintWriter pw) {
\r
581 Iterator iter = m.entrySet().iterator();
\r
582 while (iter.hasNext()) {
\r
583 dumpMethodRecord((MethodRecord)((Map.Entry)iter.next()).getValue());
\r
588 private void dumpMethodRecord(MethodRecord mr) {
\r
589 pw.println(mr.toString());
\r
592 static Map diffMethodMaps(Map m1, Map m2) {
\r
593 // get all the methods in m1 that aren't mentioned in m2 at all
\r
594 Map result = (Map)((TreeMap)m1).clone();
\r
595 result.keySet().removeAll(m2.keySet());
\r
599 private Map removeIgnored(String name, Map m1) {
\r
600 if (ignore == null) {
\r
603 if (name.startsWith(srcPrefix)) {
\r
604 name = name.substring(srcPrefix.length());
\r
606 name += " "; // to avoid accidental prefix of nested class name
\r
608 // prune ignore list to relevant items
\r
609 ArrayList il = null;
\r
610 for (int i = 0; i < ignore.length; ++i) {
\r
611 String s = ignore[i];
\r
612 if (s.startsWith(name)) {
\r
614 il = new ArrayList();
\r
623 Map result = new TreeMap(((TreeMap)m1).comparator());
\r
625 Iterator iter = result.entrySet().iterator();
\r
626 loop: while (iter.hasNext()) {
\r
627 Map.Entry e = (Map.Entry)iter.next();
\r
628 String key = (String)e.getKey();
\r
629 for (int i = 0; i < il.size(); ++i) {
\r
630 String ig = (String)il.get(i);
\r
631 if (ig.indexOf(" " + key) != 0) {
\r
640 private Set removeIgnored(String name, Set s1) {
\r
641 if (ignore == null) {
\r
644 if (name.startsWith(srcPrefix)) {
\r
645 name = name.substring(srcPrefix.length());
\r
647 name += " "; // to avoid accidental prefix of nested class name
\r
649 // prune ignore list to relevant items
\r
650 ArrayList il = null;
\r
651 for (int i = 0; i < ignore.length; ++i) {
\r
652 String s = ignore[i];
\r
653 if (s.startsWith(name)) {
\r
655 il = new ArrayList();
\r
664 Set result = (Set)((TreeSet)s1).clone();
\r
665 Iterator iter = result.iterator();
\r
666 loop: while (iter.hasNext()) {
\r
667 String key = (String)iter.next();
\r
668 String fieldname = key.substring(0, key.indexOf(' '));
\r
669 for (int i = 0; i < il.size(); ++i) {
\r
670 String ig = (String)il.get(i);
\r
671 if (ig.indexOf(" " + fieldname) != 0) {
\r
680 static final boolean[][] assignmentMap = {
\r
681 // bool char byte short int long float double void
\r
682 { true, false, false, false, false, false, false, false, false }, // boolean
\r
683 { false, true, true, true, false, false, false, false, false }, // char
\r
684 { false, false, true, false, false, false, false, false, false }, // byte
\r
685 { false, false, true, true, false, false, false, false, false }, // short
\r
686 { false, true, true, true, true, false, false, false, false }, // int
\r
687 { false, true, true, true, true, true, false, false, false }, // long
\r
688 { false, true, true, true, true, false, true, false, false }, // float
\r
689 { false, true, true, true, true, false, true, true, false }, // double
\r
690 { false, false, false, false, false, false, false, false, true }, // void
\r
693 static final Class[] prims = {
\r
694 boolean.class, char.class, byte.class, short.class,
\r
695 int.class, long.class, float.class, double.class, void.class
\r
698 static int primIndex(Class cls) {
\r
699 for (int i = 0; i < prims.length; ++i) {
\r
700 if (cls == prims[i]) {
\r
704 throw new IllegalStateException("could not find primitive class: " + cls);
\r
707 static boolean assignableFrom(Class lhs, Class rhs) {
\r
711 if (lhs.isPrimitive()) {
\r
712 if (!rhs.isPrimitive()) {
\r
715 int lhsx = primIndex(lhs);
\r
716 int rhsx = primIndex(rhs);
\r
717 return assignmentMap[lhsx][rhsx];
\r
719 return lhs.isAssignableFrom(rhs);
\r
722 private String toString(Field f) {
\r
723 StringBuffer buf = new StringBuffer(f.getName());
\r
724 int mod = f.getModifiers() & MOD_MASK;
\r
726 buf.append(" " + Modifier.toString(mod));
\r
729 String n = pairEquivalent(f.getType().getName());
\r
730 n = n.substring(n.lastIndexOf('.') + 1);
\r
732 return buf.toString();
\r
735 private Set getFieldSet(Field[] fs) {
\r
736 Set set = new TreeSet();
\r
737 for (int i = 0; i < fs.length; ++i) {
\r
738 set.add(toString(fs[i]));
\r
743 static Set diffFieldSets(Set s1, Set s2) {
\r
744 Set result = (Set)((TreeSet)s1).clone();
\r
745 result.removeAll(s2);
\r
749 private void dumpFieldSet(Set s, PrintWriter pw) {
\r
750 Iterator iter = s.iterator();
\r
751 while (iter.hasNext()) {
\r
752 pw.println(iter.next());
\r
757 // given a target string, if it matches the first of one of our pairs, return the second
\r
758 // or vice-versa if swap is true
\r
759 private String pairEquivalent(String target) {
\r
760 for (int i = 0; i < namePairs.length; i += 2) {
\r
762 if (target.equals(namePairs[i+1])) {
\r
763 return namePairs[i];
\r
766 if (target.equals(namePairs[i])) {
\r
767 return namePairs[i+1];
\r
774 private Class pairClassEquivalent(Class target) {
\r
775 for (int i = 0; i < classPairs.length; i += 2) {
\r
776 if (target.equals(classPairs[i])) {
\r
777 return classPairs[i+1];
\r
783 static final int MOD_MASK = ~(Modifier.FINAL|Modifier.SYNCHRONIZED|
\r
784 Modifier.VOLATILE|Modifier.TRANSIENT|Modifier.NATIVE);
\r