// Copyright (c) 2008, NTT DATA Corporation.
//
// Licensed 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.

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Xml;
using TERASOLUNA.Fw.Client.Configuration.Conversion;
using TERASOLUNA.Fw.Common;
using TERASOLUNA.Fw.Common.Logging;

namespace TERASOLUNA.Fw.Client.Conversion
{
    /// <summary>
    /// データセット変換機能を提供するクラスです。
    /// </summary>
    /// <remarks>
    /// <para>データセット変換設定ファイルに従い、2つの <see cref="DataSet"/> 間で変換を行います。</para>
    /// <para>リレーションシップが設定された <see cref="DataTable"/> に対しては、
    /// 正常なデータ変換を保障していないため、データセット変換機能を利用しないこと。</para>
    /// </remarks>
    public class DataSetConverter : IConverter
    {
        /// <summary>
        /// <see cref="ILog"/> 実装クラスのインスタンスです。
        /// </summary>
        /// <remarks>
        /// ログ出力に利用します。
        /// </remarks>
        private static ILog _log = LogFactory.GetLogger(typeof(DataSetConverter));

        /// <summary>
        /// <see cref="DataSetConverter"/> クラスの新しいインスタンスを初期化します。
        /// </summary>
        /// <remarks>
        /// デフォルトコンストラクタです。
        /// </remarks>
        public DataSetConverter()
        {
        }

        /// <summary>
        /// データセット変換設定ファイルに従い、 <paramref name="srcData"/> の <see cref="DataSet"/> を
        /// <paramref name="destData"/> の <see cref="DataSet"/> に変換します。
        /// </summary>
        /// <remarks>
        /// <para>指定した <paramref name="id"/> 属性の値を持つ convert 要素内の param 子要素から
        /// column 要素を取得し、<paramref name="srcData"/> から <paramref name="destData"/> に変換します。</para>
        /// <para><paramref name="destData"/> に含まれる <see cref="DataTable"/> は、
        /// 1行も <see cref="DataRow"/>を含まないことが前提であり、 <see cref="DataRow"/> を含んでいる場合は、
        /// 例外をスローします。</para>
        /// <para>
        /// <paramref name="id"/> で指定された convert 要素が定義されていない場合は、変換処理を行いません。
        /// </para>
        /// <para>
        /// column 要素が定義されていない場合は、変換処理を行いません。
        /// </para>
        /// <para>
        /// column 要素の dest 属性にカラム名が未設定の場合は、src 属性と同じテーブル名、カラム名を使用して
        /// <paramref name="destData"/> に変換します。
        /// </para>
        /// </remarks>
        /// <param name="id">キーとなる convert 要素の id 属性の値。</param>
        /// <param name="srcData">変換元 <see cref="DataSet"/> 。</param>
        /// <param name="destData">変換先 <see cref="DataSet"/> 。</param>
        /// <exception cref="ArgumentNullException">
        /// 以下のような場合に例外をスローします。
        /// <list type="bullet">
        /// <item>
        /// <description>
        /// <paramref name="id"/> が null 参照です。
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// <paramref name="srcData"/> が null 参照です。
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// <paramref name="destData"/> が null 参照です。
        /// </description>
        /// </item>
        /// </list>
        /// </exception>
        /// <exception cref="ArgumentException">
        /// 以下のような場合に例外をスローします。
        /// <list type="bullet">
        /// <item>
        /// <description>
        /// <paramref name="id"/> が空の文字列です。
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// <paramref name="destData"/> に含まれる <see cref="DataTable"/> に <see cref="DataRow"/> が存在します。
        /// </description>
        /// </item>
        /// </list>
        /// </exception>
        /// <exception cref="ConfigurationErrorsException">
        /// アプリケーション構成ファイル、または、データセット変換設定ファイルの内容が不正のため、
        /// <see cref="ConversionConfiguration"/> 設定情報を取得できません。
        /// </exception>
        /// <exception cref="TerasolunaException">
        /// 以下のような場合に例外をスローします。
        /// <list type="bullet">
        /// <item>
        /// <description>
        /// データセット変換設定ファイルに、<paramref name="id"/> に対応するデータセット変換情報がない場合。
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// データセット変換設定ファイルに定義されたテーブル名が存在しません。
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// データセット変換設定ファイルに定義されたカラム名が存在しません。
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// データセット変換設定ファイルの変換元・変換先の形式が "テーブル名.カラム名" ではありません。
        /// </description>
        /// </item>
        /// </list>
        /// </exception>       
        public virtual void Convert(string id, DataSet srcData, DataSet destData)
        {
            if (id == null)
            {
                ArgumentNullException exception = new ArgumentNullException("id");
                if (_log.IsErrorEnabled)
                {
                    _log.Error(string.Format(Client.Properties.Resources.E_NULL_ARGUMENT, "id"), exception);
                }
                throw exception;
            }
            else if (id.Length == 0)
            {
                string message = string.Format(Properties.Resources.E_EMPTY_STRING, "id");
                ArgumentException exception = new ArgumentException(message);
                if (_log.IsErrorEnabled)
                {
                    _log.Error(message, exception);
                }
                throw exception;
            }

            if (srcData == null)
            {
                ArgumentNullException exception = new ArgumentNullException("srcData");
                if (_log.IsErrorEnabled)
                {
                    _log.Error(string.Format(Client.Properties.Resources.E_NULL_ARGUMENT, "srcData"), exception);
                }
                throw exception;
            }

            if (destData == null)
            {
                ArgumentNullException exception = new ArgumentNullException("destData");
                if (_log.IsErrorEnabled)
                {
                    _log.Error(string.Format(Client.Properties.Resources.E_NULL_ARGUMENT, "destData"), exception);
                }
                throw exception;
            }

            // destDataが空のDataSetであることを確認
            foreach (DataTable table in destData.Tables)
            {
                if (table.Rows.Count > 0)
                {
                    string message = string.Format(Properties.Resources.E_CONVERSION_INVALID_DEST_DATA, id, table.TableName);
                    ArgumentException exception = new ArgumentException(message);
                    if (_log.IsErrorEnabled)
                    {
                        _log.Error(message, exception);
                    }
                    throw exception;
                }
            }

            // 設定情報の取得
            ConvertElement convertElement = CreateConvertElement(id);
            ParamElement element = convertElement.Param;

            // データセット変換
            if (_log.IsDebugEnabled)
            {
                _log.Debug(string.Format(Properties.Resources.D_CONVERT_DATA_SET,
                    "Convert", id, srcData.GetType().FullName, destData.GetType().FullName));
            }
            DoConvert(id, element, srcData, destData);

            // コミット処理
            AcceptChangesInDestData(destData);
        }

