001: /*
002: *******************************************************************************
003: * Copyright (C) 2005-2006, International Business Machines Corporation and *
004: * others. All Rights Reserved. *
005: *******************************************************************************
006: *
007: */
008:
009: package com.ibm.icu.dev.tool.docs;
010:
011: import java.io.*;
012: import java.lang.reflect.*;
013: import java.util.*;
014:
015: /**
016: * Compare ICU4J and JDK APIS.
017: *
018: * TODO: compare protected APIs. Reflection on Class allows you
019: * to either get all inherited methods with public access, or get methods
020: * on the particular class with any access, but no way to get all
021: * inherited methods with any access. Go figure.
022: */
023: public class ICUJDKCompare {
024: static final boolean DEBUG = false;
025:
026: // set up defaults
027: private static final String kSrcPrefix = "java.";
028: private static final String kTrgPrefix = "com.ibm.icu.";
029: private static final String[] kPairInfo = {
030: "lang.Character/UCharacter",
031: "lang.Character$UnicodeBlock/UCharacter$UnicodeBlock",
032: "text.BreakIterator", "text.Collator", "text.DateFormat",
033: "text.DateFormatSymbols", "text.DecimalFormat",
034: "text.DecimalFormatSymbols", "text.Format/UFormat",
035: "text.MessageFormat", "text.NumberFormat",
036: "text.SimpleDateFormat", "util.Calendar", "util.Currency",
037: "util.GregorianCalendar", "util.SimpleTimeZone",
038: "util.TimeZone", "util.Locale/ULocale",
039: "util.ResourceBundle/UResourceBundle", };
040:
041: private static final String[] kIgnore = new String[] {
042: "lang.Character <init> charValue compareTo MAX_VALUE MIN_VALUE TYPE",
043: "lang.Character$UnicodeBlock SURROGATES_AREA",
044: "util.Calendar FIELD_COUNT",
045: "util.GregorianCalendar FIELD_COUNT",
046: "util.SimpleTimeZone STANDARD_TIME UTC_TIME WALL_TIME", };
047:
048: private PrintWriter pw;
049: private String srcPrefix;
050: private String trgPrefix;
051: private Class[] classPairs;
052: private String[] namePairs;
053: private String[] ignore;
054: private boolean swap;
055: private boolean signature;
056:
057: // call System.exit with non-zero if there were some missing APIs
058: public static void main(String[] args) {
059: System.exit(doMain(args));
060: }
061:
062: // return non-zero if there were some missing APIs
063: public static int doMain(String[] args) {
064: ICUJDKCompare p = new ICUJDKCompare();
065: p.setOutputWriter(new PrintWriter(System.out));
066: p.setup(args);
067: return p.process();
068: }
069:
070: // setters
071: public ICUJDKCompare setOutputWriter(PrintWriter pw) {
072: this .pw = pw;
073: return this ;
074: }
075:
076: public ICUJDKCompare setSrcPrefix(String srcPrefix) {
077: this .srcPrefix = srcPrefix;
078: return this ;
079: }
080:
081: public ICUJDKCompare setTrgPrefix(String trgPrefix) {
082: this .trgPrefix = trgPrefix;
083: return this ;
084: }
085:
086: public ICUJDKCompare setClassPairs(Class[] classPairs) {
087: this .classPairs = classPairs;
088: return this ;
089: }
090:
091: public ICUJDKCompare setNamePairs(String[] namePairs) {
092: this .namePairs = namePairs;
093: return this ;
094: }
095:
096: public ICUJDKCompare setIgnore(String[] ignore) {
097: this .ignore = ignore;
098: return this ;
099: }
100:
101: public ICUJDKCompare setSwap(boolean swap) {
102: this .swap = swap;
103: return this ;
104: }
105:
106: public ICUJDKCompare setup(String[] args) {
107: String namelist = null;
108: String ignorelist = null;
109: for (int i = 0; i < args.length; ++i) {
110: String arg = args[i];
111: if (arg.equals("-swap")) {
112: swap = true;
113: } else if (arg.equals("-srcPrefix:")) {
114: srcPrefix = args[++i];
115: if (!srcPrefix.endsWith(".")) {
116: srcPrefix += '.';
117: }
118: } else if (arg.equals("-trgPrefix:")) {
119: trgPrefix = args[++i];
120: if (!trgPrefix.endsWith(".")) {
121: trgPrefix += '.';
122: }
123: } else if (arg.equals("-names:")) {
124: namelist = args[++i];
125: } else if (arg.equals("-ignore:")) {
126: ignorelist = args[++i];
127: } else {
128: System.err.println("unrecognized argument: " + arg);
129: throw new IllegalStateException();
130: }
131: }
132:
133: if (ignorelist != null) {
134: if (ignorelist.charAt(0) == '@') { // a file containing ignoreinfo
135: try {
136: ArrayList nl = new ArrayList();
137: File f = new File(namelist.substring(1));
138: FileInputStream fis = new FileInputStream(f);
139: InputStreamReader isr = new InputStreamReader(fis);
140: BufferedReader br = new BufferedReader(isr);
141: String line = null;
142: while (null != (line = br.readLine())) {
143: nl.add(line);
144: }
145: ignore = (String[]) nl
146: .toArray(new String[nl.size()]);
147: } catch (Exception e) {
148: System.err.println(e);
149: throw new IllegalStateException();
150: }
151: } else { // a list of ignoreinfo separated by semicolons
152: ignore = ignorelist.split("\\s*;\\s*");
153: }
154: }
155:
156: if (namelist != null) {
157: String[] names = null;
158: if (namelist.charAt(0) == '@') { // a file
159: try {
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: BufferedReader br = new BufferedReader(isr);
165: String line = null;
166: while (null != (line = br.readLine())) {
167: nl.add(line);
168: }
169: names = (String[]) nl
170: .toArray(new String[nl.size()]);
171: } catch (Exception e) {
172: System.err.println(e);
173: throw new IllegalStateException();
174: }
175: } else { // a list of names separated by semicolons
176: names = namelist.split("\\s*;\\s*");
177: }
178:
179: processPairInfo(names);
180: }
181:
182: pw.flush();
183:
184: return this ;
185: }
186:
187: private void processPairInfo(String[] names) {
188: ArrayList cl = new ArrayList();
189: ArrayList nl = new ArrayList();
190: for (int i = 0; i < names.length; ++i) {
191: String name = names[i];
192: String srcName = srcPrefix;
193: String trgName = trgPrefix;
194:
195: int n = name.indexOf('/');
196: if (n == -1) {
197: srcName += name;
198: trgName += name;
199: } else {
200: String srcSuffix = name.substring(0, n).trim();
201: String trgSuffix = name.substring(n + 1).trim();
202: int jx = srcSuffix.length() + 1;
203: int ix = trgSuffix.length() + 1;
204: while (ix != -1) {
205: jx = srcSuffix.lastIndexOf('.', jx - 1);
206: ix = trgSuffix.lastIndexOf('.', ix - 1);
207: }
208: srcName += srcSuffix;
209: trgName += srcSuffix.substring(0, jx + 1) + trgSuffix;
210: }
211:
212: try {
213: Class jc = Class.forName(srcName);
214: Class ic = Class.forName(trgName);
215: cl.add(ic);
216: cl.add(jc);
217: nl.add(ic.getName());
218: nl.add(jc.getName());
219: } catch (Exception e) {
220: if (DEBUG)
221: System.err.println("can't load class: "
222: + e.getMessage());
223: }
224: }
225: classPairs = (Class[]) cl.toArray(new Class[cl.size()]);
226: namePairs = (String[]) nl.toArray(new String[nl.size()]);
227: }
228:
229: private void println(String s) {
230: if (pw != null)
231: pw.println(s);
232: }
233:
234: private void flush() {
235: if (pw != null)
236: pw.flush();
237: }
238:
239: public int process() {
240: // set defaults
241: if (srcPrefix == null) {
242: srcPrefix = kSrcPrefix;
243: }
244:
245: if (trgPrefix == null) {
246: trgPrefix = kTrgPrefix;
247: }
248:
249: if (classPairs == null) {
250: processPairInfo(kPairInfo);
251: }
252:
253: if (ignore == null) {
254: ignore = kIgnore;
255: }
256:
257: println("ICU and Java API Comparison");
258: String ICU_VERSION = "unknown";
259: try {
260: Class cls = Class.forName("com.ibm.icu.util.VersionInfo");
261: Field fld = cls.getField("ICU_VERSION");
262: ICU_VERSION = fld.get(null).toString();
263: } catch (Exception e) {
264: if (DEBUG)
265: System.err.println("can't get VersionInfo: "
266: + e.getMessage());
267: }
268: println("ICU Version " + ICU_VERSION);
269: println("JDK Version " + System.getProperty("java.version"));
270:
271: int errorCount = 0;
272: for (int i = 0; i < classPairs.length; i += 2) {
273: try {
274: if (swap) {
275: errorCount += compare(classPairs[i + 1],
276: classPairs[i]);
277: } else {
278: errorCount += compare(classPairs[i],
279: classPairs[i + 1]);
280: }
281: } catch (Exception e) {
282: System.err.println("exception: " + e);
283: System.err.println("between " + namePairs[i] + " and "
284: + namePairs[i + 1]);
285: e.printStackTrace();
286: errorCount += 1;
287: }
288: }
289: return errorCount;
290: }
291:
292: static class MorC {
293: private Method mref;
294: private Constructor cref;
295:
296: MorC(Method m) {
297: mref = m;
298: }
299:
300: MorC(Constructor c) {
301: cref = c;
302: }
303:
304: int getModifiers() {
305: return mref == null ? cref.getModifiers() : mref
306: .getModifiers();
307: }
308:
309: Class getReturnType() {
310: return mref == null ? void.class : mref.getReturnType();
311: }
312:
313: Class[] getParameterTypes() {
314: return mref == null ? cref.getParameterTypes() : mref
315: .getParameterTypes();
316: }
317:
318: String getName() {
319: return mref == null ? "<init>" : mref.getName();
320: }
321:
322: String getSignature() {
323: return mref == null ? cref.toString() : mref.toString();
324: }
325: }
326:
327: private int compare(Class class1, Class class2) throws Exception {
328: String n1 = class1.getName();
329: String n2 = class2.getName();
330:
331: println("\ncompare " + n1 + " <> " + n2);
332:
333: MorC[] conss1 = getMorCArray(class1.getConstructors());
334: MorC[] conss2 = getMorCArray(class2.getConstructors());
335:
336: Map cmap1 = getMethodMap(conss1);
337: Map cmap2 = getMethodMap(conss2);
338:
339: MorC[] meths1 = getMorCArray(class1.getMethods());
340: MorC[] meths2 = getMorCArray(class2.getMethods());
341:
342: Map map1 = getMethodMap(meths1);
343: Map map2 = getMethodMap(meths2);
344:
345: Field[] fields1 = class1.getFields();
346: Field[] fields2 = class2.getFields();
347:
348: Set set1 = getFieldSet(fields1);
349: Set set2 = getFieldSet(fields2);
350:
351: Map diffConss = diffMethodMaps(cmap2, cmap1);
352: Map diffMeths = diffMethodMaps(map2, map1);
353: Set diffFields = diffFieldSets(set2, set1);
354:
355: diffConss = removeIgnored(n2, diffConss);
356: diffMeths = removeIgnored(n2, diffMeths);
357: diffFields = removeIgnored(n2, diffFields);
358:
359: int result = diffConss.size() + diffMeths.size()
360: + diffFields.size();
361: if (result > 0 && pw != null) {
362: pw.println("Public API in " + n2 + " but not in " + n1);
363: if (diffConss.size() > 0) {
364: pw.println("CONSTRUCTORS");
365: dumpMethodMap(diffConss, pw);
366: }
367: if (diffMeths.size() > 0) {
368: pw.println("METHODS");
369: dumpMethodMap(diffMeths, pw);
370: }
371: if (diffFields.size() > 0) {
372: pw.println("FIELDS");
373: dumpFieldSet(diffFields, pw);
374: }
375: }
376:
377: flush();
378:
379: return result;
380: }
381:
382: final class MethodRecord {
383: MorC[] overrides;
384:
385: MethodRecord(MorC m) {
386: overrides = new MorC[] { m };
387: }
388:
389: MethodRecord(MorC[] ms) {
390: overrides = ms;
391: }
392:
393: MethodRecord copy() {
394: return new MethodRecord((MorC[]) overrides.clone());
395: }
396:
397: int count() {
398: for (int i = 0; i < overrides.length; ++i) {
399: if (overrides[i] == null) {
400: return i;
401: }
402: }
403: return overrides.length;
404: }
405:
406: void add(MorC m) {
407: MorC[] temp = new MorC[overrides.length + 1];
408: for (int i = 0; i < overrides.length; ++i) {
409: temp[i] = overrides[i];
410: }
411: temp[overrides.length] = m;
412: overrides = temp;
413: }
414:
415: void remove(int index) {
416: int i = index;
417: while (overrides[i] != null && i < overrides.length - 1) {
418: overrides[i] = overrides[i + 1];
419: ++i;
420: }
421: overrides[i] = null;
422: }
423:
424: // if a call to a method can be handled by a call to t, remove the
425: // method from our list, and return true
426: boolean removeOverridden(MorC t) {
427: boolean result = false;
428: int i = 0;
429: while (i < overrides.length) {
430: MorC m = overrides[i];
431: if (m == null) {
432: break;
433: }
434: if (handles(t, m)) {
435: remove(i);
436: result = true;
437: } else {
438: ++i;
439: }
440: }
441: return result;
442: }
443:
444: // remove all methods handled by any method of mr
445: boolean removeOverridden(MethodRecord mr) {
446: boolean result = false;
447: for (int i = 0; i < mr.overrides.length; ++i) {
448: MorC t = mr.overrides[i];
449: if (t == null) {
450: // this shouldn't happen, as the target record should not have been modified
451: throw new IllegalStateException();
452: }
453: if (removeOverridden(t)) {
454: result = true;
455: }
456: }
457: return result;
458: }
459:
460: void debugmsg(MorC t, MorC m, String msg) {
461: StringBuffer buf = new StringBuffer();
462: buf.append(t.getName());
463: buf.append(" ");
464: buf.append(msg);
465: buf.append("\n ");
466: toString(t, buf);
467: buf.append("\n ");
468: toString(m, buf);
469: System.out.println(buf.toString());
470: }
471:
472: boolean handles(MorC t, MorC m) {
473: // relevant modifiers must match
474: if ((t.getModifiers() & MOD_MASK) != (m.getModifiers() & MOD_MASK)) {
475: if (DEBUG)
476: debugmsg(t, m, "modifier mismatch");
477: return false;
478: }
479:
480: Class tr = pairClassEquivalent(t.getReturnType());
481: Class mr = pairClassEquivalent(m.getReturnType());
482: if (!assignableFrom(mr, tr)) { // t return type must be same or narrower than m
483: if (DEBUG)
484: debugmsg(t, m, "return value mismatch");
485: return false;
486: }
487: Class[] tts = t.getParameterTypes();
488: Class[] mts = m.getParameterTypes();
489: if (tts.length != mts.length) {
490: if (DEBUG)
491: debugmsg(t, m, "param count mismatch");
492: return false;
493: }
494:
495: for (int i = 0; i < tts.length; ++i) {
496: Class tc = pairClassEquivalent(tts[i]);
497: Class mc = pairClassEquivalent(mts[i]);
498: if (!assignableFrom(tc, mc)) { // m param must be same or narrower than t
499: if (DEBUG)
500: debugmsg(t, m, "parameter " + i + " mismatch, "
501: + tts[i].getName()
502: + " not assignable from "
503: + mts[i].getName());
504: return false;
505: }
506: }
507: return true;
508: }
509:
510: public void toString(MorC m, StringBuffer buf) {
511: int mod = m.getModifiers();
512: if (mod != 0) {
513: buf.append(Modifier.toString(mod) + " ");
514: }
515: buf.append(nameOf(m.getReturnType()));
516: buf.append(" ");
517: buf.append(m.getName());
518: buf.append("(");
519: Class[] ptypes = m.getParameterTypes();
520: for (int j = 0; j < ptypes.length; ++j) {
521: if (j > 0) {
522: buf.append(", ");
523: }
524: buf.append(nameOf(ptypes[j]));
525: }
526: buf.append(')');
527: }
528:
529: public String toString() {
530: StringBuffer buf = new StringBuffer();
531: buf.append(overrides[0].getName());
532: for (int i = 0; i < overrides.length; ++i) {
533: MorC m = overrides[i];
534: if (m == null) {
535: break;
536: }
537: buf.append("\n ");
538: toString(m, buf);
539: }
540: return buf.toString();
541: }
542: }
543:
544: public static String nameOf(Class c) {
545: if (c.isArray()) {
546: return nameOf(c.getComponentType()) + "[]";
547: }
548: String name = c.getName();
549: return name.substring(name.lastIndexOf('.') + 1);
550: }
551:
552: static MorC[] getMorCArray(Constructor[] cons) {
553: MorC[] result = new MorC[cons.length];
554: for (int i = 0; i < cons.length; ++i) {
555: result[i] = new MorC(cons[i]);
556: }
557: return result;
558: }
559:
560: static MorC[] getMorCArray(Method[] meths) {
561: MorC[] result = new MorC[meths.length];
562: for (int i = 0; i < meths.length; ++i) {
563: result[i] = new MorC(meths[i]);
564: }
565: return result;
566: }
567:
568: private Map getMethodMap(MorC[] meths) {
569: Map result = new TreeMap();
570: for (int i = 0; i < meths.length; ++i) {
571: MorC m = meths[i];
572: String key = m.getName();
573: MethodRecord mr = (MethodRecord) result.get(key);
574: if (mr == null) {
575: mr = new MethodRecord(m);
576: result.put(key, mr);
577: } else {
578: mr.add(m);
579: }
580: }
581: return result;
582: }
583:
584: private void dumpMethodMap(Map m, PrintWriter pw) {
585: Iterator iter = m.entrySet().iterator();
586: while (iter.hasNext()) {
587: dumpMethodRecord((MethodRecord) ((Map.Entry) iter.next())
588: .getValue());
589: }
590: pw.flush();
591: }
592:
593: private void dumpMethodRecord(MethodRecord mr) {
594: pw.println(mr.toString());
595: }
596:
597: static Map diffMethodMaps(Map m1, Map m2) {
598: // get all the methods in m1 that aren't mentioned in m2 at all
599: Map result = (Map) ((TreeMap) m1).clone();
600: result.keySet().removeAll(m2.keySet());
601: return result;
602: }
603:
604: private Map removeIgnored(String name, Map m1) {
605: if (ignore == null) {
606: return m1;
607: }
608: if (name.startsWith(srcPrefix)) {
609: name = name.substring(srcPrefix.length());
610: }
611: name += " "; // to avoid accidental prefix of nested class name
612:
613: // prune ignore list to relevant items
614: ArrayList il = null;
615: for (int i = 0; i < ignore.length; ++i) {
616: String s = ignore[i];
617: if (s.startsWith(name)) {
618: if (il == null) {
619: il = new ArrayList();
620: }
621: il.add(s);
622: }
623: }
624: if (il == null) {
625: return m1;
626: }
627:
628: Map result = new TreeMap(((TreeMap) m1).comparator());
629: result.putAll(m1);
630: Iterator iter = result.entrySet().iterator();
631: loop: while (iter.hasNext()) {
632: Map.Entry e = (Map.Entry) iter.next();
633: String key = (String) e.getKey();
634: for (int i = 0; i < il.size(); ++i) {
635: String ig = (String) il.get(i);
636: if (ig.indexOf(" " + key) != 0) {
637: iter.remove();
638: continue loop;
639: }
640: }
641: }
642: return result;
643: }
644:
645: private Set removeIgnored(String name, Set s1) {
646: if (ignore == null) {
647: return s1;
648: }
649: if (name.startsWith(srcPrefix)) {
650: name = name.substring(srcPrefix.length());
651: }
652: name += " "; // to avoid accidental prefix of nested class name
653:
654: // prune ignore list to relevant items
655: ArrayList il = null;
656: for (int i = 0; i < ignore.length; ++i) {
657: String s = ignore[i];
658: if (s.startsWith(name)) {
659: if (il == null) {
660: il = new ArrayList();
661: }
662: il.add(s);
663: }
664: }
665: if (il == null) {
666: return s1;
667: }
668:
669: Set result = (Set) ((TreeSet) s1).clone();
670: Iterator iter = result.iterator();
671: loop: while (iter.hasNext()) {
672: String key = (String) iter.next();
673: String fieldname = key.substring(0, key.indexOf(' '));
674: for (int i = 0; i < il.size(); ++i) {
675: String ig = (String) il.get(i);
676: if (ig.indexOf(" " + fieldname) != 0) {
677: iter.remove();
678: continue loop;
679: }
680: }
681: }
682: return result;
683: }
684:
685: static final boolean[][] assignmentMap = {
686: // bool char byte short int long float double void
687: { true, false, false, false, false, false, false, false,
688: false }, // boolean
689: { false, true, true, true, false, false, false, false,
690: false }, // char
691: { false, false, true, false, false, false, false, false,
692: false }, // byte
693: { false, false, true, true, false, false, false, false,
694: false }, // short
695: { false, true, true, true, true, false, false, false, false }, // int
696: { false, true, true, true, true, true, false, false, false }, // long
697: { false, true, true, true, true, false, true, false, false }, // float
698: { false, true, true, true, true, false, true, true, false }, // double
699: { false, false, false, false, false, false, false, false,
700: true }, // void
701: };
702:
703: static final Class[] prims = { boolean.class, char.class,
704: byte.class, short.class, int.class, long.class,
705: float.class, double.class, void.class };
706:
707: static int primIndex(Class cls) {
708: for (int i = 0; i < prims.length; ++i) {
709: if (cls == prims[i]) {
710: return i;
711: }
712: }
713: throw new IllegalStateException(
714: "could not find primitive class: " + cls);
715: }
716:
717: static boolean assignableFrom(Class lhs, Class rhs) {
718: if (lhs == rhs) {
719: return true;
720: }
721: if (lhs.isPrimitive()) {
722: if (!rhs.isPrimitive()) {
723: return false;
724: }
725: int lhsx = primIndex(lhs);
726: int rhsx = primIndex(rhs);
727: return assignmentMap[lhsx][rhsx];
728: }
729: return lhs.isAssignableFrom(rhs);
730: }
731:
732: private String toString(Field f) {
733: StringBuffer buf = new StringBuffer(f.getName());
734: int mod = f.getModifiers() & MOD_MASK;
735: if (mod != 0) {
736: buf.append(" " + Modifier.toString(mod));
737: }
738: buf.append(" ");
739: String n = pairEquivalent(f.getType().getName());
740: n = n.substring(n.lastIndexOf('.') + 1);
741: buf.append(n);
742: return buf.toString();
743: }
744:
745: private Set getFieldSet(Field[] fs) {
746: Set set = new TreeSet();
747: for (int i = 0; i < fs.length; ++i) {
748: set.add(toString(fs[i]));
749: }
750: return set;
751: }
752:
753: static Set diffFieldSets(Set s1, Set s2) {
754: Set result = (Set) ((TreeSet) s1).clone();
755: result.removeAll(s2);
756: return result;
757: }
758:
759: private void dumpFieldSet(Set s, PrintWriter pw) {
760: Iterator iter = s.iterator();
761: while (iter.hasNext()) {
762: pw.println(iter.next());
763: }
764: pw.flush();
765: }
766:
767: // given a target string, if it matches the first of one of our pairs, return the second
768: // or vice-versa if swap is true
769: private String pairEquivalent(String target) {
770: for (int i = 0; i < namePairs.length; i += 2) {
771: if (swap) {
772: if (target.equals(namePairs[i + 1])) {
773: return namePairs[i];
774: }
775: } else {
776: if (target.equals(namePairs[i])) {
777: return namePairs[i + 1];
778: }
779: }
780: }
781: return target;
782: }
783:
784: private Class pairClassEquivalent(Class target) {
785: for (int i = 0; i < classPairs.length; i += 2) {
786: if (target.equals(classPairs[i])) {
787: return classPairs[i + 1];
788: }
789: }
790: return target;
791: }
792:
793: static final int MOD_MASK = ~(Modifier.FINAL
794: | Modifier.SYNCHRONIZED | Modifier.VOLATILE
795: | Modifier.TRANSIENT | Modifier.NATIVE);
796: }
|