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.InputSource;
import org.xml.sax.helpers.*;
/**
* Creates an API object from an XML file. The API object is the internal
* representation of an API.
* All methods in this class for populating an API object are static.
*
* See the file LICENSE.txt for copyright details.
* @author Matthew Doar, mdoar@pobox.com
*/
public class XMLToAPI {
/** The instance of the API object which is populated from the file. */
private static API api_ = null;
/** Default constructor. */
private XMLToAPI() {
}
/**
* Read the file where the XML representing the API is stored.
*
* @param filename The full name of the file containing the XML
* representing the API
* @param createGlobalComments If set, then store possible comments
* @param apiName The simple name of the API file. If -oldapidir and
* -newapidir are not used, then this is the same as
* the filename parameter
*/
public static API readFile(String filename, boolean createGlobalComments,
String apiName) {
// The instance of the API object which is populated from the file.
api_ = new API();
api_.name_ = apiName; // Checked later
try {
XMLReader parser = null;
DefaultHandler handler = new APIHandler(api_, createGlobalComments);
try {
String parserName = System.getProperty("org.xml.sax.driver");
if (parserName == null) {
parser = org.xml.sax.helpers.XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser");
} else {
// Let the underlying mechanisms try to work out which
// class to instantiate
parser = org.xml.sax.helpers.XMLReaderFactory.createXMLReader();
}
} catch (SAXException saxe) {
System.out.println("SAXException: " + saxe);
saxe.printStackTrace();
System.exit(1);
}
if (validateXML) {
parser.setFeature("http://xml.org/sax/features/namespaces", true);
parser.setFeature("http://xml.org/sax/features/validation", true);
parser.setFeature("http://apache.org/xml/features/validation/schema", true);
}
parser.setContentHandler(handler);
parser.setErrorHandler(handler);
parser.parse(new InputSource(new FileInputStream(new File(filename))));
} catch(org.xml.sax.SAXNotRecognizedException snre) {
System.out.println("SAX Parser does not recognize feature: " + snre);
snre.printStackTrace();
System.exit(1);
} catch(org.xml.sax.SAXNotSupportedException snse) {
System.out.println("SAX Parser feature is not supported: " + snse);
snse.printStackTrace();
System.exit(1);
} catch(org.xml.sax.SAXException saxe) {
System.out.println("SAX Exception parsing file '" + filename + "' : " + saxe);
saxe.printStackTrace();
System.exit(1);
} catch(java.io.IOException ioe) {
System.out.println("IOException parsing file '" + filename + "' : " + ioe);
ioe.printStackTrace();
System.exit(1);
}
// Add the inherited methods and fields to each class
addInheritedElements();
return api_;
} //readFile()
/**
* Add the inherited methods and fields to each class in turn.
*/
public static void addInheritedElements() {
Iterator iter = api_.packages_.iterator();
while (iter.hasNext()) {
PackageAPI pkg = (PackageAPI)(iter.next());
Iterator iter2 = pkg.classes_.iterator();
while (iter2.hasNext()) {
ClassAPI cls = (ClassAPI)(iter2.next());
// Look up any inherited classes or interfaces
if (cls.extends_ != null) {
ClassAPI parent = (ClassAPI)api_.classes_.get(cls.extends_);
if (parent != null)
addInheritedElements(cls, parent, cls.extends_);
}
if (cls.implements_.size() != 0) {
Iterator iter3 = cls.implements_.iterator();
while (iter3.hasNext()) {
String implName = (String)(iter3.next());
ClassAPI parent = (ClassAPI)api_.classes_.get(implName);
if (parent != null)
addInheritedElements(cls, parent, implName);
}
}
} //while (iter2.hasNext())
} //while (iter.hasNext())
}
/**
* Add all the inherited methods and fields in the second class to
* the first class, marking them as inherited from the second class.
* Do not add a method or a field if it is already defined locally.
*
* Only elements at the specified visibility level or
* higher appear in the XML file. All that remains to be tested for
* a private element, which is never inherited.
*
* If the parent class inherits any classes or interfaces, call this
* method recursively with those parents.
*/
public static void addInheritedElements(ClassAPI child, ClassAPI parent,
String fqParentName) {
if (parent.methods_.size() != 0) {
Iterator iter = parent.methods_.iterator();
while (iter.hasNext()) {
MethodAPI m = (MethodAPI)(iter.next());
// See if it the method is overridden locally
boolean overridden = false;
Iterator iter2 = child.methods_.iterator();
while (iter2.hasNext()) {
MethodAPI localM = (MethodAPI)(iter2.next());
if (localM.name_.compareTo(m.name_) == 0 &&
localM.getSignature().compareTo(m.getSignature()) == 0)
overridden = true;
}
if (!overridden && m.inheritedFrom_ == null &&
m.modifiers_.visibility != null &&
m.modifiers_.visibility.compareTo("private") != 0) {
MethodAPI m2 = new MethodAPI(m);
m2.inheritedFrom_ = fqParentName;
child.methods_.add(m2);
}
}
}
if (parent.fields_.size() != 0) {
Iterator iter = parent.fields_.iterator();
while (iter.hasNext()) {
FieldAPI f = (FieldAPI)(iter.next());
if (child.fields_.indexOf(f) == -1 &&
f.inheritedFrom_ == null &&
f.modifiers_.visibility != null &&
f.modifiers_.visibility.compareTo("private") != 0) {
FieldAPI f2 = new FieldAPI(f);
f2.inheritedFrom_ = fqParentName;
child.fields_.add(f2);
}
}
}
// Look up any inherited classes or interfaces
if (parent.extends_ != null) {
ClassAPI parent2 = (ClassAPI)api_.classes_.get(parent.extends_);
if (parent2 != null)
addInheritedElements(child, parent2, parent.extends_);
}
if (parent.implements_.size() != 0) {
Iterator iter3 = parent.implements_.iterator();
while (iter3.hasNext()) {
String implName = (String)(iter3.next());
ClassAPI parent2 = (ClassAPI)api_.classes_.get(implName);
if (parent2 != null)
addInheritedElements(child, parent2, implName);
}
}
}
//
// Methods to add data to an API object. Called by the XML parser.
//
/**
* Set the name of the API object.
*
* @param name The name of the package.
*/
public static void nameAPI(String name) {
if (name == null) {
System.out.println("Error: no API identifier found in the XML file '" + api_.name_ + "'");
System.exit(3);
}
// Check the given name against the filename currently stored in
// the name_ field
String filename2 = name.replace(' ','_');
filename2 += ".xml";
if (filename2.compareTo(api_.name_) != 0) {
System.out.println("Warning: API identifier in the XML file (" +
name + ") differs from the name of the file '" +
api_.name_ + "'");
}
api_.name_ = name;
}
/**
* Create a new package and add it to the API. Called by the XML parser.
*
* @param name The name of the package.
*/
public static void addPackage(String name) {
api_.currPkg_ = new PackageAPI(name);
api_.packages_.add(api_.currPkg_);
}
/**
* Create a new class and add it to the current package. Called by the XML parser.
*
* @param name The name of the class.
* @param parent The name of the parent class, null if no class is extended.
* @param modifiers Modifiers for this class.
*/
public static void addClass(String name, String parent,
boolean isAbstract,
Modifiers modifiers) {
api_.currClass_ = new ClassAPI(name, parent, false, isAbstract, modifiers);
api_.currPkg_.classes_.add(api_.currClass_);
String fqName = api_.currPkg_.name_ + "." + name;
ClassAPI caOld = (ClassAPI)api_.classes_.put(fqName, api_.currClass_);
if (caOld != null) {
System.out.println("Warning: duplicate class : " + fqName + " found. Using the first instance only.");
}
}
/**
* Add an new interface and add it to the current package. Called by the
* XML parser.
*
* @param name The name of the interface.
* @param parent The name of the parent interface, null if no
* interface is extended.
*/
public static void addInterface(String name, String parent,
boolean isAbstract,
Modifiers modifiers) {
api_.currClass_ = new ClassAPI(name, parent, true, isAbstract, modifiers);
api_.currPkg_.classes_.add(api_.currClass_);
}
/**
* Add an inherited interface to the current class. Called by the XML
* parser.
*
* @param name The name of the inherited interface.
*/
public static void addImplements(String name) {
api_.currClass_.implements_.add(name);
}
/**
* Add a constructor to the current class. Called by the XML parser.
*
* @param name The name of the constructor.
* @param type The type of the constructor.
* @param modifiers Modifiers for this constructor.
*/
public static void addCtor(String type, Modifiers modifiers) {
String t = type;
if (t == null)
t = "void";
api_.currCtor_ = new ConstructorAPI(t, modifiers);
api_.currClass_.ctors_.add(api_.currCtor_);
}
/**
* Add a method to the current class. Called by the XML parser.
*
* @param name The name of the method.
* @param returnType The return type of the method, null if it is void.
* @param modifiers Modifiers for this method.
*/
public static void addMethod(String name, String returnType,
boolean isAbstract, boolean isNative,
boolean isSynchronized, Modifiers modifiers) {
String rt = returnType;
if (rt == null)
rt = "void";
api_.currMethod_ = new MethodAPI(name, rt, isAbstract, isNative,
isSynchronized, modifiers);
api_.currClass_.methods_.add(api_.currMethod_);
}
/**
* Add a field to the current class. Called by the XML parser.
*
* @param name The name of the field.
* @param type The type of the field, null if it is void.
* @param modifiers Modifiers for this field.
*/
public static void addField(String name, String type, boolean isTransient,
boolean isVolatile, String value, Modifiers modifiers) {
String t = type;
if (t == null)
t = "void";
api_.currField_ = new FieldAPI(name, t, isTransient, isVolatile, value, modifiers);
api_.currClass_.fields_.add(api_.currField_);
}
/**
* Add a parameter to the current method. Called by the XML parser.
* Constuctors have their type (signature) in an attribute, since it
* is often shorter and makes parsing a little easier.
*
* @param name The name of the parameter.
* @param type The type of the parameter, null if it is void.
*/
public static void addParam(String name, String type) {
String t = type;
if (t == null)
t = "void";
ParamAPI paramAPI = new ParamAPI(name, t);
api_.currMethod_.params_.add(paramAPI);
}
/**
* Add an exception to the current method or constructor.
* Called by the XML parser.
*
* @param name The name of the parameter.
* @param type The type of the parameter.
* May be null in JDiff1.0.8 and earlier versions.
* @param currElement Name of the current element.
*/
public static void addException(String name, String type, String currElement) {
String exceptionId = type;
if (type == null || !showExceptionTypes)
exceptionId = name;
if (currElement.compareTo("method") == 0) {
if (api_.currMethod_.exceptions_.compareTo("no exceptions") == 0)
api_.currMethod_.exceptions_ = exceptionId;
else
api_.currMethod_.exceptions_ += ", " + exceptionId;
} else {
if (api_.currCtor_.exceptions_.compareTo("no exceptions") == 0)
api_.currCtor_.exceptions_ = exceptionId;
else
api_.currCtor_.exceptions_ += ", " + exceptionId;
}
}
/**
* If set, validate the XML which represents an API. By default, this is
* not set for reasons of efficiency, and also because if JDiff generated
* the XML, it should not need validating.
*/
public static boolean validateXML = false;
/**
* If set, then store and display the whole qualified name of exceptions.
* If not set, then store and display just the name of the exception,
* which is shorter, but may not detect when an exception changes class,
* but retains the same name.
*/
private static boolean showExceptionTypes = true;
}