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