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; 017 018import org.opengion.fukurou.system.LogWriter; 019import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE; // 6.1.0.0 (2014/12/26) refactoring 020 021import java.util.Set; 022import java.util.HashSet; 023import java.io.IOException; 024import java.util.regex.Pattern; // 6.3.9.1 (2015/11/27) final化に伴う整理 025 026import java.util.stream.Stream; // 6.4.3.4 (2016/03/11) 027import java.util.stream.Collectors; // 6.4.3.4 (2016/03/11) 028 029import com.sun.javadoc.RootDoc; 030import com.sun.javadoc.ClassDoc; 031import com.sun.javadoc.MethodDoc; 032import com.sun.javadoc.FieldDoc; 033import com.sun.javadoc.Doc; 034import com.sun.javadoc.ConstructorDoc; 035import com.sun.javadoc.ExecutableMemberDoc; 036import com.sun.javadoc.Type; 037import com.sun.javadoc.Parameter; 038import com.sun.javadoc.Tag; 039import com.sun.javadoc.SourcePosition; 040import com.sun.javadoc.AnnotationDesc; 041import com.sun.javadoc.AnnotationTypeDoc; 042 043/** 044 * ソースコメントから、タグ情報を取り出す Doclet クラスです。 045 * クラスファイルの仕様を表現する為、og.formSample , og.rev , og.group , 046 * version , author , since の各タグコメントより値を抽出します。 047 * また、各クラスの継承関係、インターフェース、メソッドなども抽出します。 048 * これらの抽出結果をDB化し、EXCELファイルに帳票出力する事で、クラスファイルの 049 * ソースから仕様書を逆作成します。 050 * 051 * @version 4.0 052 * @author Kazuhiko Hasegawa 053 * @since JDK5.0, 054 */ 055@SuppressWarnings(value={"deprecation","removal"}) // Ver7.0.0.0 , 7.2.2.0 (2020/03/27) 056public final class DocletSpecific { 057 private static final String SELECT_PACKAGE = "org.opengion" ; 058 private static final boolean USE_PRIVATE = false ; 059 private static final String ENCODE = "UTF-8"; 060 061 private static final String OG_FOR_SMPL = "og.formSample"; 062 private static final String OG_REV = "og.rev"; 063 private static final String OG_TAG_NAME = "og.tag"; // 6.1.2.0 (2015/01/24) チェック用 064 private static final String OG_GROUP = "og.group"; 065 private static final String DOC_VERSION = "version"; 066 private static final String DOC_AUTHOR = "author"; 067 private static final String DOC_SINCE = "since"; 068 069 private static final String DOC_PARAM = "param"; // 5.1.9.0 (2010/08/01) チェック用 070 private static final String DOC_RETURN = "return"; // 5.1.9.0 (2010/08/01) チェック用 071 072 private static final String CONSTRUCTOR = "コンストラクタ" ; 073 private static final String METHOD = "メソッド" ; 074 private static final Set<String> METHOD_SET = new HashSet<>(); // 6.4.1.1 (2016/01/16) methodSet → METHOD_SET refactoring 075 076 private static int debugLevel ; // 0:なし 1:最小チェック 2:日本語化 3:体裁 4:Verチェック 5:taglibラベル 077 078 // 5.1.9.0 (2010/08/01) ソースチェック用(半角文字+空白文字のみ) 079 private static final Pattern PTN = Pattern.compile("[\\w\\s]+"); // 6.3.9.1 (2015/11/27) 080 081 /** 082 * すべてが staticメソッドなので、コンストラクタを呼び出さなくしておきます。 083 * 084 */ 085 private DocletSpecific() {} 086 087 /** 088 * Doclet のエントリポイントメソッドです。 089 * 090 * @og.rev 5.5.4.1 (2012/07/06) Tag出力時の CR → BR 変換を行わない様にする。 091 * @og.rev 5.7.1.1 (2013/12/13) タグのインデントを止める。 092 * 093 * @param root エントリポイントのRootDocオブジェクト 094 * 095 * @return 正常実行時 true 096 */ 097 public static boolean start( final RootDoc root ) { 098 final String version = DocletUtil.getOption( "-version" , root.options() ); 099 final String file = DocletUtil.getOption( "-outfile" , root.options() ); 100 final String dbgLvl = DocletUtil.getOption( "-debugLevel" , root.options() ); // 5.5.4.1 (2012/07/06) パラメータ引数 101 if( dbgLvl != null ) { debugLevel = Integer.parseInt( dbgLvl ); } 102 103 DocletTagWriter writer = null; 104 try { 105 writer = new DocletTagWriter( file,ENCODE ); // 5.5.4.1 (2012/07/06) 106 107 // 5.7.1.1 (2013/12/13) タグのインデントを止める。 108 writer.printTag( "<?xml version=\"1.0\" encoding=\"", ENCODE, "\" ?>" ); 109 writer.printTag( "<javadoc>" ); 110 writer.printTag( " <version>",version,"</version>" ); 111 writer.printTag( " <description></description>" ); 112 writeContents( root.classes(),writer ); 113 writer.printTag( "</javadoc>" ); 114 } 115 catch( final IOException ex ) { 116 LogWriter.log( ex ); 117 } 118 finally { 119 if( writer != null ) { writer.close(); } 120 } 121 return true; 122 } 123 124 /** 125 * ClassDoc 配列よりコンテンツを作成します。 126 * 127 * @og.rev 5.5.4.1 (2012/07/06) コメントは文字列でなく、Tag配列として処理させる。 128 * @og.rev 5.6.6.0 (2013/07/05) VERSION staticフィールドと、@og.rev コメントの比較チェック 129 * @og.rev 5.7.1.1 (2013/12/13) タグのインデントを止める。 130 * @og.rev 6.4.3.0 (2016/02/05) PMDチェックのDocletでのフォロー。 131 * @og.rev 6.4.3.4 (2016/03/11) Stream と Collectors で、カンマ連結を行う。 132 * 133 * @param classes ClassDoc配列 134 * @param writer Tagを書き出すWriterオブジェクト 135 */ 136 private static void writeContents( final ClassDoc[] classes,final DocletTagWriter writer ) { 137 for( int i=0; i< classes.length; i++ ) { 138 ClassDoc classDoc = classes[i] ; 139 final String className = classDoc.name(); 140 String fullName = classDoc.qualifiedName() ; 141 final String modifiers = (classDoc.modifiers() + ( classDoc.isClass() ? " class" : "" ) ).trim(); 142 143 final Type superType = classDoc.superclassType(); 144 final String superClass = ( superType == null ) ? "" : superType.qualifiedTypeName(); 145 146 // 6.4.3.4 (2016/03/11) Stream と Collectors で、カンマ連結を行う。 147 final String intFase = Stream.of( classDoc.interfaceTypes() ) 148 .map( typ -> typ.qualifiedTypeName() ) 149 .collect( Collectors.joining( "," ) ); 150 151 final Tag[] desc = classDoc.firstSentenceTags(); 152 final Tag[] cmnt = classDoc.inlineTags(); // 5.5.4.1 (2012/07/06) 153 final Tag[] smplTags = classDoc.tags(OG_FOR_SMPL); 154 final Tag[] revTags = classDoc.tags(OG_REV); 155 final Tag[] createVer = classDoc.tags(DOC_VERSION); 156 final Tag[] author = classDoc.tags(DOC_AUTHOR); 157 final Tag[] since = classDoc.tags(DOC_SINCE); 158 final Tag[] grpTags = classDoc.tags(OG_GROUP); 159 160 // 5.7.1.1 (2013/12/13) タグのインデントを止める。 161 writer.printTag( "<classDoc>" ); 162 writer.printTag( " <fullName>" ,fullName ,"</fullName>" ); 163 writer.printTag( " <modifiers>" ,modifiers ,"</modifiers>" ); 164 writer.printTag( " <className>" ,className ,"</className>" ); 165 writer.printTag( " <superClass>" ,superClass ,"</superClass>" ); 166 writer.printTag( " <interface>" ,intFase ,"</interface>" ); 167 writer.printTag( " <createVer>" ,createVer ,"</createVer>" ); 168 writer.printTag( " <author>" ,author ,"</author>" ); 169 writer.printTag( " <since>" ,since ,"</since>" ); 170 writer.printTag( " <description>" ,desc ,"</description>" ); 171 writer.printTag( " <contents>" ,cmnt ,"</contents>" ); 172 writer.printTag( " <classGroup>" ); 173 writer.printCSVTag( grpTags ); 174 writer.printTag( " </classGroup>" ); 175 writer.printTag( " <formSample>" ,smplTags ,"</formSample>" ); 176 writer.printTag( " <history>" ,revTags ,"</history>" ); 177 178 // 5.1.9.0 (2010/08/01) ソースチェック用(コメントや概要が無い場合。スーパークラスは省く) 179 if( debugLevel >= 2 && ( cmnt.length == 0 || desc.length == 0 ) && superClass.isEmpty() ) { 180 System.err.println( "警告2:コメントC=\t" + classDoc.position() ); 181 } 182 183 int extendFlag = 0; // 0:オリジナル 1:org.opengion関連Extend 2:Java関連Extend 184 // while( fullName.startsWith( SELECT_PACKAGE ) ) { 185 186 // 6.4.3.0 (2016/02/05) PMDチェックのDocletでのフォロー。 187 checkPMD( classDoc ); 188 189 // 5.6.6.0 (2013/07/05) VERSION staticフィールドと、@og.rev コメントの比較チェック 190 // while 以下で、fullName と classDoc を順番に上にさかのぼっているので、先にチェックします。 191 checkTag2( classDoc ); 192 193 METHOD_SET.clear(); 194 while( true ) { 195 final ConstructorDoc[] cnstrctrs = classDoc.constructors( false ); // 5.1.9.0 (2010/08/01) チェック用 196 for( int j=0; j<cnstrctrs.length; j++ ) { 197 if( isAction( cnstrctrs[j],extendFlag ) ) { 198 // if( extendFlag < 2 ) { 199 checkTag( cnstrctrs[j] ); // 5.5.4.1 (2012/07/06) チェックを分離 200 menberTag( cnstrctrs[j],CONSTRUCTOR,writer,extendFlag ); 201 // } 202 } 203 } 204 205 final MethodDoc[] methods = classDoc.methods( false ); // 5.1.9.0 (2010/08/01) チェック用 206 for( int j=0; j<methods.length; j++ ) { 207 if( isAction( methods[j],extendFlag ) ) { 208 // if( extendFlag < 2 ) { 209 checkTag( methods[j] ); // 5.5.4.1 (2012/07/06) チェックを分離 210 menberTag( methods[j],METHOD,writer,extendFlag ); 211 // } 212 } 213 } 214 215 // 対象クラス(オリジナル)から、上に上がっていく。 216 final Type type = classDoc.superclassType(); 217 if( type == null ) { break; } 218 classDoc = type.asClassDoc() ; 219 fullName = classDoc.qualifiedName(); 220 // 上位は処理しない。 221 if( fullName.startsWith( SELECT_PACKAGE ) ) { 222 extendFlag = 1; 223 } 224 else { 225 break; 226 } 227 228// // java.lang.Object クラスは対象が多いため、処理しません。 229// if( "java.lang.Object".equals( fullName ) || classDoc.isEnum() ) { 230// break; 231// } 232// else if( fullName.startsWith( SELECT_PACKAGE ) ) { 233// extendFlag = 1; 234// } 235// else { 236// extendFlag = 2; // 上位は処理しない。 237// } 238 } 239 240 writer.printTag( " </classDoc>" ); 241 } 242 } 243 244 /** 245 * メンバークラスのXML化を行うかどうか[true/false]を判定します。 246 * 247 * 以下の条件に合致する場合は、処理を行いません。(false を返します。) 248 * 249 * 1.同一クラスを処理中にEXTENDで継承元をさかのぼる場合、すでに同じシグネチャのメソッドが 250 * 存在している。 251 * 2.USE_PRIVATE が true の時の private メソッド 252 * 3.extendFlag が 0以上(1,2)の時の private メソッド 253 * 4.メソッド名におかしな記号(<など)が含まれている場合 254 * 255 * @og.rev 5.5.4.1 (2012/07/06) メソッドの重複処理判定は、クラス名も含めて行う 256 * 257 * @param menber ExecutableMemberDocオブジェクト 258 * @param extendFlag 継承状態 [0:オリジナル/1:org.opengion関連Extend/2:Java関連Extend] 259 * 260 * @return XML化を行うかどうか[true/false] 261 */ 262 private static boolean isAction( final ExecutableMemberDoc menber,final int extendFlag ) { 263 final String menberName = menber.name() ; 264 final boolean rtn = ! METHOD_SET.add( menber.toString() ) // 5.5.4.1 (2012/07/06) メソッドの重複処理判定は、クラス名も含めて行う 265 || USE_PRIVATE && menber.isPrivate() 266 || extendFlag > 0 && menber.isPrivate() 267 || menberName.length() > 0 && menberName.charAt(0) == '<' ; // PMD Useless parentheses. 268 269 return ! rtn ; 270 } 271 272 /** 273 * param,return 等の整合性をチェックします。 274 * 275 * @og.rev 5.5.4.1 (2012/07/06) 新規作成。 276 * @og.rev 5.6.6.1 (2013/07/12) Deprecated アノテーション のチェック 277 * @og.rev 6.0.4.0 (2014/11/28) 警告3:PRMタイプは、警告にしない。 278 * @og.rev 6.4.4.0 (2016/03/11) 型パラメータの @param を除外する。 279 * 280 * @param menber ExecutableMemberDocオブジェクト 281 * @param menber ExecutableMemberDocオブジェクト 282 */ 283 private static void checkTag( final ExecutableMemberDoc menber ) { 284 285 // 親が Enum クラスの場合、処理しません。 286 final Type prntType = menber.containingClass().superclassType(); 287 final String prntClass = ( prntType == null ) ? "" : prntType.qualifiedTypeName(); 288 if( "java.lang.Enum".equals( prntClass ) ) { return; } 289 290 final SourcePosition posi = menber.position(); 291 292 if( menber instanceof MethodDoc ) { 293 // メソッドの処理(コンストラクターを省く) 294 final Type rtnType = ((MethodDoc)menber).returnType(); 295 final String typNm = rtnType.typeName(); 296 297 // 6.4.3.1 (2016/02/12) refactoring 298 final StringBuilder modifyBuf = new StringBuilder( BUFFER_MIDDLE ) 299 .append( posi ).append( "\t" ) 300 .append( menber.modifiers() ) 301 .append( ' ' ).append( typNm ); 302 if( rtnType.dimension() != null ) { modifyBuf.append( rtnType.dimension() ); } 303 304 String wormMsg = modifyBuf.toString(); 305 306 // 5.1.9.0 (2010/08/01) ソースチェック用(@return との整合性チェック) 307 final Tag[] docReturn = menber.tags(DOC_RETURN); // 5.1.9.0 (2010/08/01) チェック用 308 if( docReturn.length > 0 ) { 309 final String data = docReturn[0].text().trim(); // 5.5.4.1 (2012/07/06) trim でスペース等の削除 310 311 wormMsg = wormMsg + "\t" + data ; 312 313 // 5.5.4.1 (2012/07/06) ソースチェック用(@return と引数の個数が異なる場合) 314 if( debugLevel >= 1 && "void".equals( typNm ) ) { 315 System.err.println( "警告1:RTNコメント不要=\t" + wormMsg ); 316 } 317 // 「@return 解説」 の形式で、解説に日本語がなければ、警告 318 if( debugLevel >= 2 && PTN.matcher( data ).matches() ) { 319 System.err.println( "警告2:RTN未解説=\t" + wormMsg ); 320 } 321 // 「@return String」 の形式の場合は警告 322 if( debugLevel >= 2 && data.equals( typNm ) ) { 323 System.err.println( "警告2:RTN一致=\t" + wormMsg ); 324 } 325 // 「@return String[]」 など、配列や、<String>などが含まれる場合は警告。 326 // ただし、<T>などの総称型(ジェネリックなクラス)は除外したいので、1文字は、OKとします。 327 if( debugLevel >= 2 && ( data.indexOf( "[]" ) >= 0 || data.indexOf( '>' ) - data.indexOf( '<' ) > 3 ) ) { 328 System.err.println( "警告2:RTN配列=\t" + wormMsg ); 329 } 330 // 「@return String 解説」 の場合は警告(後ろにスペースか、タブがある場合) 331 if( debugLevel >= 3 && (data.indexOf( typNm + " " ) >= 0 || data.indexOf( typNm + "\t" ) >= 0 ) ) { 332 System.err.println( "警告3:RTNタイプ=\t" + wormMsg ); 333 } 334 // 「@return xxxx 解説」 の場合で、最初のスペースまでが、すべて英数字のみの場合は警告 335 final int adrs1 = data.indexOf( ' ' ); 336 if( debugLevel >= 3 && adrs1 > 0 ) { 337 boolean flag = true; 338 for( int j=0; j<adrs1; j++ ) { 339 final char ch = data.charAt( j ); 340 if( ( ch < '0' || ch > '9' ) && ( ch < 'a' || ch > 'z' ) && ( ch < 'A' || ch > 'Z' ) && ch != '[' && ch != ']' ) { 341 flag = false; // 英数字でない記号が現れた場合 342 break; 343 } 344 } 345 if( flag ) { // すべてが英数字の場合は、 346 System.err.println( "警告3:RTN値=\t" + wormMsg ); 347 } 348 } 349 } 350 else { // Tag上には、@return 記述が存在しない。 351 // 5.5.4.1 (2012/07/06) ソースチェック用(@return と引数の個数が異なる場合) 352 if( debugLevel >= 1 && !"void".equals( typNm ) ) { 353 System.err.println( "警告1:RTNコメントなし=\t" + wormMsg ); 354 } 355 } 356 357 // オーバーライドチェック:アノテーションの記述漏れ 358 // その逆は、コンパイラが警告してくれる。 359 final MethodDoc mdoc= ((MethodDoc)menber).overriddenMethod(); 360 if( debugLevel >= 3 && mdoc != null ) { 361 final AnnotationDesc[] annotations = menber.annotations(); 362 // 本来は、Override の有無を調べるべきだが、Deprecated と SuppressWarnings の付いている 363 // 旧のメソッドに、いちいちOverrideを付けないので、何もなければと条件を緩めます。 364 if( annotations.length == 0 ) { 365 System.err.println( "警告3:@Overrideなし=\t" + wormMsg ); 366 } 367 368 // 6.1.2.0 (2015/01/24) 移動 369 // 5.6.6.1 (2013/07/12) Deprecated アノテーション のチェック 370 for( int i=0; i<annotations.length; i++ ) { 371 final AnnotationTypeDoc annDoc = annotations[i].annotationType(); 372 if( "Deprecated".equalsIgnoreCase( annDoc.name() ) ) { 373 final String text = menber.commentText(); 374 if( text != null && text.indexOf( "【廃止】" ) < 0 ) { 375 System.err.println( "警告3:【廃止】=\t" + posi + "\t" + menber.name() ); 376 } 377 } 378 } 379 } 380 } 381 382 final Parameter[] prm = menber.parameters(); 383 384 // 5.1.9.0 (2010/08/01) ソースチェック用(@param と引数の個数が異なる場合) 385 // 6.4.4.0 (2016/03/11) 型パラメータの @param を除外する。 386 final Tag[] docParam = menber.tags(DOC_PARAM); // 5.1.9.0 (2010/08/01) チェック用 387 388 // 6.4.4.0 (2016/03/11) 型パラメータを含むjavadocの方が、メソッドのパラメータより多い可能性が高い 389 390 int prmId = 0; // JavaDoc側のカウンタ。型パラメータがあると、ずれてくる。 391 int dcCnt = 0; // JavaDoc側のparam個数 392 for( int k=0; k<docParam.length; k++ ) { // k は、javadocのパラメータ個数 393 394 // 5.1.9.0 (2010/08/01) ソースチェック用(@param と引数の個数が異なる場合) 395 if( prm.length > prmId ) { 396 final String typNm = prm[prmId].type().typeName(); 397 final String prmNm = prm[prmId].name(); 398 399 final String data1 = docParam[k].text().trim(); // 5.5.4.1 (2012/07/06) trim でスペース等の削除 400 // 型パラメータの場合は、パスする必要がある。continue する。 401 402 final String[] txSplit = data1.split( "[\\s]+" ); // 空白文字で分解 403 if( txSplit[0].length() == 3 && txSplit[0].charAt(0) == '<' && txSplit[0].charAt(2) == '>' ) { continue; } // 6.4.4.0 (2016/03/11) 404 dcCnt++ ; // javadocパラメータを処理した個数 405 406 final String data2 = data1.replaceAll( prmNm,"" ).trim(); 407 final String data3 = data2.replaceAll( typNm,"" ).replaceAll( "\\[\\]","" ).trim(); 408 final String wormMsg = posi + "\t" + data1 ; 409 410 // 「@param aaa 解説」形式で、aaa(引数名)がない場合 411 if( debugLevel >= 1 && data1.indexOf( prmNm ) < 0 ) { 412 System.err.println( "警告1:PRM引数名=\t" + wormMsg ); 413 } 414 // 引数の文字列の長さが、1文字の場合 415 if( debugLevel >= 2 && prmNm.length() == 1 ) { 416 System.err.println( "警告2:PRM短い=\t" + wormMsg ); 417 } 418 // 「@param aaa 解説」形式で、解説に日本語がない、または、解説がなければ、警告 419 if( debugLevel >= 2 && ( PTN.matcher( data2 ).matches() || data3.isEmpty() ) ) { 420 System.err.println( "警告2:PRM未解説=\t" + wormMsg ); 421 } 422 // 「@param aaa String[]」など、配列や、<String>などが含まれる場合は警告 423 if( debugLevel >= 2 && ( data1.indexOf( "[]" ) >= 0 || data1.indexOf( '<' ) >= 0 ) ) { 424 System.err.println( "警告2:PRM配列=\t" + wormMsg ); 425 } 426 // 「@param aaa 解説」形式で、String が有って、その後ろにスペースか、タブがあれば警告 427 // data2 を使うのは、パラメータ名(xxxMap)にタイプ名(Map)が含まれているケースの対応 428 // 6.0.4.0 (2014/11/28) 警告3:PRMタイプは、警告にしない。 429 // if( debugLevel >= 3 && (data2.indexOf( typNm + " " ) >= 0 || data2.indexOf( typNm + "\t" ) >= 0 ) ) { 430 // System.err.println( "警告3:PRMタイプ" + wormMsg ); 431 // } 432 // taglibライブラリで、「@param aaa ラベル [コード]」形式で、ラベルと[コード] の間に空白文字が無ければ警告 433 // 6.1.1.0 (2015/01/17) 434 if( debugLevel >= 3 && wormMsg.contains( "taglib" ) ) { // 6.3.9.0 (2015/11/06) String.indexOf の結果が正かどうか確かめている 435 if( data2.indexOf( '[' ) > 0 && data2.indexOf( " [" ) < 0 && !typNm.startsWith( "boolean" ) ) { 436 System.err.println( "警告3:ラベル [コード]=\t" + wormMsg ); 437 } 438 if( data2.indexOf( '[' ) < 0 && !prmNm.startsWith( "user" ) && !prmNm.startsWith( "usemap" ) && !prmNm.startsWith( "ismap" ) && 439 // ( prmNm.startsWith( "is" ) || prmNm.startsWith( "use" ) || prmNm.startsWith( "flag" ) ) ) { 440 typNm.startsWith( "boolean" ) ) { 441 System.err.println( "警告3:bool[]無=\t" + wormMsg ); 442 } 443 final String dim = prm[prmId].type().dimension(); // ディメンション 444 if( menber.isVarArgs() && dim.endsWith( "[]" ) && !data2.contains( "可変長引数" ) || 445 !dim.endsWith( "[]" ) && data2.contains( "可変長引数" ) ) { 446 System.err.println( "警告3:可変長引数=\t" + wormMsg ); 447 } 448 } 449 } 450 prmId++ ; 451 } 452 453 // 6.4.4.0 (2016/03/11) 型パラメータの @param を除外する。 454 // 型パラメータは、除外する。 455 if( debugLevel >= 1 && dcCnt != prm.length ) { 456 System.err.println( "警告1:PRM個数違い=\t" + posi ); 457 } 458 459 final Tag[] desc = menber.firstSentenceTags(); 460 final Tag[] cmnt = menber.inlineTags(); // 5.5.4.1 (2012/07/06) 461 462 // 5.1.9.0 (2010/08/01) ソースチェック用 463 if( menber instanceof MethodDoc // メソッドに限定 464 && !menber.isSynthetic() // コンパイラによって合成されていない 465 && !menber.isNative() // ネイティブメソッドでない 466 && debugLevel >= 2 ) { // debugLevel が 2 以上 467 468 final String wormMsg = posi + "\t" + menber.name() ; 469 470 final StringBuilder descBuf = new StringBuilder( BUFFER_MIDDLE ); 471 for( int i=0; i<desc.length; i++ ) { 472 descBuf.append( desc[i].text().trim() ); 473 } 474 475 if( cmnt.length == 0 || desc.length == 0 ) { // コメントや概要が無い 476 // さらに、親が Enum クラス以外 477 System.err.println( "警告2:コメントM=\t" + wormMsg ); 478 } 479 else if( desc.length > 0 ) { // 概要がある 480 final int adrs = descBuf.indexOf( "。" ); // 概要をチェック 481 if( adrs < 0 || adrs != descBuf.length()-1 ) { 482 System.err.println( "警告2:概要(。)=\t" + wormMsg ); 483 System.err.println( "「" + descBuf + "」" ); 484 } 485 } 486 487 // 6.1.2.0 (2015/01/24) 新規追加。カスタムタグの記述方法チェック 488 final Tag[] tags = menber.tags(OG_TAG_NAME); // カスタムタグに記述 489 if( debugLevel >= 3 && descBuf.indexOf( "【廃止】" ) < 0 && 490 !( tags.length > 0 ^ descBuf.indexOf( "【" ) < 0 ) ) { // !XOR 条件 491 System.err.println( "警告3:ogTag=\t" + wormMsg ); 492 } 493 494 // 6.1.2.0 (2015/01/24) writeContents から移す。 495 if( debugLevel >= 5 && docParam.length > 0 && tags.length > 0 ) { // カスタムタグの判定 496 final String data1 = docParam[0].text().trim(); // 5.5.4.1 (2012/07/06) trim でスペース等の削除 497 final String[] str = data1.split( "[\\s]+" ); // 空白記号で分割する。[0]:prm , [1]:説明 , [2]:補足説明、選択肢他 498 if( str.length > 1 && str[1].length() > 10 ) { // 10文字より長い説明文 499 System.err.println( "警告5:PRM抜出=\t" + wormMsg + " " + str[1] ); 500 } 501 } 502 } 503 } 504 505 /** 506 * VERSION staticフィールドと、@og.rev コメントの比較チェックを行います。 507 * エンジン内部では、serialVersionUID は、この、VERSION を元に作成しているため、 508 * その値もチェックします。 509 * 510 * ※ このチェックは、警告レベル4 のみ集約していますので、呼出元で、制限します。 511 * 512 * @og.rev 5.6.6.0 (2013/07/05) 新規作成 513 * @og.rev 5.7.1.1 (2013/12/13) VERSION の値を、Class.forName ではなく、FieldDoc から取得する。 514 * @og.rev 6.0.0.1 (2014/04/25) fullName 削除 515 * 516 * @param classDoc ClassDocオブジェクト 517 */ 518 private static void checkTag2( final ClassDoc classDoc ) { 519 final FieldDoc cnstVarFld = findFieldDoc( classDoc , "VERSION" ) ; // VERSION 文字列 例:5.6.6.0 (2013/07/05) 520 521 // VERSION 文字列 か、serialVersionUID のどちらかがあれば処理します。 522 // 6.1.2.0 (2015/01/24) 警告レベルの追加 523 if( cnstVarFld != null && debugLevel >= 4 ) { // 5.7.1.1 (2013/12/13) cnstVarFid のみ初めにチェック 524 String cnstVar = cnstVarFld.constantValueExpression() ; 525 // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..; 526 if( cnstVar == null ) { 527 cnstVar = "0.0.0.0 (0000/00/00)"; // 初期値 528 } 529 else { 530 if( cnstVar.length() > 1 && 531 cnstVar.charAt(0) == '"' && cnstVar.charAt( cnstVar.length()-1) == '"' ) { 532 cnstVar = cnstVar.substring( 1,cnstVar.length()-1 ); 533 } 534 } 535 536 String maxRev = cnstVar ; // 5.7.1.1 (2013/12/13) 初期値 537 final int lenVar = maxRev.length(); // 比較時に使用する長さ 538 boolean isChange = false; // max が入れ替わったら、true 539 540 // 本体、コンストラクタ、フィールド、メソッド内から、最大の @og.rev の値を取得します。 541 Doc[][] docs = new Doc[4][] ; 542 543 docs[0] = new Doc[] { classDoc } ; 544 docs[1] = classDoc.constructors( false ) ; 545 docs[2] = classDoc.fields( false ) ; 546 docs[3] = classDoc.methods( false ) ; 547 548 for( int i=0; i<docs.length; i++ ) { 549 for( int j=0; j<docs[i].length; j++ ) { 550 final Doc doc = docs[i][j]; 551 552 final Tag[] revTags = doc.tags(OG_REV); 553 for( int k=0 ; k<revTags.length; k++ ) { 554 String rev = revTags[k].text(); 555 556 if( rev.length() < lenVar ) { 557 System.err.println( "警告4:og.revが短い=\t" + rev + "\t" + doc.position() ); 558 continue; 559 } 560 561 rev = rev.substring( 0,lenVar ); 562 563 if( maxRev.compareTo( rev ) < 0 ) { // revTags の og.rev が大きい場合 564 maxRev = rev ; 565 // posi = doc.position(); // 最後に入れ替わった位置 = 最大のrevの位置 566 isChange = true; 567 } 568 } 569 } 570 } 571 572 // VERSION 文字列 の定義があり、かつ、max の入れ替えが発生した場合のみ、警告4:VERSIONが古い 573 if( isChange ) { // 5.7.1.1 (2013/12/13) 入れ替えが発生した場合 574 System.err.println( "警告4:VERSIONが古い=\t" + cnstVar + " ⇒ " + maxRev + "\t" + cnstVarFld.position() ); 575 } 576 577 // serialVersionUID の定義がある。 578 final FieldDoc seriUIDFld = findFieldDoc( classDoc , "serialVersionUID" ) ; // serialVersionUID 例:566020130705L 579 if( seriUIDFld != null ) { // 5.7.1.1 (2013/12/13) 580 final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE ); 581 // maxRev は、最大の Revか、初期のVERSION文字列 例:5.6.6.0 (2013/07/05) 582 for( int i=0; i<maxRev.length(); i++ ) { // 583 final char ch = maxRev.charAt( i ); 584 if( ch >= '0' && ch <= '9' ) { buf.append( ch ); } // 数字だけ取り出す。 例:566020130705 585 } 586 buf.append( 'L' ); // 強制的に、L を追加する。 587 final String maxSeriUID = buf.toString() ; 588 589 // 5.7.1.1 (2013/12/13) 値の取出し。Long型を表す "L" も含まれている。 590 final String seriUID = seriUIDFld.constantValueExpression() ; 591 if( !maxSeriUID.equals( seriUID ) ) { // 一致しない 592 System.err.println( "警告4:serialVersionUIDが古い=\t" + seriUID + " ⇒ " + maxSeriUID + "\t" + seriUIDFld.position() ); 593 } 594 } 595 } 596 } 597 598 /** 599 * PMDで、チェックしている処理のうち、Docletでフォローできる分をチェックします。 600 * 601 * ※ このチェックは、警告レベル5 のみ集約していますので、呼出元で、制限します。 602 * 603 * @og.rev 6.4.3.0 (2016/02/05) PMDチェックのDocletでのフォロー。 604 * @og.rev 6.8.0.1 (2017/06/30) unmodifiable という文字列もチェック対象外にします。 605 * 606 * @param classDoc ClassDocオブジェクト 607 */ 608 private static void checkPMD( final ClassDoc classDoc ) { 609 610 // 警告5:private static final 611 // PMD にはないが、インナークラスで、static で、enum でないのに、public や、final でない場合、警告します。 612// if( classDoc.isStatic() && !( classDoc.isEnum() || classDoc.isEnumConstant() ) && !classDoc.isFinal() ) { 613 // 7.3.0.0 (2021/01/06) Abstract でないという条件も追加 614 if( classDoc.isStatic() && !( classDoc.isEnum() || classDoc.isEnumConstant() ) && !classDoc.isFinal() && !classDoc.isAbstract() ) { 615 final String text = classDoc.commentText(); 616 // ごくまれに、isEnum() も isEnumConstant() も false で、superClass().name() が、"Enum" の場合がある。 617 // private static class で、継承させたい場合もあるため、final を付けない場合は、コメントに、『継承される』と記述しておく 618 if( ( text == null || !text.contains( "継承される" ) ) && classDoc.superclass() != null && !"Enum".equalsIgnoreCase( classDoc.superclass().name() ) ) { 619 final String wormMsg = classDoc.modifiers() + " " + classDoc.name() + "\t" + classDoc.position(); 620 System.err.println( "警告5:private static final=\t" + wormMsg ); 621 } 622 } 623 624 // 警告5:ConcurrentHashMap チェック(マルチスレッドでの安全性確保) 625 // 内部クラスは、都度作成するようにするので、対象外。 626 // final String clsmod = classDoc.modifiers(); 627 // if( !"private static final".equals( clsmod ) ) { 628 if( !( classDoc.isPrivate() && classDoc.isStatic() && classDoc.isFinal() ) ) { 629 for( final FieldDoc fld : classDoc.fields( false ) ) { 630 final String text = fld.commentText(); 631 // コメントが無いか、Concurrent という文字が無く、synchronized もなく、unmodifiable という文字列もない場合は、対象フィールド 632 if( text == null || !text.contains( "Concurrent" ) && !text.contains( "synchronized" ) && !text.contains( "unmodifiable" ) ) { // 6.9.7.0 (2018/05/14) PMD 633 final String type = fld.type().simpleTypeName(); 634 // フィールド定義が、Map の場合、対象になる。(ConcurrentMap は、対象外になる) 635 if( "Map".equals( type ) ) { 636 final String wormMsg = fld.modifiers() + " " + type + " " + fld.name() + "\t" + fld.position(); 637 System.err.println( "警告5:ConcurrentHashMap=\t" + wormMsg ); 638 } 639 } 640 } 641 } 642 } 643 644 /** 645 * メンバークラス(コンストラクタ、メソッド)をXML化します。 646 * 647 * @og.rev 5.5.4.1 (2012/07/06) コメントは文字列でなく、Tag配列として処理させる。 648 * 649 * @param menber ExecutableMemberDocオブジェクト 650 * @param menberType メンバータイプ(コンストラクタ、メソッド) 651 * @param writer Tagを書き出すWriterオブジェクト 652 * @param extendFlag 継承状態 [0:オリジナル/1::org.opengion関連Extend/2:Java関連Extend] 653 */ 654 private static void menberTag( final ExecutableMemberDoc menber, 655 final String menberType, 656 final DocletTagWriter writer, 657 final int extendFlag ) { 658 659 final String modifiers ; 660 if( menber instanceof MethodDoc ) { 661 // メソッドの処理 662 final Type rtnType = ((MethodDoc)menber).returnType(); 663 final StringBuilder modifyBuf = new StringBuilder( BUFFER_MIDDLE ) 664 .append( menber.modifiers() ) 665 .append( ' ' ).append( rtnType.typeName() ); // 6.0.2.5 (2014/10/31) char を append する。 666 if( rtnType.dimension() != null ) { modifyBuf.append( rtnType.dimension() ); } 667 668 modifiers = modifyBuf.toString(); 669 } 670 else { 671 // コンストラクター処理 672 modifiers = menber.modifiers(); 673 } 674 675 final String menberName = menber.name(); 676 677 // 6.0.2.5 (2014/10/31) char を append する。 678 final StringBuilder sigBuf = new StringBuilder( BUFFER_MIDDLE ) 679 .append( menberName ).append( '(' ) ; 680 final Parameter[] prm = menber.parameters(); 681 682 for( int k=0; k<prm.length; k++ ) { 683 final Type ptyp = prm[k].type(); 684 final String prmNm =prm[k].name(); 685 686 sigBuf.append( ptyp.typeName() ).append( ptyp.dimension() ).append( ' ' ) 687 .append( prmNm ).append( ',' ); 688 } 689 690 if( prm.length > 0 ) { sigBuf.deleteCharAt( sigBuf.length()-1 ); } 691 sigBuf.append( ')' ); 692 final String signature = sigBuf.toString(); 693 694 final Tag[] desc = menber.firstSentenceTags(); 695 final Tag[] cmnt = menber.inlineTags(); // 5.5.4.1 (2012/07/06) 696 final Tag[] tags = menber.tags(); 697 final Tag[] revTags = menber.tags(OG_REV); 698 final String extend = String.valueOf( extendFlag ); 699 final String extClass = ( extendFlag == 0 ) ? "" : menber.containingClass().qualifiedName() ; 700 701 final String position = String.valueOf( menber.position().line() ); 702 703 writer.printTag( " <menber>" ); 704 writer.printTag( " <type>" ,menberType ,"</type>" ); 705 writer.printTag( " <name>" ,menberName ,"</name>" ); 706 writer.printTag( " <modifiers>" ,modifiers ,"</modifiers>" ); 707 writer.printTag( " <signature>" ,signature ,"</signature>" ); 708 writer.printTag( " <position>" ,position ,"</position>" ); 709 writer.printTag( " <extendClass>",extClass ,"</extendClass>" ); 710 writer.printTag( " <extendFlag>" ,extend ,"</extendFlag>" ); 711 writer.printTag( " <description>",desc ,"</description>" ); 712 writer.printTag( " <contents>" ,cmnt ,"</contents>" ); 713 writer.printTag( " <tagText>" ); 714 writer.printTagsInfo( tags ); 715 writer.printTag( " </tagText>" ); 716 writer.printTag( " <history>" ,revTags ,"</history>" ); 717 writer.printTag( " </menber>"); 718 } 719 720 /** 721 * 指定のキーの FieldDoc オブジェクトを、ClassDoc から見つけて返します。 722 * 723 * キー情報は、大文字、小文字の区別なく、最初に見つかった、フィールド名の 724 * FieldDoc オブジェクトを見つけます。 725 * 内部的には、ループを回して検索していますので、非効率です。 726 * 見つからない場合は、null を返します。 727 * 728 * @og.rev 5.7.1.1 (2013/12/13) 新規作成 729 * 730 * @param classDoc 検索元のClassDoc 731 * @param key 検索するキー 732 * @return FieldDocオブジェクト 733 */ 734 private static FieldDoc findFieldDoc( final ClassDoc classDoc ,final String key ) { 735 FieldDoc rtn = null; 736 737 final FieldDoc[] fld = classDoc.fields( false ) ; 738 739 for( int i=0; i<fld.length; i++ ) { 740 // if( key.equalsIgnoreCase( fld[i].name() ) ) { 741 if( key.equals( fld[i].name() ) ) { // 7.3.0.0 (2021/01/06) taglet2 で小文字の version を採用したので。 742 rtn = fld[i]; 743 break; 744 } 745 } 746 return rtn; 747 } 748 749 /** 750 * カスタムオプションを使用するドックレットの必須メソッド optionLength(String) です。 751 * 752 * ドックレットに認識させる各カスタムオプションに、 optionLength がその 753 * オプションを構成する要素 (トークン) の数を返さなければなりません。 754 * このカスタムオプションでは、 -tag オプションそのものと 755 * その値の 2 つの要素で構成されるので、作成するドックレットの 756 * optionLengthメソッドは、 -tag オプションに対して 2 を返さなくては 757 * なりません。また、認識できないオプションに対しては、0 を返します。 758 * 759 * @param option オプション文字列 760 * 761 * @return 要素(トークン) の数 762 */ 763 public static int optionLength( final String option ) { 764 if( "-version".equalsIgnoreCase(option) ) { 765 return 2; 766 } 767 else if( "-outfile".equalsIgnoreCase(option) ) { 768 return 2; 769 } 770 else if( "-debugLevel".equalsIgnoreCase(option) ) { 771 return 2; 772 } 773 return 0; 774 } 775}