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 * @og.rev 8.0.2.1 (2021/12/10) コメント分割で『。』と半角の『。』の両方対応しておく。 111 * 112 * @param docEnv ドックレットの最上位 113 * @param writer DocTreeWriterオブジェクト 114 */ 115 private void writeContents( final DocletEnvironment docEnv, final DocTreeWriter writer ) { 116 docUtil = docEnv.getDocTrees(); 117 118 final Map<String,List<String>> verMap = new HashMap<>(); 119 String revYMD = null; // 初めてのRev.日付をセットする。 120 121 // get the DocTrees utility class to access document comments 122 final DocTrees docTrees = docEnv.getDocTrees(); 123// final Elements eleUtil = docEnv.getElementUtils(); 124 125 // クラス単位にループする。 126 for( final TypeElement typEle : ElementFilter.typesIn(docEnv.getIncludedElements())) { 127 final String fullName = String.valueOf(typEle).substring( omitPackage ); // パッケージ名を簡略化しておきます。 128 writer.setClassName( fullName ); 129 130 // // クラスに書かれている private static final String VERSION フィールドを先に取得する。 131 // String clsVer = null; 132 // for( final VariableElement ele : ElementFilter.fieldsIn(typEle.getEnclosedElements())) { // フィールドだけに絞る 133 // final Set<Modifier> modi = ele.getModifiers(); 134 // final String typ = String.valueOf( ele.asType() ); 135 // if( modi.contains( Modifier.PRIVATE ) && modi.contains( Modifier.STATIC ) && typ.contains( "String" ) 136 // && "VERSION".equals( ele.getSimpleName() ) ) { 137 // clsVer = String.valueOf( ele.getConstantValue() ); 138 // } 139 // } 140 141 // 5.6.6.0 (2013/07/05) VERSION staticフィールドと、@og.rev コメントの比較チェック 142 // while 以下で、fullName と classDoc を順番に上にさかのぼっているので、先にチェックします。 143 final String clsVer = checkTag2( typEle ); 144 145 for( final Element ele : typEle.getEnclosedElements()) { 146 final DocCommentTree doc = docTrees.getDocCommentTree(ele); // ドキュメンテーション・コメントが見つからない場合、null が返る。 147 if( doc == null ) { continue; } 148 149 for( final DocTree dt : doc.getBlockTags() ) { 150 final String tag = String.valueOf(dt); 151 if( tag.contains( OG_REV ) ) { 152 final String[] tags = tag.split( " ",4 ); // タグ , バージョン , 日付 , コメント (@og.rev 7.3.0.0 (2021/01/06) 新しいJavaDoc対応) 153 if( tags.length >= 2 ) { // 最低限、バージョン は記載されている 154 final String tagVer = tags[1]; // バージョン 155 156 // Ver チェックで、旧の場合 157 if( clsVer != null && clsVer.compareTo( tagVer ) < 0 ) { 158 final String msg = "旧Ver:" + fullName + ":" + version + " : " + tagVer ; 159 reporter.print(Kind.WARNING, msg ); // NOTE:情報 WARNING:警告 ERROR:エラー 160 } 161 162 if( tags.length >= 4 && tagVer.equals( version ) ) { // バージョンが一致して、コメント まで記載済み 163 // コメントを「。」で分割する。まずは、convertToOiginal で、オリジナルに戻しておく 164 final String cmnt = writer.convertToOiginal( tags[3] ); 165 // 8.0.2.1 (2021/12/10) コメント分割で『。』と半角の『。』の両方対応しておく。 166// final int idx = cmnt.indexOf( '。' ); // '。' の区切り文字の前半部分が、タイトル。 167 final int idx = Math.max( cmnt.indexOf( '。' ) , cmnt.indexOf( '。' ) ); 168 final String key = idx > 0 ? cmnt.substring( 0,idx ).trim() : cmnt ; 169 170// final String key = tags[3]; // コメントがキー 171 final String val = fullName + "#" + ele ; // メソッドが値 172 verMap.computeIfAbsent( key, k -> new ArrayList<>() ).add( val ); 173 174 if( revYMD == null ) { // 一番最初だけセットする 175 revYMD = tagVer + " " + tags[2]; // バージョン + 日付 176 } 177 } 178 } 179 } 180 } 181 } 182 } 183 184 // 書き出し 185 if( revYMD != null ) { 186 writer.printTag( revYMD ); 187 for( final Map.Entry<String,List<String>> entry : verMap.entrySet() ) { 188 final String msg = entry.getKey().trim(); 189 // writer.printTag( "\n\t[" , entry.getKey() , "]" ); 190 writer.printTag( "\n\t[" , msg , "]" ); // 8.0.0.0 (2021/07/31) 191 for( final String str : entry.getValue() ) { // リスト 192 writer.printTag( "\t\t" , str ); 193 } 194 } 195 } 196 } 197 198 /** 199 * PMDで、チェックしている処理のうち、Docletでフォローできる分をチェックします。 200 * 201 * ※ このチェックは、警告レベル5 のみ集約していますので、呼出元で、制限します。 202 * 203 * ※ DocTreeSpecific から、移植しました。 204 * 205 * @og.rev 7.3.0.0 (2021/01/06) 新しいJavaDoc対応 206 * 207 * @param typEle TypeElementオブジェクト 208 * @return VERSIONフィールドの値 209 */ 210 private String checkTag2( final TypeElement typEle ) { 211 String cnstVar = null ; // 初期値 212 String seriUID = null ; 213 214 // フィールドのみフィルタリングして取得する 215 for( final VariableElement varEle : ElementFilter.fieldsIn(typEle.getEnclosedElements())) { // フィールドだけに絞る 216 final Set<Modifier> modi = varEle.getModifiers(); 217 if( modi.contains( Modifier.PRIVATE ) && modi.contains( Modifier.STATIC ) ) { 218 final String key = String.valueOf( varEle.getSimpleName() ); 219 if( "VERSION".equals( key ) ) { 220 cnstVar = String.valueOf( varEle.getConstantValue() ); 221 } 222 else if( "serialVersionUID".equals( key ) ) { 223 seriUID = varEle.getConstantValue() + "L"; // 旧JavaDocと違い、"L" まで取ってこれないみたい 224 } 225 } 226 } 227 228 if( cnstVar == null ) { return null; } // VERSION が未定義のクラスは処理しない 229 230 String maxRev = cnstVar ; // 5.7.1.1 (2013/12/13) 初期値 231 boolean isChange = false; // max が入れ替わったら、true 232 233 // メソッドのみフィルタリングして取得する 234 for( final ExecutableElement exEle : ElementFilter.methodsIn(typEle.getEnclosedElements())) { 235 final DocCommentTree dct = docUtil.getDocCommentTree(exEle); // ドキュメンテーション・コメントが見つからない場合、null が返る。 236 final Map<String,List<String>> blkTagMap = blockTagsMap(dct); 237 final List<String> revTags = blkTagMap.get("og.rev"); 238 239 if( revTags != null ) { 240 for( final String tag :revTags ) { // 複数存在しているはず 241 final String[] tags = tag.split( " ",3 ); // 最小3つに分割する。 242 243 if( tags.length >= 2 ) { 244 final String rev = ( tags[0] + ' ' + tags[1] ).trim(); 245 if( maxRev.compareTo( rev ) < 0 ) { // revTags の og.rev が大きい場合 246 maxRev = rev ; 247 isChange = true; 248 } 249 } 250 } 251 } 252 } 253 254 final String src = "\tsrc/" + String.valueOf(typEle).replace('.','/') + ".java:100" ; // 行が判らないので、100行目 決め打ち 255 256 // VERSION 文字列 の定義があり、かつ、max の入れ替えが発生した場合のみ、警告4:VERSIONが古い 257 if( isChange ) { // 5.7.1.1 (2013/12/13) 入れ替えが発生した場合 258 System.err.println( "警告4:VERSIONが古い=\t" + cnstVar + " ⇒ " + maxRev + src ); 259 } 260 261 // serialVersionUID の定義がある。 262 if( seriUID != null ) { 263 final StringBuilder buf = new StringBuilder(); 264 // maxRev は、最大の Revか、初期のVERSION文字列 例:5.6.6.0 (2013/07/05) 265 for( int i=0; i<maxRev.length(); i++ ) { // 266 final char ch = maxRev.charAt( i ); 267 if( ch >= '0' && ch <= '9' ) { buf.append( ch ); } // 数字だけ取り出す。 例:566020130705 268 } 269 buf.append( 'L' ); // 強制的に、L を追加する。 270 final String maxSeriUID = buf.toString() ; 271 272 // 5.7.1.1 (2013/12/13) 値の取出し。Long型を表す "L" も含まれている。 273 if( !maxSeriUID.equals( seriUID ) ) { // 一致しない 274 System.err.println( "警告4:serialVersionUIDが古い=\t" + seriUID + " ⇒ " + maxSeriUID + src ); 275 } 276 } 277 278 return cnstVar ; 279 } 280 281 /** 282 * サポートされているすべてのオプションを返します。 283 * 284 * @return サポートされているすべてのオプションを含むセット、存在しない場合は空のセット 285 */ 286 @Override 287 public Set<? extends Option> getSupportedOptions() { 288 final Option[] options = { 289 new AbstractOption( "-outfile", "-version", "-omitPackage" ) { 290 291 /** 292 * 必要に応じてオプションと引数を処理します。 293 * 294 * @param opt オプション名 295 * @param arguments 引数をカプセル化したリスト 296 * @return 操作が成功した場合はtrue、そうでない場合はfalse 297 */ 298 @Override 299 public boolean process(final String opt, final List<String> arguments) { 300 if( "-outfile".equalsIgnoreCase(opt) ) { 301 outfile = arguments.get(0); 302 } 303 else if( "-version".equalsIgnoreCase(opt) ) { 304 version = arguments.get(0); 305 } 306 else if( "-omitPackage".equalsIgnoreCase(opt) ) { 307 omitPackage = arguments.get(0).length()+1; 308 } 309 return true; 310 } 311 } 312 }; 313 return new HashSet<>(Arrays.asList(options)); 314 } 315}