/*
 * 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 org.opengion.fukurou.system.LogWriter;

import com.sun.javadoc.RootDoc;
import com.sun.javadoc.ClassDoc;
import com.sun.javadoc.Type;
import com.sun.javadoc.Tag;

import java.util.Map;
import java.util.HashMap;
import java.io.IOException;

/**
 * ソースコメントから、属性情報を取り出す Doclet クラスです。
 *
 * @version  4.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
@SuppressWarnings(value={"deprecation","removal"})			// Ver7.0.0.0 , 7.2.2.0 (2020/03/27)
public final class DocletPlugin {
	/** 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。  */
	// 6.4.3.3 (2016/03/04) 匿名クラスとインスタンス初期化子による、Mapの初期化処理。
	/** staticイニシャライザ後、読み取り専用にするので、ConcurrentHashMap を使用しません。 */
	private static final Map<String,AttKeySet> ATT_KEY_MAP = new HashMap<>();
	static {
		int no = 0;				// 7.2.2.0 (2020/03/27) 連番の自動生成
		ATT_KEY_MAP.put( "org.opengion.hayabusa.db.Query"					, new AttKeySet( "Query"			,no++, "queryType"		));
		ATT_KEY_MAP.put( "org.opengion.hayabusa.db.CellRenderer"			, new AttKeySet( "Renderer"			,no++, "renderer"		));
		ATT_KEY_MAP.put( "org.opengion.hayabusa.db.CellEditor"				, new AttKeySet( "Editor"			,no++, "editor"			));
		ATT_KEY_MAP.put( "org.opengion.hayabusa.db.DBType"					, new AttKeySet( "DBType"			,no++, "dbType"			));
		ATT_KEY_MAP.put( "org.opengion.hayabusa.db.TableFilter"				, new AttKeySet( "TableFilter"		,no++, "tableFilter"	));
		ATT_KEY_MAP.put( "org.opengion.hayabusa.db.Selection"				, new AttKeySet( "Selection"		,no++, "selection"		));
		ATT_KEY_MAP.put( "org.opengion.hayabusa.db.DBConstValue"			, new AttKeySet( "DBConstValue"		,no++, "cnstVal"		));		// 5.6.3.3 (2013/04/19)
		ATT_KEY_MAP.put( "org.opengion.hayabusa.html.ViewForm"				, new AttKeySet( "ViewForm"			,no++, "viewFormType"	));
		ATT_KEY_MAP.put( "org.opengion.hayabusa.io.TableWriter"				, new AttKeySet( "TableWriter"		,no++, "writerClass"	));
		ATT_KEY_MAP.put( "org.opengion.hayabusa.io.TableReader"				, new AttKeySet( "TableReader"		,no++, "readerClass"	));
//		ATT_KEY_MAP.put( "org.opengion.hayabusa.report.DBTableReport"		, new AttKeySet( "DBTableReport"	,no++, "tableReport"	));		// 7.0.1.5 (2018/12/10) 削除
		ATT_KEY_MAP.put( "org.opengion.hayabusa.resource.CalendarQuery"		, new AttKeySet( "CalendarQuery"	,no++, "calDB"			));
		ATT_KEY_MAP.put( "org.opengion.hayabusa.resource.CalendarData"		, new AttKeySet( "CalendarData"		,no++, "calData"		));		// 5.6.3.3 (2013/04/19)
		ATT_KEY_MAP.put( "org.opengion.fukurou.process.HybsProcess"			, new AttKeySet( "Process"			,no++, "process"		));
//		ATT_KEY_MAP.put( "org.opengion.fukurou.transfer.TransferExec"		, new AttKeySet( "TransferExec"		,no++, "kbExec"			));		// 5.5.3.5 (2012/06/21)
//		ATT_KEY_MAP.put( "org.opengion.fukurou.transfer.TransferRead"		, new AttKeySet( "TransferRead"		,no++, "kbRead"			));		// 5.5.3.5 (2012/06/21)
		ATT_KEY_MAP.put( "org.opengion.fukurou.util.HybsTimerTask"			, new AttKeySet( "Daemon"			,no++, "daemon"			));		// 4.3.4.4 (2009/01/01)
		ATT_KEY_MAP.put( "org.opengion.fukurou.util.ConnectIF	"			, new AttKeySet( "ConnectIF"		,no++, "connIF"			));		// 5.6.3.3 (2013/04/19)
		ATT_KEY_MAP.put( "org.opengion.fukurou.xml.JspParserFilter"			, new AttKeySet( "JspCreate"		,no++, "jspParser"		));		// 5.6.3.3 (2013/04/19)
	}

	private static final String OG_FOR_SMPL  = "og.formSample";
	private static final String ENCODE = "UTF-8";

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

	/**
	 * Doclet のエントリポイントメソッドです。
	 *
	 * @og.rev 5.7.1.1 (2013/12/13) タグのインデントを止める。
	 *
	 * @param root ドキュメントルートオブジェクト
	 *
	 * @return 正常実行時 true
	 */
	public static boolean start( final RootDoc root ) {
		final String version = DocletUtil.getOption( "-version" , root.options() );
		final String file    = DocletUtil.getOption( "-outfile" , root.options() );

		DocletTagWriter writer = null;
		try {
			writer = new DocletTagWriter( file,ENCODE );

			// 5.7.1.1 (2013/12/13) タグのインデントを止める。
			writer.printTag( "<?xml version=\"1.0\" encoding=\"", ENCODE, "\" ?>" );
			writer.printTag( "<javadoc>" );
			writer.printTag( "  <version>",version,"</version>" );
			writer.printTag( "  <description></description>" );
			writeContents( root.classes(),writer );
			writer.printTag( "</javadoc>" );
		}
		catch( final IOException ex ) {
			LogWriter.log( ex );
		}
		finally {
			if( writer != null ) { writer.close(); }
		}
		return true;
	}

	/**
	 * ClassDoc 配列よりコンテンツを作成します。
	 * インターフェースも処理の対象とします。
	 *
	 * @og.rev 5.5.4.1 (2012/07/06) コメントは文字列でなく、Tag配列として処理させる。
	 * @og.rev 5.7.1.1 (2013/12/13) タグのインデントを止める。
	 *
	 * @param classes	ClassDoc配列
	 * @param writer	DocletTagWriterオブジェクト
	 */
	private static void writeContents( final ClassDoc[] classes,final DocletTagWriter writer ) {
		for( int i=0; i< classes.length; i++ ) {
			final ClassDoc classDoc      = classes[i] ;
			if( ! classDoc.isPublic() ) { continue; }

			final AttKeySet attSet = getAttGroupName( classDoc ) ;
			if( attSet == null ) { continue; }		// map に登録されていない。

			final String attKey   = attSet.getAttKey( classDoc.name() );
			if( attKey == null ) { continue; }		// 対象クラス名が、一致しない。

			final String attClass = classDoc.qualifiedName() ;	// Class Full Name
			final Tag[]  desc     = classDoc.firstSentenceTags();
			final Tag[]  cmnt     = classDoc.inlineTags();									// 5.5.4.1 (2012/07/06)
			final Tag[] smplTags  = classDoc.tags(OG_FOR_SMPL);

			// 5.7.1.1 (2013/12/13) タグのインデントを止める。
			writer.printTag( "<classDoc>" );
			writer.printTag( "  <attClass>"		,attClass				,"</attClass>"		);
			writer.printTag( "  <seq>"			,attSet.getSeq()		,"</seq>"			);
			writer.printTag( "  <attKey>"		,attKey					,"</attKey>"		);
			writer.printTag( "  <valueName>"	,attSet.getValueName()	,"</valueName>"		);
			writer.printTag( "  <description>"	,desc					,"</description>"	);
			writer.printTag( "  <contents>"		,cmnt					,"</contents>"		);
			writer.printTag( "  <formSample>"	,smplTags				,"</formSample>"	);
			writer.printTag( "</classDoc>" );
		}
	}

	/**
	 * 指定の ClassDoc が、処理する属性クラスのMapに含まれている場合、
	 * その AttKeySet クラスのオブジェクトを返します。
	 * 存在しない場合、null を返します。
	 *
	 * @og.rev 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。変数名も変えておきます。
	 *
	 * @param	classDoc ClassDocオブジェクト
	 *
	 * @return	ClassDocに対応する AttKeySetオブジェクト
	 */
	private static AttKeySet getAttGroupName( final ClassDoc classDoc ) {
		if( classDoc == null ) { return null; }

		final String classFullName = classDoc.qualifiedName() ;
		AttKeySet attKey = ATT_KEY_MAP.get( classFullName );		//6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。変数名も変えておきます。
		if( attKey == null ) {
			final Type type = classDoc.superclassType();			// 親クラスタイプ
			if( type != null ) {
				attKey = getAttGroupName( type.asClassDoc() );		// 親クラス
			}

			if( attKey == null ) {
				final Type[] itface = classDoc.interfaceTypes();	// 直近インターフェース
				for( int i=0; i<itface.length; i++ ) {
					attKey = getAttGroupName( itface[i].asClassDoc() );
					if( attKey != null ) { break; }
				}
			}
		}
		return attKey;
	}

	/**
	 * カスタムオプションを使用するドックレットの必須メソッド optionLength(String) です。
	 *
	 * ドックレットに認識させる各カスタムオプションに、 optionLength がその
	 * オプションを構成する要素 (トークン) の数を返さなければなりません。
	 * このカスタムオプションでは、 -tag オプションそのものと
	 * その値の 2 つの要素で構成されるので、作成するドックレットの
	 * optionLengthメソッドは、 -tag オプションに対して 2 を返さなくては
	 * なりません。また、認識できないオプションに対しては、0 を返します。
	 *
	 * @param option オプション文字列
	 *
	 * @return 要素 (トークン) の数
	 */
	public static int optionLength( final String option ) {
		if( "-version".equalsIgnoreCase(option) ) {
			return 2;
		}
		else if( "-outfile".equalsIgnoreCase(option) ) {
			return 2;
		}
		return 0;
	}

	/**
	 * 属性情報を管理する、AttKeySet クラスです。
	 *
	 * @og.rev 6.3.9.1 (2015/11/27) 別ファイルで管理していた、パッケージプライベートclass を、内部staticクラスに移動。
	 *
	 * @version  4.0
	 * @author   Kazuhiko Hasegawa
	 * @since    JDK5.0,
	 */
	private static final class AttKeySet {
		private final String searchKey ;
		private final int    len ;
		private final String seq ;
		private final String valueName ;

		/**
		 * コンストラクター
		 *
		 * @param searchKey  検索キー
		 * @param seq        シーケンス番号
		 * @param valueName  属性名
		 *
		 */
		AttKeySet( final String searchKey,final int seq,final String valueName ) {
			this.searchKey		= searchKey ;
			this.seq	        = String.valueOf( seq );
			this.valueName		= valueName ;

			len = searchKey.length();
		}

		/**
		 * シーケンス番号を返します。
		 *
		 * @return シーケンス番号
		 *
		 */
		/* default */ String getSeq() {
			return seq;
		}

		/**
		 * 属性名を返します。
		 *
		 * @return 属性名
		 *
		 */
		/* default */ String getValueName() {
			return valueName;
		}

		/**
		 * クラス名の先頭一致の場合の、**** 部分を返します。
		 * インターフェースも扱えるように修正しましたので、先頭が _ の場合は、
		 * _ を削除して返します。
		 *
		 * @param name クラスの名称 (例：DBCellEditor_**** , ViewForm_****)
		 * @return クラス名の**** 部分
		 */
		/* default */ String getAttKey( final String name ) {
			// 6.1.0.0 (2014/12/26) refactoring
			String rtn = null;								// 一致しなかった。

			if( name.equals( searchKey ) ) {				// 完全一致：インターフェース
				rtn =  "(Interface)" + name ;
			}
			else if( name.indexOf( searchKey ) == 0 ) {		// 先頭一致した。
				rtn = name.substring( len );
				if( rtn.charAt(0) == '_' ) { rtn = rtn.substring( 1 ); }	// 先頭が _ の場合は、_ を削除
			}

			return rtn ;
		}
	}
}
