Fix for DDLUTILS-245: writeDataToFile produce broken XML

git-svn-id: https://svn.apache.org/repos/asf/db/ddlutils/trunk@1098483 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/main/java/org/apache/ddlutils/dynabean/SqlDynaBean.java b/src/main/java/org/apache/ddlutils/dynabean/SqlDynaBean.java
index 721a5e8..0cba20b 100644
--- a/src/main/java/org/apache/ddlutils/dynabean/SqlDynaBean.java
+++ b/src/main/java/org/apache/ddlutils/dynabean/SqlDynaBean.java
@@ -67,4 +67,49 @@
         }
         return result.toString();
     }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int hashCode()
+    {
+        return toString().hashCode();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean equals(Object obj)
+    {
+        if (obj instanceof SqlDynaBean)
+        {
+            SqlDynaBean other     = (SqlDynaBean)obj;
+            DynaClass   dynaClass = getDynaClass();
+
+            if (dynaClass.equals(other.getDynaClass()))
+            {
+                DynaProperty[] props = dynaClass.getDynaProperties();
+
+                for (int idx = 0; idx < props.length; idx++)
+                {
+                    Object value      = get(props[idx].getName());
+                    Object otherValue = other.get(props[idx].getName());
+
+                    if (value == null)
+                    {
+                        if (otherValue != null)
+                        {
+                            return false;
+                        }
+                    }
+                    else
+                    {
+                        return value.equals(otherValue);
+                    }
+                }
+                return true;
+            }
+        }
+        return false;
+    }
 }
diff --git a/src/main/java/org/apache/ddlutils/io/ColumnXmlWriter.java b/src/main/java/org/apache/ddlutils/io/ColumnXmlWriter.java
new file mode 100644
index 0000000..d3533b3
--- /dev/null
+++ b/src/main/java/org/apache/ddlutils/io/ColumnXmlWriter.java
@@ -0,0 +1,176 @@
+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 org.apache.ddlutils.model.Column;
+
+/**
+ * Helper class for writing columns to XML.  
+ */
+public class ColumnXmlWriter extends ModelXmlWriter
+{
+    private final int AS_TABLE_ATTRIBUTE  = 0;
+    private final int AS_SUBTAG           = 1;
+    private final int AS_COLUMN_ATTRIBUTE = 2;
+    private final int AS_VALUE            = 3;
+
+    private final String columnName;
+    private final String columnValue;
+    private final boolean nameBase64Encoded;
+    private final boolean valueBase64Encoded;
+    private final int columnFormattingMethod;
+
+    /**
+     * Creates a new column writer.
+     * 
+     * @param column The column, cannot be null
+     * @param value  The value, cannot be null
+     */
+    public ColumnXmlWriter(Column column, String value)
+    {
+        /*
+         * - attribute "column name"="column value" in the parent's (table) element
+         *   iff the column name is a valid attribute name and is not "table-name" and not "column",
+         *   and the value is a valid attribute value not longer than 255 characters
+         * - otherwise, writes a sub-element <column> with an attribute column-name that contains the name
+         *   of the column, and the body of that sub-element contains the column value,
+         *   iff the column name is a valid attribute value not longer than 255 characters. If the column
+         *   value contains illegal characters, then the column sub element will have a "base64" attribute
+         *   with the value "true" and the value will be base64 encoded
+         * - otherwise writes a sub-element <column> with a sub-element <column-name> whose
+         *   body is the name of the column, and another sub-element <column-value> whose body contains
+         *   the column value. If either the column name or value contain illegal characters, then the
+         *   corresponding sub element will have a "base64" attribute with the value "true" and its body will
+         *   be base64 encoded.
+         */
+        if (XMLUtils.hasIllegalXMLCharacters(value))
+        {
+            columnValue        = XMLUtils.base64Encode(value);
+            valueBase64Encoded = true;
+        }
+        else
+        {
+            columnValue        = value;
+            valueBase64Encoded = false;
+        }
+
+        if (XMLUtils.hasIllegalXMLCharacters(column.getName())) {
+            columnName             = XMLUtils.base64Encode(column.getName());
+            nameBase64Encoded      = true;
+            columnFormattingMethod = AS_VALUE;
+        }
+        else
+        {
+            columnName        = column.getName();
+            nameBase64Encoded = false;
+            if (columnName.length() > XMLUtils.MAX_NAME_LENGTH)
+            {
+                columnFormattingMethod = AS_VALUE;
+            }
+            else if ("table-name".equals(columnName) ||
+                     "column".equals(columnName) ||
+                     DatabaseIO.BASE64_ATTR_NAME.equals(columnName) ||
+                     !XMLUtils.isWellFormedXMLName(columnName))
+            {
+                columnFormattingMethod = AS_COLUMN_ATTRIBUTE;
+            }
+            else if (valueBase64Encoded || (value.length() > XMLUtils.MAX_ATTRIBUTE_LENGTH))
+            {
+                columnFormattingMethod = AS_SUBTAG;
+            }
+            else
+            {
+                columnFormattingMethod = AS_TABLE_ATTRIBUTE;
+            }
+        }
+    }
+
+    /**
+     * Writes the column data as an attribute of the parent element if possible.
+     * Does nothing if the column name or value cannot be used in an attribute.
+     * 
+     * @param writer The writer to write to
+     * @return <code>true</code> if something was written
+     */
+    public boolean writeAttribute(DataWriter writer)
+    {
+        if (columnFormattingMethod == AS_TABLE_ATTRIBUTE)
+        {
+            writer.writeAttribute(null, columnName, columnValue);
+            return true;
+        }
+        else
+        {
+            return false;
+        }
+    }
+
+    /**
+     * Writes any sub elements necessary for the column. If no sub elements
+     * are required, then this method does nothing.
+     * 
+     * @param writer The writer to write to
+     * @return <code>true</code> if something was written
+     */
+    public boolean writeSubElement(DataWriter writer)
+    {
+        if (columnFormattingMethod != AS_TABLE_ATTRIBUTE)
+        {
+            writer.printlnIfPrettyPrinting();
+            writer.indentIfPrettyPrinting(2);
+            if (columnFormattingMethod == AS_SUBTAG)
+            {
+                writer.writeElementStart(null, columnName);
+                writeText(writer, columnValue, valueBase64Encoded);
+            }
+            else
+            {
+                writer.writeElementStart(null, "column");
+                if (columnFormattingMethod == AS_COLUMN_ATTRIBUTE)
+                {
+                    writer.writeAttribute(null, "column-name", columnName);
+                    writeText(writer, columnValue, valueBase64Encoded);
+                }
+                else if (columnFormattingMethod == AS_VALUE)
+                {
+                    writer.printlnIfPrettyPrinting();
+                    writer.indentIfPrettyPrinting(3);
+                    writer.writeElementStart(null, "column-name");
+                    writeText(writer, columnName, nameBase64Encoded);
+                    writer.writeElementEnd();
+
+                    writer.printlnIfPrettyPrinting();
+                    writer.indentIfPrettyPrinting(3);
+                    writer.writeElementStart(null, "column-value");
+                    writeText(writer, columnValue, valueBase64Encoded);
+                    writer.writeElementEnd();
+                    writer.printlnIfPrettyPrinting();
+                    writer.indentIfPrettyPrinting(2);
+                }
+            }
+            writer.writeElementEnd();
+            return true;
+        }
+        else
+        {
+            return false;
+        }
+    }
+}
diff --git a/src/main/java/org/apache/ddlutils/io/DataReader.java b/src/main/java/org/apache/ddlutils/io/DataReader.java
index 8bc7b87..8ebdd44 100644
--- a/src/main/java/org/apache/ddlutils/io/DataReader.java
+++ b/src/main/java/org/apache/ddlutils/io/DataReader.java
@@ -27,8 +27,11 @@
 import java.io.InputStream;
 import java.io.Reader;
 import java.lang.reflect.InvocationTargetException;
+import java.util.HashMap;
+import java.util.Map;
 
 import javax.xml.namespace.QName;
+import javax.xml.stream.Location;
 import javax.xml.stream.XMLInputFactory;
 import javax.xml.stream.XMLStreamException;
 import javax.xml.stream.XMLStreamReader;
@@ -311,48 +314,62 @@
      */
     private void readBean(XMLStreamReader xmlReader) throws XMLStreamException, DdlUtilsXMLException
     {
-        QName elemQName = xmlReader.getName();
-        Table table     = _model.findTable(elemQName.getLocalPart(), isCaseSensitive());
+        QName    elemQName  = xmlReader.getName();
+        Location location   = xmlReader.getLocation();
+        Map      attributes = new HashMap();
+        String   tableName  = null;
+
+        for (int idx = 0; idx < xmlReader.getAttributeCount(); idx++)
+        {
+            QName attrQName = xmlReader.getAttributeName(idx);
+
+            attributes.put(isCaseSensitive() ? attrQName.getLocalPart() : attrQName.getLocalPart().toLowerCase(),
+                           xmlReader.getAttributeValue(idx));
+        }
+        readColumnSubElements(xmlReader, attributes);
+
+        if ("table".equals(elemQName.getLocalPart()))
+        {
+            tableName = (String)attributes.get("table-name");
+        }
+        else
+        {
+            tableName  = elemQName.getLocalPart();
+        }
+
+        Table table = _model.findTable(tableName, isCaseSensitive());
 
         if (table == null)
         {
-            _log.warn("Data XML contains an element " + elemQName + " at location " + xmlReader.getLocation() +
+            _log.warn("Data XML contains an element " + elemQName + " at location " + location +
                       " but there is no table defined with this name. This element will be ignored.");
-            readOverElement(xmlReader);
         }
         else
         {
             DynaBean bean = _model.createDynaBeanFor(table);
-    
-            for (int idx = 0; idx < xmlReader.getAttributeCount(); idx++)
-            {
-                QName  attrQName = xmlReader.getAttributeName(idx);
-                Column column    = table.findColumn(attrQName.getLocalPart(), isCaseSensitive());
 
-                if (column == null)
+            for (int idx = 0; idx < table.getColumnCount(); idx++)
+            {
+                Column column = table.getColumn(idx);
+                String value  = (String)attributes.get(isCaseSensitive() ? column.getName() : column.getName().toLowerCase());
+
+                if (value != null)
                 {
-                    _log.warn("Data XML contains an attribute " + attrQName + " at location " + xmlReader.getLocation() +
-                              " but there is no column defined in table " + table.getName() + " with this name. This attribute will be ignored.");
-                }
-                else
-                {
-                    setColumnValue(bean, table, column, xmlReader.getAttributeValue(idx));
+                    setColumnValue(bean, table, column, value);
                 }
             }
-            readColumnSubElements(xmlReader, bean, table);
             getSink().addBean(bean);
             consumeRestOfElement(xmlReader);
         }
     }
 
     /**
-     * Reads all column sub elements that match the columns specified by the given table object from the xml reader into the given bean.
+     * Reads all relevant sub elements that match the columns specified by the given table object from the xml reader into the given bean.
      *  
      * @param xmlReader The reader
-     * @param bean      The bean to fill
-     * @param table     The table definition
+     * @param data      Where to store the values
      */
-    private void readColumnSubElements(XMLStreamReader xmlReader, DynaBean bean, Table table) throws XMLStreamException, DdlUtilsXMLException
+    private void readColumnSubElements(XMLStreamReader xmlReader, Map data) throws XMLStreamException, DdlUtilsXMLException
     {
         int eventType = XMLStreamReader.START_ELEMENT;
 
@@ -361,7 +378,7 @@
             eventType = xmlReader.next();
             if (eventType == XMLStreamReader.START_ELEMENT)
             {
-                readColumnSubElement(xmlReader, bean, table);
+                readColumnSubElement(xmlReader, data);
             }
         }
     }
@@ -370,48 +387,128 @@
      * Reads the next column sub element that matches a column specified by the given table object from the xml reader into the given bean.
      *  
      * @param xmlReader The reader
-     * @param bean      The bean to fill
-     * @param table     The table definition
+     * @param data      Where to store the values
      */
-    private void readColumnSubElement(XMLStreamReader xmlReader, DynaBean bean, Table table) throws XMLStreamException, DdlUtilsXMLException
+    private void readColumnSubElement(XMLStreamReader xmlReader, Map data) throws XMLStreamException, DdlUtilsXMLException
+    {
+        QName   elemQName  = xmlReader.getName();
+        Map     attributes = new HashMap();
+        boolean usesBase64 = false;
+
+        for (int idx = 0; idx < xmlReader.getAttributeCount(); idx++)
+        {
+            QName  attrQName = xmlReader.getAttributeName(idx);
+            String value     = xmlReader.getAttributeValue(idx);
+
+            if (DatabaseIO.BASE64_ATTR_NAME.equals(attrQName.getLocalPart()))
+            {
+                if ("true".equalsIgnoreCase(value))
+                {
+                    usesBase64 = true;
+                }
+            }
+            else
+            {
+                attributes.put(attrQName.getLocalPart(), value);
+            }
+        }
+
+        int          eventType = XMLStreamReader.START_ELEMENT;
+        StringBuffer content   = new StringBuffer();
+
+        while (eventType != XMLStreamReader.END_ELEMENT)
+        {
+            eventType = xmlReader.next();
+            if (eventType == XMLStreamReader.START_ELEMENT)
+            {
+                readColumnDataSubElement(xmlReader, attributes);
+            }
+            else if ((eventType == XMLStreamReader.CHARACTERS) ||
+                     (eventType == XMLStreamReader.CDATA) ||
+                     (eventType == XMLStreamReader.SPACE) ||
+                     (eventType == XMLStreamReader.ENTITY_REFERENCE))
+            {
+                content.append(xmlReader.getText());
+            }
+        }
+
+        String value = content.toString().trim();
+
+        if (usesBase64)
+        {
+            value = new String(Base64.decodeBase64(value.getBytes()));
+        }
+
+        String name = elemQName.getLocalPart();
+
+        if ("table-name".equals(name))
+        {
+            data.put("table-name", value);
+        }
+        else
+        {
+            if ("column".equals(name))
+            {
+                name = (String)attributes.get("column-name");
+            }
+            if (attributes.containsKey("column-value"))
+            {
+                value = (String)attributes.get("column-value");
+            }
+            data.put(name, value);
+        }
+        consumeRestOfElement(xmlReader);
+    }
+
+
+    /**
+     * Reads the next column-name or column-value sub element.
+     *  
+     * @param xmlReader The reader
+     * @param data      Where to store the values
+     */
+    private void readColumnDataSubElement(XMLStreamReader xmlReader, Map data) throws XMLStreamException, DdlUtilsXMLException
     {
         QName   elemQName  = xmlReader.getName();
         boolean usesBase64 = false;
 
         for (int idx = 0; idx < xmlReader.getAttributeCount(); idx++)
         {
-            QName attrQName = xmlReader.getAttributeName(idx);
+            QName  attrQName = xmlReader.getAttributeName(idx);
+            String value     = xmlReader.getAttributeValue(idx);
 
-            if (DatabaseIO.BASE64_ATTR_NAME.equals(attrQName.getLocalPart()) &&
-                "true".equalsIgnoreCase(xmlReader.getAttributeValue(idx)))
+            if (DatabaseIO.BASE64_ATTR_NAME.equals(attrQName.getLocalPart()))
             {
-                usesBase64 = true;
+                if ("true".equalsIgnoreCase(value))
+                {
+                    usesBase64 = true;
+                }
                 break;
             }
         }
 
-        Column column  = table.findColumn(elemQName.getLocalPart(), isCaseSensitive());
+        String value = xmlReader.getElementText();
 
-        if (column == null)
+        if (value != null)
         {
-            _log.warn("Data XML contains an element " + elemQName + " at location " + xmlReader.getLocation() +
-                      " but there is no column defined in table " + table.getName() + " with this name. This element will be ignored.");
-        }
-        else
-        {
-            String value = xmlReader.getElementText();
-
-            if (value != null)
+            value = value.toString().trim();
+    
+            if (usesBase64)
             {
-                value = value.trim();
-
-                if (usesBase64)
-                {
-                    value = new String(Base64.decodeBase64(value.getBytes()));
-                }
-                setColumnValue(bean, table, column, value);
+                value = new String(Base64.decodeBase64(value.getBytes()));
             }
         }
+
+        String name = elemQName.getLocalPart();
+
+        if ("column-name".equals(name))
+        {
+            data.put("column-name", value);
+        }
+        else if ("column-value".equals(name))
+        {
+            data.put("column-value", value);
+        }
         consumeRestOfElement(xmlReader);
     }
 
@@ -445,33 +542,6 @@
             throw new DdlUtilsXMLException("Could not set bean property for column " + column.getName(), ex);
         }
     }
