Blob Blame History Raw
/* Copyright (C) 2005 Tresys Technology, LLC
 * License: refer to COPYING file for license information.
 * Authors: Spencer Shimko <sshimko@tresys.com>
 * 
 * Docgen.java: The reference policy xml analyzer and documentation generator		
 */
import policy.*;

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

import javax.xml.parsers.DocumentBuilder; 
import javax.xml.parsers.DocumentBuilderFactory;  
import javax.xml.parsers.ParserConfigurationException;

import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Schema;

import javax.xml.XMLConstants;

import org.xml.sax.*;
import org.w3c.dom.*;

/**
 * The reference policy documentation generator and xml analyzer class.
 * It pulls in XML describing reference policy, transmogrifies it,
 * and spits it back out in some other arbitrary format.
 */
public class Docgen{
	// store the PIs here
	private static Vector procInstr = new Vector();
	private static boolean verbose = false;
	// the policy structure built after xml is parsed
	private Policy policy = null;
	// the xml document
	private Document dom = null;
	
	// the files/directories passed in from the command line
	private static File xmlFile;
	private static File headerFile;
	private static File footerFile;
	private static File outputDir;
		
	private static void printUsage(){
		System.out.println("Reference Policy Documentation Compiler usage:");
		System.out.println("\tjava -cp ./src Docgen [-h] [-v] -xf xmlFileIn -hf headerFile -ff footerFile -od outDirectory");
		System.out.println("-h display this message and exit");
		System.out.println("-xf XML file to parse");
		System.out.println("-hf header file for HTML output");
		System.out.println("-ff footer file for HTML output");
		System.out.println("-od output directory");
		System.exit(1);
	}
	
	/**
	 * Docgen constructor
	 * 
	 * @param output	Filename to setup for output
	 */
	public Docgen(String output) 
	throws FileNotFoundException {
	}
	
	/**
	 * The main() driver for the policy documentation generator.
	 * @param argv	Arguments, takes 1 filename parameter
	 */
	public static void main(String argv[]) {
		if (argv.length == 0){
			printUsage();
			System.exit(1);
		}
		// hacked up version of getopt()
		for (int x=0; x < argv.length; x++){
			if (argv[x].equals("-xf")){
				x++;
				if (x<argv.length){
					xmlFile = new File(argv[x]);
					if (!xmlFile.isFile()){
						printUsage();
						System.err.println("XML file is not really a file!");
						System.exit(1);
					}
				} else {
					printUsage();
					System.exit(1);
				}
			} else if (argv[x].equals("-hf")){
				x++;
				if (x<argv.length){
					headerFile = new File(argv[x]);
					if (!headerFile.isFile()){
						printUsage();
						System.err.println("Header file is not really a file!");
						System.exit(1);
					}
				} else {
					printUsage();
					System.exit(1);
				}
			} else if (argv[x].equals("-ff")){
				x++;
				if (x<argv.length){
					footerFile = new File(argv[x]);
					if (!footerFile.isFile()){
						printUsage();
						System.err.println("Footer file is not really a file!");
						System.exit(1);
					}
				} else {
					printUsage();
					System.exit(1);
				}
			} else if (argv[x].equals("-od")){
				x++;
				if (x<argv.length){
					outputDir = new File(argv[x]);
					if (!outputDir.isDirectory()){
						printUsage();
						System.err.println("Output directory is not really a directory!");
						System.exit(1);
					}
				} else {
					printUsage();
					System.exit(1);
				}
			} else if (argv[x].equals("-h")){
				printUsage();
				System.exit(1);
			} else if (argv[x].equals("-v")){
				verbose = true;
			} else {
				printUsage();
				System.out.println("Error unknown argument: " + argv[x]);
				System.exit(1);
			}
		}
		
		try {
			// create document factory 
			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
			SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
			Schema schema = schemaFactory.newSchema();
			
			factory.setValidating(true);   
			factory.setNamespaceAware(true);

			// in order for this setting to hold factory must be validating
			factory.setIgnoringElementContentWhitespace(true);

			// get builder from factory
			DocumentBuilder builder = factory.newDocumentBuilder();
				
			// create an anonymous error handler for parsing errors
			builder.setErrorHandler(
					new org.xml.sax.ErrorHandler() {
						// fatal errors
						public void fatalError(SAXParseException exception)
						throws SAXException {
							throw exception;
						}
						
						// parse exceptions will be fatal
						public void error(SAXParseException parseErr)
						throws SAXParseException
						{
							// Error generated by the parser
							System.err.println("\nPARSE ERROR: line " + parseErr.getLineNumber() 
									+  ", URI " + parseErr.getSystemId());
							System.err.println("PARSE ERROR: " + parseErr.getMessage() );
							
							// check the wrapped exception
							Exception  x = parseErr;
							if (parseErr.getException() != null)
								x = parseErr.getException();
							x.printStackTrace();					}
						
						// dump warnings too
						public void warning(SAXParseException err)
						throws SAXParseException
						{
							System.err.println("\nPARSE WARNING: line " + err.getLineNumber()
									+ ", URI " + err.getSystemId());
							System.err.println("PARSE WARNING:   " + err.getMessage());
						}
					}
			);
			
			Docgen redoc = new Docgen(argv[1]);

			redoc.dom = builder.parse(xmlFile);
			
			// do our own transformations
			redoc.processDocumentNode();
			
			// build our own converter then convert
			Converter converter = new Converter(redoc.policy, headerFile, footerFile);
			converter.Convert(outputDir);
		// TODO: figure out which of these is taken care of by the anonymous error handler above
		} catch (SAXException saxErr) {
			// sax error
			Exception  x = saxErr;
			if (saxErr.getException() != null)
				x = saxErr.getException();
			x.printStackTrace();
		} catch (ParserConfigurationException parseConfigErr) {
			// Sometimes we can't build the parser with the specified options
			parseConfigErr.printStackTrace();
		} catch (IOException ioe) {
			// I/O error
			ioe.printStackTrace();
		} catch (Exception err) {
			err.printStackTrace();
		}
		

	} // main

