/*
 * 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.fukurou.xml;

import org.opengion.fukurou.system.OgRuntimeException ;					// 6.4.2.0 (2016/01/29)
import org.opengion.fukurou.system.Closer ;
import org.opengion.fukurou.util.FileUtil ;
import static org.opengion.fukurou.system.HybsConst.CR;					// 6.1.0.0 (2014/12/26) refactoring
import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE;		// 6.4.2.1 (2016/02/05) refactoring

import java.io.PrintWriter ;
// import java.io.BufferedWriter ;
// import java.io.OutputStreamWriter ;
// import java.io.FileOutputStream ;
import java.io.IOException ;
import java.io.File;
import java.io.StringReader ;
// import java.io.FileNotFoundException ;
// import java.io.UnsupportedEncodingException;
import java.util.Stack;
import java.util.List;
import java.util.ArrayList;
// import java.util.Map;
import java.util.concurrent.ConcurrentMap;							// 6.4.3.3 (2016/03/04)
// import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;						// 6.4.3.1 (2016/02/12) refactoring

import org.xml.sax.Attributes;
import org.xml.sax.ext.DefaultHandler2;
import org.xml.sax.InputSource ;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.ParserConfigurationException;

/**
 * JSP/XMLファイルを読み取って、OGNode/OGElement オブジェクトを取得する、パーサークラスです。
 *
 * 自分自身が、DefaultHandler2 を拡張していますので、パーサー本体になります。
 * javax.xml.parsers および、org.w3c.dom の簡易処理を行います。
 * read で、トップレベルの OGNode を読み込み、write で、ファイルに書き出します。
 * 通常の W3C 系の オブジェクトを利用しないのは、属性の並び順を保障するためです。
 * ただし、属性のタブ、改行は失われます。
 * また、属性値に含まれるCR(復帰), LF(改行), TAB(タブ)は、 半角スペースに置き換えられます。
 * これは、SAXParser 側での XML の仕様の関係で、属性は、正規化されるためです。
 *
 * @og.rev 5.1.8.0 (2010/07/01) 新規作成
 * @og.rev 5.1.9.0 (2010/08/01) static メソッドを廃止。通常のオブジェクトクラスとして扱います。
 *
 * @version  5.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK6.0,
 */
public class JspSaxParser extends DefaultHandler2 {

	private final List<JspParserFilter> filters = new ArrayList<>();	// 5.1.9.0 (2010/08/01)
	private SAXParser parser	;

	// 以下、パース時に使用する変数。(パース毎に初期化する。)
	/** 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。  */
//	private Map<String,OGElement> idMap	;					// 5.1.9.0 (2010/08/01)
	private ConcurrentMap<String,OGElement> idMap	;		// 6.4.3.3 (2016/03/04)
	private Stack<OGNode>		  stack	;

	private OGNode  ele				;	// 現時点のエレメントノード
	private boolean inCDATA			;	// CDATA エレメントの中かどうかの判定
	private boolean inEntity		;	// Entity の中かどうかの判定
	private String  filename		;	// 処理実行中のファイル名

