/*
このファイルの中身は、NTT オープンソースライセンス　バージョン 1.0（「本
契約」という）の適用を受けます。
本契約を遵守しない限り、このファイルを使用してはなりません。
本契約のコピーは、次のＵＲＬから入手できます。
【配布サイトのURL】 http://www.oss.ecl.ntt.co.jp/lms/

本契約に基づき頒布されるソフトウェアは、現状のまま、明示又は黙示のあらゆ
る種類の保証なしで、頒布されます。本契約に基づく権利及び制限を律する特定
の文言については、本契約を参照してください。

「オリジナルコード」は、 NTT Cyber Space Laboratories Code です。 
「オリジナルコード」の「初期開発者」は、日本電信電話株式会社です。  
日本電信電話株式会社により創作された部分は、Copyright (C) 2004 
日本電信電話株式会社 です。
全ての権利が留保されます。 
「コントリビュータ」：_____________________________________ 


The contents of this file are subject to the NTT Opensource License
Version 1.0 (the License); you may not use this file except in
compliance with the License. You may obtain a copy of the License at
【配布サイトのURL】 http://www.oss.ecl.ntt.co.jp/lms/

Software distributed under the License is distributed on an AS IS
basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
License for the specific language governing rights and limitations
under the License.

The Original Code is NTT Cyber Space Laboratories Code .

The Initial Developer of the Original Code is NIPPON TELEGRAPH AND 
TELEPHONE CORPORATION.
Portions created by the NIPPON TELEGRAPH AND TELEPHONE CORPORATION 
are Copyright (C) 2004 NIPPON TELEGRAPH AND TELEPHONE CORPORATION. 
All Rights Reserved.

Contributor(s) ______________________________________.
*/

//
//	Engine
//	変更履歴
//		2004.02.01	新規作成
//      2006.12.15　変更 T.Kiyokawa
//                  終了時送信ログをXMLからテキスト形式に変更し、高速化
//      2007.02.01  トレースログ関連変更 T.Sakai
//                   trace.xml navitrace.xml 送信有無対応
//                   学習終了又は中断時、trace.xml navitrace.xmlを移動
//

package jp.co.ntt.lms.lo.scorm.engine;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLConnection;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.stream.StreamResult;

import jp.co.ntt.lms.lo.util.UserIDFolder;
import jp.co.ntt.lms.lo.scorm.cbtcom.CMIUtil;
import jp.co.ntt.lms.lo.scorm.kernel.Activity;
import jp.co.ntt.lms.lo.scorm.kernel.AuxiliaryResource;
import jp.co.ntt.lms.lo.scorm.kernel.Kernel;
import jp.co.ntt.lms.lo.scorm.kernel.SCORMCommunicationModule;
import jp.co.ntt.lms.lo.scorm.kernel.exception.CommunicationModuleWrapperException;
import jp.co.ntt.lms.lo.scorm.kernel.exception.SequencingException;
import jp.co.ntt.lms.lo.scorm.kernel.exception.SystemException;
import jp.co.ntt.lms.lo.scorm.util.AICCLogForCSV;
import jp.co.ntt.lms.lo.scorm.util.AICCLogForProperties;
import jp.co.ntt.lms.lo.scorm.util.CachedNode;
import jp.co.ntt.lms.lo.scorm.util.Debug;
import jp.co.ntt.lms.lo.scorm.util.SG;
import jp.co.ntt.lms.lo.scorm.util.XMLUtil;
import jp.co.ntt.lms.lo.scorm.util.XPathUtil;

import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;

/**
 *	Engine クラス<br>
 *	エンジンクラス<br>
 */
public class Engine {
	/** 起動時刻 */
	private Date startDate;

	/** 起動パラメータ */
	private ExecuteParameters executeParameters;

	/** カーネル */
	private Kernel kernel;

	/** ルートアクティビティ */
	private Activity rootActivity;

	/** 最後に発生したシーケンシング例外 */
	private SequencingException lastSequencingException;

	/**
	 * デフォルトコンストラクタ
	 */
	private Engine() {
	}

	/**
	 * コンストラクタ
	 *
	 * @param executeParameters	起動パラメータ
	 */
	public Engine(ExecuteParameters executeParameters) {
		startDate = new Date();
		this.executeParameters = executeParameters;
	}

	/**
	 * 初期化処理
	 *
	 * @param manifest			教材構造が設定されているDOM Document
	 * @param discontinueInfo	中断情報が設定されているDOM Document。
	 *							中断情報が存在しない場合はnullを指定する。
	 */
	public void initialize(Document manifest, Document discontinueInfo) throws SystemException, IOException, ParserConfigurationException, SAXException, TransformerException {

		// メンバの初期化
		kernel = null;
		rootActivity = null;
		lastSequencingException = null;
		alreadyDiscontinued = false;

		// カーネルのインスタンスを生成する
		kernel = new Kernel();

		// Activity Tree を作成する
		rootActivity = kernel.makeTree(ManifestFactory.getCachedNode(executeParameters.getManifestID(), manifest.getDocumentElement(), manifest),executeParameters.getUserID());

		// 初期化を行う
		kernel.initialize(
			executeParameters.getUserID(),
			executeParameters.getUserName(),
			SG.get("AICCURL"),
			SG.get("HACPEncoding"),
			(discontinueInfo == null) ? null : new CachedNode(discontinueInfo.getDocumentElement()));
	}

	/**
	 * 終期化処理<br>
	 */
	public void terminate() throws SystemException {
		executeParameters = null;
		rootActivity = null;

		if (kernel != null) {
			kernel.terminate();
			kernel = null;
		}
	}

	/**
	 * ナビゲーション<br>
	 *
	 * @param	command		コマンド<br>
	 * @param	activityID	アクティビティID<br>
	 * @return	継続可能かどうかを示すフラグ。trueの場合可能、falseの場合は不可能を示す<br>
	 */
	public boolean navigate(String command, String activityID) throws SequencingException, SystemException {
		kernel.navigate(command, activityID);
		return !kernel.isEndSequencingSession();
	}

	/**
	 * 次に遷移するSCO（もしくはASSETの）のURLを取得する<br>
	 *
	 * @param	learningLogFolder	カレントアクティビティが出力する
	 *								学習ログの出力ファイル名を指定する
	 * @return	次に遷移するSCO（もしくはASSETの）URLを返却する
	 */
	public String getURL(String learningLogFolder) throws SystemException, CommunicationModuleWrapperException {
		Activity currentActivity = kernel.getCurrentActivity();

		if (currentActivity == null) {
			return null;
		}

		if (!currentActivity.isActive()) {
			return null;
		}

		return currentActivity.getURL(learningLogFolder);
	}

	/**
	 *	目次情報取得<br>
	 *
	 * @return	<Contents>タグがルートに設定されているElement
	 */
	public Element getContentsMenu(Document doc) throws IOException, ParserConfigurationException, SAXException, SystemException {

		// (ルート)エレメントを作成する
		Element element = XMLUtil.createElement(doc, "Contents", (String) null);

		// root activityから目次を作成する
		makeContentMenu(doc, element, rootActivity, 0);

		// 作成できた目次情報を返却する
		return element;
	}

	/**
	 *	目次情報作成<br>
	 *
	 *	@param	doc		"Contents"ルートタグが存在するDOM Documentを指定する
	 *	@param	parent	作成した目次エレメントを追加するエレメントを指定する
	 *	@param	activity	目次情報を生成するActivityを指定する
	 *	@param	indent	インデント数を指定する。ルートアクティビティからの距離（単位はアクティビティ）を指定する
	 * @return	目次を作成する為のXMLが設定されているDOM Document
	 */
	private void makeContentMenu(Document doc, Element parent, Activity activity, int indent) throws SystemException {

		// objective progress status, objective satisfied status
		boolean objectiveProgressStatus = activity.getObjectiveProgressStatus();
		String objectiveSatisfiedStatus = "";

		if (objectiveProgressStatus) {
			objectiveSatisfiedStatus = Boolean.toString(activity.getObjectiveSatisfiedStatus());
		}

		// objective measure status, objective normalized measure
		boolean objectiveMeasureStatus = activity.getObjectiveMeasureStatus();
		String objectiveNormalizedMeasure = "";

		if (objectiveMeasureStatus) {
			objectiveNormalizedMeasure = activity.getObjectiveNormalizedMeasure().toString();
		}

		// activity progress status,
		// activity absolute duration, activity experienced duration, activity attempt count
		boolean activityProgressStatus = activity.getActivityProgressStatus();
		String activityAbsoluteDuration = "";
		String activityExperiencedDuration = "";
		String activityAttemptCount = "";

		if (activityProgressStatus) {
			activityAbsoluteDuration = Long.toString(activity.getActivityAbsoluteDuration().getMsec());
			activityExperiencedDuration = Long.toString(activity.getActivityExperiencedDuration().getMsec());
			activityAttemptCount = Integer.toString(activity.getActivityAttemptCount());
		}

		// attempt progress status,
		// attempt completion amount, attempt completion status, attempt absolute duration,
		// attempt experienced duration
		boolean attemptProgressStatus = activity.getAttemptProgressStatus();
		String attemptCompletionAmount = "";
		String attemptCompletionStatus = "";
		String attemptAbsoluteDuration = "";
		String attemptExperiencedDuration = "";

		if (attemptProgressStatus) {
			attemptCompletionAmount = activity.getAttemptCompletionAmount().toString();
			attemptCompletionStatus = Boolean.toString(activity.getAttemptCompletionStatus());
			attemptAbsoluteDuration = Long.toString(activity.getAttemptAbsoluteDuration().getMsec());
			attemptExperiencedDuration = Long.toString(activity.getAttemptExperiencedDuration().getMsec());
		}

		// 該当activityの目次情報を作成する
		Element content =
			XMLUtil.insertAttribute(
				doc,
				XMLUtil.append(
					doc,
					parent,
					"Content",
					new Element[] {
						XMLUtil.createElement(doc, "Indent", Integer.toString(indent)),
						XMLUtil.createElement(doc, "Choicable", Boolean.toString(activity.canChoice())),
						XMLUtil.createElement(doc, "Title", activity.getTitle()),
						XMLUtil.createElement(
							doc,
							"ActivityStatus",
							new Element[] {
								XMLUtil.createElement(doc, "Active", Boolean.toString(activity.isActive())),
								XMLUtil.createElement(doc, "Suspended", Boolean.toString(activity.isSuspended())),
								}),
						XMLUtil.createElement(
							doc,
							"TrackingInformation",
							new Element[] {
								XMLUtil.createElement(doc, "ObjectiveProgressStatus", Boolean.toString(objectiveProgressStatus)),
								XMLUtil.createElement(doc, "ObjectiveSatisfiedStatus", objectiveSatisfiedStatus),
								XMLUtil.createElement(doc, "ObjectiveMeasureStatus", Boolean.toString(objectiveMeasureStatus)),
								XMLUtil.createElement(doc, "ObjectiveNormalizedMeasure", objectiveNormalizedMeasure),
								XMLUtil.createElement(doc, "ActivityProgressStatus", Boolean.toString(activityProgressStatus)),
								XMLUtil.createElement(doc, "ActivityAbsoluteDuration", activityAbsoluteDuration),
								XMLUtil.createElement(doc, "ActivityExperiencedDuration", activityExperiencedDuration),
								XMLUtil.createElement(doc, "ActivityAttemptCount", activityAttemptCount),
								XMLUtil.createElement(doc, "AttemptProgressStatus", Boolean.toString(attemptProgressStatus)),
								XMLUtil.createElement(doc, "AttemptCompletionAmount", attemptCompletionAmount),
								XMLUtil.createElement(doc, "AttemptCompletionStatus", attemptCompletionStatus),
								XMLUtil.createElement(doc, "AttemptAbsoluteDuration", attemptAbsoluteDuration),
								XMLUtil.createElement(doc, "AttemptExperiencedDuration", attemptExperiencedDuration),
								}),
						}),
				new Attr[] {
					XMLUtil.createAttribute(doc, "Identifier", activity.getIdentifier()),
					XMLUtil.createAttribute(doc, "Visible", Boolean.toString(activity.getIsVisibleFlag())),
					XMLUtil.createAttribute(
						doc,
						"Current",
						Boolean.toString((kernel.getCurrentActivity() == null) ? false : (kernel.getCurrentActivity().getIdentifier() == activity.getIdentifier()))),
					});

		// activityが available children を持っている場合は、
		// available children の目次情報を作成した目次情報配下へ追加する
		if (activity.hasAvailableChildren()) {
			Activity[] nextTargets = activity.getAvailableChildren();

			for (int i = 0; i < nextTargets.length; i++) {
				makeContentMenu(doc, content, nextTargets[i], indent + 1);
			}
		}
	}

