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

import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE;	// 6.1.0.0 (2014/12/26) refactoring

import jdk.javadoc.doclet.DocletEnvironment	 ;
// import jdk.javadoc.doclet.Doclet  ;
// import jdk.javadoc.doclet.Reporter ;
import javax.lang.model.element.Element	;
import javax.lang.model.element.Modifier ;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.ElementKind	;
import javax.lang.model.element.VariableElement;
import javax.lang.model.element.ExecutableElement;
// import javax.lang.model.SourceVersion ;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeKind;
import javax.lang.model.util.ElementFilter ;
import javax.lang.model.util.Elements ;
import javax.lang.model.util.Types ;
import javax.tools.Diagnostic.Kind ;
import com.sun.source.doctree.DocCommentTree ;
import com.sun.source.util.DocTrees  ;
// import com.sun.source.util.DocSourcePositions ;
import com.sun.source.doctree.DocTree  ;

// import java.util.Locale ;
import java.util.Set;
import java.util.List;
import java.util.HashSet;
import java.util.Arrays;
// import java.util.stream.Stream;										// 6.4.3.4 (2016/03/11)
// import java.util.stream.Collectors;									// 6.4.3.4 (2016/03/11)
// import java.util.regex.Pattern;										// 6.3.9.1 (2015/11/27) final化に伴う整理

// import java.io.IOException;
// import java.io.File;
// import java.io.PrintWriter;
import java.util.stream.Stream;										// 6.4.3.4 (2016/03/11)
import java.util.stream.Collectors;									// 6.4.3.4 (2016/03/11)
import java.util.Map;

// import org.opengion.fukurou.util.FileUtil;
// import org.opengion.fukurou.util.StringUtil;

/**
 * ソースコメントから、パラメータ情報を取り出す Doclet クラスです。
 * og.paramLevel タグと og.cryptography タグを切り出します。
 * これらは、ｼｽﾃﾑパラメータとしてGE12ﾃｰﾌﾞﾙに設定される値をクラスより抽出する
 * のに使用します。
 *
 * @version  7.3
 * @author	Kazuhiko Hasegawa
 * @since	 JDK11.0,
 */
public class DocTreeSpecific extends AbstractDocTree {
	private static final String  SELECT_PACKAGE	= "org.opengion" ;
	private static final boolean USE_PRIVATE	= false ;

	private static final String	OG_FOR_SMPL		= "og.formSample";
	private static final String	OG_REV			= "og.rev";
	private static final String	OG_GROUP		= "og.group";
	private static final String	DOC_VERSION		= "version";
	private static final String	DOC_AUTHOR		= "author";
	private static final String	DOC_SINCE		= "since";

//	private static final String OG_TAG_NAME		= "og.tag";				// 6.1.2.0 (2015/01/24) チェック用
//	private static final String	DOC_PARAM		= "param";				// 5.1.9.0 (2010/08/01) チェック用
//	private static final String	DOC_RETURN		= "return";				// 5.1.9.0 (2010/08/01) チェック用

	private static final String	CONSTRUCTOR		= "コンストラクタ" ;
	private static final String	METHOD			= "メソッド" ;

	private String	version		;
	private String	outfile		;
	private int		debugLevel	;		// 0:なし 1:最小チェック 2:日本語化 3:体裁 4:Verﾁｪｯｸ 5:taglibﾗﾍﾞﾙ

	private final Set<String> mtdClsSet = new HashSet<>();

	// 5.1.9.0 (2010/08/01) ソースチェック用(半角文字＋空白文字のみ)
//	private static final Pattern PTN = Pattern.compile("[\\w\\s]+");		// 6.3.9.1 (2015/11/27)

//	private DocTrees docUtil;
//	private Elements eleUtil ;
	private Types    typUtil ;

	/**
	 * デフォルトコンストラクター
	 *
	 * @og.rev 7.3.0.0 (2021/01/06) PMD refactoring. Each class should declare at least one constructor.
	 */
	public DocTreeSpecific() { super(); }		// これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。

