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