/*
 * ConstraintTrigger class.
 *
 * Copyright (C) 2007 SATOH Takayuki All Rights Reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */
package ts.util.table;

import java.util.Map;
import java.util.List;
import java.util.LinkedList;

/**
 * テーブル変更時に、制約条件のチェック処理を呼び出すトリガ・クラス。
 * <br>
 * このオブジェクトに登録された{@link ts.util.table.Constraint Constraint}
 * オブジェクトのチェック処理を、{@link #preAppend(Table, java.util.Map)
 * preAppend}, {@link #preUpdate(Table, java.util.Map, java.util.Map)
 * preUpdate}, {@link #preDelete(Table, java.util.Map) preDelete}メソッドの
 * いずれかで実行する。
 * <br>
 * {@link ts.util.table.Constraint Constraint}オブジェクトのチェック処理で
 * 制約違反と判定された場合は、{@link
 * ts.util.table.ConstraintViolationException ConstraintViolationException}
 * が直ちにスローされる。
 * <br>
 * 但しこのオブジェクトを生成する時に、コンストラクタの引数に<tt>false</tt>を
 * 指定した場合は、全ての制約条件のチェックが実行されてから、最初に発生した
 * 例外がスローされる。２番目以降に発生した例外は、{@link
 * ts.util.table.ConstraintViolationException#next()}メソッドで連鎖的に取得する
 * ことができる。
 *
 * @param  <C> テーブルのカラム・キーのタイプ。
 * @param  <V> テーブルのカラム値のタイプ。
 *
 * @author  佐藤隆之.
 * @version $Revision: 1.3 $, $Date: 2011-08-01 15:29:33 $
 */
public class ConstraintTrigger<C,V> extends Trigger<C,V>
{
  /** シリアル・バージョン番号。 */
  static final long serialVersionUID = 3986525460537931705L;

  /** 制約違反があった場合に直ちに例外をスローするかどうかを示すフラグ。 */
  private boolean isImmediate_ = true;

  /**
   * レコードの追加直前にチェックを行う{@link ts.util.table.Constraint
   * Constraint}オブジェクトを格納するリスト。
   */
  private List<Constraint<C,V>> constraintsForAppend_ =
    new LinkedList<Constraint<C,V>>();

  /**
   * レコードの更新直前にチェックを行う{@link ts.util.table.Constraint
   * Constraint}オブジェクトを格納するリスト。
   */
  private List<Constraint<C,V>> constraintsForUpdate_ =
    new LinkedList<Constraint<C,V>>();

  /**
   * レコードの削除直前にチェックを行う{@link ts.util.table.Constraint
   * Constraint}オブジェクトを格納するリスト。
   */
  private List<Constraint<C,V>> constraintsForDelete_ =
    new LinkedList<Constraint<C,V>>();

  /**
   * デフォルトコンストラクタ。
   * <br>
   * 制約条件のチェックで制約違反があった場合は直ちに例外をスローする。
   */
  public ConstraintTrigger()
  {
    this(true);
  }

  /**
   * 制約条件のチェックで制約違反があった場合に、直ちに例外をスローするかどうか
   * を引数にとるコンストラクタ。
   *
   * @param  isImmediate 直ちに例外をスローする場合は<tt>true</tt>。
   */
  public ConstraintTrigger(boolean isImmediate)
  {
    isImmediate_ = isImmediate;
  }

  /**
   * レコードの追加直前に制約違反のチェックを行う{@link
   * ts.util.table.Constraint Constraint}オブジェクトを追加する。
   *
   * @param constraint {@link ts.util.table.Constraint Constraint}オブジェクト。
   * @throws AssertionError 引数がヌルの場合。
   */
  public void addConstraintForAppend(Constraint<C,V> constraint)
  {
    assert (constraint != null) : "@param:constraint is null.";
    constraintsForAppend_.add(constraint);
  }

  /**
   * レコードの更新直前に制約違反のチェックを行う{@link
   * ts.util.table.Constraint Constraint}オブジェクトを追加する。
   *
   * @param constraint {@link ts.util.table.Constraint Constraint}オブジェクト。
   * @throws AssertionError 引数がヌルの場合。
   */
  public void addConstraintForUpdate(Constraint<C,V> constraint)
  {
    assert (constraint != null) : "@param:constraint is null.";
    constraintsForUpdate_.add(constraint);
  }

