org.apache.ddlutils.io.DatabaseIO.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.ddlutils.io.DatabaseIO.java

Source

package org.apache.ddlutils.io;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.StringReader;
import java.io.Writer;

import javax.xml.namespace.QName;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.transform.stream.StreamSource;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ddlutils.model.CascadeActionEnum;
import org.apache.ddlutils.model.Column;
import org.apache.ddlutils.model.Database;
import org.apache.ddlutils.model.ForeignKey;
import org.apache.ddlutils.model.Index;
import org.apache.ddlutils.model.IndexColumn;
import org.apache.ddlutils.model.NonUniqueIndex;
import org.apache.ddlutils.model.Reference;
import org.apache.ddlutils.model.Table;
import org.apache.ddlutils.model.UniqueIndex;
import org.xml.sax.InputSource;

/**
 * This class provides functions to read and write database models from/to XML.
 * 
 * @version $Revision$
 */
public class DatabaseIO {
    /** The name of the XML attribute use to denote that teh content of a data XML
    element uses Base64 encoding. */
    public static final String BASE64_ATTR_NAME = "base64";

    /** The namespace used by DdlUtils. */
    public static final String DDLUTILS_NAMESPACE = "http://db.apache.org/ddlutils/schema/1.1";

    /** Qualified name of the column element. */
    public static final QName QNAME_ELEMENT_COLUMN = new QName(DDLUTILS_NAMESPACE, "column");
    /** Qualified name of the database element. */
    public static final QName QNAME_ELEMENT_DATABASE = new QName(DDLUTILS_NAMESPACE, "database");
    /** Qualified name of the foreign-key element. */
    public static final QName QNAME_ELEMENT_FOREIGN_KEY = new QName(DDLUTILS_NAMESPACE, "foreign-key");
    /** Qualified name of the index element. */
    public static final QName QNAME_ELEMENT_INDEX = new QName(DDLUTILS_NAMESPACE, "index");
    /** Qualified name of the index-column element. */
    public static final QName QNAME_ELEMENT_INDEX_COLUMN = new QName(DDLUTILS_NAMESPACE, "index-column");
    /** Qualified name of the reference element. */
    public static final QName QNAME_ELEMENT_REFERENCE = new QName(DDLUTILS_NAMESPACE, "reference");
    /** Qualified name of the table element. */
    public static final QName QNAME_ELEMENT_TABLE = new QName(DDLUTILS_NAMESPACE, "table");
    /** Qualified name of the unique element. */
    public static final QName QNAME_ELEMENT_UNIQUE = new QName(DDLUTILS_NAMESPACE, "unique");
    /** Qualified name of the unique-column element. */
    public static final QName QNAME_ELEMENT_UNIQUE_COLUMN = new QName(DDLUTILS_NAMESPACE, "unique-column");

    /** Qualified name of the autoIncrement attribute. */
    public static final QName QNAME_ATTRIBUTE_AUTO_INCREMENT = new QName(DDLUTILS_NAMESPACE, "autoIncrement");
    /** Qualified name of the default attribute. */
    public static final QName QNAME_ATTRIBUTE_DEFAULT = new QName(DDLUTILS_NAMESPACE, "default");
    /** Qualified name of the defaultIdMethod attribute. */
    public static final QName QNAME_ATTRIBUTE_DEFAULT_ID_METHOD = new QName(DDLUTILS_NAMESPACE, "defaultIdMethod");
    /** Qualified name of the description attribute. */
    public static final QName QNAME_ATTRIBUTE_DESCRIPTION = new QName(DDLUTILS_NAMESPACE, "description");
    /** Qualified name of the foreign attribute. */
    public static final QName QNAME_ATTRIBUTE_FOREIGN = new QName(DDLUTILS_NAMESPACE, "foreign");
    /** Qualified name of the foreignTable attribute. */
    public static final QName QNAME_ATTRIBUTE_FOREIGN_TABLE = new QName(DDLUTILS_NAMESPACE, "foreignTable");
    /** Qualified name of the javaName attribute. */
    public static final QName QNAME_ATTRIBUTE_JAVA_NAME = new QName(DDLUTILS_NAMESPACE, "javaName");
    /** Qualified name of the local attribute. */
    public static final QName QNAME_ATTRIBUTE_LOCAL = new QName(DDLUTILS_NAMESPACE, "local");
    /** Qualified name of the name attribute. */
    public static final QName QNAME_ATTRIBUTE_NAME = new QName(DDLUTILS_NAMESPACE, "name");
    /** Qualified name of the onDelete attribute. */
    public static final QName QNAME_ATTRIBUTE_ON_DELETE = new QName(DDLUTILS_NAMESPACE, "onDelete");
    /** Qualified name of the onUpdate attribute. */
    public static final QName QNAME_ATTRIBUTE_ON_UPDATE = new QName(DDLUTILS_NAMESPACE, "onUpdate");
    /** Qualified name of the primaryKey attribute. */
    public static final QName QNAME_ATTRIBUTE_PRIMARY_KEY = new QName(DDLUTILS_NAMESPACE, "primaryKey");
    /** Qualified name of the required attribute. */
    public static final QName QNAME_ATTRIBUTE_REQUIRED = new QName(DDLUTILS_NAMESPACE, "required");
    /** Qualified name of the size attribute. */
    public static final QName QNAME_ATTRIBUTE_SIZE = new QName(DDLUTILS_NAMESPACE, "size");
    /** Qualified name of the type attribute. */
    public static final QName QNAME_ATTRIBUTE_TYPE = new QName(DDLUTILS_NAMESPACE, "type");
    /** Qualified name of the version attribute. */
    public static final QName QNAME_ATTRIBUTE_VERSION = new QName(DDLUTILS_NAMESPACE, "version");

