/*
 * LOGICAL-PARADOX.ORG
 * Copyright (C)2005 satoshi akabane(akabane@logical-paradox.org)
 * $Id: DbFile.java,v 1.3 2005/09/17 17:35:49 rampil Exp $
 */
package org.logical_paradox.vsan.node.dbr;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.logical_paradox.common.io.CommonRandomAccessFileDelegator;
import org.logical_paradox.common.io.FileUtils;
import org.logical_paradox.common.io.RandomAccessFileDelegator;
import org.logical_paradox.common.util.ByteStreamConverter;
import org.logical_paradox.vsan.dac.DacConstant;

/**
 * 特定のレコード範囲を持つデータファイルを抽象化して取り扱うオブジェクト
 * データファイルへの入出力は，このクラスを介して間接的に実施する．
 * @author satoshi akabane@logical-paradox.org
 * @version $Revision: 1.3 $
 */
public class DbFile extends RecordSet implements Comparable {
	/** ロガー */
	private static final transient Log log = LogFactory.getLog(DbFile.class);

	/** ランダムアクセスファイルを実現するデリゲーター */
	private transient RandomAccessFileDelegator file;
	/** 現在のシークポインタがポイントしているレコード番号 */
	private transient long currentRecordNo = 0;

	/**
	 * コンストラクタ
	 * @param recordBegin_ レコード範囲(開始)
	 * @param recordEnd_ レコード範囲(終了)
	 */
	public DbFile(long recordBegin_, long recordEnd_, int recordSize_) {
		super(recordBegin_, recordEnd_, recordSize_);
		currentRecordNo = recordBegin_;
	}
	/**
	 * データファイルを開く
	 * @param dir データファイルの配置位置
	 * @throws IOException データファイルが開けなかった
	 */
	public void open(String dir) throws IOException {
		// 既存のものがあれば，念のため一度クローズしておく
		close();
		file = createPhysicalFileDelegator(dir + File.separator + getFilename());
		log.trace("物理ファイルをオープンしました");
	}
	/**
	 * データファイルを閉じる
	 * @throws IOException データファイルのクローズに失敗
	 */
	public void close() throws IOException {
		if(file != null) {
			file.close();
			log.trace("物理ファイルをクローズしました");
		}
	}
	/**
	 * データファイルを初期化する
	 * データファイルは必ず閉じられた状態で呼び出さなければならない
	 * @param dir ディレクトリ名
	 * @throws IOException 初期化処理が失敗した
	 */
	public void init(String dir) throws IOException {
		log.info("データファイルを作成しています");
		FileUtils.fill(dir + File.separator + getFilename(), (int)(getRecordEnd() - getRecordBegin()), 0);
		log.info("データファイルの作成が完了しました");
	}
	/**
	 * 物理ファイルに対するデリゲーターを生成して返す
	 * サブクラスは，このメソッドをオーバーライドすることで仮想ファイルへのデリゲーターを<br>
	 * 返すこともできる
	 * @param filename ファイル名
	 * @return 生成されたデリゲーター
	 * @throws FileNotFoundException 指定された物理ファイルが存在しなかった
	 */
	protected RandomAccessFileDelegator createPhysicalFileDelegator(String filename) throws FileNotFoundException {
		return new CommonRandomAccessFileDelegator(filename, "rw");
	}
	/**
	 * このデータファイルの文字列表現を返す
	 * @return 文字列表現
	 */
	public String toString() {
		return getFilename();
	}
	/**
	 * ファイル名を返す
	 * @return ファイル名
	 */
	public String getFilename() {
		return super.toString() + DacConstant.DBF_EXT;
	}
	/**
	 * データファイルに対して、指定されたレコードを書き込む
	 * なるべく速く書き込むには、レコード番号が昇順でソートされている必要がある
	 * @param records レコードのリスト(中身はDbRecordであること)
	 */
	public void store(List records) throws IOException {
		synchronized(file) {
			// 指定されたレコードは，基本的には連続領域だとの前提で
			// 書き込みロックを取得したら全て書き込む
			for(Iterator it = records.iterator(); it.hasNext();) {
				DbRecord record = (DbRecord)it.next();

				long recordNo = record.getRecordNo();
				if(recordNo != currentRecordNo) {
					// シークポインタを移動する(連続領域でない場合のみ)
					long seekp = (recordNo - getRecordBegin()) * (getRecordSize() +4);		// +4 = 実データサイズ(int)
					file.seek(seekp);
				}
				// データの書き込み
				file.write(ByteStreamConverter.convert(record.getDatasize()));
				file.write(record.getData());
				currentRecordNo = recordNo +1;
			}
		}
		
	}
	/**
	 * 指定レコードのデータを復元する
	 * @param recordNo レコード番号
	 * @return 復元されたレコード番号
	 */
	public DbRecord restore(long recordNo) throws IOException {
		synchronized(file) {
			if(currentRecordNo != recordNo) {
				// シークポインタを移動する(連続領域でない場合のみ)
				long seekp = (recordNo - getRecordBegin()) * (getRecordSize() +4);
				file.seek(seekp);
			}
			// データの復元
			byte[] datasize = new byte[4];
			file.read(datasize);

			byte[] bstream = new byte[getRecordSize()];
			file.read(bstream);

			DbRecord record = new DbRecord(recordNo, getRecordSize(), bstream, ByteStreamConverter.getInt(datasize));
			return record;
		}
	}

	/**
	 * データベースファイルの大小比較をする
	 * @param o 比較する値
	 * @return 0:同じ 1: this > o -1: this < o
	 */
	public int compareTo(Object o) {
		DbFile dbf1 = this;
		DbFile dbf2 = (DbFile)o;

		// 開始位置で比較する
		if(dbf1.getRecordBegin() == dbf2.getRecordBegin()) {
			return 0;
		}
		return dbf1.getRecordBegin() > dbf2.getRecordBegin() ? 1 : -1;
	}
}
