package org.itscool.commons.util;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;

/**
 * ■UTF-8 と Shift_JIS
 * いわずと知れたことかもしれませんが、Java 内部では日本語などの ２バイトコードを
 * 管理するのに Unicode を用いています。
 *
 * プラットホームに依存しないコードを書くために、これは非常に重要なことです。
 * しかし、Java はその有用性が認められてからまだ日が浅いせいか、 プラットフォーム
 * では依然として OS に依存した文字セットが使用されています。
 *
 * Java を Java として、Stand Alone Application としてや、Applet Game として
 * 使用する場合はいいのですが、 HTTP Server をはじめとする Client/Server
 * アプリケーションを構築する時にはそれなりの考慮が必要です。
 *
 * 特に Client がブラウザである場合、日本語（に関わらず ASCI コード以外の語）は
 * URLEncoding という方式で、 エンコードされます。
 * これは ASCI 文字で表せない文字を変換し、ASCI 文字だけで送信するという方法です。
 *
 * URLEncoding の詳細はさておき、Java におけるエンコーディングの注意点を解説します
 *
 * ■Java における URLEncoding
 * Java では URLEncoding を処理するためあらかじめ、URLEncoder と URLDecoder という
 * クラスが定義されています。
 * これが Java のクラスライブラリの便利なところで、 ネットワークプログラミングに
 * 必要な物は大抵ライブラリとして提供されています。
 *
 * しかし、です。
 *
 * Java が普及し、業務アプリケーションの開発にも使われ始めた昨今、 J2SE 1.3 から
 * 1.4 へとバージョンが上がり、URLEncoder の仕様に若干の変更がありました。
 *
 * 【変更点】
 *
 * ・URLEncoding に、変換対象の文字セットを指定できるようになった（Recomend）
 * ・Encoding/Decoding の方式が、W3C の仕様に準拠するようになった。
 * ・等等…。
 *
 * この、２点目が思わぬ影響を引き起こしてしまったのです。
 * ではこれまで使用されてきたエンコーディング方式とは何が違うのでしょう？
 *
 * 例えば、"テスト"という文字列を Shift_JIS エンコードするとします。
 *
 * まず、Java の URLEncoder のエンコード結果では、
 *
 * %83%65%83%58%83%67
 *
 * となりますが、Web ブラウザでエンコードすると、
 *
 * %83e%83X%83g
 *
 * となります。両者の変換結果が違いますよね？
 *
 * これでは使い物になりません。
 *
 * Shift_JIS のエンコードの方式が若干変更された結果、このような差異が発生する
 * ようになりました。
 *
 * これまでは Web ブラウザと同じエンコーディング方式だったのですが、 J2SE 1.4 からは
 * ２バイトコードの取り扱いに変更が加わったからです。
 *
 * では、UTF-8 ではどうでしょう。
 * J2SE API Document にもあるように、 J2SE 1.4 以降では、Unicode 系以外の文字セット
 * は結果を保証しないようです。 これに従い、UTF-8 でのコーディングをします。
 *
 * しかしここで新たな問題にぶつかります。
 *
 * UTF-8 に対応したブラウザが、極端に少ないのです。表示ができるものは多くあります。
 * しかし UTF-8 によるエンコーディングに対応したブラウザは、ごく少数しかありません。
 *  確認したところで、Safari 1.0、IE6.0 ぐらいでした。殆どはインプットフォームが文字
 *  化けします。 Java なのにプラットフォームに依存？　とか思ってしまいます。
 *
 *  （追記：なんと、同じIE6.0でも、MacOS用とWindows用でも取り扱いに違いがあるようです。
 *  これにLinux用ブラウザや、他のブラウザの存在を考えると、ブラウザタイプを環境変数か
 *  ら取り込んでサーバ側で対応するのはとても面倒ですよね）
 *
 *  そうです。
 *
 *  結局のところクライアント側から日本語データを受け取るためには Shift_JIS を使用する
 *  しかないのです。
 *  現在普及している殆どのブラウザで、 動作が保証されているのは Shift_JIS しかないの
 *  ですから。
 *  （将来性を考慮するなら、Client のプラットホームによって UTF-8 が 処理できない場
 *  合のみ Shift_JIS を用いる方が良いでしょう…）
 */
