/*********************************************************************
* All Rights reserved,Copyright (c) K-Opticom
**********************************************************************
*＜プログラム内容＞
*	システム名      ：eo顧客基幹システム
*	モジュール名    ：JBAEbcdicConv
*	ソースファイル名：JBAEbcdicConv.java
*	作成者          ：富士通
*	日付            ：2012年11月22日
*＜機能概要＞
*　EBCDICコードで作成されたファイルを指定されたエンコーディング(MS932等)
*　に変換する。
*＜修正履歴＞
*	バージョン  修正日       修正者      修正内容
*	v1.00.00    2012/11/22   FJ) 野村    新規作成
*
**********************************************************************/
package eo.common.util;

import java.io.InputStream           ;
import java.io.FileInputStream       ;
import java.io.BufferedInputStream   ;
import java.io.OutputStream          ;
import java.io.FileOutputStream      ;
import java.io.BufferedOutputStream  ;
import java.io.FileNotFoundException ;
import java.io.IOException           ;
import java.nio.ByteBuffer  ;
import java.nio.CharBuffer  ;
import java.nio.charset.Charset         ;
import java.nio.charset.CharsetDecoder  ;
import java.nio.charset.CharsetEncoder  ;
import java.nio.charset.CoderResult     ;
import java.nio.charset.IllegalCharsetNameException ;
import java.nio.charset.UnsupportedCharsetException ;
import java.nio.charset.MalformedInputException     ;

/**
 * EBCDICコードで作成されたファイルを指定されたエンコーディング(MS932等)に変換します。
 * <br>
 * @author 富士通
 */
public class JBAEbcdicConv{
	private static final String VERSION         = "v1.00.00"  ;
	private static final int    EOF             = -1     ;
	private static final int    EBCDIC_SO       = 0x0e   ;
	private static final int    EBCDIC_SI       = 0x0f   ;
	private static final char   UCS2_WIDE_QMARK = 0xff1f ;
	private static final String NEWLINE         = System.getProperty( "line.separator" ) ;
	private static final int    XCODE_OK           =  0 ;
	private static final int    XCODE_SUBSTITUTION =  1 ;
	private static final int    XCODE_FAILURE      =  2 ;
	private static final int    XCODE_ERROR        =  3 ;

	/** 入力ファイルのバイト列をunicode列に変換するデコーダ */
	private CharsetDecoder decoder_       = null ;
	
	/** unicode列を出力ファイルのバイト列に変換するエンコーダ */
	private CharsetEncoder encoder_       = null ;
	
	/** 変換不能時に用いる代替文字(１バイト文字用) 0の場合は代替文字を出力せずスキップする */
	private char[]         defaultSBC_    = { '?' } ;
	
	/** 変換不能時に用いる代替文字(２バイト文字用) 0の場合は代替文字を出力せずスキップする */
	private char[]         defaultDBC_    = { '?' } ;
	
	/** 変換不能時の打ち切り基準 0: 変換不能時は代替文字を使用 -1: 変換不能時は異常終了 1: 入力フォーマットエラーも異常終了せず代替文字を出力 */
	private int            exitOnFail_    = 0    ;

	/**
	 * 新しいJBAEbcdicConvを作成します。
	 * <br>
	 * @param src 入力ファイルのエンコーディング
	 * @param tgt 出力ファイルのエンコーディング
	 * @throws UbsupportedCharsetException システムでサポートされていないエンコーディングが指定された場合
	 */
	private JBAEbcdicConv( String src, String tgt ) throws UnsupportedCharsetException {
		String trying = null ;
		
		try{
			trying = src ;
			decoder_ = Charset.forName( trying ).newDecoder() ;
			trying = tgt ;
			encoder_ = Charset.forName( trying ).newEncoder() ;
		}
		catch ( IllegalCharsetNameException e ){
			throw new UnsupportedCharsetException( trying ) ;
		}
		catch ( UnsupportedCharsetException e ){
			throw new UnsupportedCharsetException( trying ) ;
		}
	}