	public static void Debug(String msg){
		if (verbose)
			System.out.println(msg);
	}
	
	void processDocumentNode() throws SAXException{
		Element docNode = dom.getDocumentElement();
		
		if (docNode != null && docNode.getNodeName().equals("policy")){
			policy = new Policy("policy");
			
			NodeList children = docNode.getChildNodes();
			int len = children.getLength();
		
			for (int index = 0; index < len; index++){
				processNode(children.item(index), policy);
			}
		} else {
			System.err.println("Failed to find document/policy node!");
			System.exit(1);
		}
	}
	
	/**
	 * Process children of the policy node (aka modules).
	 * 
	 * @param node		A child node of the policy.
	 * @param parent	The parent PolicyElement.
	 */
	void processNode(Node node, Policy parent) throws SAXException{
		Layer layer = null;
		Module module = null;
		
		// save us from null pointer de-referencing
		if (node == null){
			System.err.println(
			"Nothing to do, node is null");
			return;
		}
		
		// snag the name
		String nodeName = node.getNodeName();
		
		// validity check and pull layer attribute
		if (node.getNodeType() == Node.ELEMENT_NODE && nodeName.equals("module")){
			// display the name which might be generic
			Docgen.Debug("Encountered node: " + nodeName);
			NamedNodeMap attrList =	node.getAttributes();
			
			// the required attributes
			int attrLen = 0;
			if(attrList != null){
				attrLen = attrList.getLength();
			} else{
				fatalNodeError("Missing attributes in module. \""  
						+ "\".  \"layer\" and \"name\" are required attributes.");
			}

			Node moduleNode = attrList.getNamedItem("name");
			Node layerNode = attrList.getNamedItem("layer");
			
			if (moduleNode == null || layerNode == null)
				fatalNodeError("Missing attributes in module element.  \"layer\" and \"name\" are required attributes.");

			String moduleName = moduleNode.getNodeValue();
			String layerName = layerNode.getNodeValue();
		
			// check to see if this is a new layer or a pre-existing layer
			layer = parent.Children.get(layerName);
			if (layer == null){
					Docgen.Debug("Adding new layer: " + layerName);
					layer = new Layer(layerName, parent);	
			} else {
				Docgen.Debug("Lookup succeeded for: " + layerName);
			}
			
			if (layer.Children.containsKey(moduleName)){
				Docgen.Debug("Reusing previously defined module: " + moduleName);
				module = layer.Children.get(moduleName);
			} else {
				Docgen.Debug("Creating module: " + moduleName);
				module = new Module(moduleName, layer);
			}
			
			// take care of the attributes
			for(int i = 0; i < attrLen; i++){
				Node attrNode = attrList.item(i);
				String attrName = attrNode.getNodeName();
				String attrValue = attrNode.getNodeValue();
				if (!attrName.equals("layer") && !attrName.equals("name")){
					Docgen.Debug("\tAdding attribute: " + attrNode.getNodeName()
							+ "=" + attrValue);
					module.AddAttribute(attrName,attrValue);
				}
			}
		} else if (!isEmptyTextNode(node)){
			fatalNodeError("Unexpected child \"" + nodeName 
					+"\" node of parent \"" + parent.Name + "\".");
		}
		
		// recurse over children if both module and layer defined
		if (module != null && layer != null){
			// the containsKey check verified no duplicate module
			layer.Children.put(module.Name, module);
			parent.Children.put(layer.Name, layer);

			NodeList children = node.getChildNodes();
			if (children != null){
				int len = children.getLength();
				for (int index = 0; index < len; index++){
					processNode(children.item(index), module);
				}
			}
		}
	}
	