	/**
	 * XMLファイルを読み込み、OGDocument を返します。
	 *
	 * 内部的には、SAXParserFactory から、SAXParser を構築し、Property に、
	 * http://xml.org/sax/properties/lexical-handler を設定しています。
	 * コメントノードを処理するためです。
	 *
	 * @og.rev 5.1.9.0 (2010/08/01) static からノーマルに変更
	 *
	 * @param	aFile	XMLファイル
	 *
	 * @return	ファイルから読み取って構築したOGDocumentオブジェクト
	 * @og.rtnNotNull
	 */
	public OGDocument read( final File aFile ) {
		filename = aFile.getAbsolutePath() ;

		try {
			if( parser == null ) {
				// SAXパーサーファクトリを生成
				final SAXParserFactory spfactory = SAXParserFactory.newInstance();

				// SAXパーサーを生成
				parser = spfactory.newSAXParser();

				parser.setProperty("http://xml.org/sax/properties/lexical-handler", this);	// LexicalHandler として
			}
			// XMLファイルを指定されたハンドラーで処理します
			parser.parse( aFile, this );

		} catch( final ParserConfigurationException ex ) {
			final String errMsg = "重大な構成エラーが発生しました。"
					+ CR + "\t" + ex.getMessage()
					+ CR + "\t" + aFile ;
			throw new OgRuntimeException( errMsg,ex );
	//	5.1.9.0 (2010/08/01) 廃止
	//	} catch( final SAXNotRecognizedException ex ) {
	//	final String errMsg = "XMLReader は、認識されない機能またはプロパティー識別子を検出しました。"
	//				+ CR + "\t" + ex.getMessage()
	//				+ CR + "\t" + aFile ;
	//		if( ex2 != null ) { errMsg = errMsg + CR + "\t" + ex2.getMessage(); }
	//		throw new OgRuntimeException( errMsg,ex );
	//	} catch( final SAXNotSupportedException ex ) {
	//	final String errMsg = "XMLReader は、要求された操作 (状態または値の設定) を実行できませんでした。"
	//				+ CR + "\t" + ex.getMessage()
	//				+ CR + "\t" + aFile ;
	//		if( ex2 != null ) { errMsg = errMsg + CR + "\t" + ex2.getMessage(); }
	//		throw new OgRuntimeException( errMsg,ex );
		} catch( final SAXException ex ) {
			String errMsg = "SAX の一般的なエラーが発生しました。"
					+ CR + "\t" + ex.getMessage()
					+ CR + "\t" + aFile ;
			final Exception ex2 = ex.getException();
			if( ex2 != null ) { errMsg = errMsg + CR + "\t" + ex2.getMessage(); }
			throw new OgRuntimeException( errMsg,ex );
		} catch( final IOException ex ) {
			final String errMsg = "ファイル読取時にエラーが発生しました。"
					+ CR + "\t" + ex.getMessage()
					+ CR + "\t" + aFile ;
			throw new OgRuntimeException( errMsg,ex );
	//	5.1.9.0 (2010/08/01) 廃止
	//	} catch( final RuntimeException ex ) {
	//	final String errMsg = "実行時エラーが発生しました。"
	//				+ CR + "\t" + ex.getMessage()
	//				+ CR + "\t" + aFile ;
	//		throw new OgRuntimeException( errMsg,ex );
		}

		return getDocument() ;
	}

	/**
	 * XML形式で表現された、文字列(String) から、OGDocument を構築します。
	 *
	 * 処理的には、#read( File ) と同じで、取り出す元が、文字列というだけです。
	 * XMLファイルからの読み込みと異なり、通常は、Element を表現した文字列が作成されますが、
	 * 返されるのは、OGDocument オブジェクトです。
	 *
	 * @og.rev 5.1.9.0 (2010/08/01) static からノーマルに変更
	 *
	 * @param	str	XML形式で表現された文字列
	 *
	 * @return	ファイルから読み取って構築した OGDocumentオブジェクト
	 * @og.rtnNotNull
	 */
	public OGDocument string2Node( final String str ) {
		filename = null ;

		try {
			if( parser == null ) {
				// SAXパーサーファクトリを生成
				final SAXParserFactory spfactory = SAXParserFactory.newInstance();
				// SAXパーサーを生成
				parser = spfactory.newSAXParser();

				parser.setProperty("http://xml.org/sax/properties/lexical-handler", this);	// LexicalHandler として
			}

			// XMLファイルを指定されたデフォルトハンドラーで処理します
			final InputSource source = new InputSource( new StringReader( str ) );
			parser.parse( source, this );

		} catch( final ParserConfigurationException ex ) {
			final String errMsg = "重大な構成エラーが発生しました。"
					+ CR + ex.getMessage();
			throw new OgRuntimeException( errMsg,ex );
	//	5.1.9.0 (2010/08/01) 廃止
	//	} catch( final SAXNotRecognizedException ex ) {
	//	final String errMsg = "XMLReader は、認識されない機能またはプロパティー識別子を検出しました。"
	//				+ CR + ex.getMessage();
	//		Exception ex2 = ex.getException();
	//		if( ex2 != null ) { errMsg = errMsg + CR + "\t" + ex2.getMessage(); }
	//		throw new OgRuntimeException( errMsg,ex );
		} catch( final SAXException ex ) {
			final String errMsg = "SAX の一般的なエラーが発生しました。"
					+ CR + ex.getMessage();
	//		final Exception ex2 = ex.getException();
	//		if( ex2 != null ) { errMsg = errMsg + CR + "\t" + ex2.getMessage(); }
			throw new OgRuntimeException( errMsg,ex );
		} catch( final IOException ex ) {
			final String errMsg = "ストリームオブジェクト作成時にエラーが発生しました。"
					+ CR + ex.getMessage();
			throw new OgRuntimeException( errMsg,ex );
	//	5.1.9.0 (2010/08/01) 廃止
	//	} catch( final RuntimeException ex ) {
	//	final String errMsg = "実行時エラーが発生しました。"
	//				+ CR + ex.getMessage();
	//		throw new OgRuntimeException( errMsg,ex );
		}

		return getDocument() ;
	}

