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.beans.IntrospectionException;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;

import org.apache.commons.betwixt.io.BeanReader;
import org.apache.commons.betwixt.io.BeanWriter;
import org.apache.commons.betwixt.strategy.HyphenatedNameMapper;
import org.apache.ddlutils.DdlUtilsException;
import org.apache.ddlutils.model.Database;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

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

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

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

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

    /**
     * Returns whether the internal dtd that comes with DdlUtils is used.
     * 
     * @return <code>true</code> if parsing uses the internal dtd
     */
    public boolean isUseInternalDtd()
    {
        return _useInternalDtd;
    }

    /**
     * Specifies whether the internal dtd is to be used.
     *
     * @param useInternalDtd Whether to use the internal dtd 
     */
    public void setUseInternalDtd(boolean useInternalDtd)
    {
        _useInternalDtd = useInternalDtd;
    }

    /**
     * Returns the commons-betwixt mapping file as an {@link org.xml.sax.InputSource} object.
     * Per default, this will be classpath resource under the path <code>/mapping.xml</code>.
     *  
     * @return The input source for the mapping
     */
    protected InputSource getBetwixtMapping()
    {
        return new InputSource(getClass().getResourceAsStream("/mapping.xml"));
    }
    
    /**
     * Returns a new bean reader configured to read database models.
     * 
     * @return The reader
     */
    protected BeanReader getReader() throws IntrospectionException, SAXException, IOException
    {
        BeanReader reader = new BeanReader();

        reader.getXMLIntrospector().getConfiguration().setAttributesForPrimitives(true);
        reader.getXMLIntrospector().getConfiguration().setWrapCollectionsInElement(false);
        reader.getXMLIntrospector().getConfiguration().setElementNameMapper(new HyphenatedNameMapper());
        reader.setValidating(isValidateXml());
        if (isUseInternalDtd())
        {
            reader.setEntityResolver(new LocalEntityResolver());
        }
        reader.registerMultiMapping(getBetwixtMapping());

        return reader;
    }

    /**
     * Returns a new bean writer configured to writer database models.
     * 
     * @param output The target output writer
     * @return The writer
     */
    protected BeanWriter getWriter(Writer output) throws DdlUtilsException
    {
        try
        {
            BeanWriter writer = new BeanWriter(output);
    
            writer.getXMLIntrospector().register(getBetwixtMapping());
            writer.getXMLIntrospector().getConfiguration().setAttributesForPrimitives(true);
            writer.getXMLIntrospector().getConfiguration().setWrapCollectionsInElement(false);
            writer.getXMLIntrospector().getConfiguration().setElementNameMapper(new HyphenatedNameMapper());
            writer.getBindingConfiguration().setMapIDs(false);
            writer.enablePrettyPrint();
    
            return writer;
        }
        catch (Exception ex)
        {
            throw new DdlUtilsException(ex);
        }
    }

    /**
     * Reads the database model contained in the specified file.
     * 
     * @param filename The model file name
     * @return The database model
     */
    public Database read(String filename) throws DdlUtilsException
    {
        Database model = null;

        try
        {
            model = (Database)getReader().parse(filename);
        }
        catch (Exception ex)
        {
            throw new DdlUtilsException(ex);
        }
        model.initialize();
        return model;
    }

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

        try
        {
            model = (Database)getReader().parse(file);
        }
        catch (Exception ex)
        {
            throw new DdlUtilsException(ex);
        }
        model.initialize();
        return model;
    }

    /**
     * Reads the database model given by the reader.
     * 
     * @param reader The reader that returns the model XML
     * @return The database model
     */
    public Database read(Reader reader) throws DdlUtilsException
    {
        Database model = null;

        try
        {
            model = (Database)getReader().parse(reader);
        }
        catch (Exception ex)
        {
            throw new DdlUtilsException(ex);
        }
        model.initialize();
        return model;
    }

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

        try
        {
            model = (Database)getReader().parse(source);
        }
        catch (Exception ex)
        {
            throw new DdlUtilsException(ex);
        }
        model.initialize();
        return model;
    }

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

            try
            {
                writer = new BufferedWriter(new FileWriter(filename));
    
                write(model, writer);
                writer.flush();
            }
            finally
            {
                if (writer != null)
                {
                    writer.close();
                }
            }
        }
        catch (Exception ex)
        {
            throw new DdlUtilsException(ex);
        }
    }

    /**
     * Writes the database model to the given output stream. Note that this method
     * does not flush the stream.
     * 
     * @param model  The database model
     * @param output The output stream
     */
    public void write(Database model, OutputStream output) throws DdlUtilsException
    {
        write(model, getWriter(new OutputStreamWriter(output)));
    }

    /**
     * Writes the database model to the given output writer. Note that this method
     * does not flush the writer.
     * 
     * @param model  The database model
     * @param output The output writer
     */
    public void write(Database model, Writer output) throws DdlUtilsException
    {
        write(model, getWriter(output));
    }

    /**
     * Internal method that writes the database model using the given bean writer.
     * 
     * @param model  The database model
     * @param writer The bean writer
     */
    private void write(Database model, BeanWriter writer) throws DdlUtilsException
    {
        try
        {
            writer.writeXmlDeclaration("<?xml version=\"1.0\"?>\n<!DOCTYPE database SYSTEM \"" + LocalEntityResolver.DTD_PREFIX + "\">");
            writer.write(model);
        }
        catch (Exception ex)
        {
            throw new DdlUtilsException(ex);
        }
    }
}