        /// <summary>
        /// データセット変換設定ファイルに従い、<paramref name="destData"/> の <see cref="DataSet"/> を
        /// <paramref name="srcData"/> の <see cref="DataSet"/> に変換します。
        /// </summary>
        /// <remarks>
        /// 指定した <paramref name="id"/> 属性の値を持つ convert 要素内の result 子要素から
        /// column 要素を取得し、<paramref name="destData"/> から <paramref name="srcData"/> に変換します。
        /// <para>
        /// 変換前に、 crear-view 要素で指定された変換先の <see cref="DataTable"/> をクリアします。
        /// 指定されていない <see cref="DataTable"/> はクリアされません。
        /// </para>
        /// <para>
        /// <paramref name="id"/> で指定された convert 要素が定義されていない場合は、変換処理を行いません。
        /// </para>
        /// <para>column 要素が定義されていない場合は、変換処理を行いません。</para>
        /// <para>
        /// column 要素の dest 属性にカラム名が未設定の場合は、src 属性と同じテーブル名、カラム名を使用して
        /// <paramref name="srcData"/> に変換します。
        /// </para>
        /// </remarks>
        /// <param name="id">キーとなる convert 要素の id 属性の値。</param>
        /// <param name="srcData">変換先 <see cref="DataSet"/> 。</param>
        /// <param name="destData">変換元 <see cref="DataSet"/> 。</param>
        /// <exception cref="ArgumentNullException">
        /// 以下のような場合に例外をスローします。
        /// <list type="bullet">
        /// <item>
        /// <description>
        /// <paramref name="id"/> が null 参照です。
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// <paramref name="srcData"/> が null 参照です。
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// <paramref name="destData"/> が null 参照です。
        /// </description>
        /// </item>
        /// </list>
        /// </exception>
        /// <exception cref="ArgumentException"><paramref name="id"/> が空の文字列です。</exception>
        /// <exception cref="ConfigurationErrorsException">
        /// アプリケーション構成ファイル、または、データセット変換設定ファイルの内容が不正のため、
        /// <see cref="ConversionConfiguration"/> 設定情報を取得できません。
        /// </exception>
        /// <exception cref="TerasolunaException">
        /// 以下のような場合に例外をスローします。
        /// <list type="bullet">
        /// <item>
        /// <description>
        /// データセット変換設定ファイルに、 <paramref name="id"/> に対応するデータセット変換情報がない場合。
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// データセット変換設定ファイルに定義されたテーブル名が存在しません。
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// データセット変換設定ファイルに定義されたカラム名が存在しません。
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// データセット変換設定ファイルの clear-view 要素で定義されたテーブル名が存在しません。
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// データセット変換設定ファイルの変換元・変換先の形式が "テーブル名.カラム名" ではありません。
        /// </description>
        /// </item>
        /// </list>
        /// </exception>       
        /// <exception cref="ConversionException">
        /// 変換元 <see cref="DataSet"/> の <see cref="DataTable"/> と、
        /// 変換先 <see cref="DataSet"/> の <see cref="DataTable"/> の間で
        /// 行数が一致しません。
        /// </exception>
        public virtual void Reverse(string id, DataSet srcData, DataSet destData)
        {
            if (id == null)
            {
                ArgumentNullException exception = new ArgumentNullException("id");
                if (_log.IsErrorEnabled)
                {
                    _log.Error(string.Format(Client.Properties.Resources.E_NULL_ARGUMENT, "id"), exception);
                }
                throw exception;
            }
            else if (id.Length == 0)
            {
                string message = string.Format(Properties.Resources.E_EMPTY_STRING, "id");
                ArgumentException exception = new ArgumentException(message);
                if (_log.IsErrorEnabled)
                {
                    _log.Error(message, exception);
                }
                throw exception;
            }
            if (srcData == null)
            {
                ArgumentNullException exception = new ArgumentNullException("srcData");
                if (_log.IsErrorEnabled)
                {
                    _log.Error(string.Format(Client.Properties.Resources.E_NULL_ARGUMENT, "srcData"), exception);
                }
                throw exception;
            }
            if (destData == null)
            {
                ArgumentNullException exception = new ArgumentNullException("destData");
                if (_log.IsErrorEnabled)
                {
                    _log.Error(string.Format(Client.Properties.Resources.E_NULL_ARGUMENT, "destData"), exception);
                }
                throw exception;
            }

            // 設定情報の取得
            ConvertElement convertElement = CreateConvertElement(id);
            ResultElement element = convertElement.Result;

            // clear-view 要素で指定されたテーブルを全行削除
            ClearDataTable(element.ClearViews, srcData);

            // データセット変換
            if (_log.IsDebugEnabled)
            {
                _log.Debug(string.Format(Properties.Resources.D_CONVERT_DATA_SET,
                    "Reverse", id, destData.GetType().FullName, srcData.GetType().FullName));
            }
            DoReverse(id, element, srcData, destData);

            // コミット処理
            AcceptChangesInSrcData(srcData);
        }