	/**
	 * JBAEbcdicConvの設定情報(プロパティ等)を文字列として返します。
	 * <br>
	 * @return JBAEbcdicConvの設定情報文字列
	 */
	private String showSetup(){
		StringBuilder out = new StringBuilder() ;
		
		out.append( getClass() ) ;
		out.append( NEWLINE ) ;
		out.append( "Version         : " ) ;
		out.append( VERSION ) ;
		out.append( NEWLINE ) ;
		out.append( "Input  Encoding : " ) ;
		out.append( decoder_.charset().name() ) ;
		out.append( NEWLINE ) ;
		out.append( "Output Encoding : " ) ;
		out.append( encoder_.charset().name() ) ;
		out.append( NEWLINE ) ;
		out.append( "Substitution MBC: " ) ;
		out.append( String.format( "u+%04x%s", (int)defaultDBC_[0], NEWLINE ) ) ;
		out.append( "Substitution SBC: " ) ;
		out.append( String.format( "u+%04x%s", (int)defaultSBC_[0], NEWLINE ) ) ;
		out.append( "On Failure      : " ) ;
		if      ( 0 < exitOnFail_ ){
			out.append( "continue even on illegal input." ) ;
		}
		else if ( 0 > exitOnFail_ ){
			out.append( "exit on mapping failure." ) ;
		}
		else{
			out.append( "exit on illegal input."   ) ;
		}
		out.append( NEWLINE ) ;
		return ( out.toString() ) ;
	}

	/**
	 * 変換不能時の打ち切り基準の現在値を返します。
	 * <br>
	 * @return 変換不能時の打ち切り基準の現在値
	 */
	private int getExitOnFail(){
		return ( exitOnFail_ ) ;
	}
	
	/**
	 * 変換不能時の打ち切り基準値を設定します
	 * <br>
	 * @param value 変換不能時の打ち切り基準値
	 * @return this
	 */
	private JBAEbcdicConv setExitOnFail( int value ){
		exitOnFail_ = value ;
		return ( this ) ;
	}
	
	/**
	 * １バイト文字用代替文字の現在値を返します。
	 * <br>
	 * @return １バイト文字用代替文字(配列)
	 */
	private char[] setDefaultSBC(){
		return ( defaultSBC_ ) ;
	}
	
	/**
	 * １バイト文字用代替文字を設定します(サロゲートペア、複合文字は考慮していません)。
	 * <br>
	 * @param value １バイト文字用代替文字
	 * @return this
	 * @throws NumberFormatException 代替文字として不適切(指定コードへ変換不能)なunicode文字が与えられた場合
	 */
	private JBAEbcdicConv setDefaultSBC( long value ) throws NumberFormatException {
		if ( !encoder_.canEncode( (char)value ) ){

			throw new NumberFormatException() ;
		}

		defaultSBC_    = new char[1] ;
		defaultSBC_[0] = (char)value ;
		return ( this ) ;
	}
	
	/**
	 * ２バイト文字用代替文字の現在値を返します。
	 * <br>
	 * @return ２バイト文字用代替文字(配列)
	 */
	private char[] getDefaultDBC(){
		return ( defaultDBC_ ) ;
	}
	
	/**
	 * ２バイト文字用代替文字を設定します(サロゲートペア、複合文字は考慮していません)。
	 * <br>
	 * @param value ２バイト文字用代替文字
	 * @return this
	 * @throws NumberFormatException 代替文字として不適切(指定コードへ変換不能)なunicode文字が与えられた場合
	 */
	private JBAEbcdicConv  setDefaultDBC( long value ) throws NumberFormatException {
		if ( !encoder_.canEncode( (char)value ) ){
			throw new NumberFormatException() ;
		}

		defaultDBC_    = new char[1] ;
		defaultDBC_[0] = (char)value ;
		return ( this ) ;
	}
	
