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 ctorName = attributes.getValue("name");
107            String ctorType = attributes.getValue("type");
108            XMLToAPI.addCtor(ctorName, ctorType, getModifiers(attributes));
109        } else if (localName.compareTo("method") == 0) {
110            currentElement = localName;
111            String methodName = attributes.getValue("name");
112            String returnType = attributes.getValue("return");
113            boolean isAbstract = false;
114            if (attributes.getValue("abstract").compareTo("true") == 0)
115                isAbstract = true;
116            boolean isNative = false;
117            if (attributes.getValue("native").compareTo("true") == 0)
118                isNative = true;
119            boolean isSynchronized = false;
120            if (attributes.getValue("synchronized").compareTo("true") == 0)
121                isSynchronized = true;
122            XMLToAPI.addMethod(methodName, returnType, isAbstract, isNative,
123                               isSynchronized, getModifiers(attributes));
124        } else if (localName.compareTo("field") == 0) {
125            currentElement = localName;
126            String fieldName = attributes.getValue("name");
127            String fieldType = attributes.getValue("type");
128            boolean isTransient = false;
129            if (attributes.getValue("transient").compareTo("true") == 0)
130                isTransient = true;
131            boolean isVolatile = false;
132            if (attributes.getValue("volatile").compareTo("true") == 0)
133                isVolatile = true;
134            String value = attributes.getValue("value");
135            XMLToAPI.addField(fieldName, fieldType, isTransient, isVolatile,
136                              value, getModifiers(attributes));
137        } else if (localName.compareTo("param") == 0 || localName.compareTo("parameter") == 0) {
138            String paramName = attributes.getValue("name");
139            String paramType = attributes.getValue("type");
140            XMLToAPI.addParam(paramName, paramType, currentElement.compareTo("constructor") == 0);
141        } else if (localName.compareTo("exception") == 0) {
142            String paramName = attributes.getValue("name");
143            String paramType = attributes.getValue("type");
144            XMLToAPI.addException(paramName, paramType, currentElement);
145        } else if (localName.compareTo("doc") == 0) {
146            inDoc = true;
147            currentText = null;
148        } else {
149            if (inDoc) {
150                // Start of an element, probably an HTML element
151                addStartTagToText(localName, attributes);
152            } else {
153                System.out.println("Error: unknown element type: " + localName);
154                System.exit(-1);
155            }
156        }
157    }
158
159    /** Called when the end of an element is reached. */
160    public void endElement(java.lang.String uri, java.lang.String localName,
161                           java.lang.String qName) {
162        if (localName.equals(""))
163            localName = qName;
164        // Deal with the end of doc blocks
165        if (localName.compareTo("doc") == 0) {
166            inDoc = false;
167            // Add the assembled comment text to the appropriate current
168            // program element, as determined by currentElement.
169            addTextToComments();
170        } else if (inDoc) {
171            // An element was found inside the HTML text
172            addEndTagToText(localName);
173        } else if (currentElement.compareTo("constructor") == 0 &&
174                   localName.compareTo("constructor") == 0) {
175            currentElement = "class";
176        } else if (currentElement.compareTo("method") == 0 &&
177                   localName.compareTo("method") == 0) {
178            currentElement = "class";
179        } else if (currentElement.compareTo("field") == 0 &&
180                   localName.compareTo("field") == 0) {
181            currentElement = "class";
182        } else if (currentElement.compareTo("class") == 0 ||
183                   currentElement.compareTo("interface") == 0) {
184            // Feature request 510307 and bug 517383: duplicate comment ids.
185            // The end of a member element leaves the currentElement at the
186            // "class" level, but the next class may in fact be an interface
187            // and so the currentElement here will be "interface".
188            if (localName.compareTo("class") == 0 ||
189                localName.compareTo("interface") == 0) {
190                currentElement = "package";
191            }
192        }
193    }
194
195    /** Called to process text. */
196    public void characters(char[] ch, int start, int length) {
197         if (inDoc) {
198            String chunk = new String(ch, start, length);
199            if (currentText == null)
200                currentText = chunk;
201            else
202                currentText += chunk;
203         }
204    }
205
206    /**
207     * Trim the current text, check it is a sentence and add it to the
208     * current program element.
209     */
210    public void addTextToComments() {
211        // Eliminate any whitespace at each end of the text.
212        currentText = currentText.trim();
213        // Convert any @link tags to HTML links.
214        if (convertAtLinks) {
215            currentText = Comments.convertAtLinks(currentText, currentElement,
216                                                  api_.currPkg_, api_.currClass_);
217        }
218        // Check that it is a sentence
219        if (checkIsSentence && !currentText.endsWith(".") &&
220            currentText.compareTo(Comments.placeHolderText) != 0) {
221            System.out.println("Warning: text of comment does not end in a period: " + currentText);
222        }
223        // The construction of the commentID assumes that the
224        // documentation is the final element to be parsed. The format matches
225        // the format used in the report generator to look up comments in the
226        // the existingComments object.
227        String commentID = null;
228        // Add this comment to the current API element.
229        if (currentElement.compareTo("package") == 0) {
230            api_.currPkg_.doc_ = currentText;
231            commentID = api_.currPkg_.name_;
232        } else if (currentElement.compareTo("class") == 0 ||
233                   currentElement.compareTo("interface") == 0) {
234            api_.currClass_.doc_ = currentText;
235            commentID = api_.currPkg_.name_ + "." + api_.currClass_.name_;
236        } else if (currentElement.compareTo("constructor") == 0) {
237            api_.currCtor_.doc_ = currentText;
238            commentID = api_.currPkg_.name_ + "." + api_.currClass_.name_ +
239                ".ctor_changed(";
240            if (api_.currCtor_.getSignature().compareTo("void") == 0)
241                commentID = commentID + ")";
242            else
243                commentID = commentID + api_.currCtor_.getSignature() + ")";
244        } else if (currentElement.compareTo("method") == 0) {
245            api_.currMethod_.doc_ = currentText;
246            commentID = api_.currPkg_.name_ + "." + api_.currClass_.name_ +
247                "." + api_.currMethod_.name_ + "_changed(" +
248                api_.currMethod_.getSignature() + ")";
249        } else if (currentElement.compareTo("field") == 0) {
250            api_.currField_.doc_ = currentText;
251            commentID = api_.currPkg_.name_ + "." + api_.currClass_.name_ +
252                "." + api_.currField_.name_;
253        }
254        // Add to the list of possible comments for use when an
255        // element has changed (not removed or added).
256        if (createGlobalComments_ && commentID != null) {
257            String ct = currentText;
258            // Use any deprecation text as the possible comment, ignoring
259            // any other comment text.
260            if (currentDepText != null) {
261                ct = currentDepText;
262                currentDepText = null; // Never reuse it. Bug 469794
263            }
264            String ctOld = (String)(Comments.allPossibleComments.put(commentID, ct));
265            if (ctOld != null) {
266                System.out.println("Error: duplicate comment id: " + commentID);
267                System.exit(5);
268            }
269        }
270    }
271
272    /**
273     * Add the start tag to the current comment text.
274     */
275    public void addStartTagToText(String localName, Attributes attributes) {
276        // Need to insert the HTML tag into the current text
277        String currentHTMLTag = localName;
278        // Save the tag in a stack
279        tagStack.add(currentHTMLTag);
280        String tag = "<" + currentHTMLTag;
281        // Now add all the attributes into the current text
282        int len = attributes.getLength();
283        for (int i = 0; i < len; i++) {
284            String name = attributes.getLocalName(i);
285            String value = attributes.getValue(i);
286            tag += " " + name + "=\"" + value+ "\"";
287        }
288
289        // End the tag
290        if (Comments.isMinimizedTag(currentHTMLTag)) {
291            tag += "/>";
292        } else {
293            tag += ">";
294        }
295        // Now insert the HTML tag into the current text
296        if (currentText == null)
297            currentText = tag;
298        else
299            currentText += tag;
300    }
301
302    /**
303     * Add the end tag to the current comment text.
304     */
305    public void addEndTagToText(String localName) {
306        // Close the current HTML tag
307        String currentHTMLTag = (String)(tagStack.removeLast());
308        if (!Comments.isMinimizedTag(currentHTMLTag))
309            currentText += "</" + currentHTMLTag + ">";
310    }
311
312    /** Extra modifiers which are common to all program elements. */
313    public Modifiers getModifiers(Attributes attributes) {
314        Modifiers modifiers = new Modifiers();
315        modifiers.isStatic = false;
316        if (attributes.getValue("static").compareTo("true") == 0)
317            modifiers.isStatic = true;
318        modifiers.isFinal = false;
319        if (attributes.getValue("final").compareTo("true") == 0)
320            modifiers.isFinal = true;
321        modifiers.isDeprecated = false;
322        String cdt = attributes.getValue("deprecated");
323        if (cdt.compareTo("not deprecated") == 0) {
324            modifiers.isDeprecated = false;
325            currentDepText = null;
326        } else if (cdt.compareTo("deprecated, no comment") == 0) {
327            modifiers.isDeprecated = true;
328            currentDepText = null;
329        } else {
330            modifiers.isDeprecated = true;
331            currentDepText = API.showHTMLTags(cdt);
332        }
333        modifiers.visibility = attributes.getValue("visibility");
334        return modifiers;
335    }
336
337    public void warning(SAXParseException e) {
338        System.out.println("Warning (" + e.getLineNumber() + "): parsing XML API file:" + e);
339        e.printStackTrace();
340    }
341
342    public void error(SAXParseException e) {
343        System.out.println("Error (" + e.getLineNumber() + "): parsing XML API file:" + e);
344        e.printStackTrace();
345        System.exit(1);
346    }
347
348    public void fatalError(SAXParseException e) {
349        System.out.println("Fatal Error (" + e.getLineNumber() + "): parsing XML API file:" + e);
350        e.printStackTrace();
351        System.exit(1);
352    }
353
354    /**
355     * If set, then attempt to convert @link tags to HTML links.
356     * A few of the HTML links may be broken links.
357     */
358    private static boolean convertAtLinks = true;
359
360    /** Set to enable increased logging verbosity for debugging. */
361    private static boolean trace = false;
362
363}
364