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.Map;
022import java.util.TreeMap;
023import java.util.Set;                                                   // 6.2.0.0 (2015/02/27)
024import java.util.TreeSet;                                               // 6.2.0.0 (2015/02/27)
025import java.io.IOException;
026
027import com.sun.javadoc.RootDoc;
028import com.sun.javadoc.ClassDoc;
029import com.sun.javadoc.ExecutableMemberDoc;
030import com.sun.javadoc.Type;
031import com.sun.javadoc.Parameter;
032import com.sun.javadoc.Tag;
033
034/**
035 * ソースコメントから、タグ情報を取り出す Doclet クラスです。
036 * パラメータの version に一致する og.rev タグの 書かれたメソッドを、
037 * ピックアップします。
038 * og.rev タグ で、まとめて表示します。
039 *
040 * ルールとしては、X.X.X.X だけが引数で渡されますので、 X.X.X.X (YYYY/MM/DD) が
041 * コメントとして記載されているとして、処理します。
042 * そして、それ以降の記述の、「。」までを、キーワードとして、まとめます。
043 * キーワード以下は、それぞれのコメントとして、各メソッドの直前に表示します。
044 * このクラスは、RELEASE-NOTES.txt を作成する場合に、変更箇所をピックアップするのに使用します。
045 *
046 * @version  6.0.2.3 (2014/10/10)
047 * @author   Kazuhiko Hasegawa
048 * @since    JDK5.0,
049 */
050@SuppressWarnings(value={"deprecation","removal"})                      // Ver7.0.0.0 , 7.2.2.0 (2020/03/27)
051public final class DocletVerCheck {
052        private static final String  SELECT_PACKAGE     = "org.opengion" ;
053        private static final String  ENCODE                     = "UTF-8";
054
055        private static final String     OG_REV                  = "og.rev";
056
057        // 6.2.0.0 (2015/02/27) List → Set に変更して、TreeSet を使います。
058        // 6.4.3.1 (2016/02/12) インスタンス変数をローカル変数に変更。
059
060        private static  int     debugLevel      ;                                                                       // 6.2.0.0 (2015/02/27) 0:なし 1:ソース行出力
061
062        private static  int     omitPackage     = SELECT_PACKAGE.length()+1     ;               // 6.8.2.2 (2017/11/02) 汎用的に対応させます。
063
064        /**
065         * すべてが staticメソッドなので、コンストラクタを呼び出さなくしておきます。
066         *
067         */
068        private DocletVerCheck() {}
069
070        /**
071         * Doclet のエントリポイントメソッドです。
072         *
073         * @og.rev 6.0.2.3 (2014/10/10) 新規作成
074         * @og.rev 6.8.2.2 (2017/11/02) パッケージ名の簡略化を、汎用的に対応させます。
075         *
076         * @param       root    エントリポイントのRootDocオブジェクト
077         *
078         * @return 正常実行時 true
079         */
080        public static boolean start( final RootDoc root ) {
081                final String version    = DocletUtil.getOption( "-version" , root.options() );
082                final String file               = DocletUtil.getOption( "-outfile" , root.options() );
083                final String dbgLvl             = DocletUtil.getOption( "-debugLevel" , root.options() );               // 6.2.0.0 (2015/02/27)
084                if( dbgLvl != null ) { debugLevel = Integer.parseInt( dbgLvl ); }
085                final String omitPkg    = DocletUtil.getOption( "-omitPackage" , root.options() );              // 6.8.2.2 (2017/11/02)
086                if( omitPkg != null && !omitPkg.isEmpty() ) { omitPackage = omitPkg.length()+1; }               // 6.8.2.2 (2017/11/02)
087
088                DocletTagWriter writer = null;
089                try {
090                        writer = new DocletTagWriter( file,ENCODE );
091
092                        writeContents( root.classes(),writer,version );
093                }
094                catch( final IOException ex ) {
095                        LogWriter.log( ex );
096                }
097                finally {
098                        if( writer != null ) { writer.close(); }
099                }
100                return true;
101        }
102
103        /**
104         * TreeSet用の Comparator 実装クラス
105         *
106         * これは、String[] 配列を TreeSet でソートする為のComparatorです。
107         * ソートは、同一Set内で、配列の3番目の要素(ポジション)です。
108         * つまり、クラス名、メソッド名、行番号順になります。
109         *
110         * @og.rev 6.2.0.0 (2015/02/27) List → Set に変更して、TreeSet を使います。
111         * @og.rev 6.3.9.0 (2015/11/06) Comparator を実装する場合、Serializable も実装するべきかどうか検討するべきです(findbugs)。
112         *
113         */
114        private static final class ObjComp implements java.util.Comparator<String[]> , java.io.Serializable {
115                private static final long serialVersionUID = 639020151030L ;
116
117                /**
118                 * 比較メソッド
119                 *
120                 * これは、String[] 配列を TreeSet でソートする為のComparatorです。
121                 * ソートは、同一Set内で、配列の3番目の要素(ポジション)です。
122                 * つまり、クラス名、メソッド名、行番号順になります。
123                 *
124                 * @og.rev 6.2.0.0 (2015/02/27) List → Set に変更して、TreeSet を使います。
125                 *
126                 * @param o1    文字列配列(比較対象の最初のオブジェクト)
127                 * @param o2    文字列配列(比較対象の2番目のオブジェクト)
128                 * @return              最初の引数が2番目の引数より小さい場合は負の整数、両方が等しい場合は0、最初の引数が2番目の引数より大きい場合は正の整数
129                 */
130                public int compare( final String[] o1, final String[] o2 ) {
131                        return o1[2].compareTo( o2[2] );
132                }
133        }
134
135        /**
136         * ClassDoc 配列よりコンテンツを作成します。
137         *
138         * @og.rev 6.0.2.3 (2014/10/10) 新規作成
139         * @og.rev 6.2.0.0 (2015/02/27) List → Set に変更して、TreeSet を使います。
140         * @og.rev 6.3.9.0 (2015/11/06) Map.keySet() ではなく、Map.entrySet() を使う様に変更。
141         * @og.rev 6.4.3.1 (2016/02/12) インスタンス変数をローカル変数に変更。
142         * @og.rev 6.8.2.2 (2017/11/02) パッケージ名の簡略化を、汎用的に対応させます。
143         *
144         * @param classes       ClassDoc配列
145         * @param writer        Tagを書き出すWriterオブジェクト
146         * @param version       チェックする対象のバージョン
147         */
148        private static void writeContents( final ClassDoc[] classes,final DocletTagWriter writer,final String version ) {
149
150                // 6.4.3.1 (2016/02/12) インスタンス変数をローカル変数に変更。
151                final Map<String,Set<String[]>> VER_INFO_MAP = new TreeMap<>();                 // 6.4.1.1 (2016/01/16) verInfo → VER_INFO_MAP refactoring
152
153                ExecutableMemberDoc[][] docs = new ExecutableMemberDoc[2][];            // 6.1.0.0 (2014/12/26) refactoring
154                for( int x=0; x<classes.length; x++ ) {
155                        final ClassDoc classDoc = classes[x] ;
156                        String fullName         = classDoc.qualifiedName();
157
158                        fullName = fullName.substring( omitPackage );                                   // パッケージ名を簡略化しておきます。   6.8.2.2 (2017/11/02) 汎用的に対応させます。
159
160                        // コンストラクタ、メソッドから、対象の @og.rev の値を取得します。
161                        docs[0] = classDoc.constructors( false ) ;
162                        docs[1] = classDoc.methods( false ) ;
163
164                        for( int i=0; i<docs.length; i++ ) {
165                                for( int j=0; j<docs[i].length; j++ ) {
166                                        final ExecutableMemberDoc doc = docs[i][j];
167                                        final Tag[] revTags = doc.tags(OG_REV);
168                                        for( int k=0 ; k<revTags.length; k++ ) {
169                                                final String rev = revTags[k].text();
170                                                if( rev.startsWith( version ) ) {
171                                                        // 6.2.0.0 (2015/02/27) List → Set に変更して、TreeSet を使います。
172                                                        Set<String[]> set = VER_INFO_MAP.get( rev );            // rev コメントでまとめます。
173                                                        if( set == null ) { set = new TreeSet<>( new ObjComp() ); }
174                                                        set.add( menberCheck( doc,fullName ) );
175                                                        VER_INFO_MAP.put( rev,set );
176
177                                                        break;
178                                                }
179                                        }
180                                }
181                        }
182                }
183
184                String revYMD = null;   // 初めてのRev.日付をセットする。
185                String title = "";                      // キーブレイクのマーカー
186                // 6.3.9.0 (2015/11/06) keySet() ではなく、entrySet() を使う様に変更
187                for( final Map.Entry<String,Set<String[]>> entry : VER_INFO_MAP.entrySet() ) {          // 6.3.9.0 (2015/11/06)
188                        final String key = entry.getKey();                                                                                      // 6.3.9.0 (2015/11/06)
189                        if( revYMD == null ) {
190                                final int idx = key.indexOf( ')' ) ;
191                                revYMD = key.substring( 0,idx+1 );
192                                writer.printTag( revYMD );                                                                      // 一番先頭の Rev-日付出力
193                        }
194
195                        // バージョン、日付以降のメッセージを取得。
196                        final String msg = key.substring( revYMD.length() ).trim();     // 日付以降のコメント
197                        final int idx = msg.indexOf( '。' );                                                     // '。' の区切り文字の前半部分が、タイトル。
198
199                        if( idx >= 0 ) {                // 分割する場合
200                                final String newTitle = msg.substring( 0,idx ).trim() ;
201                                if( !title.equals( newTitle ) ) {
202                                        writer.printTag( "\n\t[" , newTitle , "]" );
203                                        title = newTitle ;
204                                }
205                                if( idx != msg.length()-1 ) {                           // 分割する場合、最後の「。」は、分割対象外
206                                        writer.printTag( "\t" , msg.substring( idx+1 ).trim() );
207                                }
208                        }
209                        else {
210                                writer.printTag( "\n\t[" , msg , "]" );
211                                title = msg ;
212                        }
213
214                        for( final String[] vals : entry.getValue() ) {                                 // 6.3.9.0 (2015/11/06) keySet() ではなく、entrySet() を使う様に変更
215                                if( debugLevel > 0 ) {                                                                          // 6.2.0.0 (2015/02/27)
216                                        writer.printTag( vals[2] );
217                                }
218                                writer.printTag( "\t\t" , vals[0] , "#" , vals[1] );
219                        }
220                }
221        }
222
223        /**
224         * メンバークラス(コンストラクタ、メソッド)から、メソッド名、シグネチャを取得します。
225         *
226         * @og.rev 6.0.2.3 (2014/10/10) 新規作成
227         *
228         * @param       menber          ExecutableMemberDocオブジェクト
229         * @param       fullName        パッケージを含めたクラスのフル名称
230         * @return      配列にセットされた情報( fullName,signature,position )
231         * @og.rtnNotNull
232         */
233        private static String[] menberCheck( final ExecutableMemberDoc menber,final String fullName ) {
234
235                final String menberName = menber.name();
236
237                // 6.0.2.5 (2014/10/31) char を append する。
238                final StringBuilder sigBuf = new StringBuilder( BUFFER_MIDDLE );
239                sigBuf.append( menberName ).append( '(' ) ;
240                final Parameter[] prm = menber.parameters();
241
242                for( int k=0; k<prm.length; k++ ) {
243                        final Type ptyp = prm[k].type();
244                        final String prmNm =prm[k].name();
245
246                        sigBuf.append( ptyp.typeName() ).append( ptyp.dimension() ).append( ' ' )
247                                        .append( prmNm ).append( ',' );
248                }
249
250                if( prm.length > 0 ) { sigBuf.deleteCharAt( sigBuf.length()-1 ); }
251                sigBuf.append( ')' );
252                final String signature = sigBuf.toString();
253
254                final String position = menber.position().toString();
255                System.out.println( position );         // デバッグ情報
256
257                return new String[] { fullName , signature , position };
258        }
259
260        /**
261         * カスタムオプションを使用するドックレットの必須メソッド optionLength(String) です。
262         *
263         * ドックレットに認識させる各カスタムオプションに、 optionLength がその
264         * オプションを構成する要素 (トークン) の数を返さなければなりません。
265         * このカスタムオプションでは、 -tag オプションそのものと
266         * その値の 2 つの要素で構成されるので、作成するドックレットの
267         * optionLengthメソッドは、 -tag オプションに対して 2 を返さなくては
268         * なりません。また、認識できないオプションに対しては、0 を返します。
269         *
270         * @og.rev 6.8.2.2 (2017/11/02) パッケージ名の簡略化を、汎用的に対応させます。
271         *
272         * @param       option  オプション文字列
273         *
274         * @return      要素(トークン) の数
275         */
276        public static int optionLength( final String option ) {
277                if( "-version".equalsIgnoreCase(option) ) {
278                        return 2;
279                }
280                else if( "-outfile".equalsIgnoreCase(option) ) {
281                        return 2;
282                }
283                // 6.2.0.0 (2015/02/27) ソース行番号出力
284                else if( "-debugLevel".equalsIgnoreCase(option) ) {
285                        return 2;
286                }
287                // 6.8.2.2 (2017/11/02) 汎用的に対応させます。
288                else if( "-omitPackage".equalsIgnoreCase(option) ) {
289                        return 2;
290                }
291                return 0;
292        }
293}