/*
 * Lifetime 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.lifetime;

import java.util.Date;
import java.util.List;
import java.util.LinkedList;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Enumeration;
import java.util.Collections;
import java.lang.reflect.Method;

/**
 * オブジェクトの生存期間を表すクラス。
 * <br>
 * {@link ts.util.lifetime.Life Life}インターフェイスをインプリメントした
 * オブジェクトの生存期間を限る。
 * <br>
 * 例えば、このクラスをスレッドに合わせて派生させた
 * {@link ts.util.lifetime.ThreadLifetime ThreadLifetime}クラスは、指定された
 * スレッドの終了に合わせて、登録された{@link ts.util.lifetime.Life Life}
 * オブジェクトに終了を通知する。
 * <br>
 * 登録された{@link ts.util.lifetime.Life Life}オブジェクトには、登録直後に
 * {@link ts.util.lifetime.Life#wasBorn() wasBorn()}メソッドを呼び出し、終了の
 * 通知には{@link ts.util.lifetime.Life#willDie() willDie()}メソッドを呼び出す。
 * <br>
 * 登録された{@link ts.util.lifetime.Life}オブジェクトのクラスが
 * {@link ts.util.lifetime.EventfulLife}クラスの派生クラスであった場合は、
 * このオブジェクトの{@link ts.util.lifetime.EventfulLife#happens()}メソッドを
 * 指定されたタイミングで呼び出すための仕組みを提供する。
 *
 * @author  佐藤隆之.
 * @version $Revision: 1.1.1.1 $, $Date: 2010-10-16 00:03:47 $
 */
public class Lifetime
{
  /** {@link ts.util.lifetime.Life Life}オブジェクトを格納するリスト。 } */
  private List<Life> lifeLst_ ;

  /** 
   * {@link ts.util.lifetime.EventfulLife EventfulLife}オブジェクトのための
   * タイマー。 
   */
  private Timer lifeTimer_ = new Timer(true);

  /**
   * デフォルトコンストラクタ。
   */
  public Lifetime()
  {
    lifeLst_ = createLifeList();
  }

  /**
   * {@link ts.util.lifetime.Life Life}オブジェクトを格納するリストを作成する。
   *
   * @return {@link ts.util.lifetime.Life Life}オブジェクトを格納するリスト。
   */
  protected List<Life> createLifeList()
  {
    return new LinkedList<Life>();
  }

  /**
   * 指定された名前の{@link ts.util.lifetime.Life Life}オブジェクトのメソッドを
   * 取得する。
   * <br>
   * 但し、対象となるのは、引数なしのメソッドのみである。
   * <br>
   * 指定された名前の引数なしのメソッドが存在しなかった場合は、ヌルを返す。
   *
   * @param  life {@link ts.util.lifetime.Life Life}オブジェクト。
   * @param  methodName メソッド名。
   */
  private Method getLifeMethod(Life life, String methodName)
  {
    try {
      return life.getClass().getMethod(methodName);
    }
    catch (Exception e) {
      return null;
    }
  }

  /**
   * {@link ts.util.lifetime.Life Life}オブジェクトを追加する。
   * <br>
   * 引数の{@link ts.util.lifetime.Life Life}オブジェクトを登録し、
   * {@link ts.util.lifetime.Life#wasBorn() wasBorn()}メソッドを呼び出す。
   *
   * @param  life {@link ts.util.lifetime.Life Life}オブジェクト。
   * @throws AssertionError 引数がヌルの場合（デバッグ・モードのみ）。
   */
  public synchronized void add(Life life)
  {
    assert (life != null) : "@param:life is null.";

    lifeLst_.add(life);

    try {
      life.wasBorn();
    }
    catch (Exception e) {
      catchException(e, life, getLifeMethod(life, "wasBorn"));
    }

    if (life instanceof PeriodicEventfulLife) {
      setupPeriodicEvent(PeriodicEventfulLife.class.cast(life));
    }
    else if (life instanceof ScheduledEventfulLife) {
      setupScheduledEvent(ScheduledEventfulLife.class.cast(life));
    }
  }

