Package com.btaz.datautil.xml.model

Source Code of com.btaz.datautil.xml.model.Document

package com.btaz.datautil.xml.model;

import com.btaz.datautil.xml.model.querypath.PathQueryParser;

import java.util.List;

/**
* The Document builder class has two major purposes:
* - make it easy to build a model from XmlStax parser events
* - make it easy to build a model for unit test purposes
* User: msundell
*/
public class Document implements Cloneable {
    private String name;
    private Element root;
    private Element current;
    private ElementBuilder elementBuilder;

    /**
     * Initialize an empty XML document
     */
    public Document() {
        this(null);
    }

    /**
     * Initialize an empty XML document and give it a name
     */
    public Document(String name) {
        this.name = name;
        elementBuilder = new ElementBuilder();
    }

    /**
     * Get the name of this document
     * @return {@code String} name or null if not set
     */
    public String getName() {
        return name;
    }

    /**
     * Set the name of this document
     * @param name document name
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * Get the root element for this {@code Document}
     * @return {@code Element}
     */
    public Element getRoot() {
        return root;
    }

    /**
     * Add a new XML fragment to this {@code Document}
     * @return {@code Document} so that you can easily chain adds
     */
    public Document addElement(String xmlFragment) {
        Element element = elementBuilder.getElement(xmlFragment);
        return addElement(element);
    }

    /**
     * Add a new {@code Element} to this {@code Document}
     * @return {@code Document} so that you can easily chain adds
     */
    public Document addElement(Element element) {
        if(current != null) {
            // attach to existing model
            current.addChildElement(element);
        } else {
            // attach to a new model
            root =  element;
        }
        if(! element.isEmptyElementTag()) {
            current = element;
        }
        return this;
    }

    /**
     * End this Element tag. This will effectively move the document cursor to the parent element of that last added
     * {@code Element}
     * <pre>
     *     // create this structure:
     *     // <fruits>
     *     //     <banana></banana>
     *     //     <orange />
     *     // </fruits>
     *     //
     *     // Note: any elements that are not closed at the end are auto-closed i.e. </fruits>
     *     Document doc = new Document()
     *         .addElement("<fruits>")
     *         .addElement("<banana>");
     *         .endElement();
     *         .addElement("<orange />"); // empty element tags do not need the endElement() call
     * </pre>
     * @return {@code Document} instance
     */
    public Document endElement() {
        current = current.getParent();
        return this;
    }

    /**
     * Add a {@code Content} element. The cursor will point to the parent element after calling this method since child
     * elements that are of the Content type can't have child elements.
     * @param text character data
     * @return {@code Document}
     */
    public Document addContent(String text) {
        if(current == null) {
            throw new XmlModelException("Content can not be attached as root element: " + text);
        }
        Content content = new Content(text);
        content.setParent(current);
        current.addChildElement(content);
        return this;
    }

    /**
     * Add a {@code Content} element. The cursor will point to the parent element after calling this method since child
     * elements that are of the Content type can't have child elements.
     * @param content {@code Content} object
     * @return {@code Document}
     */
    public Document addContent(Content content) {
        if(current == null) {
            throw new XmlModelException("Content can not be attached as root element: " + content.toString());
        }
        content.setParent(current);
        current.addChildElement(content);
        return this;
    }

    /**
     * Find elements through an XPath like pathQuery
     * e.g.
     *   /fruits/orange
     * will find the orange element:
     *   <fruits><orange/></fruits>
     *
     * @param pathQuery path pathQuery string
     * @return {@code List} of {@code Node} objects matching the query
     * @throws XmlModelException XML model exception
     */
    public List<Node> pathQuery(String pathQuery) throws XmlModelException {
        return new PathQueryParser(root).pathQuery(pathQuery);
    }

