/*
 * Copyright (c) 2009 The openGion Project.
 *
 * 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.
 */
package org.opengion.hayabusa.filter;

import org.opengion.hayabusa.common.HybsSystem;				// 6.9.9.2 (2018/09/18)
import org.opengion.fukurou.util.StringUtil;
import org.opengion.fukurou.util.FileUtil;

import java.util.regex.Pattern ;
import java.util.regex.Matcher ;
import java.util.Set ;
import java.util.HashSet ;
import java.io.File;

/**
 * FileFilter で使用する、紙芝居用HTMLファイル作成時に内部文字列を変換するクラスです。
 *
 * @og.group フィルター処理
 *
 * @version  4.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
public class FileResponseTransform {

	// 5.6.4.2 (2013/05/17) JFreeChart の画像ファイル(ChartTempフォルダ) のコピー関係の固定値
	private static final String CHART_KEY = "/ChartTemp/" ;
	private static final int    KEY_LEN   = CHART_KEY.length();

	private static final Set<String> TARGET_CHANGE_SET = new HashSet<>();

	private static final ChangeData[] CHG_DATA = new ChangeData[] {											// 6.4.1.1 (2016/01/16) data → CHG_DATA refactoring
		 new ChangeData( null , "\"/[^/]*/jsp/"				,"\"../" )				// 5.5.7.2 (2012/10/09) マッチ条件を広げる
		,new ChangeData( null , "'/[^/]*/jsp/"				,"'../" )				// 5.5.7.2 (2012/10/09) マッチ条件を広げる
		,new ChangeData( null , "\\(/[^/]*/jsp/"			,"(../" )				// 6.4.3.2 (2016/02/19) マッチ条件を広げる
		,new ChangeData( null , "=\"/[^/]*/[^/]*/ChartTemp/","=\"../ChartTemp/" )	// 5.5.2.5 (2012/05/21) JfreeChart関係の画像のアドレス変換
		,new ChangeData( null , "='/[^/]*/[^/]*/ChartTemp/"	,"='../ChartTemp/" )	// 5.5.2.5 (2012/05/21) JfreeChart関係の画像のアドレス変換
		,new ChangeData( null , "=\"/[^/]*/help/"			,"=\"../help/" )		// 5.5.2.5 (2012/05/21) help関係の画像のアドレス変換
		,new ChangeData( null , "='/[^/]*/help/"			,"='../help/" )			// 5.5.2.5 (2012/05/21) help関係の画像のアドレス変換
		,new ChangeData( null , "\\.jsp"					,".htm" )
		,new ChangeData( "query.htm"	, "action=\"result"			,"action=\"forward" )					// 6.2.5.0 (2015/06/05) GMIS V6 対応(３ペイン対応)
		,new ChangeData( "forward.htm"	, "frame src=\"result"		,"frame src=\"forward" )				// 5.6.3.4 (2013/04/26) forward.htm にフレームを使うパターン(３ペイン)
		,new ChangeData( "reset.htm"	, "frame src=\"result"		,"frame src=\"forward" )				// 5.6.4.2 (2013/05/17) reset.htm で、フレームを使うパターン(３ペイン)
		,new ChangeData( "index.htm"	, "frame src=\"forward.htm"	,"frame src=\"../common/dummy.html" )
		,new ChangeData( "index.htm"	, "frame src=\"entry.htm"	,"frame src=\"../common/dummy.html" )	// 5.6.3.4 (2013/04/26) ENTRY系の特殊対応
		,new ChangeData( "indexRNW.htm"	, "frame src=\"forward.htm"	,"frame src=\"renew.htm" )
		,new ChangeData( "indexNW.htm"	, "frame src=\"query.htm"	,"frame src=\"queryNW.htm" )
		,new ChangeData( "indexNW.htm"	, "frame src=\"entry.htm"	,"frame src=\"../common/dummy.html" )	// 5.6.3.4 (2013/04/26) ENTRY系の特殊対応
		,new ChangeData( "entry"		, "<input[^>]*history.back[^>]*>"	,"" )							// 6.3.8.0 (2015/09/11) hiistory.back() のタグ削除 の対象を、entry を含むに変更
		,new ChangeData( null , "onSubmit=\"return oneClick\\(\\);\"","onSubmit=\"return false;\"" )		// 5.5.7.2 (2012/10/09) 変更は、すべて行う。
		,new ChangeData( null , "onSubmit=\"\""						,"onSubmit=\"return false;\"" )			// 5.6.3.4 (2013/04/26) onSubmit 引数のないケースへの対応
		,new ChangeData( null , "src=\"\\.\\./common/option/ajaxSubmit\\.js\\?v=[^\"]+\"","" )				// 5.6.3.4 (2013/04/26) ajaxSubmit.js を削除
		// 4.3.3.0 (2008/10/01) 戻るリンクの対応
		,new ChangeData( "queryNW.htm" ,"=\"http://.*jsp/+?"	,"=\"../" )