	/**
	 * Doclet のエントリポイントメソッドです(昔の startメソッド)。
	 *
	 * @og.rev 7.3.0.0 (2021/01/06) 新しいJavaDoc対応
	 *
	 * @param docEnv ドックレットを1回呼び出す操作環境
	 *
	 * @return 正常実行時 true
	 */
	@Override
	public boolean run( final DocletEnvironment docEnv ) {
		try( DocTreeWriter writer = new DocTreeWriter( outfile,ENCODE ) ) {
			writer.printTag( "<?xml version=\"1.0\" encoding=\"", ENCODE , "\" ?>" );
			writer.printTag( "<javadoc>" );
			writer.printTag( "  <version>",version,"</version>" );
			writer.printTag( "  <description></description>" );
			writeContents( docEnv,writer );
			writer.printTag( "</javadoc>" );
		}
		catch( final Throwable th ) {
			reporter.print(Kind.ERROR, th.getMessage());
		}

		return true;
	}

	/**
	 * DocletEnvironmentよりコンテンツを作成します。
	 *
	 * @og.rev 7.3.0.0 (2021/01/06) 新しいJavaDoc対応
	 *
	 * @param docEnv	ドックレットの最上位
	 * @param writer	DocTreeWriterオブジェクト
	 */
	private void writeContents( final DocletEnvironment docEnv, final DocTreeWriter writer ) {
//		docUtil = docEnv.getDocTrees();
		final DocTrees docUtil = docEnv.getDocTrees();
		final Elements eleUtil = docEnv.getElementUtils();
		typUtil = docEnv.getTypeUtils();

		// クラス単位にループする。
		for( final TypeElement typEle : ElementFilter.typesIn(docEnv.getIncludedElements())) {
			final String fullName	= String.valueOf( typEle.getQualifiedName() ) ;
			writer.setClassName( fullName );

			final String className = String.valueOf( typEle.getSimpleName() );

			final StringBuilder modiBuf = new StringBuilder();
			typEle.getModifiers().forEach( modi -> modiBuf.append( modi ).append( ' ' ) );
			final ElementKind eleKind = typEle.getKind();
			if( ElementKind.CLASS.equals( eleKind ) ) { modiBuf.append( "class" ); }
			final String modifiers = modiBuf.toString().trim();

			final TypeMirror superType 	= typEle.getSuperclass();
			final String superClass = TypeKind.NONE.equals( superType.getKind() ) ? "" : superType.toString();

			final String intFase = Stream.of( typEle.getInterfaces() )
										.map( typ -> String.valueOf(typ) )
										.collect( Collectors.joining( "," ) );

			final DocCommentTree docTree = docUtil.getDocCommentTree(typEle);		// ﾄﾞｷｭﾒﾝﾃｰｼｮﾝ･ｺﾒﾝﾄが見つからない場合、null が返る。

			final List<? extends DocTree> desc	= docTree == null ? EMPTY_LIST : docTree.getFirstSentence();
			final List<? extends DocTree> cmnt	= docTree == null ? EMPTY_LIST : docTree.getFullBody();

			final Map<String,List<String>> blkTagMap = blockTagsMap(docTree);
			final String smplTags	= getBlockTag( OG_FOR_SMPL, blkTagMap, ""   );
			final String revTags	= getBlockTag( OG_REV     , blkTagMap, "\n" );
			final String createVer	= getBlockTag( DOC_VERSION, blkTagMap, ""   );
			final String author		= getBlockTag( DOC_AUTHOR , blkTagMap, ""   );
			final String since		= getBlockTag( DOC_SINCE  , blkTagMap, ""   );
			final String grpTags	= getBlockTag( OG_GROUP   , blkTagMap, ","  );

			// 5.7.1.1 (2013/12/13) タグのインデントを止める。
			writer.printTag( "<classDoc>" );
			writer.printTag( "  <fullName>"		,fullName		,"</fullName>"		);
			writer.printTag( "  <modifiers>"	,modifiers		,"</modifiers>"		);
			writer.printTag( "  <className>"	,className		,"</className>"		);
			writer.printTag( "  <superClass>"	,superClass		,"</superClass>"	);
			writer.printTag( "  <interface>"	,intFase		,"</interface>"		);
			writer.printTag( "  <createVer>"	,createVer		,"</createVer>"		);
			writer.printTag( "  <author>"		,author			,"</author>"		);
			writer.printTag( "  <since>"		,since			,"</since>"			);
			writer.printTag( "  <description>"	,desc			,"</description>"	);
			writer.printTag( "  <contents>"		,cmnt			,"</contents>"		);
			writer.printTag( "  <classGroup>"	,grpTags		,"</classGroup>"	);
			writer.printTag( "  <formSample>"	,smplTags		,"</formSample>"	);
			writer.printTag( "  <history>"		,revTags		,"</history>"		);


			// 5.1.9.0 (2010/08/01) ソースチェック用(コメントや概要が無い場合。スーパークラスは省く)
			if( debugLevel >= 2 && ( cmnt.isEmpty() || desc.isEmpty() ) && superClass.isEmpty() ) {
	//			final DocSourcePositions srcPos = docUtil.getSourcePositions();
	//			System.err.println( "警告2:コメントC=\t" + srcPos );
				System.err.println( "②警告2:コメントC=\t" + typEle );
			}

			int extendFlag = 0;		// 0:オリジナル 1:org.opengion関連Extend 2:Java関連Extend

			// 6.4.3.0 (2016/02/05) PMDチェックのDocletでのフォロー。
	//		checkPMD( typEle );

			// 5.6.6.0 (2013/07/05) VERSION staticフィールドと、@og.rev コメントの比較チェック
			// while 以下で、fullName と classDoc を順番に上にさかのぼっているので、先にチェックします。
	//		checkTag2( typEle );

			mtdClsSet.clear();
			TypeElement loopEle = typEle;
			String superName = fullName;
			while( loopEle != null ) {
				writer.setClassName( superName );

				// 対象クラスの スーパークラスを取得しておく(ﾒｿｯﾄﾞ内で使うのと、上位に上がってく時に使う)
				final TypeMirror spType = loopEle.getSuperclass();

				// AbstractObjectPool<java.sql.Connection> の様な型を削除します。
				final StringBuilder buf = new StringBuilder().append(spType);
				int st = buf.indexOf("<");
				int ed = buf.indexOf(">",st);
				while( st > 0 ) {
					buf.delete(st,ed+1);
					st = buf.indexOf("<");
					ed = buf.indexOf(">",st);
				}
				superName = buf.toString();
		//		superName = String.valueOf( spType );

				final String extClass   = ( extendFlag == 0 ) ? "" : superName ;

				// コンストラクタのみフィルタリングして取得する
				for( final ExecutableElement exEle : ElementFilter.constructorsIn(loopEle.getEnclosedElements()) ) {
					final DocCommentTree dct = docUtil.getDocCommentTree(exEle);		// ﾄﾞｷｭﾒﾝﾃｰｼｮﾝ･ｺﾒﾝﾄが見つからない場合、null が返る。
					if( dct != null && isAction( exEle,extendFlag ) ) {
		//				checkTag( exEle,dct );												// 5.5.4.1 (2012/07/06)  チェックを分離
						menberTag( exEle,dct,CONSTRUCTOR,writer,extendFlag,extClass );
					}
				}

				// メソッドのみフィルタリングして取得する
				for( final ExecutableElement exEle : ElementFilter.methodsIn(loopEle.getEnclosedElements())) {
					final DocCommentTree dct = docUtil.getDocCommentTree(exEle);		// ﾄﾞｷｭﾒﾝﾃｰｼｮﾝ･ｺﾒﾝﾄが見つからない場合、null が返る。
					if( dct != null && isAction( exEle,extendFlag ) ) {
		//				checkTag( exEle,dct );
						menberTag( exEle,dct,METHOD,writer,extendFlag,extClass );
					}
				}

				// 対象クラス(オリジナル)から、上に上がっていく。
				if( superName.startsWith( SELECT_PACKAGE ) ) {
					extendFlag = 1;
				}
				else {
					break;
				}

				loopEle = eleUtil.getTypeElement(superName);
			}
			writer.printTag( "</classDoc>" );
		}
	}

