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