blob: 0d6aadfc38f6a8226d80e32cebf2387176d4d019 [file] [log] [blame]
/*
*******************************************************************************
* Copyright (C) 2004, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*
*/
package com.ibm.icu.dev.tool.docs;
import java.io.*;
import java.lang.reflect.*;
import java.util.*;
/**
* Compare ICU4J and JDK APIS.
*
* TODO: compare protected APIs. Reflection on Class allows you
* to either get all inherited methods with public access, or get methods
* on the particular class with any access, but no way to get all
* inherited methods with any access. Go figure.
*/
public class ICUJDKCompare {
static final boolean DEBUG = false;
static final Class[] pairClasses = {
com.ibm.icu.lang.UCharacter.class, java.lang.Character.class,
com.ibm.icu.lang.UCharacter.UnicodeBlock.class, java.lang.Character.UnicodeBlock.class,
com.ibm.icu.text.BreakIterator.class, java.text.BreakIterator.class,
com.ibm.icu.text.Collator.class, java.text.Collator.class,
com.ibm.icu.text.DateFormat.class, java.text.DateFormat.class,
com.ibm.icu.text.DateFormatSymbols.class, java.text.DateFormatSymbols.class,
com.ibm.icu.text.DecimalFormat.class, java.text.DecimalFormat.class,
com.ibm.icu.text.DecimalFormatSymbols.class, java.text.DecimalFormatSymbols.class,
com.ibm.icu.text.UFormat.class, java.text.Format.class,
com.ibm.icu.text.MessageFormat.class, java.text.MessageFormat.class,
com.ibm.icu.text.NumberFormat.class, java.text.NumberFormat.class,
com.ibm.icu.text.SimpleDateFormat.class, java.text.SimpleDateFormat.class,
com.ibm.icu.util.Calendar.class, java.util.Calendar.class,
com.ibm.icu.util.Currency.class, java.util.Currency.class,
com.ibm.icu.util.GregorianCalendar.class, java.util.GregorianCalendar.class,
com.ibm.icu.util.SimpleTimeZone.class, java.util.SimpleTimeZone.class,
com.ibm.icu.util.TimeZone.class, java.util.TimeZone.class,
com.ibm.icu.util.ULocale.class, java.util.Locale.class,
com.ibm.icu.util.UResourceBundle.class, java.util.ResourceBundle.class
};
static final String[] pairs = new String[pairClasses.length];
static {
for (int i = 0; i < pairs.length; ++i) {
pairs[i] = pairClasses[i].getName();
}
}
public static void main(String[] args) {
System.out.println("ICU and Java API Comparison");
System.out.println("ICU Version " + com.ibm.icu.util.VersionInfo.ICU_VERSION);
System.out.println("JDK Version " + System.getProperty("java.version"));
for (int i = 0; i < pairs.length; i += 2) {
try {
compare(pairClasses[i], pairClasses[i+1]);
}
catch (Exception e) {
System.out.println("exception: " + e);
System.out.println("between " + pairs[i] + " and " + pairs[i+1]);
e.printStackTrace();
}
}
}
static class MorC {
private Method mref;
private Constructor cref;
MorC(Method m) {
mref = m;
}
MorC(Constructor c) {
cref = c;
}
int getModifiers() {
return mref == null ? cref.getModifiers() : mref.getModifiers();
}
Class getReturnType() {
return mref == null ? void.class : mref.getReturnType();
}
Class[] getParameterTypes() {
return mref == null ? cref.getParameterTypes() : mref.getParameterTypes();
}
String getName() {
return mref == null ? "<init>" : mref.getName();
}
}
static void compare(Class class1, Class class2) throws Exception {
String n1 = class1.getName();
String n2 = class2.getName();
MorC[] conss1 = getMorCArray(class1.getConstructors());
MorC[] conss2 = getMorCArray(class2.getConstructors());
Map cmap1 = getMethodMap(conss1);
Map cmap2 = getMethodMap(conss2);
MorC[] meths1 = getMorCArray(class1.getMethods());
MorC[] meths2 = getMorCArray(class2.getMethods());
Map map1 = getMethodMap(meths1);
Map map2 = getMethodMap(meths2);
Field[] fields1 = class1.getFields();
Field[] fields2 = class2.getFields();
Set set1 = getFieldSet(fields1);
Set set2 = getFieldSet(fields2);
PrintWriter pw = new PrintWriter(System.out);
Map diffConss = diffMethodMaps(cmap2, cmap1);
Map diffMeths = diffMethodMaps(map2, map1);
Set diffFields = diffFieldSets(set2, set1);
if (diffConss.size() + diffMeths.size() + diffFields.size() > 0) {
pw.println("\n============\nAPI in " + n2 + " missing from " + n1);
if (diffConss.size() > 0) {
pw.println("\nCONSTRUCTORS");
dumpMethodMap(diffConss, pw);
}
if (diffMeths.size() > 0) {
pw.println("\nMETHODS");
dumpMethodMap(diffMeths, pw);
}
if (diffFields.size() > 0) {
pw.println("\nFIELDS");
dumpFieldSet(diffFields, pw);
}
} else {
pw.println("\n" + n1 + " OK");
}
}
static final class MethodRecord {
MorC[] overrides;
MethodRecord(MorC m) {
overrides = new MorC[] { m };
}
MethodRecord(MorC[] ms) {
overrides = ms;
}
MethodRecord copy() {
return new MethodRecord((MorC[])overrides.clone());
}
int count() {
for (int i = 0; i < overrides.length; ++i) {
if (overrides[i] == null) {
return i;
}
}
return overrides.length;
}
void add(MorC m) {
MorC[] temp = new MorC[overrides.length + 1];
for (int i = 0; i < overrides.length; ++i) {
temp[i] = overrides[i];
}
temp[overrides.length] = m;
overrides = temp;
}
void remove(int index) {
int i = index;
while (overrides[i] != null && i < overrides.length-1) {
overrides[i] = overrides[i+1];
++i;
}
overrides[i] = null;
}
// if a call to a method can be handled by a call to t, remove the
// method from our list, and return true
boolean removeOverridden(MorC t) {
boolean result = false;
int i = 0;
while (i < overrides.length) {
MorC m = overrides[i];
if (m == null) {
break;
}
if (handles(t, m)) {
remove(i);
result = true;
} else {
++i;
}
}
return result;
}
// remove all methods handled by any method of mr
boolean removeOverridden(MethodRecord mr) {
boolean result = false;
for (int i = 0; i < mr.overrides.length; ++i) {
MorC t = mr.overrides[i];
if (t == null) {
// this shouldn't happen, as the target record should not have been modified
throw new InternalError();
}
if (removeOverridden(t)) {
result = true;
}
}
return result;
}
void msg(MorC t, MorC m, String msg) {
StringBuffer buf = new StringBuffer();
buf.append(t.getName());
buf.append(" ");
buf.append(msg);
buf.append("\n ");
toString(t, buf);
buf.append("\n ");
toString(m, buf);
System.out.println(buf.toString());
}
boolean handles(MorC t, MorC m) {
// relevant modifiers must match
if ((t.getModifiers() & MOD_MASK) != (m.getModifiers() & MOD_MASK)) {
if (DEBUG) msg(t, m, "modifier mismatch");
return false;
}
Class tr = pairClassEquivalent(t.getReturnType());
Class mr = pairClassEquivalent(m.getReturnType());
if (!assignableFrom(mr, tr)) { // t return type must be same or narrower than m
if (DEBUG) msg(t, m, "return value mismatch");
return false;
}
Class[] tts = t.getParameterTypes();
Class[] mts = m.getParameterTypes();
if (tts.length != mts.length) {
if (DEBUG) msg(t, m, "param count mismatch");
return false;
}
for (int i = 0; i < tts.length; ++i) {
Class tc = pairClassEquivalent(tts[i]);
Class mc = pairClassEquivalent(mts[i]);
if (!assignableFrom(tc, mc)) { // m param must be same or narrower than t
if (DEBUG) msg(t, m, "parameter " + i + " mismatch, " +
tts[i].getName() + " not assignable from " + mts[i].getName());
return false;
}
}
return true;
}
public void toString(MorC m, StringBuffer buf) {
int mod = m.getModifiers();
if (mod != 0) {
buf.append(Modifier.toString(mod) + " ");
}
buf.append(m.getReturnType().getName() + " (");
Class[] ptypes = m.getParameterTypes();
for (int j = 0; j < ptypes.length; ++j) {
if (j > 0) {
buf.append(", ");
}
buf.append(ptypes[j].getName());
}
buf.append(')');
}
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append(overrides[0].getName());
for (int i = 0; i < overrides.length; ++i) {
MorC m = overrides[i];
if (m == null) {
break;
}
buf.append("\n ");
toString(m, buf);
}
return buf.toString();
}
}
static MorC[] getMorCArray(Constructor[] cons) {
MorC[] result = new MorC[cons.length];
for (int i = 0 ; i < cons.length; ++i) {
result[i] = new MorC(cons[i]);
}
return result;
}
static MorC[] getMorCArray(Method[] meths) {
MorC[] result = new MorC[meths.length];
for (int i = 0 ; i < meths.length; ++i) {
result[i] = new MorC(meths[i]);
}
return result;
}
static Map getMethodMap(MorC[] meths) {
Map result = new TreeMap();
for (int i = 0; i < meths.length; ++i) {
MorC m = meths[i];
String key = m.getName();
MethodRecord mr = (MethodRecord)result.get(key);
if (mr == null) {
mr = new MethodRecord(m);
result.put(key, mr);
} else {
mr.add(m);
}
}
return result;
}
static void dumpMethodMap(Map m, PrintWriter out) {
Iterator iter = m.entrySet().iterator();
while (iter.hasNext()) {
MethodRecord mr = (MethodRecord)((Map.Entry)iter.next()).getValue();
out.println(mr);
}
out.flush();
}
static Map diffMethodMaps(Map m1, Map m2) {
// get all the methods in m1 that aren't mentioned in m2 at all
Map result = (Map)((TreeMap)m1).clone();
result.keySet().removeAll(m2.keySet());
return result;
}
static final boolean[][] assignmentMap = {
// bool char byte short int long float double void
{ true, false, false, false, false, false, false, false, false }, // boolean
{ false, true, true, true, false, false, false, false, false }, // char
{ false, false, true, false, false, false, false, false, false }, // byte
{ false, false, true, true, false, false, false, false, false }, // short
{ false, true, true, true, true, false, false, false, false }, // int
{ false, true, true, true, true, true, false, false, false }, // long
{ false, true, true, true, true, false, true, false, false }, // float
{ false, true, true, true, true, false, true, true, false }, // double
{ false, false, false, false, false, false, false, false, true }, // void
};
static final Class[] prims = {
boolean.class, char.class, byte.class, short.class,
int.class, long.class, float.class, double.class, void.class
};
static int primIndex(Class cls) {
for (int i = 0; i < prims.length; ++i) {
if (cls == prims[i]) {
return i;
}
}
throw new InternalError("could not find primitive class: " + cls);
}
static boolean assignableFrom(Class lhs, Class rhs) {
if (lhs == rhs) {
return true;
}
if (lhs.isPrimitive()) {
if (!rhs.isPrimitive()) {
return false;
}
int lhsx = primIndex(lhs);
int rhsx = primIndex(rhs);
return assignmentMap[lhsx][rhsx];
}
return lhs.isAssignableFrom(rhs);
}
static String toString(Field f) {
StringBuffer buf = new StringBuffer(f.getName());
int mod = f.getModifiers() & MOD_MASK;
if (mod != 0) {
buf.append(" " + Modifier.toString(mod));
}
buf.append(" " + pairEquivalent(f.getType().getName()));
return buf.toString();
}
static Set getFieldSet(Field[] fs) {
Set set = new TreeSet();
for (int i = 0; i < fs.length; ++i) {
set.add(toString(fs[i]));
}
return set;
}
static Set diffFieldSets(Set s1, Set s2) {
Set result = (Set)((TreeSet)s1).clone();
result.removeAll(s2);
return result;
}
static void dumpFieldSet(Set s, PrintWriter pw) {
Iterator iter = s.iterator();
while (iter.hasNext()) {
pw.println(iter.next());
}
pw.flush();
}
// given a target string, if it matches the first of one of our pairs, return the second
static String pairEquivalent(String target) {
for (int i = 0; i < pairs.length; i += 2) {
if (target.equals(pairs[i])) {
return pairs[i+1];
}
}
return target;
}
static Class pairClassEquivalent(Class target) {
for (int i = 0; i < pairClasses.length; i += 2) {
if (target.equals(pairClasses[i])) {
return pairClasses[i+1];
}
}
return target;
}
static final int MOD_MASK = ~(Modifier.FINAL|Modifier.SYNCHRONIZED|
Modifier.VOLATILE|Modifier.TRANSIENT|Modifier.NATIVE);
}