public class URLDecodeUtil{
    /**
     * ■Shift_JIS をデコードする
     *
     * ブラウザによるエンコード結果の、
     *
     * %83e%83X%83g
     *
     * これを見て気づくこと、それはブラウザによる Shift_JIS エンコードは、２バイト
     * 文字のうち 後ろのバイトを Alpha Newmeric で表示しているということです。
     *
     * つまり、２バイト目を エンコードする際、ASCI 文字コードの範囲にあるものは、
     * そのままの形式で表示しているのです。
     *
     * こうすることによって転送するデータ量を減らそうということでしょうか。
     * なんにせよ W3C の正式な仕様ではなく、 この（Java 曰く行儀の悪い）方法が、
     * デファクトスタンダードとして一般に使用されています。
     *
     * 単一文字 'e' を１６進数表示をすると、"65" になります （X="58"、g="67"。
     * Java によるエンコーディング結果と比較してみて）。この要領で変換していくと
     * Java でも理解できるエンコード形式に置き換えることができます。
     *
     * サンプルはこんな感じです。
     *
     * 文字列中にエスケープ記号の％があるとき、その次の２文字はShift_JISの１文字目と
     * いうことになります。
     *
     * このときは何もしません。
     * ３文字目も%であれば同じく何もしません。
     * ３文字目が％でない場合、その文字のASCIコードを１６進数表記に直し%と一緒に文字
     * 列に追加します。
     *
     * これで2BYTE文字を変換できました。
     * これを繰り返すことによってShift_JIS文字列をJava URLDecoderが理解できる形式に
     * 変換します。当然、すべてのエンコード結果に%がついている場合でも問題ありません。
     *
     * これを URLDecoder クラスの decode() メソッドの中に記述しましょう。
     * オーバライドするなり、Java RuntimeのZIPファイルを解凍するなりして編集すれば
     * よいと思います。
     *
     * （ライセンスの問題があるかもしれません。。。）まあ、URLDecode結果を正規化
     * するメソッドとして追加記述するのが一番簡単だと思いますが。
     *
     * このロジックでは、正しいエンコード方法での文字列には影響を与えません。
     *  後はこれで変換された文字列を通常通り decode() すれば、Shift_JIS をデコード
     *  完了です。
     *  だいぶ力技ですが、Clientを制限、変更することを考えればはるかにまし
     *
     * @param s 変換元文字列
     * @param enc エンコード名
     * @return 変換後文字列
     * @throws UnsupportedEncodingException
     */
    public static String decode(String s, String enc)
    throws UnsupportedEncodingException{
        if (enc.toUpperCase().compareTo("UTF-8") != 0) {
            StringBuffer tmp = new StringBuffer();
            char pc = 0;
            int j = 0;
            while (j < s.length()) {
                pc = s.charAt(j);
                if (pc == '%') {
                    if (Integer.parseInt(s.substring(j + 1, j + 3), 16) < 0x81) {
                        tmp.append(s.substring(j, j + 3));
                        j += 3;
                        continue;
                    }
                    tmp.append(s.substring(j, j + 3));
                    j += 3;
                    if ((pc = s.charAt(j)) == '%') {
                        tmp.append(s.substring(j, j + 3));
                        j += 3;
                    } else {
                        tmp.append('%');
                        tmp.append(Integer.toHexString((int) pc));
                        j++;
                    }
                } else {
                    tmp.append(pc);
                    j++;
                }
            }
            s = tmp.toString();
            //numChars = s.length();
        }
        return URLDecoder.decode(s, enc);
    }
    
    /**
     * リクエストパラメータのフィルタ処理を行います。
     * @param value 変換元文字列
     * @return フィルタ処理後のパラメータを返します
     */
    public static String filter(String value){
        if( value== null ){
            return "";
        }
        
        StringBuffer result = new StringBuffer();
        for( int i=0; i<value.length(); i++){
            switch( value.charAt(i)){
                case '<' :
                    result.append("&lt;");
                    break;
                case '>' :
                    result.append("&gt;");
                    break;
                case '&' :
                    result.append("&amp;");
                    break;
                case '"' :
                    result.append("&quot;");
                    break;
                default :
                    result.append(value.charAt(i));
                    
            }
        }
        return result.toString();
    }
}