	/**
	 * 入力バッファからユニコードバッファにバイト列デコードします。１文字分づつの変換を前提にしています。
	 * <br>
	 * @param from デコード元バッファ(未flip)、成否に拘わらず本メソッドでclear
	 * @param ucs2 デコート先バッファ(clear状態)、成功時のみflip
	 * @param eof  入力ファイルの終了時にtrue、まだ続く場合はfalse
	 * @param off  入力ファイルのオフセット(Malformat例外のパラメータ)
	 * @return 入力バイト列がunicode文字に変換できた場合true、できなかった場合false
	 * @throws MalformedInputException 入力バイト列のシーケンスに異常があった場合
	 */
	private boolean decodeIn( ByteBuffer from, CharBuffer ucs2, boolean eof, int off ) throws MalformedInputException {
		CoderResult res     = null ;

		from.flip() ;
		res = decoder_.decode( from, ucs2, eof ) ;
		from.clear() ;
		if ( res.isError() ){
			if( res.isMalformed() && (0 >= exitOnFail_) ) throw new MalformedInputException( off ) ;
			return ( false ) ;
		}
		if ( eof ){
			res = decoder_.flush( ucs2 ) ;
			if( res.isError() ){
				if( res.isMalformed() && (0 >= exitOnFail_) ) throw new MalformedInputException( off ) ;
				return ( false ) ;
			}
		}
		ucs2.flip() ;
		return ( true ) ;
	}

	/**
	 * ユニコードバッファから出力バッファにバイト列エンコードします。１文字分づつの変換を前提にしています。
	 * <br>
	 * @param ucs2 エンコート元ユニコードバッファ(flip済)、成否に拘わらず本メソッドでclear
	 * @param to   エンコード先バッファ(clear状態)、本メソッドではflipは行わない
	 * @param eof  入力ファイルの終了時にtrue、まだ続く場合はfalse
	 * @return unicode文字列が出力バイト列に変換できた場合true、できなかった場合false
	 */
	private boolean encodeOut( CharBuffer ucs2, ByteBuffer to, boolean eof ){
		CoderResult res     = null ;

		res = encoder_.encode( ucs2, to, eof ) ;
		ucs2.clear() ;
		if ( res.isError() ){
			return ( false ) ;
		}
		if ( eof ){
			res = encoder_.flush( to ) ;
			if( res.isError() ){
				return ( false ) ;
			}
		}
		return ( true ) ;
	}

	/**
	 * 入力バイト列からのデコードに失敗した場合に、メッセージを出力し、以後の処理を決定します。
	 * <br>
	 * @param c    変換に失敗した入力データ(２バイト文字の場合MSB側に第一バイト)
	 * @param dbcs ２バイト文字の場合true、１バイト文字の場合false
	 * @param off  変換に失敗した入力データの入力ファイルオフセット
	 * @param eof  入力ファイルの終了時にtrue、まだ続く場合はfalse
	 * @param to   代替文字出力用バッファ(clear状態)、本メソッドではflipは行わない
	 * @return 代替文字で処理を継続する場合true、処理を打ち切る場合false
	 */
	private boolean failureRescue( int c, boolean dbcs, int off, boolean eof, ByteBuffer to ){
		char[] alt = (dbcs)? defaultDBC_ : defaultSBC_ ;
		char[] nul = {} ;

		if ( dbcs ){
			System.err.printf( "Cannot decode '0x%04x' at %d(x%x).%s", c, off, off, NEWLINE ) ;
		}
		else{
			System.err.printf( "Cannot decode '0x%02x' at %d(x%x).%s", c, off, off, NEWLINE ) ;
		}
		if ( 0 > exitOnFail_ ){
			return ( false ) ;
		}
		if ( 0 == alt[0] ){
			encodeOut( CharBuffer.wrap( nul ), to, eof ) ;
		}
		else{
			encodeOut( CharBuffer.wrap( alt ), to, eof ) ;
		}
		return ( true ) ;
	}
	
