Home | History | Annotate | Download | only in jdiff
      1 package jdiff;
      2 
      3 import java.io.*;
      4 import java.util.*;
      5 
      6 /* For SAX parsing in APIHandler */
      7 import org.xml.sax.Attributes;
      8 import org.xml.sax.SAXException;
      9 import org.xml.sax.SAXParseException;
     10 import org.xml.sax.XMLReader;
     11 import org.xml.sax.helpers.DefaultHandler;
     12 
     13 /**
     14  * Handle the parsing of an XML file and the generation of an API object.
     15  *
     16  * See the file LICENSE.txt for copyright details.
     17  * @author Matthew Doar, mdoar (at) pobox.com
     18  */
     19 class APIHandler extends DefaultHandler {
     20 
     21     /** The API object which is populated from the XML file. */
     22     public API api_;
     23 
     24     /** Default constructor. */
     25     public APIHandler(API api, boolean createGlobalComments) {
     26         api_ = api;
     27         createGlobalComments_ = createGlobalComments;
     28         tagStack = new LinkedList();
     29     }
     30 
     31     /** If set, then check that each comment is a sentence. */
     32     public static boolean checkIsSentence = false;
     33 
     34     /**
     35      * Contains the name of the current package element type
     36      * where documentation is being added. Also used as the level
     37      * at which to add documentation into an element, i.e. class-level
     38      * or package-level.
     39      */
     40     private String currentElement = null;
     41 
     42     /** If set, then create the global list of comments. */
     43     private boolean createGlobalComments_ = false;
     44 
     45     /** Set if inside a doc element. */
     46     private boolean inDoc = false;
     47 
     48     /** The current comment text being assembled. */
     49     private String currentText = null;
     50 
     51     /** The current text from deprecation, null if empty. */
     52     private String currentDepText = null;
     53 
     54     /**
     55      * The stack of SingleComment objects awaiting the comment text
     56      * currently being assembled.
     57      */
     58     private LinkedList tagStack = null;
     59 
     60     /** Called at the start of the document. */
     61     public void startDocument() {
     62     }
     63 
     64     /** Called when the end of the document is reached. */
     65     public void endDocument() {
     66         if (trace)
     67             api_.dump();
     68         System.out.println(" finished");
     69     }
     70 
     71     /** Called when a new element is started. */
     72     public void startElement(java.lang.String uri, java.lang.String localName,
     73                              java.lang.String qName, Attributes attributes) {
     74 	 // The change to JAXP compliance produced this change.
     75 	if (localName.equals(""))
     76 	    localName = qName;
     77         if (localName.compareTo("api") == 0) {
     78             String apiName = attributes.getValue("name");
     79             String version = attributes.getValue("jdversion"); // Not used yet
     80             XMLToAPI.nameAPI(apiName);
     81         } else if (localName.compareTo("package") == 0) {
     82             currentElement = localName;
     83             String pkgName = attributes.getValue("name");
     84             XMLToAPI.addPackage(pkgName);
     85         } else if (localName.compareTo("class") == 0) {
     86             currentElement = localName;
     87             String className = attributes.getValue("name");
     88             String parentName = attributes.getValue("extends");
     89             boolean isAbstract = false;
     90             if (attributes.getValue("abstract").compareTo("true") == 0)
     91                 isAbstract = true;
     92             XMLToAPI.addClass(className, parentName, isAbstract, getModifiers(attributes));
     93         } else if (localName.compareTo("interface") == 0) {
     94             currentElement = localName;
     95             String className = attributes.getValue("name");
     96             String parentName = attributes.getValue("extends");
     97             boolean isAbstract = false;
     98             if (attributes.getValue("abstract").compareTo("true") == 0)
     99                 isAbstract = true;
    100             XMLToAPI.addInterface(className, parentName, isAbstract, getModifiers(attributes));
    101         } else if (localName.compareTo("implements") == 0) {
    102             String interfaceName = attributes.getValue("name");
    103             XMLToAPI.addImplements(interfaceName);
    104         } else if (localName.compareTo("constructor") == 0) {
    105             currentElement = localName;
    106             String ctorType = attributes.getValue("type");
    107             XMLToAPI.addCtor(ctorType, getModifiers(attributes));
    108         } else if (localName.compareTo("method") == 0) {
    109             currentElement = localName;
    110             String methodName = attributes.getValue("name");
    111             String returnType = attributes.getValue("return");
    112             boolean isAbstract = false;
    113             if (attributes.getValue("abstract").compareTo("true") == 0)
    114                 isAbstract = true;
    115             boolean isNative = false;
    116             if (attributes.getValue("native").compareTo("true") == 0)
    117                 isNative = true;
    118             boolean isSynchronized = false;
    119             if (attributes.getValue("synchronized").compareTo("true") == 0)
    120                 isSynchronized = true;
    121             XMLToAPI.addMethod(methodName, returnType, isAbstract, isNative,
    122                                isSynchronized, getModifiers(attributes));
    123         } else if (localName.compareTo("field") == 0) {
    124             currentElement = localName;
    125             String fieldName = attributes.getValue("name");
    126             String fieldType = attributes.getValue("type");
    127             boolean isTransient = false;
    128             if (attributes.getValue("transient").compareTo("true") == 0)
    129                 isTransient = true;
    130             boolean isVolatile = false;
    131             if (attributes.getValue("volatile").compareTo("true") == 0)
    132                 isVolatile = true;
    133             String value = attributes.getValue("value");
    134             XMLToAPI.addField(fieldName, fieldType, isTransient, isVolatile,
    135                               value, getModifiers(attributes));
    136         } else if (localName.compareTo("param") == 0) {
    137             String paramName = attributes.getValue("name");
    138             String paramType = attributes.getValue("type");
    139             XMLToAPI.addParam(paramName, paramType);
    140         } else if (localName.compareTo("exception") == 0) {
    141             String paramName = attributes.getValue("name");
    142             String paramType = attributes.getValue("type");
    143             XMLToAPI.addException(paramName, paramType, currentElement);
    144         } else if (localName.compareTo("doc") == 0) {
    145             inDoc = true;
    146             currentText = null;
    147         } else {
    148             if (inDoc) {
    149                 // Start of an element, probably an HTML element
    150                 addStartTagToText(localName, attributes);
    151             } else {
    152                 System.out.println("Error: unknown element type: " + localName);
    153                 System.exit(-1);
    154             }
    155         }
    156     }
    157 
    158     /** Called when the end of an element is reached. */
    159     public void endElement(java.lang.String uri, java.lang.String localName,
    160                            java.lang.String qName) {
    161 	if (localName.equals(""))
    162 	    localName = qName;
    163         // Deal with the end of doc blocks
    164         if (localName.compareTo("doc") == 0) {
    165             inDoc = false;
    166             // Add the assembled comment text to the appropriate current
    167             // program element, as determined by currentElement.
    168             addTextToComments();
    169         } else if (inDoc) {
    170             // An element was found inside the HTML text
    171             addEndTagToText(localName);
    172         } else if (currentElement.compareTo("constructor") == 0 &&
    173                    localName.compareTo("constructor") == 0) {
    174             currentElement = "class";
    175         } else if (currentElement.compareTo("method") == 0 &&
    176                    localName.compareTo("method") == 0) {
    177             currentElement = "class";
    178         } else if (currentElement.compareTo("field") == 0 &&
    179                    localName.compareTo("field") == 0) {
    180             currentElement = "class";
    181         } else if (currentElement.compareTo("class") == 0 ||
    182                    currentElement.compareTo("interface") == 0) {
    183             // Feature request 510307 and bug 517383: duplicate comment ids.
    184             // The end of a member element leaves the currentElement at the
    185             // "class" level, but the next class may in fact be an interface
    186             // and so the currentElement here will be "interface".
    187             if (localName.compareTo("class") == 0 ||
    188                 localName.compareTo("interface") == 0) {
    189                 currentElement = "package";
    190             }
    191         }
    192     }
    193 
    194     /** Called to process text. */
    195     public void characters(char[] ch, int start, int length) {
    196          if (inDoc) {
    197             String chunk = new String(ch, start, length);
    198             if (currentText == null)
    199                 currentText = chunk;
    200             else
    201                 currentText += chunk;
    202          }
    203     }
    204 
    205     /**
    206      * Trim the current text, check it is a sentence and add it to the
    207      * current program element.
    208      */
    209     public void addTextToComments() {
    210         // Eliminate any whitespace at each end of the text.
    211         currentText = currentText.trim();
    212         // Convert any @link tags to HTML links.
    213         if (convertAtLinks) {
    214             currentText = Comments.convertAtLinks(currentText, currentElement,
    215                                                   api_.currPkg_, api_.currClass_);
    216         }
    217         // Check that it is a sentence
    218         if (checkIsSentence && !currentText.endsWith(".") &&
    219             currentText.compareTo(Comments.placeHolderText) != 0) {
    220             System.out.println("Warning: text of comment does not end in a period: " + currentText);
    221         }
    222         // The construction of the commentID assumes that the
    223         // documentation is the final element to be parsed. The format matches
    224         // the format used in the report generator to look up comments in the
    225         // the existingComments object.
    226         String commentID = null;
    227         // Add this comment to the current API element.
    228         if (currentElement.compareTo("package") == 0) {
    229             api_.currPkg_.doc_ = currentText;
    230             commentID = api_.currPkg_.name_;
    231         } else if (currentElement.compareTo("class") == 0 ||
    232                    currentElement.compareTo("interface") == 0) {
    233             api_.currClass_.doc_ = currentText;
    234             commentID = api_.currPkg_.name_ + "." + api_.currClass_.name_;
    235         } else if (currentElement.compareTo("constructor") == 0) {
    236             api_.currCtor_.doc_ = currentText;
    237             commentID = api_.currPkg_.name_ + "." + api_.currClass_.name_ +
    238                 ".ctor_changed(";
    239             if (api_.currCtor_.type_.compareTo("void") == 0)
    240                 commentID = commentID + ")";
    241             else
    242                 commentID = commentID + api_.currCtor_.type_ + ")";
    243         } else if (currentElement.compareTo("method") == 0) {
    244             api_.currMethod_.doc_ = currentText;
    245             commentID = api_.currPkg_.name_ + "." + api_.currClass_.name_ +
    246                 "." + api_.currMethod_.name_ + "_changed(" +
    247                 api_.currMethod_.getSignature() + ")";
    248         } else if (currentElement.compareTo("field") == 0) {
    249             api_.currField_.doc_ = currentText;
    250             commentID = api_.currPkg_.name_ + "." + api_.currClass_.name_ +
    251                 "." + api_.currField_.name_;
    252         }
    253         // Add to the list of possible comments for use when an
    254         // element has changed (not removed or added).
    255         if (createGlobalComments_ && commentID != null) {
    256             String ct = currentText;
    257             // Use any deprecation text as the possible comment, ignoring
    258             // any other comment text.
    259             if (currentDepText != null) {
    260                 ct = currentDepText;
    261                 currentDepText = null; // Never reuse it. Bug 469794
    262             }
    263             String ctOld = (String)(Comments.allPossibleComments.put(commentID, ct));
    264             if (ctOld != null) {
    265                 System.out.println("Error: duplicate comment id: " + commentID);
    266                 System.exit(5);
    267             }
    268         }
    269     }
    270 
    271     /**
    272      * Add the start tag to the current comment text.
    273      */
    274     public void addStartTagToText(String localName, Attributes attributes) {
    275         // Need to insert the HTML tag into the current text
    276         String currentHTMLTag = localName;
    277         // Save the tag in a stack
    278         tagStack.add(currentHTMLTag);
    279         String tag = "<" + currentHTMLTag;
    280         // Now add all the attributes into the current text
    281         int len = attributes.getLength();
    282         for (int i = 0; i < len; i++) {
    283             String name = attributes.getLocalName(i);
    284             String value = attributes.getValue(i);
    285             tag += " " + name + "=\"" + value+ "\"";
    286         }
    287 
    288         // End the tag
    289         if (Comments.isMinimizedTag(currentHTMLTag)) {
    290             tag += "/>";
    291         } else {
    292             tag += ">";
    293         }
    294         // Now insert the HTML tag into the current text
    295         if (currentText == null)
    296             currentText = tag;
    297         else
    298             currentText += tag;
    299     }
    300 
    301     /**
    302      * Add the end tag to the current comment text.
    303      */
    304     public void addEndTagToText(String localName) {
    305         // Close the current HTML tag
    306         String currentHTMLTag = (String)(tagStack.removeLast());
    307         if (!Comments.isMinimizedTag(currentHTMLTag))
    308             currentText += "</" + currentHTMLTag + ">";
    309     }
    310 
    311     /** Extra modifiers which are common to all program elements. */
    312     public Modifiers getModifiers(Attributes attributes) {
    313         Modifiers modifiers = new Modifiers();
    314         modifiers.isStatic = false;
    315         if (attributes.getValue("static").compareTo("true") == 0)
    316             modifiers.isStatic = true;
    317         modifiers.isFinal = false;
    318         if (attributes.getValue("final").compareTo("true") == 0)
    319             modifiers.isFinal = true;
    320         modifiers.isDeprecated = false;
    321         String cdt = attributes.getValue("deprecated");
    322         if (cdt.compareTo("not deprecated") == 0) {
    323             modifiers.isDeprecated = false;
    324             currentDepText = null;
    325         } else if (cdt.compareTo("deprecated, no comment") == 0) {
    326             modifiers.isDeprecated = true;
    327             currentDepText = null;
    328         } else {
    329             modifiers.isDeprecated = true;
    330             currentDepText = API.showHTMLTags(cdt);
    331         }
    332         modifiers.visibility = attributes.getValue("visibility");
    333         return modifiers;
    334     }
    335 
    336     public void warning(SAXParseException e) {
    337         System.out.println("Warning (" + e.getLineNumber() + "): parsing XML API file:" + e);
    338         e.printStackTrace();
    339     }
    340 
    341     public void error(SAXParseException e) {
    342         System.out.println("Error (" + e.getLineNumber() + "): parsing XML API file:" + e);
    343         e.printStackTrace();
    344         System.exit(1);
    345     }
    346 
    347     public void fatalError(SAXParseException e) {
    348         System.out.println("Fatal Error (" + e.getLineNumber() + "): parsing XML API file:" + e);
    349         e.printStackTrace();
    350         System.exit(1);
    351     }
    352 
    353     /**
    354      * If set, then attempt to convert @link tags to HTML links.
    355      * A few of the HTML links may be broken links.
    356      */
    357     private static boolean convertAtLinks = true;
    358 
    359     /** Set to enable increased logging verbosity for debugging. */
    360     private static boolean trace = false;
    361 
    362 }
    363