	/**
	 * OGDocument を所定のファイルに、XML形式で書き出します。
	 *
	 * @og.rev 6.3.8.0 (2015/09/11) FileUtil#getPrintWriter( File,String ) を使用。
	 *
	 * @param	aFile	書き出すファイル
	 * @param	node	書き出す OGDocument
	 */
	public void write( final File aFile, final OGDocument node ) {
		PrintWriter	 out	= null;
		final String encode = node.getEncode();
		try {
			// 6.3.8.0 (2015/09/11) FileUtil#getPrintWriter( File,String ) を使用。
//			out = new PrintWriter( new BufferedWriter( new OutputStreamWriter( new FileOutputStream(aFile),encode )));
			out = FileUtil.getPrintWriter( aFile,encode ) ;		// 6.3.8.0 (2015/09/11)
			out.println( node.toString() );
		}
//		// 6.3.8.0 (2015/09/11) FileUtil#getPrintWriter( File,String ) を使用したため、エラーが発生しなくなった。
//		catch( final FileNotFoundException ex ) {
//			final String errMsg = "指定されたパス名で示されるファイルが存在しませんでした。"
//					+ CR + "\t" + ex.getMessage()
//					+ CR + "\t" + aFile ;
//			throw new OgRuntimeException( errMsg,ex );
//		}
//		catch( final UnsupportedEncodingException ex ) {
//			final String errMsg = "文字のエンコーディング(" + encode + ")がサポートされていません。"
//					+ CR + "\t" + ex.getMessage()
//					+ CR + "\t" + aFile ;
//			throw new OgRuntimeException( errMsg,ex );
//		}
		//	5.1.9.0 (2010/08/01) 廃止。 6.3.8.0 (2015/09/11) 復活
		catch( final RuntimeException ex ) {
			final String errMsg = "実行時エラーが発生しました。"	+ CR
					+ "\t " + ex.getMessage()						+ CR
					+ "\t File=["   + aFile	+ ']'					+ CR
					+ "\t Encode=[" + encode	+ ']' ;
			throw new OgRuntimeException( errMsg,ex );
		}
		finally {
			Closer.ioClose( out );
		}
	}