        /// <summary>
        /// <see cref="DataTable"/> からすべてのデータを消去します。
        /// </summary>
        /// <remarks>
        /// <see cref="ClearViewCollection"/> に格納された <see cref="ClearViewElement"/> から、
        /// table 属性の値を取得し、指定されたテーブルのデータをすべて消去します。
        /// <para>
        /// ※<see cref="Reverse"/> 実行時に使用します。
        /// </para>
        /// </remarks>
        /// <param name="clearTables">消去するテーブル名を含む <see cref="ClearViewCollection"/> 。</param>
        /// <param name="dataSet">消去対象の <see cref="DataTable"/> を含む <see cref="DataSet"/> 。</param>
        /// <exception cref="TerasolunaException">
        /// データセット変換設定ファイルの clear-view 要素で定義されたテーブル名が存在しません。</exception>
        protected virtual void ClearDataTable(ClearViewCollection clearTables, DataSet dataSet)
        {
            DataTable table;
            foreach (ClearViewElement clearTable in clearTables)
            {
                table = dataSet.Tables[clearTable.TableName];

                if (table == null)
                {
                    string message = string.Format(
                        Properties.Resources.E_CONVERSION_CLEAR_TABLE_NOT_FOUND, clearTable.TableName, dataSet.GetType().FullName);
                    TerasolunaException exception = new TerasolunaException(message);
                    if (_log.IsErrorEnabled)
                    {
                        _log.Error(message, exception);
                    }
                    throw exception;
                }

                ClearDataTable(table);
            }
        }

