mil.tatrc.physiology.datamodel.doxygen.XSDToDoxygen.java Source code

Java tutorial

Introduction

Here is the source code for mil.tatrc.physiology.datamodel.doxygen.XSDToDoxygen.java

Source

/*****************************************************************************
**
**        THIS MATERIAL MAY BE REPRODUCED BY OR FOR THE U.S. GOVERNMENT
**  PURSUANT TO THE COPYRIGHT LICENSE UNDER THE CLAUSE AT DFARS 252.227-7014.
**  COPYRIGHT(C)2015, APPLIED RESEARCH ASSOCIATES, INC., ALL RIGHTS RESERVED.
**
*****************************************************************************/
package mil.tatrc.physiology.datamodel.doxygen;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.jdom2.Comment;
import org.jdom2.Content;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.Parent;
import org.jdom2.input.SAXBuilder;

/**
 * 
 */
public class XSDToDoxygen {
    private static final String XS = "http://www.w3.org/2001/XMLSchema";

    private File inputFile;
    private File outputFile;
    private Document xsdDocument;
    private Element schemaElement;
    private PrintWriter ixxWriter;
    private LinkedList<File> filesToProcess = new LinkedList<File>();
    private LinkedList<XSDTree> roots = new LinkedList<XSDTree>();
    private HashSet<File> filesProcessed = new HashSet<File>();

    private XSDTree xsdRoot;
    /** Stack of XSDTree nodes that are currently in scope starting from the xs:schema */
    private LinkedList<XSDTree> xsdStack = new LinkedList<XSDTree>();
    /** 
     * Map of the long fully qualified defgroup name to the xsd type name 
     * e.g CodeCitationData -> Codes_CodeCitationData
     */
    private HashMap<String, String> typeNameMap = new HashMap<String, String>();

    public XSDToDoxygen(File inputFile, File outputFile) {
        this.outputFile = outputFile;
        filesToProcess.add(inputFile);
    }

    public void run() throws Exception {
        while (!filesToProcess.isEmpty()) {
            inputFile = filesToProcess.removeFirst();
            System.out.println("Reading: " + inputFile.getAbsolutePath());

            parse();
            xsdRoot = elementToTreeNode(schemaElement);
            if (xsdRoot.comment.contains("@defgroup")) {
                DefGroup dg = xsdRoot.getDefGroup();
                xsdRoot.name = dg.bareName;
            } else {
                String fn = inputFile.getName();
                xsdRoot.name = fn.substring(0, fn.indexOf('.'));
            }

            xsdStack.addLast(xsdRoot);
            processSchemaElement(schemaElement);

            roots.add(xsdRoot);
            xsdStack.clear();
        }

        try {
            System.out.println("Writing: " + outputFile.getAbsolutePath());
            ixxWriter = new PrintWriter(new FileWriter(outputFile));
            for (XSDTree root : roots) {
                ixxWriter.println("/**");
                writeTree(root);
                ixxWriter.println(" */");
                ixxWriter.println();
            }
        } finally {
            ixxWriter.close();
        }
    }

    private void parse() throws Exception {
        SAXBuilder jdomBuilder = new SAXBuilder();
        xsdDocument = jdomBuilder.build(inputFile);
        schemaElement = xsdDocument.getRootElement();
        if (!XS.equals(schemaElement.getNamespace().getURI())) {
            throw new Exception("Unexpected namespace: " + schemaElement.getNamespace());
        }
    }