	/**
	 * 出力バイト列へのエンコードに失敗した場合に、メッセージを出力し、以後の処理を決定します。
	 * <br>
	 * @param c    変換に失敗した入力文字(２バイト文字の場合MSB側に第一バイト)
	 * @param dbcs ２バイト文字の場合true、１バイト文字の場合false
	 * @param off  変換に失敗した入力文字の入力ファイルオフセット
	 * @param eof  入力ファイルの終了時にtrue、まだ続く場合はfalse
	 * @param ucs2 変換に失敗した入力文字のUnicode値
	 * @param to   代替文字出力用バッファ(clear状態)、本メソッドではflipは行わない
	 * @return 代替文字で処理を継続する場合true、処理を打ち切る場合false
	 */
	private boolean failureRescue( int c, boolean dbcs, int off, boolean eof, int ucs2, ByteBuffer to ){
		char[] alt = (dbcs)? defaultDBC_ : defaultSBC_ ;
		char[] nul = {} ;

		if ( dbcs ){
			System.err.printf( "Cannot encode '0x%04x'(u%04x) at %d(x%x).%s", c, ucs2, off, off, NEWLINE ) ;
		}
		else{
			System.err.printf( "Cannot encode '0x%02x'(u%04x) at %d(x%x).%s", c, ucs2, off, off, NEWLINE ) ;
		}
		if ( 0 > exitOnFail_ ){
			return ( false ) ;
		}
		if ( 0 == alt[0] ){
			encodeOut( CharBuffer.wrap( nul ), to, eof ) ;
		}
		else{
			encodeOut( CharBuffer.wrap( alt ), to, eof ) ;
		}
		return ( true ) ;
	}
	
	/**
	 * (Javaのバグ等)経験的に、都合の悪い文字をUnicodeレベルで変換します。
	 * 現在行っている変換は、ターゲットコードが"windows-31j"の時、'−'(u2212)を'−'(uff0d)に入れ替えることだけです。<br>
	 * 	Cp939のEBCDICから読み込まれた場合はuff0dにマップされますが、Cp930ではu2212にマップされます。これは"windows-31j"への変換で失敗します。<br>
	 * 	MS932->Cp930への変換時にはどちらもuff0dを経由するので、これはJavaのバグと思われます。<br>
	 * バージョンにより異なるかもしれないので実行環境でのチェックが必要です。
	 * <br>
	 * @param dbcs 入力コンテクスト(true:２バイトコンテクスト,false:１バイトコンテクスト)
	 * @param buf  デコード(flip状態)後のユニコードバッファ(本メソッドで配列値を書き換え)
	 * @return bufパラメータ
	 */
	private CharBuffer patchUcs2( boolean dbcs, CharBuffer buf ){
		String target = encoder_.charset().name() ;
		char[] ucs2   = buf.array() ;

		if ( target.equals( "windows-31j" ) ){
			switch ( ucs2[0] ){
			  case 0x2212 :
				ucs2[0] = 0xff0d ;	// BUG in Java Cp930 decoder?
				break ;
				
			  default     : ;		// nothing to do.
			}
		}
		return ( buf ) ;
	}

	/**
	 * ターゲットエンコーディングに変換された文字列を出力ストリームに出力します。
	 * <br>
	 * @param buf エンコーディング変換後のバッファ(未flip状態),本メソッドでclear
	 * @return bufパラメータ
	 */
	private void flushOut( ByteBuffer buf, BufferedOutputStream out ) throws IOException {
		buf.flip() ;
		while( buf.hasRemaining() ){
			out.write( buf.get() ) ;
		}
		buf.clear() ;
	}

