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}