        /// <summary>
        /// 指定した <see cref="DataTable"/> からすべてのデータを消去します。
        /// </summary>
        /// <remarks>
        /// <see cref="DataSet"/> が <see cref="XmlDataDocument"/> にバインドされている場合、
        /// <see cref="DataTable.Clear">DataTable.Clear()</see> を呼び出すと <see cref="NotSupportedException"/> が発生します。
        /// これを回避するために、テーブルを走査し、1 行ずつ削除します。
        /// <para>
        /// <paramref name="dataTable"/> が null の場合、何もしません。
        /// </para>
        /// </remarks>
        /// <param name="dataTable">データを消去したい <see cref="DataTable"/> 。</param>
        protected virtual void ClearDataTable(DataTable dataTable)
        {
            int rowCount = dataTable.Rows.Count;
            for (int i = rowCount - 1; 0 <= i; i--)
            {
                // 最終行から削除
                dataTable.Rows[i].Delete();
            }
        }

        /// <summary>
        /// "." で区切られた文字列（テーブル名.カラム名）から、カラム名を取得します。
        /// </summary>
        /// <remarks>
        /// 文字列が "." で区切られていない場合は、<paramref name="value"/> を返します。
        /// </remarks>
        /// <param name="value">"." で区切られた文字列（テーブル名.カラム名）。</param>
        /// <returns>"." 以降の文字列。</returns>
        /// <exception cref="TerasolunaException"><paramref name="value"/> の形式が "テーブル名.カラム名" ではありません。</exception>
        protected virtual string GetColumnName(string value)
        {
            string[] str = value.Split('.');

            if (str.Length != 2 || string.IsNullOrEmpty(str[0]) || string.IsNullOrEmpty(str[1]))
            {
                string message = string.Format(Properties.Resources.E_CONVERSION_INVALID_EXPRESSION, value);
                TerasolunaException exception = new TerasolunaException(message);
                if (_log.IsErrorEnabled)
                {
                    _log.Error(message, exception);
                }
                throw exception;
            }
            return str[1];
        }

        /// <summary>
        /// "." で区切られた文字列（テーブル名.カラム名）から、テーブル名を取得します。
        /// </summary>
        /// <remarks>
        /// 文字列が "." で区切られていない場合は、<paramref name="value"/> を返します。
        /// </remarks>
        /// <param name="value">"." で区切られた文字列（テーブル名.カラム名）。</param>
        /// <returns>"." 以前の文字列。</returns>
        /// <exception cref="TerasolunaException"><paramref name="value"/> の形式が "テーブル名.カラム名" ではありません。</exception>
        protected virtual string GetTableName(string value)
        {
            string[] str = value.Split('.');

            if (str.Length != 2 || string.IsNullOrEmpty(str[0]) || string.IsNullOrEmpty(str[1]))
            {
                string message = string.Format(Properties.Resources.E_CONVERSION_INVALID_EXPRESSION, value);
                TerasolunaException exception = new TerasolunaException(message);
                if (_log.IsErrorEnabled)
                {
                    _log.Error(message, exception);
                }
                throw exception;
            }

            return str[0];
        }

