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

import org.opengion.fukurou.system.OgRuntimeException ;		// 6.4.2.0 (2016/01/29)
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.SocketAddress;
import java.net.URL;
import java.net.URLConnection;

import org.apache.commons.codec.binary.Base64;

import org.opengion.fukurou.system.HybsConst;						// fukurou.util.StringUtil → fukurou.system.HybsConst に変更
import org.opengion.fukurou.system.Closer;							// 6.4.2.0 (2016/01/29) package変更 fukurou.util → fukurou.system
import org.opengion.fukurou.system.LogWriter;						// 6.4.2.0 (2016/01/29) package変更 fukurou.util → fukurou.system
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.1.0.0 (2014/12/26) refactoring

/**
 * URLConnect は、指定のURL にアクセスして、情報/データを取得します。
 * URL へのアクセスにより、エンジンでは各種処理を実行させることが可能になります。
 * 例えば、帳票デーモンの起動や、長時間かかる処理の実行などです。
 * なお、URLに引数が付く場合は、ダブルコーテーションで括って下さい。
 * - 付き引数は、指定順番は、関係ありません。- 無し引数(url,user:passwd)は、
 * 順番があります。
 *
 * Usage: java org.opengion.fukurou.util.URLConnect [-info/-data] … url [user:passwd]
 *
 *   args[*] : [-info/-data]       情報の取得か、データの取得かを指定します(初期値:data)
 *   args[*] : [-post=ファイル名]  POSTメソッドを指定して、ファイルデータを送信します
 *   args[*] : [-timeout=1]        接続タイムアウト時間を(秒)で指定します(初期値:-1)
 *   args[*] : [-encode=UTF-8]     エンコードを指定します(通常は接続先のencodeを使用)
 *   args[*] : [-out=ファイル名]   結果を指定されたファイルエンコードでファイルに出力します
 *   args[*] : [-errEx=true/false] trueの場合、ﾚｽﾎﾟﾝｽｺｰﾄﾞが、4XX,5XX の時に RuntimeException を投げます(初期値:false)
 *   args[A] : url                 ＵＲＬを指定します。GETの場合、パラメータは ?KEY=VALです
 *   args[B] : [user:passwd]       BASIC認証のエリアへのアクセス時に指定します
 *
 * ※ プロキシ設定の３つの方法
 * プロキシ設定には、３つの方法があります。
 *   1.
 *     URL url = URL( "http",proxyHost,proxyPort, url );
 *     URLConnection urlConn = url.openConnection();
 *   2.
 *     SocketAddress scaddr = new InetSocketAddress( proxyHost, proxyPort );
 *     Proxy proxy = new Proxy( Proxy.Type.HTTP, scaddr );
 *     URL url = new Url( url );
 *     URLConnection urlConn = url.openConnection( proxy );
 *   3.
 *     System.setProperty( "http.proxyHost",host );
 *     System.setProperty( "http.proxyPort",String.valueOf( port ) );
 *     URL url = new Url( url );
 *     URLConnection urlConn = url.openConnection();
 *     System.clearProperty( "http.proxyHost" );
 *     System.clearProperty( "http.proxyPort" );
 *
 * 1. 、2. の方法は、urlConn.getContentType() を実行すると、エラーになります。(原因不明)
 * 3. の方法では、マルチスレッドで実行する場合に、問題が発生します。
 * 本クラスでは、方法２ を使用しています。
 *
 * @version  4.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
public class URLConnect {

	private final String urlStr ;
	private final String userPass ;

	private int				rpsCode		= -1;
	private String			rpsMethod	;
	private String			rpsMessage	;
	private String			type		;
	private String			charset		;
	private String			postData	;
	private int				timeout		= -1;		// 6.2.5.0 (2015/06/05) timeout属性追加
	private long			length		= -1;
	private long			date		= -1;
	private long			modified	= -1;
	private boolean			isPost		;
	private URLConnection 	conn		;
	private Proxy			proxy		= Proxy.NO_PROXY;

	// 5.8.3.0 (2015/01/09) 追加
	private String[]		propKeys;
	private String[]		propVals;

	/**
	 * コンストラクター
	 *
	 * @param	url	接続するアドレスを指定します。(http://server:port/dir/file.html)
	 * @param	pass	ユーザー：パスワード(認証接続が必要な場合)
	 */
	public URLConnect( final String url, final String pass ) {
		urlStr   = url;
		userPass = pass;
	}

	/**
	 * 指定のURLに対して、コネクトするのに使用するプロキシ設定を行います。
	 * このときに、ヘッダー情報を内部変数に設定しておきます。
	 *
	 * @param	host	接続するプロキシのホスト名
	 * @param	port	接続するプロキシのポート番号
	 */
	public void setProxy( final String host,final int port ) {
		// 方法2.
		final SocketAddress scaddr = new InetSocketAddress( host, port );
		proxy = new Proxy( Proxy.Type.HTTP, scaddr );
	}

	/**
	 * 指定のURLに対して、コネクトします。
	 * このときに、ヘッダー情報を内部変数に設定しておきます。
	 *
	 * @og.rev 4.0.1.0 (2007/12/12) Postで複数キーを使えるように修正
	 * @og.rev 5.1.6.0 (2010/05/01) charsetを指定できるようにする
	 * @throws  IOException 入出力エラーが発生したとき
	 */
	public void connect() throws IOException {
		conn = getConnection();

		if( isPost ) {
			conn.setDoOutput( true );			// POST可能にする

			OutputStream os = null;				// POST用のOutputStream
			PrintStream  ps = null;
			try {
				os = conn.getOutputStream();	// POST用のOutputStreamを取得
				// 5.1.6.0 (2010/05/01)
				// 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..;
				ps = charset == null ? new PrintStream( os ) : new PrintStream( os, false, charset );

//				if( charset != null ) {
//					ps = new PrintStream( os, false, charset );
//				}
//				else {
//					ps = new PrintStream( os );
//				}
				ps.print( postData );			// 4.1.0.0 (2007/12/22)
			}
			finally {
				Closer.ioClose( ps );		// close 処理時の IOException を無視
				Closer.ioClose( os );		// close 処理時の IOException を無視
			}
		}
		else {
			// GET 時のコネクション接続
			conn.connect();
		}

		setInfo( conn );
	}

	/**
	 * U接続先のデータを取得します。
	 *
	 * この処理の前に、connect() 処理を実行しておく必要があります。
	 * 取得したデータは、指定のURL へのアクセスのみです。
	 * 通常のWebブラウザは、イメージや、JavaScriptファイル、CSSファイルなど、
	 * 各種ファイル毎にHTTP接続を行い、取得して、レンダリングします。
	 * このメソッドでの処理では、それらのファイル内に指定されているURLの
	 * 再帰的な取得は行いません。
	 * よって、フレーム処理なども行いません。
	 * 本来は、Stream のまま処理することで、バイナリデータも扱えますが、ここでは、
	 * テキストデータ(String)に変換して使用できるデータのみ扱えます。
	 *
	 * @return	接続結果
	 * @og.rtnNotNull
	 * @throws  IOException 入出力エラーが発生したとき
	 */
	public String readData() throws IOException {
		if( conn == null ) {
			final String errMsg = "connect() されていません。データ取得前にconnect()してください。";
			throw new OgRuntimeException( errMsg );
		}

		BufferedReader reader = null;
		final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
		try {
			reader = getReader();

			String line ;
			while( (line = reader.readLine()) != null ) {
				buf.append( line ).append( CR );
			}
		}
		catch( UnsupportedEncodingException ex ) {
			final String errMsg = "指定された文字エンコーディングがサポートされていません。" + CR
						+ " url=[" + urlStr + "]"
						+ " charset=[" + charset + "]" ;
			throw new OgRuntimeException( errMsg,ex );
		}
		finally {
			Closer.ioClose( reader );
			disconnect();
		}

		return buf.toString();
	}

	/**
	 * サーバへのほかの要求が今後発生しそうにないことを示します。
	 *
	 */
	public void disconnect() {
		if( conn instanceof HttpURLConnection ) {
			((HttpURLConnection)conn).disconnect() ;
		}
	}

	/**
	 * URL と ユーザー：パスワードを与えて、URLConnectionを返します。
	 *
	 * ユーザー：パスワード が null でない場合は、BASCI認証エリアへのアクセスの為、
	 * BASE64Encoder を行って、Authorization プロパティーを設定します。
	 * ここで返す URLConnection は、すでに、connect() メソッド実行済みの
	 * リモート接続が完了した状態のオブジェクトです。
	 * 
	 * @og.rev 5.8.3.0 (2015/01/09) ヘッダ等指定のためにsetRequestPropertyの値を指定できるようにします。
	 * @og.rev 6.2.5.0 (2015/06/05) timeout属性追加
	 * @og.rev 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs)
	 * @og.rev 6.4.2.0 (2016/01/29) fukurou.util.StringUtil → fukurou.system.HybsConst に変更
	 *
	 * @return  URLConnectionオブジェクト
	 * @throws  IOException 入出力エラーが発生したとき
	 */
	// 5.1.5.0 (2010/04/01) SOAP対応により、PROTECTED化
	protected URLConnection getConnection() throws IOException {
		final URL url = new URL( urlStr );

		// 方法2.
		final URLConnection urlConn = url.openConnection( proxy );

		if( userPass != null ) {
//			final byte[] encoded = Base64.encodeBase64( userPass.getBytes( StringUtil.DEFAULT_CHARSET ) );	// 5.5.2.6 (2012/05/25) findbugs対応
//			final String userPassEnc = new String( encoded,StringUtil.DEFAULT_CHARSET );		// 5.5.2.6 (2012/05/25) findbugs対応
			final byte[] encoded = Base64.encodeBase64( userPass.getBytes( HybsConst.DEFAULT_CHARSET ) );	// 5.5.2.6 (2012/05/25) findbugs対応
			final String userPassEnc = new String( encoded,HybsConst.DEFAULT_CHARSET );		// 5.5.2.6 (2012/05/25) findbugs対応
			urlConn.setRequestProperty( "Authorization","Basic " + userPassEnc );
		}

		// 5.8.3.0 (2015/01/09) RequestPropertyのセット
		// 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs)
//		if( propKeys != null && propKeys.length > 0 ){
		if( propKeys != null && propVals != null ){
			for( int i=0; i<propKeys.length; i++ ){
				urlConn.setRequestProperty( propKeys[i], propVals[i] );
			}
		}

		// 6.2.5.0 (2015/06/05) timeout属性追加
		if( timeout >= 0 ) {
			urlConn.setConnectTimeout( timeout * 1000 );	// 引数は(秒)、設定は(ミリ秒)
		}

		return urlConn ;
	}

	/**
	 * 接続先の情報を内部変数に設定します。
	 *
	 * ここでは、タイプ,エンコード,レスポンスコード,レスポンスメッセージ を設定します。
	 * レスポンスコード,レスポンスメッセージは、接続コネクションが、HttpURLConnection の
	 * 場合のみセットされます。
	 * 途中でエラーが発生した場合でも、継続処理できるようにします。これは、プロキシ
	 * 設定の方法により、conn.getContentType()  でエラーが発生する場合があるためです。
	 *
	 * @og.rev 5.5.9.1 (2012/12/07) charsetは、null の場合のみ設定します。
	 *
	 * @param   conn 接続先のコネクション
	 */
	private void setInfo( final URLConnection conn ) {
		try {
			// 5.5.9.1 (2012/12/07) charsetは、null の場合のみ設定します。
			if( charset == null ) { charset = conn.getContentEncoding(); }
			type	= conn.getContentType() ;
			length	= conn.getContentLength();
			date	= conn.getDate();
			modified= conn.getLastModified();

			if( charset == null && type != null ) {
				final int adrs = type.indexOf( "charset" );
				final int adrs2 = type.indexOf( '=',adrs );
				if( adrs > 0 && adrs2 > adrs ) {
					charset = type.substring( adrs2+1 ).trim();
				}
			}

			if( conn instanceof HttpURLConnection ) {
				final HttpURLConnection httpConn = (HttpURLConnection) conn;
				rpsCode		= httpConn.getResponseCode();
				rpsMethod	= httpConn.getRequestMethod();
				rpsMessage	= httpConn.getResponseMessage() + code2Message( rpsCode );
			}
		}
		// 4.0.0.0 (2007/11/29) Exception から、IOException と RuntimeException に変更
		catch( IOException ex ) {
			System.out.println( ex.getMessage() );
		}
		catch( RuntimeException ex ) {
			System.out.println( ex.getMessage() );
		}
	}

	/**
	 * URL 情報を取得します。
	 *
	 * @og.rev 4.3.4.4 (2009/01/01) メソッド名変更
	 *
	 * @return	URL情報
	 */
	public String getUrl() { return urlStr; }

	/**
	 * setRequestPropertyでセットするデータを設定します。
	 *
	 * ヘッダ等指定のためにsetRequestPropertyの値を指定できるようにします。
	 * 例えばJSON形式でPOSTする場合は通常"Content-Type", "application/json"を指定します。
	 * keys,vals各々、カンマ区切り(CSV形式)で渡されますので、分解しておきます。
	 *
	 * @og.rev 5.8.3.0 (2015/01/09) ヘッダ等指定のためにsetRequestPropertyの値を指定できるようにします。
	 *
	 * @param	keys	パラメータキー(CSV形式)
	 * @param	vals	パラメータ値(CSV形式)
	 * @throws  IllegalArgumentException 引数の数が一致しない場合
	 */
	public void setRequestProperty( final String keys, final String vals ) {
		if( keys != null && keys.length() > 0 && vals != null && vals.length() > 0 ){
			propKeys = StringUtil.csv2Array( keys );
			propVals = StringUtil.csv2Array( vals );

			if( propKeys.length != propVals.length ) {
				final String errMsg = "パラメータのキーと、値の数が一致しません。"	+ CR
							+ " key=[" + keys + "]"									+ CR
							+ " val=[" + vals + "]" ;
				throw new IllegalArgumentException( errMsg );
			}
		}
	}

	/**
	 * POSTするデータを設定します。
	 *
	 * POSTする場合は、connect() 処理を行う前に、データを設定しておく必要があります。
	 *
	 * @og.rev 4.1.0.0 (2007/12/22) キーと値のセットを取得するよう変更
	 * @param	data	POSTデータ
	 */
	public void setPostData( final String data ) {
		postData = data;
		if( postData != null && "?".indexOf( postData ) == 0 ) { // 先頭の?を抜く
			postData = postData.substring(1);
		}
		if( postData != null ) { isPost = true; }
	}

	/**
	 * タイプ 情報を取得します。
	 *
	 * @return	タイプ 情報
	 */
	public String getType() { return type; }

	/**
	 * データ量 情報を取得します。
	 *
	 * @return	データ量 情報
	 */
	public long getLength() { return length; }

	/**
	 * 作成日時 情報を取得します。
	 *
	 * @return	作成日時
	 */
	public long getDate() { return date; }

	/**
	 * 更新日時 情報を取得します。
	 *
	 * @return	更新日時
	 */
	public long getModified() { return modified; }

	/**
	 * 結果コード 情報(HttpURLConnection)を取得します。
	 *
	 * @return	結果コード 情報
	 */
	public int getCode() { return rpsCode; }

	/**
	 * メソッド 情報(HttpURLConnection)を取得します。
	 *
	 * @return	メソッド 情報
	 */
	public String getMethod() { return rpsMethod; }

	/**
	 * メッセージ 情報(HttpURLConnection)を取得します。
	 *
	 * @return	メッセージ 情報
	 */
	public String getMessage() { return rpsMessage; }

	/**
	 * キャラクタ 情報を取得します。
	 *
	 * @return	キャラクタ 情報
	 */
	public String getCharset() { return charset; }

	/**
	 * キャラクタ 情報を設定します。
	 *
	 * @param  chset キャラクタ 情報
	 */
	public void setCharset( final String chset ) { charset = chset; }

	/**
	 * 接続タイムアウト時間を(秒)で指定します
	 *
	 * 実際には、java.net.URLConnection#setConnectTimeout(int) に 1000倍して設定されます。
	 * 0 は、無限のタイムアウト、マイナスは、設定しません。(つまりJavaの初期値のまま)
	 *
	 * @og.rev 6.2.5.0 (2015/06/05) timeout属性追加
	 *
	 * @param	tout	タイムアウト時間(秒) (ゼロは、無制限)
	 * @see		java.net.URLConnection#setConnectTimeout(int)
	 */
	public void setTimeout( final int tout ) {
		timeout = tout;
	}

	/**
	 * 接続先のデータのリーダーを取得します。
	 *
	 * この処理の前に、connect() 処理を実行しておく必要があります。
	 * 取得したデータは、指定のURL へのアクセスのみです。
	 * 通常のWebブラウザは、イメージや、JavaScriptファイル、CSSファイルなど、
	 * 各種ファイル毎にHTTP接続を行い、取得して、レンダリングします。
	 * このメソッドでの処理では、それらのファイル内に指定されているURLの
	 * 再帰的な取得は行いません。
	 * よって、フレーム処理なども行いません。
	 *
	 * @og.rev 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs)
	 * @og.rev 6.4.2.0 (2016/01/29) fukurou.util.StringUtil → fukurou.system.HybsConst に変更
	 *
	 * @return	接続結果のリーダー
	 * @throws  IOException 入出力エラーが発生したとき
	 */
	public BufferedReader getReader() throws IOException {
		// 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs)
		if( conn == null ) {
			final String errMsg = "connect() されていません。データ取得前にconnect()してください。" + CR
							+ "  urlStr=" + urlStr + CR ;
			throw new OgRuntimeException( errMsg );
		}

		final InputStream in = conn.getInputStream();

		// 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..;
		return charset == null
//					? new BufferedReader( new InputStreamReader( in,StringUtil.DEFAULT_CHARSET ) )		// 5.5.2.6 (2012/05/25) findbugs対応
					? new BufferedReader( new InputStreamReader( in,HybsConst.DEFAULT_CHARSET ) )		// 6.4.2.0 (2016/01/29)
					: new BufferedReader( new InputStreamReader( in,charset ) );

//		final BufferedReader reader ;
//		if( charset != null ) {
//			reader = new BufferedReader( new InputStreamReader( in,charset ) );
//		}
//		else {
//			reader = new BufferedReader( new InputStreamReader( in,StringUtil.DEFAULT_CHARSET ) );		// 5.5.2.6 (2012/05/25) findbugs対応
//		}
//
//		return reader;
	}

	/**
	 * 接続先のデータの入力ストリームを取得します。
	 *
	 * この処理の前に、connect() 処理を実行しておく必要があります。
	 * 取得したデータは、指定のURL へのアクセスのみです。
	 * 通常のWebブラウザは、イメージや、JavaScriptファイル、CSSファイルなど、
	 * 各種ファイル毎にHTTP接続を行い、取得して、レンダリングします。
	 * このメソッドでの処理では、それらのファイル内に指定されているURLの
	 * 再帰的な取得は行いません。
	 * よって、フレーム処理なども行いません。
	 *
	 * @og.rev 5.4.2.0 (2011/12/01) 新規追加
	 * @og.rev 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs)
	 *
	 * @return	接続結果の入力を出力します。
	 * @og.rtnNotNull
	 * @throws  IOException 入出力エラーが発生したとき
	 */
	public InputStream getInputStream() throws IOException {
		// 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs)
		if( conn == null ) {
			final String errMsg = "connect() されていません。データ取得前にconnect()してください。" + CR
							+ "  urlStr=" + urlStr + CR ;
			throw new OgRuntimeException( errMsg );
		}

		return new BufferedInputStream( conn.getInputStream() );		// 5.5.2.4 (2012/05/16)
	}

	/**
	 * HttpURLConnection のレスポンスコードに対応するメッセージ文字列を返します。
	 *
	 * HttpURLConnection の getResponseCode() メソッドにより取得された、HTTPレスポンスコード
	 * に対応する文字列を返します。この文字列は、HttpURLConnection で定義された
	 * static 定数のコメントを、定義しています。
	 *
	 * @og.rev 5.6.7.0 (2013/07/27) レスポンスコード例 追加
	 *
	 * @param	code	HTTPレスポンスコード
	 *
	 * @return	レスポンスコードに対応する文字列
	 * @og.rtnNotNull
	 * @see HttpURLConnection#HTTP_ACCEPTED
	 */
	public static String code2Message( final int code ) {
		final String msg ;
		switch( code ) {
			case 100					 					: msg = "100: 要求は続行可能です。"						;	break;	// 5.6.7.0 (2013/07/27)
			case 101										: msg = "101: プロトコルを切り替えます。"				;	break;	// 5.6.7.0 (2013/07/27)
			case HttpURLConnection.HTTP_OK 					: msg = "200: OK です。"								;	break;
			case HttpURLConnection.HTTP_CREATED 			: msg = "201: 作成されました。"							;	break;
			case HttpURLConnection.HTTP_ACCEPTED			: msg = "202: 許可されました。"							;	break;
			case HttpURLConnection.HTTP_NOT_AUTHORITATIVE 	: msg = "203: 不当な情報です。"							;	break;
			case HttpURLConnection.HTTP_NO_CONTENT 			: msg = "204: コンテンツがありません。"					;	break;
			case HttpURLConnection.HTTP_RESET 				: msg = "205: コンテンツをリセットします。"				;	break;
			case HttpURLConnection.HTTP_PARTIAL 			: msg = "206: 部分的なコンテンツです。"					;	break;
			case HttpURLConnection.HTTP_MULT_CHOICE 		: msg = "300: 複数選択されています。"					;	break;
			case HttpURLConnection.HTTP_MOVED_PERM 			: msg = "301: 永続的に移動されました。"					;	break;
			case HttpURLConnection.HTTP_MOVED_TEMP 			: msg = "302: 一時的に切り替えます。"					;	break;
			case HttpURLConnection.HTTP_SEE_OTHER 			: msg = "303: 他を参照してください。"					;	break;
			case HttpURLConnection.HTTP_NOT_MODIFIED 		: msg = "304: 修正されませんでした。"					;	break;
			case HttpURLConnection.HTTP_USE_PROXY 			: msg = "305: プロキシを使用してください。"				;	break;
			case 306					 					: msg = "306: 仕様の拡張案です。"						;	break;	// 5.6.7.0 (2013/07/27)
			case 307										: msg = "307: 一時的なリダイレクトです。"				;	break;	// 5.6.7.0 (2013/07/27)
			case HttpURLConnection.HTTP_BAD_REQUEST 		: msg = "400: 不当な要求です。"							;	break;
			case HttpURLConnection.HTTP_UNAUTHORIZED 		: msg = "401: 認証されませんでした。"					;	break;
			case HttpURLConnection.HTTP_PAYMENT_REQUIRED 	: msg = "402: 支払いが必要です。"						;	break;
			case HttpURLConnection.HTTP_FORBIDDEN 			: msg = "403: 禁止されています。"						;	break;
			case HttpURLConnection.HTTP_NOT_FOUND 			: msg = "404: 見つかりませんでした。"					;	break;
			case HttpURLConnection.HTTP_BAD_METHOD 			: msg = "405: メソッドは許可されません。"				;	break;
			case HttpURLConnection.HTTP_NOT_ACCEPTABLE 		: msg = "406: 許容されません。"							;	break;
			case HttpURLConnection.HTTP_PROXY_AUTH 			: msg = "407: プロキシの認証が必要です。"				;	break;
			case HttpURLConnection.HTTP_CLIENT_TIMEOUT 		: msg = "408: 要求が時間切れです。"						;	break;
			case HttpURLConnection.HTTP_CONFLICT 			: msg = "409: 重複しています。"							;	break;
			case HttpURLConnection.HTTP_GONE 				: msg = "410: 存在しません。"							;	break;
			case HttpURLConnection.HTTP_LENGTH_REQUIRED 	: msg = "411: 長さが必要です。"							;	break;
			case HttpURLConnection.HTTP_PRECON_FAILED 		: msg = "412: 前提条件が正しくありません。"				;	break;
			case HttpURLConnection.HTTP_ENTITY_TOO_LARGE 	: msg = "413: 要求エンティティが長すぎます。"			;	break;
			case HttpURLConnection.HTTP_REQ_TOO_LONG 		: msg = "414: 要求 URL が長すぎます。"					;	break;
			case HttpURLConnection.HTTP_UNSUPPORTED_TYPE 	: msg = "415: サポートされないメディアタイプです。"		;	break;
			case 416					 					: msg = "416: 要求された範囲は不十分です。"				;	break;	// 5.6.7.0 (2013/07/27)
			case 417										: msg = "417: 要求どおりの処理が不可能です。"			;	break;	// 5.6.7.0 (2013/07/27)
			case HttpURLConnection.HTTP_INTERNAL_ERROR 		: msg = "500: 内部サーバエラーです。"					;	break;
			case HttpURLConnection.HTTP_NOT_IMPLEMENTED 	: msg = "501: 実装されていません。"						;	break;
			case HttpURLConnection.HTTP_BAD_GATEWAY 		: msg = "502: 誤ったゲートウェイです。"					;	break;
			case HttpURLConnection.HTTP_UNAVAILABLE 		: msg = "503: サービスが利用できません。"				;	break;
			case HttpURLConnection.HTTP_GATEWAY_TIMEOUT 	: msg = "504: ゲートウェイが時間切れです。"				;	break;
			case HttpURLConnection.HTTP_VERSION 			: msg = "505: HTTP バージョンがサポートされていません。";	break;
			default 										: msg = code + ": 未定義" 								;	break;	// 6.0.2.5 (2014/10/31) break追記
		}
		return msg ;
	}

	/**
	 * サンプル実行用のメインメソッド
	 *
	 * Usage: java org.opengion.fukurou.util.URLConnect [-info/-data] … url [user:passwd]
	 *
	 *   args[*] : [-info/-data]       情報の取得か、データの取得かを指定します(初期値:-data)。
	 *   args[*] : [-post=ファイル名]  POSTメソッドを指定して、ファイルデータを送信します(初期値:-get)。
	 *   args[*] : [-timeout=1]        接続タイムアウト時間を(秒)で指定します
	 *   args[*] : [-encode=UTF-8]     エンコードを指定します(通常は接続先のencodeを使用)
	 *   args[*] : [-out=ファイル名]   結果をファイルに出力します。ファイルエンコードも指定します。
	 *   args[*] : [-errEx=true/false] trueの場合、ﾚｽﾎﾟﾝｽｺｰﾄﾞが、4XX,5XX の時に RuntimeException を投げます(初期値:false)。
	 *   args[A] : url                 ＵＲＬを指定します。GETの場合、パラメータは ?KEY=VALです。
	 *   args[B] : [user:passwd]       BASIC認証のエリアへのアクセス時に指定します。
	 *
	 * @og.rev 5.6.7.0 (2013/07/27) -errEx 追加
	 * @og.rev 6.2.5.0 (2015/06/05) timeout属性追加	
	 *
	 * @param	args	コマンド引数配列
	 * @throws IOException 入出力エラーが発生したとき
	 */
	public static void main( final String[] args ) throws IOException {
		if( args.length < 3 ) {
			LogWriter.log( "Usage: java org.opengion.fukurou.util.URLConnect [-info/-data] … url [user:passwd]"				);
			LogWriter.log( "   args[*] : [-info/-data]       情報の取得か、データの取得かを指定します(初期値:data)"				);
			LogWriter.log( "   args[*] : [-post=ファイル名]  POSTメソッドを指定して、ファイルデータを送信します"				);
			LogWriter.log( "   args[*] : [-timeout=1]        接続タイムアウト時間を(秒)で指定します(初期値:-1)"					);
			LogWriter.log( "   args[*] : [-encode=UTF-8]     エンコードを指定します。(通常は接続先のencodeを使用)"				);
			LogWriter.log( "   args[*] : [-out=ファイル名]   結果をファイルに出力します。ファイルエンコードも指定します"		);
			LogWriter.log( "   args[*] : [-errEx=true/false] trueの場合、ﾚｽﾎﾟﾝｽｺｰﾄﾞが、4XX,5XX の時に RuntimeException を投げます(初期値:false)" );
			LogWriter.log( "   args[A] : url                 ＵＲＬを指定します。GETの場合、パラメータは ?KEY=VALです"			);
			LogWriter.log( "   args[B] : [user:passwd]       BASIC認証のエリアへのアクセス時に指定します"						);
			return;
		}

		boolean isInfo	= false ;
		boolean isPost	= false ;
		int    timeout	= -1 ;					// 6.2.5.0 (2015/06/05) timeout属性追加
		String postKey	= null ;
		String postFile	= null ;
		String encode	= null ;
		String outFile	= null ;
		boolean isEx	= false ;				// 5.6.7.0 (2013/07/27) 追加
		String[] vals	= new String[2];		// url,userPass の順に引数設定

		int adrs = 0;
		for( int i=0; i<args.length; i++ ) {
			final String arg = args[i];
//			if( arg.equalsIgnoreCase( "-info" ) ) {
			if( "-info".equalsIgnoreCase( arg ) ) {			// 6.4.1.1 (2016/01/16) PMD refactoring. Position literals first in String comparisons for EqualsIgnoreCase.
				isInfo = true;
			}
//			else if( arg.equalsIgnoreCase( "-data" ) ) {
			else if( "-data".equalsIgnoreCase( arg ) ) {	// 6.4.1.1 (2016/01/16) PMD refactoring. Position literals first in String comparisons for EqualsIgnoreCase.
				isInfo = false;
			}
			else if( arg.startsWith( "-post=" ) ) {
				isPost = true;
				final int sepAdrs = arg.indexOf( ':',"-post=".length() );
				postKey  = arg.substring( "-post=".length(),sepAdrs );
				postFile = arg.substring( sepAdrs+1 );
			}
			else if( arg.startsWith( "-timeout=" ) ) {						// 6.2.5.0 (2015/06/05) timeout属性追加
				timeout = Integer.parseInt( arg.substring( "-timeout=".length() ) );
			}
			else if( arg.startsWith( "-encode=" ) ) {
				encode = arg.substring( "-encode=".length() );
			}
			else if( arg.startsWith( "-out=" ) ) {
				outFile = arg.substring( "-out=".length() );
			}
			else if( arg.startsWith( "-errEx=" ) ) {						// 5.6.7.0 (2013/07/27) 追加
				isEx = "true".equalsIgnoreCase( arg.substring( "-errEx=".length() ) );
			}
			else if( StringUtil.startsChar( arg , '-' ) ) {					// 6.2.0.0 (2015/02/27) １文字 String.startsWith
				System.out.println( "Error Argment:" + arg );
			}
			else {
				vals[adrs++] = arg;
			}
		}

		final String urlStr	= vals[0] ;
		final String userPass = vals[1] ;

		final URLConnect conn = new URLConnect( urlStr,userPass );

		// POST データは、connect() する前に、設定します。
		if( isPost ) {
			final FileString file = new FileString();
			file.setFilename( postFile );
			final String postData = file.getValue();

			conn.setPostData( XHTMLTag.urlEncode(postKey, postData) );
		}

		conn.connect();
		// 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..;
		if( encode == null ) {
			encode = conn.getCharset();		// 指定がなければ、接続先の charset を使用
		}
		else {
			conn.setCharset( encode );		// encode 指定
		}

//		if( encode != null ) {
//			conn.setCharset( encode );		// encode 指定
//		}
//		else {
//			encode = conn.getCharset();		// 指定がなければ、接続先の charset を使用
//		}

		conn.setTimeout( timeout );			// 6.2.5.0 (2015/06/05) timeout属性追加

		// 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..;
		final PrintWriter writer = outFile == null
										? FileUtil.getLogWriter( "System.out" )
										: FileUtil.getPrintWriter( new File( outFile ),encode );

//		final PrintWriter writer ;
//		if( outFile != null ) {
//			writer = FileUtil.getPrintWriter( new File( outFile ),encode );
//		}
//		else {
//			writer = FileUtil.getLogWriter( "System.out" );
//		}

		final int code = conn.getCode();		// 5.6.7.0 (2013/07/27) レスポンスコードは、常に拾っておきます。
		if( isInfo ) {
			writer.println( "URL    :" + conn.getUrl() );
			writer.println( "Type   :" + conn.getType() );
			writer.println( "Code   :" + code );					// 5.6.7.0 (2013/07/27) 取得済みの値を利用。
			writer.println( "Message:" + conn.getMessage() );
			writer.println( "Charset:" + conn.getCharset() );
		}
		else {
			writer.println( conn.readData() );
		}

		conn.disconnect();

		Closer.ioClose( writer );

		// 5.6.7.0 (2013/07/27) trueの場合、ﾚｽﾎﾟﾝｽｺｰﾄﾞが、4XX,5XX の時に RuntimeException を投げます
		if( isEx && code >= 400 ) {
			final String errMsg = URLConnect.code2Message( code );
			throw new OgRuntimeException( errMsg );
		}
	}
}
