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