    /** The log. */
    private final Log _log = LogFactory.getLog(DatabaseIO.class);

    /** Whether to validate the XML. */
    private boolean _validateXml = true;
    /** Whether to use the internal dtd that comes with DdlUtils. */
    private boolean _useInternalDtd = true;

    /**
     * Returns whether XML is validated upon reading it.
     * 
     * @return <code>true</code> if read XML is validated
     */
    public boolean isValidateXml() {
        return _validateXml;
    }

    /**
     * Specifies whether XML shall be validated upon reading it.
     * 
     * @param validateXml <code>true</code> if read XML shall be validated
     */
    public void setValidateXml(boolean validateXml) {
        _validateXml = validateXml;
    }

    /**
     * Returns whether the internal dtd that comes with DdlUtils is used.
     * 
     * @return <code>true</code> if parsing uses the internal dtd
     * @deprecated Switched to XML schema, and the internal XML schema should always be used 
     */
    public boolean isUseInternalDtd() {
        return _useInternalDtd;
    }

    /**
     * Specifies whether the internal dtd is to be used.
     *
     * @param useInternalDtd Whether to use the internal dtd
     * @deprecated Switched to XML schema, and the internal XML schema should always be used 
     */
    public void setUseInternalDtd(boolean useInternalDtd) {
        _useInternalDtd = useInternalDtd;
    }

    /**
     * Reads the database model contained in the specified file.
     * 
     * @param filename The model file name
     * @return The database model
     */
    public Database read(String filename) throws DdlUtilsXMLException {
        return read(new File(filename));
    }