	/**
	 * トレースログ情報取得<br>
	 *
	 * @return	トレースログ情報が設定されているDOM Document
	 */
	public Document getTraceLog() throws SystemException {
		return kernel.getTraceLog();
	}

	/**
	 * 最後に発生したシーケンシング例外の設定<br>
	 *
	 * @param lastSequencingException	シーケンシング例外
	 */
	public void setLastSequencingException(SequencingException lastSequencingException) {
		this.lastSequencingException = lastSequencingException;
	}

	/**
	 * 最後に発生したシーケンシング例外の取得<br>
	 *
	 * @return 直前のgetLastSequencingExceptionメソッドで設定されたシーケンシング例外を返却する
	 */
	public SequencingException getLastSequencingException() {
		return lastSequencingException;
	}

	/**
	 * カレントアクティビティが"Previous"可能かどうかをチェックする
	 *
	 * @return "Previous"可能な場合trueを、不可能な場合はfalseを返却する
	 */
	public boolean canPrevious() throws SystemException {
		Activity currentActivity = kernel.getCurrentActivity();

		if (currentActivity == null) {
			return false;
		}

		return currentActivity.canPrevious();
	}

	/**
	 * カレントアクティビティが"Continue"可能かどうかをチェックする
	 *
	 * @return "Continue"可能な場合trueを、不可能な場合はfalseを返却する
	 */
	public boolean canContinue() throws SystemException {
		Activity currentActivity = kernel.getCurrentActivity();

		if (currentActivity == null) {
			return false;
		}

		return currentActivity.canContinue();
	}

	/**
	 * カレントアクティビティが"Exit"可能かどうかをチェックする
	 *
	 * @return "Exit"可能な場合trueを、不可能な場合はfalseを返却する
	 */
	public boolean canExit() {
		Activity currentActivity = kernel.getCurrentActivity();

		if (currentActivity == null) {
			return false;
		}

		return currentActivity.canExit();
	}

	/**
	 * カレントアクティビティが"ExitAll"可能かどうかをチェックする
	 *
	 * @return "ExitAll"可能な場合trueを、不可能な場合はfalseを返却する
	 */
	public boolean canExitAll() {
		Activity currentActivity = kernel.getCurrentActivity();

		if (currentActivity == null) {
			return false;
		}

		return true;
	}

	/**
	 * カレントアクティビティが"SuspendAll"可能かどうかをチェックする
	 *
	 * @return "SuspendAll"可能な場合trueを、不可能な場合はfalseを返却する
	 */
	public boolean canSuspendAll() {
		Activity currentActivity = kernel.getCurrentActivity();

		if (currentActivity == null) {
			return false;
		}

		return true;
	}

	/**
	 * カレントアクティビティが"Choice"可能かどうかをチェックする
	 *
	 * @return "Choice"可能な場合trueを、不可能な場合はfalseを返却する
	 */
	public boolean canChoice() throws SystemException {
		Activity currentActivity = kernel.getCurrentActivity();

		if (currentActivity == null) {
			return false;
		}

		return currentActivity.canChoice();
	}

	/**
	 * 指定されたURIで指定されたActivityが"Choice"可能かどうかをチェックする
	 *
	 * @param	activity	アクティビティを指定する。自身を含む指定された配下の
	 *						アクティビティからURIパラメータで指定されたアクティビ
	 *						ティIDに一致するアクティビティのcanChoiceメソッドの実
	 *						行結果を返却する。nullが指定された場合はルートアクティ
	 *						ビティから検索を行う。
	 * @param	URI			アクティビティを特定する為のURI
	 * @return	URIで指定されたアクティビティが存在しなかった場合はnullを、
	 *			存在した場合"Choice"可能な場合「true」値を、不可能な場合は「false」
	 *			値を返却する
	 */
	public Boolean canChoice(Activity activity, String URI) throws SystemException {
		if (activity == null) {
			activity = rootActivity;
		}

		if (activity.getIdentifier().equals(URI)) {
			return new Boolean(activity.canChoice());
		}

		Activity[] targets = activity.getAvailableChildren();

		if (targets != null) {
			for (int i = 0; i < targets.length; i++) {
				Boolean result = canChoice(targets[i], URI);

				if (result != null) {
					return result;
				}
			}
		}

		return null;
	}

	// 2010.10.05 taku-harada add >>
	/**
	 * 指定されたURIで指定されたActivityが"Jump"可能かどうかをチェックする
	 *
	 * @param	activity	アクティビティを指定する。自身を含む指定された配下の
	 *						アクティビティからURIパラメータで指定されたアクティビ
	 *						ティIDに一致するアクティビティのcanJumpメソッドの実
	 *						行結果を返却する。nullが指定された場合はルートアクティ
	 *						ビティから検索を行う。
	 * @param	URI			アクティビティを特定する為のURI
	 * @return	URIで指定されたアクティビティが存在しなかった場合はnullを、
	 *			存在した場合"Jump"可能な場合「true」値を、不可能な場合は「false」
	 *			値を返却する
	 */
	public Boolean canJump(Activity activity, String URI) throws SystemException {
		if (activity == null) {
			activity = rootActivity;
		}

		if (activity.getIdentifier().equals(URI)) {
			return new Boolean(activity.canJump());
		}

		Activity[] targets = activity.getAvailableChildren();

		if (targets != null) {
			for (int i = 0; i < targets.length; i++) {
				Boolean result = canJump(targets[i], URI);

				if (result != null) {
					return result;
				}
			}
		}

		return null;
	}
	// 2010.10.05 taku-harada add <<

	/**
	 * カレントアクティビティのアクティビティIDを取得する
	 *
	 * @return カレントアクティビティのアクティビティIDを返却する。
	 * 			カレントアクティビティが存在しない場合はnullを返却する。
	 */
	public String getActivityID() {
		Activity currentActivity = kernel.getCurrentActivity();

		if (currentActivity == null) {
			return null;
		}

		return currentActivity.getIdentifier();
	}

	/**
	 * ルートアクティビティを取得する
	 *
	 * @return ルートアクティビティ
	 */
	public Activity getRootActivity() {
		return rootActivity;
	}

	/**
	 * カレントアクティビティのAuxiliaryResourceを取得する<br>
	 *
	 * @return	カレントアクティビティが持つAuxiliaryResourceの配列を返却する。<br>
	 *			AuxiliaryResourceが存在しない場合はnullを返却する<br>
	 */
	public AuxiliaryResource[] getAuxiliaryResources() throws SystemException {
		Activity currentActivity = kernel.getCurrentActivity();

		if (currentActivity == null) {
			return null;
		}

		return currentActivity.getAuxiliaryResources();
	}

	/**
	 * 教材のタイトルを取得する
	 *
	 * @return	教材のタイトルを返却する
	 */
	public String getTitle() throws SystemException {
		return kernel.getTitle();
	}

	/**
	 * カレントアクティビティのSCORMCommunicationModuleクラスを取得する<br>
	 *
	 * @return	カレントアクティビティのSCORMCommunicationModuleを返却する。<br>
	 *			カレントアクティビティが存在しない場合はnullを返却する<br>
	 */
	public SCORMCommunicationModule getCommunicationModule() throws SystemException {
		Activity currentActivity = kernel.getCurrentActivity();

		if (currentActivity == null) {
			return null;
		}

		return currentActivity.getCommunicationModule();
	}