	/**
	 * ディレクトリの再帰処理でパース処理を行います。
	 *
	 * @og.rev 5.1.9.0 (2010/08/01) static からノーマルに変更
	 *
	 * @param	fromFile	読み取りもとのファイル/フォルダ
	 * @param	toFile	書き込み先のファイル/フォルダ
	 */
	public void copyDirectry( final File fromFile, final File toFile ) {
		// コピー元がファイルの場合はコピーして、終了する。
		if( fromFile.exists() && fromFile.isFile() ) {
			boolean isOK = false;
			final String name = fromFile.getName();
			if( name.endsWith( ".jsp" ) || name.endsWith( ".xml" ) ) {
				try {
					OGDocument doc = read( fromFile );
					if( doc != null && !filters.isEmpty() ) {
						for( final JspParserFilter filter: filters ) {
							doc = filter.filter( doc );
							if( doc == null ) { break; }	// エラー、または処理の中止
						}
					}
					if( doc != null ) {
						write( toFile,doc );
						isOK = true;
					}
				}
				catch( final RuntimeException ex ) {
			//		ex.printStackTrace();
					System.out.println( ex.getMessage() );
				}
			}

			// JSPやXMLでない、パースエラー、書き出しエラーなど正常終了できなかった場合は、バイナリコピー
			if( !isOK ) {
				FileUtil.copy( fromFile,toFile,true );
			}
			return ;
		}

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

		// ディレクトリ内のファイルをすべて取得する
		final File[] files = fromFile.listFiles();

		// ディレクトリ内のファイルに対しコピー処理を行う
		// 6.3.9.0 (2015/11/06) null になっている可能性がある(findbugs)
		if( files != null ) {
			for( final File file : files ) {
				copyDirectry( file, new File( toFile, file.getName()) );
			}
		}
//		for( int i=0; i<files.length; i++ ){
//			copyDirectry( files[i], new File( toFile, files[i].getName()) );
//		}
	}

	/**
	 * copyDirectry 処理で、OGDocument をフィルター処理するオブジェクトを登録します。
	 *
	 * 内部リストへフィルターを追加します。
	 * フィルター処理は、追加された順に行われます。
	 * 内部リストへの追加はできますが、削除はできません。
	 *
	 * @og.rev 5.1.9.0 (2010/08/01) 新規追加
	 *
	 * @param	filter	フィルターオブジェクト
	 */
	public void addFilter( final JspParserFilter filter ) {
		filters.add( filter );
	}

	// ********************************************************************************************** //
	// **                                                                                          ** //
	// ** ここから下は、DefaultHandler2 の実装になります。                                         ** //
	// **                                                                                          ** //
	// ********************************************************************************************** //

	/**
	 * 文書の開始通知を受け取ります。
	 *
	 * インタフェース ContentHandler 内の startDocument
	 *
	 * @og.rev 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。
	 *
	 * @see org.xml.sax.helpers.DefaultHandler#startDocument()
	 * @see org.xml.sax.ContentHandler#startDocument()
	 */
	@Override
	public void startDocument() {
		stack	= new Stack<>();
		ele		= new OGDocument();
		((OGDocument)ele).setFilename( filename );

//		idMap	= new HashMap<>();				// 5.1.9.0 (2010/08/01) 追加
		idMap	= new ConcurrentHashMap<>();	// 6.4.3.1 (2016/02/12)

		inCDATA  = false;	// CDATA エレメントの中かどうかの判定
		inEntity = false;	// Entity の中かどうかの判定
	}

	/**
	 * 要素の開始通知を受け取ります。
	 *
	 * インタフェース ContentHandler 内の startElement
	 *
	 * @param	uri			名前空間 ＵＲＩ。要素が名前空間 ＵＲＩ を持たない場合、または名前空間処理が実行されない場合は null
	 * @param	localName	前置修飾子を含まないローカル名。名前空間処理が行われない場合は空文字列
	 * @param	qName		接頭辞を持つ修飾名。修飾名を使用できない場合は空文字列
	 * @param	attributes	要素に付加された属性。属性が存在しない場合、空の Attributesオブジェクト
	 *
	 * @see org.xml.sax.helpers.DefaultHandler#startElement(String,String,String,Attributes)
	 * @see org.xml.sax.ContentHandler#startElement(String,String,String,Attributes)
	 */
	@Override
	public void startElement( final String uri, final String localName, final String qName, final Attributes attributes ) {
		final OGElement newEle = new OGElement( qName,attributes );
		final String id = newEle.getId();
		if( id != null ) { idMap.put( id,newEle ); }		// 5.1.9.0 (2010/08/01) idをMapにキャッシュ

		ele.addNode( newEle );
		stack.push( ele );
		ele = newEle ;
	}