	/**
	 * EBCDIC系コードでエンコードされた入力ストリームをコード変換し、出力ストリームに出力します。
	 * <br>
	 * @param in  EBCDIC系コードでエンコードされた入力ストリーム
	 * @param out コード変換先出力ストリーム
	 * @return 代替文字の使用回数
	 * @throws IOException 何らかの入出力エラーが発生した場合
	 * @throws MalformedInputException コード変換不能時異常終了が設定されている状態でコード変換が失敗した場合
	 */
	private int convert( BufferedInputStream in, BufferedOutputStream out ) throws IOException, MalformedInputException {
		ByteBuffer fromBuf = ByteBuffer.allocate( 4  ) ;
		ByteBuffer toBuf   = ByteBuffer.allocate( 32 ) ;
		char[]     ucs2    = new char[8] ;
		CharBuffer ucs2Buf = CharBuffer.wrap( ucs2 ) ;
		boolean    eof     = false ;
		boolean    dbcs    = false ;
		int        fails   = 0     ;
		int        off     = 0     ;

		decoder_.reset() ;
		encoder_.reset() ;
		while ( !eof ){
			int  c  = 0 ;
			off++ ;
			if ( !(eof = (EOF == (c = in.read()))) ){
				fromBuf.put( (byte)c ) ;
			}
			if ( (EBCDIC_SO == c) || (EBCDIC_SI == c) ){
				dbcs = (c == EBCDIC_SO) ;
				continue ;
			}
			if ( dbcs ){
				int  c2 = 0 ;
				off++ ;
				if( !(eof = (EOF == (c2 = in.read()))) ){
					fromBuf.put( (byte)c2 ) ;
					c = (c << 8) + c2 ;
				}
			}
			if ( !decodeIn( fromBuf, ucs2Buf, eof, off - (dbcs? 2 : 1) ) ){
				fails++ ;
				if ( !failureRescue( c, dbcs, off - (dbcs? 2 : 1), eof, toBuf ) ){
					return ( -1 ) ;
				}
			}
			else{
				patchUcs2( dbcs, ucs2Buf ) ;
				if ( !encodeOut( ucs2Buf, toBuf, eof ) ){
					fails++ ;
					if ( !failureRescue( c, dbcs, off - (dbcs? 2 : 1), eof, (int)ucs2[0], toBuf ) ){
						return ( -1 ) ;
					}
				}
			}
			flushOut( toBuf, out ) ;
		}
		if( 0 != fails ){
			System.err.printf( "%d characters are lost.%s", fails, NEWLINE ) ;
		}
		return ( fails ) ;
	}

	//-------------------------------------
	// static method section
	//-------------------------------------

	/**
	 * 本プログラムの使用法を表示します。
	 * <br>
	 * @param val 返却値となるパススルーパラメータ(通常は終了コード)
	 * @return valパラメータ
	 */
	private static int usage( int val ){
		System.err.println( "Usage: JBAEbcdicConv [<options>] [{<infile>|'-'} [<outfile>]]" ) ;
		System.err.println( " JBAEbcdicConv copies <infile> to <outfile>, changing its encoding." ) ;
		System.err.println( " If <outfile> is omitted, JBAEbcdicConv writes 'stdout'." ) ;
		System.err.println( " If <infile> is omitted or '-' is specified, JBAEbcdicConv reads 'stdin'." ) ;
		System.err.println( " Input file must adopt JBAEbcdic MBCS encoding scheme." ) ;
		System.err.println() ;
		System.err.println( " Options:" ) ;
		System.err.println( " -v" ) ;
		System.err.println( "	show program version." ) ;
		System.err.println( " -V" ) ;
		System.err.println( "	show setup." ) ;
		System.err.println( " -x" ) ;
		System.err.println( "	exit on mapping failure, rather than replacing to default characters." ) ;
		System.err.println( " -X" ) ;
		System.err.println( "	handle illegal input sequences as mapping failures." ) ;
		System.err.println( " -f <source-encoding>" ) ;
		System.err.println( "	specify source encoding, default is Cp930(i.e. x-IBM930)." ) ;
		System.err.println( " -t <target-encoding>" ) ;
		System.err.println( "	specify target encoding, default is MS932(i.e. windows-31j)." ) ;
		System.err.println( " -s <unicode>" ) ;
		System.err.println( "	change default single byte character, default is 0x3f('?')." ) ;
		System.err.println( "	<unicode> can be 0xFFFF or u+FFFF in hexadecimal, 0777777 in octal, 99999 in decimal." ) ;
		System.err.println( " -d <unicode>" ) ;
		System.err.println( "	change default double byte character, default is 0x3f('?')." ) ;
		return ( val ) ;
	}