	/**
	 * カレントアクティビティを取得する
	 *
	 * @return	カレントアクティビティを返却する。カレントアクティビティが
	 *			存在しない場合はnullを返却する
	 */
	public Activity getCurrentActivity() {
		return kernel.getCurrentActivity();
	}

	/**
	 * カレントアクティビティの教材構造に指定されている、「表示してはいけない」
	 * ボタンの名称の一覧を取得する
	 *
	 * @return	カレントアクティビティに「ボタンを表示しない」指定が存在する
	 *			場合はtrueを存在しない場合はfalseを返却する
	 */
	public String[] getHideLMSUI() {
		Activity currentActivity = kernel.getCurrentActivity();

		if (currentActivity == null) {
			return null;
		}

		return currentActivity.getHideLMSUI();
	}

	/** 中断処理実施済みフラグ */
	private boolean alreadyDiscontinued;

	/**
	 *	FolderFilter クラス<br>
	 *	ファイルの一覧からディレクトリのみを抽出する為のフィルタ<br>
	 */
	private static class FolderFilter implements FileFilter {
		/**
		 * 指定された抽象パスがフォルダかどうかチェックする
		 *
		 * @param	pathname	抽象パスを指定する
		 * @return	pathnameがフォルダの場合はtrueを、そうでない場合はfalseを返却する
		 */
		public boolean accept(File pathname) {
			return pathname.isDirectory();
		}
	}

	/** ディレクトリのみをフィルタする為のフィルタ */
	private FolderFilter folderFilter = new FolderFilter();

	/**
	 *	AICCLogFilter クラス<br>
	 *	ファイルの一覧からAICCログファイルのみを抽出する為のフィルタ<br>
	 */
	private static class AICCLogFilter implements FilenameFilter {
		/** .prmのファイル名 */
		private String core;

		/** .intのファイル名 */
		private String interaction;

		/** .cmtのファイル名 */
		private String comment;

		/**
		 * コンストラクタ
		 *
		 * @param	activityID	アクティビティIDを指定する
		 */
		public AICCLogFilter(String activityID) {
			core = activityID + ".prm";
			interaction = activityID + ".int";
			comment = activityID + ".cmt";
		}

		/**
		 * .prmのファイル名を取得する
		 *
		 * @return .prmのファイル名を返却する
		 */
		public String getCoreName() {
			return core;
		}

		/**
		 * .intのファイル名を取得する
		 *
		 * @return .intのファイル名を返却する
		 */
		public String getInteractionName() {
			return interaction;
		}

		/**
		 * .cmtのファイル名を取得する
		 *
		 * @return .cmtのファイル名を返却する
		 */
		public String getCommentName() {
			return comment;
		}

		/**
		 * 指定されたファイル名がAICCログファイルの .prm, .int, .cmtか
		 * どうかチェックする
		 *
		 * @param	dir		フォルダを指定する
		 * @param	name	ファイル名を指定する
		 * @return	AICCログファイルの場合はtrueを、そうでない場合はfalseを返却する
		 */
		public boolean accept(File dir, String name) {
			return (name.equals(core) || name.equals(interaction) || name.equals(comment));
		}
	}

	/**
	 * 中断処理を行う<br>
	 * 1.doSuspendAllフラグから、"SuspendAll"コマンドが実行している場合はkernelに対し"SuspendAll"
	 * コマンドを実行する。<br>
	 * 2.AICCログファイルを読み込みCSV化を行う。<br>
	 * 3.トレースログのXMLファイルを作成する。(中身は空)<br>
	 * 4.カーネルから中断情報(XML)を取得する。<br>
	 * 5.EndLearnServletを起動する。<br>
	 * 6.EndLearnServletから応答を受信する。<br>
	 *
	 * @param	result			終了理由を指定する"SUSPEND"の場合は中断を、"EXIT"の場合は終了を示す。
	 * @param	doSuspendAll	kernelに対し、"SuspendAll"コマンドを実行するかどうかを示すフラグ
	 */
	public void discontinue(String result, boolean doSuspendAll) throws IOException, ParserConfigurationException, SAXException, TransformerException, SequencingException, SystemException {

		// 既に中断処理が実行済みかどうかチェックする
		synchronized (this) {
			// 既に処理済みの場合
			if (alreadyDiscontinued) {
				// 正常終了する
				return;
			}

			// 「中断処理済み」に設定する
			alreadyDiscontinued = true;
		}

		// ----------------------------------------------------------------------
		// 1.doSuspendAllフラグから、"SuspendAll"コマンドが実行している場合はkernelに対し"SuspendAll"
		//   コマンドを実行する。
		// ----------------------------------------------------------------------
		
		// "SuspendAll"コマンドを実行する指定がされている場合
		if (doSuspendAll) {

			// カーネルが生成されていない、もしくはカレントアクティビティが存在しない場合は
			if ((kernel == null) || (kernel.getCurrentActivity() == null)) {
				// "ABANDON"の応答を行う
				responseAbandon(executeParameters, "ABANDON");

				// 終期化を行う
				terminate();

				return;
			}
			else {
				// "SuspendAll"コマンドを実行する
				navigate("SuspendAll", null);

				// トレースログ取得
				Document traceLog = getTraceLog();
				// トレースログが存在する場合
				if (traceLog != null) {
					// トレースログのフルパス名を作成する
					StringBuffer navtraceLogFile = new StringBuffer();

					navtraceLogFile.append(userIDPath(SG.get("ResumeDataBaseURL"), executeParameters.getUserID()));
					navtraceLogFile.append(File.separator);
					navtraceLogFile.append(executeParameters.getLOID());
					navtraceLogFile.append(File.separator);
					navtraceLogFile.append(executeParameters.getStudySessionCount());
					navtraceLogFile.append(File.separator);
					navtraceLogFile.append(executeParameters.getLaunchCount());
					navtraceLogFile.append(File.separator);
					navtraceLogFile.append("navtrace.xml");

					// トレースログを「追加モード」でオープンする
					BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(navtraceLogFile.toString(), true));

					try {
						// navtrace.xmlにトレースログを追加する
						XMLUtil.output(traceLog.getDocumentElement(), true, SG.get("XMLEncoding"), new StreamResult(bos));
					}
					finally {
						bos.close();
					}
				}
			}
		}

		// 中断ログの格納先フォルダ名を作成する
		StringBuffer outputFolder = new StringBuffer();

		outputFolder.append(userIDPath(SG.get("ResumeDataBaseURL"), executeParameters.getUserID()));
		outputFolder.append(File.separator);
		outputFolder.append(executeParameters.getLOID());
		outputFolder.append(File.separator);
		outputFolder.append(executeParameters.getStudySessionCount());
		outputFolder.append(File.separator);
		// 2010.10.07 taku-harada mod >>
		//outputFolder.append(executeParameters.getLaunchCount());
		//outputFolder.append(File.separator);
		// 2010.10.07 taku-harada mod <<

		String outputFolderName = outputFolder.toString();

		// 中断ログの格納先フォルダを作成する
		new File(outputFolderName).mkdirs();

		// 2010.10.07 taku-harada add >>
		// aiccログの格納先フォルダ名を作成する
		StringBuffer outputFolder_aicc = new StringBuffer();

		outputFolder_aicc.append(userIDPath(SG.get("ResumeDataBaseURL"), executeParameters.getUserID()));
		outputFolder_aicc.append(File.separator);
		outputFolder_aicc.append(executeParameters.getLOID());
		outputFolder_aicc.append(File.separator);
		outputFolder_aicc.append(executeParameters.getStudySessionCount());
		outputFolder_aicc.append(File.separator);
		outputFolder_aicc.append(executeParameters.getLaunchCount());
		outputFolder_aicc.append(File.separator);

		String outputFolderName_aicc = outputFolder_aicc.toString();

		// aiccログの格納先フォルダを作成する
		new File(outputFolderName_aicc).mkdirs();
		
		// 2010.10.07 taku-harada add <<

		String encoding = SG.get("XMLEncoding");

		// ----------------------------------------------------------------------
		// 2.AICCログファイルを読み込みCSV化を行う。
		// ----------------------------------------------------------------------
		
		// aicc.csvを作成する
		ArrayList aiccCSV = createAICCLog();

		// 2010.10.07 taku-harada mod >>
		// aicc.csvファイルのフルパス名を作成する
		//String aiccCsvFileName = outputFolderName + "aicc.csv";
		String aiccCsvFileName = outputFolderName_aicc + "aicc.csv";
		// 2010.10.07 taku-harada mod <<
		
		// Fileクラス設定
		File aiccCsvFile = new File(aiccCsvFileName) ;
		
		// aicc.csvファイルのBufferedOutputStreamを作成する
		PrintWriter pw = null;
		try {
			pw = new PrintWriter( new BufferedWriter(new FileWriter(aiccCsvFileName)) );
		
			for (int i = 0 ; i < aiccCSV.size() ; i++ )
			{
				pw.println(aiccCSV.get(i));
			}
		}
		finally {
			try {
				if (pw != null) {
					pw.close();
				}
			} catch (Exception e) {}
		}
		
		// ----------------------------------------------------------------------
		// 3.トレースログのXMLファイルを作成する。
		// ----------------------------------------------------------------------
		
		// Fileクラス設定
		File traceLogFile = new File(outputFolderName + "trace.xml") ;		
		// trace.xmlを作成する
		createTraceLogDocument(outputFolderName , encoding, result);
		
		// ----------------------------------------------------------------------
		// 4.カーネルから中断情報(XML)を取得する。
		// ----------------------------------------------------------------------

		// <resume>タグを作成する
		Document resumeDoc = XMLUtil.createDocument("resume", false);
		kernel.save(resumeDoc);
		
		// resume.xmlファイルのフルパス名を作成する
		String resumeLogFileName = outputFolderName + "resume.xml";
		
		// 作成したxmlを"resume.xml"へ出力する
		File resumeXMLFile = new File(resumeLogFileName);

		// resume.xmlファイルのBufferedOutputStreamを作成する
		BufferedOutputStream resumeBos = new BufferedOutputStream(new FileOutputStream(resumeXMLFile));