        /// <summary>
        /// <paramref name="destData"/> の変更をコミットします。
        /// </summary>
        /// <param name="destData">変換先のビジネスロジック入力 <see cref="DataSet"/> 。</param>
        protected virtual void AcceptChangesInDestData(DataSet destData)
        {
            destData.AcceptChanges();
        }

        /// <summary>
        /// <paramref name="srcData"/> の変更をコミットします。
        /// </summary>
        /// <param name="srcData">変換先の画面 <see cref="DataSet"/> 。</param>
        protected virtual void AcceptChangesInSrcData(DataSet srcData)
        {
            srcData.AcceptChanges();
        }

        /// <summary>
        /// <see cref="ConvertElement"/> を作成します。
        /// </summary>
        /// <remarks>
        /// 作成できない場合は、<see cref="TerasolunaException"/> をスローします。
        /// null を返却することはありません。
        /// </remarks>
        /// <param name="id">作成する <see cref="ConvertElement"/> の識別子</param>
        /// <returns><see cref="ConvertElement"/> のインスタンス。</returns>
        /// <exception cref="TerasolunaException">
        /// データセット変換設定ファイルに <paramref name="id"/> に対するデータセット変換情報が設定されていません。
        /// </exception>
        protected virtual ConvertElement CreateConvertElement(string id)
        {
            ConvertElement convertElement = ConversionConfiguration.GetConvert(id);
            if (convertElement == null)
            {   // id に対する convert 要素がない場合
                string message = string.Format(Properties.Resources.E_CONVERSION_TYPE_NOT_FOUND, id);
                TerasolunaException exception = new TerasolunaException(message);
                if (_log.IsErrorEnabled)
                {
                    _log.Error(message, exception);
                }
                throw exception;
            }

            return convertElement;
        }

        /// <summary>
        /// <paramref name="srcData"/> から <paramref name="destData"/> へデータ変換します。
        /// </summary>
        /// <param name="element">データセット変換設定情報インスタンス</param>
        /// <param name="id">キーとなる convert 要素の id 属性の値。</param>
        /// <param name="srcData">変換元 <see cref="DataSet"/> 。</param>
        /// <param name="destData">変換先 <see cref="DataSet"/> 。</param>
        /// <exception cref="TerasolunaException">
        /// 以下のような場合に例外をスローします。
        /// <list type="bullet">
        /// <item>
        /// <description>
        /// データセット変換設定ファイルに、<paramref name="id"/> に対応するデータセット変換情報がない場合。
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// データセット変換設定ファイルに定義されたテーブル名が存在しません。
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// データセット変換設定ファイルに定義されたカラム名が存在しません。
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// データセット変換設定ファイルの変換元・変換先の形式が "テーブル名.カラム名" ではありません。
        /// </description>
        /// </item>
        /// </list>
        /// </exception>
        protected virtual void DoConvert(string id, ParamElement element, DataSet srcData, DataSet destData)
        {
            // srcData → destData
            // column 要素ごとに変換（column 要素がない場合、何もしない）
            foreach (ColumnElement param in element.Columns)
            {
                // srcData の DataSet のカラム名
                string srcColName = GetColumnName(param.SrcName);
                // srcData の DataSet のテーブル名
                string srcTableName = GetTableName(param.SrcName);

                // destData の DataSet で使用するカラム名
                string destColName = null;
                // destData の DataSet で使用するテーブル名
                string destTableName = null;

                if (param.DestName == null)
                {   // destData の DataSet のカラム名が未設定の場合
                    destColName = srcColName;
                    destTableName = srcTableName;
                }
                else
                {
                    destColName = this.GetColumnName(param.DestName);
                    destTableName = this.GetTableName(param.DestName);
                }

                DataTable srcTable = srcData.Tables[srcTableName];
                DataTable destTable = destData.Tables[destTableName];

                if (srcTable == null)
                {   // 該当するテーブル名がない
                    string message = string.Format(
                        Properties.Resources.E_CONVERSION_TABLE_NOT_FOUND, id, srcTableName, srcData.GetType().FullName);
                    TerasolunaException exception = new TerasolunaException(message);
                    if (_log.IsErrorEnabled)
                    {
                        _log.Error(message, exception);
                    }
                    throw exception;
                }
                if (!srcTable.Columns.Contains(srcColName))
                {   // 該当するカラム名がない
                    string message = string.Format(
                        Properties.Resources.E_CONVERSION_COLUMN_NOT_FOUND, id, srcColName, srcData.GetType().FullName, srcTableName);
                    TerasolunaException exception = new TerasolunaException(message);
                    if (_log.IsErrorEnabled)
                    {
                        _log.Error(message, exception);
                    }
                    throw exception;
                }

                if (destTable == null)
                {   // 該当するテーブル名がない
                    string message = string.Format(
                        Properties.Resources.E_CONVERSION_TABLE_NOT_FOUND, id, destTableName, destData.GetType().FullName);
                    TerasolunaException exception = new TerasolunaException(message);
                    if (_log.IsErrorEnabled)
                    {
                        _log.Error(message, exception);
                    }
                    throw exception;
                }
                if (!destTable.Columns.Contains(destColName))
                {   // 該当するカラム名がない
                    string message = string.Format(
                        Properties.Resources.E_CONVERSION_COLUMN_NOT_FOUND, id, destColName, destData.GetType().FullName, destTable);
                    TerasolunaException exception = new TerasolunaException(message);
                    if (_log.IsErrorEnabled)
                    {
                        _log.Error(message, exception);
                    }
                    throw exception;
                }

                int rowCount = srcTable.Rows.Count;
                for (int i = 0; i < rowCount; i++)
                {
                    if (destTable.Rows.Count < rowCount)
                    {
                        destTable.Rows.Add(destTable.NewRow());
                    }

                    destTable.Rows[i][destColName] = srcTable.Rows[i][srcColName];
                }
            }
        }