  /**
   * 引数の{@link ts.util.lifetime.ScheduledEventfulLife ScheduledEventfulLife}
   * オブジェクトに対して、日時指定のイベントを設定する。
   *
   * @param  life {@link ts.util.lifetime.ScheduledEventfulLife
   *           ScheduledEventfulLife}オブジェクト。
   */
  protected void setupScheduledEvent(final ScheduledEventfulLife life)
  {
    class ScheduleThread extends Thread {
      public void run() {
        try {
          Date nextTime = null;
          try {
            nextTime = life.getNextTime();
          }
          catch (Exception e) {
            catchException(e, life, getLifeMethod(life, "getNextTime"));
            throw e;
          }

          if (nextTime.getTime() > System.currentTimeMillis()) {
            TimerTask task = new TimerTask() {
              public void run() {
                try {
                  life.happens();
                }
                catch (Exception e) {
                  catchException(e, life, getLifeMethod(life, "happens"));
                }
                new ScheduleThread().start();
              }
            };
            lifeTimer_.schedule(task, nextTime);
          }
        }
        catch (Exception e) {
          new ScheduleThread().start();
        }
      }
    }

    new ScheduleThread().start();
  }

  /**
   * 引数の{@link ts.util.lifetime.PeriodicEventfulLife PeriodicEventfulLife}
   * オブジェクトに対して、周期起動のイベントを設定する。
   *
   * @param  life {@link ts.util.lifetime.PeriodicEventfulLife
   *           PeriodicEventfulLife}オブジェクト。
   */
  protected void setupPeriodicEvent(final PeriodicEventfulLife life)
  {
    long interval = life.getInterval();
    if (interval > 0) {
      try {
        TimerTask task = new TimerTask() {
          public void run() {
            try {
              life.happens();
            }
            catch (Exception e) {
              catchException(e, life, getLifeMethod(life, "happens"));
            }
          }
        };
        lifeTimer_.schedule(task, 0L, interval);
      }
      catch (RuntimeException e) {
        setupPeriodicEvent(life);
      }
    }
  }

  /**
   * {@link ts.util.lifetime.Life Life}オブジェクトの生存期間を終了させる。
   * <br>
   * 登録されている{@link ts.util.lifetime.Life Life}オブジェクトをこの
   * オブジェクトから削除して、{@link ts.util.lifetime.Life#willDie() willDie()}
   * メソッドを呼び出す。
   */
  protected synchronized void kill()
  {
    lifeTimer_.cancel();
    lifeTimer_ = new Timer(true);

    for (Life life : lifeLst_) {
      try {
        life.willDie();
      }
      catch (Exception e) {
        catchException(e, life, getLifeMethod(life, "willDie"));
      }
    }

    while (! lifeLst_.isEmpty()) {
      lifeLst_.remove(0);
    }
  }

  /**
   * {@link ts.util.lifetime.Life Life}オブジェクトのメソッドを実行して例外が
   * 発生した際に、その例外を受け取る。
   * <br>
   * 例外発生時に何らかの処理を実行したい場合は、このメソッドをオーバーライド
   * する。
   * <br>
   * このクラスでは、何もしない。
   *
   * @param  exc  発生した例外。
   * @param  life 例外をスローした{@link ts.util.lifetime.Life Life}
   *           オブジェクト。
   * @param  method 例外をスローした{@link ts.util.lifetime.Life Life}
   *           オブジェクトのメソッド。
   */
  protected void catchException(Exception exc, Life life, Method method)
  {}

  /**
   * このオブジェクトに格納されている{@link ts.util.lifetime.Life Life}
   * オブジェクトを列挙する。
   *
   * @return {@link ts.util.lifetime.Life Life}オブジェクトの列挙オブジェクト。
   */
  protected Enumeration<Life> enumeration()
  {
    return Collections.enumeration(lifeLst_);
  }

  /**
   * このオブジェクトがガーベージ・コレクションによって破棄される際に呼び出さ
   * れるメソッドである。
   * <br>
   * 破棄される前に、{@link #kill()}メソッドを呼び出している。
   *
   * @throws Throwable このメソッド内で例外やエラーが発生した場合。
   */
  protected void finalize() throws Throwable
  {
    kill();
    super.finalize();
  }
}

