2 *******************************************************************************
3 * Copyright (C) 2005-2012, International Business Machines Corporation and *
4 * others. All Rights Reserved. *
5 *******************************************************************************
9 package com.ibm.icu.dev.tool.docs;
11 import java.io.BufferedReader;
13 import java.io.FileInputStream;
14 import java.io.InputStreamReader;
15 import java.io.PrintWriter;
16 import java.lang.reflect.Constructor;
17 import java.lang.reflect.Field;
18 import java.lang.reflect.Method;
19 import java.lang.reflect.Modifier;
20 import java.util.ArrayList;
21 import java.util.Iterator;
24 import java.util.TreeMap;
25 import java.util.TreeSet;
28 * Compare ICU4J and JDK APIS.
30 * TODO: compare protected APIs. Reflection on Class allows you
31 * to either get all inherited methods with public access, or get methods
32 * on the particular class with any access, but no way to get all
33 * inherited methods with any access. Go figure.
35 public class ICUJDKCompare {
36 static final boolean DEBUG = false;
39 private static final String kSrcPrefix = "java.";
40 private static final String kTrgPrefix = "com.ibm.icu.";
41 private static final String[] kPairInfo = {
42 "lang.Character/UCharacter",
43 "lang.Character$UnicodeBlock/UCharacter$UnicodeBlock",
47 "text.DateFormatSymbols",
49 "text.DecimalFormatSymbols",
50 "text.Format/UFormat",
53 "text.SimpleDateFormat",
56 "util.GregorianCalendar",
57 "util.SimpleTimeZone",
59 "util.Locale/ULocale",
60 "util.ResourceBundle/UResourceBundle",
63 private static final String[] kIgnore = new String[] {
64 "lang.Character <init> charValue compareTo MAX_VALUE MIN_VALUE TYPE",
65 "lang.Character$UnicodeBlock SURROGATES_AREA",
66 "util.Calendar FIELD_COUNT",
67 "util.GregorianCalendar FIELD_COUNT",
68 "util.SimpleTimeZone STANDARD_TIME UTC_TIME WALL_TIME",
71 private PrintWriter pw;
72 private String srcPrefix;
73 private String trgPrefix;
74 private Class[] classPairs;
75 private String[] namePairs;
76 private String[] ignore;
78 //private boolean signature;
80 // call System.exit with non-zero if there were some missing APIs
81 public static void main(String[] args) {
82 System.exit(doMain(args));
85 // return non-zero if there were some missing APIs
86 public static int doMain(String[] args) {
87 ICUJDKCompare p = new ICUJDKCompare();
88 p.setOutputWriter(new PrintWriter(System.out));
94 public ICUJDKCompare setOutputWriter(PrintWriter pw) {
99 public ICUJDKCompare setSrcPrefix(String srcPrefix) {
100 this.srcPrefix = srcPrefix;
104 public ICUJDKCompare setTrgPrefix(String trgPrefix) {
105 this.trgPrefix = trgPrefix;
109 public ICUJDKCompare setClassPairs(Class[] classPairs) {
110 this.classPairs = classPairs;
114 public ICUJDKCompare setNamePairs(String[] namePairs) {
115 this.namePairs = namePairs;
119 public ICUJDKCompare setIgnore(String[] ignore) {
120 this.ignore = ignore;
124 public ICUJDKCompare setSwap(boolean swap) {
129 public ICUJDKCompare setup(String[] args) {
130 String namelist = null;
131 String ignorelist = null;
132 for (int i = 0; i < args.length; ++i) {
133 String arg = args[i];
134 if (arg.equals("-swap")) {
136 } else if (arg.equals("-srcPrefix:")) {
137 srcPrefix = args[++i];
138 if (!srcPrefix.endsWith(".")) {
141 } else if (arg.equals("-trgPrefix:")) {
142 trgPrefix = args[++i];
143 if (!trgPrefix.endsWith(".")) {
146 } else if (arg.equals("-names:")) {
147 namelist = args[++i];
148 } else if (arg.equals("-ignore:")) {
149 ignorelist = args[++i];
151 System.err.println("unrecognized argument: " + arg);
152 throw new IllegalStateException();
156 if (ignorelist != null) {
157 if (ignorelist.charAt(0) == '@') { // a file containing ignoreinfo
158 BufferedReader br = null;
160 ArrayList nl = new ArrayList();
161 File f = new File(namelist.substring(1));
162 FileInputStream fis = new FileInputStream(f);
163 InputStreamReader isr = new InputStreamReader(fis);
164 br = new BufferedReader(isr);
166 while (null != (line = br.readLine())) {
169 ignore = (String[])nl.toArray(new String[nl.size()]);
171 catch (Exception e) {
172 System.err.println(e);
173 throw new IllegalStateException();
179 } catch (Exception e) {
184 } else { // a list of ignoreinfo separated by semicolons
185 ignore = ignorelist.split("\\s*;\\s*");
189 if (namelist != null) {
190 String[] names = null;
191 if (namelist.charAt(0) == '@') { // a file
192 BufferedReader br = null;
194 ArrayList nl = new ArrayList();
195 File f = new File(namelist.substring(1));
196 FileInputStream fis = new FileInputStream(f);
197 InputStreamReader isr = new InputStreamReader(fis);
198 br = new BufferedReader(isr);
200 while (null != (line = br.readLine())) {
203 names = (String[])nl.toArray(new String[nl.size()]);
205 catch (Exception e) {
206 System.err.println(e);
207 throw new IllegalStateException();
212 } catch (Exception e) {
218 } else { // a list of names separated by semicolons
219 names = namelist.split("\\s*;\\s*");
221 processPairInfo(names);
229 private void processPairInfo(String[] names) {
230 ArrayList cl = new ArrayList();
231 ArrayList nl = new ArrayList();
232 for (int i = 0; i < names.length; ++i) {
233 String name = names[i];
234 String srcName = srcPrefix;
235 String trgName = trgPrefix;
237 int n = name.indexOf('/');
242 String srcSuffix = name.substring(0, n).trim();
243 String trgSuffix = name.substring(n+1).trim();
244 int jx = srcSuffix.length()+1;
245 int ix = trgSuffix.length()+1;
247 jx = srcSuffix.lastIndexOf('.', jx-1);
248 ix = trgSuffix.lastIndexOf('.', ix-1);
250 srcName += srcSuffix;
251 trgName += srcSuffix.substring(0, jx+1) + trgSuffix;
255 Class jc = Class.forName(srcName);
256 Class ic = Class.forName(trgName);
259 nl.add(ic.getName());
260 nl.add(jc.getName());
262 catch (Exception e) {
263 if (DEBUG) System.err.println("can't load class: " + e.getMessage());
266 classPairs = (Class[])cl.toArray(new Class[cl.size()]);
267 namePairs = (String[])nl.toArray(new String[nl.size()]);
270 private void println(String s) {
271 if (pw != null) pw.println(s);
274 private void flush() {
275 if (pw != null) pw.flush();
278 public int process() {
280 if (srcPrefix == null) {
281 srcPrefix = kSrcPrefix;
284 if (trgPrefix == null) {
285 trgPrefix = kTrgPrefix;
288 if (classPairs == null) {
289 processPairInfo(kPairInfo);
292 if (ignore == null) {
296 println("ICU and Java API Comparison");
297 String ICU_VERSION = "unknown";
299 Class cls = Class.forName("com.ibm.icu.util.VersionInfo");
300 Field fld = cls.getField("ICU_VERSION");
301 ICU_VERSION = fld.get(null).toString();
303 catch (Exception e) {
304 if (DEBUG) System.err.println("can't get VersionInfo: " + e.getMessage());
306 println("ICU Version " + ICU_VERSION);
307 println("JDK Version " + System.getProperty("java.version"));
310 for (int i = 0; i < classPairs.length; i += 2) {
313 errorCount += compare(classPairs[i+1], classPairs[i]);
315 errorCount += compare(classPairs[i], classPairs[i+1]);
318 catch (Exception e) {
319 System.err.println("exception: " + e);
320 System.err.println("between " + namePairs[i] + " and " + namePairs[i+1]);
330 private Constructor cref;
336 MorC(Constructor c) {
341 return mref == null ? cref.getModifiers() : mref.getModifiers();
344 Class getReturnType() {
345 return mref == null ? void.class : mref.getReturnType();
348 Class[] getParameterTypes() {
349 return mref == null ? cref.getParameterTypes() : mref.getParameterTypes();
353 return mref == null ? "<init>" : mref.getName();
356 String getSignature() {
357 return mref == null ? cref.toString() : mref.toString();
361 private int compare(Class class1, Class class2) throws Exception {
362 String n1 = class1.getName();
363 String n2 = class2.getName();
365 println("\ncompare " + n1 + " <> " + n2);
367 MorC[] conss1 = getMorCArray(class1.getConstructors());
368 MorC[] conss2 = getMorCArray(class2.getConstructors());
370 Map cmap1 = getMethodMap(conss1);
371 Map cmap2 = getMethodMap(conss2);
373 MorC[] meths1 = getMorCArray(class1.getMethods());
374 MorC[] meths2 = getMorCArray(class2.getMethods());
376 Map map1 = getMethodMap(meths1);
377 Map map2 = getMethodMap(meths2);
379 Field[] fields1 = class1.getFields();
380 Field[] fields2 = class2.getFields();
382 Set set1 = getFieldSet(fields1);
383 Set set2 = getFieldSet(fields2);
385 if (n1.indexOf("DecimalFormatSymbols") != -1) {
386 pw.format("fields in %s: %s%n", n1, set1);
387 pw.format("fields in %s: %s%n", n2, set2);
390 Map diffConss = diffMethodMaps(cmap2, cmap1);
391 Map diffMeths = diffMethodMaps(map2, map1);
392 Set diffFields = diffFieldSets(set2, set1);
394 diffConss = removeIgnored(n2, diffConss);
395 diffMeths = removeIgnored(n2, diffMeths);
396 diffFields = removeIgnored(n2, diffFields);
398 int result = diffConss.size() + diffMeths.size() + diffFields.size();
399 if (result > 0 && pw != null) {
400 pw.println("Public API in " + n2 + " but not in " + n1);
401 if (diffConss.size() > 0) {
402 pw.println("CONSTRUCTORS");
403 dumpMethodMap(diffConss, pw);
405 if (diffMeths.size() > 0) {
406 pw.println("METHODS");
407 dumpMethodMap(diffMeths, pw);
409 if (diffFields.size() > 0) {
410 pw.println("FIELDS");
411 dumpFieldSet(diffFields, pw);
420 final class MethodRecord {
423 MethodRecord(MorC m) {
424 overrides = new MorC[] { m };
427 MethodRecord(MorC[] ms) {
431 MethodRecord copy() {
432 return new MethodRecord((MorC[])overrides.clone());
436 for (int i = 0; i < overrides.length; ++i) {
437 if (overrides[i] == null) {
441 return overrides.length;
445 MorC[] temp = new MorC[overrides.length + 1];
446 for (int i = 0; i < overrides.length; ++i) {
447 temp[i] = overrides[i];
449 temp[overrides.length] = m;
453 void remove(int index) {
455 while (overrides[i] != null && i < overrides.length-1) {
456 overrides[i] = overrides[i+1];
462 // if a call to a method can be handled by a call to t, remove the
463 // method from our list, and return true
464 boolean removeOverridden(MorC t) {
465 boolean result = false;
467 while (i < overrides.length) {
468 MorC m = overrides[i];
482 // remove all methods handled by any method of mr
483 boolean removeOverridden(MethodRecord mr) {
484 boolean result = false;
485 for (int i = 0; i < mr.overrides.length; ++i) {
486 MorC t = mr.overrides[i];
488 // this shouldn't happen, as the target record should not have been modified
489 throw new IllegalStateException();
491 if (removeOverridden(t)) {
498 void debugmsg(MorC t, MorC m, String msg) {
499 StringBuffer buf = new StringBuffer();
500 buf.append(t.getName());
507 System.out.println(buf.toString());
510 boolean handles(MorC t, MorC m) {
511 // relevant modifiers must match
512 if ((t.getModifiers() & MOD_MASK) != (m.getModifiers() & MOD_MASK)) {
513 if (DEBUG) debugmsg(t, m, "modifier mismatch");
517 Class tr = pairClassEquivalent(t.getReturnType());
518 Class mr = pairClassEquivalent(m.getReturnType());
519 if (!assignableFrom(mr, tr)) { // t return type must be same or narrower than m
520 if (DEBUG) debugmsg(t, m, "return value mismatch");
523 Class[] tts = t.getParameterTypes();
524 Class[] mts = m.getParameterTypes();
525 if (tts.length != mts.length) {
526 if (DEBUG) debugmsg(t, m, "param count mismatch");
530 for (int i = 0; i < tts.length; ++i) {
531 Class tc = pairClassEquivalent(tts[i]);
532 Class mc = pairClassEquivalent(mts[i]);
533 if (!assignableFrom(tc, mc)) { // m param must be same or narrower than t
534 if (DEBUG) debugmsg(t, m, "parameter " + i + " mismatch, " +
535 tts[i].getName() + " not assignable from " + mts[i].getName());
542 public void toString(MorC m, StringBuffer buf) {
543 int mod = m.getModifiers();
545 buf.append(Modifier.toString(mod) + " ");
547 buf.append(nameOf(m.getReturnType()));
549 buf.append(m.getName());
551 Class[] ptypes = m.getParameterTypes();
552 for (int j = 0; j < ptypes.length; ++j) {
556 buf.append(nameOf(ptypes[j]));
561 public String toString() {
562 StringBuffer buf = new StringBuffer();
563 buf.append(overrides[0].getName());
564 for (int i = 0; i < overrides.length; ++i) {
565 MorC m = overrides[i];
572 return buf.toString();
576 public static String nameOf(Class c) {
578 return nameOf(c.getComponentType()) + "[]";
580 String name = c.getName();
581 return name.substring(name.lastIndexOf('.') + 1);
584 static MorC[] getMorCArray(Constructor[] cons) {
585 MorC[] result = new MorC[cons.length];
586 for (int i = 0 ; i < cons.length; ++i) {
587 result[i] = new MorC(cons[i]);
592 static MorC[] getMorCArray(Method[] meths) {
593 MorC[] result = new MorC[meths.length];
594 for (int i = 0 ; i < meths.length; ++i) {
595 result[i] = new MorC(meths[i]);
600 private Map getMethodMap(MorC[] meths) {
601 Map result = new TreeMap();
602 for (int i = 0; i < meths.length; ++i) {
604 String key = m.getName();
605 MethodRecord mr = (MethodRecord)result.get(key);
607 mr = new MethodRecord(m);
616 private void dumpMethodMap(Map m, PrintWriter pw) {
617 Iterator iter = m.entrySet().iterator();
618 while (iter.hasNext()) {
619 dumpMethodRecord((MethodRecord)((Map.Entry)iter.next()).getValue());
624 private void dumpMethodRecord(MethodRecord mr) {
625 pw.println(mr.toString());
628 static Map diffMethodMaps(Map m1, Map m2) {
629 // get all the methods in m1 that aren't mentioned in m2 at all
630 Map result = (Map)((TreeMap)m1).clone();
631 result.keySet().removeAll(m2.keySet());
635 private Map removeIgnored(String name, Map m1) {
636 if (ignore == null) {
639 if (name.startsWith(srcPrefix)) {
640 name = name.substring(srcPrefix.length());
642 name += " "; // to avoid accidental prefix of nested class name
644 // prune ignore list to relevant items
646 for (int i = 0; i < ignore.length; ++i) {
647 String s = ignore[i];
648 if (s.startsWith(name)) {
650 il = new ArrayList();
659 Map result = new TreeMap(((TreeMap)m1).comparator());
661 Iterator iter = result.entrySet().iterator();
662 loop: while (iter.hasNext()) {
663 Map.Entry e = (Map.Entry)iter.next();
664 String key = (String)e.getKey();
665 for (int i = 0; i < il.size(); ++i) {
666 String ig = (String)il.get(i);
667 if (ig.indexOf(" " + key) != 0) {
676 private Set removeIgnored(String name, Set s1) {
677 if (ignore == null) {
680 if (name.startsWith(srcPrefix)) {
681 name = name.substring(srcPrefix.length());
683 name += " "; // to avoid accidental prefix of nested class name
685 // prune ignore list to relevant items
687 for (int i = 0; i < ignore.length; ++i) {
688 String s = ignore[i];
689 if (s.startsWith(name)) {
691 il = new ArrayList();
700 Set result = (Set)((TreeSet)s1).clone();
701 Iterator iter = result.iterator();
702 loop: while (iter.hasNext()) {
703 String key = (String)iter.next();
704 String fieldname = key.substring(0, key.indexOf(' '));
705 for (int i = 0; i < il.size(); ++i) {
706 String ig = (String)il.get(i);
707 if (ig.indexOf(" " + fieldname) != 0) {
716 static final boolean[][] assignmentMap = {
717 // bool char byte short int long float double void
718 { true, false, false, false, false, false, false, false, false }, // boolean
719 { false, true, true, true, false, false, false, false, false }, // char
720 { false, false, true, false, false, false, false, false, false }, // byte
721 { false, false, true, true, false, false, false, false, false }, // short
722 { false, true, true, true, true, false, false, false, false }, // int
723 { false, true, true, true, true, true, false, false, false }, // long
724 { false, true, true, true, true, false, true, false, false }, // float
725 { false, true, true, true, true, false, true, true, false }, // double
726 { false, false, false, false, false, false, false, false, true }, // void
729 static final Class[] prims = {
730 boolean.class, char.class, byte.class, short.class,
731 int.class, long.class, float.class, double.class, void.class
734 static int primIndex(Class cls) {
735 for (int i = 0; i < prims.length; ++i) {
736 if (cls == prims[i]) {
740 throw new IllegalStateException("could not find primitive class: " + cls);
743 static boolean assignableFrom(Class lhs, Class rhs) {
747 if (lhs.isPrimitive()) {
748 if (!rhs.isPrimitive()) {
751 int lhsx = primIndex(lhs);
752 int rhsx = primIndex(rhs);
753 return assignmentMap[lhsx][rhsx];
755 return lhs.isAssignableFrom(rhs);
758 private String toString(Field f) {
759 StringBuffer buf = new StringBuffer(f.getName());
760 int mod = f.getModifiers() & MOD_MASK;
762 buf.append(" " + Modifier.toString(mod));
765 String n = pairEquivalent(f.getType().getName());
766 n = n.substring(n.lastIndexOf('.') + 1);
768 return buf.toString();
771 private Set getFieldSet(Field[] fs) {
772 Set set = new TreeSet();
773 for (int i = 0; i < fs.length; ++i) {
774 set.add(toString(fs[i]));
779 static Set diffFieldSets(Set s1, Set s2) {
780 Set result = (Set)((TreeSet)s1).clone();
781 result.removeAll(s2);
785 private void dumpFieldSet(Set s, PrintWriter pw) {
786 Iterator iter = s.iterator();
787 while (iter.hasNext()) {
788 pw.println(iter.next());
793 // given a target string, if it matches the first of one of our pairs, return the second
794 // or vice-versa if swap is true
795 private String pairEquivalent(String target) {
796 for (int i = 0; i < namePairs.length; i += 2) {
798 if (target.equals(namePairs[i+1])) {
802 if (target.equals(namePairs[i])) {
803 return namePairs[i+1];
810 private Class pairClassEquivalent(Class target) {
811 for (int i = 0; i < classPairs.length; i += 2) {
812 if (target.equals(classPairs[i])) {
813 return classPairs[i+1];
819 static final int MOD_MASK = ~(Modifier.FINAL|Modifier.SYNCHRONIZED|
820 Modifier.VOLATILE|Modifier.TRANSIENT|Modifier.NATIVE);