    /**
     * Compare to document trees. Note, this is a strict comparison. For a relaxed comparison use the comparison method.
     * @param anObject the other document
     * @return {@code boolean} true if equal to the other document
     */
    @Override
    public boolean equals(Object anObject) {
        if (this == anObject) return true;
        if (anObject == null || getClass() != anObject.getClass()) return false;

        Document document = (Document) anObject;

        //noinspection RedundantIfStatement
        if(! bfsComparison(root, document.root)) {
            return false;
        }

        return true;
    }

    /**
     * A strict breadth-first search traversal to evaluate two XML trees against each other
     * @param root first tree
     * @param other second tree
     * @return {@code boolean} true if the trees matches, false otherwise
     */
    private boolean bfsComparison(Node root, Node other) {
        if(root instanceof Content || other instanceof Content) {
            return root.equals(other);
        }
        if(! root.equals(other)) {
            return false;
        }

        List<Node> a = ((Element)root).getChildElements();
        List<Node> b = ((Element)other).getChildElements();
        if(a.size() != b.size()) {
            return false;
        }
        for(int i=0; i<a.size(); i++) {
            if(! a.get(i).equals(b.get(i))) {
                return false;
            }
        }

        for(int i=0; i<a.size(); i++) {
            if(! bfsComparison(a.get(i), b.get(i))) {
                return false;
            }
        }

        return true;
    }

    /**
     * BSF based generated hash code for an XML document tree
     * @return {@code int} hash code value
     */
    @Override
    public int hashCode() {
        int result = 0;

        if(root == null) {
            return result;
        }

        return bsfHashCode(root, result);
    }

    /**
     * Recursive hash code creator
     * @param node root node
     * @param result cumulative hash code value
     * @return result resulting cumulative hash code value
     */
    private int bsfHashCode(Node node, int result) {
        result += 31 * node.hashCode();

        if(node instanceof Content) {
            return result;
        }

        Element elem = (Element) node;
        List<Node> childElements = elem.getChildElements();

        for (Node childElement : childElements) {
            result += 31 * childElement.hashCode();
        }

        for (Node child : childElements) {
            if (child instanceof Content) {
                result += 31 * child.hashCode();
            } else {
                result = bsfHashCode(child, result);
            }
        }

        return result;
    }

    /**
     * This method is used to export a document as a XML {@code String}
     * @return {@code String} XML
     */
    @Override
    public String toString() {
        if(root == null) {
            return "";
        }
        return root.toString(true);
    }

    /**
     * This method is used to export a document as a XML {@code String}
     * @param flat export in a flat XML data format. Note that this will strip out all EOL characters
     * @return {@code String} XML
     */
    public String toString(boolean flat) {
        if(root == null) {
            return null;
        }
        return root.toString(true, flat);
    }

    /**
     * This method is used to export the root document element as a XML {@code String}
     * @return {@code String} XML
     */
    public String rootElementAsTagXml() {
        if(root == null) {
            return "";
        }
        return root.toString(false, true);
    }

    /**
     * Clone a document. This is method performs deep cloning
     * @return {@code Object} the cloned {@code Document} instance
     * @throws CloneNotSupportedException
     */
    @Override
    public Object clone() throws CloneNotSupportedException {
        super.clone();
        Document doc = new Document(name);
        if(root != null) {
            doc.addElement((Element)root.clone());
            deepCopy(root, doc.root);
        }

        return doc;
    }

    /**
     * Deep copy recursive helper method.
     * @param origElement original element
     * @param copyElement copy element
     */
    private void deepCopy(Element origElement, Element copyElement) {
        List<Node> children = origElement.getChildElements();
        for(Node node : children) {
            try {
                if(node instanceof Content) {
                    Content content = (Content) ((Content) node).clone();
                    copyElement.addChildElement(content);
                } else {
                    Element element = (Element) ((Element) node).clone();
                    copyElement.addChildElement(element);
                    deepCopy((Element)node, element);
                }
            } catch (CloneNotSupportedException e) {
                throw new XmlModelException("Unable to clone object", e);
            }
        }
    }
}
TOP

Related Classes of com.btaz.datautil.xml.model.Document

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.