Home | History | Annotate | Download | only in jdiff
      1 package jdiff;
      2 
      3 import java.io.*;
      4 import java.util.*;
      5 import javax.xml.parsers.ParserConfigurationException;
      6 
      7 /* For SAX parsing in APIHandler */
      8 import org.xml.sax.Attributes;
      9 import org.xml.sax.SAXException;
     10 import org.xml.sax.SAXParseException;
     11 import org.xml.sax.XMLReader;
     12 import org.xml.sax.InputSource;
     13 import org.xml.sax.helpers.*;
     14 
     15 /**
     16  * Creates an API object from an XML file. The API object is the internal
     17  * representation of an API.
     18  * All methods in this class for populating an API object are static.
     19  *
     20  * See the file LICENSE.txt for copyright details.
     21  * @author Matthew Doar, mdoar (at) pobox.com
     22  */
     23 public class XMLToAPI {
     24 
     25     /** The instance of the API object which is populated from the file. */
     26     private static API api_ = null;
     27 
     28     /** Default constructor. */
     29     private XMLToAPI() {
     30     }
     31 
     32     /**
     33      * Read the file where the XML representing the API is stored.
     34      *
     35      * @param filename The full name of the file containing the XML
     36      *                 representing the API
     37      * @param createGlobalComments If set, then store possible comments
     38      * @param apiName The simple name of the API file. If -oldapidir and
     39      *                -newapidir are not used, then this is the same as
     40      *                the filename parameter
     41      */
     42     public static API readFile(String filename, boolean createGlobalComments,
     43             String apiName) {
     44         // The instance of the API object which is populated from the file.
     45         api_ = new API();
     46         api_.name_ = apiName; // Checked later
     47         try {
     48             XMLReader parser = null;
     49             DefaultHandler handler = new APIHandler(api_, createGlobalComments);
     50             try {
     51                 parser = javax.xml.parsers.SAXParserFactory.newInstance().newSAXParser().getXMLReader();
     52             } catch (SAXException saxe) {
     53                 System.out.println("SAXException: " + saxe);
     54                 saxe.printStackTrace();
     55                 System.exit(1);
     56             } catch (ParserConfigurationException pce) {
     57                 System.out.println("ParserConfigurationException: " + pce);
     58                 pce.printStackTrace();
     59                 System.exit(1);
     60             }
     61 
     62             if (validateXML) {
     63                 parser.setFeature("https://xml.org/sax/features/namespaces", true);
     64                 parser.setFeature("https://xml.org/sax/features/validation", true);
     65                 parser.setFeature("https://apache.org/xml/features/validation/schema", true);
     66             }
     67 
     68             parser.setContentHandler(handler);
     69             parser.setErrorHandler(handler);
     70             parser.parse(new InputSource(new FileInputStream(new File(filename))));
     71         } catch(org.xml.sax.SAXNotRecognizedException snre) {
     72             System.out.println("SAX Parser does not recognize feature: " + snre);
     73             snre.printStackTrace();
     74             System.exit(1);
     75         } catch(org.xml.sax.SAXNotSupportedException snse) {
     76             System.out.println("SAX Parser feature is not supported: " + snse);
     77             snse.printStackTrace();
     78             System.exit(1);
     79         } catch(org.xml.sax.SAXException saxe) {
     80             System.out.println("SAX Exception parsing file '" + filename + "' : " + saxe);
     81             saxe.printStackTrace();
     82             System.exit(1);
     83         } catch(java.io.IOException ioe) {
     84             System.out.println("IOException parsing file '" + filename + "' : " + ioe);
     85             ioe.printStackTrace();
     86             System.exit(1);
     87         }
     88 
     89         // Add the inherited methods and fields to each class
     90         addInheritedElements();
     91         return api_;
     92     } //readFile()
     93 
     94     /**
     95      * Add the inherited methods and fields to each class in turn.
     96      */
     97     public static void addInheritedElements() {
     98         Iterator iter = api_.packages_.iterator();
     99         while (iter.hasNext()) {
    100             PackageAPI pkg = (PackageAPI)(iter.next());
    101             Iterator iter2 = pkg.classes_.iterator();
    102             while (iter2.hasNext()) {
    103                 ClassAPI cls = (ClassAPI)(iter2.next());
    104                 // Look up any inherited classes or interfaces
    105                 if (cls.extends_ != null) {
    106                     ClassAPI parent = (ClassAPI)api_.classes_.get(cls.extends_);
    107                     if (parent != null)
    108                         addInheritedElements(cls, parent, cls.extends_);
    109                 }
    110                 if (cls.implements_.size() != 0) {
    111                     Iterator iter3 = cls.implements_.iterator();
    112                     while (iter3.hasNext()) {
    113                         String implName = (String)(iter3.next());
    114                         ClassAPI parent = (ClassAPI)api_.classes_.get(implName);
    115                         if (parent != null)
    116                             addInheritedElements(cls, parent, implName);
    117                     }
    118                 }
    119             } //while (iter2.hasNext())
    120         } //while (iter.hasNext())
    121     }
    122 
    123     /**
    124      * Add all the inherited methods and fields in the second class to
    125      * the first class, marking them as inherited from the second class.
    126      * Do not add a method or a field if it is already defined locally.
    127      *
    128      * Only elements at the specified visibility level or
    129      * higher appear in the XML file. All that remains to be tested for
    130      * a private element, which is never inherited.
    131      *
    132      * If the parent class inherits any classes or interfaces, call this
    133      * method recursively with those parents.
    134      */
    135     public static void addInheritedElements(ClassAPI child, ClassAPI parent,
    136                                             String fqParentName) {
    137         if (parent.methods_.size() != 0) {
    138             Iterator iter = parent.methods_.iterator();
    139             while (iter.hasNext()) {
    140                 MethodAPI m = (MethodAPI)(iter.next());
    141                 // See if it the method is overridden locally
    142                 boolean overridden = false;
    143                 Iterator iter2 = child.methods_.iterator();
    144                 while (iter2.hasNext()) {
    145                     MethodAPI localM = (MethodAPI)(iter2.next());
    146                     if (localM.name_.compareTo(m.name_) == 0 &&
    147                         localM.getSignature().compareTo(m.getSignature()) == 0)
    148                         overridden = true;
    149                 }
    150                 if (!overridden && m.inheritedFrom_ == null &&
    151                     m.modifiers_.visibility != null &&
    152                     m.modifiers_.visibility.compareTo("private") != 0) {
    153                     MethodAPI m2 = new MethodAPI(m);
    154                     m2.inheritedFrom_ = fqParentName;
    155                     child.methods_.add(m2);
    156                 }
    157             }
    158         }
    159         if (parent.fields_.size() != 0) {
    160             Iterator iter = parent.fields_.iterator();
    161             while (iter.hasNext()) {
    162                 FieldAPI f = (FieldAPI)(iter.next());
    163                 if (child.fields_.indexOf(f) == -1 &&
    164                     f.inheritedFrom_ == null &&
    165                     f.modifiers_.visibility != null &&
    166                     f.modifiers_.visibility.compareTo("private") != 0) {
    167                     FieldAPI f2 = new FieldAPI(f);
    168                     f2.inheritedFrom_ = fqParentName;
    169                     child.fields_.add(f2);
    170                 }
    171             }
    172         }
    173 
    174         // Look up any inherited classes or interfaces
    175         if (parent.extends_ != null) {
    176             ClassAPI parent2 = (ClassAPI)api_.classes_.get(parent.extends_);
    177             if (parent2 != null)
    178                 addInheritedElements(child, parent2, parent.extends_);
    179         }
    180         if (parent.implements_.size() != 0) {
    181             Iterator iter3 = parent.implements_.iterator();
    182             while (iter3.hasNext()) {
    183                 String implName = (String)(iter3.next());
    184                 ClassAPI parent2 = (ClassAPI)api_.classes_.get(implName);
    185                 if (parent2 != null)
    186                     addInheritedElements(child, parent2, implName);
    187             }
    188         }
    189     }
    190 
    191 //
    192 // Methods to add data to an API object. Called by the XML parser.
    193 //
    194 
    195     /**
    196      * Set the name of the API object.
    197      *
    198      * @param name The name of the package.
    199      */
    200     public static void nameAPI(String name) {
    201         if (name == null) {
    202             System.out.println("Warning: no API identifier found in the XML file '" + api_.name_ + "'");
    203             String filename = api_.name_.substring(0, api_.name_.lastIndexOf(".xml"));
    204             // System.out.println(" api level:" + filename);
    205             System.out.println("Using '" + filename + "' as the API identifier.");
    206             api_.name_ = filename;
    207             // System.exit(3);
    208             return;
    209         }
    210         // Check the given name against the filename currently stored in
    211         // the name_ field
    212         String filename2 = name.replace(' ','_');
    213         filename2 += ".xml";
    214         if (filename2.compareTo(api_.name_) != 0) {
    215             System.out.println("Warning: API identifier in the XML file (" +
    216                                name + ") differs from the name of the file '" +
    217                                api_.name_ + "'");
    218         }
    219         api_.name_ = name;
    220     }
    221 
    222     /**
    223      * Create a new package and add it to the API. Called by the XML parser.
    224      *
    225      * @param name The name of the package.
    226      */
    227     public static void addPackage(String name) {
    228         api_.currPkg_ = new PackageAPI(name);
    229         api_.packages_.add(api_.currPkg_);
    230     }
    231 
    232     /**
    233      * Create a new class and add it to the current package. Called by the XML parser.
    234      *
    235      * @param name The name of the class.
    236      * @param parent The name of the parent class, null if no class is extended.
    237      * @param modifiers Modifiers for this class.
    238      */
    239     public static void addClass(String name, String parent,
    240                                 boolean isAbstract,
    241                                 Modifiers modifiers) {
    242         api_.currClass_ = new ClassAPI(name, parent, false, isAbstract, modifiers);
    243         api_.currPkg_.classes_.add(api_.currClass_);
    244         String fqName = api_.currPkg_.name_ + "." + name;
    245         ClassAPI caOld = (ClassAPI)api_.classes_.put(fqName, api_.currClass_);
    246         if (caOld != null) {
    247             System.out.println("Warning: duplicate class : " + fqName + " found. Using the first instance only.");
    248         }
    249     }
    250 
    251     /**
    252      * Add an new interface and add it to the current package. Called by the
    253      * XML parser.
    254      *
    255      * @param name The name of the interface.
    256      * @param parent The name of the parent interface, null if no
    257      *               interface is extended.
    258      */
    259     public static void addInterface(String name, String parent,
    260                                     boolean isAbstract,
    261                                     Modifiers modifiers) {
    262         api_.currClass_ = new ClassAPI(name, parent, true, isAbstract, modifiers);
    263         api_.currPkg_.classes_.add(api_.currClass_);
    264     }
    265 
    266     /**
    267      * Add an inherited interface to the current class. Called by the XML
    268      * parser.
    269      *
    270      * @param name The name of the inherited interface.
    271      */
    272     public static void addImplements(String name) {
    273        api_.currClass_.implements_.add(name);
    274     }
    275 
    276     /**
    277      * Add a constructor to the current class. Called by the XML parser.
    278      *
    279      * @param name The name of the constructor (optional).
    280      * @param type The type of the constructor.
    281      * @param modifiers Modifiers for this constructor.
    282      */
    283     public static void addCtor(String name, String type, Modifiers modifiers) {
    284         String t = type;
    285         if (t == null)
    286             t = "void";
    287         api_.currCtor_ = new ConstructorAPI(name, t, modifiers);
    288         api_.currClass_.ctors_.add(api_.currCtor_);
    289     }
    290 
    291     /**
    292      * Add a method to the current class. Called by the XML parser.
    293      *
    294      * @param name The name of the method.
    295      * @param returnType The return type of the method, null if it is void.
    296      * @param modifiers Modifiers for this method.
    297      */
    298     public static void addMethod(String name, String returnType,
    299                                  boolean isAbstract, boolean isNative,
    300                                  boolean isSynchronized, Modifiers modifiers) {
    301         String rt = returnType;
    302         if (rt == null)
    303             rt = "void";
    304         api_.currMethod_ = new MethodAPI(name, rt, isAbstract, isNative,
    305                                          isSynchronized, modifiers);
    306         api_.currClass_.methods_.add(api_.currMethod_);
    307     }
    308 
    309     /**
    310      * Add a field to the current class. Called by the XML parser.
    311      *
    312      * @param name The name of the field.
    313      * @param type The type of the field, null if it is void.
    314      * @param modifiers Modifiers for this field.
    315      */
    316     public static void addField(String name, String type, boolean isTransient,
    317                                 boolean isVolatile, String value, Modifiers modifiers) {
    318         String t = type;
    319         if (t == null)
    320             t = "void";
    321         api_.currField_ = new FieldAPI(name, t, isTransient, isVolatile, value, modifiers);
    322         api_.currClass_.fields_.add(api_.currField_);
    323     }
    324 
    325     /**
    326      * Add a parameter to the current method or constructor. Called by the XML parser.
    327      * Constuctors have their type (signature) in an attribute, since it
    328      * is often shorter and makes parsing a little easier.
    329      *
    330      * @param name The name of the parameter.
    331      * @param type The type of the parameter, null if it is void.
    332      * @param isConstructor Whether the given parameter is for a constructor
    333      */
    334     public static void addParam(String name, String type, boolean isConstructor) {
    335         String t = type;
    336         if (t == null)
    337             t = "void";
    338         ParamAPI paramAPI = new ParamAPI(name, t);
    339         if (isConstructor) {
    340             api_.currCtor_.params_.add(paramAPI);
    341         } else {
    342             api_.currMethod_.params_.add(paramAPI);
    343         }
    344     }
    345 
    346     /**
    347      * Add an exception to the current method or constructor.
    348      * Called by the XML parser.
    349      *
    350      * @param name The name of the parameter.
    351      * @param type The type of the parameter.
    352      *             May be null in JDiff1.0.8 and earlier versions.
    353      * @param currElement Name of the current element.
    354      */
    355     public static void addException(String name, String type, String currElement) {
    356 	String exceptionId = type;
    357 	if (type == null || !showExceptionTypes)
    358 	    exceptionId = name;
    359         if (currElement.compareTo("method") == 0) {
    360             if (api_.currMethod_.exceptions_.compareTo("no exceptions") == 0)
    361                 api_.currMethod_.exceptions_ = exceptionId;
    362             else
    363                 api_.currMethod_.exceptions_ += ", " + exceptionId;
    364         } else {
    365             if (api_.currCtor_.exceptions_.compareTo("no exceptions") == 0)
    366                 api_.currCtor_.exceptions_ = exceptionId;
    367             else
    368                 api_.currCtor_.exceptions_ += ", " + exceptionId;
    369         }
    370     }
    371 
    372     /**
    373      * If set, validate the XML which represents an API. By default, this is
    374      * not set for reasons of efficiency, and also because if JDiff generated
    375      * the XML, it should not need validating.
    376      */
    377     public static boolean validateXML = false;
    378 
    379     /**
    380      * If set, then store and display the whole qualified name of exceptions.
    381      * If not set, then store and display just the name of the exception,
    382      * which is shorter, but may not detect when an exception changes class,
    383      * but retains the same name.
    384      */
    385     private static boolean showExceptionTypes = true;
    386 }
    387