	/**
	 * 要素内の文字データの通知を受け取ります。
	 *
	 * エンティティー内かどうかを判断する、inEntity フラグが true の間は、
	 * 何も処理しません。
	 *
	 * インタフェース ContentHandler 内の characters
	 *
	 * @param	cbuf	文字データ配列
	 * @param	off		文字配列内の開始位置
	 * @param	len		文字配列から使用される文字数
	 *
	 * @see org.xml.sax.helpers.DefaultHandler#characters(char[],int,int)
	 * @see org.xml.sax.ContentHandler#characters(char[],int,int)
	 */
	@Override
	public void characters( final char[] cbuf, final int off, final int len ) {
		if( inEntity ) { return ; }		// &lt; ⇒ < に変換されるので、エンティティ内では、なにも処理しない。

		final String text = toText( cbuf,off,len );
		if( inCDATA ) {
			ele.addNode( text );
			return ;
		}

		final OGNode node = new OGNode( text );
		ele.addNode( node );

		// 6.0.2.5 (2014/10/31) refactoring 読み出されないフィールド:attTab
		// '\r'(CR:復帰)+ '\n'(LF:改行)の可能性があるが、 '\n'(LF:改行)が、より後ろにあるので、これで判定。
	}

	/**
	 * CDATA セクションの開始を報告します。
	 *
	 * CDATA セクションのコンテンツは、正規の characters イベントを介して報告されます。
	 * このイベントは境界の報告だけに使用されます。
	 *
	 * インタフェース LexicalHandler 内の startCDATA
	 *
	 * @see org.xml.sax.ext.DefaultHandler2#startCDATA()
	 * @see org.xml.sax.ext.LexicalHandler#startCDATA()
	 */
	@Override
	public void startCDATA() {
		final OGNode node = new OGNode();
		node.setNodeType( OGNodeType.Cdata );

		ele.addNode( node );
		stack.push( ele );
		ele = node ;
		inCDATA = true;
	}

	/**
	 * CDATA セクションの終わりを報告します。
	 *
	 * インタフェース LexicalHandler 内の endCDATA
	 *
	 * @see org.xml.sax.ext.DefaultHandler2#endCDATA()
	 * @see org.xml.sax.ext.LexicalHandler#endCDATA()
	 */
	@Override
	public void endCDATA() {
		ele = stack.pop();
		inCDATA = false;
	}

	/**
	 * DTD 宣言がある場合、その開始を報告します。
	 *
	 * start/endDTD イベントは、ContentHandler の
	 * start/endDocument イベント内の最初の startElement イベントの前に出現します。
	 *
	 * インタフェース LexicalHandler 内の startDTD
	 *
	 * @param	name	文書型名
	 * @param	publicId	宣言された外部 DTD サブセットの公開識別子。 宣言されていない場合は null
	 * @param	systemId	宣言された外部 DTD サブセットのシステム識別子。 宣言されていない場合は null。
	 * 		  ドキュメントのベース URI に対しては解決されないことに 注意すること
	 * @see org.xml.sax.ext.DefaultHandler2#startDTD( String , String , String )
	 * @see org.xml.sax.ext.LexicalHandler#startDTD( String , String , String )
	 */
	@Override
	public void startDTD( final String name, final String publicId, final String systemId ) {
		// 6.0.2.5 (2014/10/31) char を append する。
		final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE )
			.append( "<!DOCTYPE " ).append( name );
		if( publicId != null ) { buf.append( " PUBLIC \"" ).append( publicId ).append( '"' ); }
		if( systemId != null ) { buf.append( '"' ).append( systemId ).append( '"' ); }

