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.メソッド名におかしな記号(&lt;など)が含まれている場合
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}