2 *******************************************************************************
\r
3 * Copyright (C) 2005-2010, International Business Machines Corporation and *
\r
4 * others. All Rights Reserved. *
\r
5 *******************************************************************************
\r
9 package com.ibm.icu.dev.tool.docs;
\r
11 import java.io.BufferedReader;
\r
12 import java.io.File;
\r
13 import java.io.FileInputStream;
\r
14 import java.io.InputStreamReader;
\r
15 import java.io.PrintWriter;
\r
16 import java.lang.reflect.Constructor;
\r
17 import java.lang.reflect.Field;
\r
18 import java.lang.reflect.Method;
\r
19 import java.lang.reflect.Modifier;
\r
20 import java.util.ArrayList;
\r
21 import java.util.Iterator;
\r
22 import java.util.Map;
\r
23 import java.util.Set;
\r
24 import java.util.TreeMap;
\r
25 import java.util.TreeSet;
\r
28 * Compare ICU4J and JDK APIS.
\r
30 * TODO: compare protected APIs. Reflection on Class allows you
\r
31 * to either get all inherited methods with public access, or get methods
\r
32 * on the particular class with any access, but no way to get all
\r
33 * inherited methods with any access. Go figure.
\r
35 public class ICUJDKCompare {
\r
36 static final boolean DEBUG = false;
\r
39 private static final String kSrcPrefix = "java.";
\r
40 private static final String kTrgPrefix = "com.ibm.icu.";
\r
41 private static final String[] kPairInfo = {
\r
42 "lang.Character/UCharacter",
\r
43 "lang.Character$UnicodeBlock/UCharacter$UnicodeBlock",
\r
44 "text.BreakIterator",
\r
47 "text.DateFormatSymbols",
\r
48 "text.DecimalFormat",
\r
49 "text.DecimalFormatSymbols",
\r
50 "text.Format/UFormat",
\r
51 "text.MessageFormat",
\r
52 "text.NumberFormat",
\r
53 "text.SimpleDateFormat",
\r
56 "util.GregorianCalendar",
\r
57 "util.SimpleTimeZone",
\r
59 "util.Locale/ULocale",
\r
60 "util.ResourceBundle/UResourceBundle",
\r
63 private static final String[] kIgnore = new String[] {
\r
64 "lang.Character <init> charValue compareTo MAX_VALUE MIN_VALUE TYPE",
\r
65 "lang.Character$UnicodeBlock SURROGATES_AREA",
\r
66 "util.Calendar FIELD_COUNT",
\r
67 "util.GregorianCalendar FIELD_COUNT",
\r
68 "util.SimpleTimeZone STANDARD_TIME UTC_TIME WALL_TIME",
\r
71 private PrintWriter pw;
\r
72 private String srcPrefix;
\r
73 private String trgPrefix;
\r
74 private Class[] classPairs;
\r
75 private String[] namePairs;
\r
76 private String[] ignore;
\r
77 private boolean swap;
\r
78 //private boolean signature;
\r
80 // call System.exit with non-zero if there were some missing APIs
\r
81 public static void main(String[] args) {
\r
82 System.exit(doMain(args));
\r
85 // return non-zero if there were some missing APIs
\r
86 public static int doMain(String[] args) {
\r
87 ICUJDKCompare p = new ICUJDKCompare();
\r
88 p.setOutputWriter(new PrintWriter(System.out));
\r
94 public ICUJDKCompare setOutputWriter(PrintWriter pw) {
\r
99 public ICUJDKCompare setSrcPrefix(String srcPrefix) {
\r
100 this.srcPrefix = srcPrefix;
\r
104 public ICUJDKCompare setTrgPrefix(String trgPrefix) {
\r
105 this.trgPrefix = trgPrefix;
\r
109 public ICUJDKCompare setClassPairs(Class[] classPairs) {
\r
110 this.classPairs = classPairs;
\r
114 public ICUJDKCompare setNamePairs(String[] namePairs) {
\r
115 this.namePairs = namePairs;
\r
119 public ICUJDKCompare setIgnore(String[] ignore) {
\r
120 this.ignore = ignore;
\r
124 public ICUJDKCompare setSwap(boolean swap) {
\r
129 public ICUJDKCompare setup(String[] args) {
\r
130 String namelist = null;
\r
131 String ignorelist = null;
\r
132 for (int i = 0; i < args.length; ++i) {
\r
133 String arg = args[i];
\r
134 if (arg.equals("-swap")) {
\r
136 } else if (arg.equals("-srcPrefix:")) {
\r
137 srcPrefix = args[++i];
\r
138 if (!srcPrefix.endsWith(".")) {
\r
141 } else if (arg.equals("-trgPrefix:")) {
\r
142 trgPrefix = args[++i];
\r
143 if (!trgPrefix.endsWith(".")) {
\r
146 } else if (arg.equals("-names:")) {
\r
147 namelist = args[++i];
\r
148 } else if (arg.equals("-ignore:")) {
\r
149 ignorelist = args[++i];
\r
151 System.err.println("unrecognized argument: " + arg);
\r
152 throw new IllegalStateException();
\r
156 if (ignorelist != null) {
\r
157 if (ignorelist.charAt(0) == '@') { // a file containing ignoreinfo
\r
159 ArrayList nl = new ArrayList();
\r
160 File f = new File(namelist.substring(1));
\r
161 FileInputStream fis = new FileInputStream(f);
\r
162 InputStreamReader isr = new InputStreamReader(fis);
\r
163 BufferedReader br = new BufferedReader(isr);
\r
164 String line = null;
\r
165 while (null != (line = br.readLine())) {
\r
168 ignore = (String[])nl.toArray(new String[nl.size()]);
\r
170 catch (Exception e) {
\r
171 System.err.println(e);
\r
172 throw new IllegalStateException();
\r
174 } else { // a list of ignoreinfo separated by semicolons
\r
175 ignore = ignorelist.split("\\s*;\\s*");
\r
179 if (namelist != null) {
\r
180 String[] names = null;
\r
181 if (namelist.charAt(0) == '@') { // a file
\r
183 ArrayList nl = new ArrayList();
\r
184 File f = new File(namelist.substring(1));
\r
185 FileInputStream fis = new FileInputStream(f);
\r
186 InputStreamReader isr = new InputStreamReader(fis);
\r
187 BufferedReader br = new BufferedReader(isr);
\r
188 String line = null;
\r
189 while (null != (line = br.readLine())) {
\r
192 names = (String[])nl.toArray(new String[nl.size()]);
\r
194 catch (Exception e) {
\r
195 System.err.println(e);
\r
196 throw new IllegalStateException();
\r
198 } else { // a list of names separated by semicolons
\r
199 names = namelist.split("\\s*;\\s*");
\r
202 processPairInfo(names);
\r
210 private void processPairInfo(String[] names) {
\r
211 ArrayList cl = new ArrayList();
\r
212 ArrayList nl = new ArrayList();
\r
213 for (int i = 0; i < names.length; ++i) {
\r
214 String name = names[i];
\r
215 String srcName = srcPrefix;
\r
216 String trgName = trgPrefix;
\r
218 int n = name.indexOf('/');
\r
223 String srcSuffix = name.substring(0, n).trim();
\r
224 String trgSuffix = name.substring(n+1).trim();
\r
225 int jx = srcSuffix.length()+1;
\r
226 int ix = trgSuffix.length()+1;
\r
228 jx = srcSuffix.lastIndexOf('.', jx-1);
\r
229 ix = trgSuffix.lastIndexOf('.', ix-1);
\r
231 srcName += srcSuffix;
\r
232 trgName += srcSuffix.substring(0, jx+1) + trgSuffix;
\r
236 Class jc = Class.forName(srcName);
\r
237 Class ic = Class.forName(trgName);
\r
240 nl.add(ic.getName());
\r
241 nl.add(jc.getName());
\r
243 catch (Exception e) {
\r
244 if (DEBUG) System.err.println("can't load class: " + e.getMessage());
\r
247 classPairs = (Class[])cl.toArray(new Class[cl.size()]);
\r
248 namePairs = (String[])nl.toArray(new String[nl.size()]);
\r
251 private void println(String s) {
\r
252 if (pw != null) pw.println(s);
\r
255 private void flush() {
\r
256 if (pw != null) pw.flush();
\r
259 public int process() {
\r
261 if (srcPrefix == null) {
\r
262 srcPrefix = kSrcPrefix;
\r
265 if (trgPrefix == null) {
\r
266 trgPrefix = kTrgPrefix;
\r
269 if (classPairs == null) {
\r
270 processPairInfo(kPairInfo);
\r
273 if (ignore == null) {
\r
277 println("ICU and Java API Comparison");
\r
278 String ICU_VERSION = "unknown";
\r
280 Class cls = Class.forName("com.ibm.icu.util.VersionInfo");
\r
281 Field fld = cls.getField("ICU_VERSION");
\r
282 ICU_VERSION = fld.get(null).toString();
\r
284 catch (Exception e) {
\r
285 if (DEBUG) System.err.println("can't get VersionInfo: " + e.getMessage());
\r
287 println("ICU Version " + ICU_VERSION);
\r
288 println("JDK Version " + System.getProperty("java.version"));
\r
290 int errorCount = 0;
\r
291 for (int i = 0; i < classPairs.length; i += 2) {
\r
294 errorCount += compare(classPairs[i+1], classPairs[i]);
\r
296 errorCount += compare(classPairs[i], classPairs[i+1]);
\r
299 catch (Exception e) {
\r
300 System.err.println("exception: " + e);
\r
301 System.err.println("between " + namePairs[i] + " and " + namePairs[i+1]);
\r
302 e.printStackTrace();
\r
309 static class MorC {
\r
310 private Method mref;
\r
311 private Constructor cref;
\r
317 MorC(Constructor c) {
\r
321 int getModifiers() {
\r
322 return mref == null ? cref.getModifiers() : mref.getModifiers();
\r
325 Class getReturnType() {
\r
326 return mref == null ? void.class : mref.getReturnType();
\r
329 Class[] getParameterTypes() {
\r
330 return mref == null ? cref.getParameterTypes() : mref.getParameterTypes();
\r
334 return mref == null ? "<init>" : mref.getName();
\r
337 String getSignature() {
\r
338 return mref == null ? cref.toString() : mref.toString();
\r
342 private int compare(Class class1, Class class2) throws Exception {
\r
343 String n1 = class1.getName();
\r
344 String n2 = class2.getName();
\r
346 println("\ncompare " + n1 + " <> " + n2);
\r
348 MorC[] conss1 = getMorCArray(class1.getConstructors());
\r
349 MorC[] conss2 = getMorCArray(class2.getConstructors());
\r
351 Map cmap1 = getMethodMap(conss1);
\r
352 Map cmap2 = getMethodMap(conss2);
\r
354 MorC[] meths1 = getMorCArray(class1.getMethods());
\r
355 MorC[] meths2 = getMorCArray(class2.getMethods());
\r
357 Map map1 = getMethodMap(meths1);
\r
358 Map map2 = getMethodMap(meths2);
\r
360 Field[] fields1 = class1.getFields();
\r
361 Field[] fields2 = class2.getFields();
\r
363 Set set1 = getFieldSet(fields1);
\r
364 Set set2 = getFieldSet(fields2);
\r
366 if (n1.indexOf("DecimalFormatSymbols") != -1) {
\r
367 pw.format("fields in %s: %s%n", n1, set1);
\r
368 pw.format("fields in %s: %s%n", n2, set2);
\r
371 Map diffConss = diffMethodMaps(cmap2, cmap1);
\r
372 Map diffMeths = diffMethodMaps(map2, map1);
\r
373 Set diffFields = diffFieldSets(set2, set1);
\r
375 diffConss = removeIgnored(n2, diffConss);
\r
376 diffMeths = removeIgnored(n2, diffMeths);
\r
377 diffFields = removeIgnored(n2, diffFields);
\r
379 int result = diffConss.size() + diffMeths.size() + diffFields.size();
\r
380 if (result > 0 && pw != null) {
\r
381 pw.println("Public API in " + n2 + " but not in " + n1);
\r
382 if (diffConss.size() > 0) {
\r
383 pw.println("CONSTRUCTORS");
\r
384 dumpMethodMap(diffConss, pw);
\r
386 if (diffMeths.size() > 0) {
\r
387 pw.println("METHODS");
\r
388 dumpMethodMap(diffMeths, pw);
\r
390 if (diffFields.size() > 0) {
\r
391 pw.println("FIELDS");
\r
392 dumpFieldSet(diffFields, pw);
\r
401 final class MethodRecord {
\r
404 MethodRecord(MorC m) {
\r
405 overrides = new MorC[] { m };
\r
408 MethodRecord(MorC[] ms) {
\r
412 MethodRecord copy() {
\r
413 return new MethodRecord((MorC[])overrides.clone());
\r
417 for (int i = 0; i < overrides.length; ++i) {
\r
418 if (overrides[i] == null) {
\r
422 return overrides.length;
\r
426 MorC[] temp = new MorC[overrides.length + 1];
\r
427 for (int i = 0; i < overrides.length; ++i) {
\r
428 temp[i] = overrides[i];
\r
430 temp[overrides.length] = m;
\r
434 void remove(int index) {
\r
436 while (overrides[i] != null && i < overrides.length-1) {
\r
437 overrides[i] = overrides[i+1];
\r
440 overrides[i] = null;
\r
443 // if a call to a method can be handled by a call to t, remove the
\r
444 // method from our list, and return true
\r
445 boolean removeOverridden(MorC t) {
\r
446 boolean result = false;
\r
448 while (i < overrides.length) {
\r
449 MorC m = overrides[i];
\r
453 if (handles(t, m)) {
\r
463 // remove all methods handled by any method of mr
\r
464 boolean removeOverridden(MethodRecord mr) {
\r
465 boolean result = false;
\r
466 for (int i = 0; i < mr.overrides.length; ++i) {
\r
467 MorC t = mr.overrides[i];
\r
469 // this shouldn't happen, as the target record should not have been modified
\r
470 throw new IllegalStateException();
\r
472 if (removeOverridden(t)) {
\r
479 void debugmsg(MorC t, MorC m, String msg) {
\r
480 StringBuffer buf = new StringBuffer();
\r
481 buf.append(t.getName());
\r
488 System.out.println(buf.toString());
\r
491 boolean handles(MorC t, MorC m) {
\r
492 // relevant modifiers must match
\r
493 if ((t.getModifiers() & MOD_MASK) != (m.getModifiers() & MOD_MASK)) {
\r
494 if (DEBUG) debugmsg(t, m, "modifier mismatch");
\r
498 Class tr = pairClassEquivalent(t.getReturnType());
\r
499 Class mr = pairClassEquivalent(m.getReturnType());
\r
500 if (!assignableFrom(mr, tr)) { // t return type must be same or narrower than m
\r
501 if (DEBUG) debugmsg(t, m, "return value mismatch");
\r
504 Class[] tts = t.getParameterTypes();
\r
505 Class[] mts = m.getParameterTypes();
\r
506 if (tts.length != mts.length) {
\r
507 if (DEBUG) debugmsg(t, m, "param count mismatch");
\r
511 for (int i = 0; i < tts.length; ++i) {
\r
512 Class tc = pairClassEquivalent(tts[i]);
\r
513 Class mc = pairClassEquivalent(mts[i]);
\r
514 if (!assignableFrom(tc, mc)) { // m param must be same or narrower than t
\r
515 if (DEBUG) debugmsg(t, m, "parameter " + i + " mismatch, " +
\r
516 tts[i].getName() + " not assignable from " + mts[i].getName());
\r
523 public void toString(MorC m, StringBuffer buf) {
\r
524 int mod = m.getModifiers();
\r
526 buf.append(Modifier.toString(mod) + " ");
\r
528 buf.append(nameOf(m.getReturnType()));
\r
530 buf.append(m.getName());
\r
532 Class[] ptypes = m.getParameterTypes();
\r
533 for (int j = 0; j < ptypes.length; ++j) {
\r
537 buf.append(nameOf(ptypes[j]));
\r
542 public String toString() {
\r
543 StringBuffer buf = new StringBuffer();
\r
544 buf.append(overrides[0].getName());
\r
545 for (int i = 0; i < overrides.length; ++i) {
\r
546 MorC m = overrides[i];
\r
553 return buf.toString();
\r
557 public static String nameOf(Class c) {
\r
559 return nameOf(c.getComponentType()) + "[]";
\r
561 String name = c.getName();
\r
562 return name.substring(name.lastIndexOf('.') + 1);
\r
565 static MorC[] getMorCArray(Constructor[] cons) {
\r
566 MorC[] result = new MorC[cons.length];
\r
567 for (int i = 0 ; i < cons.length; ++i) {
\r
568 result[i] = new MorC(cons[i]);
\r
573 static MorC[] getMorCArray(Method[] meths) {
\r
574 MorC[] result = new MorC[meths.length];
\r
575 for (int i = 0 ; i < meths.length; ++i) {
\r
576 result[i] = new MorC(meths[i]);
\r
581 private Map getMethodMap(MorC[] meths) {
\r
582 Map result = new TreeMap();
\r
583 for (int i = 0; i < meths.length; ++i) {
\r
585 String key = m.getName();
\r
586 MethodRecord mr = (MethodRecord)result.get(key);
\r
588 mr = new MethodRecord(m);
\r
589 result.put(key, mr);
\r
597 private void dumpMethodMap(Map m, PrintWriter pw) {
\r
598 Iterator iter = m.entrySet().iterator();
\r
599 while (iter.hasNext()) {
\r
600 dumpMethodRecord((MethodRecord)((Map.Entry)iter.next()).getValue());
\r
605 private void dumpMethodRecord(MethodRecord mr) {
\r
606 pw.println(mr.toString());
\r
609 static Map diffMethodMaps(Map m1, Map m2) {
\r
610 // get all the methods in m1 that aren't mentioned in m2 at all
\r
611 Map result = (Map)((TreeMap)m1).clone();
\r
612 result.keySet().removeAll(m2.keySet());
\r
616 private Map removeIgnored(String name, Map m1) {
\r
617 if (ignore == null) {
\r
620 if (name.startsWith(srcPrefix)) {
\r
621 name = name.substring(srcPrefix.length());
\r
623 name += " "; // to avoid accidental prefix of nested class name
\r
625 // prune ignore list to relevant items
\r
626 ArrayList il = null;
\r
627 for (int i = 0; i < ignore.length; ++i) {
\r
628 String s = ignore[i];
\r
629 if (s.startsWith(name)) {
\r
631 il = new ArrayList();
\r
640 Map result = new TreeMap(((TreeMap)m1).comparator());
\r
642 Iterator iter = result.entrySet().iterator();
\r
643 loop: while (iter.hasNext()) {
\r
644 Map.Entry e = (Map.Entry)iter.next();
\r
645 String key = (String)e.getKey();
\r
646 for (int i = 0; i < il.size(); ++i) {
\r
647 String ig = (String)il.get(i);
\r
648 if (ig.indexOf(" " + key) != 0) {
\r
657 private Set removeIgnored(String name, Set s1) {
\r
658 if (ignore == null) {
\r
661 if (name.startsWith(srcPrefix)) {
\r
662 name = name.substring(srcPrefix.length());
\r
664 name += " "; // to avoid accidental prefix of nested class name
\r
666 // prune ignore list to relevant items
\r
667 ArrayList il = null;
\r
668 for (int i = 0; i < ignore.length; ++i) {
\r
669 String s = ignore[i];
\r
670 if (s.startsWith(name)) {
\r
672 il = new ArrayList();
\r
681 Set result = (Set)((TreeSet)s1).clone();
\r
682 Iterator iter = result.iterator();
\r
683 loop: while (iter.hasNext()) {
\r
684 String key = (String)iter.next();
\r
685 String fieldname = key.substring(0, key.indexOf(' '));
\r
686 for (int i = 0; i < il.size(); ++i) {
\r
687 String ig = (String)il.get(i);
\r
688 if (ig.indexOf(" " + fieldname) != 0) {
\r
697 static final boolean[][] assignmentMap = {
\r
698 // bool char byte short int long float double void
\r
699 { true, false, false, false, false, false, false, false, false }, // boolean
\r
700 { false, true, true, true, false, false, false, false, false }, // char
\r
701 { false, false, true, false, false, false, false, false, false }, // byte
\r
702 { false, false, true, true, false, false, false, false, false }, // short
\r
703 { false, true, true, true, true, false, false, false, false }, // int
\r
704 { false, true, true, true, true, true, false, false, false }, // long
\r
705 { false, true, true, true, true, false, true, false, false }, // float
\r
706 { false, true, true, true, true, false, true, true, false }, // double
\r
707 { false, false, false, false, false, false, false, false, true }, // void
\r
710 static final Class[] prims = {
\r
711 boolean.class, char.class, byte.class, short.class,
\r
712 int.class, long.class, float.class, double.class, void.class
\r
715 static int primIndex(Class cls) {
\r
716 for (int i = 0; i < prims.length; ++i) {
\r
717 if (cls == prims[i]) {
\r
721 throw new IllegalStateException("could not find primitive class: " + cls);
\r
724 static boolean assignableFrom(Class lhs, Class rhs) {
\r
728 if (lhs.isPrimitive()) {
\r
729 if (!rhs.isPrimitive()) {
\r
732 int lhsx = primIndex(lhs);
\r
733 int rhsx = primIndex(rhs);
\r
734 return assignmentMap[lhsx][rhsx];
\r
736 return lhs.isAssignableFrom(rhs);
\r
739 private String toString(Field f) {
\r
740 StringBuffer buf = new StringBuffer(f.getName());
\r
741 int mod = f.getModifiers() & MOD_MASK;
\r
743 buf.append(" " + Modifier.toString(mod));
\r
746 String n = pairEquivalent(f.getType().getName());
\r
747 n = n.substring(n.lastIndexOf('.') + 1);
\r
749 return buf.toString();
\r
752 private Set getFieldSet(Field[] fs) {
\r
753 Set set = new TreeSet();
\r
754 for (int i = 0; i < fs.length; ++i) {
\r
755 set.add(toString(fs[i]));
\r
760 static Set diffFieldSets(Set s1, Set s2) {
\r
761 Set result = (Set)((TreeSet)s1).clone();
\r
762 result.removeAll(s2);
\r
766 private void dumpFieldSet(Set s, PrintWriter pw) {
\r
767 Iterator iter = s.iterator();
\r
768 while (iter.hasNext()) {
\r
769 pw.println(iter.next());
\r
774 // given a target string, if it matches the first of one of our pairs, return the second
\r
775 // or vice-versa if swap is true
\r
776 private String pairEquivalent(String target) {
\r
777 for (int i = 0; i < namePairs.length; i += 2) {
\r
779 if (target.equals(namePairs[i+1])) {
\r
780 return namePairs[i];
\r
783 if (target.equals(namePairs[i])) {
\r
784 return namePairs[i+1];
\r
791 private Class pairClassEquivalent(Class target) {
\r
792 for (int i = 0; i < classPairs.length; i += 2) {
\r
793 if (target.equals(classPairs[i])) {
\r
794 return classPairs[i+1];
\r
800 static final int MOD_MASK = ~(Modifier.FINAL|Modifier.SYNCHRONIZED|
\r
801 Modifier.VOLATILE|Modifier.TRANSIENT|Modifier.NATIVE);
\r