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

import java.lang.reflect.Field;

import com.sun.javadoc.Tag;
import com.sun.javadoc.Doc;
import com.sun.javadoc.MethodDoc;
import com.sun.javadoc.ClassDoc;
import com.sun.javadoc.PackageDoc;
import com.sun.javadoc.ProgramElementDoc;

/**
 * Doclet を処理するプログラムで共通して使用される簡易メソッド群(ユーティリティクラス)です。
 *
 * @version  4.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
public final class DocletUtil {
	/** リターンコード  System.getProperty("line.separator")  */
	public static final String CR = System.getProperty("line.separator");

	private static final String CLS = "org.opengion.hayabusa.common.BuildNumber";		// package.class
	private static final String FLD = "VERSION_NO";										// field
	private static String versionNo = null;	// 最初に一度だけ取得しておきます。

	/**
	 * すべてが staticメソッドなので、コンストラクタを呼び出さなくしておきます。
	 *
	 */
	private DocletUtil() {}

	/**
	 * 入力文字列の HTML 文字をフィルタリングします。
	 * "&" は、"&amp;#38;" 、"<" は、"&amp;lt;" 、">" は、"&amp;gt;" に変換します。
	 * StringUtil.htmlFilter との違いは、';' → "&#59;" への変換があることです。
	 *
	 * @og.rev 5.5.4.1 (2012/07/06) DocletUtil.htmlFilter → StringUtil.htmlFilter に変更のため、廃止
	 *
	 * @param input String
	 *
	 * @return 変換後文字列
	 */
//	public static String htmlFilter( final String input ) {
//		if( input == null ) { return ""; }
//
//		int len = input.length();
//
//		StringBuilder rtn = new StringBuilder( len + 50 );
//		char ch;
//		for(int i=0; i<len; i++) {
//			ch = input.charAt(i);
//			switch( ch ) {
//				case '<'  : rtn.append("&lt;");   break;
//				case '>'  : rtn.append("&gt;");   break;
//				case '"'  : rtn.append("&quot;"); break;
//				case '\'' : rtn.append("&#39;");  break;
//				case '&'  : rtn.append("&amp;");  break;
//				case ';'  : rtn.append("&#59;");  break;
//				default   : rtn.append(ch);
//			}
//		}
//		return( rtn.toString() );
//	}

	/**
	 * target 文字列に含まれる from 文字列を to 文字列に置き換えます。
	 *
	 * @param	target	元の文字列
	 * @param	from	置換元FROM
	 * @param	to		置換先TO
	 *
	 * @return	変換後文字列
	 */
	public static String replace( final String target,final String from,final String to ) {
		if( target == null || from == null || to == null ) { return target; }
		StringBuilder strBuf = new StringBuilder( 200 );

		int start = 0;
		int end   = target.indexOf( from,start );
		while( end >= 0  ) {
			strBuf.append( target.substring( start,end ) );
			strBuf.append( to );
			start = end + from.length();
			end   = target.indexOf( from,start );
		}
		strBuf.append( target.substring( start ) );

		return strBuf.toString();
	}

	/**
	 * コメント部の文字列を取得します。
	 *
	 * @og.rev 5.5.4.1 (2012/07/06) コメントは文字列でなく、Tag配列として処理させる。
	 *
	 * @param cmnt String
	 *
	 * @return コメント文字列
	 */