    /**
     * Walk throug the Schema DOM tree building the XSDTree 
     * @param element
     * @throws IOException 
     */
    private void processSchemaElement(Element element) throws IOException {
        List<Element> children = element.getChildren();
        for (Element child : children) {
            String childName = child.getName();
            if (XSDType.isDocType(childName)) {
                XSDTree childTree = elementToTreeNode(child);
                childTree.parent = xsdStack.getLast();

                // This can't be done until the parent pointer is linked up.
                if (childTree.xsdType == XSDType.COMPLEXTYPE || childTree.xsdType == XSDType.SIMPLETYPE) {
                    DefGroup group = childTree.getDefGroup();
                    typeNameMap.put(childTree.name, group.fullName);
                }

                if (childTree.xsdType == XSDType.ENUMERATION) {
                    extractCommentFromParent(childTree);
                }

                if (childTree.xsdType == XSDType.ELEMENT) {
                    childTree.minOccurs = child.getAttributeValue("minOccurs");
                    childTree.maxOccurs = child.getAttributeValue("maxOccurs");
                }

                xsdStack.getLast().children.add(childTree);
                xsdStack.addLast(childTree);
                processSchemaElement(child);
                xsdStack.removeLast();
            } else if (childName.equals("extension") || childName.equals("restriction")) {
                String base = child.getAttributeValue("base");
                if (base != null) {
                    xsdStack.getLast().extension = base;
                }
                processSchemaElement(child);
            } else if (childName.equals("include")) {
                String location = child.getAttributeValue("schemaLocation");
                File locationFile = new File(inputFile.getParentFile(), location).getCanonicalFile();
                if (!filesProcessed.contains(locationFile)) {
                    filesToProcess.add(locationFile);
                    filesProcessed.add(locationFile);
                }
            } else {
                processSchemaElement(child);
            }
        }
    }

    /**
     * Searches for the enumeration value documentation in the parent comment.
     * If the text is there, remove it from the parent and move it down into 
     * the enumeration value. (See the documentationfor enumHEMPTAPSE3TerrainLayeredConductivityPreference)
     * @param enumeration Enumeration value to check
     */
    private void extractCommentFromParent(XSDTree enumeration) {
        XSDTree parentType = enumeration.parent;
        if (parentType.comment != null && parentType.comment.contains(enumeration.name)) {
            int enumValueIndex = parentType.comment.indexOf(enumeration.name);
            int enumStartIndex = parentType.comment.lastIndexOf('\n', enumValueIndex);
            int enumEndIndex = parentType.comment.indexOf('\n', enumValueIndex);
            if (enumEndIndex < 0)
                enumEndIndex = parentType.comment.length();

            // Extract the comment for this enum value, remove the name
            String enumComment = parentType.comment.substring(enumStartIndex + 1, enumEndIndex).trim();

            // Peform a sanity check, the comment should contain a colon to indicate
            // that the comment is defining this value (vs. possibly mentioning it
            // in passing)
            if (enumComment.contains(":")) {
                Pattern p = Pattern.compile("^\\([0-9]+\\)");
                Matcher m = p.matcher(enumComment);
                enumComment = m.replaceAll("");
                // Delete the name because this is kept in enumeration.name
                enumComment = enumComment.replace(enumeration.name, "");
                // Delete the "": surrounding where the name was
                enumComment = enumComment.replace("\"\":", "");

                // Store the result in the comment
                enumeration.comment = enumComment.trim();

                // Delete the redundant line from the parent element comment
                StringBuffer parentComment = new StringBuffer(parentType.comment);
                parentComment.delete(enumStartIndex, enumEndIndex);
                parentType.comment = parentComment.toString();
            }
        }
    }

    private void writeTree(XSDTree tree) {
        tree.collapseEnumeration();

        DefGroup defgroup = tree.getDefGroup();
        println(defgroup.toString());
        Description description = tree.getDescription(typeNameMap);
        String descriptionString = description.toString();
        if (descriptionString.length() > 0) {
            println(descriptionString);
        }

        /*
        if (!tree.comment.contains("@defgroup"))
        {
          println ("@defgroup "+tree.name);
        }
        if (tree.xsdType == XSDType.ELEMENT)
        {
          if (!tree.elementType.contains(":"))
          {
            println ("@brief @ref "+tree.elementType);
          }
        }
        println (tree.comment);
        */

        if (!tree.children.isEmpty()) {
            println("@{");
            for (XSDTree child : tree.children) {
                writeTree(child);
            }
            println("@}");
            println();
        }
    }

