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