aboutsummaryrefslogtreecommitdiff
path: root/src/jdiff/APIHandler.java
blob: be1a6fcafe44e8e8bbd2131f4dec71ffdf9e89e9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
package jdiff;

import java.io.*;
import java.util.*;

/* For SAX parsing in APIHandler */
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;

/**
 * Handle the parsing of an XML file and the generation of an API object.
 *
 * See the file LICENSE.txt for copyright details.
 * @author Matthew Doar, mdoar@pobox.com
 */
class APIHandler extends DefaultHandler {

    /** The API object which is populated from the XML file. */
    public API api_;

    /** Default constructor. */
    public APIHandler(API api, boolean createGlobalComments) {
        api_ = api;
        createGlobalComments_ = createGlobalComments;
        tagStack = new LinkedList();
    }   

    /** If set, then check that each comment is a sentence. */
    public static boolean checkIsSentence = false;

    /** 
     * Contains the name of the current package element type
     * where documentation is being added. Also used as the level
     * at which to add documentation into an element, i.e. class-level
     * or package-level.
     */
    private String currentElement = null;

    /** If set, then create the global list of comments. */
    private boolean createGlobalComments_ = false;

    /** Set if inside a doc element. */
    private boolean inDoc = false;

    /** The current comment text being assembled. */
    private String currentText = null;

    /** The current text from deprecation, null if empty. */
    private String currentDepText = null;

    /** 
     * The stack of SingleComment objects awaiting the comment text 
     * currently being assembled. 
     */
    private LinkedList tagStack = null;

    /** Called at the start of the document. */
    public void startDocument() {
    }
    
    /** Called when the end of the document is reached. */
    public void endDocument() {
        if (trace)
            api_.dump();
        System.out.println(" finished");
    }

    /** Called when a new element is started. */
    public void startElement(java.lang.String uri, java.lang.String localName,
                             java.lang.String qName, Attributes attributes) {
	 // The change to JAXP compliance produced this change.
	if (localName.equals(""))
	    localName = qName;
        if (localName.compareTo("api") == 0) {
            String apiName = attributes.getValue("name");
            String version = attributes.getValue("jdversion"); // Not used yet
            XMLToAPI.nameAPI(apiName);
        } else if (localName.compareTo("package") == 0) {
            currentElement = localName;
            String pkgName = attributes.getValue("name");
            XMLToAPI.addPackage(pkgName);
        } else if (localName.compareTo("class") == 0) {
            currentElement = localName;
            String className = attributes.getValue("name");
            String parentName = attributes.getValue("extends");
            boolean isAbstract = false;
            if (attributes.getValue("abstract").compareTo("true") == 0)
                isAbstract = true;
            XMLToAPI.addClass(className, parentName, isAbstract, getModifiers(attributes));
        } else if (localName.compareTo("interface") == 0) {
            currentElement = localName;
            String className = attributes.getValue("name");
            String parentName = attributes.getValue("extends");
            boolean isAbstract = false;
            if (attributes.getValue("abstract").compareTo("true") == 0)
                isAbstract = true;
            XMLToAPI.addInterface(className, parentName, isAbstract, getModifiers(attributes));
        } else if (localName.compareTo("implements") == 0) {
            String interfaceName = attributes.getValue("name");
            XMLToAPI.addImplements(interfaceName);
        } else if (localName.compareTo("constructor") == 0) {
            currentElement = localName;
            String ctorType = attributes.getValue("type");
            XMLToAPI.addCtor(ctorType, getModifiers(attributes));
        } else if (localName.compareTo("method") == 0) {
            currentElement = localName;
            String methodName = attributes.getValue("name");
            String returnType = attributes.getValue("return");
            boolean isAbstract = false;
            if (attributes.getValue("abstract").compareTo("true") == 0)
                isAbstract = true;
            boolean isNative = false;
            if (attributes.getValue("native").compareTo("true") == 0)
                isNative = true;
            boolean isSynchronized = false;
            if (attributes.getValue("synchronized").compareTo("true") == 0)
                isSynchronized = true;
            XMLToAPI.addMethod(methodName, returnType, isAbstract, isNative, 
                               isSynchronized, getModifiers(attributes));
        } else if (localName.compareTo("field") == 0) {
            currentElement = localName;
            String fieldName = attributes.getValue("name");
            String fieldType = attributes.getValue("type");
            boolean isTransient = false;
            if (attributes.getValue("transient").compareTo("true") == 0)
                isTransient = true;
            boolean isVolatile = false;
            if (attributes.getValue("volatile").compareTo("true") == 0)
                isVolatile = true;
            String value = attributes.getValue("value");
            XMLToAPI.addField(fieldName, fieldType, isTransient, isVolatile, 
                              value, getModifiers(attributes));
        } else if (localName.compareTo("param") == 0) {
            String paramName = attributes.getValue("name");
            String paramType = attributes.getValue("type");
            XMLToAPI.addParam(paramName, paramType);
        } else if (localName.compareTo("exception") == 0) {
            String paramName = attributes.getValue("name");
            String paramType = attributes.getValue("type");
            XMLToAPI.addException(paramName, paramType, currentElement);
        } else if (localName.compareTo("doc") == 0) {
            inDoc = true;
            currentText = null;
        } else {
            if (inDoc) {
                // Start of an element, probably an HTML element
                addStartTagToText(localName, attributes);
            } else {
                System.out.println("Error: unknown element type: " + localName);
                System.exit(-1);
            }
        }
    }
    