  /**
   * レコードの削除直前に制約違反のチェックを行う{@link
   * ts.util.table.Constraint Constraint}オブジェクトを追加する。
   *
   * @param constraint {@link ts.util.table.Constraint Constraint}オブジェクト。
   * @throws AssertionError 引数がヌルの場合。
   */
  public void addConstraintForDelete(Constraint<C,V> constraint)
  {
    assert (constraint != null) : "@param:constraint is null.";
    constraintsForDelete_.add(constraint);
  }

  /**
   * レコードの追加の直前に呼び出されるメソッド。
   * <br>
   * レコードの追加直前に制約違反のチェックを行う{@link
   * ts.util.table.Constraint Constraint}オブジェクトのチェック処理を呼び出す。
   * 
   * @param  table テーブル・オブジェクト。
   * @param  record 追加されるレコード・オブジェクト。
   * @throws ConstraintViolationException 制約違反があった場合。
   */
  @Override
  protected void preAppend(Table<C,V> table, Map<C,V> record)
  {
    assert (table != null) : "@param:table is null.";
    assert (record != null) : "@param:record is null.";

    ConstraintViolationException top = null;
    ConstraintViolationException cur = null;

    for (Constraint<C,V> constraint : constraintsForAppend_) {
      try {
        constraint.check(Trigger.Action.Append, table, record);
      }
      catch (ConstraintViolationException e) {
        if (isImmediate_) throw e;

        if (cur == null) { cur = top = e; }
        else { cur.setNext(e); cur = e; }
      }
    }

    if (top != null) throw top;
  }

  /**
   * レコードの更新の直前に呼び出されるメソッド。
   * <br>
   * レコードの更新直前に制約違反のチェックを行う{@link
   * ts.util.table.Constraint Constraint}オブジェクトのチェック処理を呼び出す。
   * 
   * @param  table テーブル・オブジェクト。
   * @param  record 更新されるレコード・オブジェクト。
   * @param  destination 更新するカラム・キーとその値を格納したマップ。
   * @throws ConstraintViolationException 制約違反があった場合。
   */
  @Override
  protected void preUpdate(Table<C,V> table, Map<C,V> record,
    Map<C,V> destination)
  {
    assert (table != null) : "@param:table is null.";
    assert (record != null) : "@param:record is null.";
    assert (destination != null) : "@param:destination is null.";

    ConstraintViolationException top = null;
    ConstraintViolationException cur = null;

    for (Constraint<C,V> constraint : constraintsForUpdate_) {
      try {
        constraint.check(Trigger.Action.Update, table, record);
      }
      catch (ConstraintViolationException e) {
        if (isImmediate_) throw e;

        if (cur == null) { cur = top = e; }
        else { cur.setNext(e); cur = e; }
      }
    }

    if (top != null) throw top;
  }

  /**
   * レコードの削除の直前に呼び出されるメソッド。
   * <br>
   * レコードの削除直前に制約違反のチェックを行う{@link
   * ts.util.table.Constraint Constraint}オブジェクトのチェック処理を呼び出す。
   * 
   * @param  table テーブル・オブジェクト。
   * @param  record 削除されるレコード・オブジェクト。
   * @throws ConstraintViolationException 制約違反があった場合。
   */
  @Override
  protected void preDelete(Table<C,V> table, Map<C,V> record)
  {
    assert (table != null) : "@param:table is null.";
    assert (record != null) : "@param:record is null.";

    ConstraintViolationException top = null;
    ConstraintViolationException cur = null;

    for (Constraint<C,V> constraint : constraintsForDelete_) {
      try {
        constraint.check(Trigger.Action.Delete, table, record);
      }
      catch (ConstraintViolationException e) {
        if (isImmediate_) throw e;

        if (cur == null) { cur = top = e; }
        else { cur.setNext(e); cur = e; }
      }
    }

    if (top != null) throw top;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected final void postAppend(Table<C,V> table, Map<C,V> record)
  {}

  /**
   * {@inheritDoc}
   */
  @Override
  protected final void postUpdate(Table<C,V> table, Map<C,V> record,
    Map<C,V> destination)
  {}

  /**
   * {@inheritDoc}
   */
  @Override
  protected final void postDelete(Table<C,V> table, Map<C,V> record)
  {}
}

