Home | History | Annotate | Download | only in docs
      1 /*
      2  *******************************************************************************
      3  * Copyright (C) 2005-2012, International Business Machines Corporation and    *
      4  * others. All Rights Reserved.                                                *
      5  *******************************************************************************
      6  *
      7  */
      8 
      9 package com.ibm.icu.dev.tool.docs;
     10 
     11 import java.io.BufferedReader;
     12 import java.io.File;
     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;
     22 import java.util.Map;
     23 import java.util.Set;
     24 import java.util.TreeMap;
     25 import java.util.TreeSet;
     26 
     27 /**
     28  * Compare ICU4J and JDK APIS.
     29  *
     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.
     34  */
     35 public class ICUJDKCompare {
     36     static final boolean DEBUG = false;
     37 
     38     // set up defaults
     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",
     44         "text.BreakIterator",
     45         "text.Collator",
     46         "text.DateFormat",
     47         "text.DateFormatSymbols",
     48         "text.DecimalFormat",
     49         "text.DecimalFormatSymbols",
     50         "text.Format/UFormat",
     51         "text.MessageFormat",
     52         "text.NumberFormat",
     53         "text.SimpleDateFormat",
     54         "util.Calendar",
     55         "util.Currency",
     56         "util.GregorianCalendar",
     57         "util.SimpleTimeZone",
     58         "util.TimeZone",
     59         "util.Locale/ULocale",
     60         "util.ResourceBundle/UResourceBundle",
     61     };
     62 
     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",
     69     };
     70 
     71     private PrintWriter pw;
     72     private String srcPrefix;
     73     private String trgPrefix;
     74     private Class[] classPairs;
     75     private String[] namePairs;
     76     private String[] ignore;
     77     private boolean swap;
     78     //private boolean signature;
     79 
     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));
     83     }
     84 
     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));
     89         p.setup(args);
     90         return p.process();
     91     }
     92 
     93     // setters
     94     public ICUJDKCompare setOutputWriter(PrintWriter pw) {
     95         this.pw = pw;
     96         return this;
     97     }
     98 
     99     public ICUJDKCompare setSrcPrefix(String srcPrefix) {
    100         this.srcPrefix = srcPrefix;
    101         return this;
    102     }
    103 
    104     public ICUJDKCompare setTrgPrefix(String trgPrefix) {
    105         this.trgPrefix = trgPrefix;
    106         return this;
    107     }
    108 
    109     public ICUJDKCompare setClassPairs(Class[] classPairs) {
    110         this.classPairs = classPairs;
    111         return this;
    112     }
    113 
    114     public ICUJDKCompare setNamePairs(String[] namePairs) {
    115         this.namePairs = namePairs;
    116         return this;
    117     }
    118 
    119     public ICUJDKCompare setIgnore(String[] ignore) {
    120         this.ignore = ignore;
    121         return this;
    122     }
    123 
    124     public ICUJDKCompare setSwap(boolean swap) {
    125         this.swap = swap;
    126         return this;
    127     }
    128 
    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")) {
    135                 swap = true;
    136             } else if (arg.equals("-srcPrefix:")) {
    137                 srcPrefix = args[++i];
    138                 if (!srcPrefix.endsWith(".")) {
    139                     srcPrefix += '.';
    140                 }
    141             } else if (arg.equals("-trgPrefix:")) {
    142                 trgPrefix = args[++i];
    143                 if (!trgPrefix.endsWith(".")) {
    144                     trgPrefix += '.';
    145                 }
    146             } else if (arg.equals("-names:")) {
    147                 namelist = args[++i];
    148             } else if (arg.equals("-ignore:")) {
    149                 ignorelist = args[++i];
    150             } else {
    151                 System.err.println("unrecognized argument: " + arg);
    152                 throw new IllegalStateException();
    153             }
    154         }
    155 
    156         if (ignorelist != null) {
    157             if (ignorelist.charAt(0) == '@') { // a file containing ignoreinfo
    158                 BufferedReader br = null;
    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                     br = new BufferedReader(isr);
    165                     String line = null;
    166                     while (null != (line = br.readLine())) {
    167                         nl.add(line);
    168                     }
    169                     ignore = (String[])nl.toArray(new String[nl.size()]);
    170                 }
    171                 catch (Exception e) {
    172                     System.err.println(e);
    173                     throw new IllegalStateException();
    174                 }
    175                 finally {
    176                     if (br != null) {
    177                         try {
    178                             br.close();
    179                         } catch (Exception e) {
    180                             // ignore
    181                         }
    182                     }
    183                 }
    184             } else { // a list of ignoreinfo separated by semicolons
    185                 ignore = ignorelist.split("\\s*;\\s*");
    186             }
    187         }
    188 
    189         if (namelist != null) {
    190             String[] names = null;
    191             if (namelist.charAt(0) == '@') { // a file
    192                 BufferedReader br = null;
    193                 try {
    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);
    199                     String line = null;
    200                     while (null != (line = br.readLine())) {
    201                         nl.add(line);
    202                     }
    203                     names = (String[])nl.toArray(new String[nl.size()]);
    204                 }
    205                 catch (Exception e) {
    206                     System.err.println(e);
    207                     throw new IllegalStateException();
    208                 } finally {
    209                     if (br != null) {
    210                         try {
    211                             br.close();
    212                         } catch (Exception e) {
    213                             // ignore
    214                         }
    215                     }
    216                 }
    217 
    218             } else { // a list of names separated by semicolons
    219                 names = namelist.split("\\s*;\\s*");
    220             }
    221             processPairInfo(names);
    222         }
    223 
    224         pw.flush();
    225 
    226         return this;
    227     }
    228 
    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;
    236 
    237             int n = name.indexOf('/');
    238             if (n == -1) {
    239                 srcName += name;
    240                 trgName += name;
    241             } else {
    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;
    246                 while (ix != -1) {
    247                     jx = srcSuffix.lastIndexOf('.', jx-1);
    248                     ix = trgSuffix.lastIndexOf('.', ix-1);
    249                 }
    250                 srcName += srcSuffix;
    251                 trgName += srcSuffix.substring(0, jx+1) + trgSuffix;
    252             }
    253 
    254             try {
    255                 Class jc = Class.forName(srcName);
    256                 Class ic = Class.forName(trgName);
    257                 cl.add(ic);
    258                 cl.add(jc);
    259                 nl.add(ic.getName());
    260                 nl.add(jc.getName());
    261             }
    262             catch (Exception e) {
    263                 if (DEBUG) System.err.println("can't load class: " + e.getMessage());
    264             }
    265         }
    266         classPairs = (Class[])cl.toArray(new Class[cl.size()]);
    267         namePairs = (String[])nl.toArray(new String[nl.size()]);
    268     }
    269 
    270     private void println(String s) {
    271         if (pw != null) pw.println(s);
    272     }
    273 
    274     private void flush() {
    275         if (pw != null) pw.flush();
    276     }
    277 
    278     public int process() {
    279         // set defaults
    280         if (srcPrefix == null) {
    281             srcPrefix = kSrcPrefix;
    282         }
    283 
    284         if (trgPrefix == null) {
    285             trgPrefix = kTrgPrefix;
    286         }
    287 
    288         if (classPairs == null) {
    289             processPairInfo(kPairInfo);
    290         }
    291 
    292         if (ignore == null) {
    293             ignore = kIgnore;
    294         }
    295 
    296         println("ICU and Java API Comparison");
    297         String ICU_VERSION = "unknown";
    298         try {
    299             Class cls = Class.forName("com.ibm.icu.util.VersionInfo");
    300             Field fld = cls.getField("ICU_VERSION");
    301             ICU_VERSION = fld.get(null).toString();
    302         }
    303         catch (Exception e) {
    304             if (DEBUG) System.err.println("can't get VersionInfo: " + e.getMessage());
    305         }
    306         println("ICU Version " + ICU_VERSION);
    307         println("JDK Version " + System.getProperty("java.version"));
    308 
    309         int errorCount = 0;
    310         for (int i = 0; i < classPairs.length; i += 2) {
    311             try {
    312                 if (swap) {
    313                     errorCount += compare(classPairs[i+1], classPairs[i]);
    314                 } else {
    315                     errorCount += compare(classPairs[i], classPairs[i+1]);
    316                 }
    317             }
    318             catch (Exception e) {
    319                 System.err.println("exception: " + e);
    320                 System.err.println("between " + namePairs[i] + " and " + namePairs[i+1]);
    321                 e.printStackTrace();
    322                 errorCount += 1;
    323             }
    324         }
    325         return errorCount;
    326     }
    327 
    328     static class MorC {
    329         private Method mref;
    330         private Constructor cref;
    331 
    332         MorC(Method m) {
    333             mref = m;
    334         }
    335 
    336         MorC(Constructor c) {
    337             cref = c;
    338         }
    339 
    340         int getModifiers() {
    341             return mref == null ? cref.getModifiers() : mref.getModifiers();
    342         }
    343 
    344         Class getReturnType() {
    345             return mref == null ? void.class : mref.getReturnType();
    346         }
    347 
    348         Class[] getParameterTypes() {
    349             return mref == null ? cref.getParameterTypes() : mref.getParameterTypes();
    350         }
    351 
    352         String getName() {
    353             return mref == null ? "<init>" : mref.getName();
    354         }
    355 
    356         String getSignature() {
    357             return mref == null ? cref.toString() : mref.toString();
    358         }
    359     }
    360 
    361     private int compare(Class class1, Class class2) throws Exception {
    362         String n1 = class1.getName();
    363         String n2 = class2.getName();
    364 
    365         println("\ncompare " + n1 + " <> " + n2);
    366 
    367         MorC[] conss1 = getMorCArray(class1.getConstructors());
    368         MorC[] conss2 = getMorCArray(class2.getConstructors());
    369 
    370         Map cmap1 = getMethodMap(conss1);
    371         Map cmap2 = getMethodMap(conss2);
    372 
    373         MorC[] meths1 = getMorCArray(class1.getMethods());
    374         MorC[] meths2 = getMorCArray(class2.getMethods());
    375 
    376         Map map1 = getMethodMap(meths1);
    377         Map map2 = getMethodMap(meths2);
    378 
    379         Field[] fields1 = class1.getFields();
    380         Field[] fields2 = class2.getFields();
    381 
    382         Set set1 = getFieldSet(fields1);
    383         Set set2 = getFieldSet(fields2);
    384 
    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);
    388         }
    389 
    390         Map diffConss = diffMethodMaps(cmap2, cmap1);
    391         Map diffMeths = diffMethodMaps(map2, map1);
    392         Set diffFields = diffFieldSets(set2, set1);
    393 
    394         diffConss = removeIgnored(n2, diffConss);
    395         diffMeths = removeIgnored(n2, diffMeths);
    396         diffFields = removeIgnored(n2, diffFields);
    397 
    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);
    404             }
    405             if (diffMeths.size() > 0) {
    406                 pw.println("METHODS");
    407                 dumpMethodMap(diffMeths, pw);
    408             }
    409             if (diffFields.size() > 0) {
    410                 pw.println("FIELDS");
    411                 dumpFieldSet(diffFields, pw);
    412             }
    413         }
    414 
    415         flush();
    416 
    417         return result;
    418     }
    419 
    420     final class MethodRecord {
    421         MorC[] overrides;
    422 
    423         MethodRecord(MorC m) {
    424             overrides = new MorC[] { m };
    425         }
    426 
    427         MethodRecord(MorC[] ms) {
    428             overrides = ms;
    429         }
    430 
    431         MethodRecord copy() {
    432             return new MethodRecord((MorC[])overrides.clone());
    433         }
    434 
    435         int count() {
    436             for (int i = 0; i < overrides.length; ++i) {
    437                 if (overrides[i] == null) {
    438                     return i;
    439                 }
    440             }
    441             return overrides.length;
    442         }
    443 
    444         void add(MorC m) {
    445             MorC[] temp = new MorC[overrides.length + 1];
    446             for (int i = 0; i < overrides.length; ++i) {
    447                 temp[i] = overrides[i];
    448             }
    449             temp[overrides.length] = m;
    450             overrides = temp;
    451         }
    452 
    453         void remove(int index) {
    454             int i = index;
    455             while (overrides[i] != null && i < overrides.length-1) {
    456                 overrides[i] = overrides[i+1];
    457                 ++i;
    458             }
    459             overrides[i] = null;
    460         }
    461 
    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;
    466             int i = 0;
    467             while (i < overrides.length) {
    468                 MorC m = overrides[i];
    469                 if (m == null) {
    470                     break;
    471                 }
    472                 if (handles(t, m)) {
    473                     remove(i);
    474                     result = true;
    475                 } else {
    476                     ++i;
    477                 }
    478             }
    479             return result;
    480         }
    481 
    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];
    487                 if (t == null) {
    488                     // this shouldn't happen, as the target record should not have been modified
    489                     throw new IllegalStateException();
    490                 }
    491                 if (removeOverridden(t)) {
    492                     result = true;
    493                 }
    494             }
    495             return result;
    496         }
    497 
    498         void debugmsg(MorC t, MorC m, String msg) {
    499             StringBuffer buf = new StringBuffer();
    500             buf.append(t.getName());
    501             buf.append(" ");
    502             buf.append(msg);
    503             buf.append("\n   ");
    504             toString(t, buf);
    505             buf.append("\n   ");
    506             toString(m, buf);
    507             System.out.println(buf.toString());
    508         }
    509 
    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");
    514                 return false;
    515             }
    516 
    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");
    521                 return false;
    522             }
    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");
    527                 return false;
    528             }
    529 
    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());
    536                     return false;
    537                 }
    538             }
    539             return true;
    540         }
    541 
    542         public void toString(MorC m, StringBuffer buf) {
    543             int mod = m.getModifiers();
    544             if (mod != 0) {
    545                 buf.append(Modifier.toString(mod) + " ");
    546             }
    547             buf.append(nameOf(m.getReturnType()));
    548             buf.append(" ");
    549             buf.append(m.getName());
    550             buf.append("(");
    551             Class[] ptypes = m.getParameterTypes();
    552             for (int j = 0; j < ptypes.length; ++j) {
    553                 if (j > 0) {
    554                     buf.append(", ");
    555                 }
    556                 buf.append(nameOf(ptypes[j]));
    557             }
    558             buf.append(')');
    559         }
    560 
    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];
    566                 if (m == null) {
    567                     break;
    568                 }
    569                 buf.append("\n   ");
    570                 toString(m, buf);
    571             }
    572             return buf.toString();
    573         }
    574     }
    575 
    576     public static String nameOf(Class c) {
    577         if (c.isArray()) {
    578             return nameOf(c.getComponentType()) + "[]";
    579         }
    580         String name = c.getName();
    581         return name.substring(name.lastIndexOf('.') + 1);
    582     }
    583 
    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]);
    588         }
    589         return result;
    590     }
    591 
    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]);
    596         }
    597         return result;
    598     }
    599 
    600     private Map getMethodMap(MorC[] meths) {
    601         Map result = new TreeMap();
    602         for (int i = 0; i < meths.length; ++i) {
    603             MorC m = meths[i];
    604             String key = m.getName();
    605             MethodRecord mr = (MethodRecord)result.get(key);
    606             if (mr == null) {
    607                 mr = new MethodRecord(m);
    608                 result.put(key, mr);
    609             } else {
    610                 mr.add(m);
    611             }
    612         }
    613         return result;
    614     }
    615 
    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());
    620         }
    621         pw.flush();
    622     }
    623 
    624     private void dumpMethodRecord(MethodRecord mr) {
    625         pw.println(mr.toString());
    626     }
    627 
    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());
    632         return result;
    633     }
    634 
    635     private Map removeIgnored(String name, Map m1) {
    636         if (ignore == null) {
    637             return m1;
    638         }
    639         if (name.startsWith(srcPrefix)) {
    640             name = name.substring(srcPrefix.length());
    641         }
    642         name += " "; // to avoid accidental prefix of nested class name
    643 
    644         // prune ignore list to relevant items
    645         ArrayList il = null;
    646         for (int i = 0; i < ignore.length; ++i) {
    647             String s = ignore[i];
    648             if (s.startsWith(name)) {
    649                 if (il == null) {
    650                     il = new ArrayList();
    651                 }
    652                 il.add(s);
    653             }
    654         }
    655         if (il == null) {
    656             return m1;
    657         }
    658 
    659         Map result = new TreeMap(((TreeMap)m1).comparator());
    660         result.putAll(m1);
    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) {
    668                     iter.remove();
    669                     continue loop;
    670                 }
    671             }
    672         }
    673         return result;
    674     }
    675 
    676     private Set removeIgnored(String name, Set s1) {
    677         if (ignore == null) {
    678             return s1;
    679         }
    680         if (name.startsWith(srcPrefix)) {
    681             name = name.substring(srcPrefix.length());
    682         }
    683         name += " "; // to avoid accidental prefix of nested class name
    684 
    685         // prune ignore list to relevant items
    686         ArrayList il = null;
    687         for (int i = 0; i < ignore.length; ++i) {
    688             String s = ignore[i];
    689             if (s.startsWith(name)) {
    690                 if (il == null) {
    691                     il = new ArrayList();
    692                 }
    693                 il.add(s);
    694             }
    695         }
    696         if (il == null) {
    697             return s1;
    698         }
    699 
    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) {
    708                     iter.remove();
    709                     continue loop;
    710                 }
    711             }
    712         }
    713         return result;
    714     }
    715 
    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
    727     };
    728 
    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
    732     };
    733 
    734     static int primIndex(Class cls) {
    735         for (int i = 0; i < prims.length; ++i) {
    736             if (cls == prims[i]) {
    737                 return i;
    738             }
    739         }
    740         throw new IllegalStateException("could not find primitive class: " + cls);
    741     }
    742 
    743     static boolean assignableFrom(Class lhs, Class rhs) {
    744         if (lhs == rhs) {
    745             return true;
    746         }
    747         if (lhs.isPrimitive()) {
    748             if (!rhs.isPrimitive()) {
    749                 return false;
    750             }
    751             int lhsx = primIndex(lhs);
    752             int rhsx = primIndex(rhs);
    753             return assignmentMap[lhsx][rhsx];
    754         }
    755         return lhs.isAssignableFrom(rhs);
    756     }
    757 
    758     private String toString(Field f) {
    759         StringBuffer buf = new StringBuffer(f.getName());
    760         int mod = f.getModifiers() & MOD_MASK;
    761         if (mod != 0) {
    762             buf.append(" " + Modifier.toString(mod));
    763         }
    764         buf.append(" ");
    765         String n = pairEquivalent(f.getType().getName());
    766         n = n.substring(n.lastIndexOf('.') + 1);
    767         buf.append(n);
    768         return buf.toString();
    769     }
    770 
    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]));
    775         }
    776         return set;
    777     }
    778 
    779     static Set diffFieldSets(Set s1, Set s2) {
    780         Set result = (Set)((TreeSet)s1).clone();
    781         result.removeAll(s2);
    782         return result;
    783     }
    784 
    785     private void dumpFieldSet(Set s, PrintWriter pw) {
    786         Iterator iter = s.iterator();
    787         while (iter.hasNext()) {
    788             pw.println(iter.next());
    789         }
    790         pw.flush();
    791     }
    792 
    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) {
    797             if (swap) {
    798                 if (target.equals(namePairs[i+1])) {
    799                     return namePairs[i];
    800                 }
    801             } else {
    802                 if (target.equals(namePairs[i])) {
    803                     return namePairs[i+1];
    804                 }
    805             }
    806         }
    807         return target;
    808     }
    809 
    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];
    814             }
    815         }
    816         return target;
    817     }
    818 
    819     static final int MOD_MASK = ~(Modifier.FINAL|Modifier.SYNCHRONIZED|
    820                                   Modifier.VOLATILE|Modifier.TRANSIENT|Modifier.NATIVE);
    821 }
    822