    /** Called when the end of an element is reached. */
    public void endElement(java.lang.String uri, java.lang.String localName, 
                           java.lang.String qName) {
	if (localName.equals(""))
	    localName = qName;
        // Deal with the end of doc blocks
        if (localName.compareTo("doc") == 0) {
            inDoc = false;
            // Add the assembled comment text to the appropriate current
            // program element, as determined by currentElement.
            addTextToComments();
        } else if (inDoc) {
            // An element was found inside the HTML text
            addEndTagToText(localName);
        } else if (currentElement.compareTo("constructor") == 0 && 
                   localName.compareTo("constructor") == 0) {
            currentElement = "class";
        } else if (currentElement.compareTo("method") == 0 && 
                   localName.compareTo("method") == 0) {
            currentElement = "class";
        } else if (currentElement.compareTo("field") == 0 && 
                   localName.compareTo("field") == 0) {
            currentElement = "class";
        } else if (currentElement.compareTo("class") == 0 ||
                   currentElement.compareTo("interface") == 0) {
            // Feature request 510307 and bug 517383: duplicate comment ids.
            // The end of a member element leaves the currentElement at the 
            // "class" level, but the next class may in fact be an interface
            // and so the currentElement here will be "interface".
            if (localName.compareTo("class") == 0 || 
                localName.compareTo("interface") == 0) {
                currentElement = "package";
            }
        }
    }

    /** Called to process text. */
    public void characters(char[] ch, int start, int length) {
         if (inDoc) {
            String chunk = new String(ch, start, length);
            if (currentText == null)
                currentText = chunk;
            else
                currentText += chunk;
         }
    }

    /** 
     * Trim the current text, check it is a sentence and add it to the
     * current program element. 
     */
    public void addTextToComments() {
        // Eliminate any whitespace at each end of the text.
        currentText = currentText.trim();        
        // Convert any @link tags to HTML links.
        if (convertAtLinks) {
            currentText = Comments.convertAtLinks(currentText, currentElement, 
                                                  api_.currPkg_, api_.currClass_);
        }
        // Check that it is a sentence
        if (checkIsSentence && !currentText.endsWith(".") && 
            currentText.compareTo(Comments.placeHolderText) != 0) {
            System.out.println("Warning: text of comment does not end in a period: " + currentText);
        }
        // The construction of the commentID assumes that the 
        // documentation is the final element to be parsed. The format matches
        // the format used in the report generator to look up comments in the
        // the existingComments object.
        String commentID = null;
        // Add this comment to the current API element.
        if (currentElement.compareTo("package") == 0) {
            api_.currPkg_.doc_ = currentText;
            commentID = api_.currPkg_.name_;
        } else if (currentElement.compareTo("class") == 0 ||
                   currentElement.compareTo("interface") == 0) {
            api_.currClass_.doc_ = currentText;
            commentID = api_.currPkg_.name_ + "." + api_.currClass_.name_;
        } else if (currentElement.compareTo("constructor") == 0) {
            api_.currCtor_.doc_ = currentText;
            commentID = api_.currPkg_.name_ + "." + api_.currClass_.name_ +
                ".ctor_changed(";
            if (api_.currCtor_.type_.compareTo("void") == 0)
                commentID = commentID + ")";
            else
                commentID = commentID + api_.currCtor_.type_ + ")";
        } else if (currentElement.compareTo("method") == 0) {
            api_.currMethod_.doc_ = currentText;
            commentID = api_.currPkg_.name_ + "." + api_.currClass_.name_ +
                "." + api_.currMethod_.name_ + "_changed(" + 
                api_.currMethod_.getSignature() + ")";
        } else if (currentElement.compareTo("field") == 0) {
            api_.currField_.doc_ = currentText;
            commentID = api_.currPkg_.name_ + "." + api_.currClass_.name_ +
                "." + api_.currField_.name_;
        }            
        // Add to the list of possible comments for use when an
        // element has changed (not removed or added).
        if (createGlobalComments_ && commentID != null) {
            String ct = currentText;
            // Use any deprecation text as the possible comment, ignoring 
            // any other comment text.
            if (currentDepText != null) {
                ct = currentDepText;
                currentDepText = null; // Never reuse it. Bug 469794
            }
            String ctOld = (String)(Comments.allPossibleComments.put(commentID, ct));
            if (ctOld != null) {
                System.out.println("Error: duplicate comment id: " + commentID);
                System.exit(5);
            }
        }
    }