//	public static String commentText( final String cmnt ) {
//		if( cmnt == null ) { return ""; }
//
//		String rtn = cmnt;
//		int indx = cmnt.indexOf( "{@value}" );
//		if( indx >= 0 ) {
//			rtn = cmnt.substring( indx+8 );	// {@value} 以前を削除
//		}
//		return htmlFilter( rtn );
//	}

	/**
	 * セッターメソッドの setXXXX の set を削除し、次の文字を小文字化します。
	 * つまり、セッターメソッドから属性値を推測します。
	 * (超特殊処理)セッターメソッドのset以下２文字目が大文字の場合は、
	 * １文字目も大文字と考えて小文字化を行いません。
	 * 例えば、setSYS や setUSER など、RequestValueTag.javaに使用するケースです。
	 *
	 * @param target 処理対象となる文字列
	 *
	 * @return オプション文字列
	 */
	public static String removeSetter( final String target ) {
		if( target != null && target.startsWith( "set" ) ) {
			char[] chs = target.toCharArray();
			if( chs.length > 4 && !( chs[4] >= 'A' && chs[4] <= 'Z' ) ) {
				chs[3] = Character.toLowerCase( chs[3] ) ;
			}
			return new String( chs,3,chs.length-3 );
		}
		return target;
	}

	/**
	 * オプション配列文字列より、指定のキーに対応するオプション値を返します。
	 *
	 * @param	key		キー
	 * @param	options	オプション配列文字列
	 *
	 * @return	オプション文字列
	 */
	public static String getOption( final String key , final String[][] options ) {
		String rtn = "";
		if( key == null || options == null ) { return rtn; }

		for( int i=0; i<options.length; i++ ) {
			if( key.equalsIgnoreCase( options[i][0] ) ) {
				rtn = options[i][1];
				break ;
			}
		}
		return rtn ;
	}

	/**
	 * {&#064;og.value package.class#field} 形式のvalueタグを文字列に置き換えます。
	 *
	 * 処理的には、リフレクションで、値を取得します。値は、staticフィールドのみ取得可能です。
	 *
	 * @og.rev 5.5.4.1 (2012/07/06) 新規追加
	 * @og.rev 5.5.5.6 (2012/08/31) クラス名の取得で、ProgramElementDoc で処理するように変更
	 *
	 * @param tag Tagオブジェクト
	 *
	 * @return valueタグの解析結果の文字列
	 */
	public static String valueTag( final Tag tag ) {
		String txt = tag.text();
		if( txt != null ) {
			String cls = null;		// package.class
			String fld = null;		// field

			// package.class#field 形式の解析。
			int adrs = txt.indexOf('#') ;
			if( adrs > 0 ) {
				cls = txt.substring( 0,adrs );		// package.class
				fld = txt.substring( adrs+1 );		// field
			}
			else if( adrs == 0 ) {
				fld = txt.substring( 1 );			// #field
			}
			else {
				String errMsg = "警告:{@value package.class#field} 形式の フィールド名 #field がありません。" + CR
								+ tag.position() + " : " + txt + CR ;
				System.err.println( errMsg );
				// # を付け忘れたと考え、自分自身のクラスを利用
				fld = txt;							// field
			}

			// package.class をきちんと作成する。
			Doc doc = tag.holder();
//			if( doc.isMethod() ) {
//				MethodDoc mdoc = (MethodDoc)doc;
//				ClassDoc cdoc = mdoc.containingClass();
//				if( cls == null ) {					// package.class が登録されていない場合。
//					cls = cdoc.qualifiedName() ;
//				}
//				else if( cls.indexOf('.') < 0 ) {	// class のみが登録されている場合。findClass で、package 込の正式クラス名を検索する。
//					ClassDoc pdoc = cdoc.findClass( cls );
//					cls = pdoc.qualifiedName() ;
//				}
//			}

			// 5.5.5.6 (2012/08/31) ProgramElementDoc で処理するように変更
			if( doc instanceof ProgramElementDoc ) {
				ProgramElementDoc pdoc = (ProgramElementDoc)doc;
				ClassDoc cdoc = pdoc.containingClass();
				if( cdoc != null ) {
					if( cls == null ) {					// package.class が登録されていない場合。
						cls = cdoc.qualifiedName() ;
					}
					else if( cls.indexOf('.') < 0 ) {	// class のみが登録されている場合。findClass で、package 込の正式クラス名を検索する。
						ClassDoc fdoc = cdoc.findClass( cls );
						if( fdoc != null ) {
							cls = fdoc.qualifiedName() ;
						}
					}
				}
				else {
					if( cls == null ) {					// package.class が登録されていない場合。
						cls = pdoc.qualifiedName() ;
					}
				}
			}

			//  5.6.3.3 (2013/04/19) メソッド化で共有します。
			txt = getStaticField( cls,fld );

//			try {
//				Field fldObj = Class.forName( cls ).getDeclaredField( fld );
//				// privateフィールドへのアクセス。(セキュリティーマネージャーによってアクセス制限がかけられていない場合)
//				if( !fldObj.isAccessible() ) { fldObj.setAccessible( true ); }
//				txt = String.valueOf( fldObj.get( null ) );		// static フィールドは、引数 null で値を取得
//			}
//			catch( Exception ex ) {
//				String errMsg = "警告:{@value package.class#field} 形式の対象がありません。" + CR
//								+ tag.position() + " : " + tag.text() + CR
//								+ ex.getMessage() + CR;
//				System.err.println( errMsg );
//			}
		}
		return txt ;
	}

	/**
	 * {&#064;og.doc03Link queryType Query_**** クラス} 形式のdoc03Linkタグをリンク文字列に置き換えます。
	 *
	 * <a href="/gf/jsp/DOC03/index.jsp?command=NEW&GAMENID=DOC03&VERNO=X.X.X.X&VALUENAME=queryType" target="CONTENTS">Query_**** クラス</a>
	 * のようなリンクを作成します。
	 * 第一引数は、VALUENAME の引数です。
	 * それ以降のテキストは、リンク文字列のドキュメントになります。
	 * DOC03 画面へのリンクを作成するに当たり、バージョンが必要です。org.opengion.hayabusa.common.BuildNumber#VERSION_NO から取得しますが、
	 * パッケージの優先順の関係で、リフレクションを使用します。
	 *
	 * @og.rev 5.6.3.3 (2013/04/19) 新規作成
	 *
	 * @param tag Tagオブジェクト
	 *
	 * @return valueタグの解析結果の文字列
	 */
	public static String doc03LinkTag( final Tag tag ) {
		if( versionNo == null ) { versionNo = getStaticField( CLS , FLD ); }

		String txt = tag.text();
		if( txt != null ) {
			String valnm = null;			// VALUENAME
			String body  = null;			// ドキュメント

			int adrs = txt.indexOf(' ') ;	// 最初のスペースで分離します。
			if( adrs > 0 ) {
				valnm = txt.substring( 0,adrs );		// VALUENAME
				body  = txt.substring( adrs+1 );		// ドキュメント
			}
			else {
				valnm = txt;							// VALUENAME
				body  = txt;							// ドキュメント
			}

			txt = "&lt;a href=\"/gf/jsp/DOC03/index.jsp?command=NEW&amp;GAMENID=DOC03"
					+ "&amp;VERNO="     + versionNo
					+ "&amp;VALUENAME=" + valnm
					+ "\" target=\"CONTENTS\"&gt;"
					+ body
					+ "&lt;/a&gt;" ;
		}
		return txt ;
	}

	/**
	 * パッケージ.クラス名 と、フィールド名 から、staticフィールドの値を取得します。
	 *
	 * Field fldObj = Class.forName( cls ).getDeclaredField( fld ); で、Fieldオブジェクトを呼出し、
	 * String.valueOf( fldObj.get( null ) ); で、値を取得しています。
	 * static フィールドは、引数 null で値を取得できます。
	 *
	 * 例；
     *      String cls = "org.opengion.hayabusa.common.BuildNumber";        // package.class
     *      String fld = "VERSION_NO";                                      // field
	 *
	 * @og.rev 5.6.3.3 (2013/04/19) 新規作成
	 *
	 * @param cls パッケージ.クラス名
	 * @param fld フィールド名
	 * @return 取得値
	 */
	public static String getStaticField( final String cls , final String fld ) {

		String txt = null;
		try {
			Field fldObj = Class.forName( cls ).getDeclaredField( fld );
			// privateフィールドへのアクセス。(セキュリティーマネージャーによってアクセス制限がかけられていない場合)
			if( !fldObj.isAccessible() ) { fldObj.setAccessible( true ); }
			txt = String.valueOf( fldObj.get( null ) );		// static フィールドは、引数 null で値を取得
		}
		catch( Exception ex ) {
			String errMsg = "package.class = " + cls + " field = " + fld + " の取得に失敗しました。"
							+ ex.getMessage() + CR;
			System.err.println( errMsg );
		}

		return txt ;
	}
}
