/*
 * 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.util.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,
 */
public final class DocletPlugin {
	private static Map<String,AttKeySet> map = new HashMap<String,AttKeySet>();

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

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

	/**
	 * Doclet のエントリポイントメソッドです。
	 *
	 * @param root RootDoc
	 * @return 正常実行時 true
	 */
	public static boolean start( final RootDoc root ) {
		String version = DocletUtil.getOption( "-version" , root.options() );
		String file    = DocletUtil.getOption( "-outfile" , root.options() );

		mapInit();

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

			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( IOException ex ) {
			LogWriter.log( ex );
		}
		finally {
			if( writer != null ) { writer.close(); }
		}
		return true;
	}

	/**
	 * ClassDoc 配列よりコンテンツを作成します。
	 * インターフェースも処理の対象とします。
	 *
	 * @param classes ClassDoc[]
	 * @param writer DocletTagWriter
	 */
	private static void writeContents( final ClassDoc[] classes,final DocletTagWriter writer ) {
		for(int i=0; i< classes.length; i++) {
			ClassDoc classDoc      = classes[i] ;

			if( ! classDoc.isPublic() ) { continue; }

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

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

			Tag[]  desc     = classDoc.firstSentenceTags();
			String cmnt     = DocletUtil.htmlFilter( classDoc.commentText() );
			Tag[] smplTags  = classDoc.tags(OG_FOR_SMPL);

			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>" );
		}
	}

	/**
	 * 処理する属性クラスのMapを初期化します。
	 * 指定できるのは、親クラスか、直接のインターフェースです。
	 * 
	 * @og.rev 4.3.5.0 (2008/02/01) daemonパッケージ追加
	 *
	 */
	private static void mapInit() {
		map.put( "org.opengion.hayabusa.db.Query"				, new AttKeySet( "Query"			,0, "queryType"		));
		map.put( "org.opengion.hayabusa.db.CellRenderer"			, new AttKeySet( "Renderer"			,1, "renderer"		));
		map.put( "org.opengion.hayabusa.db.CellEditor"			, new AttKeySet( "Editor"			,2, "editor"		));
		map.put( "org.opengion.hayabusa.db.DBType"				, new AttKeySet( "DBType"			,3, "dbType"		));
		map.put( "org.opengion.hayabusa.db.TableFilter"			, new AttKeySet( "TableFilter"		,4, "tableFilter"	));
		map.put( "org.opengion.hayabusa.db.Selection"			, new AttKeySet( "Selection"		,5, "selection"		));
		map.put( "org.opengion.hayabusa.html.ViewForm"			, new AttKeySet( "ViewForm"			,6, "viewFormType"	));
		map.put( "org.opengion.hayabusa.io.TableWriter"			, new AttKeySet( "TableWriter"		,7, "writerClass"	));
		map.put( "org.opengion.hayabusa.io.TableReader"			, new AttKeySet( "TableReader"		,8, "readerClass"	));
		map.put( "org.opengion.hayabusa.io.ChartWriter"			, new AttKeySet( "ChartWriter"		,9, "chartClass"	));
		map.put( "org.opengion.hayabusa.resource.CalendarQuery"	, new AttKeySet( "CalendarQuery"	,10, "calDB"		));
		map.put( "org.opengion.fukurou.process.HybsProcess"		, new AttKeySet( "Process"			,11, "process"		));
		map.put( "org.opengion.fukurou.util.HybsTimerTask"		, new AttKeySet( "Daemon"			,12, "daemon"		)); // 4.3.4.4 (2009/01/01)
	}

	/**
	 * 指定の ClassDoc が、処理する属性クラスのMapに含まれている場合、
	 * その AttKeySet クラスのオブジェクトを返します。
	 * 存在しない場合、null を返します。
	 *
	 * @param classDoc ClassDoc
	 * @return AttKeySet ClassDoc に対応する AttKeySet オブジェクト
	 */
	private static AttKeySet getAttGroupName( final ClassDoc classDoc ) {
		if( classDoc == null ) { return null; }

		String classFullName = classDoc.qualifiedName() ;
		AttKeySet attKey = map.get( classFullName );
		if( attKey == null ) {
			Type type = classDoc.superclassType();	// 親クラスタイプ
			if( type != null ) {
				attKey = getAttGroupName( type.asClassDoc() );	// 親クラス
			}

			if( attKey == null ) {
				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 String
	 * @return 要素 (トークン) の数
	 */
	public static int optionLength( final String option ) {
		if(option.equalsIgnoreCase("-version")) {
			return 2;
		}
		else if(option.equalsIgnoreCase("-outfile")) {
			return 2;
		}
		return 0;
	}
}
