1package jdiff;
2
3import java.io.*;
4import java.util.*;
5
6/* For SAX parsing in APIHandler */
7import org.xml.sax.Attributes;
8import org.xml.sax.SAXException;
9import org.xml.sax.SAXParseException;
10import org.xml.sax.XMLReader;
11import 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@pobox.com
18 */
19class 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