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 org.opengion.fukurou.util.StringUtil;
020
021import com.sun.javadoc.RootDoc;
022import com.sun.javadoc.ClassDoc;
023import com.sun.javadoc.MethodDoc;
024import com.sun.javadoc.Type;
025import com.sun.javadoc.Tag;
026
027import java.util.Map;
028import java.util.HashMap;
029import java.io.IOException;
030import java.util.stream.Stream;                                                                 // 6.4.3.4 (2016/03/11)
031import java.util.stream.Collectors;                                                             // 6.4.3.4 (2016/03/11)
032
033/**
034 * ソースコメントから、タグ情報を取り出す Doclet クラスです。
035 * この Doclet は、":org.opengion.hayabusa.taglib" のみ対象として処理します。
036 * og.formSample , og.tag , og.group タグを切り出します。
037 *
038 * @version  4.0
039 * @author   Kazuhiko Hasegawa
040 * @since    JDK5.0,
041 */
042@SuppressWarnings(value={"deprecation","removal"})                      // Ver7.0.0.0 , 7.2.2.0 (2020/03/27)
043public final class DocletTaglib {
044        // 6.4.3.1 (2016/02/12) インスタンス変数をローカル変数に変更。変数名も変えておきます。
045
046        private static final String OG_FOR_SMPL = "og.formSample";
047        private static final String OG_TAG_NAME = "og.tag";
048        private static final String OG_GROUP    = "og.group";
049
050        private static final String     DOC_PARAM       = "param";              // 6.1.1.0 (2015/01/17)
051
052        private static final String OG_TAG_CLASS = "org.opengion.hayabusa.taglib";
053        private static final String ENCODE = "UTF-8";
054
055        /**
056         * すべてが staticメソッドなので、コンストラクタを呼び出さなくしておきます。
057         *
058         */
059        private DocletTaglib() {}
060
061        /**
062         * Doclet のエントリポイントメソッドです。
063         *
064         * @og.rev 5.7.1.1 (2013/12/13) タグのインデントを止める。
065         *
066         * @param       root    エントリポイントのRootDocオブジェクト
067         *
068         * @return      正常実行時 true
069         */
070        public static boolean start( final RootDoc root ) {
071                final String version = DocletUtil.getOption( "-version" , root.options() );
072                final String file    = DocletUtil.getOption( "-outfile" , root.options() );
073
074                DocletTagWriter writer = null;
075                try {
076                        writer = new DocletTagWriter( file,ENCODE );
077
078                        // 5.7.1.1 (2013/12/13) タグのインデントを止める。
079                        writer.printTag( "<?xml version=\"1.0\" encoding=\"", ENCODE, "\" ?>" );
080                        writer.printTag( "<javadoc>" );
081                        writer.printTag( "  <version>",version,"</version>" );
082                        writer.printTag( "  <description></description>" );
083                        writeContents( root.classes(),writer );
084                        writer.printTag( "</javadoc>" );
085                }
086                catch( final IOException ex ) {
087                        LogWriter.log( ex );
088                }
089                finally {
090                        if( writer != null ) { writer.close(); }
091                }
092                return true;
093        }
094
095        /**
096         * ClassDoc 配列よりコンテンツを作成します。
097         *
098         * @og.rev 5.5.4.1 (2012/07/06) コメントは文字列でなく、Tag配列として処理させる。
099         * @og.rev 5.6.6.1 (2013/07/12) og.group を、tagGroup として独立させる。
100         * @og.rev 5.7.1.1 (2013/12/13) cmnt と tags の間に改行をセット
101         * @og.rev 5.7.1.1 (2013/12/13) タグのインデントを止める。
102         * @og.rev 6.1.1.0 (2015/01/17) タグリブの param 属性取得(0:パラメータ、1:ラベル、2:コメント、3:コード)
103         * @og.rev 6.2.5.0 (2015/06/05) 2:コメント を、comment として出力します。
104         * @og.rev 6.4.3.1 (2016/02/12) インスタンス変数をローカル変数に変更。変数名も変えておきます。
105         *
106         * @param classes       ClassDoc配列
107         * @param writer        DocletTagWriterオブジェクト
108         */
109        private static void writeContents( final ClassDoc[] classes,final DocletTagWriter writer ) {
110                final Map<String,String> mtdClsMap = new HashMap<>();                   //  6.4.3.1 (2016/02/12) 変数名も変えておきます。
111                for( int i=0; i<classes.length; i++ ) {
112                        ClassDoc classDoc      = classes[i] ;
113                        final String   classFullName = classDoc.qualifiedName() ;
114
115                        if( ! classDoc.isPublic() ||
116                                classFullName.indexOf( OG_TAG_CLASS ) < 0 ) { continue; }
117
118                        final Tag[]  desc = classDoc.firstSentenceTags();
119                        Tag[]  cmnt = classDoc.inlineTags();                                                                    // 5.5.4.1 (2012/07/06)
120                        final Tag[] smplTags    = classDoc.tags(OG_FOR_SMPL);
121                        final Tag[] grpTags     = classDoc.tags(OG_GROUP);
122
123                        // 5.7.1.1 (2013/12/13) タグのインデントを止める。
124                        writer.printTag( "<classDoc>" );
125                        writer.printTag( "  <tagClass>"         ,classFullName                          ,"</tagClass>" );
126                        // 5.6.6.1 (2013/07/12) og.group を、tagGroup として独立させる。
127                        writer.printTag( "  <tagGroup>"         ,makeGroupTag( grpTags )        ,"</tagGroup>" );
128                        writer.printTag( "  <description>"      ,desc                                           ,"</description>" );
129                        writer.printTag( "  <contents>"         ,cmnt                                           ,"</contents>" );
130                        writer.printTag( "  <formSample>"       ,smplTags                                       ,"</formSample>" );
131
132                        // 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。変数名も変えておきます。
133                        mtdClsMap.clear();
134                        String className = classDoc.name();
135                        while(  ! "BodyTagSupport".equals( className ) &&
136                                        ! "TagSupport".equals( className ) ) {
137                                String extendFlag = "false";
138                                if( "HTMLTagSupport".equals( className ) ) {
139                                        extendFlag = "true" ;
140                                }
141                                final MethodDoc[] methods = classDoc.methods();
142                                for( int j=0; j<methods.length; j++ ) {
143                                        if( ! methods[j].isPublic() ) { continue; }
144                                        final Tag[] tags = methods[j].tags(OG_TAG_NAME);
145                                        if( tags.length > 0 ) {
146                                                final String methodName = DocletUtil.removeSetter( methods[j].name() );
147                                                // 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。変数名も変えておきます。
148                                                if( mtdClsMap.containsKey( methodName ) ) { continue; }
149                                                mtdClsMap.put( methodName,className );
150                                                final Tag[] ftag = methods[j].firstSentenceTags();
151                                                cmnt = methods[j].inlineTags();                                                                 // 5.5.4.1 (2012/07/06)
152
153                                                // 6.1.1.0 (2015/01/17) タグリブの param 属性取得(0:パラメータ、1:ラベル、2:コメント、3:コード)
154                                                final String[] keylblCd = methodLabelCode( methods[j] );
155
156                                                // 5.7.1.1 (2013/12/13) タグのインデントを止める。
157                                                writer.printTag( "  <method>" );
158                                                writer.printTag( "    <name>"           ,methodName     ,"</name>" );
159                                                writer.printTag( "    <label>"          ,keylblCd[1],"</label>" );              // 6.1.1.0 (2015/01/17)
160                                                writer.printTag( "    <comment>"        ,keylblCd[2],"</comment>" );    // 6.2.5.0 (2015/06/05)
161                                                writer.printTag( "    <code>"           ,keylblCd[3],"</code>" );               // 6.1.1.0 (2015/01/17)
162                                                writer.printTag( "    <htmlExtend>"     ,extendFlag     ,"</htmlExtend>" );
163                                                writer.printTag( "    <description>",ftag               ,"</description>" );
164                                                // 5.7.1.1 (2013/12/13) cmnt と tags の間に改行をセット
165                                                writer.printTag( "    <contents>"       ,cmnt           ,"" );
166                                                writer.printTag( ""                                     ,tags           ,"</contents>" );
167                                                writer.printTag( "  </method>");
168                                        }
169                                }
170                                final Type type = classDoc.superclassType();
171                                if( type == null ) { break; }
172                                classDoc  = type.asClassDoc() ;
173                                className = classDoc.name();
174                        }
175                        writer.printTag( "  </classDoc>" );
176                }
177        }
178
179        /**
180         * MethodDocを受け取り、0:パラメータ、1:ラベル、2:コメント、3:コードを文字列配列に入れて返します。
181         *
182         * これは、タグリブのラベルリソース登録時に使用する情報です。
183         * タグリブのローカルルールとして、0:パラメータ 1:ラベル 2:ラベル以降の解説 3:コード
184         * という記述を行う事を前提にしています。
185         *
186         * 0:パラメータは、引数です。メソッド名ではありませんので、ご注意ください。
187         * 1:ラベルは、パラメータの次の空白文字から、次の空白文字、または、"[" までの文字です。
188         * 2:コメントは、ラベル以降の文字列で、コードの記述部分も含みます。
189         * 3:コード は、特別な処理を行います。"[" と "]" 内に記述された内容を取り出します。
190         *
191         * @og.rev 6.1.1.0 (2015/01/17) タグリブの param 属性取得(0:パラメータ、1:ラベル、2:コメント、3:コード)
192         * @og.rev 6.3.6.0 (2015/08/16) System.arraycopy が使える箇所は、置き換えます。
193         *
194         * @param       method メソッドドック
195         *
196         * @return      0:パラメータ、1:ラベル、2:コメント、3:コードを文字列配列に詰めた値(長さ4の配列)
197         * @og.rtnNotNull
198         */
199        private static String[] methodLabelCode( final MethodDoc method ) {
200                final String[] arys = new String[] { "","","","" } ;    // 後で内容を更新する。
201
202                final Tag[] docParam = method.tags(DOC_PARAM);
203                if( docParam.length == 1 ) {                                                    // Taglibのsetter は、paramは一つだけのハズ
204                        final String data = docParam[0].text().trim();          // @param の説明文
205
206                        // 最大3つ と指定しているが、0:パラメータと1:ラベルの2つしか保証されていない。
207                        final String[] temp = data.split( "[\\s]+",3 );         // 空白文字で3つに分解する。
208                        // 6.3.6.0 (2015/08/16) System.arraycopy が使える箇所は、置き換えます。
209                        System.arraycopy( temp,0,arys,0,temp.length );          // 6.3.6.0 (2015/08/16)
210
211                        // 3:コード があれば、2:コメントから取り出す。
212                        final int st1 = arys[2].indexOf( '[' );
213                        if( st1 >= 0 ) {
214                                final int st2 = arys[2].indexOf( ']',st1 );
215                                if( st2 > 0 ) {
216                                        // 前後の [] は、取り除き、'/' があれば、' ' に置換する。(コード文字列化)
217                                        arys[3] = arys[2].substring( st1+1,st2 ).replace( '/' , ' ' );
218                                }
219                        }
220                }
221                return arys ;
222        }
223
224        /**
225         * タグ配列を受け取り、タグ出力します。
226         * 複数のタグを出力する場合に、CSV形式で連結します。
227         *
228         * @og.rev 5.5.4.1 (2012/07/06) DocletUtil.htmlFilter → StringUtil.htmlFilter に変更
229         * @og.rev 5.6.6.1 (2013/07/12) og.group の表示方法を変更する。
230         * @og.rev 6.4.3.4 (2016/03/11) CSV形式の文字連結を、stream 経由で行います。
231         *
232         * @param       tag タグ配列(可変長引数)
233         *
234         * @return      タグ出力文字列
235         * @og.rtnNotNull
236         */
237        private static String makeGroupTag( final Tag... tag ) {
238
239                return Stream.of( tag )
240                                        .map( tg -> StringUtil.htmlFilter( tg.text() ) )
241                                        .collect( Collectors.joining( "】,【" , "【" , "】" ) );    // 連結文字 , 最初 , 最後
242        }
243
244        /**
245         * カスタムオプションを使用するドックレットの必須メソッド optionLength(String) です。
246         *
247         * ドックレットに認識させる各カスタムオプションに、 optionLength がその
248         * オプションを構成する要素 (トークン) の数を返さなければなりません。
249         * このカスタムオプションでは、 -tag オプションそのものと
250         * その値の 2 つの要素で構成されるので、作成するドックレットの
251         * optionLengthメソッドは、 -tag オプションに対して 2 を返さなくては
252         * なりません。また、認識できないオプションに対しては、0 を返します。
253         *
254         * @param option カスタムオプションのキーワード
255         *
256         * @return 要素 (トークン) の数
257         */
258        public static int optionLength( final String option ) {
259                if( "-version".equalsIgnoreCase(option) ) {
260                        return 2;
261                }
262                else if( "-outfile".equalsIgnoreCase(option) ) {
263                        return 2;
264                }
265                return 0;
266        }
267}