//		,new ChangeData( "queryNW.htm" ,"\"query.htm?"	,"\"querNWy.htm?" )									// 6.4.2.1 (2016/02/05) クリアボタンのとび先
		,new ChangeData( "queryNW.htm" ,"\"query.htm?"	,"\"queryNW.htm?" )									// 6.9.3.1 (2018/04/02) クリアボタンのとび先(綴り間違い)
		,new ChangeData( "query.htm"   ,"renew\\('query.htm'"	,"renew\\('queryNW.htm'" )					// 5.6.4.2 (2013/05/17) renew('query.htm' 変換
		 // 漢字のボタンでは、後ろにショートカット文字が入る為、前方一致で挿入する。
	//	,new ChangeData( "forward.htm","value=\"追加","onClick=\"location.href='insert.htm'\" value=\"追加" )
	//	,new ChangeData( "forward.htm","value=\"複写","onClick=\"location.href='copy.htm'\"   value=\"複写" )
	//	,new ChangeData( "forward.htm","value=\"変更","onClick=\"location.href='modify.htm'\" value=\"変更" )
	//	,new ChangeData( "forward.htm","value=\"削除","onClick=\"location.href='delete.htm'\" value=\"削除" )
	//	,new ChangeData( "query.htm","index.htm\\?command=RENEW"	,"indexRNW.htm?command=RENEW" )
	//	,new ChangeData( null		,"index.htm\\?command=NEW"		,"indexNW.htm?command=NEW" )
		,new IndexMatrixMenuData()			// 4.3.3.0 (2008/10/01) マトリクスメニュー対応
		,new IndexChangeData()
		,new HrefChangeData()
		,new NoTranHrefChangeData()			// 5.6.3.4 (2013/04/26) entry.htm に "noTransitionUrl" が存在するときの処理。
		,new FileDownloadChangeData()		// 5.6.4.2 (2013/05/17) fileDownload.htm 対応
		,new ChangeData( "multiMenu.htm" , "\\.\\./common/gamen", "../")			// 6.9.9.2 (2018/09/18) oota tmp add 01_「menu_リンク先微調整_管谷」対応
		,new ChangeData( null, "\\.\\./common/reportView.htm", "reportView.htm")	// 6.9.9.2 (2018/09/18) oota tmp 02_「SAL1021_出荷_帳票リンク_新沼」対応
		// 6.9.9.2 (2018/09/18) oota tmp POPUP対応(href指定あり) 03_「POPUP全般_index.htm作成されない_管谷」対応
		,new ChangeData( null, "(ogPopup\\( '\\.\\./.*?/)(index|query).htm", "$1$2NW.htm")
		// 6.9.9.2 (2018/09/18) oota tmp POPUP対応(href指定なし) indexNW.htmを付与 03_「POPUP全般_index.htm作成されない_管谷」対応
		,new ChangeData( null, "(ogPopup\\( '\\.\\./.*?/)'", "$1indexNW.htm'")
		// 6.9.9.2 (2018/09/18) oota tmp submitタグでgamenIdを指定して、別画面に遷移時。 04_「FMA0103_ドキュメント登録_追加ボタンが動作しない_原野」対応
		,new ChangeData( null, "(parent.location.href='\\.\\./.*?/)(index|query).htm", "$1$2NW.htm")
		,new QuerySubmitChange()	// 6.9.9.2 (2018/09/18) oota tmp add 09_「DIE0005_金型保守_チェック処理ボタン動かない_北林」対応
	};

	/**
	 * デフォルトコンストラクター
	 *
	 * @og.rev 6.4.2.0 (2016/01/29) PMD refactoring. Each class should declare at least one constructor.
	 */
	public FileResponseTransform() { super(); }		// これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。

	/**
	 * 変換を行います。
	 * 実際には、各内部クラスのメソッドで処理を行います。
	 *
	 * @og.rev 5.6.4.2 (2013/05/17) JFreeChart の画像ファイル(ChartTempフォルダ) のコピー
	 *
	 * @param	file 	対象ファイル名
	 * @param	inStr	対象データ
	 *
	 * @return	変換後データ
	 */
	public String replace( final String file,final String inStr ) {
		String rtnStr = inStr;

		// query 画面で、登録コマンドが発行された場合の特殊処理
		if( file.indexOf( "query.htm" ) >= 0 && inStr.indexOf( "name=\"command\" value=\"登録" ) >= 0 ) {
			rtnStr = inStr.replace( "forward.jsp","entry.htm" );
		}

		// 5.6.4.2 (2013/05/17) JFreeChart の画像ファイル(ChartTempフォルダ) のコピー
		if( inStr.indexOf( "/ChartTemp/" ) >= 0 ) {
			chartTempFileCopy( file,inStr );
		}

		for( int i=0; i<CHG_DATA.length; i++ ) {
			rtnStr = CHG_DATA[i].replace( file,rtnStr );
		}
		return rtnStr;
	}

	/**
	 * JFreeChart の画像ファイル(ChartTempフォルダ) のコピーを行います。
	 * これは、Tomcatを停止させずに、ChartTempフォルダ を人手てコピーする予定でしたが、
	 * 万一、停止させると、ファイルが自動削除されるため、自動コピー機能を入れておきます。
	 *
	 * ここの処理は、対象データ(inStr) の文字列変換ではなく、画像ファイルを見つけて、
	 * コピーするという処理を行います。非常に特殊な処理です。
	 *
	 * @og.rev 5.6.4.2 (2013/05/17) 新規追加
	 *
	 * @param	file 	対象ファイル名
	 * @param	inStr	対象データ
	 */
	private void chartTempFileCopy( final String file,final String inStr ) {
		// 以下、決め打ちします。本当は saveDir や、ChartTemp をパラメータから取得すべき
		// 大前提として、fromDir = filetemp/ChartTemp/
		//               toDir   = filetemp/DIR/ChartTemp/

		final int adrs = file.indexOf( "filetemp/DIR" );			// jsp ファイルの出力先なので、DIR を含んでいます。
		final File toDir   = new File( file.substring( 0,adrs ) + "filetemp/DIR/ChartTemp/" );

		// コピー先ディレクトリが存在しなければ・・・
		// 6.0.0.1 (2014/04/25) These nested if statements could be combined
		if( !toDir.exists() && !toDir.mkdirs() ) {
			System.err.println( toDir + " の ディレクトリ作成に失敗しました。" );
			return ;
		}

		// 6.4.1.1 (2016/01/16) PMD refactoring. Avoid declaring a variable if it is unreferenced before a possible exit point.
		final File fromDir = new File( file.substring( 0,adrs ) + "filetemp/ChartTemp/" );
		// 画像ファイル名を求めます。
		int st = inStr.indexOf( CHART_KEY ) ;
		while( st >= 0 ) {
			final int ed = inStr.indexOf( ".png" ,st + KEY_LEN ) ;			// 検索開始位置は、CHART_KEYの発見場所＋文字数
			final String fname = inStr.substring( st + KEY_LEN , ed + 4 );	// + 4 は、".png" の分

			FileUtil.copy( new File( fromDir,fname ) , new File( toDir,fname ) );

			st = inStr.indexOf( CHART_KEY , ed + 4 ) ;
		}
	}

	/**
	 * 個々の変換データを管理している、データクラス
	 * このクラスは、不変クラスのため、マルチスレッドでの同時使用に対して、安全です。
	 *
	 * ※ このクラスは継承されるため、final化しません。
	 */
	private static class ChangeData {
		private final String filename ;
		private final String org ;
		private final String rep ;

		/**
		 * デフォルトコンストラクター
		 * サブクラス作成用に用意された、コンストラクタ。このクラス自身には不要。
		 */
		public ChangeData() {
			this( null,null,null );
		}

		/**
		 * コンストラクター
		 * 初期設定を行います。
		 * 対象ファイル名は、処理を実行するかどうかの判定に使用します。
		 * 指定の文字列が含まれているかどうかで判定します。
		 * null の場合は、すべてのファイルを対象にします。
		 *
		 * @param	filename	対象ファイル名
		 * @param	org	変換元データ
		 * @param	rep	変換後データ
		 */
		public ChangeData( final String filename,final String org,final String rep ) {
			this.filename = filename;
			this.org = org;
			this.rep = rep;
		}

		/**
		 * 実際に変換を行うメソッド
		 * 内部的には、入力文字列.replaceAll( 変換元,変換後 ) メソッドを実行します。
		 *
		 * @param	file	対象ファイル名
		 * @param	inStr	対象データ
		 * @return	変換後データ
		 */
		public String replace( final String file,final String inStr ) {

			String rtnStr = inStr;
			if( filename == null || file.indexOf( filename ) >= 0 ) {
				rtnStr = inStr.replaceAll( org,rep );
			}
			return rtnStr;
		}

		/**
		 * このオブジェクトの文字列表現
		 * "ChangeData( " + filename + " , " + org + " , " + rep + " )" を返します。
		 *
		 * @return	文字列表現
		 * @og.rtnNotNull
		 */
		@Override
		public String toString() {
			return "ChangeData( " + filename + " , " + org + " , " + rep + " )" ;
		}
	}

	/**
	 * マトリクスメニュー対応のデータ置き換えクラスです。
	 * jsp/ は、先に、../ に変換済みなので、その "../" からの検索条件で判断します。
	 * multiMenu と、matrixMenu は、標準は、menu フォルダですが、場合によっては、custom フォルダに存在する
	 * 場合があるため、menu/ は検索条件に含めません。
	 * ③と④は、変換処理は無いはずなので、ロジックは記述していません。
	 *
	 *   matrixMenu対応
	 * 		   URI分離          URI分離           request取出
	 * 		① gamenId="jsp"  + index.jsp       + GAMENID=XXXX  ⇒ saveDir + "jsp/indexXXXX.htm"         Matrixメニューからの画面呼出し。
	 * 		② gamenId="jsp"  + result.jsp      + GAMENID=XXXX  ⇒ saveDir + "jsp/indexXXXX.htm"         画面QUERYのヘッダーメニュー
	 * 		③ gamenId="menu" + multiMenu.jsp   + group=YYYY    ⇒ saveDir + "menu/menuYYYY.htm"         通常メニューのグループ選択
	 * 		④ gamenId="menu" + matrixMenu.jsp  + group=YYYY    ⇒ saveDir + "menu/matrixMenuYYYY.htm"   Matrixメニューのグループ選択
	 *
	 * このクラスは、不変クラスのため、マルチスレッドでの同時使用に対して、安全です。
	 *
	 * @og.rev 4.3.3.0 (2008/10/01) Matrixメニュー対応
	 * @og.rev 5.5.2.5 (2012/05/21) TopMenuTag の ONELEVEL メニューリンク対応
	 * @og.rev 5.6.4.2 (2013/05/17) 正規表現修正、判定条件変更
	 * @og.rev 6.3.8.4 (2015/10/09) セーブフォルダを、URIではなく、画面IDから取得する。
	 * @og.rev 6.4.3.2 (2016/02/19) matrixMenu の、../画面ID/index.htm の場合の対応
	 */
	private static final class IndexMatrixMenuData extends ChangeData {
		/**
		 * 実際に変換を行うメソッド。
		 *
		 * @param	file	対象ファイル名
		 * @param	inStr	対象データ
		 * @return	変換後データ
		 */
		@Override
		public String replace( final String file,final String inStr ) {
			String rtnStr = inStr;

			// 5.6.4.2 (2013/05/17) 正規表現修正、判定条件変更
			// ① gamenId="jsp"  + index.jsp       + GAMENID=XXXX  ⇒ saveDir + "jsp/indexXXXX.htm"         Matrixメニューからの画面呼出し。
			// ④ gamenId="menu" + matrixMenu.jsp  + group=YYYY    ⇒ saveDir + "menu/matrixMenuYYYY.htm"   Matrixメニューのグループ選択
			if( file.indexOf( "matrixMenu" ) >= 0 ) {
				// 6.4.3.2 (2016/02/19) ../画面ID/index.htm の場合の対応
				// 6.4.3.2 (2016/02/19) 従来の../index.htm では、/ がひとつなので、注意。先頭の ".. にしておかないと、前方のタグまで食ってしまう。
				rtnStr = rtnStr.replaceAll( "\"../[^/]*[/]*index.htm\\?[^\"]*GAMENID=([^&\"]*)[^\"]*\"","\"../jsp/index$1.htm\"" );	// ①
				rtnStr = rtnStr.replaceAll( "matrixMenu.htm\\?[^\"]*group=([^&\"]*)[^\"]*\"","matrixMenu$1.htm\"" );	// ④
				rtnStr = rtnStr.replaceAll( "=\"../../../mr/jsp/","=\"../" );
			}
			// 6.3.8.4 (2015/10/09) セーブフォルダを、URIではなく、画面IDから取得する。
			// ① のケースの、jsp/indexXXXX.htm ファイルの内容(frame)のフォルダ名を画面IDに変更
			else if( file.indexOf( "jsp/index" ) >= 0 ) {
				rtnStr = rtnStr.replaceAll( "frame src=\"../[^\"]*GAMENID=([^&\"]*)[^\"]*\"","frame src=\"../$1/index.htm\"" );		// ①
			}
			// ② gamenId="jsp"  + result.jsp      + GAMENID=XXXX  ⇒ saveDir + "XXXX/index.htm"            画面QUERYのヘッダーメニュー
			else if( file.indexOf( "query" ) >= 0 ) {
				rtnStr = rtnStr.replaceAll( "../result.htm\\?[^\"]*GAMENID=([^&\"]*)[^\"]*\"","../jsp/index$1.htm\"" );	// ②
			}
			// ③ gamenId="menu" + multiMenu.jsp   + group=YYYY    ⇒ saveDir + "jsp/menuYYYY.htm"          通常メニューのグループ選択
			else if( file.indexOf( "multiMenu" ) >= 0 || file.indexOf( "menu" ) >= 0 || file.indexOf( "normalMenu" ) >= 0 ) {
				rtnStr = rtnStr.replaceAll( "multiMenu.htm\\?[^\"]*group=([^&\"]*)[^\"]*\"","menu$1.htm\"" );			// ③
			}
			return rtnStr;
		}

		/**
		 * このオブジェクトの文字列表現
		 * "IndexMatrixMenuData()" を返します。
		 *
		 * @return	文字列表現
		 * @og.rtnNotNull
		 */
		@Override
		public String toString() {
			return "IndexMatrixMenuData()" ;
		}
	}

	/**
	 * index.htm のコマンド単位のファイル名の置き換えクラスです。
	 * このクラスは、不変クラスのため、マルチスレッドでの同時使用に対して、安全です。
	 *
	 */
	private static final class IndexChangeData extends ChangeData {
		// <a href="aaaa/index.htm?command=RENEW&GAMENID=bbbb 形式とマッチし、index.htm 部分を前方参照します。
		private static final Pattern PTN1 = Pattern.compile( "index.htm\\?[^\"]*command=(NEW|RENEW)" );

		/**
		 * 実際に変換を行うメソッド。
		 *
		 * @param	file	対象ファイル名
		 * @param	inStr	対象データ
		 * @return	変換後データ
		 */
		@Override
		public String replace( final String file,final String inStr ) {
			String rtnStr = inStr;
	//		if( file.indexOf( "query" ) >= 0 ) {		// query の場合
				final Matcher mch = PTN1.matcher( rtnStr );
				int adrs = 0;
				while( mch.find( adrs ) ) {
					final int indx = mch.start() ;
					final String cmd = mch.group(1);		// command=(NEW|RENEW) 部分の()内文字列
					// index.htm 文字列部に、NW または RNW を追加し、indexNW.html を作成する。
					if( "NEW".equalsIgnoreCase( cmd ) ) {
						rtnStr = rtnStr.substring(0,indx+5) + "NW" + rtnStr.substring(indx+5) ;
					}
					else if( "RENEW".equalsIgnoreCase( cmd ) ) {
						rtnStr = rtnStr.substring(0,indx+5) + "RNW" + rtnStr.substring(indx+5) ;
					}
					adrs = mch.end() ;
					mch.reset( rtnStr );
				}
	//		}
			return rtnStr;
		}

		/**
		 * このオブジェクトの文字列表現
		 * "IndexChangeData()" を返します。
		 *
		 * @return	文字列表現
		 * @og.rtnNotNull
		 */
		@Override
		public String toString() {
			return "IndexChangeData()" ;
		}
	}

	/**
	 * コマンド転送先を、onClick="location.href=XXX" で指定するように、変換します。
	 * <input type="hidden" name="hX_複写(C)" value="copy.htm" > を見つけ、
	 * 前方参照で、複写(C) と、copy.htm を取り出します。
	 * その後、<input name="command" value="複写(C)" という文字列をキーに、
	 * <input name="command" onClick="location.href='copy.htm'" value="複写(C)" という
	 * 文字列に置き換えます。
	 * これにより、ボタンを押したときに、ボタンごとに異なる画面に遷移します。
	 * 前提条件として、下記の項目を満たしておく必要がある。
	 *   ・form には、onSubmit="return false;" を記述し、フォームを無効化しておく。
	 *   ・input タグの type="submit" を、type="button" にしておく。(ボタンイベント)
	 *   ・query.htm 以外のファイルのみ適用。location.href では、フレームのtarget指定
	 *     まで行っていない。
	 *   ・上と同意で、query.htm の登録時処理は、別に行う。
	 *
	 * @og.rev 5.5.2.5 (2012/05/21) update.jsp に出力されるファイルを、コマンド名.htm に出力するように機能追加
	 * @og.rev 5.6.4.2 (2013/05/17) ３ペイン、エントリなど、特殊な画面にフラグを付けます。(TARGET_CHANGE_SET)
	 * @og.rev 6.2.5.0 (2015/06/05) GMIS V6 対応(location.href時に、type="submit" を type="button" に変更)
	 */
	private static final class HrefChangeData extends ChangeData {
		private static final String PTN1 = "<input type=\"hidden\" name=\"hX_([^\"]*)\" value=\"([^\"]*.htm)" ;
		private static final Pattern PTN_OBJ1 = Pattern.compile( PTN1 );											// 6.4.1.1 (2016/01/16) ptnObj1 → PTN_OBJ1 refactoring

		// 5.5.7.2 (2012/10/09) 定数名の変更
		private static final String ORG  = "<input name=\"command\"" ;
		private static final String SELF = "<input name=\"command\" onClick=\"location.href='" ;
		private static final String PRNT = "<input name=\"command\" onClick=\"parent.location.href='" ;
		private static final String TOP  = "<input name=\"command\" onClick=\"top.location.href='" ;

		// 5.5.7.2 (2012/10/09) formのtargetを取得。location.href に利用する。
		private static final String PTN2 = "<form .*target=\"([^\"]*)\"" ;
		private static final Pattern PTN_OBJ2 = Pattern.compile( PTN2 );											// 6.4.1.1 (2016/01/16) ptnObj2 → PTN_OBJ2 refactoring

		// 6.2.5.0 (2015/06/05) GMIS V6 対応(location.href時に、type="submit" を type="button" に変更)
		private static final String LOC_HREF  = "location.href" ;
		private static final String TP_SUBMIT = "type=\"submit\"" ;
		private static final String TP_BUTTON = "type=\"button\"" ;

		/**
		 * コマンド転送先を、onClick="location.href=XXX" で指定するように、変換します。
		 * <input type="hidden" name="hX_複写(C)" value="copy.htm" > を見つけ、
		 * 前方参照で、複写(C) と、copy.htm を取り出します。
		 * その後、<input name="command" value="複写(C)" という文字列をキーに、
		 * <input name="command" onClick="location.href='copy.htm'" value="複写(C)" という
		 * 文字列に置き換えます。
		 *
		 * @og.rev 5.5.2.5 (2012/05/21) update.jsp に出力されるファイルを、コマンド名.htm に出力するように機能追加
		 * @og.rev 5.5.7.2 (2012/10/09) 定数名の変更。formのtargetを加味した、location.href を作成する。
		 * @og.rev 5.6.4.2 (2013/05/17) ３ペイン、エントリなど、特殊な画面にフラグを付けます。(TARGET_CHANGE_SET)
		 * @og.rev 6.2.5.0 (2015/06/05) query*** 以外
		 * @og.rev 6.3.8.0 (2015/09/11) idxEnd = -1 の場合、無限ループになる。(javadoc上では -1 のハズ)
		 * @og.rev 7.0.1.0 (2018/10/15) XHTML → HTML5 対応(空要素の、"／＞" 止めを、"＞" に変更します)。
		 *
		 * @param	file	対象ファイル名
		 * @param	inStr	対象データ
		 * @return	変換後データ
		 */
		@Override
		public String replace( final String file,final String inStr ) {
			String rtnStr = inStr;
			// 6.2.5.0 (2015/06/05) query.htm , queryNW.htm , query1.htm 以外
			if( file.indexOf( "query" ) < 0 ) {			// 6.2.5.0 (2015/06/05) query*** 以外
				// 5.5.7.2 (2012/10/09) formのtargetを加味した、location.href を作成する。
				final Matcher mch2 = PTN_OBJ2.matcher( rtnStr );
				String ptnHref = SELF;			// 標準は、location.href
				if( mch2.find() ) {
					// 5.6.4.2 (2013/05/17) ３ペイン、エントリなど、特殊な画面にフラグを付けます。(TARGET_CHANGE_SET)
					final int indx = file.lastIndexOf( '/' );
					final String fileKey = file.substring( 0,indx );

					final String frmTgt = mch2.group(1);
					if( "CONTENTS".equals( frmTgt ) )  { ptnHref = PRNT; }
					else if( "_top".equals( frmTgt ) ) { ptnHref = TOP;  }
					// 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..;
					else if( "RESULT".equals( frmTgt ) || frmTgt == null ) {
						// 5.6.4.2 (2013/05/17) ある画面で、特殊なターゲット(INPUT,BUTTOMなど)を使用している場合のチェック
						if( TARGET_CHANGE_SET.contains( fileKey ) ) {
							ptnHref = PRNT ;
						}
					}
					else {
						ptnHref = "<input name=\"command\" onClick=\"parent." + frmTgt + ".location.href='" ;
						// 5.6.4.2 (2013/05/17) ある画面で、特殊なターゲット(INPUT,BUTTOMなど)を使用している場合に記憶
						TARGET_CHANGE_SET.add( fileKey );		// 別のファイルを処理するときに参照する。
					}
				}

				final Matcher mch = PTN_OBJ1.matcher( rtnStr );
				int adrs = 0;
				while( mch.find( adrs ) ) {
					final String cmd = mch.group(1);
					if( !cmd.endsWith( "CMD" ) ) {
						final String val = mch.group(2);
						final String str1 = ORG + " value=\"" + cmd ;
						String str2 ;

						if( val != null && val.startsWith( "../" ) ) {
							str2 = PRNT + val + "'\" value=\"" + cmd ;
						}
						// 5.5.2.5 (2012/05/21) update.jsp に出力されるファイルを、コマンド名.htm に出力するように機能追加
						else if( val != null && val.startsWith( "update" ) ) {
							str2 = ptnHref + cmd + ".htm'\" value=\"" + cmd ;
						}
						else {
							str2 = ptnHref + val + "'\" value=\"" + cmd ;
						}
						rtnStr = rtnStr.replace( str1,str2 );
					}
					adrs = mch.end();
					mch.reset( rtnStr );
				}

				// 6.2.5.0 (2015/06/05) GMIS V6 対応(location.href時に、type="submit" を type="button" に変更)
				// 正規表現の置き換え箇所が、難しそうなので、もう一度初めから処理します。
//				// location.href① … type="submit"② … />③ の関係チェック
				// location.href① … type="submit"② … >③ の関係チェック
				final StringBuilder buf = new StringBuilder( rtnStr );

				int idxHref = buf.indexOf( LOC_HREF );						// ①
				while( idxHref >= 0 ) {
					final int idxType = buf.indexOf( TP_SUBMIT , idxHref );	// ②
//					final int idxEnd  = buf.indexOf( "/>"      , idxHref );	// ③
					final int idxEnd  = buf.indexOf( ">"       , idxHref );	// ③ 7.0.1.0 (2018/10/15)
					if( idxType >= 0 && idxEnd >= 0 && idxType < idxEnd ) {
						buf.replace( idxType , idxType + TP_SUBMIT.length() , TP_BUTTON );	// 範囲置換
					}
					// 6.3.8.0 (2015/09/11) idxEnd = -1 の場合、無限ループになる。(javadoc上では -1 のハズ)
					idxHref = buf.indexOf( LOC_HREF , Math.max( idxEnd , idxHref + LOC_HREF.length() ) );
				}
				rtnStr = buf.toString();
			}
			return rtnStr;
		}

		/**
		 * このオブジェクトの文字列表現
		 * "HrefChangeData()" を返します。
		 *
		 * @return	文字列表現
		 * @og.rtnNotNull
		 */
		@Override
		public String toString() {
			return "HrefChangeData()" ;
		}
	}

	/**
	 * 雛形自動作成 で、useAjaxSubmit="true" の対策
	 *
	 * update.jsp で、useAjaxSubmit="true" の場合、entry.htm は、update.jsp の
	 * JavaScriptでforward されるため、雛形には、HTMLの結果は出力されません。
	 * (result.jsp に出力されます。)
	 * そこで、雛形作成時には、entry.htm にJavaScriptを入れて、forward させます。
	 * JavaScript の挿入位置は、 BODYの終了タグがあれば、その直前に、なければ、最後に追加します。
	 *
	 * ※ 6.2.5.0 (2015/06/05)
	 *    本当は、ForwardTag#doEndTag() の noTransitionUrl を出力している箇所に入れれば
	 *    一番きれいだが、各種各画面で使用しているタグなので、あまり変更したくないのと、
	 *    style 属性で設定すると、画面が動作しなくなったため、何か問題が起こりそうなので、
	 *    紙芝居作成時のみ対応することとします。
	 *    非表示(display:none;)は、<div id="noTransitionUrl"> の直前にします。
	 *
	 * @og.rev 5.6.3.4 (2013/04/26) entry.htm に "noTransitionUrl" が存在するときの処理。
	 * @og.rev 6.2.5.0 (2015/06/05) GMIS V6 対応(noTransitionUrl,noTransitionTargetを非表示にする)
	 */
	private static final class NoTranHrefChangeData extends ChangeData {
		// 6.2.5.0 (2015/06/05) GMIS V6 対応(noTransitionUrl,noTransitionTargetを非表示にする)
		private static final String DIV_TAG      = "<div id=\"noTransitionUrl\">" ;
		private static final String DISPLAY_NONE = "<style type=\"text/css\">#noTransitionUrl,#noTransitionTarget{display:none;}</style>";

		private static final String BODY_END   = "</body>" ;
		private static final String APPEND_JS  = "<script type=\"text/javascript\" src=\"../common/option/noTranHref.js\" ><!-- --></script>" ;

		/**
		 * 実際に変換を行うメソッド。
		 *
		 * @param	file	対象ファイル名
		 * @param	inStr	対象データ
		 * @return	変換後データ
		 */
		@Override
		public String replace( final String file,final String inStr ) {
			String rtnStr = inStr;
			// entry.jsp で、かつ noTransitionUrl 文字列を含む場合のみ
			if( file.indexOf( "entry" ) >= 0 && inStr.indexOf( "noTransitionUrl" ) >= 0 ) {
				// 6.2.5.0 (2015/06/05) GMIS V6 対応
				final StringBuilder buf = new StringBuilder( rtnStr );
				final int divAd = buf.indexOf( DIV_TAG );
				if( divAd >= 0 ) { buf.insert( divAd,DISPLAY_NONE ); }

				final int adrs = buf.indexOf( BODY_END );
				if( adrs >= 0 ) { buf.insert( adrs,APPEND_JS ); }	// </body> タグが存在した場合は、その直前に挿入する。
				else            { buf.append( APPEND_JS ); }		// 存在しない場合は、最後に挿入する。

				rtnStr = buf.toString();
			}
			return rtnStr;
		}

		/**
		 * このオブジェクトの文字列表現
		 * "NoTranHrefChangeData()" を返します。
		 *
		 * @return	文字列表現
		 * @og.rtnNotNull
		 */
		@Override
		public String toString() {
			return "NoTranHrefChangeData()" ;
		}
	}

	/**
	 * 雛形自動作成 で、fileDownload の日本語名対応
	 *
	 * 標準的な、fileDownload 処理では、../common/fileDownload.jsp?・・・・GAMENID=XXXX&filename=YYYY" と
	 * なっており、filename 部分は、日本語にも対応できるように urlEncode されています。
	 * これを、元に戻さないとうまくダウンロードできませんでした。
	 *
	 * ※ 参考情報
	 *  1.urlEncode のままで、ファイル名を取得する場合は、下記の標準系で対応可能です。
	 *		,new ChangeData( null ,"../common/fileDownload.htm\\?[^\"]*filename=([^&\"]*)[^\"]*\"","$1\"" )
	 *  2.ファイル名を、fileDownload.xls 固定にする場合は、下記の標準系で対応可能です。
	 *		,new ChangeData( null ,"../common/fileDownload.htm\\?[^\"]*\"","fileDownload.xls\"" )
	 *
	 * @og.rev 5.6.4.2 (2013/05/17) 新規追加
	 */
	private static final class FileDownloadChangeData extends ChangeData {
		private static final String PTN1 = "../common/fileDownload.htm\\?[^\"]*filename=([^&\"]*)[^\"]*\"" ;
		private static final Pattern PTN_OBJ1 = Pattern.compile( PTN1 );											// 6.4.1.1 (2016/01/16) ptnObj1 → PTN_OBJ1 refactoring

		/**
		 * 実際に変換を行うメソッド。
		 *
		 * @param	file	対象ファイル名
		 * @param	inStr	対象データ
		 * @return	変換後データ
		 */
		@Override
		public String replace( final String file,final String inStr ) {
			String rtnStr = inStr;
			// 対象データから、fileDownload.htm を含む場合に処理を実行します。
			if( rtnStr.indexOf( "../common/fileDownload.htm" ) >= 0 ) {
				final Matcher mch = PTN_OBJ1.matcher( rtnStr );
				int adrs = 0;
				while( mch.find( adrs ) ) {
					String fname = mch.group(1);
					fname = StringUtil.urlDecode( fname );		// デコードしています。

					final int indx = mch.start() ;
					adrs = mch.end();
					rtnStr = rtnStr.substring( 0,indx ) + fname + "\"" + rtnStr.substring( adrs ) ;

					mch.reset( rtnStr );
				}
			}
			return rtnStr;
		}

		/**
		 * このオブジェクトの文字列表現
		 * "FileDownloadChangeData()" を返します。
		 *
		 * @return	文字列表現
		 * @og.rtnNotNull
		 */
		@Override
		public String toString() {
			return "FileDownloadChangeData()" ;
		}
	}

	/**
	 * 09_「DIE0005_金型保守_チェック処理ボタン動かない_北林」対応
	 *
	 * @og.rev 6.9.9.2 (2018/09/18) 新規追加
	 * @og.rev 7.0.1.0 (2018/10/15) XHTML → HTML5 対応(空要素の、"／＞" 止めを、"＞" に変更します)。
	 */
	private static final class QuerySubmitChange extends ChangeData{
//		private static final String PTN1 = "<input type=\"hidden\" name=\"" + HybsSystem.NO_XFER_KEY + "(.*)?\" value=\"(.*?\\.htm)\".*?/>" ;
		private static final String PTN1 = "<input type=\"hidden\" name=\"" + HybsSystem.NO_XFER_KEY + "(.*)?\" value=\"(.*?\\.htm)\".*?>" ;
		private static final Pattern PTN_OBJ1 = Pattern.compile( PTN1 );											// 6.4.1.1 (2016/01/16) ptnObj1 → PTN_OBJ1 refactoring

		/**
		 * 実際に変換を行うメソッド。
		 * RESULTをターゲットとした画面のみを考慮している。
		 *
		 * @og.rev 7.0.1.0 (2018/10/15) XHTML → HTML5 対応(空要素の、"／＞" 止めを、"＞" に変更します)。
		 *
		 * @param	file	対象ファイル名
		 * @param	inStr	対象データ
		 * @return	変換後データ
		 */
		@Override
		public String replace( final String file,final String inStr ) {
			String rtnStr = inStr;

			// query.htmの場合の処理
			if( file.indexOf( "query.htm" ) >= 0 ) {
				// hX_XXXのinput type="hidden"のデータを検索
	//			String regex = "<input type=\"hidden\" name=\"" + HybsSystem.NO_XFER_KEY + "(.*)?\" value=\"(.*?\\.htm)\".*?/>";
	//			Pattern p = Pattern.compile(regex);
	//			Matcher m = p.matcher(rtnStr);
				final Matcher mch = PTN_OBJ1.matcher( rtnStr );

				// 存在する場合は input type="submit"のデータを検索して、
				// 存在する場合はボタンに変換する。
	//			while(m.find()) {
				while( mch.find() ) {
	//				String name = m.group(1);			// 名称
	//				String value = m.group(2);			// htm名
					final String name  = mch.group(1);		// 名称
					final String value = mch.group(2);		// htm名

//					final String regex2 = "<input name=\"command\" value=\"" + name + "\" type=\"submit\"  />";
					final String regex2 = "<input name=\"command\" value=\"" + name + "\" type=\"submit\" >";
//					final String af     = "<input type=\"button\" onClick=\"parent.RESULT.location.href='" + value + "';\" value=\"" + name + "\"/>";
					final String af     = "<input type=\"button\" onClick=\"parent.RESULT.location.href='" + value + "';\" value=\"" + name + "\">";

					rtnStr = rtnStr.replaceFirst( regex2, af );
				}
			}

			return rtnStr;
		}
	}
}