	/**
	 * 数値文字列(0xFFFF, 0XFFFF, UFFFF, uFFFF, 0oooo, DDDD)をunicode配列に変換します。
	 * <br>
	 * @param code 数値文字列
	 * @return 数値文字列の表すバイナリ値
	 * @throws NumberFormatException 与えられた文字列が数値文字列として認められない場合
	 */
	private static long ucs2Code( String code ) throws NumberFormatException {
		long  ucs2 = 0L ;
		
		if ( 1 >= code.length() ){
			ucs2 = Long.parseLong( code, 10 ) ;
		}
		else{
			if ( '0' == code.charAt( 0 ) ){
				if ( ('x' == code.charAt( 1 )) || ('X' == code.charAt( 1 )) ){
					ucs2 = Long.parseLong( code.substring( 2 ), 16 ) ;
				}
				else{
					ucs2 = Long.parseLong( code.substring( 1 ),  8 ) ;
				}
			}
			else if ( ('u' == code.charAt( 0 )) || ('U' == code.charAt( 0 )) ){
				if ( '+' == code.charAt( 1 ) ){
					ucs2 = Long.parseLong( code.substring( 2 ), 16 ) ;
				}
				else{
					ucs2 = Long.parseLong( code.substring( 1 ), 16 ) ;
				}
			}
			else{
					ucs2 = Long.parseLong( code, 10 ) ;
			}
		}
		return( ucs2 ) ;
	}

	/**
	 * mainメソッドのオプションパラメータ部を処理し、オプション値に応じたインスタンスを作成します。
	 * 但し、-vオプション、-Vオプション指定時は、mainに制御を戻さず終了します。
	 * <br>
	 * @param args コマンドラインオプション(順不同)<br>
	 *	-v	:	本プログラムのバージョンを表示して終了<br>
	 *	-V	:	パラメータ設定を表示して終了<br>
	 *	-x	:	変換失敗時異常終了<br>
	 *	-X	:	入力ファイル異常時でも代替文字を使用して継続(-x指定時は無視)<br>
	 *	-s <１バイト文字用代替文字>	: 省略時 u003f('?')  0の場合は空(スキップ)<br>
	 *	-d <２バイト文字用代替文字>	: 省略時 uff1d('？')  0の場合は空(スキップ)<br>
	 *	-f <入力ファイルのエンコーディング>	: 省略時 Cp930<br>
	 *	-t <変換先のエンコーディング>	: 省略時 MS932<br>
	 * @return オプション設定済みインスタンス(オプション値に問題があった場合はnull)
	 * @throws NumberFormatException 与えられた文字列が数値文字列として認められない場合
	 */
	private static JBAEbcdicConv newConverter( String[] args ){
		JBAEbcdicConv entity = null ;
		String  src     = "Cp930" ;
		String  tgt     = "MS932" ;
		String  sbc     = null    ;
		String  dbc     = null    ;
		int     i       = 0       ;
		String  arg     = null    ;
		String  trying  = null    ;
		int     xmode   = 0       ;
		boolean verbose = false   ;

		for( i = 0 ; (i < args.length) && (0 < (arg=args[i]).length()) && !"-".equals( arg ) && ('-' == arg.charAt( 0 )) ; i++ ){
			if     ( "-x".equals( arg ) ){
				xmode = -1 ;
			}
			else if( "-X".equals( arg ) ){
				xmode = (0 <= xmode)? 1 : xmode ;
			}
			else if( "-V".equals( arg ) ){
				verbose = true ;
			}
			else if( "-v".equals( arg ) ){
				System.out.println( VERSION ) ;
				System.exit( XCODE_OK ) ;
			}
			else{
				if( ++i >= args.length ){
					System.exit( usage( XCODE_ERROR ) ) ;
				}
				if     ( "-f".equals( arg ) ){
					src = args[i] ;
				}
				else if( "-t".equals( arg ) ){
					tgt = args[i] ;
				}
				else if( "-s".equals( arg ) ){
					sbc = args[i] ;
				}
				else if( "-d".equals( arg ) ){
					dbc = args[i] ;
				}
				else{
					System.exit( usage( XCODE_ERROR ) ) ;
				}
			}
		}
		try{
			(entity = new JBAEbcdicConv( src, tgt )).setExitOnFail( xmode ) ;
			if( null != (trying=sbc) ){
				entity.setDefaultSBC( ucs2Code( trying ) ) ;
			}
			if( null != (trying=dbc) ){
				entity.setDefaultDBC( ucs2Code( trying ) ) ;
			}
		}
		catch ( NumberFormatException e ){
			System.err.printf( "'%s' is no good <unicode> specification.%s", trying, NEWLINE ) ;
			entity = null ;
		}
		catch ( UnsupportedCharsetException e ){
			System.err.printf( "'%s' is unknown encoding.%s", e.getMessage(), NEWLINE ) ;
			entity = null ;
		}
		if ( (null != entity) && verbose ){
			System.out.print( entity.showSetup() ) ;
			System.exit( XCODE_OK ) ;
		}
		return( entity ) ;
		// 50行をこえているが、これ以上分割すると不自然
	}