	/**
	 * Process children of the module node (aka interfaces).
	 * 
	 * @param node		A child node of the policy.
	 * @param parent	The parent PolicyElement.
	 */
	void processNode(Node node, Module parent) throws SAXException{
		Interface iface = null;
		
		// save us from null pointer de-referencing
		if (node == null){
			System.err.println(
			"Nothing to do, node is null");
			return;
		}
		
		// snag the name
		String nodeName = node.getNodeName();
		
		// if summary node
		if (node.getNodeType() == Node.ELEMENT_NODE && nodeName.equals("summary")){
			// unfortunately we still need to snag the PCDATA child node for the actual text
			Docgen.Debug("Encountered node: " + nodeName);
			NodeList children = node.getChildNodes();
			if (children != null && children.getLength() == 1){
				if (children.item(0).getNodeType() == Node.TEXT_NODE){
					parent.PCDATA = children.item(0).getNodeValue();
					return;
				} 
			}
			fatalNodeError("Unexpected child \"" + nodeName 
					+"\" node of parent \"" + parent.Name + "\".");
		// if interface node
		} else if (node.getNodeType() == Node.ELEMENT_NODE && nodeName.equals("interface")){
			NamedNodeMap attrList =	node.getAttributes();
			// the required attributes
			int attrLen = 0;
			if(attrList != null){
				attrLen = attrList.getLength();
			} else{
				fatalNodeError("Missing attribute in interface.  " 
						+ "\"name\" is a required attribute.");
			}

			Node nameNode = attrList.getNamedItem("name");
						
			if (nameNode == null )
				fatalNodeError("Missing attribute in interface.  " 
						+ "\"name\" is a required attribute.");


			String iName = nameNode.getNodeValue();
		
			Docgen.Debug("Creating interface: " + iName);
			iface = new Interface(iName, parent);
			
			// take care of the attributes
			for(int i = 0; i < attrLen; i++){
				Node attrNode = attrList.item(i);
				String attrName = attrNode.getNodeName();
				String attrValue = attrNode.getNodeValue();
				if (!attrName.equals("name")){
					Docgen.Debug("\tAdding attribute: " + attrNode.getNodeName()
							+ "=" + attrValue);
					iface.AddAttribute(attrName,attrValue);
				}
			}
		} else if (!isEmptyTextNode(node)){
			fatalNodeError("Unexpected child \"" + nodeName 
					+"\" node of parent \"" + parent.Name + "\".");
		}
		
		// recurse over children if both module and layer defined
		if (iface != null && parent != null){
			// FIXME: containsKey() check for duplicate
			parent.Children.put(iface.Name, iface);

			NodeList children = node.getChildNodes();
			if (children != null){
				int len = children.getLength();
				for (int index = 0; index < len; index++){
					processNode(children.item(index), iface);
				}
			}
		}
	}