		try {
			XMLUtil.output(resumeDoc.getDocumentElement(), false, encoding, new StreamResult(resumeBos));
		}
		finally {
			resumeBos.close();
		}
		
		// 合格状態と完了状態を取得する
		boolean satisfied = false;

		if (rootActivity.getObjectiveProgressStatus()) {
			satisfied = rootActivity.getObjectiveSatisfiedStatus();
		}

		boolean completion = false;

		if (rootActivity.getAttemptProgressStatus()) {
			completion = rootActivity.getAttemptCompletionStatus();
		}

		// ----------------------------------------------------------------------
		// 5.EndLearnServletを起動し、送信する。<br>
		// 6.EndLearnServletから応答を受信する。<br>
		// ----------------------------------------------------------------------
		// EndLearnServletへのURLを作成する
		StringBuffer endLearnServletURL = new StringBuffer();

		endLearnServletURL.append(executeParameters.getEndLearnServletURL());
		endLearnServletURL.append("?");
		endLearnServletURL.append(executeParameters.makeEndLearnServletURLParameters(SG.get("ManagerEncoding")));
		endLearnServletURL.append("&Reason=");
		endLearnServletURL.append(result);
		endLearnServletURL.append("&satisfied=");
		endLearnServletURL.append(satisfied);
		endLearnServletURL.append("&completion=");
		endLearnServletURL.append(completion);

		// EndLearnServletへ接続する
		URLConnection uc = new URL(endLearnServletURL.toString()).openConnection();

		// Output:する、キャッシュ:しない、Content-Type: text/plain
		uc.setDoOutput(true);
		uc.setUseCaches(false);
		uc.setRequestProperty("Content-Type", "text/plain");

		// Input:する
		uc.setDoInput(true);

		OutputStream os = null;
		InputStream is = null;
		
		// "navtrace.xml"のファイルインスタンス作成
		File navTraceFile = new File(outputFolderName + "navtrace.xml");

		//
		// 作成したファイルをEndLearnServletへ送信する
		//
		try {
// トレースログ関連変更 ADD from '07/02/01
			// トレースログ送信有無を取得 
			String TraceSend = null;
			try {
				TraceSend = SG.get("TraceLogSend");
			} catch (Exception e) {
				// 取得エラー時はデフォルトとして送信するを設定
				TraceSend = "1";
			}
// トレースログ関連変更 ADD to '07/02/01

			int aiccCount = aiccCSV.size() ;
// トレースログ関連変更 MOD from '07/02/01
//			int traceCount = getFileRecordCount(traceLogFile) ;
//			int navtraceCount = getFileRecordCount(navTraceFile) ;
			// トレースログ件数
			int traceCount = 0;
			int navtraceCount = 0 ;

			// トレースログ送信有無判定
			if (!"0".equals(TraceSend)) {
				// 送信する場合
				traceCount = getFileRecordCount(traceLogFile);
				navtraceCount = getFileRecordCount(navTraceFile);
			}
// トレースログ関連変更 MOD to '07/02/01
			int resumeCount = getFileRecordCount(resumeXMLFile) ;
			String countString = 
				resumeCount + "," + aiccCount + ","  + traceCount + "," + navtraceCount + "\r\n" ;

			// 送信先の設定
			os = uc.getOutputStream();

			// 行数を送信する
			os.write( countString.getBytes() ) ;

			// 送信は以下の順に行う
		    // １．中断情報
		    // ２．学習ログ情報
			// ３．トレース情報
		    // ４．navtrace情報

			// 中断ログを送信する
			sendFile(resumeXMLFile, os);
			
			// 学習ログを送信する
			sendFile(aiccCsvFile, os);

// トレースログ関連変更 MOD from '07/02/01
			// トレースログを送信する
//			sendFile(traceLogFile, os);
//			sendFile(navTraceFile, os);
			// トレースログ送信有無判定
			if (!"0".equals(TraceSend)) {
				// 送信する場合
				sendFile(traceLogFile, os);
				sendFile(navTraceFile, os);
			}
// トレースログ関連変更 MOD to '07/02/01

			// 結果を受け取る為にInputStreamを取得する
			is = uc.getInputStream();

			// EndLearnServletから処理結果を取得する
			Document endLearnServletResult = XMLUtil.createDocument(is, false);

			// 処理結果文字列を取得する
			Node xmsResult = XPathUtil.selectSingleChildNode(endLearnServletResult.getDocumentElement(), "text()");

			// 処理結果が「正常」でなかった場合は、SystemExceptionをthrowする
			if ((xmsResult == null) || (!xmsResult.getNodeValue().equals("OK"))) {
				StringWriter sw = new StringWriter();

				XMLUtil.output(endLearnServletResult.getDocumentElement(), true, null, new StreamResult(sw));
				throw new SystemException("EndLearnServletからの応答が不正です[" + sw.toString() + "]");
			}
// トレースログ関連変更 ADD from '07/02/01
			// 移動先ファイルパス取得
			StringBuffer moveDirPath = new StringBuffer();
			
// 32000 問題対応 20071027
			//moveDirPath.append(SG.get("ResumeDataBaseURL"));
			//moveDirPath.append(executeParameters.getUserID());
			moveDirPath.append(userIDPath(SG.get("ResumeDataBaseURL"), executeParameters.getUserID()));
			moveDirPath.append(File.separator);
// 32000 問題対応 20071027

			moveDirPath.append(File.separator);
			moveDirPath.append(executeParameters.getLOID());

			// トレースログ移動
			moveFIle(moveDirPath.toString(), traceLogFile);
			moveFIle(moveDirPath.toString(), navTraceFile);
// トレースログ関連変更 ADD to '07/02/01
		}
		finally {
			// InputStreamのクローズ
			if (is != null) {
				try {
					is.close();
				}
				catch (IOException ex) {
					Debug.write(ex);

					ex.printStackTrace(System.out);
				}
			}

			// OutputStreamのクローズ
			if (os != null) {
				try {
					os.close();
				}
				catch (IOException ex) {
					Debug.write(ex);

					ex.printStackTrace(System.out);
				}
			}

			// エンジンの終期化
			try {
				terminate();
			}
			catch (Exception ex) {
				Debug.write(ex);

				ex.printStackTrace(System.out);
			}
		}
	}

// トレースログ関連変更 moveFIleメソッド　ADD from '07/02/01
	/**
	 * ファイルを移動します。引数で指定された移動元ファイルを移動先パスへ移動します。<br>
	 * 移動先に同一ファイルが既に存在する場合、移動先ファイルを削除後、移動します。
	 * @param moveDirPath 移動先ファイルパス
	 * @param traceLogFile 移動元ファイル
	 */
	private void moveFIle(String moveDirPath, File traceLogFile) {
		// 移動元ファイル名取得
		String strFileName = traceLogFile.getName();

		// 移動先ファイル存在チェック
		File toFile = new File(moveDirPath, strFileName);

		if (toFile.isFile()) {
			// ファイルが存在する場合、ファイル削除
			if (!toFile.delete()) {
				// 移動先ファイル削除エラー 
				Debug.write("file delete Error(" + moveDirPath + "/" + strFileName + ")");
			}
		}
		// ファイル移動(リネーム)
		if (!traceLogFile.renameTo(toFile)) {
			// リネームエラー
			Debug.write("file renameTo Error(" + moveDirPath + "/" + strFileName + ")");
		}
	}