	/**
	 * メンバークラスのXML化を行うかどうか[true/false]を判定します。
	 *
	 * 以下の条件に合致する場合は、処理を行いません。(false を返します。)
	 *
	 * １．同一クラスを処理中にEXTENDで継承元をさかのぼる場合、すでに同じシグネチャのメソッドが
	 *     存在している。
	 * ２．USE_PRIVATE が true の時の private メソッド
	 * ３．extendFlag が 0以上(1,2)の時の private メソッド
	 * ４．メソッド名におかしな記号(&lt;など)が含まれている場合
	 *
	 * @og.rev 7.3.0.0 (2021/01/06) 新しいJavaDoc対応
	 *
	 * @param	menber ExecutableMemberDocオブジェクト
	 * @param	extendFlag	継承状態 [0:オリジナル/1:org.opengion関連Extend/2:Java関連Extend]
	 *
	 * @return	XML化を行うかどうか[true/false]
	 */
	private boolean isAction( final ExecutableElement menber,final int extendFlag ) {
//		final String menberName = String.valueOf( menber.getSimpleName() ) ;
		final boolean isPrivate   = menber.getModifiers().contains( Modifier.PRIVATE );
		final boolean isTypeParam = ! menber.getTypeParameters().isEmpty();
		final boolean rtn = ! mtdClsSet.add( String.valueOf( menber ) )		// 5.5.4.1 (2012/07/06) メソッドの重複処理判定は、クラス名も含めて行う
						||	USE_PRIVATE    && isPrivate
						||	extendFlag > 0 && isPrivate
						||	isTypeParam;									// PMD Useless parentheses.

		return ! rtn ;
	}

//	/**
//	 * param,return 等の整合性をチェックします。
//	 *
//	 * @og.rev 7.3.0.0 (2021/01/06) 新しいJavaDoc対応
//	 *
//	 * @param exEle ExecutableElementオブジェクト
//	 * @param menber DocCommentTreeオブジェクト
//	 */
//	private void checkTag( final ExecutableElement exEle , final DocCommentTree menber ) {
//		// 未実装
//	}

//	/**
//	 * PMDで、チェックしている処理のうち、Docletでフォローできる分をチェックします。
//	 *
//	 * ※ このチェックは、警告レベル5 のみ集約していますので、呼出元で、制限します。
//	 *
//	 * @og.rev 7.3.0.0 (2021/01/06) 新しいJavaDoc対応
//	 *
//	 * @param typEle TypeElementオブジェクト
//	 */
//	private void checkTag2( final TypeElement typEle ) {
//		String cnstVar = null ;		// 初期値
//		String seriUID = null ;
//		final String src = "\tsrc/" + String.valueOf(typEle).replace('.','/') + ".java:100" ;			// 行が判らないので、100行目 決め打ち
//
//		// フィールドのみフィルタリングして取得する
//		for( final VariableElement varEle : ElementFilter.fieldsIn(typEle.getEnclosedElements())) {		// フィールドだけに絞る
//			final String key = String.valueOf(varEle);
//			if( "VERSION".equals( key ) ) {
//				cnstVar = String.valueOf( varEle.getConstantValue() );
//			}
//			else if( "serialVersionUID".equals( key ) ) {
//				seriUID = String.valueOf( varEle.getConstantValue() ) + "L";			// 旧JavaDocと違い、"L" まで取ってこれないみたい
//			}
//		}
//
//		if( cnstVar == null ) { return; }				// VERSION が未定義のクラスは処理しない
//
//		String maxRev = cnstVar ;						// 5.7.1.1 (2013/12/13) 初期値
//		boolean isChange = false;						// max が入れ替わったら、true
//
//		// メソッドのみフィルタリングして取得する
//		for( final ExecutableElement exEle : ElementFilter.methodsIn(typEle.getEnclosedElements())) {
//			final DocCommentTree dct = docUtil.getDocCommentTree(exEle);		// ﾄﾞｷｭﾒﾝﾃｰｼｮﾝ･ｺﾒﾝﾄが見つからない場合、null が返る。
//			final Map<String,List<String>> blkTagMap = blockTagsMap(dct);
//			final List<String> revTags = blkTagMap.get("og.rev");
//
//			if( revTags != null ) {
//				for( final String tag :revTags ) {								// 複数存在しているはず
//					final int idx = tag.indexOf( ' ' );							// 最初のスペース
//					if( idx > 0 ) {
//						final String rev = tag.substring( 0,idx ).trim();
//						if( maxRev.compareTo( rev ) < 0 ) {						// revTags の og.rev が大きい場合
//							maxRev = rev ;
//							isChange = true;
//						}
//					}
//				}
//			}
//		}
//
//		// VERSION 文字列 の定義があり、かつ、max の入れ替えが発生した場合のみ、警告4:VERSIONが古い
//		if( isChange ) {			// 5.7.1.1 (2013/12/13) 入れ替えが発生した場合
//			System.err.println( "警告4:VERSIONが古い=\t" + cnstVar + " ⇒ " + maxRev + src );
//		}
//
//		// serialVersionUID の定義がある。
//		if( seriUID != null ) {
//			final StringBuilder buf = new StringBuilder();
//			// maxRev は、最大の Revか、初期のVERSION文字列 例:5.6.6.0 (2013/07/05)
//			for( int i=0; i<maxRev.length(); i++ ) {	//
//				final char ch = maxRev.charAt( i );
//				if( ch >= '0' && ch <= '9' ) { buf.append( ch ); }	// 数字だけ取り出す。 例:566020130705
//			}
//			buf.append( 'L' );	// 強制的に、L を追加する。
//			final String maxSeriUID = buf.toString() ;
//
//			// 5.7.1.1 (2013/12/13) 値の取出し。Long型を表す "L" も含まれている。
//			if( !maxSeriUID.equals( seriUID ) ) {	// 一致しない
//				System.err.println( "警告4:serialVersionUIDが古い=\t" + seriUID + " ⇒ " + maxSeriUID + src );
//			}
//		}
//	}

//	/**
//	 * PMDで、チェックしている処理のうち、Docletでフォローできる分をチェックします。
//	 *
//	 * ※ このチェックは、警告レベル5 のみ集約していますので、呼出元で、制限します。
//	 *
//	 * @og.rev 7.3.0.0 (2021/01/06) 新しいJavaDoc対応
//	 *
//	 * @param typEle TypeElementオブジェクト
//	 */
//	private void checkPMD( final TypeElement typEle ) {
//		// 未実装
//	}