-
-    // TODO: move these two into a helper class:
-    
-    /**
-     * 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--;
-            }
-        }
-    }
     
     /**
      * Consumes the rest of the current element. This assumes that the current XML stream
diff --git a/src/main/java/org/apache/ddlutils/io/DataWriter.java b/src/main/java/org/apache/ddlutils/io/DataWriter.java
index 902138f..d6b38ab 100644
--- a/src/main/java/org/apache/ddlutils/io/DataWriter.java
+++ b/src/main/java/org/apache/ddlutils/io/DataWriter.java
@@ -20,39 +20,25 @@
  */

 

 import java.io.OutputStream;

-import java.io.UnsupportedEncodingException;

 import java.io.Writer;

 import java.util.ArrayList;

 import java.util.Collection;

-import java.util.HashMap;

 import java.util.Iterator;

 import java.util.List;

-import java.util.Map;

-

 import org.apache.commons.beanutils.DynaBean;

-import org.apache.commons.codec.binary.Base64;

 import org.apache.commons.logging.Log;

 import org.apache.commons.logging.LogFactory;

 import org.apache.ddlutils.dynabean.SqlDynaBean;

 import org.apache.ddlutils.dynabean.SqlDynaClass;

-import org.apache.ddlutils.io.converters.ConversionException;

 import org.apache.ddlutils.io.converters.SqlTypeConverter;

 import org.apache.ddlutils.model.Column;

 import org.apache.ddlutils.model.Table;

 

 /**

  * Writes dyna beans matching a specified database model into an XML file.

- * 

- * TODO: Make names (tables, columns) XML-compliant

- * 

- * @version $Revision: 289996 $

  */

 public class DataWriter extends PrettyPrintingXmlWriter

 {

-    /** String values with a size not bigger than this value will be written to attributes;

-        if their size is longer, then a sub element is generated instead. */ 

-    private static final int MAX_ATTRIBUTE_LENGTH = 255;

-

     /** Our log. */

     private final Log _log = LogFactory.getLog(DataWriter.class);

 

@@ -139,162 +125,36 @@
      */

     public void write(SqlDynaBean bean) throws DataWriterException

     {

-        SqlDynaClass dynaClass   = (SqlDynaClass)bean.getDynaClass();

-        Table        table       = dynaClass.getTable();

-        HashMap      subElements = new HashMap();

+        SqlDynaClass   dynaClass     = (SqlDynaClass)bean.getDynaClass();

+        Table          table         = dynaClass.getTable();

+        TableXmlWriter tableWriter   = new TableXmlWriter(table);

+        List           columnWriters = new ArrayList();

 

-        try

+        for (int idx = 0; idx < table.getColumnCount(); idx++)

         {

-            indentIfPrettyPrinting(1);

-            writeElementStart(null, table.getName());

-            for (int idx = 0; idx < table.getColumnCount(); idx++)

-            {

-                Column           column      = table.getColumn(idx);

-                Object           value       = bean.get(column.getName());

-                SqlTypeConverter converter   = _converterConf.getRegisteredConverter(table, column);

-                String           valueAsText = null;

+            Column           column      = table.getColumn(idx);

+            Object           value       = bean.get(column.getName());

+            SqlTypeConverter converter   = _converterConf.getRegisteredConverter(table, column);

+            String           valueAsText = null;

 

-                if (converter == null)

+            if (converter == null)

+            {

+                if (value != null)

                 {

-                    if (value != null)

-                    {

-                        valueAsText = value.toString();

-                    }

-                }

-                else

-                {

-                    valueAsText = converter.convertToString(value, column.getTypeCode());

-                }

-                if (valueAsText != null)

-                {

-                    // we create an attribute only if the text is not too long

-                    // and if it does not contain special characters

-                    if ((valueAsText.length() > MAX_ATTRIBUTE_LENGTH) || analyzeText(valueAsText, null))

-                    {

-                        // we defer writing the sub elements

-                        subElements.put(column.getName(), valueAsText);

-                    }

-                    else

-                    {

-                        writeAttribute(null, column.getName(), valueAsText);

-                    }

+                    valueAsText = value.toString();

                 }

             }

-            if (!subElements.isEmpty())

+            else

             {

-                List cutPoints = new ArrayList();

-

-                for (Iterator it = subElements.entrySet().iterator(); it.hasNext();)

-                {

-                    Map.Entry entry     = (Map.Entry)it.next();

-                    String    content   = entry.getValue().toString();

-

-                    printlnIfPrettyPrinting();

-                    indentIfPrettyPrinting(2);

-                    writeElementStart(null, entry.getKey().toString());

-

-                    // if the content contains special characters, we have to apply base64 encoding to it

-                    // if the content is too short, then it has to contain special characters (otherwise

-                    // it would have been written as an attribute already), otherwise we check

-                    cutPoints.clear();

-

-                    boolean writeBase64Encoded = analyzeText(content, cutPoints);

-

-                    if (writeBase64Encoded)

-                    {

-                        writeAttribute(null, DatabaseIO.BASE64_ATTR_NAME, "true");

-                        try {

-                            writeCData(new String(Base64.encodeBase64(content.getBytes()), getEncoding()));

-                        }

-                        catch (UnsupportedEncodingException ex) {

-                            throw new DataWriterException(ex);

-                        }

-                    }

-                    else

-                    {

-                        if (cutPoints.isEmpty())

-                        {

-                            writeCData(content);

-                        }

-                        else

-                        {

-                            int lastPos = 0;

-

-                            for (Iterator cutPointIt = cutPoints.iterator(); cutPointIt.hasNext();)

-                            {

-                                int curPos = ((Integer)cutPointIt.next()).intValue();

-

-                                writeCData(content.substring(lastPos, curPos));

-                                lastPos = curPos;

-                            }

-                            if (lastPos < content.length())

-                            {

-                                writeCData(content.substring(lastPos));

-                            }

-                        }

-                    }

-

-                    writeElementEnd();

-                }

-                printlnIfPrettyPrinting();

-                indentIfPrettyPrinting(1);

+                valueAsText = converter.convertToString(value, column.getTypeCode());

             }

-            writeElementEnd();

-            printlnIfPrettyPrinting();

-        }

-        catch (ConversionException ex)

-        {

-            throw new DataWriterException(ex);

-        }

-    }

-

-    /**

-     * Determines whether the given string contains special characters that cannot

-     * be used in XML, and if not, finds the cut points where to split the text

-     * when writing it in a CDATA section.

-     * 

-     * @param text      The text

-     * @param cutPoints Will be filled with cut points to split the text when writing it

-     *                  in a CDATA section (only if the method returns <code>false</code>)

-     * @return <code>true</code> if the text contains special characters

-     */

-    private boolean analyzeText(String text, List cutPoints)

-    {

-        List tmpCutPoints          = cutPoints == null ? null : new ArrayList();

-        int  numChars              = text.length();

-        int  numFoundCDataEndChars = 0;

-

-        for (int charPos = 0; charPos < numChars; charPos++)

-        {

-            char c = text.charAt(charPos);

-

-            if ((c < 0x0020) && (c != '\n') && (c != '\r') && (c != '\t'))

+            if (valueAsText != null)

             {

-                return true;

-            }

-            else if (cutPoints != null)

-            {

-                if ((c == ']') && ((numFoundCDataEndChars == 0) || (numFoundCDataEndChars == 1)))

-                {

-                    numFoundCDataEndChars++;

-                }

-                else if ((c == '>') && (numFoundCDataEndChars == 2))

-                {

-                    // we have to split the CDATA right here before the '>' (see DDLUTILS-174)

-                    tmpCutPoints.add(new Integer(charPos));

-                    numFoundCDataEndChars = 0;

-                }

-                else

-                {

-                    numFoundCDataEndChars = 0;

-                }

+                columnWriters.add(new ColumnXmlWriter(column, valueAsText));

             }

         }

-        if (cutPoints != null)

-        {

-            cutPoints.addAll(tmpCutPoints);

-        }

-        return false;

+

+        tableWriter.write(columnWriters, this);

     }

 

     /**

diff --git a/src/main/java/org/apache/ddlutils/io/ModelXmlWriter.java b/src/main/java/org/apache/ddlutils/io/ModelXmlWriter.java
new file mode 100644
index 0000000..375aa2d
--- /dev/null
+++ b/src/main/java/org/apache/ddlutils/io/ModelXmlWriter.java
@@ -0,0 +1,64 @@
+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.util.Iterator;
+import java.util.List;
+
+/**
+ * Base class providing helper functions for writing model elements to XML.  
+ */
+public abstract class ModelXmlWriter
+{
+    protected void writeText(DataWriter writer, String value, boolean isBase64Encoded)
+    {
+        if (isBase64Encoded)
+        {
+            writer.writeAttribute(null, "base64", "true");
+            writer.writeCharacters(value);
+        }
+        else
+        {
+            List cutPoints = XMLUtils.findCDataCutPoints(value);
+    
+            // if the content contains special characters, we have to apply base64 encoding to it
+            if (cutPoints.isEmpty())
+            {
+                writer.writeCharacters(value);
+            }
+            else
+            {
+                int lastPos = 0;
+    
+                for (Iterator cutPointIt = cutPoints.iterator(); cutPointIt.hasNext();)
+                {
+                    int curPos = ((Integer)cutPointIt.next()).intValue();
+    
+                    writer.writeCData(value.substring(lastPos, curPos));
+                    lastPos = curPos;
+                }
+                if (lastPos < value.length())
+                {
+                    writer.writeCData(value.substring(lastPos));
+                }
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/apache/ddlutils/io/PrettyPrintingXmlWriter.java b/src/main/java/org/apache/ddlutils/io/PrettyPrintingXmlWriter.java
index df245c5..7d73c16 100644
--- a/src/main/java/org/apache/ddlutils/io/PrettyPrintingXmlWriter.java
+++ b/src/main/java/org/apache/ddlutils/io/PrettyPrintingXmlWriter.java
@@ -373,4 +373,24 @@
             }
         }
     }
+
+    /**
+     * Writes a text segment.
+     * 
+     * @param data The data to write
+     */
+    public void writeCharacters(String data) throws DdlUtilsXMLException
+    {
+        if (data != null)
+        {
+            try
+            {
+                _writer.writeCharacters(data);
+            }
+            catch (XMLStreamException ex)
+            {
+                throwException(ex);
+            }
+        }
+    }
 }
diff --git a/src/main/java/org/apache/ddlutils/io/TableXmlWriter.java b/src/main/java/org/apache/ddlutils/io/TableXmlWriter.java
new file mode 100644
index 0000000..42d345f
--- /dev/null
+++ b/src/main/java/org/apache/ddlutils/io/TableXmlWriter.java
@@ -0,0 +1,115 @@
+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.util.Iterator;
+import java.util.List;
+import org.apache.ddlutils.model.Table;
+
+/**
+ * Base interface for different strategies to write the XML for a data bean for a specific table.
+ */
+public class TableXmlWriter extends ModelXmlWriter
+{
+    private static final int AS_TAG_NAME  = 0;
+    private static final int AS_ATTRIBUTE = 1;
+    private static final int AS_SUB_TAG   = 2;
+
+    private final String tableName;
+    private final int formattingMethod;
+    private final boolean base64Encoded;
+
+    public TableXmlWriter(Table table)
+    {
+        if (XMLUtils.hasIllegalXMLCharacters(table.getName()))
+        {
+            tableName        = XMLUtils.base64Encode(table.getName());
+            formattingMethod = AS_SUB_TAG;
+            base64Encoded    = true;
+        }
+        else
+        {
+            tableName     = table.getName();
+            base64Encoded = false;
+            if (tableName.length() > XMLUtils.MAX_NAME_LENGTH)
+            {
+                formattingMethod = AS_SUB_TAG;
+            }
+            else if ("table".equals(tableName) || !XMLUtils.isWellFormedXMLName(tableName))
+            {
+                formattingMethod = AS_ATTRIBUTE;
+            }
+            else
+            {
+                formattingMethod = AS_TAG_NAME;
+            }
+        }
+    }
+
+    /**
+     * Write the table data to XML to the given writer.
+     * 
+     * @param columnXmlWriters A list of column xml writers for writing out the bean's values to XML
+     * @param writer           The writer to write to
+     */
+    public void write(List columnXmlWriters, DataWriter writer)
+    {
+        writer.indentIfPrettyPrinting(1);
+        if (formattingMethod == AS_TAG_NAME)
+        {
+            writer.writeElementStart(null, tableName);
+        }
+        else
+        {
+            writer.writeElementStart(null, "table");
+        }
+        if (formattingMethod == AS_ATTRIBUTE)
+        {
+            writer.writeAttribute(null, "table-name", tableName);
+        }
+        for (Iterator it = columnXmlWriters.iterator(); it.hasNext();)
+        {
+            ((ColumnXmlWriter)it.next()).writeAttribute(writer);
+        }
+
+        boolean hasSubTags = false;
+
+        if (formattingMethod == AS_SUB_TAG)
+        {
+            writer.printlnIfPrettyPrinting();
+            writer.indentIfPrettyPrinting(2);
+            writer.writeElementStart(null, "table-name");
+            writeText(writer, tableName, base64Encoded);
+            writer.writeElementEnd();
+            hasSubTags = true;
+        }
+        for (Iterator it = columnXmlWriters.iterator(); it.hasNext();)
+        {
+            hasSubTags = ((ColumnXmlWriter)it.next()).writeSubElement(writer) || hasSubTags;
+        }
+        if (hasSubTags)
+        {
+            writer.printlnIfPrettyPrinting();
+            writer.indentIfPrettyPrinting(1);
+        }
+        writer.writeElementEnd();
+        writer.printlnIfPrettyPrinting();
+    }
+}
diff --git a/src/main/java/org/apache/ddlutils/io/XMLUtils.java b/src/main/java/org/apache/ddlutils/io/XMLUtils.java
index a879aaf..b876473 100644
--- a/src/main/java/org/apache/ddlutils/io/XMLUtils.java
+++ b/src/main/java/org/apache/ddlutils/io/XMLUtils.java
@@ -19,9 +19,14 @@
  * under the License.
  */
 
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.commons.codec.binary.Base64;
+
 /**
  * <p>Contains basic utility methods for XML.</p>
- * This class is borrowed from <a href='http://commons.apache.org/betwixt/'>Apache Commons Betwixt</a>
+ * Parts of this class are borrowed from <a href='http://commons.apache.org/betwixt/'>Apache Commons Betwixt</a>
  * whose class in turn is based on code in <a href='http://xerces.apache.org/xerces2-j/index.html'>Apache Xerces</a>.
  * <p>The code for {@link #isWellFormedXMLName} is based on code in 
  * <code>org.apache.xerces.util.XMLChar</code> 
@@ -34,10 +39,14 @@
  * @author Arnaud  Le Hors, IBM
  * @author Rahul Srivastava, Sun Microsystems Inc.  
  * @author Robert Burrell Donkin
- * @version $Revision: $
  */
 public class XMLUtils
 {
+    /** Maximum length of attribute values that we want to generate. */ 
+    public static final int MAX_ATTRIBUTE_LENGTH = 255;
+    /** Maximum length of a tag or attribute name that we want to generate. */ 
+    public static final int MAX_NAME_LENGTH = 255;
+
     /** Name start character mask. */
     private static final int MASK_NAME_START = 0x01;
     /** Name character mask. */
@@ -295,4 +304,88 @@
     {
         return (c < 0x10000) && ((CHARS[c] & MASK_NAME_START) != 0);
     }
+
+    /**
+     * Determines whether the given string contains special characters that cannot
+     * be used in XML.
+     * 
+     * @param text The text
+     * @return <code>true</code> if the text contains special characters
+     */
+    public static boolean hasIllegalXMLCharacters(String text)
+    {
+        int numChars = text.length();
+
+        for (int charPos = 0; charPos < numChars; charPos++)
+        {
+            char c = text.charAt(charPos);
+
+            if ((c != 0x9) && (c != 0xA) && (c != 0xD) && ((c < 0x20) || (c > 0xD7FF)) && ((c < 0xE000) || (c > 0xFFFD)) && ((c < 0x10000) || (c > 0x10FFFF)))
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Encodes the given value with Base64.
+     * 
+     * @param value The value to encode
+     * @return The encoded value
+     */
+    public static String base64Encode(String value)
+    {
+        try
+        {
+            return value == null ? null : new String(Base64.encodeBase64(value.getBytes("UTF-8")), "UTF-8");
+        }
+        catch (UnsupportedEncodingException ex)
+        {
+            throw new IllegalStateException(ex);
+        }
+    }
+
+    /**
+     * Determines whether the given string contains special characters that cannot
+     * be used in XML, and if not, finds the cut points where to split the text
+     * when writing it in a CDATA section.
+     * 
+     * @param text The text
+     * @return <code>null</code> if the text contains special characters, or the list of cut points otherwise
+     */
+    public static List findCDataCutPoints(String text)
+    {
+        List cutPoints             = new ArrayList();
+        int  numChars              = text.length();
+        int  numFoundCDataEndChars = 0;
+
+        for (int charPos = 0; charPos < numChars; charPos++)
+        {
+            char c = text.charAt(charPos);
+
+            if ((c != 0x9) && (c != 0xA) && (c != 0xD) && ((c < 0x20) || (c > 0xD7FF)) && ((c < 0xE000) || (c > 0xFFFD)) && ((c < 0x10000) || (c > 0x10FFFF)))
+            {
+                return null;
+            }
+            else
+            {
+                if ((c == ']') && ((numFoundCDataEndChars == 0) || (numFoundCDataEndChars == 1)))
+                {
+                    numFoundCDataEndChars++;
+                }
+                else if ((c == '>') && (numFoundCDataEndChars == 2))
+                {
+                    // we have to split the CDATA right here before the '>' (see DDLUTILS-174)
+                    cutPoints.add(new Integer(charPos));
+                    numFoundCDataEndChars = 0;
+                }
+                else
+                {
+                    numFoundCDataEndChars = 0;
+                }
+            }
+        }
+        return cutPoints;
+    }
 }
diff --git a/src/test/java/org/apache/ddlutils/io/TestDataReaderAndWriter.java b/src/test/java/org/apache/ddlutils/io/TestDataReaderAndWriter.java
index 982e9de..8e8b7d2 100644
--- a/src/test/java/org/apache/ddlutils/io/TestDataReaderAndWriter.java
+++ b/src/test/java/org/apache/ddlutils/io/TestDataReaderAndWriter.java
@@ -26,17 +26,18 @@
 import java.io.FileInputStream;

 import java.io.FileWriter;

 import java.io.StringReader;

-import java.io.StringWriter;

+import java.io.UnsupportedEncodingException;

 import java.io.Writer;

 import java.util.ArrayList;

-

+import java.util.List;

 import junit.framework.TestCase;

-

 import org.apache.commons.beanutils.DynaBean;

 import org.apache.commons.codec.binary.Base64;

 import org.apache.commons.lang.StringUtils;

 import org.apache.ddlutils.dynabean.SqlDynaBean;

+import org.apache.ddlutils.model.Column;

 import org.apache.ddlutils.model.Database;

+import org.apache.ddlutils.model.Table;

 

 /**

  * Tests the {@link org.apache.ddlutils.io.DataReader} and {@link org.apache.ddlutils.io.DataWriter} classes.

@@ -85,11 +86,102 @@
     }

 

     /**

+     * Reads the given schema xml into a {@link Database} object.

+     * 

+     * @param schemaXml The schema xml

+     * @return The database model object

+     */

+    private Database readModel(String schemaXml)

+    {

+        DatabaseIO modelIO = new DatabaseIO();

+

+        modelIO.setValidateXml(true);

+        

+        return modelIO.read(new StringReader(schemaXml));

+    }

+

+    /**

+     * Writes the given dyna bean via a {@link DataWriter} and returns the raw xml output.

+     * 

+     * @param model    The database model to use

+     * @param bean     The bean to write

+     * @param encoding The encoding in which to write the xml

+     * @return The xml output as raw bytes

+     */

+    private byte[] writeBean(Database model, SqlDynaBean bean, String encoding)

+    {

+        ByteArrayOutputStream output     = new ByteArrayOutputStream();

+        DataWriter            dataWriter = new DataWriter(output, encoding);

+

+        dataWriter.writeDocumentStart();

+        dataWriter.write(bean);

+        dataWriter.writeDocumentEnd();

+

+        return output.toByteArray();

+    }

+

+    /**

+     * Uses a {@link DataReader} with default settings to read dyna beans from the given xml data.

+     * 

+     * @param model   The database model to use

+     * @param dataXml The raw xml data

+     * @return The read dyna beans

+     */

+    private List readBeans(Database model, byte[] dataXml)

+    {

+        ArrayList  beans      = new ArrayList();

+        DataReader dataReader = new DataReader();

+

+        dataReader.setModel(model);

+        dataReader.setSink(new TestDataSink(beans));

+        dataReader.read(new ByteArrayInputStream(dataXml));

+        return beans;

+    }

+

+    /**

+     * Uses a {@link DataReader} with default settings to read dyna beans from the given xml data.

+     * 

+     * @param model   The database model to use

+     * @param dataXml The xml data

+     * @return The read dyna beans

+     */

+    private List readBeans(Database model, String dataXml)

+    {

+        ArrayList  beans      = new ArrayList();

+        DataReader dataReader = new DataReader();

+

+        dataReader.setModel(model);

+        dataReader.setSink(new TestDataSink(beans));

+        dataReader.read(new StringReader(dataXml));

+        return beans;

+    }

+

+    /**

+     * Helper method to perform a test that writes a bean and then reads it back.

+     * 

+     * @param model           The database model to use

+     * @param bean            The bean to write and read back

+     * @param encoding        The encoding to use for the data xml

+     * @param expectedDataXml The expected xml generated for the bean

+     */

+    private void roundtripTest(Database model, SqlDynaBean bean, String encoding, String expectedDataXml) throws UnsupportedEncodingException

+    {

+        byte[] xmlData = writeBean(model, bean, encoding);

+

+        assertEquals(expectedDataXml, new String(xmlData, encoding));

+

+        List beans = readBeans(model, xmlData);

+

+        assertEquals(1, beans.size());

+        assertEquals(bean, beans.get(0));

+    }

+

+    /**

      * Tests reading the data from XML.

      */

     public void testRead() throws Exception

     {

-        final String testSchemaXml = 

+        Database model = readModel( 

             "<?xml version='1.0' encoding='ISO-8859-1'?>\n"+

             "<database xmlns='" + DatabaseIO.DDLUTILS_NAMESPACE + "' name='bookstore'>\n"+

             "  <table name='author'>\n"+

@@ -110,8 +202,9 @@
             "      <index-column name='isbn'/>\n"+

             "    </index>\n"+

             "  </table>\n"+

-            "</database>";

-        final String testDataXml =

+            "</database>");

+        List beans = readBeans(

+            model,

             "<data>\n"+

             "  <author author_id='1' name='Ernest Hemingway'/>\n"+

             "  <author author_id='2' name='William Shakespeare'/>\n"+

@@ -130,27 +223,15 @@
             "    <title>A Midsummer Night's Dream</title>\n"+

             "    <issue_date>1595</issue_date>\n"+

             "  </book>\n"+

-            "</data>";

+            "</data>");

 

-        DatabaseIO modelReader = new DatabaseIO();

+        assertEquals(5, beans.size());

 

-        modelReader.setValidateXml(true);

-        

-        Database        model       = modelReader.read(new StringReader(testSchemaXml));

-        final ArrayList readObjects = new ArrayList();

-        DataReader      dataReader  = new DataReader();

-

-        dataReader.setModel(model);

-        dataReader.setSink(new TestDataSink(readObjects));

-        dataReader.read(new StringReader(testDataXml));

-

-        assertEquals(5, readObjects.size());

-

-        DynaBean obj1 = (DynaBean)readObjects.get(0);

-        DynaBean obj2 = (DynaBean)readObjects.get(1);

-        DynaBean obj3 = (DynaBean)readObjects.get(2);

-        DynaBean obj4 = (DynaBean)readObjects.get(3);

-        DynaBean obj5 = (DynaBean)readObjects.get(4);

+        DynaBean obj1 = (DynaBean)beans.get(0);

+        DynaBean obj2 = (DynaBean)beans.get(1);

+        DynaBean obj3 = (DynaBean)beans.get(2);

+        DynaBean obj4 = (DynaBean)beans.get(3);

+        DynaBean obj5 = (DynaBean)beans.get(4);

 

         assertEquals("author",

                      obj1.getDynaClass().getName());

@@ -207,15 +288,15 @@
      */

     public void testReadFromFile1() throws Exception

     {

-        final String testSchemaXml = 

+        Database model = readModel(

             "<?xml version='1.0' encoding='ISO-8859-1'?>\n"+

             "<database xmlns='" + DatabaseIO.DDLUTILS_NAMESPACE + "' name='test'>\n" +

             "  <table name='test'>\n"+

             "    <column name='id' type='INTEGER' primaryKey='true' required='true'/>\n"+

             "    <column name='value' type='VARCHAR' size='50' required='true'/>\n"+

             "  </table>\n"+

-            "</database>";

-        final String testDataXml =

+            "</database>");

+        String testDataXml =

             "<data>\n"+

             "  <test id='1' value='foo'/>\n"+

             "</data>";

@@ -229,21 +310,16 @@
             writer.write(testDataXml);

             writer.close();

 

-            DatabaseIO modelReader = new DatabaseIO();

-

-            modelReader.setValidateXml(true);

-            

-            Database        model       = modelReader.read(new StringReader(testSchemaXml));

-            final ArrayList readObjects = new ArrayList();

-            DataReader      dataReader  = new DataReader();

+            ArrayList  beans      = new ArrayList();

+            DataReader dataReader = new DataReader();

     

             dataReader.setModel(model);

-            dataReader.setSink(new TestDataSink(readObjects));

+            dataReader.setSink(new TestDataSink(beans));

             dataReader.read(tmpFile.getAbsolutePath());

     

-            assertEquals(1, readObjects.size());

+            assertEquals(1, beans.size());

     

-            DynaBean obj = (DynaBean)readObjects.get(0);

+            DynaBean obj = (DynaBean)beans.get(0);

     

             assertEquals("test",

                          obj.getDynaClass().getName());

@@ -263,15 +339,15 @@
      */

     public void testReadFromFile2() throws Exception

     {

-        final String testSchemaXml = 

+        Database model = readModel(

             "<?xml version='1.0' encoding='ISO-8859-1'?>\n"+

             "<database xmlns='" + DatabaseIO.DDLUTILS_NAMESPACE + "' name='test'>\n" +

             "  <table name='test'>\n"+

             "    <column name='id' type='INTEGER' primaryKey='true' required='true'/>\n"+

             "    <column name='value' type='VARCHAR' size='50' required='true'/>\n"+

             "  </table>\n"+

-            "</database>";

-        final String testDataXml =

+            "</database>");

+        String testDataXml =

             "<data>\n"+

             "  <test id='1' value='foo'/>\n"+

             "</data>";

@@ -285,21 +361,16 @@
             writer.write(testDataXml);

             writer.close();

 

-            DatabaseIO modelReader = new DatabaseIO();

-

-            modelReader.setValidateXml(true);

-            

-            Database        model       = modelReader.read(new StringReader(testSchemaXml));

-            final ArrayList readObjects = new ArrayList();

-            DataReader      dataReader  = new DataReader();

+            ArrayList  beans      = new ArrayList();

+            DataReader dataReader = new DataReader();

     

             dataReader.setModel(model);

-            dataReader.setSink(new TestDataSink(readObjects));

+            dataReader.setSink(new TestDataSink(beans));

             dataReader.read(tmpFile);

     

-            assertEquals(1, readObjects.size());

+            assertEquals(1, beans.size());

     

-            DynaBean obj = (DynaBean)readObjects.get(0);

+            DynaBean obj = (DynaBean)beans.get(0);

     

             assertEquals("test",

                          obj.getDynaClass().getName());

@@ -319,15 +390,15 @@
      */

     public void testReadFromFile3() throws Exception

     {

-        final String testSchemaXml = 

+        Database model = readModel(

             "<?xml version='1.0' encoding='ISO-8859-1'?>\n"+

             "<database xmlns='" + DatabaseIO.DDLUTILS_NAMESPACE + "' name='test'>\n" +

             "  <table name='test'>\n"+

             "    <column name='id' type='INTEGER' primaryKey='true' required='true'/>\n"+

             "    <column name='value' type='VARCHAR' size='50' required='true'/>\n"+

             "  </table>\n"+

-            "</database>";

-        final String testDataXml =

+            "</database>");

+        String testDataXml =

             "<data>\n"+

             "  <test id='1' value='foo'/>\n"+

             "</data>";

@@ -341,21 +412,16 @@
             writer.write(testDataXml);

             writer.close();

 

-            DatabaseIO modelReader = new DatabaseIO();

-

-            modelReader.setValidateXml(true);

-            

-            Database        model       = modelReader.read(new StringReader(testSchemaXml));

-            final ArrayList readObjects = new ArrayList();

-            DataReader      dataReader  = new DataReader();

+            ArrayList  beans      = new ArrayList();

+            DataReader dataReader = new DataReader();

     

             dataReader.setModel(model);

-            dataReader.setSink(new TestDataSink(readObjects));

+            dataReader.setSink(new TestDataSink(beans));

             dataReader.read(new FileInputStream(tmpFile));

     

-            assertEquals(1, readObjects.size());

+            assertEquals(1, beans.size());

     

-            DynaBean obj = (DynaBean)readObjects.get(0);

+            DynaBean obj = (DynaBean)beans.get(0);

     

             assertEquals("test",

                          obj.getDynaClass().getName());

@@ -375,15 +441,16 @@
      */

     public void testSubElements() throws Exception

     {

-        final String testSchemaXml = 

+        Database model = readModel(

             "<?xml version='1.0' encoding='ISO-8859-1'?>\n"+

             "<database xmlns='" + DatabaseIO.DDLUTILS_NAMESPACE + "' name='test'>\n" +

             "  <table name='test'>\n"+

             "    <column name='id' type='INTEGER' primaryKey='true' required='true'/>\n"+

             "    <column name='value' type='VARCHAR' size='50' required='true'/>\n"+

             "  </table>\n"+

-            "</database>";

-        final String testDataXml =

+            "</database>");

+        List beans = readBeans(

+            model, 

             "<data>\n"+

             "  <test id='1'>\n"+

             "    <value>foo</value>\n"+

@@ -393,23 +460,11 @@
             "  </test>\n"+

             "  <test id='3' value='baz'>\n"+

             "  </test>\n"+

-            "</data>";

+            "</data>");

 

-        DatabaseIO modelReader = new DatabaseIO();

+        assertEquals(3, beans.size());

 

-        modelReader.setValidateXml(true);

-        

-        Database        model       = modelReader.read(new StringReader(testSchemaXml));

-        final ArrayList readObjects = new ArrayList();

-        DataReader      dataReader  = new DataReader();

-

-        dataReader.setModel(model);

-        dataReader.setSink(new TestDataSink(readObjects));

-        dataReader.read(new StringReader(testDataXml));

-

-        assertEquals(3, readObjects.size());

-

-        DynaBean obj = (DynaBean)readObjects.get(0);

+        DynaBean obj = (DynaBean)beans.get(0);

 

         assertEquals("test",

                      obj.getDynaClass().getName());

@@ -418,7 +473,7 @@
         assertEquals("foo",

                      obj.get("value").toString());

 

-        obj = (DynaBean)readObjects.get(1);

+        obj = (DynaBean)beans.get(1);

 

         assertEquals("test",

                      obj.getDynaClass().getName());

@@ -427,7 +482,7 @@
         assertEquals("bar",

                      obj.get("value").toString());

 

-        obj = (DynaBean)readObjects.get(2);

+        obj = (DynaBean)beans.get(2);

 

         assertEquals("test",

                      obj.getDynaClass().getName());

@@ -442,34 +497,23 @@
      */

     public void testRootElementNameDoesntMatter() throws Exception

     {

-        final String testSchemaXml = 

+        Database model = readModel(

             "<?xml version='1.0' encoding='ISO-8859-1'?>\n"+

             "<database xmlns='" + DatabaseIO.DDLUTILS_NAMESPACE + "' name='test'>\n" +

             "  <table name='test'>\n"+

             "    <column name='id' type='INTEGER' primaryKey='true' required='true'/>\n"+

             "    <column name='value' type='VARCHAR' size='50' required='true'/>\n"+

             "  </table>\n"+

-            "</database>";

-        final String testDataXml =

+            "</database>");

+        List beans = readBeans(

+            model,

             "<someRandomName>\n"+

             "  <test id='1' value='foo'/>\n"+

-            "</someRandomName>";

+            "</someRandomName>");

 

-        DatabaseIO modelReader = new DatabaseIO();

+        assertEquals(1, beans.size());

 

-        modelReader.setValidateXml(true);

-        

-        Database        model       = modelReader.read(new StringReader(testSchemaXml));

-        final ArrayList readObjects = new ArrayList();

-        DataReader      dataReader  = new DataReader();

-

-        dataReader.setModel(model);

-        dataReader.setSink(new TestDataSink(readObjects));

-        dataReader.read(new StringReader(testDataXml));

-

-        assertEquals(1, readObjects.size());

-

-        DynaBean obj = (DynaBean)readObjects.get(0);

+        DynaBean obj = (DynaBean)beans.get(0);

 

         assertEquals("test",

                      obj.getDynaClass().getName());

@@ -484,36 +528,25 @@
      */

     public void testElementForUndefinedTable() throws Exception

     {

-        final String testSchemaXml = 

+        Database model = readModel(

             "<?xml version='1.0' encoding='ISO-8859-1'?>\n"+

             "<database xmlns='" + DatabaseIO.DDLUTILS_NAMESPACE + "' name='test'>\n" +

             "  <table name='test'>\n"+

             "    <column name='id' type='INTEGER' primaryKey='true' required='true'/>\n"+

             "    <column name='value' type='VARCHAR' size='50' required='true'/>\n"+

             "  </table>\n"+

-            "</database>";

-        final String testDataXml =

+            "</database>");

+        List beans = readBeans(

+            model,

             "<data>\n"+

             "  <test id='1' value='foo'/>\n"+

             "  <other id='2' value='bar'/>\n"+

             "  <test id='3' value='baz'/>\n"+

-            "</data>";

+            "</data>");

 

-        DatabaseIO modelReader = new DatabaseIO();

+        assertEquals(2, beans.size());

 

-        modelReader.setValidateXml(true);

-        

-        Database        model       = modelReader.read(new StringReader(testSchemaXml));

-        final ArrayList readObjects = new ArrayList();

-        DataReader      dataReader  = new DataReader();

-

-        dataReader.setModel(model);

-        dataReader.setSink(new TestDataSink(readObjects));

-        dataReader.read(new StringReader(testDataXml));

-

-        assertEquals(2, readObjects.size());

-

-        DynaBean obj = (DynaBean)readObjects.get(0);

+        DynaBean obj = (DynaBean)beans.get(0);

 

         assertEquals("test",

                      obj.getDynaClass().getName());

@@ -522,7 +555,7 @@
         assertEquals("foo",

                      obj.get("value").toString());

 

-        obj = (DynaBean)readObjects.get(1);

+        obj = (DynaBean)beans.get(1);

 

         assertEquals("test",

                      obj.getDynaClass().getName());

@@ -537,34 +570,23 @@
      */

     public void testAttributeForUndefinedColumn() throws Exception

     {

-        final String testSchemaXml = 

+        Database model = readModel(

             "<?xml version='1.0' encoding='ISO-8859-1'?>\n"+

             "<database xmlns='" + DatabaseIO.DDLUTILS_NAMESPACE + "' name='test'>\n" +

             "  <table name='test'>\n"+

             "    <column name='id' type='INTEGER' primaryKey='true' required='true'/>\n"+

             "    <column name='value' type='VARCHAR' size='50' required='true'/>\n"+

             "  </table>\n"+

-            "</database>";

-        final String testDataXml =

+            "</database>");

+        List beans = readBeans(

+            model,

             "<data>\n"+

             "  <test id='1' value1='foo'/>\n"+

-            "</data>";

+            "</data>");

 

-        DatabaseIO modelReader = new DatabaseIO();

+        assertEquals(1, beans.size());

 

-        modelReader.setValidateXml(true);

-        

-        Database        model       = modelReader.read(new StringReader(testSchemaXml));

-        final ArrayList readObjects = new ArrayList();

-        DataReader      dataReader  = new DataReader();

-

-        dataReader.setModel(model);

-        dataReader.setSink(new TestDataSink(readObjects));

-        dataReader.read(new StringReader(testDataXml));

-

-        assertEquals(1, readObjects.size());

-

-        DynaBean obj = (DynaBean)readObjects.get(0);

+        DynaBean obj = (DynaBean)beans.get(0);

 

         assertEquals("test",

                      obj.getDynaClass().getName());

@@ -578,36 +600,25 @@
      */

     public void testSubElementForUndefinedColumn() throws Exception

     {

-        final String testSchemaXml = 

+        Database model = readModel(

             "<?xml version='1.0' encoding='ISO-8859-1'?>\n"+

             "<database xmlns='" + DatabaseIO.DDLUTILS_NAMESPACE + "' name='test'>\n" +

             "  <table name='test'>\n"+

             "    <column name='id' type='INTEGER' primaryKey='true' required='true'/>\n"+

             "    <column name='value' type='VARCHAR' size='50' required='true'/>\n"+

             "  </table>\n"+

-            "</database>";

-        final String testDataXml =

+            "</database>");

+        List beans = readBeans(

+            model,

             "<data>\n"+

             "  <test id='1'>\n"+

             "    <value2>foo</value2>\n"+

             "  </test>\n"+

-            "</data>";

+            "</data>");

 

-        DatabaseIO modelReader = new DatabaseIO();

+        assertEquals(1, beans.size());

 

-        modelReader.setValidateXml(true);

-        

-        Database        model       = modelReader.read(new StringReader(testSchemaXml));

-        final ArrayList readObjects = new ArrayList();

-        DataReader      dataReader  = new DataReader();

-

-        dataReader.setModel(model);

-        dataReader.setSink(new TestDataSink(readObjects));

-        dataReader.read(new StringReader(testDataXml));

-

-        assertEquals(1, readObjects.size());

-

-        DynaBean obj = (DynaBean)readObjects.get(0);

+        DynaBean obj = (DynaBean)beans.get(0);

 

         assertEquals("test",

                      obj.getDynaClass().getName());

@@ -621,36 +632,31 @@
      */

     public void testCaseSensitivityTurnedOn() throws Exception

     {

-        final String testSchemaXml = 

+        Database model = readModel(

             "<?xml version='1.0' encoding='ISO-8859-1'?>\n"+

             "<database xmlns='" + DatabaseIO.DDLUTILS_NAMESPACE + "' name='test'>\n" +

             "  <table name='Test'>\n"+

             "    <column name='Id' type='INTEGER' primaryKey='true' required='true'/>\n"+

             "    <column name='Value' type='VARCHAR' size='50' required='true'/>\n"+

             "  </table>\n"+

-            "</database>";

-        final String testDataXml =

+            "</database>");

+        String testDataXml =

             "<data>\n"+

             "  <test Id='1' Value='foo'/>\n"+

             "  <Test Id='2' value='baz'/>\n"+

             "</data>";

 

-        DatabaseIO modelReader = new DatabaseIO();

-

-        modelReader.setValidateXml(true);

-        

-        Database        model       = modelReader.read(new StringReader(testSchemaXml));

-        final ArrayList readObjects = new ArrayList();

-        DataReader      dataReader  = new DataReader();

+        ArrayList  beans      = new ArrayList();

+        DataReader dataReader = new DataReader();

 

         dataReader.setCaseSensitive(true);

         dataReader.setModel(model);

-        dataReader.setSink(new TestDataSink(readObjects));

+        dataReader.setSink(new TestDataSink(beans));

         dataReader.read(new StringReader(testDataXml));

 

-        assertEquals(1, readObjects.size());

+        assertEquals(1, beans.size());

 

-        DynaBean obj = (DynaBean)readObjects.get(0);

+        DynaBean obj = (DynaBean)beans.get(0);

 

         assertEquals("Test",

                      obj.getDynaClass().getName());

@@ -664,37 +670,32 @@
      */

     public void testCaseSensitivityTurnedOff() throws Exception

     {

-        final String testSchemaXml = 

+        Database model = readModel(

             "<?xml version='1.0' encoding='ISO-8859-1'?>\n"+

             "<database xmlns='" + DatabaseIO.DDLUTILS_NAMESPACE + "' name='test'>\n" +

             "  <table name='Test'>\n"+

             "    <column name='Id' type='INTEGER' primaryKey='true' required='true'/>\n"+

             "    <column name='Value' type='VARCHAR' size='50' required='true'/>\n"+

             "  </table>\n"+

-            "</database>";

-        final String testDataXml =

+            "</database>");

+        String testDataXml =

             "<data>\n"+

             "  <test Id='1' Value='foo'/>\n"+

             "  <Test Id='2' value='bar'/>\n"+

             "  <Test id='3' Value='baz'/>\n"+

             "</data>";

 

-        DatabaseIO modelReader = new DatabaseIO();

-

-        modelReader.setValidateXml(true);

-        

-        Database        model       = modelReader.read(new StringReader(testSchemaXml));

-        final ArrayList readObjects = new ArrayList();

-        DataReader      dataReader  = new DataReader();

+        ArrayList  beans      = new ArrayList();

+        DataReader dataReader = new DataReader();

 

         dataReader.setCaseSensitive(false);

         dataReader.setModel(model);

-        dataReader.setSink(new TestDataSink(readObjects));

+        dataReader.setSink(new TestDataSink(beans));

         dataReader.read(new StringReader(testDataXml));

 

-        assertEquals(3, readObjects.size());

+        assertEquals(3, beans.size());

 

-        DynaBean obj = (DynaBean)readObjects.get(0);

+        DynaBean obj = (DynaBean)beans.get(0);

 

         assertEquals("Test",

                      obj.getDynaClass().getName());

@@ -703,7 +704,7 @@
         assertEquals("foo",

                      obj.get("Value").toString());

 

-        obj = (DynaBean)readObjects.get(1);

+        obj = (DynaBean)beans.get(1);

 

         assertEquals("Test",

                      obj.getDynaClass().getName());

@@ -712,7 +713,7 @@
         assertEquals("bar",

                      obj.get("Value").toString());

 

-        obj = (DynaBean)readObjects.get(2);

+        obj = (DynaBean)beans.get(2);

 

         assertEquals("Test",

                      obj.getDynaClass().getName());

@@ -727,58 +728,28 @@
      */

     public void testSpecialCharacters() throws Exception

     {

-        final String testSchemaXml = 

+        Database model = readModel(

             "<?xml version='1.0' encoding='ISO-8859-1'?>\n"+

             "<database xmlns='" + DatabaseIO.DDLUTILS_NAMESPACE + "' name='test'>\n" +

             "  <table name='test'>\n"+

             "    <column name='id' type='INTEGER' primaryKey='true' required='true'/>\n"+

             "    <column name='value' type='VARCHAR' size='50' required='true'/>\n"+

             "  </table>\n"+

-            "</database>";

-        final String testedValue = "Some Special Characters: \u0001\u0009\u0010";

+            "</database>");

+        String testedValue = "Some Special Characters: \u0001\u0009\u0010";

 

-        DatabaseIO modelIO = new DatabaseIO();

-

-        modelIO.setValidateXml(true);

-        

-        Database              model      = modelIO.read(new StringReader(testSchemaXml));

-        ByteArrayOutputStream output     = new ByteArrayOutputStream();

-        DataWriter            dataWriter = new DataWriter(output, "ISO-8859-1");

-        SqlDynaBean           bean       = (SqlDynaBean)model.createDynaBeanFor(model.getTable(0));

+        SqlDynaBean bean = (SqlDynaBean)model.createDynaBeanFor(model.getTable(0));

 

         bean.set("id", new Integer(1));

         bean.set("value", testedValue);

-        dataWriter.writeDocumentStart();

-        dataWriter.write(bean);

-        dataWriter.writeDocumentEnd();

 

-        final ArrayList readObjects = new ArrayList();

-        DataReader      dataReader  = new DataReader();

-

-        String dataXml = new String(output.toByteArray(), "ISO-8859-1");

-

-        assertEquals("<?xml version='1.0' encoding='ISO-8859-1'?>\n" +

-                     "<data>\n" +

-                     "  <test id=\"1\">\n" +

-                     "    <value " + DatabaseIO.BASE64_ATTR_NAME + "=\"true\"><![CDATA[" + new String(Base64.encodeBase64(testedValue.getBytes()), "ISO-8859-1") + "]]></value>\n" +

-                     "  </test>\n" +

-                     "</data>\n",

-                     dataXml);

-

-        dataReader.setModel(model);

-        dataReader.setSink(new TestDataSink(readObjects));

-        dataReader.read(new ByteArrayInputStream(output.toByteArray()));

-

-        assertEquals(1, readObjects.size());

-

-        DynaBean obj = (DynaBean)readObjects.get(0);

-

-        assertEquals("test",

-                     obj.getDynaClass().getName());

-        assertEquals("1",

-                     obj.get("id").toString());

-        assertEquals(testedValue,

-                     obj.get("value").toString());

+        roundtripTest(model, bean, "ISO-8859-1",

+                      "<?xml version='1.0' encoding='ISO-8859-1'?>\n" +

+                      "<data>\n" +

+                      "  <test id=\"1\">\n" +

+                      "    <value " + DatabaseIO.BASE64_ATTR_NAME + "=\"true\">" + new String(Base64.encodeBase64(testedValue.getBytes("UTF-8")), "ISO-8859-1") + "</value>\n" +

+                      "  </test>\n" +

+                      "</data>\n");

     }

 

     /**

@@ -786,58 +757,28 @@
      */

     public void testSpecialCharactersUTF8() throws Exception

     {

-        final String testSchemaXml = 

+        Database model = readModel(

             "<?xml version='1.0' encoding='UTF-8'?>\n"+

             "<database xmlns='" + DatabaseIO.DDLUTILS_NAMESPACE + "' name='test'>\n" +

             "  <table name='test'>\n"+

             "    <column name='id' type='INTEGER' primaryKey='true' required='true'/>\n"+

             "    <column name='value' type='VARCHAR' size='50' required='true'/>\n"+

             "  </table>\n"+

-            "</database>";

-        final String testedValue = "Some Special Characters: \u0001\u0009\u0010";

+            "</database>");

+        String testedValue = "Some Special Characters: \u0001\u0009\u0010";

 

-        DatabaseIO modelIO = new DatabaseIO();

-

-        modelIO.setValidateXml(true);

-        

-        Database              model      = modelIO.read(new StringReader(testSchemaXml));

-        ByteArrayOutputStream output     = new ByteArrayOutputStream();

-        DataWriter            dataWriter = new DataWriter(output, "UTF-8");

-        SqlDynaBean           bean       = (SqlDynaBean)model.createDynaBeanFor(model.getTable(0));

+        SqlDynaBean bean = (SqlDynaBean)model.createDynaBeanFor(model.getTable(0));

 

         bean.set("id", new Integer(1));

         bean.set("value", testedValue);

-        dataWriter.writeDocumentStart();

-        dataWriter.write(bean);

-        dataWriter.writeDocumentEnd();

 

-        String dataXml = new String(output.toByteArray(), "UTF-8");

-

-        assertEquals("<?xml version='1.0' encoding='UTF-8'?>\n" +

-                     "<data>\n" +

-                     "  <test id=\"1\">\n" +

-                     "    <value " + DatabaseIO.BASE64_ATTR_NAME + "=\"true\"><![CDATA[" + new String(Base64.encodeBase64(testedValue.getBytes()), "UTF-8") + "]]></value>\n" +

-                     "  </test>\n" +

-                     "</data>\n",

-                     dataXml);

-

-        final ArrayList readObjects = new ArrayList();

-        DataReader      dataReader  = new DataReader();

-

-        dataReader.setModel(model);

-        dataReader.setSink(new TestDataSink(readObjects));

-        dataReader.read(new ByteArrayInputStream(output.toByteArray()));

-

-        assertEquals(1, readObjects.size());

-

-        DynaBean obj = (DynaBean)readObjects.get(0);

-

-        assertEquals("test",

-                     obj.getDynaClass().getName());

-        assertEquals("1",

-                     obj.get("id").toString());

-        assertEquals(testedValue,

-                     obj.get("value").toString());

+        roundtripTest(model, bean, "ISO-8859-1",

+                      "<?xml version='1.0' encoding='ISO-8859-1'?>\n" +

+                      "<data>\n" +

+                      "  <test id=\"1\">\n" +

+                      "    <value " + DatabaseIO.BASE64_ATTR_NAME + "=\"true\">" + new String(Base64.encodeBase64(testedValue.getBytes("UTF-8")), "UTF-8") + "</value>\n" +

+                      "  </test>\n" +

+                      "</data>\n");

     }

 

     /**

@@ -845,7 +786,7 @@
      */

     public void testCData() throws Exception

     {

-        final String testSchemaXml = 

+        Database model = readModel(

             "<?xml version='1.0' encoding='ISO-8859-1'?>\n"+

             "<database xmlns='" + DatabaseIO.DDLUTILS_NAMESPACE + "' name='test'>\n" +

             "  <table name='test'>\n"+

@@ -856,21 +797,14 @@
             "    <column name='value4' type='LONGVARCHAR' size='4000' required='true'/>\n"+

             "    <column name='value5' type='LONGVARCHAR' size='4000' required='true'/>\n"+

             "  </table>\n"+

-            "</database>";

-        final String testedValue1 = "<?xml version='1.0' encoding='ISO-8859-1'?><test><![CDATA[some text]]></test>";

-        final String testedValue2 = StringUtils.repeat("a ", 1000) + testedValue1;

-        final String testedValue3 = "<div>\n<h1><![CDATA[WfMOpen]]></h1>\n" + StringUtils.repeat("Make it longer\n", 99) +  "</div>";

-        final String testedValue4 = "<![CDATA[" + StringUtils.repeat("b \n", 1000) +  "]]>";

-        final String testedValue5 = "<<![CDATA[" + StringUtils.repeat("b \n", 500) +  "]]>><![CDATA[" + StringUtils.repeat("c \n", 500) +  "]]>";

+            "</database>");

+        String testedValue1 = "<?xml version='1.0' encoding='ISO-8859-1'?><test><![CDATA[some text]]></test>";

+        String testedValue2 = StringUtils.repeat("a ", 1000) + testedValue1;

+        String testedValue3 = "<div>\n<h1><![CDATA[WfMOpen]]></h1>\n" + StringUtils.repeat("Make it longer\n", 99) +  "</div>";

+        String testedValue4 = "<![CDATA[" + StringUtils.repeat("b \n", 1000) +  "]]>";

+        String testedValue5 = "<<![CDATA[" + StringUtils.repeat("b \n", 500) +  "]]>><![CDATA[" + StringUtils.repeat("c \n", 500) +  "]]>";

 

-        DatabaseIO modelIO = new DatabaseIO();

-

-        modelIO.setValidateXml(true);

-

-        Database     model      = modelIO.read(new StringReader(testSchemaXml));

-        StringWriter output     = new StringWriter();

-        DataWriter   dataWriter = new DataWriter(output, "UTF-8");

-        SqlDynaBean  bean       = (SqlDynaBean)model.createDynaBeanFor(model.getTable(0));

+        SqlDynaBean  bean = (SqlDynaBean)model.createDynaBeanFor(model.getTable(0));

 

         bean.set("id", new Integer(1));

         bean.set("value1", testedValue1);

@@ -878,37 +812,42 @@
         bean.set("value3", testedValue3);

         bean.set("value4", testedValue4);

         bean.set("value5", testedValue5);

-        dataWriter.writeDocumentStart();

-        dataWriter.write(bean);

-        dataWriter.writeDocumentEnd();

 

-        String dataXml = output.toString();

+        byte[] xmlData = writeBean(model, bean, "UTF-8");

+        List   beans   = readBeans(model, xmlData);

 

-        final ArrayList readObjects = new ArrayList();

-        DataReader      dataReader  = new DataReader();

+        assertEquals(1, beans.size());

+        assertEquals(bean, beans.get(0));

+    }

 

-        dataReader.setModel(model);

-        dataReader.setSink(new TestDataSink(readObjects));

-        dataReader.read(new StringReader(dataXml));

+    /**

+     * Tests the reader & writer behavior when the table name is not a valid XML identifier.

+     */

+    public void testTableNameLong() throws Exception

+    {

+        String   tableName = StringUtils.repeat("test", 100);

+        Database model     = readModel(

+            "<?xml version='1.0' encoding='UTF-8'?>\n"+

+            "<database xmlns='" + DatabaseIO.DDLUTILS_NAMESPACE + "' name='test'>\n" +

+            "  <table name='" + tableName + "'>\n"+

+            "    <column name='id' type='INTEGER' primaryKey='true' required='true'/>\n"+

+            "    <column name='value' type='VARCHAR' size='50' required='true'/>\n"+

+            "  </table>\n"+

+            "</database>");

+        String testedValue = "Some Text";

 

-        assertEquals(1, readObjects.size());

+        SqlDynaBean bean = (SqlDynaBean)model.createDynaBeanFor(model.getTable(0));

 

-        DynaBean obj = (DynaBean)readObjects.get(0);

+        bean.set("id", new Integer(1));

+        bean.set("value", testedValue);

 

-        assertEquals("test",

-                     obj.getDynaClass().getName());

-        assertEquals("1",

-                     obj.get("id").toString());

-        assertEquals(testedValue1,

-                     obj.get("value1").toString());

-        assertEquals(testedValue2,

-                     obj.get("value2").toString());

-        assertEquals(testedValue3,

-                     obj.get("value3").toString());

-        assertEquals(testedValue4,

-                     obj.get("value4").toString());

-        assertEquals(testedValue5,

-                     obj.get("value5").toString());

+        roundtripTest(model, bean, "UTF-8",

+                      "<?xml version='1.0' encoding='UTF-8'?>\n" +

+                      "<data>\n" +

+                      "  <table id=\"1\" value=\"" + testedValue + "\">\n" +

+                      "    <table-name>" + tableName + "</table-name>\n" +

+                      "  </table>\n" +

+                      "</data>\n");

     }

 

     /**

@@ -916,53 +855,864 @@
      */

     public void testTableNameNotAValidXmlIdentifier() throws Exception

     {

-        final String testSchemaXml = 

-            "<?xml version='1.0' encoding='ISO-8859-1'?>\n"+

+        Database model = readModel(

+            "<?xml version='1.0' encoding='UTF-8'?>\n"+

+            "<database xmlns='" + DatabaseIO.DDLUTILS_NAMESPACE + "' name='test'>\n" +

+            "  <table name='test$'>\n"+

+            "    <column name='id' type='INTEGER' primaryKey='true' required='true'/>\n"+

+            "    <column name='value' type='VARCHAR' size='50' required='true'/>\n"+

+            "  </table>\n"+

+            "</database>");

+        String testedValue = "Some Text";

+

+        SqlDynaBean bean = (SqlDynaBean)model.createDynaBeanFor(model.getTable(0));

+

+        bean.set("id", new Integer(1));

+        bean.set("value", testedValue);

+

+        roundtripTest(model, bean, "UTF-8",

+                      "<?xml version='1.0' encoding='UTF-8'?>\n" +

+                      "<data>\n" +

+                      "  <table table-name=\"test$\" id=\"1\" value=\"" + testedValue + "\" />\n" +

+                      "</data>\n");

+    }

+

+    /**

+     * Tests the reader & writer behavior when the table name is not a valid XML identifier and too long.

+     */

+    public void testTableNameInvalidAndLong() throws Exception

+    {

+        String   tableName = StringUtils.repeat("table name", 50);

+        Database model     = readModel(

+            "<?xml version='1.0' encoding='UTF-8'?>\n"+

+            "<database xmlns='" + DatabaseIO.DDLUTILS_NAMESPACE + "' name='test'>\n" +

+            "  <table name='" + tableName + "'>\n"+

+            "    <column name='id' type='INTEGER' primaryKey='true' required='true'/>\n"+

+            "    <column name='value' type='VARCHAR' size='50' required='true'/>\n"+

+            "  </table>\n"+

+            "</database>");

+        String testedValue = "Some Text";

+

+        SqlDynaBean bean = (SqlDynaBean)model.createDynaBeanFor(model.getTable(0));

+

+        bean.set("id", new Integer(1));

+        bean.set("value", testedValue);

+

+        roundtripTest(model, bean, "UTF-8",

+                      "<?xml version='1.0' encoding='UTF-8'?>\n" +

+                      "<data>\n" +

+                      "  <table id=\"1\" value=\"" + testedValue + "\">\n" +

+                      "    <table-name>" + tableName + "</table-name>\n" +

+                      "  </table>\n" +

+                      "</data>\n");

+    }

+

+    /**

+     * Tests the reader & writer behavior when the table name contains a '&' character.

+     */

+    public void testTableNameContainsAmpersand() throws Exception

+    {

+        String   tableName   = "test&table";

+        Database model       = new Database("test");

+        Table    table       = new Table();

+        Column   idColumn    = new Column();

+        Column   valueColumn = new Column();

+

+        idColumn.setName("id");

+        idColumn.setType("INTEGER");

+        idColumn.setPrimaryKey(true);

+        idColumn.setRequired(true);

+        valueColumn.setName("value");

+        valueColumn.setType("VARCHAR");

+        valueColumn.setSize("50");

+        valueColumn.setRequired(true);

+        table.setName(tableName);

+        table.addColumn(idColumn);

+        table.addColumn(valueColumn);

+        model.addTable(table);

+

+        SqlDynaBean bean        = (SqlDynaBean)model.createDynaBeanFor(model.getTable(0));

+        String      testedValue = "Some Text";

+

+        bean.set("id", new Integer(1));

+        bean.set("value", testedValue);

+

+        roundtripTest(model, bean, "UTF-8",

+                      "<?xml version='1.0' encoding='UTF-8'?>\n" +

+                      "<data>\n" +

+                      "  <table table-name=\"test&amp;table\" id=\"1\" value=\"" + testedValue + "\" />\n" +

+                      "</data>\n");

+    }

+

+    /**

+     * Tests the reader & writer behavior when the table name contains a '<' character.

+     */

+    public void testTableNameContainsLessCharacter() throws Exception

+    {

+        String   tableName   = "test<table";

+        Database model       = new Database("test");

+        Table    table       = new Table();

+        Column   idColumn    = new Column();

+        Column   valueColumn = new Column();

+

+        idColumn.setName("id");

+        idColumn.setType("INTEGER");

+        idColumn.setPrimaryKey(true);

+        idColumn.setRequired(true);

+        valueColumn.setName("value");

+        valueColumn.setType("VARCHAR");

+        valueColumn.setSize("50");

+        valueColumn.setRequired(true);

+        table.setName(tableName);

+        table.addColumn(idColumn);

+        table.addColumn(valueColumn);

+        model.addTable(table);

+

+        SqlDynaBean bean        = (SqlDynaBean)model.createDynaBeanFor(model.getTable(0));

+        String      testedValue = "Some Text";

+

+        bean.set("id", new Integer(1));

+        bean.set("value", testedValue);

+

+        roundtripTest(model, bean, "UTF-8",

+                      "<?xml version='1.0' encoding='UTF-8'?>\n" +

+                      "<data>\n" +

+                      "  <table table-name=\"test&lt;table\" id=\"1\" value=\"" + testedValue + "\" />\n" +

+                      "</data>\n");

+    }

+

+    /**

+     * Tests the reader & writer behavior when the table name contains a '>' character.

+     */

+    public void testTableNameContainsMoreCharacter() throws Exception

+    {

+        String   tableName   = "test>table";

+        Database model       = new Database("test");

+        Table    table       = new Table();

+        Column   idColumn    = new Column();

+        Column   valueColumn = new Column();

+

+        idColumn.setName("id");

+        idColumn.setType("INTEGER");

+        idColumn.setPrimaryKey(true);

+        idColumn.setRequired(true);

+        valueColumn.setName("value");

+        valueColumn.setType("VARCHAR");

+        valueColumn.setSize("50");

+        valueColumn.setRequired(true);

+        table.setName(tableName);

+        table.addColumn(idColumn);

+        table.addColumn(valueColumn);

+        model.addTable(table);

+

+        SqlDynaBean bean        = (SqlDynaBean)model.createDynaBeanFor(model.getTable(0));

+        String      testedValue = "Some Text";

+

+        bean.set("id", new Integer(1));

+        bean.set("value", testedValue);

+

+        roundtripTest(model, bean, "UTF-8",

+                      "<?xml version='1.0' encoding='UTF-8'?>\n" +

+                      "<data>\n" +

+                      "  <table table-name=\"test>table\" id=\"1\" value=\"" + testedValue + "\" />\n" +

+                      "</data>\n");

+    }

+

+    /**

+     * Tests the reader & writer behavior when the table name contains characters not allowed in XML.

+     */

+    public void testTableNameContainsInvalidCharacters() throws Exception

+    {

+        String   tableName   = "test\u0000table";

+        Database model       = new Database("test");

+        Table    table       = new Table();

+        Column   idColumn    = new Column();

+        Column   valueColumn = new Column();

+

+        idColumn.setName("id");

+        idColumn.setType("INTEGER");

+        idColumn.setPrimaryKey(true);

+        idColumn.setRequired(true);

+        valueColumn.setName("value");

+        valueColumn.setType("VARCHAR");

+        valueColumn.setSize("50");

+        valueColumn.setRequired(true);

+        table.setName(tableName);

+        table.addColumn(idColumn);

+        table.addColumn(valueColumn);

+        model.addTable(table);

+

+        SqlDynaBean bean        = (SqlDynaBean)model.createDynaBeanFor(model.getTable(0));

+        String      testedValue = "Some Text";

+

+        bean.set("id", new Integer(1));

+        bean.set("value", testedValue);

+

+        roundtripTest(model, bean, "UTF-8",

+                      "<?xml version='1.0' encoding='UTF-8'?>\n" +

+                      "<data>\n" +

+                      "  <table id=\"1\" value=\"" + testedValue + "\">\n" +

+                      "    <table-name " + DatabaseIO.BASE64_ATTR_NAME + "=\"true\">" + new String(Base64.encodeBase64(tableName.getBytes("UTF-8")), "UTF-8") + "</table-name>\n" +

+                      "  </table>\n" +

+                      "</data>\n");

+    }

+

+    /**

+     * Tests the reader & writer behavior when the table name is 'table'.

+     */

+    public void testTableNameIsTable() throws Exception

+    {

+        Database model = readModel(

+            "<?xml version='1.0' encoding='UTF-8'?>\n"+

+            "<database xmlns='" + DatabaseIO.DDLUTILS_NAMESPACE + "' name='test'>\n" +

+            "  <table name='table'>\n"+

+            "    <column name='id' type='INTEGER' primaryKey='true' required='true'/>\n"+

+            "    <column name='value' type='VARCHAR' size='50' required='true'/>\n"+

+            "  </table>\n"+

+            "</database>");

+        String testedValue = "Some Text";

+

+        DatabaseIO modelIO = new DatabaseIO();

+

+        modelIO.setValidateXml(true);

+

+        SqlDynaBean bean = (SqlDynaBean)model.createDynaBeanFor(model.getTable(0));

+

+        bean.set("id", new Integer(1));

+        bean.set("value", testedValue);

+

+        roundtripTest(model, bean, "UTF-8",

+                      "<?xml version='1.0' encoding='UTF-8'?>\n" +

+                      "<data>\n" +

+                      "  <table table-name=\"table\" id=\"1\" value=\"" + testedValue + "\" />\n" +

+                      "</data>\n");

+    }

+

+    /**

+     * Tests the reader & writer behavior when a column name is a normal valid tag,

+     * and both column name and value are shorter than 255 characters.

+     */

+    public void testColumnNameAndValueShort() throws Exception

+    {

+        Database model = readModel(

+            "<?xml version='1.0' encoding='UTF-8'?>\n"+

             "<database xmlns='" + DatabaseIO.DDLUTILS_NAMESPACE + "' name='test'>\n" +

             "  <table name='test'>\n"+

             "    <column name='id' type='INTEGER' primaryKey='true' required='true'/>\n"+

             "    <column name='value' type='VARCHAR' size='50' required='true'/>\n"+

             "  </table>\n"+

-            "</database>";

-        final String testedValue = "Some Special Characters: \u0001\u0009\u0010";

+            "</database>");

+        String testedValue = "Some Text";

 

-        DatabaseIO modelIO = new DatabaseIO();

-

-        modelIO.setValidateXml(true);

-        

-        Database     model      = modelIO.read(new StringReader(testSchemaXml));

-        StringWriter output     = new StringWriter();

-        DataWriter   dataWriter = new DataWriter(output, "UTF-8");

-        SqlDynaBean  bean       = (SqlDynaBean)model.createDynaBeanFor(model.getTable(0));

+        SqlDynaBean bean = (SqlDynaBean)model.createDynaBeanFor(model.getTable(0));

 

         bean.set("id", new Integer(1));

         bean.set("value", testedValue);

-        dataWriter.writeDocumentStart();

-        dataWriter.write(bean);

-        dataWriter.writeDocumentEnd();

 

-        String dataXml = output.toString();

-

-        final ArrayList readObjects = new ArrayList();

-        DataReader      dataReader  = new DataReader();

-

-        dataReader.setModel(model);

-        dataReader.setSink(new TestDataSink(readObjects));

-        dataReader.read(new StringReader(dataXml));

-

-        assertEquals(1, readObjects.size());

-

-        DynaBean obj = (DynaBean)readObjects.get(0);

-

-        assertEquals("test",

-                     obj.getDynaClass().getName());

-        assertEquals("1",

-                     obj.get("id").toString());

-        assertEquals(testedValue,

-                     obj.get("value").toString());

+        roundtripTest(model, bean, "UTF-8",

+                      "<?xml version='1.0' encoding='UTF-8'?>\n" +

+                      "<data>\n" +

+                      "  <test id=\"1\" value=\"" + testedValue + "\" />\n" +

+                      "</data>\n");

     }

 

-    // TODO: additional tests

-    // - table name with illegal-for-XML characters, e.g space, &, ... (write)

-    // - column name with illegal-for-XML characters, e.g space, &, ... (write)

+    /**

+     * Tests the reader & writer behavior when a column name is a normal valid tag,

+     * and the column name is shorter than 255 characters but the value is longer.

+     */

+    public void testColumnNameShortAndValueLong() throws Exception

+    {

+        Database model = readModel(

+            "<?xml version='1.0' encoding='UTF-8'?>\n"+

+            "<database xmlns='" + DatabaseIO.DDLUTILS_NAMESPACE + "' name='test'>\n" +

+            "  <table name='test'>\n"+

+            "    <column name='id' type='INTEGER' primaryKey='true' required='true'/>\n"+

+            "    <column name='value' type='VARCHAR' size='400' required='true'/>\n"+

+            "  </table>\n"+

+            "</database>");

+        String testedValue = StringUtils.repeat("Some Text", 40);

+

+        SqlDynaBean bean = (SqlDynaBean)model.createDynaBeanFor(model.getTable(0));

+

+        bean.set("id", new Integer(1));

+        bean.set("value", testedValue);

+

+        roundtripTest(model, bean, "UTF-8",

+                      "<?xml version='1.0' encoding='UTF-8'?>\n" +

+                      "<data>\n" +

+                      "  <test id=\"1\">\n" +

+                      "    <value>" + testedValue + "</value>\n" +

+                      "  </test>\n" +

+                      "</data>\n");

+    }

+

+    /**

+     * Tests the reader & writer behavior when a column name is not a valid XML identifier.

+     */

+    public void testColumnNameShortAndInvalidAndValueShort() throws Exception

+    {

+        Database model = readModel(

+            "<?xml version='1.0' encoding='UTF-8'?>\n"+

+            "<database xmlns='" + DatabaseIO.DDLUTILS_NAMESPACE + "' name='test'>\n" +

+            "  <table name='test'>\n"+

+            "    <column name='id' type='INTEGER' primaryKey='true' required='true'/>\n"+

+            "    <column name='the value' type='VARCHAR' size='50' required='true'/>\n"+

+            "  </table>\n"+

+            "</database>");

+        String testedValue = "Some Text";

+

+        SqlDynaBean bean = (SqlDynaBean)model.createDynaBeanFor(model.getTable(0));

+

+        bean.set("id", new Integer(1));

+        bean.set("the value", testedValue);

+

+        roundtripTest(model, bean, "UTF-8",

+                      "<?xml version='1.0' encoding='UTF-8'?>\n" +

+                      "<data>\n" +

+                      "  <test id=\"1\">\n" +

+                      "    <column column-name=\"the value\">" + testedValue + "</column>\n" +

+                      "  </test>\n" +

+                      "</data>\n");

+    }

+

+    /**

+     * Tests the reader & writer behavior when a column name is not a valid tag,

+     * and the column name is shorter than 255 characters and the value is longer.

+     */

+    public void testColumnNameShortAndInvalidAndValueLong() throws Exception

+    {

+        Database model = readModel(

+            "<?xml version='1.0' encoding='UTF-8'?>\n"+

+            "<database xmlns='" + DatabaseIO.DDLUTILS_NAMESPACE + "' name='test'>\n" +

+            "  <table name='test'>\n"+

+            "    <column name='id' type='INTEGER' primaryKey='true' required='true'/>\n"+

+            "    <column name='the value' type='VARCHAR' size='50' required='true'/>\n"+

+            "  </table>\n"+

+            "</database>");

+        String testedValue = StringUtils.repeat("Some Text", 40);

+

+        SqlDynaBean bean = (SqlDynaBean)model.createDynaBeanFor(model.getTable(0));

+

+        bean.set("id", new Integer(1));

+        bean.set("the value", testedValue);

+

+        roundtripTest(model, bean, "UTF-8",

+                      "<?xml version='1.0' encoding='UTF-8'?>\n" +

+                      "<data>\n" +

+                      "  <test id=\"1\">\n" +

+                      "    <column column-name=\"the value\">" + testedValue + "</column>\n" +

+                      "  </test>\n" +

+                      "</data>\n");

+    }

+

+    /**

+     * Tests the reader & writer behavior when a column name is a valid tag,

+     * and the column name is longer than 255 characters and the value is shorter.

+     */

+    public void testColumnNameLongAndValueShort() throws Exception

+    {

+        String columnName = StringUtils.repeat("value", 100);

+        Database model = readModel(

+            "<?xml version='1.0' encoding='UTF-8'?>\n"+

+            "<database xmlns='" + DatabaseIO.DDLUTILS_NAMESPACE + "' name='test'>\n" +

+            "  <table name='test'>\n"+

+            "    <column name='id' type='INTEGER' primaryKey='true' required='true'/>\n"+

+            "    <column name='" + columnName + "' type='VARCHAR' size='50' required='true'/>\n"+

+            "  </table>\n"+

+            "</database>");

+        String testedValue = "Some Text";

+

+        SqlDynaBean bean = (SqlDynaBean)model.createDynaBeanFor(model.getTable(0));

+

+        bean.set("id", new Integer(1));

+        bean.set(columnName, testedValue);

+

+        roundtripTest(model, bean, "UTF-8",

+                      "<?xml version='1.0' encoding='UTF-8'?>\n" +

+                      "<data>\n" +

+                      "  <test id=\"1\">\n" +

+                      "    <column>\n" +

+                      "      <column-name>" + columnName + "</column-name>\n" +

+                      "      <column-value>" + testedValue + "</column-value>\n" +

+                      "    </column>\n" +

+                      "  </test>\n" +

+                      "</data>\n");

+    }

+

+    /**

+     * Tests the reader & writer behavior when a column name is a valid tag,

+     * and both the column name and value are longer than 255 characters.

+     */

+    public void testColumnNameLongAndValueLong() throws Exception

+    {

+        String columnName = StringUtils.repeat("value", 100);

+        Database model = readModel(

+            "<?xml version='1.0' encoding='UTF-8'?>\n"+

+            "<database xmlns='" + DatabaseIO.DDLUTILS_NAMESPACE + "' name='test'>\n" +

+            "  <table name='test'>\n"+

+            "    <column name='id' type='INTEGER' primaryKey='true' required='true'/>\n"+

+            "    <column name='" + columnName + "' type='VARCHAR' size='500' required='true'/>\n"+

+            "  </table>\n"+

+            "</database>");

+        String testedValue = StringUtils.repeat("Some Text", 40);

+

+        SqlDynaBean bean = (SqlDynaBean)model.createDynaBeanFor(model.getTable(0));

+

+        bean.set("id", new Integer(1));

+        bean.set(columnName, testedValue);

+

+        roundtripTest(model, bean, "UTF-8",

+                      "<?xml version='1.0' encoding='UTF-8'?>\n" +

+                      "<data>\n" +

+                      "  <test id=\"1\">\n" +

+                      "    <column>\n" +

+                      "      <column-name>" + columnName + "</column-name>\n" +

+                      "      <column-value>" + testedValue + "</column-value>\n" +

+                      "    </column>\n" +

+                      "  </test>\n" +

+                      "</data>\n");

+    }

+

+    /**

+     * Tests the reader & writer behavior when a column name is a valid tag,

+     * and the column name is longer than 255 characters and the value is shorter.

+     */

+    public void testColumnNameAndValueLong() throws Exception

+    {

+        String columnName = StringUtils.repeat("value", 100);

+        Database model = readModel(

+            "<?xml version='1.0' encoding='UTF-8'?>\n"+

+            "<database xmlns='" + DatabaseIO.DDLUTILS_NAMESPACE + "' name='test'>\n" +

+            "  <table name='test'>\n"+

+            "    <column name='id' type='INTEGER' primaryKey='true' required='true'/>\n"+

+            "    <column name='" + columnName + "' type='VARCHAR' size='50' required='true'/>\n"+

+            "  </table>\n"+

+            "</database>");

+        String testedValue = "Some Text";

+

+        SqlDynaBean bean = (SqlDynaBean)model.createDynaBeanFor(model.getTable(0));

+

+        bean.set("id", new Integer(1));

+        bean.set(columnName, testedValue);

+

+        roundtripTest(model, bean, "UTF-8",

+                      "<?xml version='1.0' encoding='UTF-8'?>\n" +

+                      "<data>\n" +

+                      "  <test id=\"1\">\n" +

+                      "    <column>\n" +

+                      "      <column-name>" + columnName + "</column-name>\n" +

+                      "      <column-value>" + testedValue + "</column-value>\n" +

+                      "    </column>\n" +

+                      "  </test>\n" +

+                      "</data>\n");

+    }

+

+    /**

+     * Tests the reader & writer behavior when a column name is not a valid tag,

+     * and the value is invalid, and both are short.

+     */

+    public void testColumnNameAndValueShortAndInvalid() throws Exception

+    {

+        Database model = readModel(

+            "<?xml version='1.0' encoding='UTF-8'?>\n"+

+            "<database xmlns='" + DatabaseIO.DDLUTILS_NAMESPACE + "' name='test'>\n" +

+            "  <table name='test'>\n"+

+            "    <column name='id' type='INTEGER' primaryKey='true' required='true'/>\n"+

+            "    <column name='the value' type='VARCHAR' size='50' required='true'/>\n"+

+            "  </table>\n"+

+            "</database>");

+        String testedValue = "the\u0000value";

+

+        SqlDynaBean bean = (SqlDynaBean)model.createDynaBeanFor(model.getTable(0));

+

+        bean.set("id", new Integer(1));

+        bean.set("the value", testedValue);

+

+        roundtripTest(model, bean, "UTF-8",

+                      "<?xml version='1.0' encoding='UTF-8'?>\n" +

+                      "<data>\n" +

+                      "  <test id=\"1\">\n" +

+                      "    <column column-name=\"the value\" " + DatabaseIO.BASE64_ATTR_NAME + "=\"true\">" + new String(Base64.encodeBase64(testedValue.getBytes("UTF-8")), "UTF-8") + "</column>\n" +

+                      "  </test>\n" +

+                      "</data>\n");

+    }

+

+    /**

+     * Tests the reader & writer behavior when a column name is a valid tag and longer,

+     * than 255 characters, and the value is invalid and shorter than 255 characters.

+     */

+    public void testColumnNameLongAndValueInvalidAndShort() throws Exception

+    {

+        String columnName = StringUtils.repeat("value", 100);

+        Database model = readModel(

+            "<?xml version='1.0' encoding='UTF-8'?>\n"+

+            "<database xmlns='" + DatabaseIO.DDLUTILS_NAMESPACE + "' name='test'>\n" +

+            "  <table name='test'>\n"+

+            "    <column name='id' type='INTEGER' primaryKey='true' required='true'/>\n"+

+            "    <column name='" + columnName + "' type='VARCHAR' size='50' required='true'/>\n"+

+            "  </table>\n"+

+            "</database>");

+        String testedValue = "the\u0000value";

+

+        SqlDynaBean bean = (SqlDynaBean)model.createDynaBeanFor(model.getTable(0));

+

+        bean.set("id", new Integer(1));

+        bean.set(columnName, testedValue);

+

+        roundtripTest(model, bean, "UTF-8",

+                      "<?xml version='1.0' encoding='UTF-8'?>\n" +

+                      "<data>\n" +

+                      "  <test id=\"1\">\n" +

+                      "    <column>\n" +

+                      "      <column-name>" + columnName + "</column-name>\n" +

+                      "      <column-value " + DatabaseIO.BASE64_ATTR_NAME + "=\"true\">" + new String(Base64.encodeBase64(testedValue.getBytes("UTF-8")), "UTF-8") + "</column-value>\n" +

+                      "    </column>\n" +

+                      "  </test>\n" +

+                      "</data>\n");

+    }

+

+    /**

+     * Tests the reader & writer behavior when a column name is not a valid tag,

+     * and the value is invalid, and both are short.

+     */

+    public void testColumnNameAndValueLongAndInvalid() throws Exception

+    {

+        Database model       = new Database("test");

+        Table    table       = new Table();

+        Column   idColumn    = new Column();

+        Column   valueColumn = new Column();

+        String   columnName  = StringUtils.repeat("the\u0000name", 100);

+

+        idColumn.setName("id");

+        idColumn.setType("INTEGER");

+        idColumn.setPrimaryKey(true);

+        idColumn.setRequired(true);

+        valueColumn.setName(columnName);

+        valueColumn.setType("VARCHAR");

+        valueColumn.setSize("50");

+        valueColumn.setRequired(true);

+        table.setName("test");

+        table.addColumn(idColumn);

+        table.addColumn(valueColumn);

+        model.addTable(table);

+

+        SqlDynaBean bean        = (SqlDynaBean)model.createDynaBeanFor(model.getTable(0));

+        String      testedValue = StringUtils.repeat("the\u0000value", 40);

+

+        bean.set("id", new Integer(1));

+        bean.set(columnName, testedValue);

+

+        roundtripTest(model, bean, "UTF-8",

+                      "<?xml version='1.0' encoding='UTF-8'?>\n" +

+                      "<data>\n" +

+                      "  <test id=\"1\">\n" +

+                      "    <column>\n" +

+                      "      <column-name " + DatabaseIO.BASE64_ATTR_NAME + "=\"true\">" + new String(Base64.encodeBase64(columnName.getBytes("UTF-8")), "UTF-8") + "</column-name>\n" +

+                      "      <column-value " + DatabaseIO.BASE64_ATTR_NAME + "=\"true\">" + new String(Base64.encodeBase64(testedValue.getBytes("UTF-8")), "UTF-8") + "</column-value>\n" +

+                      "    </column>\n" +

+                      "  </test>\n" +

+                      "</data>\n");

+    }

+

+    /**

+     * Tests the reader & writer behavior when a column name contains an invalid character.

+     */

+    public void testColumnNameContainsInvalidCharacters() throws Exception

+    {

+        Database model = readModel(

+            "<?xml version='1.0' encoding='UTF-8'?>\n"+

+            "<database xmlns='" + DatabaseIO.DDLUTILS_NAMESPACE + "' name='test'>\n" +

+            "  <table name='test'>\n"+

+            "    <column name='id' type='INTEGER' primaryKey='true' required='true'/>\n"+

+            "    <column name='value' type='VARCHAR' size='50' required='true'/>\n"+

+            "  </table>\n"+

+            "</database>");

+        String  testedValue = "the\u0000value";

+

+        SqlDynaBean bean = (SqlDynaBean)model.createDynaBeanFor(model.getTable(0));

+

+        bean.set("id", new Integer(1));

+        bean.set("value", testedValue);

+

+        roundtripTest(model, bean, "UTF-8",

+                      "<?xml version='1.0' encoding='UTF-8'?>\n" +

+                      "<data>\n" +

+                      "  <test id=\"1\">\n" +

+                      "    <value " + DatabaseIO.BASE64_ATTR_NAME + "=\"true\">" + new String(Base64.encodeBase64(testedValue.getBytes("UTF-8")), "UTF-8") + "</value>\n" +

+                      "  </test>\n" +

+                      "</data>\n");

+    }

+

+    /**

+     * Tests the reader & writer behavior when a column value contains an invalid character.

+     */

+    public void testColumnValueContainsInvalidCharacters() throws Exception

+    {

+        Database model       = new Database("test");

+        Table    table       = new Table();

+        Column   idColumn    = new Column();

+        Column   valueColumn = new Column();

+        String   columnName  = "the\u0000value";

+

+        idColumn.setName("id");

+        idColumn.setType("INTEGER");

+        idColumn.setPrimaryKey(true);

+        idColumn.setRequired(true);

+        valueColumn.setName(columnName);

+        valueColumn.setType("VARCHAR");

+        valueColumn.setSize("50");

+        valueColumn.setRequired(true);

+        table.setName("test");

+        table.addColumn(idColumn);

+        table.addColumn(valueColumn);

+        model.addTable(table);

+

+        SqlDynaBean bean        = (SqlDynaBean)model.createDynaBeanFor(model.getTable(0));

+        String      testedValue = "Some Text";

+

+        bean.set("id", new Integer(1));

+        bean.set(columnName, testedValue);

+

+        roundtripTest(model, bean, "UTF-8",

+                      "<?xml version='1.0' encoding='UTF-8'?>\n" +

+                      "<data>\n" +

+                      "  <test id=\"1\">\n" +

+                      "    <column>\n" +

+                      "      <column-name " + DatabaseIO.BASE64_ATTR_NAME + "=\"true\">" + new String(Base64.encodeBase64(columnName.getBytes("UTF-8")), "UTF-8") + "</column-name>\n" +

+                      "      <column-value>" + testedValue + "</column-value>\n" +

+                      "    </column>\n" +

+                      "  </test>\n" +

+                      "</data>\n");

+    }

+

+    /**

+     * Tests the reader & writer behavior when a column value contains the '&' character.

+     */

+    public void testColumnValueContainsAmpersand() throws Exception

+    {

+        Database model       = new Database("test");

+        Table    table       = new Table();

+        Column   idColumn    = new Column();

+        Column   valueColumn = new Column();

+        String   columnName  = "foo&bar";

+

+        idColumn.setName("id");

+        idColumn.setType("INTEGER");

+        idColumn.setPrimaryKey(true);

+        idColumn.setRequired(true);

+        valueColumn.setName(columnName);

+        valueColumn.setType("VARCHAR");

+        valueColumn.setSize("50");

+        valueColumn.setRequired(true);

+        table.setName("test");

+        table.addColumn(idColumn);

+        table.addColumn(valueColumn);

+        model.addTable(table);

+

+        SqlDynaBean bean        = (SqlDynaBean)model.createDynaBeanFor(model.getTable(0));

+        String      testedValue = "Some Text";

+

+        bean.set("id", new Integer(1));

+        bean.set(columnName, testedValue);

+

+        roundtripTest(model, bean, "UTF-8",

+                      "<?xml version='1.0' encoding='UTF-8'?>\n" +

+                      "<data>\n" +

+                      "  <test id=\"1\">\n" +

+                      "    <column column-name=\"foo&amp;bar\">" + testedValue + "</column>\n" +

+                      "  </test>\n" +

+                      "</data>\n");

+    }

+

+    /**

+     * Tests the reader & writer behavior when a column value contains the '<' character.

+     */

+    public void testColumnValueContainsLessCharacter() throws Exception

+    {

+        Database model       = new Database("test");

+        Table    table       = new Table();

+        Column   idColumn    = new Column();

+        Column   valueColumn = new Column();

+        String   columnName  = "foo<bar";

+

+        idColumn.setName("id");

+        idColumn.setType("INTEGER");

+        idColumn.setPrimaryKey(true);

+        idColumn.setRequired(true);

+        valueColumn.setName(columnName);

+        valueColumn.setType("VARCHAR");

+        valueColumn.setSize("50");

+        valueColumn.setRequired(true);

+        table.setName("test");

+        table.addColumn(idColumn);

+        table.addColumn(valueColumn);

+        model.addTable(table);

+

+        SqlDynaBean bean        = (SqlDynaBean)model.createDynaBeanFor(model.getTable(0));

+        String      testedValue = "Some Text";

+

+        bean.set("id", new Integer(1));

+        bean.set(columnName, testedValue);

+

+        roundtripTest(model, bean, "UTF-8",

+                      "<?xml version='1.0' encoding='UTF-8'?>\n" +

+                      "<data>\n" +

+                      "  <test id=\"1\">\n" +

+                      "    <column column-name=\"foo&lt;bar\">" + testedValue + "</column>\n" +

+                      "  </test>\n" +

+                      "</data>\n");

+    }

+

+    /**

+     * Tests the reader & writer behavior when a column value contains the '>' character.

+     */

+    public void testColumnValueContainsMoreCharacter() throws Exception

+    {

+        Database model       = new Database("test");

+        Table    table       = new Table();

+        Column   idColumn    = new Column();

+        Column   valueColumn = new Column();

+        String   columnName  = "foo>bar";

+

+        idColumn.setName("id");

+        idColumn.setType("INTEGER");

+        idColumn.setPrimaryKey(true);

+        idColumn.setRequired(true);

+        valueColumn.setName(columnName);

+        valueColumn.setType("VARCHAR");

+        valueColumn.setSize("50");

+        valueColumn.setRequired(true);

+        table.setName("test");

+        table.addColumn(idColumn);

+        table.addColumn(valueColumn);

+        model.addTable(table);

+

+        SqlDynaBean bean        = (SqlDynaBean)model.createDynaBeanFor(model.getTable(0));

+        String      testedValue = "Some Text";

+

+        bean.set("id", new Integer(1));

+        bean.set(columnName, testedValue);

+

+        roundtripTest(model, bean, "UTF-8",

+                      "<?xml version='1.0' encoding='UTF-8'?>\n" +

+                      "<data>\n" +

+                      "  <test id=\"1\">\n" +

+                      "    <column column-name=\"foo>bar\">" + testedValue + "</column>\n" +

+                      "  </test>\n" +

+                      "</data>\n");

+    }

+

+    /**

+     * Tests the reader & writer behavior when a column name is 'column'.

+     */

+    public void testColumnNameIsColumn() throws Exception

+    {

+        Database model = readModel(

+            "<?xml version='1.0' encoding='UTF-8'?>\n"+

+            "<database xmlns='" + DatabaseIO.DDLUTILS_NAMESPACE + "' name='test'>\n" +

+            "  <table name='test'>\n"+

+            "    <column name='id' type='INTEGER' primaryKey='true' required='true'/>\n"+

+            "    <column name='column' type='VARCHAR' size='50' required='true'/>\n"+

+            "  </table>\n"+

+            "</database>");

+        String testedValue = "Some Text";

+

+        SqlDynaBean bean = (SqlDynaBean)model.createDynaBeanFor(model.getTable(0));

+

+        bean.set("id", new Integer(1));

+        bean.set("column", testedValue);

+

+        roundtripTest(model, bean, "UTF-8",

+                      "<?xml version='1.0' encoding='UTF-8'?>\n" +

+                      "<data>\n" +

+                      "  <test id=\"1\">\n" +

+                      "    <column column-name=\"column\">" + testedValue + "</column>\n" +

+                      "  </test>\n" +

+                      "</data>\n");

+    }

+

+    /**

+     * Tests the reader & writer behavior when a column name is 'column'.

+     */

+    public void testColumnNameIsColumnName() throws Exception

+    {

+        Database model = readModel(

+            "<?xml version='1.0' encoding='UTF-8'?>\n"+

+            "<database xmlns='" + DatabaseIO.DDLUTILS_NAMESPACE + "' name='test'>\n" +

+            "  <table name='test'>\n"+

+            "    <column name='id' type='INTEGER' primaryKey='true' required='true'/>\n"+

+            "    <column name='column-name' type='VARCHAR' size='50' required='true'/>\n"+

+            "  </table>\n"+

+            "</database>");

+        String testedValue = "Some Text";

+

+        SqlDynaBean bean = (SqlDynaBean)model.createDynaBeanFor(model.getTable(0));

+

+        bean.set("id", new Integer(1));

+        bean.set("column-name", testedValue);

+

+        roundtripTest(model, bean, "UTF-8",

+                      "<?xml version='1.0' encoding='UTF-8'?>\n" +

+                      "<data>\n" +

+                      "  <test id=\"1\" column-name=\"" + testedValue + "\" />\n" +

+                      "</data>\n");

+    }

+

+    /**

+     * Tests the reader & writer behavior when a column name is 'column'.

+     */

+    public void testColumnNameIsTableName() throws Exception

+    {

+        Database model = readModel(

+            "<?xml version='1.0' encoding='UTF-8'?>\n"+

+            "<database xmlns='" + DatabaseIO.DDLUTILS_NAMESPACE + "' name='test'>\n" +

+            "  <table name='test'>\n"+

+            "    <column name='id' type='INTEGER' primaryKey='true' required='true'/>\n"+

+            "    <column name='the value' type='VARCHAR' size='50' required='true'/>\n"+

+            "  </table>\n"+

+            "</database>");

+        String testedValue = "Some Text";

+

+        SqlDynaBean bean = (SqlDynaBean)model.createDynaBeanFor(model.getTable(0));

+

+        bean.set("id", new Integer(1));

+        bean.set("the value", testedValue);

+

+        roundtripTest(model, bean, "UTF-8",

+                      "<?xml version='1.0' encoding='UTF-8'?>\n" +

+                      "<data>\n" +

+                      "  <test id=\"1\">\n" +

+                      "    <column column-name=\"the value\">" + testedValue + "</column>\n" +

+                      "  </test>\n" +

+                      "</data>\n");

+    }

+

+    /**

+     * Tests the reader & writer behavior when a column name is 'base64'.

+     */

+    public void testColumnNameIsBase64() throws Exception

+    {

+        Database model = readModel(

+            "<?xml version='1.0' encoding='UTF-8'?>\n"+

+            "<database xmlns='" + DatabaseIO.DDLUTILS_NAMESPACE + "' name='test'>\n" +

+            "  <table name='test'>\n"+

+            "    <column name='id' type='INTEGER' primaryKey='true' required='true'/>\n"+

+            "    <column name='" + DatabaseIO.BASE64_ATTR_NAME + "' type='VARCHAR' size='50' required='true'/>\n"+

+            "  </table>\n"+

+            "</database>");

+        String testedValue = "Some Text";

+

+        SqlDynaBean bean = (SqlDynaBean)model.createDynaBeanFor(model.getTable(0));

+

+        bean.set("id", new Integer(1));

+        bean.set(DatabaseIO.BASE64_ATTR_NAME, testedValue);

+

+        roundtripTest(model, bean, "UTF-8",

+                      "<?xml version='1.0' encoding='UTF-8'?>\n" +

+                      "<data>\n" +

+                      "  <test id=\"1\">\n" +

+                      "    <column column-name=\"" + DatabaseIO.BASE64_ATTR_NAME + "\">" + testedValue + "</column>\n" +

+                      "  </test>\n" +

+                      "</data>\n");

+    }

 }

diff --git a/src/main/java/org/apache/ddlutils/util/DatabaseTestHelper.java b/src/test/java/org/apache/ddlutils/util/DatabaseTestHelper.java
similarity index 100%
rename from src/main/java/org/apache/ddlutils/util/DatabaseTestHelper.java
rename to src/test/java/org/apache/ddlutils/util/DatabaseTestHelper.java