        /// <summary>
        /// <paramref name="destData"/> から <paramref name="srcData"/> へデータ変換します。
        /// </summary>
        /// <param name="element">データセット変換設定情報インスタンス</param>
        /// <param name="id">キーとなる convert 要素の id 属性の値。</param>
        /// <param name="srcData">変換先 <see cref="DataSet"/> 。</param>
        /// <param name="destData">変換元 <see cref="DataSet"/> 。</param>
        /// <exception cref="TerasolunaException">
        /// 以下のような場合に例外をスローします。
        /// <list type="bullet">
        /// <item>
        /// <description>
        /// データセット変換設定ファイルに、 <paramref name="id"/> に対応するデータセット変換情報がない場合。
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// データセット変換設定ファイルに定義されたテーブル名が存在しません。
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// データセット変換設定ファイルに定義されたカラム名が存在しません。
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// データセット変換設定ファイルの clear-view 要素で定義されたテーブル名が存在しません。
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// データセット変換設定ファイルの変換元・変換先の形式が "テーブル名.カラム名" ではありません。
        /// </description>
        /// </item>
        /// </list>
        /// </exception>       
        /// <exception cref="ConversionException">
        /// 変換元 <see cref="DataSet"/> の <see cref="DataTable"/> と、
        /// 変換先 <see cref="DataSet"/> の <see cref="DataTable"/> の間で
        /// 行数が一致しません。
        /// </exception>
        protected virtual void DoReverse(string id, ResultElement element, DataSet srcData, DataSet destData)
        {
            // 各DataTableの削除行数を記憶するIDictionaryを利用
            IDictionary<string, int> deletedRowCountMap = new Dictionary<string, int>();

            // destData → srcData
            // column 要素ごとに変換
            foreach (ColumnElement param in element.Columns)
            {
                // srcData のカラム名
                string srcColName = this.GetColumnName(param.SrcName);
                // srcData のテーブル名
                string srcTableName = this.GetTableName(param.SrcName);

                // destData で使用するカラム名
                string destColName = null;
                // destData で使用するテーブル名
                string destTableName = null;

                if (param.DestName == null)
                {   // destData のカラム名が未設定の場合
                    destColName = srcColName;
                    destTableName = srcTableName;
                }
                else
                {
                    destColName = GetColumnName(param.DestName);
                    destTableName = GetTableName(param.DestName);
                }

                DataTable srcTable = srcData.Tables[srcTableName];
                DataTable destTable = destData.Tables[destTableName];

                if (srcTable == null)
                {   // 該当するテーブル名がない
                    string message = string.Format(
                        Properties.Resources.E_CONVERSION_TABLE_NOT_FOUND, id, srcTableName, srcData.GetType().FullName);
                    TerasolunaException exception = new TerasolunaException(message);
                    if (_log.IsErrorEnabled)
                    {
                        _log.Error(message, exception);
                    }
                    throw exception;
                }
                if (!srcTable.Columns.Contains(srcColName))
                {   // 該当するカラム名がない
                    string message = string.Format(
                        Properties.Resources.E_CONVERSION_COLUMN_NOT_FOUND, id, srcColName, srcData.GetType().FullName, srcTableName);
                    TerasolunaException exception = new TerasolunaException(message);
                    if (_log.IsErrorEnabled)
                    {
                        _log.Error(message, exception);
                    }
                    throw exception;
                }

                if (destTable == null)
                {   // 該当するテーブル名がない
                    string message = string.Format(
                        Properties.Resources.E_CONVERSION_TABLE_NOT_FOUND, id, destTableName, destData.GetType().FullName);
                    TerasolunaException exception = new TerasolunaException(message);
                    if (_log.IsErrorEnabled)
                    {
                        _log.Error(message, exception);
                    }
                    throw exception;
                }
                if (!destTable.Columns.Contains(destColName))
                {   // 該当するカラム名がない
                    string message = string.Format(
                        Properties.Resources.E_CONVERSION_COLUMN_NOT_FOUND, id, destColName, destData.GetType().FullName, destTable);
                    TerasolunaException exception = new TerasolunaException(message);
                    if (_log.IsErrorEnabled)
                    {
                        _log.Error(message, exception);
                    }
                    throw exception;
                }

                // srcData がクリアされたかどうかで処理を分岐させる
                if (element.ClearViews[srcTableName] == null)
                {
                    // srcData のテーブルに上書きする場合。
                    // 上書きの場合は、destDataにおける変換先のテーブルと
                    // srcData におけるテーブルが同じ行数でなければならない。
                    // 異なっていたら例外をスローする。
                    int srcRowCount = srcTable.Rows.Count;
                    if (srcRowCount != destTable.Rows.Count)
                    {
                        string message = string.Format(
                            Properties.Resources.E_CONVERSION_NOT_SAME_ROW_COUNT, id, srcTableName, destTableName);
                        ConversionException exception = new ConversionException(message);
                        if (_log.IsErrorEnabled)
                        {
                            _log.Error(message, exception);
                        }
                        throw exception;
                    }

                    for (int i = 0; i < srcRowCount; i++)
                    {
                        srcTable.Rows[i][srcColName] = destTable.Rows[i][destColName];
                    }
                }
                else
                {
                    // クリアした srcData のテーブルに変換する場合。
                    // ステータスが Delete の行には代入できないため、
                    // 後ろに行を追加し destData の値を反映させる。
                    int deletedRowCount = 0;
                    if (deletedRowCountMap.ContainsKey(srcTableName))
                    {
                        deletedRowCount = deletedRowCountMap[srcTableName];
                    }
                    else
                    {
                        DataTable deletedRowTable = srcTable.GetChanges(DataRowState.Deleted);
                        if (deletedRowTable == null)
                        {
                            deletedRowCount = 0;
                        }
                        else
                        {
                            deletedRowCount = deletedRowTable.Rows.Count;
                        }
                        deletedRowCountMap.Add(srcTableName, deletedRowCount);
                    }

                    int destRowCount = destTable.Rows.Count;
                    for (int i = 0; i < destRowCount; i++)
                    {
                        if (srcTable.Rows.Count - deletedRowCount < destRowCount)
                        {
                            srcTable.Rows.Add(srcTable.NewRow());
                        }

                        srcTable.Rows[i + deletedRowCount][srcColName] = destTable.Rows[i][destColName];
                    }
                }
            }
        }
    }
}