	/**
	 * メンバークラス(コンストラクタ、メソッド)をXML化します。
	 *
	 * @og.rev 7.3.0.0 (2021/01/06) 新しいJavaDoc対応
	 *
	 * @param	menber		ExecutableElementオブジェクト
	 * @param	docTree		DocCommentTreeオブジェクト
	 * @param	menberType	メンバータイプ(コンストラクタ、メソッド)
	 * @param	writer		Tagを書き出すWriterオブジェクト
	 * @param	extendFlag	継承状態 [0:オリジナル/1::org.opengion関連Extend/2:Java関連Extend]
	 * @param	extClass	継承クラス(オリジナルの場合は、空文字列)
	 */
	private void menberTag(	final ExecutableElement menber,
							final DocCommentTree docTree ,
							final String menberType,
							final DocTreeWriter writer,
							final int extendFlag ,
							final String extClass ) {

		final String modifiers ;

		final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
		menber.getModifiers().forEach( modi -> buf.append( modi ).append( ' ' ) );
		if( ElementKind.METHOD.equals( menber.getKind() ) ) {		// メソッドの処理。コンストラクタの場合は、返り値がないので処理しない。
			buf.append( menber.getReturnType() );
		}
		modifiers = buf.toString();

		final String menberName = String.valueOf( menber.getSimpleName() );			// コンストラクタの場合は、<init> が返る。

		final StringBuilder sigBuf = new StringBuilder().append( menberName ).append( '(' ) ;
		boolean flag = false;
		for( final VariableElement valEle : menber.getParameters() ) {
			flag = true;
			final Element ele = typUtil.asElement( valEle.asType() );		// 型が対応する要素を持たない場合はnullを返します。
			final String key = ele == null									// 配列や、プリミティブ型の場合は、null になっている。
									? String.valueOf( valEle.asType() )
									: String.valueOf( ele.getSimpleName() );

			sigBuf.append( key ).append( ' ' )
					.append( valEle.getSimpleName() ).append( ',' );
		}

		if( flag ) { sigBuf.deleteCharAt( sigBuf.length()-1 ); }
		sigBuf.append( ')' );
		final String signature = sigBuf.toString();
//		final String signature = String.valueOf( menber );

		final List<? extends DocTree> desc	= docTree == null ? EMPTY_LIST : docTree.getFirstSentence();
		final List<? extends DocTree> cmnt	= docTree == null ? EMPTY_LIST : docTree.getFullBody();

		final Map<String,List<String>> blkTagMap = blockTagsMap(docTree);
		final String revTags = getBlockTag( OG_REV, blkTagMap, "\n" );

		// tags は、OG_REV 以外のすべてで、かつ、ｷｰﾜｰﾄﾞも含む。
		final StringBuilder tagBuf = new StringBuilder();
		if( docTree != null ) {
			for( final DocTree dt : docTree.getBlockTags() ) {
				final String tag = String.valueOf(dt).trim();
				if( !tag.contains( OG_REV ) ) { tagBuf.append( tag ).append( '\n' ); }
			}
		}
		final String tags = tagBuf.toString().trim();

//		final StringBuilder tagBuf = new StringBuilder();
//		final StringBuilder revBuf = new StringBuilder();
//		if( docTree != null ) {
//			for( final DocTree dt : docTree.getBlockTags() ) {
//				final String tag = String.valueOf(dt).trim();
//				if( tag.contains( OG_REV ) ) { revBuf.append( cutTag( tag,OG_REV ) ).append( '\n' ); }
//				else { tagBuf.append( tag ).append( '\n' ); }
//			}
//		}
//		final String revTags = revBuf.toString().trim();
//		final String tags    = tagBuf.toString().trim();

		final String	extend  = String.valueOf( extendFlag );

	//	final DocSourcePositions srcPos = docUtil.getSourcePositions();
	//	final String	position = String.valueOf( srcPos.getStartPosition( null,docTree,null ) );
		final String	position = "";

		writer.printTag( "  <menber>" );
		writer.printTag( "    <type>"		,menberType	,"</type>"			);
		writer.printTag( "    <name>"		,menberName	,"</name>"			);
		writer.printTag( "    <modifiers>"	,modifiers	,"</modifiers>"		);
		writer.printTag( "    <signature>"	,signature	,"</signature>"		);
		writer.printTag( "    <position>"	,position	,"</position>"		);
		writer.printTag( "    <extendClass>",extClass	,"</extendClass>"	);
		writer.printTag( "    <extendFlag>"	,extend		,"</extendFlag>"	);
		writer.printTag( "    <description>",desc		,"</description>"	);
		writer.printTag( "    <contents>"	,cmnt		,"</contents>"		);
		writer.printTag( "    <tagText>"	,tags		,"</tagText>"		);
		writer.printTag( "    <history>"	,revTags	,"</history>"		);
		writer.printTag( "  </menber>");
	}

	/**
	 * サポートされているすべてのオプションを返します。
	 *
	 * @return サポートされているすべてのオプションを含むセット、存在しない場合は空のセット
	 */
	@Override
	public Set<? extends Option> getSupportedOptions() {
		final Option[] options = {
			new AbstractOption( "-outfile", "-version", "-debugLevel" ) {

				/**
				 * 必要に応じてオプションと引数を処理します。
				 *
				 * @param  opt オプション名
				 * @param  arguments 引数をカプセル化したリスト
				 * @return 操作が成功した場合はtrue、そうでない場合はfalse
				 */
				@Override
				public boolean process(final String opt, final List<String> arguments) {
					if( "-outfile".equalsIgnoreCase(opt) ) {
						outfile = arguments.get(0);
					}
					else if( "-version".equalsIgnoreCase(opt) ) {
						version = arguments.get(0);
					}
					else if( "-debugLevel".equalsIgnoreCase(opt) ) {
						debugLevel = Integer.parseInt( arguments.get(0) );
					}
					return true;
				}
			}
		};
		return new HashSet<>(Arrays.asList(options));
	}
}