// トレースログ関連変更 moveFIleメソッド　ADD to '07/02/01
	/**
	 * ファイルを送信する
	 * @param f ファイル
	 * @param os 出力先
	 * @param buffer バッファ
	 * @throws IOException
	 */
	private void sendFile(File f, OutputStream os)
	throws IOException {
		
		BufferedInputStream bis = null ;
		byte[] buffer = new byte[4096];
		
		try
		{
			bis = new BufferedInputStream(new FileInputStream(f));
			while (true) {
				int length = bis.read(buffer);
				if (length == -1) {
					break;
				}
				os.write(buffer, 0, length);
			}
		}
		catch (FileNotFoundException ex)
		{
			// ファイルがない場合は送信しない（エラーとはしない）
		}
		catch (IOException ex)
		{
			throw ex ;
		}
		finally
		{
			// 入力ストリームを閉じる
			if ( bis != null )
			{
				try
				{
					bis.close() ;
				}
				finally{}
			}
		}
	}

	/**
	 * ファイルの行数取得
	 * @param f ファイル名
	 * @return 行数
	 * @throws IOException
	 */
	private static int getFileRecordCount(File f) 
	throws IOException
	{
		int lineCount = 0;

		try {
			// ファイル読み込み
			BufferedReader reader = new BufferedReader( new FileReader(f) );
			// 全行ループ
			while(true) {
				String line = reader.readLine();
				if(line==null) break;
				lineCount++;
			}
			reader.close();
		}
		catch(FileNotFoundException ex){}
		catch(IOException ex) {}
		
		return lineCount ;
	
	}
	
	/**
	 * 教材の起動失敗処理<br>
	 * EndLearnServletの起動を行い、XMSⅡに起動失敗を通知する
	 *
	 * @param executeParameters	起動パラメータ
	 * @param result 理由を指定する
	 */
	public static void responseAbandon(ExecuteParameters executeParameters, String result) throws UnsupportedEncodingException, IOException, ParserConfigurationException, SAXException {

		// EndLearnServletへのURLを作成する
		StringBuffer endLearnServletURL = new StringBuffer();

		endLearnServletURL.append(executeParameters.getEndLearnServletURL());
		endLearnServletURL.append("?");
		endLearnServletURL.append(executeParameters.makeEndLearnServletURLParameters(SG.get("ManagerEncoding")));
		endLearnServletURL.append("&Reason=");
		endLearnServletURL.append(result);

		// EndLearnServletへ接続する
		URLConnection uc = new URL(endLearnServletURL.toString()).openConnection();

		// Output:しない、キャッシュ:しない、Content-Type: text/xml
		uc.setDoOutput(false);
		uc.setUseCaches(false);
		uc.setRequestProperty("Content-Type", "text/xml");

		// Input:する
		uc.setDoInput(true);

		InputStream is = null;

		try {
			// EndLearnServletからの応答を受信する
			is = uc.getInputStream();
			XMLUtil.createDocument(is, false);
		}
		finally {
			// InputStreamのクローズ
			if (is != null) {
				try {
					is.close();
				}
				catch (IOException ex) {
					Debug.write(ex);

					ex.printStackTrace(System.out);
				}
			}
		}
	}

	/**
	 * AICCログからXMSへ送信するCSVを作成する
	 *
	 * @return AICCログ（CSV）
	 */
	private ArrayList createAICCLog()
		throws UnsupportedEncodingException, FileNotFoundException, SystemException, IOException, ParserConfigurationException, SAXException, TransformerException {

		// AICCログファイルが格納されているディレクトリのベースディレクトリ名を作成する
		StringBuffer baseLogFolderName = new StringBuffer();

		baseLogFolderName.append(userIDPath(SG.get("LearningLogBaseURL"), executeParameters.getUserID()));
		baseLogFolderName.append(File.separator);
		baseLogFolderName.append(executeParameters.getLOID());
		baseLogFolderName.append(File.separator);
		baseLogFolderName.append(executeParameters.getStudySessionCount());
		baseLogFolderName.append(File.separator);
		baseLogFolderName.append(executeParameters.getLaunchCount());

		File baseLogFolder = new File(baseLogFolderName.toString());

		// 最終的に保存するCSVデータ
		ArrayList outputCSV = new ArrayList() ;
		
		// activity idのフォルダ名一覧を取得する
		File[] activityIDs = baseLogFolder.listFiles(folderFilter);
		for (int i = 0; i < activityIDs.length; i++) {

			// [1]ActivityID
			String activityID = activityIDs[i].getName() ;

			// attempt回数のフォルダ名一覧を取得する
			File[] attempts = activityIDs[i].listFiles(folderFilter);
			for (int j = 0; j < attempts.length; j++) {
				// 生成されるCSVのリスト
				String attemptCSV = null ;
				ArrayList interactionCSV = null ;
				ArrayList learnerCommentCSV = null ;
				ArrayList lmsCommentCSV = null ;
				ArrayList objectivesCSV = null ;

				int _attemptCount = Integer.parseInt(attempts[j].getName());

				// attempt countが0(以下)のログは破棄する
				if (_attemptCount <= 0) {
					continue;
				}

				// [2]attempt回数
				String attemptCount = Integer.toString(_attemptCount) ;

				// attempt時刻のフォルダ名一覧を取得する
				File[] attemptDates = attempts[j].listFiles(folderFilter);
				for (int k = 0; k < attemptDates.length; k++) {
					
					// [3]attempt時刻
					String learnDate = attemptDates[k].getName() ;

					AICCLogFilter filter = new AICCLogFilter(activityIDs[i].getName());

					// AICCログファイルの一覧を取得する
					File[] aiccLogs = attemptDates[k].listFiles(filter);
					for (int l = 0; l < aiccLogs.length; l++) {
						// .prm
						if (filter.getCoreName().equals(aiccLogs[l].getName())) {
							// attemptを作成する
							attemptCSV = createAttemptCSV(
									aiccLogs[l], 
									activityID, 
									attemptCount, 
									learnDate);
							// objectivesを作成する
							objectivesCSV = createObjectiveCSV(
									aiccLogs[l], 
									activityID, 
									attemptCount, 
									learnDate) ;
						}
						
						// .int
						else if (filter.getInteractionName().equals(aiccLogs[l].getName())) {
							// interactionsを作成する
							interactionCSV = createInteractionCSV(
									aiccLogs[l], 
									activityID, 
									attemptCount, 
									learnDate);
						}
						// .cmt
						else if (filter.getCommentName().equals(aiccLogs[l].getName())) {
							// lmscommentsを作成する
							learnerCommentCSV = createCommentCSV(
									aiccLogs[l], 
									activityID, 
									attemptCount, 
									learnDate);
						}
					}
				}
				
				// 生成されたCSVをひとつにまとめて保存しておく
				if ( attemptCSV != null )
				{
					outputCSV.add(attemptCSV) ;
				}

				if ( interactionCSV != null )
				{
					for ( int row = 0 ; row < interactionCSV.size() ; row ++ )
					{
						outputCSV.add(interactionCSV.get(row)) ;
					}
				}

				if ( learnerCommentCSV != null )
				{
					for ( int row = 0 ; row < learnerCommentCSV.size() ; row ++ )
					{
						outputCSV.add(learnerCommentCSV.get(row)) ;
					}
				}

				if ( lmsCommentCSV != null )
				{
					for ( int row = 0 ; row < lmsCommentCSV.size() ; row ++ )
					{
						outputCSV.add(lmsCommentCSV.get(row)) ;
					}
				}

				if ( objectivesCSV != null )
				{
					for ( int row = 0 ; row < objectivesCSV.size() ; row ++ )
					{
						outputCSV.add(objectivesCSV.get(row)) ;
					}
				}					
				
			}
		}
		
		return outputCSV ;
	}

	/**
	 * /**
	 * AICCログファイル(.prm)からXMSへ送信するCSVを作成する
	 *
	 * @param aiccLog					XML化するAICCログファイルを指定する。
	 * @param activityID 				ActivityID
	 * @param attemptCount 				試行回数
	 * @param learnDate 				学習日時
	 * @param outAttemptRow 		生成されるattempt行
	 * @param outObjectiveRowList 	生成されるobjectives行配列
	 * @throws UnsupportedEncodingException
	 * @throws FileNotFoundException
	 * @throws SystemException
	 * @throws IOException
	 * @throws ParserConfigurationException
	 * @throws SAXException
	 * @throws TransformerException
	 */
	private String createAttemptCSV(
			File aiccLog, 
			String activityID, 
			String attemptCount, 
			String learnDate)
		throws UnsupportedEncodingException, FileNotFoundException, SystemException, IOException, ParserConfigurationException, SAXException, TransformerException 
	{

		// CSV格納変数を初期化
		EndLearnAICCData attemptCSV = 
			new EndLearnAICCData( activityID, attemptCount, learnDate, EndLearnAICCData.TABLE_KIND_ATTEMPT ) ;
		
		String ret = "" ;
		
		HashMap map;
		String value;
		String[] seps;

		// .prmファイルを展開する
		AICCLogForProperties coreFile = new AICCLogForProperties();

		coreFile.initialize(aiccLog.getPath());

		// [Core]セクションを取得する
		map = coreFile.get("Core");

		// [Core]セクションが存在した場合
		if (map != null) {
			
			// CSV保存するフィールドの値
			String compStatus 	= "" ;
			String scoreScaled 	= "" ;
			String scoreRaw 	= "" ;
			String scoreMax		= "" ;
			String scoreMin		= "" ;
			String sessionTime	= "" ;
			String successStatus= "" ;
			String location		= "" ;
			String exit			= "" ;
			
			// Lesson_Statusを取得する
			value = (String)map.get("Lesson_Status");
			if ((value != null) && (value.length() > 0)) {
				seps = value.split(",");

				if (seps.length >= 2) {
					compStatus = seps[1] ;
				}
				
				if (seps.length >= 1) {
					successStatus = seps[0] ;
				}

				if (seps.length >= 3) {
					exit = seps[2] ;
				}
			}

			// Scoreを取得する
			value = (String)map.get("Score");
			if ((value != null) && (value.length() > 0)) {

				seps = value.split(",");

				if (seps.length >= 1) {
					scoreScaled = seps[0] ;
				}

				if (seps.length >= 2) {
					scoreRaw = seps[1] ;
				}

				if (seps.length >= 3) {
					scoreMax = seps[2] ;
				}

				if (seps.length >= 4) {
					scoreMin = seps[3] ;
				}
			}

			// Timeを取得する
			value = (String)map.get("Time");
			if (value != null) {
				sessionTime = value ;
			}

			// Lesson_Locationを取得する
			value = (String)map.get("Lesson_Location");
			if (value != null) {
				location = value ;
			}

			// CSVデータとして保存
			// ※CSVフォーマットに従って順番に追加すること！
			attemptCSV.addFieldData(compStatus) ;
			attemptCSV.addFieldData(scoreScaled) ;
			attemptCSV.addFieldData(scoreRaw) ;
			attemptCSV.addFieldData(scoreMax) ;
			attemptCSV.addFieldData(scoreMin) ;
			attemptCSV.addFieldData(sessionTime) ;
			attemptCSV.addFieldData(successStatus) ;
			attemptCSV.addFieldData(location) ;
			attemptCSV.addFieldData(exit) ;
			
			// 戻り値に設定
			ret = attemptCSV.getCSVDataRow() ;
		}
		
		return ret ;
	}


	/**
	 * /**
	 * AICCログファイル(.prm)からXMSへ送信するCSVを作成する
	 *
	 * @param aiccLog					XML化するAICCログファイルを指定する。
	 * @param activityID 				ActivityID
	 * @param attemptCount 				試行回数
	 * @param learnDate 				学習日時
	 * @param outAttemptRow 		生成されるattempt行
	 * @param outObjectiveRowList 	生成されるobjectives行配列
	 * @throws UnsupportedEncodingException
	 * @throws FileNotFoundException
	 * @throws SystemException
	 * @throws IOException
	 * @throws ParserConfigurationException
	 * @throws SAXException
	 * @throws TransformerException
	 */
	private ArrayList createObjectiveCSV(
			File aiccLog, 
			String activityID, 
			String attemptCount, 
			String learnDate)
		throws UnsupportedEncodingException, FileNotFoundException, SystemException, IOException, ParserConfigurationException, SAXException, TransformerException 
	{

		HashMap map;
		String value;
		String[] seps;
		
		// .prmファイルを展開する
		AICCLogForProperties coreFile = new AICCLogForProperties();

		coreFile.initialize(aiccLog.getPath());
		
		// [Objectives_Status]セクションを取得する
		map = coreFile.get("Objectives_Status");
		
		// 全行格納リストの初期化
		ArrayList ret = new ArrayList() ;

		if (map != null) {
			for (int i = 0;; i++) {
				// J_ID.n が存在するかどうかチェックする
				value = (String)map.get("J_ID." + (i + 1));

				// J_ID.nが存在しなくなった時点でbreak
				if (value == null) {
					break;
				}

				// CSV格納変数を初期化
				String id 				= value ;
				String scoreScaled 		= "" ;
				String scoreRaw 		= "" ;
				String scoreMax			= "" ;
				String scoreMin			= "" ;
				String successStatus	= "" ;
				String completionStatus = "" ;

				// J_Score.nを取得する
				value = (String)map.get("J_Score." + (i + 1));

				if ((value != null) && (value.length() > 0)) {

					seps = value.split(",");

					if (seps.length >= 1) {
						scoreScaled = seps[0] ;
					}

					if (seps.length >= 2) {
						scoreRaw = seps[1] ;
					}

					if (seps.length >= 3) {
						scoreMax = seps[2] ;
					}

					if (seps.length >= 4) {
						scoreMin = seps[3] ;
					}
				}

				// J_Status.nを取得する
				value = (String)map.get("J_Status." + (i + 1));
				if ((value != null) && (value.length() > 0)) {
					seps = value.split(",");

					if (seps.length >= 1) {
						successStatus =  seps[0] ;
					}

					if (seps.length >= 2) {
						completionStatus = seps[1];
					}
				}
				
				// CSVデータとして保存
				EndLearnAICCData objectiveCSV = 
					new EndLearnAICCData( activityID, attemptCount, learnDate, EndLearnAICCData.TABLE_KIND_OBJECTIVES ) ;

				// ※CSVフォーマットに従って順番に追加すること！
				objectiveCSV.addFieldData(id) ;
				objectiveCSV.addFieldData(scoreScaled) ;
				objectiveCSV.addFieldData(scoreRaw) ;
				objectiveCSV.addFieldData(scoreMin) ;
				objectiveCSV.addFieldData(scoreMax) ;
				objectiveCSV.addFieldData(successStatus) ;
				objectiveCSV.addFieldData(completionStatus) ;

				// 戻り値に設定
				ret.add(objectiveCSV.getCSVDataRow()) ;
			}
		}
		
		return ret;
	}

	/**
	 * AICCログファイル(.int)からXMSへ送信するXMLを作成する
	 *
	 * @param doc			<aicc>タグがルートタグのDOM Documentを指定する。本
	 *						パラメータはlearnSessionパラメータのOwner Documentで
	 *						なければならない。<br>
	 * @param learnSession	<learnsession>タグを指定する。
	 * @param aiccLog		XML化するAICCログファイルを指定する。
	 */
	private ArrayList createInteractionCSV(
			File aiccLog,
			String activityID, 
			String attemptCount, 
			String learnDate)
		throws UnsupportedEncodingException, FileNotFoundException, SystemException, IOException, ParserConfigurationException, SAXException, TransformerException {

		// .intファイルを展開する
		AICCLogForCSV interactionFile = new AICCLogForCSV();

		interactionFile.initialize(aiccLog.getPath());

		int max = interactionFile.size();

		ArrayList ret = new ArrayList(max) ;

		for (int i = 0; i < max; i++) {

			EndLearnAICCData interactionCSV = 
				new EndLearnAICCData(activityID, attemptCount, learnDate, EndLearnAICCData.TABLE_KIND_INTERACTION) ;

			String[] data = interactionFile.get(i);

			interactionCSV.addFieldData( data[1] ) ; // id
			interactionCSV.addFieldData( data[3] ) ; // type
			interactionCSV.addFieldData( data[2] ) ; // objectiveid
			interactionCSV.addFieldData( data[0] ) ; // timestamp
			
			String[] correctResponses = data[4].split(";");
			for ( int j = 0 ; j < 5 ; j++ )
			{
				if ( j < correctResponses.length )
				{
					interactionCSV.addFieldData(correctResponses[j]) ;	// correctResponses
				}
				else
				{
					interactionCSV.addFieldData("") ;
				}
			}
			
			interactionCSV.addFieldData( data[7] ) ; // weighting
			interactionCSV.addFieldData( data[5] ) ; // learnerresponse
			interactionCSV.addFieldData( data[6] ) ; // result 
			interactionCSV.addFieldData( data[8] ) ; // latency 
			interactionCSV.addFieldData( data[9] ) ; // description
			
			// 戻り値に設定
			ret.add(interactionCSV.getCSVDataRow()) ;
		}
		
		return ret ;
	}

	/**
	 * AICCログファイル(.cmt)からXMSへ送信するCSVを作成する
	 *
	 * @param doc			<aicc>タグがルートタグのDOM Documentを指定する。本
	 *						パラメータはlearnSessionパラメータのOwner Documentで
	 *						なければならない。<br>
	 * @param learnSession	<learnsession>タグを指定する。
	 * @param aiccLog		XML化するAICCログファイルを指定する。
	 */
	private ArrayList createCommentCSV(
			File aiccLog,
			String activityID, 
			String attemptCount, 
			String learnDate)
		throws UnsupportedEncodingException, FileNotFoundException, SystemException, IOException, ParserConfigurationException, SAXException, TransformerException {

		// .cmtファイルを展開する
		AICCLogForCSV commentFile = new AICCLogForCSV();

		commentFile.initialize(aiccLog.getPath());

		int max = commentFile.size();

		ArrayList ret = new ArrayList(max) ;

		for (int i = 0; i < max; i++) {
			// learnercommentを作成する
			String[] data = commentFile.get(i);

			EndLearnAICCData commentCSV = 
				new EndLearnAICCData(activityID, attemptCount, learnDate, EndLearnAICCData.TABLE_KIND_LEARNER_COMMENT) ;
			
			commentCSV.addFieldData(data[2]) ;	// comment
			commentCSV.addFieldData(data[1]) ;	// location
			commentCSV.addFieldData(data[0]) ;	// datetime
			
			// 戻り値に設定
			ret.add(commentCSV.getCSVDataRow()) ;
		}
		
		return ret ;
	}

	// CommentOut Start 2006.11.27 T.Kiyokawa