    /** 
     * Add the start tag to the current comment text. 
     */
    public void addStartTagToText(String localName, Attributes attributes) {
        // Need to insert the HTML tag into the current text
        String currentHTMLTag = localName;
        // Save the tag in a stack
        tagStack.add(currentHTMLTag);
        String tag = "<" + currentHTMLTag;
        // Now add all the attributes into the current text
        int len = attributes.getLength();
        for (int i = 0; i < len; i++) {
            String name = attributes.getLocalName(i);
            String value = attributes.getValue(i);
            tag += " " + name + "=\"" + value+ "\"";
        }

        // End the tag
        if (Comments.isMinimizedTag(currentHTMLTag)) {
            tag += "/>";
        } else {
            tag += ">";
        }
        // Now insert the HTML tag into the current text
        if (currentText == null)
            currentText = tag;
        else
            currentText += tag;
    }

    /** 
     * Add the end tag to the current comment text. 
     */
    public void addEndTagToText(String localName) {
        // Close the current HTML tag
        String currentHTMLTag = (String)(tagStack.removeLast());
        if (!Comments.isMinimizedTag(currentHTMLTag))
            currentText += "</" + currentHTMLTag + ">";
    }

    /** Extra modifiers which are common to all program elements. */
    public Modifiers getModifiers(Attributes attributes) {
        Modifiers modifiers = new Modifiers();
        modifiers.isStatic = false;
        if (attributes.getValue("static").compareTo("true") == 0)
            modifiers.isStatic = true;
        modifiers.isFinal = false;
        if (attributes.getValue("final").compareTo("true") == 0)
            modifiers.isFinal = true;
        modifiers.isDeprecated = false;
        String cdt = attributes.getValue("deprecated");
        if (cdt.compareTo("not deprecated") == 0) {
            modifiers.isDeprecated = false;
            currentDepText = null;
        } else if (cdt.compareTo("deprecated, no comment") == 0) {
            modifiers.isDeprecated = true;
            currentDepText = null;
        } else {
            modifiers.isDeprecated = true;
            currentDepText = API.showHTMLTags(cdt);
        }
        modifiers.visibility = attributes.getValue("visibility");
        return modifiers;
    }

    public void warning(SAXParseException e) {
        System.out.println("Warning (" + e.getLineNumber() + "): parsing XML API file:" + e);
        e.printStackTrace();
    }

    public void error(SAXParseException e) {
        System.out.println("Error (" + e.getLineNumber() + "): parsing XML API file:" + e);
        e.printStackTrace();
        System.exit(1);
    }
    
    public void fatalError(SAXParseException e) {
        System.out.println("Fatal Error (" + e.getLineNumber() + "): parsing XML API file:" + e);
        e.printStackTrace();
        System.exit(1);
    }    

    /** 
     * If set, then attempt to convert @link tags to HTML links. 
     * A few of the HTML links may be broken links.
     */
    private static boolean convertAtLinks = true;

    /** Set to enable increased logging verbosity for debugging. */
    private static boolean trace = false;

}