    /**
     * Reads the database model contained in the specified file.
     * 
     * @param file The model file
     * @return The database model
     */
    public Database read(File file) throws DdlUtilsXMLException {
        FileReader reader = null;

        if (_validateXml) {
            try {
                reader = new FileReader(file);
                new ModelValidator().validate(new StreamSource(reader));
            } catch (IOException ex) {
                throw new DdlUtilsXMLException(ex);
            } finally {
                if (reader != null) {
                    try {
                        reader.close();
                    } catch (IOException ex) {
                        _log.warn("Could not close reader for file " + file.getAbsolutePath());
                    }
                    reader = null;
                }
            }
        }

        try {
            reader = new FileReader(file);
            return read(getXMLInputFactory().createXMLStreamReader(reader));
        } catch (XMLStreamException ex) {
            throw new DdlUtilsXMLException(ex);
        } catch (IOException ex) {
            throw new DdlUtilsXMLException(ex);
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException ex) {
                    _log.warn("Could not close reader for file " + file.getAbsolutePath());
                }
            }
        }
    }

    /**
     * Reads the database model given by the reader. Note that this method does not close the
     * given reader.
     * 
     * @param reader The reader that returns the model XML
     * @return The database model
     */
    public Database read(Reader reader) throws DdlUtilsXMLException {
        try {
            if (_validateXml) {
                StringBuffer tmpXml = new StringBuffer();
                char[] buf = new char[4096];
                int len;

                while ((len = reader.read(buf)) >= 0) {
                    tmpXml.append(buf, 0, len);
                }
                new ModelValidator().validate(new StreamSource(new StringReader(tmpXml.toString())));
                return read(getXMLInputFactory().createXMLStreamReader(new StringReader(tmpXml.toString())));
            } else {
                return read(getXMLInputFactory().createXMLStreamReader(reader));
            }
        } catch (XMLStreamException ex) {
            throw new DdlUtilsXMLException(ex);
        } catch (IOException ex) {
            throw new DdlUtilsXMLException(ex);
        }
    }

    /**
     * Reads the database model from the given input source.
     *
     * @param source The input source
     * @return The database model
     */
    public Database read(InputSource source) throws DdlUtilsXMLException {
        return read(source.getCharacterStream());
    }

    /**
     * Creates a new, initialized XML input factory object.
     * 
     * @return The factory object
     */
    private XMLInputFactory getXMLInputFactory() {
        XMLInputFactory factory = XMLInputFactory.newInstance();

        factory.setProperty("javax.xml.stream.isCoalescing", Boolean.TRUE);
        factory.setProperty("javax.xml.stream.isNamespaceAware", Boolean.TRUE);
        return factory;
    }

    /**
     * Reads the database model from the given XML stream reader.
     * 
     * @param xmlReader The reader
     * @return The database model
     */
    private Database read(XMLStreamReader xmlReader) throws DdlUtilsXMLException {
        Database model = null;

        try {
            while (xmlReader.getEventType() != XMLStreamReader.START_ELEMENT) {
                if (xmlReader.next() == XMLStreamReader.END_DOCUMENT) {
                    return null;
                }
            }
            if (isSameAs(xmlReader.getName(), QNAME_ELEMENT_DATABASE)) {
                model = readDatabaseElement(xmlReader);
            }
        } catch (IOException ex) {
            throw new DdlUtilsXMLException(ex);
        } catch (XMLStreamException ex) {
            throw new DdlUtilsXMLException(ex);
        }
        if (model != null) {
            model.initialize();
        }
        return model;
    }

    /**
     * Reads a database element from the XML stream reader.
     * 
     * @param xmlReader The reader
     * @return The database object
     */
    private Database readDatabaseElement(XMLStreamReader xmlReader) throws XMLStreamException, IOException {
        Database model = new Database();

        for (int idx = 0; idx < xmlReader.getAttributeCount(); idx++) {
            QName attrQName = xmlReader.getAttributeName(idx);

            if (isSameAs(attrQName, QNAME_ATTRIBUTE_NAME)) {
                model.setName(xmlReader.getAttributeValue(idx));
            } else if (isSameAs(attrQName, QNAME_ATTRIBUTE_DEFAULT_ID_METHOD)) {
                model.setIdMethod(xmlReader.getAttributeValue(idx));
            } else if (isSameAs(attrQName, QNAME_ATTRIBUTE_VERSION)) {
                model.setVersion(xmlReader.getAttributeValue(idx));
            }
        }
        readTableElements(xmlReader, model);
        consumeRestOfElement(xmlReader);
        return model;
    }

    /**
     * Reads table elements from the XML stream reader and adds them to the given
     * database model.
     * 
     * @param xmlReader The reader
     * @param model     The database model to add the table objects to
     */
    private void readTableElements(XMLStreamReader xmlReader, Database model)
            throws XMLStreamException, IOException {
        int eventType = XMLStreamReader.START_ELEMENT;

        while (eventType != XMLStreamReader.END_ELEMENT) {
            eventType = xmlReader.next();
            if (eventType == XMLStreamReader.START_ELEMENT) {
                if (isSameAs(xmlReader.getName(), QNAME_ELEMENT_TABLE)) {
                    model.addTable(readTableElement(xmlReader));
                } else {
                    readOverElement(xmlReader);
                }
            }
        }
    }

    /**
     * Reads a table element from the XML stream reader.
     * 
     * @param xmlReader The reader
     * @return The table object
     */
    private Table readTableElement(XMLStreamReader xmlReader) throws XMLStreamException, IOException {
        Table table = new Table();

        for (int idx = 0; idx < xmlReader.getAttributeCount(); idx++) {
            QName attrQName = xmlReader.getAttributeName(idx);

            if (isSameAs(attrQName, QNAME_ATTRIBUTE_NAME)) {
                table.setName(xmlReader.getAttributeValue(idx));
            } else if (isSameAs(attrQName, QNAME_ATTRIBUTE_DESCRIPTION)) {
                table.setDescription(xmlReader.getAttributeValue(idx));
            }
        }
        readTableSubElements(xmlReader, table);
        consumeRestOfElement(xmlReader);
        return table;
    }

    /**
     * Reads table sub elements (column, foreign key, index) from the XML stream reader and adds
     * them to the given table.
     * 
     * @param xmlReader The reader
     * @param table     The table
     */
    private void readTableSubElements(XMLStreamReader xmlReader, Table table)
            throws XMLStreamException, IOException {
        int eventType = XMLStreamReader.START_ELEMENT;

        while (eventType != XMLStreamReader.END_ELEMENT) {
            eventType = xmlReader.next();
            if (eventType == XMLStreamReader.START_ELEMENT) {
                QName elemQName = xmlReader.getName();

                if (isSameAs(elemQName, QNAME_ELEMENT_COLUMN)) {
                    table.addColumn(readColumnElement(xmlReader));
                } else if (isSameAs(elemQName, QNAME_ELEMENT_FOREIGN_KEY)) {
                    table.addForeignKey(readForeignKeyElement(xmlReader));
                } else if (isSameAs(elemQName, QNAME_ELEMENT_INDEX)) {
                    table.addIndex(readIndexElement(xmlReader));
                } else if (isSameAs(elemQName, QNAME_ELEMENT_UNIQUE)) {
                    table.addIndex(readUniqueElement(xmlReader));
                } else {
                    readOverElement(xmlReader);
                }
            }
        }
    }

    /**
     * Reads a column element from the XML stream reader.
     * 
     * @param xmlReader The reader
     * @return The column object
     */
    private Column readColumnElement(XMLStreamReader xmlReader) throws XMLStreamException, IOException {
        Column column = new Column();

        for (int idx = 0; idx < xmlReader.getAttributeCount(); idx++) {
            QName attrQName = xmlReader.getAttributeName(idx);

            if (isSameAs(attrQName, QNAME_ATTRIBUTE_NAME)) {
                column.setName(xmlReader.getAttributeValue(idx));
            } else if (isSameAs(attrQName, QNAME_ATTRIBUTE_PRIMARY_KEY)) {
                column.setPrimaryKey(getAttributeValueAsBoolean(xmlReader, idx));
            } else if (isSameAs(attrQName, QNAME_ATTRIBUTE_REQUIRED)) {
                column.setRequired(getAttributeValueAsBoolean(xmlReader, idx));
            } else if (isSameAs(attrQName, QNAME_ATTRIBUTE_TYPE)) {
                column.setType(xmlReader.getAttributeValue(idx));
            } else if (isSameAs(attrQName, QNAME_ATTRIBUTE_SIZE)) {
                column.setSize(getAttributeValueBeingNullAware(xmlReader, idx));
            } else if (isSameAs(attrQName, QNAME_ATTRIBUTE_DEFAULT)) {
                column.setDefaultValue(xmlReader.getAttributeValue(idx));
            } else if (isSameAs(attrQName, QNAME_ATTRIBUTE_AUTO_INCREMENT)) {
                column.setAutoIncrement(getAttributeValueAsBoolean(xmlReader, idx));
            } else if (isSameAs(attrQName, QNAME_ATTRIBUTE_DESCRIPTION)) {
                column.setDescription(xmlReader.getAttributeValue(idx));
            } else if (isSameAs(attrQName, QNAME_ATTRIBUTE_JAVA_NAME)) {
                column.setJavaName(xmlReader.getAttributeValue(idx));
            }
        }
        consumeRestOfElement(xmlReader);
        return column;
    }

    /**
     * Reads a foreign key element from the XML stream reader.
     * 
     * @param xmlReader The reader
     * @return The foreign key object
     */
    private ForeignKey readForeignKeyElement(XMLStreamReader xmlReader) throws XMLStreamException, IOException {
        ForeignKey foreignKey = new ForeignKey();

        for (int idx = 0; idx < xmlReader.getAttributeCount(); idx++) {
            QName attrQName = xmlReader.getAttributeName(idx);

            if (isSameAs(attrQName, QNAME_ATTRIBUTE_FOREIGN_TABLE)) {
                foreignKey.setForeignTableName(xmlReader.getAttributeValue(idx));
            } else if (isSameAs(attrQName, QNAME_ATTRIBUTE_NAME)) {
                foreignKey.setName(xmlReader.getAttributeValue(idx));
            } else if (isSameAs(attrQName, QNAME_ATTRIBUTE_ON_UPDATE)) {
                foreignKey.setOnUpdate(getAttributeValueAsCascadeEnum(xmlReader, idx));
            } else if (isSameAs(attrQName, QNAME_ATTRIBUTE_ON_DELETE)) {
                foreignKey.setOnDelete(getAttributeValueAsCascadeEnum(xmlReader, idx));
            }
        }
        readReferenceElements(xmlReader, foreignKey);
        consumeRestOfElement(xmlReader);
        return foreignKey;
    }

    /**
     * Reads reference elements from the XML stream reader and adds them to the given
     * foreign key.
     * 
     * @param xmlReader  The reader
     * @param foreignKey The foreign key
     */
    private void readReferenceElements(XMLStreamReader xmlReader, ForeignKey foreignKey)
            throws XMLStreamException, IOException {
        int eventType = XMLStreamReader.START_ELEMENT;

        while (eventType != XMLStreamReader.END_ELEMENT) {
            eventType = xmlReader.next();
            if (eventType == XMLStreamReader.START_ELEMENT) {
                QName elemQName = xmlReader.getName();

                if (isSameAs(elemQName, QNAME_ELEMENT_REFERENCE)) {
                    foreignKey.addReference(readReferenceElement(xmlReader));
                } else {
                    readOverElement(xmlReader);
                }
            }
        }
    }

    /**
     * Reads a reference element from the XML stream reader.
     * 
     * @param xmlReader The reader
     * @return The reference object
     */
    private Reference readReferenceElement(XMLStreamReader xmlReader) throws XMLStreamException, IOException {
        Reference reference = new Reference();

        for (int idx = 0; idx < xmlReader.getAttributeCount(); idx++) {
            QName attrQName = xmlReader.getAttributeName(idx);

            if (isSameAs(attrQName, QNAME_ATTRIBUTE_LOCAL)) {
                reference.setLocalColumnName(xmlReader.getAttributeValue(idx));
            } else if (isSameAs(attrQName, QNAME_ATTRIBUTE_FOREIGN)) {
                reference.setForeignColumnName(xmlReader.getAttributeValue(idx));
            }
        }
        consumeRestOfElement(xmlReader);
        return reference;
    }

    /**
     * Reads an index element from the XML stream reader.
     * 
     * @param xmlReader The reader
     * @return The index object
     */
    private Index readIndexElement(XMLStreamReader xmlReader) throws XMLStreamException, IOException {
        Index index = new NonUniqueIndex();

        for (int idx = 0; idx < xmlReader.getAttributeCount(); idx++) {
            QName attrQName = xmlReader.getAttributeName(idx);

            if (isSameAs(attrQName, QNAME_ATTRIBUTE_NAME)) {
                index.setName(xmlReader.getAttributeValue(idx));
            }
        }
        readIndexColumnElements(xmlReader, index);
        consumeRestOfElement(xmlReader);
        return index;
    }

    /**
     * Reads an unique index element from the XML stream reader.
     * 
     * @param xmlReader The reader
     * @return The unique index object
     */
    private Index readUniqueElement(XMLStreamReader xmlReader) throws XMLStreamException, IOException {
        Index index = new UniqueIndex();

        for (int idx = 0; idx < xmlReader.getAttributeCount(); idx++) {
            QName attrQName = xmlReader.getAttributeName(idx);

            if (isSameAs(attrQName, QNAME_ATTRIBUTE_NAME)) {
                index.setName(xmlReader.getAttributeValue(idx));
            }
        }
        readUniqueColumnElements(xmlReader, index);
        consumeRestOfElement(xmlReader);
        return index;
    }

    /**
     * Reads index column elements from the XML stream reader and adds them to the given
     * index object.
     * 
     * @param xmlReader The reader
     * @param index     The index object
     */
    private void readIndexColumnElements(XMLStreamReader xmlReader, Index index)
            throws XMLStreamException, IOException {
        int eventType = XMLStreamReader.START_ELEMENT;

        while (eventType != XMLStreamReader.END_ELEMENT) {
            eventType = xmlReader.next();
            if (eventType == XMLStreamReader.START_ELEMENT) {
                QName elemQName = xmlReader.getName();

                if (isSameAs(elemQName, QNAME_ELEMENT_INDEX_COLUMN)) {
                    index.addColumn(readIndexColumnElement(xmlReader));
                } else {
                    readOverElement(xmlReader);
                }
            }
        }
    }

    /**
     * Reads unique index column elements from the XML stream reader and adds them to the given
     * index object.
     * 
     * @param xmlReader The reader
     * @param index     The index object
     */
    private void readUniqueColumnElements(XMLStreamReader xmlReader, Index index)
            throws XMLStreamException, IOException {
        int eventType = XMLStreamReader.START_ELEMENT;

        while (eventType != XMLStreamReader.END_ELEMENT) {
            eventType = xmlReader.next();
            if (eventType == XMLStreamReader.START_ELEMENT) {
                QName elemQName = xmlReader.getName();

                if (isSameAs(elemQName, QNAME_ELEMENT_UNIQUE_COLUMN)) {
                    index.addColumn(readIndexColumnElement(xmlReader));
                } else {
                    readOverElement(xmlReader);
                }
            }
        }
    }

    /**
     * Reads an index column element from the XML stream reader.
     * 
     * @param xmlReader The reader
     * @return The index column object
     */
    private IndexColumn readIndexColumnElement(XMLStreamReader xmlReader) throws XMLStreamException, IOException {
        IndexColumn indexColumn = new IndexColumn();

        for (int idx = 0; idx < xmlReader.getAttributeCount(); idx++) {
            QName attrQName = xmlReader.getAttributeName(idx);

            if (isSameAs(attrQName, QNAME_ATTRIBUTE_NAME)) {
                indexColumn.setName(xmlReader.getAttributeValue(idx));
            } else if (isSameAs(attrQName, QNAME_ATTRIBUTE_SIZE)) {
                indexColumn.setSize(getAttributeValueBeingNullAware(xmlReader, idx));
            }
        }
        consumeRestOfElement(xmlReader);
        return indexColumn;
    }

    /**
     * Compares the given qnames. This specifically ignores the namespace
     * uri of the other qname if the namespace of the current element is
     * empty.
     * 
     * @param curElemQName The qname of the current element
     * @param qName        The qname to compare to
     * @return <code>true</code> if they are the same
     */
    private boolean isSameAs(QName curElemQName, QName qName) {
        if (StringUtils.isEmpty(curElemQName.getNamespaceURI())) {
            return qName.getLocalPart().equals(curElemQName.getLocalPart());
        } else {
            return qName.equals(curElemQName);
        }
    }

    /**
     * Returns the value of the indicated attribute of the current element as a string.
     * This method can handle "null" in which case it returns a null object.
     * 
     * @param xmlReader    The xml reader
     * @param attributeIdx The index of the attribute
     * @return The attribute's value
     */
    private String getAttributeValueBeingNullAware(XMLStreamReader xmlReader, int attributeIdx)
            throws DdlUtilsXMLException {
        String value = xmlReader.getAttributeValue(attributeIdx);

        return "null".equalsIgnoreCase(value) ? null : value;
    }

    /**
     * Returns the value of the indicated attribute of the current element as a boolean.
     * If the value is not a valid boolean, then an exception is thrown.
     * 
     * @param xmlReader    The xml reader
     * @param attributeIdx The index of the attribute
     * @return The attribute's value as a boolean
     */
    private boolean getAttributeValueAsBoolean(XMLStreamReader xmlReader, int attributeIdx)
            throws DdlUtilsXMLException {
        String value = xmlReader.getAttributeValue(attributeIdx);

        if ("true".equalsIgnoreCase(value)) {
            return true;
        } else if ("false".equalsIgnoreCase(value)) {
            return false;
        } else {
            throw new DdlUtilsXMLException("Illegal boolean value '" + value + "' for attribute "
                    + xmlReader.getAttributeLocalName(attributeIdx));
        }
    }

    /**
     * Returns the value of the indicated attribute of the current element as a boolean.
     * If the value is not a valid boolean, then an exception is thrown.
     * 
     * @param xmlReader    The xml reader
     * @param attributeIdx The index of the attribute
     * @return The attribute's value as a boolean
     */
    private CascadeActionEnum getAttributeValueAsCascadeEnum(XMLStreamReader xmlReader, int attributeIdx)
            throws DdlUtilsXMLException {
        String value = xmlReader.getAttributeValue(attributeIdx);
        CascadeActionEnum enumValue = value == null ? null : CascadeActionEnum.getEnum(value.toLowerCase());

        if (enumValue == null) {
            throw new DdlUtilsXMLException("Illegal boolean value '" + value + "' for attribute "
                    + xmlReader.getAttributeLocalName(attributeIdx));
        } else {
            return enumValue;
        }
    }

    /**
     * Consumes the rest of the current element. This assumes that the current XML stream
     * event type is not START_ELEMENT.
     * 
     * @param reader The xml reader
     */
    private void consumeRestOfElement(XMLStreamReader reader) throws XMLStreamException {
        int eventType = reader.getEventType();

        while ((eventType != XMLStreamReader.END_ELEMENT) && (eventType != XMLStreamReader.END_DOCUMENT)) {
            eventType = reader.next();
        }
    }

    /**
     * Reads over the current element. This assumes that the current XML stream event type is
     * START_ELEMENT.
     *  
     * @param reader The xml reader
     */
    private void readOverElement(XMLStreamReader reader) throws XMLStreamException {
        int depth = 1;

        while (depth > 0) {
            int eventType = reader.next();

            if (eventType == XMLStreamReader.START_ELEMENT) {
                depth++;
            } else if (eventType == XMLStreamReader.END_ELEMENT) {
                depth--;
            }
        }
    }

    /**
     * Writes the database model to the specified file.
     * 
     * @param model    The database model
     * @param filename The model file name
     */
    public void write(Database model, String filename) throws DdlUtilsXMLException {
        try {
            BufferedWriter writer = null;

            try {
                writer = new BufferedWriter(new FileWriter(filename));

                write(model, writer);
                writer.flush();
            } finally {
                if (writer != null) {
                    writer.close();
                }
            }
        } catch (Exception ex) {
            throw new DdlUtilsXMLException(ex);
        }
    }

    /**
     * Writes the database model to the given output stream. Note that this method
     * does not flush or close the stream.
     * 
     * @param model  The database model
     * @param output The output stream
     */
    public void write(Database model, OutputStream output) throws DdlUtilsXMLException {
        PrettyPrintingXmlWriter xmlWriter = new PrettyPrintingXmlWriter(output, "UTF-8");

        xmlWriter.setDefaultNamespace(DDLUTILS_NAMESPACE);
        xmlWriter.writeDocumentStart();
        writeDatabaseElement(model, xmlWriter);
        xmlWriter.writeDocumentEnd();
    }

    /**
     * Writes the database model to the given output writer. Note that this method
     * does not flush or close the writer.
     * 
     * @param model  The database model
     * @param output The output writer
     */
    public void write(Database model, Writer output) throws DdlUtilsXMLException {
        PrettyPrintingXmlWriter xmlWriter = new PrettyPrintingXmlWriter(output, "UTF-8");

        xmlWriter.setDefaultNamespace(DDLUTILS_NAMESPACE);
        xmlWriter.writeDocumentStart();
        writeDatabaseElement(model, xmlWriter);
        xmlWriter.writeDocumentEnd();
    }

    /**
     * Writes the database model to the given XML writer.
     * 
     * @param model     The database model
     * @param xmlWriter The XML writer
     */
    private void writeDatabaseElement(Database model, PrettyPrintingXmlWriter xmlWriter)
            throws DdlUtilsXMLException {
        writeElementStart(xmlWriter, QNAME_ELEMENT_DATABASE);
        xmlWriter.writeNamespace(null, DDLUTILS_NAMESPACE);
        writeAttribute(xmlWriter, QNAME_ATTRIBUTE_NAME, model.getName());
        writeAttribute(xmlWriter, QNAME_ATTRIBUTE_DEFAULT_ID_METHOD, model.getIdMethod());
        writeAttribute(xmlWriter, QNAME_ATTRIBUTE_VERSION, model.getVersion());
        if (model.getTableCount() > 0) {
            xmlWriter.printlnIfPrettyPrinting();
            for (int idx = 0; idx < model.getTableCount(); idx++) {
                writeTableElement(model.getTable(idx), xmlWriter);
            }
        }
        writeElementEnd(xmlWriter);
    }

    /**
     * Writes the table object to the given XML writer.
     * 
     * @param table     The table object
     * @param xmlWriter The XML writer
     */
    private void writeTableElement(Table table, PrettyPrintingXmlWriter xmlWriter) throws DdlUtilsXMLException {
        xmlWriter.indentIfPrettyPrinting(1);
        writeElementStart(xmlWriter, QNAME_ELEMENT_TABLE);
        writeAttribute(xmlWriter, QNAME_ATTRIBUTE_NAME, table.getName());
        writeAttribute(xmlWriter, QNAME_ATTRIBUTE_DESCRIPTION, table.getDescription());
        if ((table.getColumnCount() > 0) || (table.getForeignKeyCount() > 0) || (table.getIndexCount() > 0)) {
            xmlWriter.printlnIfPrettyPrinting();
            for (int idx = 0; idx < table.getColumnCount(); idx++) {
                writeColumnElement(table.getColumn(idx), xmlWriter);
            }
            for (int idx = 0; idx < table.getForeignKeyCount(); idx++) {
                writeForeignKeyElement(table.getForeignKey(idx), xmlWriter);
            }
            for (int idx = 0; idx < table.getIndexCount(); idx++) {
                writeIndexElement(table.getIndex(idx), xmlWriter);
            }
            xmlWriter.indentIfPrettyPrinting(1);
        }
        writeElementEnd(xmlWriter);
    }

    /**
     * Writes the column object to the given XML writer.
     * 
     * @param column    The column object
     * @param xmlWriter The XML writer
     */
    private void writeColumnElement(Column column, PrettyPrintingXmlWriter xmlWriter) throws DdlUtilsXMLException {
        xmlWriter.indentIfPrettyPrinting(2);
        writeElementStart(xmlWriter, QNAME_ELEMENT_COLUMN);
        writeAttribute(xmlWriter, QNAME_ATTRIBUTE_NAME, column.getName());
        writeAttribute(xmlWriter, QNAME_ATTRIBUTE_PRIMARY_KEY, String.valueOf(column.isPrimaryKey()));
        writeAttribute(xmlWriter, QNAME_ATTRIBUTE_REQUIRED, String.valueOf(column.isRequired()));
        writeAttribute(xmlWriter, QNAME_ATTRIBUTE_TYPE, column.getType());
        writeAttribute(xmlWriter, QNAME_ATTRIBUTE_SIZE, column.getSize());
        writeAttribute(xmlWriter, QNAME_ATTRIBUTE_DEFAULT, column.getDefaultValue());
        writeAttribute(xmlWriter, QNAME_ATTRIBUTE_AUTO_INCREMENT, String.valueOf(column.isAutoIncrement()));
        writeAttribute(xmlWriter, QNAME_ATTRIBUTE_DESCRIPTION, column.getDescription());
        writeAttribute(xmlWriter, QNAME_ATTRIBUTE_JAVA_NAME, column.getJavaName());
        writeElementEnd(xmlWriter);
    }

    /**
     * Writes the foreign key object to the given XML writer.
     * 
     * @param foreignKey The foreign key object
     * @param xmlWriter  The XML writer
     */
    private void writeForeignKeyElement(ForeignKey foreignKey, PrettyPrintingXmlWriter xmlWriter)
            throws DdlUtilsXMLException {
        xmlWriter.indentIfPrettyPrinting(2);
        writeElementStart(xmlWriter, QNAME_ELEMENT_FOREIGN_KEY);
        writeAttribute(xmlWriter, QNAME_ATTRIBUTE_FOREIGN_TABLE, foreignKey.getForeignTableName());
        writeAttribute(xmlWriter, QNAME_ATTRIBUTE_NAME, foreignKey.getName());
        if (foreignKey.getOnUpdate() != CascadeActionEnum.NONE) {
            writeAttribute(xmlWriter, QNAME_ATTRIBUTE_ON_UPDATE, foreignKey.getOnUpdate().getName());
        }
        if (foreignKey.getOnDelete() != CascadeActionEnum.NONE) {
            writeAttribute(xmlWriter, QNAME_ATTRIBUTE_ON_DELETE, foreignKey.getOnDelete().getName());
        }
        if (foreignKey.getReferenceCount() > 0) {
            xmlWriter.printlnIfPrettyPrinting();
            for (int idx = 0; idx < foreignKey.getReferenceCount(); idx++) {
                writeReferenceElement(foreignKey.getReference(idx), xmlWriter);
            }
            xmlWriter.indentIfPrettyPrinting(2);
        }
        writeElementEnd(xmlWriter);
    }

    /**
     * Writes the reference object to the given XML writer.
     * 
     * @param reference The reference object
     * @param xmlWriter The XML writer
     */
    private void writeReferenceElement(Reference reference, PrettyPrintingXmlWriter xmlWriter)
            throws DdlUtilsXMLException {
        xmlWriter.indentIfPrettyPrinting(3);
        writeElementStart(xmlWriter, QNAME_ELEMENT_REFERENCE);
        writeAttribute(xmlWriter, QNAME_ATTRIBUTE_LOCAL, reference.getLocalColumnName());
        writeAttribute(xmlWriter, QNAME_ATTRIBUTE_FOREIGN, reference.getForeignColumnName());
        writeElementEnd(xmlWriter);
    }

    /**
     * Writes the index object to the given XML writer.
     * 
     * @param index     The index object
     * @param xmlWriter The XML writer
     */
    private void writeIndexElement(Index index, PrettyPrintingXmlWriter xmlWriter) throws DdlUtilsXMLException {
        xmlWriter.indentIfPrettyPrinting(2);
        writeElementStart(xmlWriter, index.isUnique() ? QNAME_ELEMENT_UNIQUE : QNAME_ELEMENT_INDEX);
        writeAttribute(xmlWriter, QNAME_ATTRIBUTE_NAME, index.getName());
        if (index.getColumnCount() > 0) {
            xmlWriter.printlnIfPrettyPrinting();
            for (int idx = 0; idx < index.getColumnCount(); idx++) {
                writeIndexColumnElement(index.getColumn(idx), index.isUnique(), xmlWriter);
            }
            xmlWriter.indentIfPrettyPrinting(2);
        }
        writeElementEnd(xmlWriter);
    }

    /**
     * Writes the index column object to the given XML writer.
     * 
     * @param indexColumn The index column object
     * @param isUnique    Whether the index that the index column belongs to, is unique
     * @param xmlWriter   The XML writer
     */
    private void writeIndexColumnElement(IndexColumn indexColumn, boolean isUnique,
            PrettyPrintingXmlWriter xmlWriter) throws DdlUtilsXMLException {
        xmlWriter.indentIfPrettyPrinting(3);
        writeElementStart(xmlWriter, isUnique ? QNAME_ELEMENT_UNIQUE_COLUMN : QNAME_ELEMENT_INDEX_COLUMN);
        writeAttribute(xmlWriter, QNAME_ATTRIBUTE_NAME, indexColumn.getName());
        writeAttribute(xmlWriter, QNAME_ATTRIBUTE_SIZE, indexColumn.getSize());
        writeElementEnd(xmlWriter);
    }

    /**
     * Writes the start of the specified XML element to the given XML writer.
     * 
     * @param xmlWriter The xml writer
     * @param qName     The qname of the XML element
     */
    private void writeElementStart(PrettyPrintingXmlWriter xmlWriter, QName qName) throws DdlUtilsXMLException {
        xmlWriter.writeElementStart(qName.getNamespaceURI(), qName.getLocalPart());
    }

    /**
     * Writes an attribute to the given XML writer.
     * 
     * @param xmlWriter The xml writer
     * @param qName     The qname of the attribute
     * @param value     The value; if empty, then nothing is written
     */
    private void writeAttribute(PrettyPrintingXmlWriter xmlWriter, QName qName, String value)
            throws DdlUtilsXMLException {
        if (value != null) {
            xmlWriter.writeAttribute(null, qName.getLocalPart(), value);
        }
    }

    /**
     * Writes the end of the current XML element to the given XML writer.
     * 
     * @param xmlWriter The xml writer
     */
    private void writeElementEnd(PrettyPrintingXmlWriter xmlWriter) throws DdlUtilsXMLException {
        xmlWriter.writeElementEnd();
        xmlWriter.printlnIfPrettyPrinting();
    }
}