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