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