    001/*
    002 * Copyright (c) 2009 The openGion Project.
    003 *
    004 * Licensed under the Apache License, Version 2.0 (the "License");
    005 * you may not use this file except in compliance with the License.
    006 * You may obtain a copy of the License at
    007 *
    008 *        http://www.apache.org/licenses/LICENSE-2.0
    009 *
    010 * Unless required by applicable law or agreed to in writing, software
    011 * distributed under the License is distributed on an "AS IS" BASIS,
    012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
    013 * either express or implied. See the License for the specific language
    014 * governing permissions and limitations under the License.
    015 */
    016package org.opengion.fukurou.taglet;            // 7.4.4.0 (2021/06/30) openGionV8事前準備(taglet2→taglet)
    017
    018import jdk.javadoc.doclet.DocletEnvironment      ;
    019// import jdk.javadoc.doclet.Doclet  ;
    020// import jdk.javadoc.doclet.Reporter ;
    021// import javax.lang.model.element.Element      ;
    022import javax.lang.model.element.Modifier ;
    023import javax.lang.model.element.TypeElement;
    024// import javax.lang.model.element.ElementKind  ;
    025// import javax.lang.model.element.VariableElement;
    026import javax.lang.model.element.ExecutableElement;
    027// import javax.lang.model.SourceVersion ;
    028import javax.lang.model.type.TypeMirror;
    029import javax.lang.model.type.TypeKind;
    030import javax.lang.model.util.ElementFilter ;
    031import javax.lang.model.util.Elements ;
    032import javax.tools.Diagnostic.Kind ;
    033import com.sun.source.doctree.DocCommentTree  ;
    034import com.sun.source.util.DocTrees  ;
    035// import com.sun.source.util.DocSourcePositions  ;
    036import com.sun.source.doctree.DocTree  ;
    037
    038// import java.util.Locale ;
    039import java.util.Set;
    040import java.util.Map;
    041// import java.util.HashMap;
    042import java.util.List;
    043import java.util.HashSet;
    044import java.util.Arrays;
    045// import java.util.stream.Stream;                                                                              // 6.4.3.4 (2016/03/11)
    046// import java.util.stream.Collectors;                                                                  // 6.4.3.4 (2016/03/11)
    047
    048// import java.io.IOException;
    049// import java.io.File;
    050// import java.io.PrintWriter;
    051
    052// import org.opengion.fukurou.util.FileUtil;
    053// import org.opengion.fukurou.util.StringUtil;
    054
    055/**
    056 * ソースコメントから、パラメータ情報を取り出す Doclet クラスです。
    057 * og.paramLevel タグと og.cryptography タグを切り出します。
    058 * これらは、ｼｽﾃﾑパラメータとしてGE12ﾃｰﾌﾞﾙに設定される値をクラスより抽出する
    059 * のに使用します。
    060 *
    061 * @version  7.3
    062 * @author      Kazuhiko Hasegawa
    063 * @since        JDK11.0,
    064 */
    065public class DocTreeTaglib extends AbstractDocTree {
    066        private static final String OG_FOR_SMPL = "og.formSample";
    067        private static final String OG_TAG_NAME = "og.tag";
    068        private static final String OG_GROUP    = "og.group";
    069
    070        private static final String     DOC_PARAM       = "param";              // 6.1.1.0 (2015/01/17)
    071
    072        private static final String OG_TAG_CLASS = "org.opengion.hayabusa.taglib";
    073
    074        private String  version         ;
    075        private String  outfile         ;
    076
    077//      private DocTrees docUtil;
    078//      private Elements eleUtil ;
    079
    080        /**
    081         * デフォルトコンストラクター
    082         *
    083         * @og.rev 7.3.0.0 (2021/01/06) PMD refactoring. Each class should declare at least one constructor.
    084         */
    085        public DocTreeTaglib() { super(); }             // これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。
    086
    087        /**
    088         * Doclet のエントリポイントメソッドです(昔の startメソッド)。
    089         *
    090         * @og.rev 7.3.0.0 (2021/01/06) 新しいJavaDoc対応
    091         *
    092         * @param docEnv ドックレットを1回呼び出す操作環境
    093         *
    094         * @return 正常実行時 true
    095         */
    096        @Override
    097        public boolean run( final DocletEnvironment docEnv ) {
    098                try( DocTreeWriter writer = new DocTreeWriter( outfile,ENCODE ) ) {
    099                        writer.printTag( "<?xml version=\"1.0\" encoding=\"", ENCODE , "\" ?>" );
    100                        writer.printTag( "<javadoc>" );
    101                        writer.printTag( "  <version>",version,"</version>" );
    102                        writer.printTag( "  <description></description>" );
    103                        writeContents( docEnv,writer );
    104                        writer.printTag( "</javadoc>" );
    105                }
    106                catch( final Throwable th ) {
    107                        reporter.print(Kind.ERROR, th.getMessage());
    108                }
    109
    110                return true;
    111        }
    112
    113        /**
    114         * DocletEnvironmentよりコンテンツを作成します。
    115         *
    116         * @og.rev 7.3.0.0 (2021/01/06) 新しいJavaDoc対応
    117         *
    118         * @param docEnv        ドックレットの最上位
    119         * @param writer        DocTreeWriterオブジェクト
    120         */
    121        private void writeContents( final DocletEnvironment docEnv, final DocTreeWriter writer ) {
    122//              docUtil = docEnv.getDocTrees();
    123//              eleUtil = docEnv.getElementUtils();
    124
    125//              // get the DocTrees utility class to access document comments
    126                final DocTrees docUtil = docEnv.getDocTrees();
    127                final Elements eleUtil  = docEnv.getElementUtils();
    128
    129                final Set<String> mtdClsSet = new HashSet<>();                  //  6.4.3.1 (2016/02/12) 変数名も変えておきます。
    130
    131                // クラス単位にループする。
    132                for( final TypeElement typEle : ElementFilter.typesIn(docEnv.getIncludedElements())) {
    133                        final String fullName   = String.valueOf( typEle.getQualifiedName() ) ;
    134//                      final String fullName = String.valueOf( typEle ) ;
    135                        writer.setClassName( fullName );
    136
    137                        if( !typEle.getModifiers().contains( Modifier.PUBLIC ) ||
    138                                !fullName.contains( OG_TAG_CLASS ) ) { continue; }              // public かつ taglib に絞る
    139
    140                        final DocCommentTree docTree = docUtil.getDocCommentTree(typEle);               // ﾄﾞｷｭﾒﾝﾃｰｼｮﾝ･ｺﾒﾝﾄが見つからない場合、null が返る。
    141
    142                        final List<? extends DocTree> desc      = docTree == null ? EMPTY_LIST : docTree.getFirstSentence();
    143                        final List<? extends DocTree> cmnt      = docTree == null ? EMPTY_LIST : docTree.getFullBody();
    144
    145                        final Map<String,List<String>> blkTagMap = blockTagsMap(docTree);
    146                        final String smplTags   = getBlockTag( OG_FOR_SMPL, blkTagMap, ""  );
    147                        final String grpTags    = getBlockTag( OG_GROUP   , blkTagMap, "," );
    148
    149//                      String smplTags         = "";
    150//                      final StringBuilder grpBuf = new StringBuilder();
    151//                      if( docTree != null ) {
    152//                              for( final DocTree dt : docTree.getBlockTags() ) {
    153//                                      final String tag = String.valueOf(dt);
    154//                                      if(      tag.contains( OG_FOR_SMPL      ) ) { smplTags  = cutTag( tag,OG_FOR_SMPL       ); }
    155//                                      else if( tag.contains( OG_GROUP         ) ) { grpBuf.append( '【' ).append( cutTag( tag,OG_GROUP ) ).append( "】," ); }
    156//                              }
    157//                      }
    158//                      final String grpTags = grpBuf.length() == 0 ? "" : grpBuf.substring(0,grpBuf.length()-1);       // 最後のｶﾝﾏを削除
    159
    160                        // 5.7.1.1 (2013/12/13) タグのインデントを止める。
    161                        writer.printTag( "<classDoc>" );
    162                        writer.printTag( "  <tagClass>"         ,fullName       ,"</tagClass>"          );
    163                        writer.printTag( "  <tagGroup>"         ,grpTags        ,"</tagGroup>"          );
    164                        writer.printTag( "  <description>"      ,desc           ,"</description>"       );
    165                        writer.printTag( "  <contents>"         ,cmnt           ,"</contents>"          );
    166                        writer.printTag( "  <formSample>"       ,smplTags       ,"</formSample>"        );
    167
    168                        mtdClsSet.clear();
    169                        String className = String.valueOf( typEle );
    170                        TypeElement loopEle = typEle;
    171                        while(  loopEle != null                                         &&
    172                                        className.contains( OG_TAG_CLASS ) ) {
    173
    174                                writer.setClassName( className );
    175                                final String extendFlag = String.valueOf( className.contains( "HTMLTagSupport" ) );
    176
    177                                // メソッドのみフィルタリングして取得する
    178                                for( final ExecutableElement ele : ElementFilter.methodsIn(loopEle.getEnclosedElements())) {            // メソッドだけに絞る
    179                                        if( !ele.getModifiers().contains( Modifier.PUBLIC ) ) { continue; }                                             // public だけに絞る
    180
    181                                        final DocCommentTree mdoc = docUtil.getDocCommentTree(ele);             // ﾄﾞｷｭﾒﾝﾃｰｼｮﾝ･ｺﾒﾝﾄが見つからない場合、null が返る。
    182                                        if( mdoc == null ) { continue; }
    183
    184                                        final Map<String,List<String>> blkTagMap2 = blockTagsMap(mdoc);
    185                                        final String tags = getBlockTag( OG_TAG_NAME , blkTagMap2, "" );
    186
    187//                                      String tags = "";
    188//                                      for( final DocTree dt : mdoc.getBlockTags() ) {
    189//                                              final String tag = String.valueOf(dt);
    190//                                              if( tag.contains( OG_TAG_NAME ) ) { tags = cutTag( tag,OG_TAG_NAME      ); }
    191//                                      }
    192
    193                                        if( !tags.isEmpty() ) {
    194                                                final String fname = String.valueOf( ele );
    195                                                if( !mtdClsSet.add( fname ) ) { continue; }             // 継承もとに同じメソッド名がある場合は、無視する。
    196
    197                                                final String methodName = removeSetter( String.valueOf( ele.getSimpleName() ) );
    198
    199                                                final List<? extends DocTree> ftag      = mdoc.getFirstSentence();
    200                                                final List<? extends DocTree> mcmnt     = mdoc.getFullBody();
    201                                                final String[] keylblCd = methodLabelCode( mdoc );
    202
    203//                                              final List<? extends DocTree> doc1      = mdoc.getPostamble();
    204//                                              final List<? extends DocTree> doc2      = mdoc.getPreamble();
    205
    206                                                // 5.7.1.1 (2013/12/13) タグのインデントを止める。
    207                                                writer.printTag( "  <method>" );
    208                                                writer.printTag( "    <name>"           ,methodName     ,"</name>" );
    209                                                writer.printTag( "    <label>"          ,keylblCd[1],"</label>" );              // 6.1.1.0 (2015/01/17)
    210                                                writer.printTag( "    <comment>"        ,keylblCd[2],"</comment>" );    // 6.2.5.0 (2015/06/05)
    211                                                writer.printTag( "    <code>"           ,keylblCd[3],"</code>" );               // 6.1.1.0 (2015/01/17)
    212                                                writer.printTag( "    <htmlExtend>"     ,extendFlag     ,"</htmlExtend>" );
    213                                                writer.printTag( "    <description>",ftag               ,"</description>" );
    214                                                writer.printTag( "    <contents>"       ,mcmnt          ,"" );
    215                                                writer.printTag( ""                                     ,tags           ,"</contents>" );
    216                                                writer.printTag( "  </method>");
    217                                        }
    218                                }
    219
    220                                final TypeMirror superType      = loopEle.getSuperclass();
    221                                className = String.valueOf( superType );
    222                                loopEle = null;                                                 // while ループの終了
    223                                if( !TypeKind.NONE.equals( superType.getKind() ) ) {
    224                                        for( final TypeElement tp : eleUtil.getAllTypeElements(className) ) {
    225                                                if( className.equals( String.valueOf(tp) ) ) {
    226                                                        loopEle = tp ; break;
    227                                                }
    228                                        }
    229                                }
    230                        }
    231                        writer.printTag( "</classDoc>" );
    232                }
    233        }
    234
    235        /**
    236         * セッターメソッドの setXXXX の set を削除し、次の文字を小文字化します。
    237         * つまり、セッターメソッドから属性値を推測します。
    238         * (超特殊処理)セッターメソッドのset以下２文字目が大文字の場合は、
    239         * １文字目も大文字と考えて小文字化を行いません。
    240         * 例えば、setSYS や setUSER など、RequestValueTag.javaに使用するケースです。
    241         *
    242         * @param target 処理対象となる文字列
    243         *
    244         * @return オプション文字列
    245         */
    246        private String removeSetter( final String target ) {
    247                if( target != null && target.startsWith( "set" ) ) {
    248                        char[] chs = target.toCharArray();
    249                        if( chs.length > 4 && !( chs[4] >= 'A' && chs[4] <= 'Z' ) ) {
    250                                chs[3] = Character.toLowerCase( chs[3] ) ;
    251                        }
    252                        return new String( chs,3,chs.length-3 );
    253                }
    254                return target;
    255        }
    256
    257        /**
    258         * MethodDocを受け取り、0:ﾊﾟﾗﾒｰﾀ、1:ﾗﾍﾞﾙ、2:ｺﾒﾝﾄ、3:ｺｰﾄﾞを文字列配列に入れて返します。
    259         *
    260         * これは、タグリブのラベルリソース登録時に使用する情報です。
    261         * タグリブのローカルルールとして、0:ﾊﾟﾗﾒｰﾀ 1:ﾗﾍﾞﾙ 2:ﾗﾍﾞﾙ以降の解説 3:ｺｰﾄﾞ
    262         * という記述を行う事を前提にしています。
    263         *
    264         * 0:ﾊﾟﾗﾒｰﾀは、引数です。メソッド名ではありませんので、ご注意ください。
    265         * 1:ﾗﾍﾞﾙは、ﾊﾟﾗﾒｰﾀの次の空白文字から、次の空白文字、または、"[" までの文字です。
    266         * 2:ｺﾒﾝﾄは、ﾗﾍﾞﾙ以降の文字列で、ｺｰﾄﾞの記述部分も含みます。
    267         * 3:ｺｰﾄﾞ は、特別な処理を行います。"[" と "]" 内に記述された内容を取り出します。
    268         *
    269         * @og.rev 7.3.0.0 (2021/01/06) 新しいJavaDoc対応
    270         *
    271         * @param       mdoc DocCommentTreeｵﾌﾞｼﾞｪｸﾄ
    272         *
    273         * @return      0:ﾊﾟﾗﾒｰﾀ、1:ﾗﾍﾞﾙ、2:ｺﾒﾝﾄ、3:ｺｰﾄﾞを文字列配列に詰めた値(長さ４の配列)
    274         * @og.rtnNotNull
    275         */
    276        private String[] methodLabelCode( final DocCommentTree mdoc ) {
    277                final String[] arys = new String[] { "","","","" } ;    // 後で内容を更新する。
    278
    279                final Map<String,List<String>> blkTagMap = blockTagsMap(mdoc);
    280                final String prmTag = getBlockTag( DOC_PARAM, blkTagMap, "" );
    281
    282//              String prmTag = "";
    283//              for( final DocTree dt : mdoc.getBlockTags() ) {
    284//                      final String tag = String.valueOf(dt);
    285//                      if( tag.contains( DOC_PARAM ) ) {
    286//                              prmTag = cutTag( tag,DOC_PARAM ); break;                // Taglibのsetter は、paramは一つだけのハズ
    287//                      }
    288//              }
    289
    290                // 最大３つ と指定しているが、0:ﾊﾟﾗﾒｰﾀと1:ﾗﾍﾞﾙの２つしか保証されていない。
    291                final String[] temp = prmTag.split( "[\\s]+",3 );               // 空白文字で３つに分解する。
    292                System.arraycopy( temp,0,arys,0,temp.length );                  // 6.3.6.0 (2015/08/16)
    293
    294                // 3:ｺｰﾄﾞ があれば、2:ｺﾒﾝﾄから取り出す。
    295                final int st1 = arys[2].indexOf( '[' );
    296                if( st1 >= 0 ) {
    297                        final int st2 = arys[2].indexOf( ']',st1 );
    298                        if( st2 > 0 ) {
    299                                // 前後の [] は、取り除き、'/' があれば、' ' に置換する。(ｺｰﾄﾞ文字列化)
    300                                arys[3] = arys[2].substring( st1+1,st2 ).replace( '/' , ' ' );
    301                        }
    302                }
    303
    304                return arys ;
    305        }
    306
    307        /**
    308         * サポートされているすべてのオプションを返します。
    309         *
    310         * @return サポートされているすべてのオプションを含むセット、存在しない場合は空のセット
    311         */
    312        @Override
    313        public Set<? extends Option> getSupportedOptions() {
    314                final Option[] options = {
    315                        new AbstractOption( "-outfile", "-version" ) {
    316
    317                                /**
    318                                 * 必要に応じてオプションと引数を処理します。
    319                                 *
    320                                 * @param  opt オプション名
    321                                 * @param  arguments 引数をカプセル化したリスト
    322                                 * @return 操作が成功した場合はtrue、そうでない場合はfalse
    323                                 */
    324                                @Override
    325                                public boolean process(final String opt, final List<String> arguments) {
    326                                        if( "-outfile".equalsIgnoreCase(opt) ) {
    327                                                outfile = arguments.get(0);
    328                                        }
    329                                        else if( "-version".equalsIgnoreCase(opt) ) {
    330                                                version = arguments.get(0);
    331                                        }
    332                                        return true;
    333                                }
    334                        }
    335                };
    336                return new HashSet<>(Arrays.asList(options));
    337        }
    338}