//	/**
//	 * AICCログからXMSへ送信するXMLを作成する
//	 *
//	 * @param doc			トレースログ情報を追加するDOM Documentを指定する
//	 * @param parent		<tracelog>タグを設定する(親の)エレメントを指定する
//	 */
//	private void createAICCLog(Document doc, Element parent)
//		throws UnsupportedEncodingException, FileNotFoundException, SystemException, IOException, ParserConfigurationException, SAXException, TransformerException {
//
//		// AICCログファイルが格納されているディレクトリのベースディレクトリ名を作成する
//		StringBuffer baseLogFolderName = new StringBuffer();
//
//		baseLogFolderName.append(SG.get("LearningLogBaseURL"));
//		baseLogFolderName.append(executeParameters.getUserID());
//		baseLogFolderName.append(File.separator);
//		baseLogFolderName.append(executeParameters.getLOID());
//		baseLogFolderName.append(File.separator);
//		baseLogFolderName.append(executeParameters.getStudySessionCount());
//		baseLogFolderName.append(File.separator);
//		baseLogFolderName.append(executeParameters.getLaunchCount());
//
//		File baseLogFolder = new File(baseLogFolderName.toString());
//
//		// <aicc>タグを作成する
//		Element aicc = XMLUtil.append(doc, parent, "aicc");
//
//		// activity idのフォルダ名一覧を取得する
//		File[] activityIDs = baseLogFolder.listFiles(folderFilter);
//		for (int i = 0; i < activityIDs.length; i++) {
//			// <scoactivitylog>タグを作成する
//			Element SCOActivityLog = XMLUtil.appendChild(doc, aicc, XMLUtil.insertAttribute(doc, "scoactivitylog", new Attr[] { XMLUtil.createAttribute(doc, "id", activityIDs[i].getName()), }));
//
//			// attempt回数のフォルダ名一覧を取得する
//			File[] attempts = activityIDs[i].listFiles(folderFilter);
//			for (int j = 0; j < attempts.length; j++) {
//				int _attemptCount = Integer.parseInt(attempts[j].getName());
//
//				// attempt countが0(以下)のログは破棄する
//				if (_attemptCount <= 0) {
//					continue;
//				}
//
//				// <attemptcount>タグを作成する
//				Element attemptCount =
//					XMLUtil.appendChild(doc, SCOActivityLog, XMLUtil.insertAttribute(doc, "attemptcount", new Attr[] { XMLUtil.createAttribute(doc, "count", Integer.toString(_attemptCount)), }));
//
//				// attempt時刻のフォルダ名一覧を取得する
//				File[] attemptDates = attempts[j].listFiles(folderFilter);
//				for (int k = 0; k < attemptDates.length; k++) {
//					// <learnsession>タグを作成する
//					Element learnSession =
//						XMLUtil.appendChild(doc, attemptCount, XMLUtil.insertAttribute(doc, "learnsession", new Attr[] { XMLUtil.createAttribute(doc, "date", attemptDates[k].getName()), }));
//
//					AICCLogFilter filter = new AICCLogFilter(activityIDs[i].getName());
//
//					// AICCログファイルの一覧を取得する
//					File[] aiccLogs = attemptDates[k].listFiles(filter);
//					for (int l = 0; l < aiccLogs.length; l++) {
//						// .prm
//						if (filter.getCoreName().equals(aiccLogs[l].getName())) {
//							// <attempt>タグを作成する
//							setCore(doc, learnSession, aiccLogs[l]);
//						}
//						// .int
//						else if (filter.getInteractionName().equals(aiccLogs[l].getName())) {
//							// <interactions>タグを作成する
//							setInteraction(doc, learnSession, aiccLogs[l]);
//						}
//						// .cmt
//						else if (filter.getCommentName().equals(aiccLogs[l].getName())) {
//							// <lmscomments>タグを作成する
//							setComment(doc, learnSession, aiccLogs[l]);
//						}
//					}
//				}
//			}
//		}
//	}
//
//	/**
//	 * AICCログファイル(.prm)からXMSへ送信するXMLを作成する
//	 *
//	 * @param doc			<aicc>タグがルートタグのDOM Documentを指定する。本
//	 *						パラメータはlearnSessionパラメータのOwner Documentで
//	 *						なければならない。<br>
//	 * @param learnSession	<learnsession>タグを指定する。
//	 * @param aiccLog		XML化するAICCログファイルを指定する。
//	 */
//	private void setCore(Document doc, Element learnSession, File aiccLog)
//		throws UnsupportedEncodingException, FileNotFoundException, SystemException, IOException, ParserConfigurationException, SAXException, TransformerException {
//
//		HashMap map;
//		String value;
//		String[] seps;
//
//		// .prmファイルを展開する
//		AICCLogForProperties coreFile = new AICCLogForProperties();
//
//		coreFile.initialize(aiccLog.getPath());
//
//		// <attempt>タグを作成する
//		Element attempt = XMLUtil.append(doc, learnSession, "attempt");
//
//		// [Core]セクションを取得する
//		map = coreFile.get("Core");
//
//		// [Core]セクションが存在した場合
//		if (map != null) {
//			// Lesson_Statusを取得する
//			value = (String)map.get("Lesson_Status");
//			if ((value != null) && (value.length() > 0)) {
//				seps = value.split(",");
//
//				if (seps.length >= 1) {
//					XMLUtil.append(doc, attempt, XMLUtil.createElement(doc, "successstatus", seps[0]));
//				}
//
//				if (seps.length >= 2) {
//					XMLUtil.append(doc, attempt, XMLUtil.createElement(doc, "completionstatus", seps[1]));
//				}
//
//				if (seps.length >= 3) {
//					XMLUtil.append(doc, attempt, XMLUtil.createElement(doc, "exit", seps[2]));
//				}
//			}
//
//			// Scoreを取得する
//			value = (String)map.get("Score");
//			if ((value != null) && (value.length() > 0)) {
//				Element score = XMLUtil.append(doc, attempt, "score");
//
//				seps = value.split(",");
//
//				if (seps.length >= 1) {
//					XMLUtil.append(doc, score, XMLUtil.createElement(doc, "scaled", seps[0]));
//				}
//
//				if (seps.length >= 2) {
//					XMLUtil.append(doc, score, XMLUtil.createElement(doc, "raw", seps[1]));
//				}
//
//				if (seps.length >= 3) {
//					XMLUtil.append(doc, score, XMLUtil.createElement(doc, "max", seps[2]));
//				}
//
//				if (seps.length >= 4) {
//					XMLUtil.append(doc, score, XMLUtil.createElement(doc, "min", seps[3]));
//				}
//			}
//
//			// Timeを取得する
//			value = (String)map.get("Time");
//			if (value != null) {
//				XMLUtil.append(doc, attempt, XMLUtil.createElement(doc, "sessiontime", value));
//			}
//
//			// Lesson_Locationを取得する
//			value = (String)map.get("Lesson_Location");
//			if (value != null) {
//				XMLUtil.append(doc, attempt, XMLUtil.createElement(doc, "location", value));
//			}
//		}
//
//		// <objectives>タグを作成する
//		Element objectives = XMLUtil.append(doc, learnSession, "objectives");
//
//		// [Objectives_Status]セクションを取得する
//		map = coreFile.get("Objectives_Status");
//		if (map != null) {
//			for (int i = 0;; i++) {
//				// J_ID.n が存在するかどうかチェックする
//				value = (String)map.get("J_ID." + (i + 1));
//
//				// J_ID.nが存在しなくなった時点でbreak
//				if (value == null) {
//					break;
//				}
//
//				// <objective>タグを作成する
//				Element objective = XMLUtil.append(doc, objectives, "objective");
//
//				// <id>タグを作成する
//				XMLUtil.append(doc, objective, XMLUtil.createElement(doc, "id", value));
//
//				// J_Score.nを取得する
//				value = (String)map.get("J_Score." + (i + 1));
//
//				if ((value != null) && (value.length() > 0)) {
//					Element score = XMLUtil.append(doc, objective, "score");
//
//					seps = value.split(",");
//
//					if (seps.length >= 1) {
//						XMLUtil.append(doc, score, XMLUtil.createElement(doc, "scaled", seps[0]));
//					}
//
//					if (seps.length >= 2) {
//						XMLUtil.append(doc, score, XMLUtil.createElement(doc, "raw", seps[1]));
//					}
//
//					if (seps.length >= 3) {
//						XMLUtil.append(doc, score, XMLUtil.createElement(doc, "max", seps[2]));
//					}
//
//					if (seps.length >= 4) {
//						XMLUtil.append(doc, score, XMLUtil.createElement(doc, "min", seps[3]));
//					}
//				}
//
//				// J_Status.nを取得する
//				value = (String)map.get("J_Status." + (i + 1));
//				if ((value != null) && (value.length() > 0)) {
//					seps = value.split(",");
//
//					if (seps.length >= 1) {
//						XMLUtil.append(doc, objective, XMLUtil.createElement(doc, "successstatus", seps[0]));
//					}
//
//					if (seps.length >= 2) {
//						XMLUtil.append(doc, objective, XMLUtil.createElement(doc, "completionstatus", seps[1]));
//					}
//				}
//			}
//		}
//	}
//
//	/**
//	 * AICCログファイル(.int)からXMSへ送信するXMLを作成する
//	 *
//	 * @param doc			<aicc>タグがルートタグのDOM Documentを指定する。本
//	 *						パラメータはlearnSessionパラメータのOwner Documentで
//	 *						なければならない。<br>
//	 * @param learnSession	<learnsession>タグを指定する。
//	 * @param aiccLog		XML化するAICCログファイルを指定する。
//	 */
//	private void setInteraction(Document doc, Element learnSession, File aiccLog)
//		throws UnsupportedEncodingException, FileNotFoundException, SystemException, IOException, ParserConfigurationException, SAXException, TransformerException {
//
//		// .intファイルを展開する
//		AICCLogForCSV interactionFile = new AICCLogForCSV();
//
//		interactionFile.initialize(aiccLog.getPath());
//
//		// <interactions>タグを作成する
//		Element interactions = XMLUtil.append(doc, learnSession, "interactions");
//
//		int max = interactionFile.size();
//
//		for (int i = 0; i < max; i++) {
//			// <interaction>タグを作成する
//			Element interaction = XMLUtil.append(doc, interactions, "interaction");
//
//			String[] data = interactionFile.get(i);
//
//			XMLUtil.append(doc, interaction, XMLUtil.createElement(doc, "id", data[1]));
//			XMLUtil.append(doc, interaction, XMLUtil.createElement(doc, "type", data[3]));
//			XMLUtil.append(doc, interaction, XMLUtil.createElement(doc, "objectiveid", data[2]));
//			XMLUtil.append(doc, interaction, XMLUtil.createElement(doc, "timestamp", data[0]));
//
//			Element correctResponses = XMLUtil.append(doc, interaction, "correctresponses");
//
//			String[] seps = data[4].split(";");
//			for (int j = 0;(j < seps.length) && (j < 5); j++) {
//				XMLUtil.append(doc, correctResponses, XMLUtil.createElement(doc, "pattern" + (j + 1), seps[j]));
//			}
//
//			XMLUtil.append(doc, interaction, XMLUtil.createElement(doc, "weighting", data[7]));
//			XMLUtil.append(doc, interaction, XMLUtil.createElement(doc, "learnerresponse", data[5]));
//			XMLUtil.append(doc, interaction, XMLUtil.createElement(doc, "result", data[6]));
//			XMLUtil.append(doc, interaction, XMLUtil.createElement(doc, "latency", data[8]));
//			XMLUtil.append(doc, interaction, XMLUtil.createElement(doc, "description", data[9]));
//		}
//	}
//
//	/**
//	 * AICCログファイル(.cmt)からXMSへ送信するXMLを作成する
//	 *
//	 * @param doc			<aicc>タグがルートタグのDOM Documentを指定する。本
//	 *						パラメータはlearnSessionパラメータのOwner Documentで
//	 *						なければならない。<br>
//	 * @param learnSession	<learnsession>タグを指定する。
//	 * @param aiccLog		XML化するAICCログファイルを指定する。
//	 */
//	private void setComment(Document doc, Element learnSession, File aiccLog)
//		throws UnsupportedEncodingException, FileNotFoundException, SystemException, IOException, ParserConfigurationException, SAXException, TransformerException {
//
//		// .cmtファイルを展開する
//		AICCLogForCSV commentFile = new AICCLogForCSV();
//
//		commentFile.initialize(aiccLog.getPath());
//
//		// <lmscomments>タグを作成する
//		Element learnerComments = XMLUtil.append(doc, learnSession, "learnercomments");
//
//		int max = commentFile.size();
//
//		for (int i = 0; i < max; i++) {
//			// <lmscomment>タグを作成する
//			String[] data = commentFile.get(i);
//
//			XMLUtil.append(
//				doc,
//				learnerComments,
//				"learnercomment",
//				new Element[] { XMLUtil.createElement(doc, "comment", data[2]), XMLUtil.createElement(doc, "location", data[1]), XMLUtil.createElement(doc, "datetime", data[0]), });
//		}
//	}
//
//	/**
//	 * XMSへ送信するトレースログ(trace.xml)を作成し、作成したtrace.xmlのDOM Documentを返却する。
//	 *
//	 * @param doc			トレースログ情報を追加するDOM Documentを指定する
//	 * @param parent		<tracelog>タグを設定する(親の)エレメントを指定する
//	 * @param outputFolder	trace.xmlファイルを作成するフォルダを指定する。<br>
//	 * @param encoding		trace.xmlを作成する際の文字エンコーディングを指定する<br>
//	 * @param result		終了理由を指定する"SUSPEND"の場合は中断を、"EXIT"の場合は終了を示す。
//	 */
//	private void createTraceLogDocument(Document doc, Element parent, String outputFolder, String encoding, String result)
//		throws IOException, ParserConfigurationException, SAXException, TransformerConfigurationException, TransformerException {
//
//		// trace.xmlファイルのフルパス名を作成する
//		String traceLogFileName = outputFolder + "trace.xml";
//
//		// trace.xmlファイルのPrintWriterを作成する
//		PrintWriter pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(new BufferedOutputStream(new FileOutputStream(traceLogFileName)), encoding)));
//
//		StreamResult out = new StreamResult(pw);
//
//		Element rootElement;
//
//		try {
//			SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMddHHmmssSSS");
//
//			// xml宣言
//			pw.println("<?xml version=\"1.0\" encoding=\"" + encoding + "\"?>");
//
//			// DOCTYPE宣言 - ENTITYとして"navtrace"にnavtrace.xmlの内容を設定する
//			pw.println("<!DOCTYPE tracelog [");
//			pw.println("<!ENTITY navtrace SYSTEM \"" + outputFolder + "navtrace.xml\">");
//			pw.println("]>");
//
//			// <tracelog>ルートタグ
//			pw.println("<tracelog>");
//
//			int traceLevel = Integer.parseInt(SG.get("TraceLevel"));
//			if (traceLevel > 1) {
//				// <logininformation>タグを作成する
//				Document login = XMLUtil.createDocument("logininformation", false);
//				rootElement = login.getDocumentElement();
//
//				XMLUtil.append(login, rootElement, "userid", executeParameters.getUserID());
//				XMLUtil.append(login, rootElement, "username", executeParameters.getUserName());
//				XMLUtil.append(login, rootElement, "loid", executeParameters.getLOID());
//				XMLUtil.append(login, rootElement, "studysession", executeParameters.getStudySessionCount());
//				XMLUtil.append(login, rootElement, "loginsessionid", executeParameters.getLaunchCount());
//				XMLUtil.append(login, rootElement, "manifestid", executeParameters.getManifestID());
//				XMLUtil.append(login, rootElement, "datetime", formatter.format(startDate));
//				XMLUtil.append(login, rootElement, "command", executeParameters.getStartCommand());
//
//				XMLUtil.output(login, true, encoding, out);
//
//				// navtraceエンティティの実体参照
//				pw.println("&navtrace;");
//
//				// <logoutinformation>タグを作成する
//				Document logout = XMLUtil.createDocument("logoutinformation", false);
//				rootElement = logout.getDocumentElement();
//
//				XMLUtil.append(logout, rootElement, "datetime", formatter.format(new Date()));
//				XMLUtil.append(logout, rootElement, "reason", result);
//
//				XMLUtil.output(logout, true, encoding, out);
//			}
//			
//			// <tracelog>ルートタグを閉じる
//			pw.println("</tracelog>");
//		}
//		finally {
//			pw.close();
//		}
//
//		XMLUtil.output(new File(traceLogFileName), true, null, new DOMResult(parent));
//	}
//
	// CommentOut End
	
	/**
	 * XMSへ送信するトレースログ(trace.xml)を作成する。
	 *
	 * @param outputFolder	trace.xmlファイルを作成するフォルダを指定する。<br>
	 * @param encoding		trace.xmlを作成する際の文字エンコーディングを指定する<br>
	 * @param result		終了理由を指定する"SUSPEND"の場合は中断を、"EXIT"の場合は終了を示す。
	 */
	private void createTraceLogDocument(String outputFolder, String encoding, String result)
		throws IOException, ParserConfigurationException, SAXException, TransformerConfigurationException, TransformerException {

		// trace.xmlファイルのフルパス名を作成する
		String traceLogFileName = outputFolder + "trace.xml";

		// trace.xmlファイルのPrintWriterを作成する
		PrintWriter pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(new BufferedOutputStream(new FileOutputStream(traceLogFileName)), encoding)));

		StreamResult out = new StreamResult(pw);

		Element rootElement;

		try {
			SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMddHHmmssSSS");

			// xml宣言
			pw.println("<?xml version=\"1.0\" encoding=\"" + encoding + "\"?>");

			// DOCTYPE宣言 - ENTITYとして"navtrace"にnavtrace.xmlの内容を設定する
			pw.println("<!DOCTYPE tracelog [");
			pw.println("<!ENTITY navtrace SYSTEM \"" + "navtrace.xml\">");
			pw.println("]>");

			// <tracelog>ルートタグ
			pw.println("<tracelog>");

			int traceLevel = Integer.parseInt(SG.get("TraceLevel"));
			if (traceLevel > 1) {
				// <logininformation>タグを作成する
				Document login = XMLUtil.createDocument("logininformation", false);
				rootElement = login.getDocumentElement();

				XMLUtil.append(login, rootElement, "userid", executeParameters.getUserID());
				XMLUtil.append(login, rootElement, "username", executeParameters.getUserName());
				XMLUtil.append(login, rootElement, "loid", executeParameters.getLOID());
				XMLUtil.append(login, rootElement, "studysession", executeParameters.getStudySessionCount());
				XMLUtil.append(login, rootElement, "loginsessionid", executeParameters.getLaunchCount());
				XMLUtil.append(login, rootElement, "manifestid", executeParameters.getManifestID());
				XMLUtil.append(login, rootElement, "datetime", formatter.format(startDate));
				XMLUtil.append(login, rootElement, "command", executeParameters.getStartCommand());

				XMLUtil.output(login, true, encoding, out);

				// navtraceエンティティの実体参照
				pw.println("&navtrace;");

				// <logoutinformation>タグを作成する
				Document logout = XMLUtil.createDocument("logoutinformation", false);
				rootElement = logout.getDocumentElement();

				XMLUtil.append(logout, rootElement, "datetime", formatter.format(new Date()));
				XMLUtil.append(logout, rootElement, "reason", result);

				XMLUtil.output(logout, true, encoding, out);
			}
			
			// <tracelog>ルートタグを閉じる
			pw.println("</tracelog>");
		}
		finally {
			pw.close();
		}
	}

	/**
	 * 
	 * 終了学習情報の格納クラス
	 * @author EndLearnAICCData
	 *
	 */
	private static class EndLearnAICCData
	{
		//*********************************
		// 内部ステータス
		//*********************************
		private String activityID = null ;
		
		private String attemptCount = null ;
		
		private String learnDate = null ;
		
		private String tableKind = null ;
		
		private ArrayList fieldDataList = null ;
		
		//*********************************
		// テーブル種別定数
		//*********************************

		/**
		 * テーブル種別:"attempt"
		 */
		public static final String TABLE_KIND_ATTEMPT = "attempt";
		
		/**
		 * テーブル種別:"interaction"
		 */
		public static final String TABLE_KIND_INTERACTION = "interaction";
		
		/**
		 * テーブル種別:"learnerComment"
		 */
		public static final String TABLE_KIND_LEARNER_COMMENT = "learnerComment";
		
		/**
		 * テーブル種別:"lmsComment"
		 */
		public static final String TABLE_KIND_LMS_COMMENT = "lmsComment";

		/**
		 * テーブル種別:"objectives"
		 */
		public static final String TABLE_KIND_OBJECTIVES = "objectives";
		
		/**
		 * コンストラクタ
		 * @param activityID ActivityID
		 * @param attemptCount 試行回数
		 * @param learnDate 学習日時
		 * @param tableKind 登録テーブル
		 */
		public EndLearnAICCData(String activityID, String attemptCount, String learnDate, String tableKind )
		{
			this.activityID = activityID ;
			this.attemptCount = attemptCount ;
			this.learnDate = learnDate ;
			this.tableKind = tableKind ;
			this.fieldDataList = new ArrayList(20) ;	// CSV20列分までメモリを確保しておく
		}

		//*********************************
		// setter/getter
		//*********************************
		/**
		 * @return activityID
		 */
		public String getActivityID() {
			return activityID;
		}

		/**
		 * @param activityID 設定する activityID
		 */
		public void setActivityID(String activityID) {
			this.activityID = activityID;
		}

		/**
		 * @return attemptCount
		 */
		public String getAttemptCount() {
			return attemptCount;
		}

		/**
		 * @param attemptCount 設定する attemptCount
		 */
		public void setAttemptCount(String attemptCount) {
			this.attemptCount = attemptCount;
		}

		/**
		 * @return learnDate
		 */
		public String getLearnDate() {
			return learnDate;
		}

		/**
		 * @param learnDate 設定する learnDate
		 */
		public void setLearnDate(String learnDate) {
			this.learnDate = learnDate;
		}

		/**
		 * @return tableKind
		 */
		public String getTableKind() {
			return tableKind;
		}

		/**
		 * @param tableKind 設定する tableKind
		 */
		public void setTableKind(String tableKind) {
			this.tableKind = tableKind;
		}

		/**
		 * @return fieldDataList
		 */
		public ArrayList getFieldDataList() {
			return fieldDataList;
		}

		/**
		 * @param fieldDataList 設定する fieldDataList
		 */
		public void setFieldDataList(ArrayList fieldDataList) {
			this.fieldDataList = fieldDataList;
		}
		
		/**
		 * @param fieldData 追加する fieldData
		 */
		public void addFieldData(String data) {
			if ( data == null )
			{
				fieldDataList.add("") ;
			}
			else
			{
				fieldDataList.add(data);
			}
		}

		/**
		 * @return fieldData
		 */
		public String getFieldData(int i) {
			return this.fieldDataList.get(i).toString() ;
		}
		
		/**
		 * CSVデータ行を取得する。
		 * @return CSVデータ行
		 */
		public String getCSVDataRow()
		{
			String ret = "" ;
			
			ret = ret + CMIUtil.urlEncode( this.activityID ) ;
			ret = ret + "," + CMIUtil.urlEncode(this.attemptCount ) ;
			ret = ret + "," + CMIUtil.urlEncode(this.learnDate ) ;
			ret = ret + ","	+ CMIUtil.urlEncode(this.tableKind ) ;
			
			for ( int i = 0 ; i < this.fieldDataList.size() ; i++ )
			{
				ret = ret + "," + CMIUtil.urlEncode( this.fieldDataList.get(i).toString() ) ;
			}
			
			return ret ;
		}
	}

	private static String userIDPath(String strDirPath, String strUserID)
	{
		return UserIDFolder.getPath(strDirPath, strUserID);
	}
}
