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