	/**
	 * プログラムのエントリ、各種オプションでインスタンスを作成後、(必要に応じ)コード変換変換元ファイル、変換先ファイルを開き
	 * インスタンスのconvertメソッドを呼び出します。
	 * <br>
	 * @param args コマンドライン引数<br>
	 *	各種オプションフラグまたはパラメータ(newConverter参照)<br>
	 *	<入力ファイル>	省略時または"-"は標準入力、出力ファイル指定がある場合は省略不可<br>
	 *	<出力ファイル>	省略時または"-"は標準出力<br>
	 * @return 
	 */
	public static void main( String[] args ){
		JBAEbcdicConv        entity  = newConverter( args ) ; // for anticipating the 50 lines limit rule.
		InputStream          in      = System.in  ;
		BufferedInputStream  bin     = null       ;
		OutputStream         out     = System.out ;
		BufferedOutputStream bout    = null       ;
		int     i      = 0    ;
		String  trying = null ;
		int     res    = 0    ;
		int     xcode  = XCODE_ERROR ;
		
		if( entity == null ){
			System.exit( xcode ) ;
		}
		for( i = 0 ; (i < args.length) && (0 < args[i].length()) && !"-".equals( args[i] ) && ('-' == args[i].charAt( 0 )) ; i++ ){
			i += ("-x".equals( args[i] ) || "-X".equals( args[i] ) || "-v".equals( args[i] ) || "-V".equals( args[i] ))? 0 : 1  ;
		}
		try{
			switch ( args.length - i ){
			  case 2 :
				trying = args[i+1] ;
				if( !("-".equals( trying )) ){
					out = new FileOutputStream( trying ) ;
				}	// breakしない
			  case 1 :
				trying = args[i] ;
				if( !("-".equals( trying )) ){
					in  = new FileInputStream(  trying ) ;
				}	// breakしない
			  case 0 : break ;
				
			  default:
				System.exit( usage( xcode ) ) ;
			}
			bin  = new BufferedInputStream( in ) ;
			bout = new BufferedOutputStream( out ) ;
			res = entity.convert( bin, bout ) ;
			bout.flush() ;
			xcode = (0 == res)? XCODE_OK : ((0 < res)? XCODE_SUBSTITUTION : XCODE_FAILURE)  ;
		}
		catch( FileNotFoundException e ){
			System.err.printf( "Cannot open '%s'.%s", trying, NEWLINE ) ;
		}
		catch( MalformedInputException e ){
			System.err.printf( "The input file has an illegal character. %s.%s", e.getMessage(), NEWLINE ) ;
			xcode = XCODE_FAILURE ;
		}
		catch( IOException e ){
			System.err.println( "IO Error!" ) ;
		}
		finally{
			try{ 
				if( (null != bout) && (System.out != out) ){
					bout.close() ;
				}
				if( (null != bin ) && (System.in  != in ) ){
					bin.close()  ;
				}
			}
			catch ( Exception e ){
				System.err.println( "IO Error!" ) ;
				xcode = XCODE_ERROR ;
			}
		}
	 	System.exit( xcode ) ;
	}
	// 50行をこえているが、これ以上分割すると不自然
}