	/**
	 * Process children of the interface node (aka parameters, desc., infoflow).
	 * 
	 * @param node		A child node of the policy.
	 * @param parent	The parent PolicyElement.
	 */
	void processNode(Node node, Interface parent) throws SAXException{
		Parameter param = null;
		
		// save us from null pointer de-referencing
		if (node == null){
			System.err.println(
			"Nothing to do, node is null");
			return;
		}
		
		// snag the name
		String nodeName = node.getNodeName();
		
		// if description node
		if (node.getNodeType() == Node.ELEMENT_NODE && nodeName.equals("description")){
			// unfortunately we still need to snag the PCDATA child node for the actual text
			NodeList children = node.getChildNodes();
			if (children != null && children.getLength() == 1){
				if (children.item(0).getNodeType() == Node.TEXT_NODE){
					parent.PCDATA = children.item(0).getNodeValue();
					return;
				} 
			}
			fatalNodeError("Unexpected child \"" + nodeName 
					+"\" node of parent \"" + parent.Name + "\".");
		// if infoflow node
		} else if (node.getNodeType() == Node.ELEMENT_NODE && nodeName.equals("infoflow")){
			NamedNodeMap attrList =	node.getAttributes();
			// the required attributes
			int attrLen = 0;
			if(attrList != null){
				attrLen = attrList.getLength();
			} else{
				fatalNodeError("Missing attribute in infoflow." 
						+ "  \"type\" and \"weight\" are required attributes.");
			}

			Node typeNode = attrList.getNamedItem("type");
			Node weightNode = attrList.getNamedItem("weight");
				
			String type = typeNode.getNodeValue();
			if (typeNode == null || 
					(!type.equals("none") && weightNode == null))
				fatalNodeError("Missing attribute in infoflow." 
						+ "  \"type\" and \"weight\" are required attributes (unless type is none).");

			if (type.equals("read")){
				parent.Type = InterfaceType.Read;
				parent.Weight = Integer.parseInt(weightNode.getNodeValue());
			}else if (type.equals("write")){
				parent.Type = InterfaceType.Write;
				parent.Weight = Integer.parseInt(weightNode.getNodeValue());
			}else if (type.equals("both")){
				parent.Type = InterfaceType.Both;
				parent.Weight = Integer.parseInt(weightNode.getNodeValue());
			}else if (type.equals("none")){
				parent.Type = InterfaceType.None;
				parent.Weight = -1;
			} else {
				System.err.println("Infoflow type must be read, write, both, or none!"); 
			}
			
		} else if (node.getNodeType() == Node.ELEMENT_NODE && nodeName.equals("parameter")){
			NamedNodeMap attrList =	node.getAttributes();
			// the required attributes
			int attrLen = 0;
			if(attrList != null){
				attrLen = attrList.getLength();
			} else{
				fatalNodeError("Missing attribute in parameter \"" 
						+ "\".  \"name\" is a required attribute.");
			}

			Node nameNode = attrList.getNamedItem("name");
						
			if (nameNode == null )
				fatalNodeError("Missing attribute in parameter \"" 
						+ "\".  \"name\" is a required attribute.");

			String paramName = nameNode.getNodeValue();
		
			Docgen.Debug("Creating parameter: " + paramName);
			param = new Parameter(paramName, parent);

			// unfortunately we still need to snag the PCDATA child node for the actual text
			NodeList children = node.getChildNodes();
			if (children != null && children.getLength() == 1){
				if (children.item(0).getNodeType() == Node.TEXT_NODE){
					param.PCDATA = children.item(0).getNodeValue();
				} 
			} else {
				fatalNodeError("Unexpected child \"" 
						+"\" node of parameter.");
			}
				
			// take care of the attributes
			for(int i = 0; i < attrLen; i++){
				Node attrNode = attrList.item(i);
				String attrName = attrNode.getNodeName();
				String attrValue = attrNode.getNodeValue();
				if (!attrName.equals("name")){
					Docgen.Debug("\tAdding attribute: " + attrNode.getNodeName()
							+ "=" + attrValue);
					param.AddAttribute(attrName,attrValue);
				}
			}
		} else if (!isEmptyTextNode(node)){
			fatalNodeError("Unexpected child \"" + nodeName 
					+"\" node of parent \"" + parent.Name + "\".");
		}
		
		// recurse over children if both parent and param defined
		if (param != null && parent != null){
			// the containsKey check verified no duplicate module
			// FIXME: containsKey() check for duplicate
			parent.Children.put(param.Name, param);
		}
	}

	public boolean isEmptyTextNode(Node node){
		/*
		 * FIXME: remove once properly validating
		 * Since we aren't validating yet we needed our
		 * own pointless whitespace remover.
		 */

		if (node.getNodeType() == Node.TEXT_NODE &&
				node.getNodeValue().trim().length() == 0)
				return true;
		return false;
	}	
	
	public void fatalNodeError(String msg) 
	throws SAXException {
		// FIXME: figure out how to throw SAXParseException w/ location
		throw new SAXException(msg);
	}
}