		final OGNode node = new OGNode( buf.toString() );
		node.setNodeType( OGNodeType.DTD );
		ele.addNode( node );
	}

	/**
	 * DTD 宣言の終わりを報告します。
	 *
	 * このメソッドは、DOCTYPE 宣言の終わりを報告するメソッドです。
	 * ここでは、何もしません。
	 *
	 * インタフェース LexicalHandler 内の endDTD
	 *
	 * @see org.xml.sax.ext.DefaultHandler2#endDTD()
	 * @see org.xml.sax.ext.LexicalHandler#endDTD()
	 */
	@Override
	public void endDTD() {
		// ここでは何もしません。
	}

	/**
	 * 内部および外部の XML エンティティーの一部の開始を報告します。
	 *
	 * インタフェース LexicalHandler の記述:
	 *
	 * ※ ここでは、&amp;lt; などの文字列が、lt という名のエンティティーで
	 * 報告されるため、元の＆付きの文字列に復元しています。
	 * エンティティー内かどうかを判断する、inEntity フラグを true にセットします。
	 * inEntity=true の間は、#characters(char[],int,int) は、何も処理しません。
	 *
	 * @param	name	エンティティーの名前
	 * @see org.xml.sax.ext.LexicalHandler#startEntity(String)
	 */
	@Override
	public void startEntity( final String name ) {
		final String text = "&" + name + ";" ;
		final OGNode node = new OGNode( text );
		ele.addNode( node );
		inEntity = true;
	}

	/**
	 * エンティティーの終わりを報告します。
	 *
	 * インタフェース LexicalHandler の記述:
	 *
	 * ※ ここでは、inEntity=false を設定するだけです。
	 *
	 * @param	name	エンティティーの名前
	 * @see org.xml.sax.ext.LexicalHandler#endEntity(String)
	 */
	@Override
	public void endEntity( final String name ) {
		inEntity = false;
	}

	/**
	 * 要素コンテンツに含まれる無視できる空白文字の通知を受け取ります。
	 *
	 * インタフェース ContentHandler 内の ignorableWhitespace
	 *
	 * @param	cbuf	文字データ配列(空白文字)
	 * @param	off		文字配列内の開始位置
	 * @param	len		文字配列から使用される文字数
	 *
	 * @see org.xml.sax.ContentHandler#ignorableWhitespace(char[],int,int)
	 */
	@Override
	public void ignorableWhitespace( final char[] cbuf, final int off, final int len ) {
		final String text = toText( cbuf,off,len );
		final OGNode node = new OGNode( text );
		ele.addNode( node );
	}

	/**
	 * 文書内の任意の位置にある XML コメントを報告します。
	 *
	 * インタフェース LexicalHandler の記述:
	 *
	 * @param	cbuf	文字データ配列(コメント文字)
	 * @param	off		配列内の開始位置
	 * @param	len		配列から読み取られる文字数
	 *
	 * @see org.xml.sax.helpers.DefaultHandler#characters(char[],int,int)
	 */
	@Override
	public void comment( final char[] cbuf, final int off, final int len ) {
		final String text = toText( cbuf,off,len );
		final OGNode node = new OGNode( text );
		node.setNodeType( OGNodeType.Comment );
		ele.addNode( node );
	}

	/**
	 * 要素の終了通知を受け取ります。
	 *
	 * @param	uri			名前空間 ＵＲＩ。要素が名前空間 ＵＲＩ を持たない場合、または名前空間処理が実行されない場合は null
	 * @param	localName	前置修飾子を含まないローカル名。名前空間処理が行われない場合は空文字列
	 * @param	qName		接頭辞を持つ修飾名。修飾名を使用できない場合は空文字列
	 *
	 * @see org.xml.sax.helpers.DefaultHandler#endElement(String,String,String)
	 * @see org.xml.sax.ContentHandler#endElement(String,String,String)
	 */
	@Override
	public void endElement( final String uri, final String localName, final String qName ) {
		ele = stack.pop();
	}

	/**
	 * パーサー警告の通知を受け取ります。
	 *
	 * インタフェース org.xml.sax.ErrorHandler 内の warning
	 *
	 * ここでは、パーサー警告の内容を標準エラーに表示します。
	 *
	 * @param	ex	例外として符号化された警告情報
	 * @see org.xml.sax.ErrorHandler#warning(SAXParseException)
	 */
	@Override
	public void warning( final SAXParseException ex ) {
		final String errMsg = ex.getMessage() + ":" + ex.getPublicId()
					+ CR + "\t" + filename  + " (" + ex.getLineNumber() + ")";
		System.err.println( "WARNING:" + errMsg );
	}

	/**
	 * 文字配列から、文字列を作成します。(改行コードの統一)
	 *
	 * 処理的には、new String( cbuf,off,len ) ですが、XMLでリード
	 * されたファイルは、改行コードが、'\r'(CR:復帰)+ '\n'(LF:改行)ではなく、
	 * '\n'(LF:改行) のみに処理されます。(されるようです。規定不明)
	 * そこで、実行環境の改行コード(System.getProperty("line.separator"))と
	 * 置き換えます。
	 *
	 * @param	cbuf	文字データ配列
	 * @param	off		配列内の開始位置
	 * @param	len		配列から読み取られる文字数
	 *
	 * @return	最終的な、Stringオブジェクト
	 * @og.rtnNotNull
	 */
	private String toText( final char[] cbuf, final int off, final int len ) {
		final String text = new String( cbuf,off,len );
		return text.replaceAll( "\n", CR );
	}

	/**
	 * OGDocument を取得します。
	 *
	 * @return	最終的な、OGNodeオブジェクトに相当します
	 */
	private OGDocument getDocument() {
		OGDocument doc = null;
		if( ele != null && ele.getNodeType() == OGNodeType.Document ) {
			// 6.0.2.5 (2014/10/31) refactoring: getNodeType でチェックしているので間違いはないが、findBugs対応
			if( ele instanceof OGDocument ) {
				doc = (OGDocument)ele;
				doc.setIdMap( idMap );
			}
			else {			// 基本、あり得ない。
				final String errMsg = "この、OGNode は、OGDocument のインスタンスではありません。" ;
				System.err.println( "WARNING:" + errMsg );
			}
		}
		return doc;
	}

	/**
	 * サンプルプログラムです。
	 *
	 * 引数の IN がファイルの場合は、OUTもファイルとして扱います。
	 * IN がフォルダの場合は、階層にしたがって、再帰的に処理を行い、OUT に出力します。
	 * フォルダ階層をパースしている最中に、XMLとして処理できない、処理中にエラーが発生した
	 * などの場合は、バイナリコピーを行います。
	 *
	 * "Usage: org.opengion.fukurou.xml.JspSaxParser  &lt;inFile|inDir&gt; &lt;outFile|outDir&gt; [&lt;JspParserFilter1&gt; ･･･ ]"
	 *
	 * @og.rev 6.3.9.1 (2015/11/27) A method/constructor shouldnt explicitly throw java.lang.Exception(PMD)。
	 * @og.rev 6.4.3.3 (2016/03/04) リフレクション系の例外の共通クラスに置き換えます。
	 *
	 * @param	args	コマンド引数配列
	 * @throws ClassNotFoundException クラスが見つからない場合
	 * @throws InstantiationException インスタンスを生成できなかった場合
	 * @throws IllegalAccessException 不正なアクセスがあった場合
	 */
//	public static void main( final String[] args ) throws Exception {
//	public static void main( final String[] args ) throws ClassNotFoundException,InstantiationException,IllegalAccessException {
	public static void main( final String[] args ) throws ReflectiveOperationException {
		if( args.length < 2 ) {
			System.out.println( "Usage: org.opengion.fukurou.xml.JspSaxParser <inFile|inDir> <outFile|outDir> [<JspParserFilter1> ･･･ ]" );
		}

		final File in   = new File( args[0] );
		final File out  = new File( args[1] );

		final JspSaxParser jsp = new JspSaxParser();

		if( args.length >= 3 ) {
			for( int i=2; i<args.length; i++ ) {
				final JspParserFilter filter = (JspParserFilter)Class.forName( args[i] ).newInstance();
				jsp.addFilter( filter );
			}
		}

		jsp.copyDirectry( in,out );
	}
}
