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.taglet2;
017
018
019import jdk.javadoc.doclet.DocletEnvironment      ;
020// import jdk.javadoc.doclet.Doclet  ;
021// import jdk.javadoc.doclet.Reporter ;
022import javax.lang.model.element.Element ;
023import javax.lang.model.element.Modifier ;
024import javax.lang.model.element.TypeElement;
025// import javax.lang.model.element.ElementKind  ;
026import javax.lang.model.element.VariableElement;
027import javax.lang.model.element.ExecutableElement;
028// import javax.lang.model.SourceVersion ;
029import javax.lang.model.util.ElementFilter ;
030// import javax.lang.model.util.Elements         ;
031import javax.tools.Diagnostic.Kind ;
032import com.sun.source.doctree.DocCommentTree  ;
033import com.sun.source.doctree.DocTree  ;
034import com.sun.source.util.DocTrees  ;
035
036// import java.util.Locale ;
037import java.util.Set;
038import java.util.Map;
039import java.util.List;
040import java.util.ArrayList;
041import java.util.HashSet;
042import java.util.HashMap;
043import java.util.Arrays;
044
045// import java.io.IOException;
046// import java.io.File;
047// import java.io.PrintWriter;
048
049// import org.opengion.fukurou.util.FileUtil;
050// import org.opengion.fukurou.util.StringUtil;
051
052/**
053 * ソースコメントから、タグ情報を取り出す Doclet クラスです。
054 * パラメータの version に一致する og.rev タグの 書かれたメソッドを、
055 * ピックアップします。
056 * og.rev タグ で、まとめて表示します。
057 *
058 * ルールとしては、X.X.X.X だけが引数で渡されますので、 X.X.X.X (YYYY/MM/DD) が
059 * コメントとして記載されているとして、処理します。
060 * そして、それ以降の記述の、「。」までを、キーワードとして、まとめます。
061 * キーワード以下は、それぞれのコメントとして、各メソッドの直前に表示します。
062 * このクラスは、RELEASE-NOTES.txt を作成する場合に、変更箇所をピックアップするのに使用します。
063 *
064 * @version  7.3
065 * @author      Kazuhiko Hasegawa
066 * @since        JDK11.0,
067 */
068public class DocTreeVerCheck extends AbstractDocTree {
069        private static final String  SELECT_PACKAGE     = "org.opengion." ;
070
071        private static final String     OG_REV                  = "og.rev";
072
073        private static  int     omitPackage     = SELECT_PACKAGE.length()       ;       // パッケージ名の先頭をカットするため
074
075        private String  version ;
076        private String  outfile ;
077
078        private DocTrees docUtil;
079
080        /**
081         * Doclet のエントリポイントメソッドです(昔の startメソッド)。
082         *
083         * @og.rev 7.3.0.0 (2021/01/06) 新しいJavaDoc対応
084         *
085         * @param docEnv ドックレットを1回呼び出す操作環境
086         *
087         * @return 正常実行時 true
088         */
089        @Override
090        public boolean run( final DocletEnvironment docEnv ) {
091                try( DocTreeWriter writer = new DocTreeWriter( outfile,ENCODE ) ) {
092                        writeContents( docEnv,writer );
093                }
094                catch( final Throwable th ) {
095                        reporter.print(Kind.ERROR, th.getMessage());
096                }
097
098                return true;
099        }
100
101        /**
102         * DocletEnvironmentよりコンテンツを作成します。
103         *
104         * @og.rev 7.3.0.0 (2021/01/06) 新しいJavaDoc対応
105         *
106         * @param docEnv        ドックレットの最上位
107         * @param writer        DocTreeWriterオブジェクト
108         */
109        private void writeContents( final DocletEnvironment docEnv, final DocTreeWriter writer ) {
110                docUtil = docEnv.getDocTrees();
111
112                final Map<String,List<String>> verMap = new HashMap<>();
113                String revYMD = null;   // 初めてのRev.日付をセットする。
114
115                // get the DocTrees utility class to access document comments
116                final DocTrees docTrees = docEnv.getDocTrees();
117//              final Elements eleUtil  = docEnv.getElementUtils();
118
119                // クラス単位にループする。
120                for( final TypeElement typEle : ElementFilter.typesIn(docEnv.getIncludedElements())) {
121                        final String fullName = String.valueOf(typEle).substring( omitPackage );                // パッケージ名を簡略化しておきます。
122                        writer.setClassName( fullName );
123
124        //              // クラスに書かれている private static final String VERSION フィールドを先に取得する。
125        //              String clsVer = null;
126        //              for( final VariableElement ele : ElementFilter.fieldsIn(typEle.getEnclosedElements())) {                // フィールドだけに絞る
127        //                      final Set<Modifier> modi = ele.getModifiers();
128        //                      final String typ = String.valueOf( ele.asType() );
129        //                      if( modi.contains(  Modifier.PRIVATE  ) && modi.contains( Modifier.STATIC ) && typ.contains( "String" )
130        //                                                      && "VERSION".equals( ele.getSimpleName() ) ) {
131        //                              clsVer = String.valueOf( ele.getConstantValue() );
132        //                      }
133        //              }
134
135                        // 5.6.6.0 (2013/07/05) VERSION staticフィールドと、@og.rev コメントの比較チェック
136                        // while 以下で、fullName と classDoc を順番に上にさかのぼっているので、先にチェックします。
137                        final String clsVer = checkTag2( typEle );
138
139                        for( final Element ele : typEle.getEnclosedElements()) {
140                                final DocCommentTree doc = docTrees.getDocCommentTree(ele);             // ドキュメンテーション・コメントが見つからない場合、null が返る。
141                                if( doc == null ) { continue; }
142
143                                for( final DocTree dt : doc.getBlockTags() ) {
144                                        final String tag = String.valueOf(dt);
145                                        if( tag.contains( OG_REV ) ) {
146                                                final String[] tags = tag.split( " ",4 );               // タグ , バージョン , 日付 , コメント (@og.rev 7.3.0.0 (2021/01/06) 新しいJavaDoc対応)
147                                                if( tags.length >= 2 ) {                                                // 最低限、バージョン は記載されている
148                                                        final String tagVer = tags[1];                          // バージョン
149
150                                                        // Ver チェックで、旧の場合
151                                                        if( clsVer != null && clsVer.compareTo( tagVer ) < 0 ) {
152                                                                final String msg = "旧Ver:" + fullName + ":" + version + " : " + tagVer ;
153                                                                reporter.print(Kind.WARNING, msg );                                     // NOTE:情報 WARNING:警告 ERROR:エラー
154                                                        }
155
156                                                        if( tags.length >= 4 && tagVer.equals( version ) ) {    // バージョンが一致して、コメント まで記載済み
157                                                                final String key = tags[3];                                                     // コメントがキー
158                                                                final String val = fullName + "#" + ele ;                       // メソッドが値
159                                                                verMap.computeIfAbsent( key, k -> new ArrayList<>() ).add( val );
160
161                                                                if( revYMD == null ) {                                                          // 一番最初だけセットする
162                                                                        revYMD = tagVer + " " + tags[2];                                // バージョン + 日付
163                                                                }
164                                                        }
165                                                }
166                                        }
167                                }
168                        }
169                }
170
171                // 書き出し
172                if( revYMD != null ) {
173                        writer.printTag( revYMD );
174                        for( final Map.Entry<String,List<String>> entry : verMap.entrySet() ) {
175                                writer.printTag( "\n\t[" , entry.getKey() , "]" );
176                                for( final String str : entry.getValue() ) {                            // リスト
177                                        writer.printTag( "\t\t" , str );
178                                }
179                        }
180                }
181        }
182
183        /**
184         * PMDで、チェックしている処理のうち、Docletでフォローできる分をチェックします。
185         *
186         * ※ このチェックは、警告レベル5 のみ集約していますので、呼出元で、制限します。
187         *
188         * ※ DocTreeSpecific から、移植しました。
189         *
190         * @og.rev 7.3.0.0 (2021/01/06) 新しいJavaDoc対応
191         *
192         * @param typEle TypeElementオブジェクト
193         * @return VERSIONフィールドの値
194         */
195        private String checkTag2( final TypeElement typEle ) {
196                String cnstVar = null ;         // 初期値
197                String seriUID = null ;
198
199                // フィールドのみフィルタリングして取得する
200                for( final VariableElement varEle : ElementFilter.fieldsIn(typEle.getEnclosedElements())) {             // フィールドだけに絞る
201                        final Set<Modifier> modi = varEle.getModifiers();
202                        if( modi.contains( Modifier.PRIVATE ) && modi.contains( Modifier.STATIC ) ) {
203                                final String key = String.valueOf( varEle.getSimpleName() );
204                                if( "VERSION".equals( key ) ) {
205                                        cnstVar = String.valueOf( varEle.getConstantValue() );
206                                }
207                                else if( "serialVersionUID".equals( key ) ) {
208                                        seriUID = varEle.getConstantValue() + "L";                      // 旧JavaDocと違い、"L" まで取ってこれないみたい
209                                }
210                        }
211                }
212
213                if( cnstVar == null ) { return null; }                  // VERSION が未定義のクラスは処理しない
214
215                String maxRev = cnstVar ;                                               // 5.7.1.1 (2013/12/13) 初期値
216                boolean isChange = false;                                               // max が入れ替わったら、true
217
218                // メソッドのみフィルタリングして取得する
219                for( final ExecutableElement exEle : ElementFilter.methodsIn(typEle.getEnclosedElements())) {
220                        final DocCommentTree dct = docUtil.getDocCommentTree(exEle);            // ドキュメンテーション・コメントが見つからない場合、null が返る。
221                        final Map<String,List<String>> blkTagMap = blockTagsMap(dct);
222                        final List<String> revTags = blkTagMap.get("og.rev");
223
224                        if( revTags != null ) {
225                                for( final String tag :revTags ) {                                                              // 複数存在しているはず
226                                        final String[] tags = tag.split( " ",3 );                                       // 最小3つに分割する。
227
228                                        if( tags.length >= 2 ) {
229                                                final String rev = ( tags[0] + ' ' + tags[1] ).trim();
230                                                if( maxRev.compareTo( rev ) < 0 ) {                                             // revTags の og.rev が大きい場合
231                                                        maxRev = rev ;
232                                                        isChange = true;
233                                                }
234                                        }
235                                }
236                        }
237                }
238
239                final String src = "\tsrc/" + String.valueOf(typEle).replace('.','/') + ".java:100" ;                   // 行が判らないので、100行目 決め打ち
240
241                // VERSION 文字列 の定義があり、かつ、max の入れ替えが発生した場合のみ、警告4:VERSIONが古い
242                if( isChange ) {                        // 5.7.1.1 (2013/12/13) 入れ替えが発生した場合
243                        System.err.println( "警告4:VERSIONが古い=\t" + cnstVar + " ⇒ " + maxRev + src );
244                }
245
246                // serialVersionUID の定義がある。
247                if( seriUID != null ) {
248                        final StringBuilder buf = new StringBuilder();
249                        // maxRev は、最大の Revか、初期のVERSION文字列 例:5.6.6.0 (2013/07/05)
250                        for( int i=0; i<maxRev.length(); i++ ) {        //
251                                final char ch = maxRev.charAt( i );
252                                if( ch >= '0' && ch <= '9' ) { buf.append( ch ); }      // 数字だけ取り出す。 例:566020130705
253                        }
254                        buf.append( 'L' );      // 強制的に、L を追加する。
255                        final String maxSeriUID = buf.toString() ;
256
257                        // 5.7.1.1 (2013/12/13) 値の取出し。Long型を表す "L" も含まれている。
258                        if( !maxSeriUID.equals( seriUID ) ) {   // 一致しない
259                                System.err.println( "警告4:serialVersionUIDが古い=\t" + seriUID + " ⇒ " + maxSeriUID + src );
260                        }
261                }
262
263                return cnstVar ;
264        }
265
266        /**
267         * サポートされているすべてのオプションを返します。
268         *
269         * @return サポートされているすべてのオプションを含むセット、存在しない場合は空のセット
270         */
271        @Override
272        public Set<? extends Option> getSupportedOptions() {
273                final Option[] options = {
274                        new AbstractOption( "-outfile", "-version" ) {
275
276                                /**
277                                 * 必要に応じてオプションと引数を処理します。
278                                 *
279                                 * @param  opt オプション名
280                                 * @param  arguments 引数をカプセル化したリスト
281                                 * @return 操作が成功した場合はtrue、そうでない場合はfalse
282                                 */
283                                @Override
284                                public boolean process(final String opt, final List<String> arguments) {
285                                        if( "-outfile".equalsIgnoreCase(opt) ) {
286                                                outfile = arguments.get(0);
287                                        }
288                                        else if( "-version".equalsIgnoreCase(opt) ) {
289                                                version = arguments.get(0);
290                                        }
291                                        return true;
292                                }
293                        }
294                };
295                return new HashSet<>(Arrays.asList(options));
296        }
297}