    /**
     * Create an XSDTree element from a schema XML element
     * @param element Schema XML element
     * @return
     */
    private XSDTree elementToTreeNode(Element element) {
        XSDTree treeNode = new XSDTree();
        for (XSDType type : XSDType.values()) {
            if (type.getTypeName().equals(element.getName())) {
                treeNode.xsdType = type;
                break;
            }
        }

        // Not a recognized type
        if (treeNode.xsdType == null) {
            return null;
        }

        if (treeNode.xsdType == XSDType.ENUMERATION) {
            treeNode.name = element.getAttributeValue("value");
        } else {
            treeNode.name = element.getAttributeValue("name");
        }
        treeNode.elementType = element.getAttributeValue("type");
        treeNode.comment = getComment(element);

        return treeNode;
    }

    /**
     * Emits a blank line to the output stream
     */
    private void println() {
        ixxWriter.println(" *");
    }

    /**
     * Prints a line to the output stream.  This inserts comment prefixes
     * in front of each line.
     * @param line
     */
    private void println(String line) {
        String[] lines = line.split("\n");
        for (String oneline : lines) {
            ixxWriter.println(" * " + oneline.trim());
        }
    }

    /**
     * Obtains any XML comments sibling content that immediately preceed this
     * element
     * @param element
     * @return
     */
    private String getComment(Element element) {
        Parent parent = element.getParent();
        int i = parent.indexOf(element);

        StringBuffer comments = new StringBuffer();

        // Search backward from this position for preceding comments
        for (int x = i - 1; x >= 0; x--) {
            Content c = parent.getContent(x);
            if (c.getCType() == Content.CType.Comment) {
                Comment comment = (Comment) c;
                String text = comment.getText().trim();
                if (isValidComment(text)) {
                    // This comment pertains to the preceding element
                    if (text.startsWith("<<")) {
                        break;
                    }
                    if (comments.length() > 0) {
                        comments.insert(0, '\n');
                    }
                    comments.insert(0, text);
                }
            } else if (c.getCType() == Content.CType.Element) {
                // Stop looking when we hit another element 
                break;
            }
        }

        // Search forward from this position for inline commentx
        for (int x = i + 1; x < parent.getContentSize(); x++) {
            Content c = parent.getContent(x);
            if (c.getCType() == Content.CType.Comment) {
                Comment comment = (Comment) c;
                String text = comment.getText().trim();
                if (isValidComment(text)) {
                    // This comment pertains to the preceding element
                    if (text.startsWith("<<")) {
                        // Strip the "<<"
                        text = text.substring(2);
                        if (comments.length() > 0) {
                            comments.insert(0, '\n');
                        }
                        comments.insert(0, text);
                    } else {
                        break;
                    }
                }
            } else if (c.getCType() == Content.CType.Element) {
                // Stop looking when we hit another element 
                break;
            }
        }
        return comments.toString();
    }

    /**
     * Verifies rule that the comment must contain brief and details tags
     * @param text Text to verify
     * @return
     */
    private boolean isValidComment(String text) {
        return text != null && text.contains("@copydoc") || text.contains("@brief");
    }

    /**
     * @param args
     */
    public static void main(String[] args) throws Exception {
        if (args.length < 2) {
            System.err.println("XSDToDoxygen <input.xsd> <output.xii>");
            System.exit(1);
        }

        File inputFile = new File(args[0]);
        if (!inputFile.exists()) {
            System.err.println("Input file " + inputFile.getAbsolutePath() + " does not exist");
            System.exit(1);
        }

        File outputFile = new File(args[1]);
        if (!outputFile.getParentFile().isDirectory()) {
            System.err.println(
                    "Output file directory does not exist " + outputFile.getParentFile().getAbsolutePath());
            System.exit(1);
        }

        XSDToDoxygen xsdToDoxygen = new XSDToDoxygen(inputFile, outputFile);

        xsdToDoxygen.run();
    }

}