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;
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;
026import java.util.Map;
027import java.util.HashMap;
028import java.io.IOException;
029
030/**
031 * ソースコメントから、タグ情報を取り出す Doclet クラスです。
032 * この Doclet は、":org.opengion.hayabusa.taglib" のみ対象として処理します。
033 * og.formSample , og.tag , og.group タグを切り出します。
034 *
035 * @version  4.0
036 * @author   Kazuhiko Hasegawa
037 * @since    JDK5.0,
038 */
039public final class DocletTaglib {
040        private static Map<String,String> map = new HashMap<String,String>();
041
042        private static final String OG_FOR_SMPL = "og.formSample";
043        private static final String OG_TAG_NAME = "og.tag";
044        private static final String OG_GROUP    = "og.group";
045
046        private static final String OG_TAG_CLASS = "org.opengion.hayabusa.taglib";
047        private static final String ENCODE = "UTF-8";
048
049        /**
050         * すべてが staticメソッドなので、コンストラクタを呼び出さなくしておきます。
051         *
052         */
053        private DocletTaglib() {}
054
055        /**
056         * Doclet のエントリポイントメソッドです。
057         *
058         * @og.rev 5.7.1.1 (2013/12/13) タグのインデントを止める。
059         *
060         * @param       root    エントリポイントのRootDocオブジェクト
061         *
062         * @return      正常実行時 true
063         */
064        public static boolean start( final RootDoc root ) {
065                String version = DocletUtil.getOption( "-version" , root.options() );
066                String file    = DocletUtil.getOption( "-outfile" , root.options() );
067
068                DocletTagWriter writer = null;
069                try {
070                        writer = new DocletTagWriter( file,ENCODE );
071
072                        // 5.7.1.1 (2013/12/13) タグのインデントを止める。
073                        writer.printTag( "<?xml version=\"1.0\" encoding=\"", ENCODE, "\" ?>" );
074                        writer.printTag( "<javadoc>" );
075                        writer.printTag(   "<version>",version,"</version>" );
076                        writer.printTag(   "<description></description>" );
077                        writeContents( root.classes(),writer );
078                        writer.printTag( "</javadoc>" );
079                }
080                catch( IOException ex ) {
081                        LogWriter.log( ex );
082                }
083                finally {
084                        if( writer != null ) { writer.close(); }
085                }
086                return true;
087        }
088
089        /**
090         * ClassDoc 配列よりコンテンツを作成します。
091         *
092         * @og.rev 5.5.4.1 (2012/07/06) コメントは文字列でなく、Tag配列として処理させる。
093         * @og.rev 5.6.6.1 (2013/07/12) og.group を、tagGroup として独立させる。
094         * @og.rev 5.7.1.1 (2013/12/13) cmnt と tags の間に改行をセット
095         * @og.rev 5.7.1.1 (2013/12/13) タグのインデントを止める。
096         *
097         * @param classes       ClassDoc配列
098         * @param writer        DocletTagWriterオブジェクト
099         */
100        private static void writeContents( final ClassDoc[] classes,final DocletTagWriter writer ) {
101                for(int i=0; i< classes.length; i++) {
102                        ClassDoc classDoc      = classes[i] ;
103                        String   classFullName = classDoc.qualifiedName() ;
104
105                        if( ! classDoc.isPublic() ||
106                                classFullName.indexOf( OG_TAG_CLASS ) < 0 ) { continue; }
107
108                        Tag[]  desc = classDoc.firstSentenceTags();
109                        Tag[]  cmnt = classDoc.inlineTags();                                                                    // 5.5.4.1 (2012/07/06)
110                        Tag[] smplTags  = classDoc.tags(OG_FOR_SMPL);
111                        Tag[] grpTags   = classDoc.tags(OG_GROUP);
112
113                        // 5.7.1.1 (2013/12/13) タグのインデントを止める。
114                        writer.printTag( "<classDoc>" );
115                        writer.printTag(   "<tagClass>"         ,classFullName  ,"</tagClass>" );
116                        // 5.6.6.1 (2013/07/12) og.group を、tagGroup として独立させる。
117                        writer.printTag(   "<tagGroup>"         ,makeGroupTag( grpTags )        ,"</tagGroup>" );
118                        writer.printTag(   "<description>"      ,desc                                           ,"</description>" );
119                        writer.printTag(   "<contents>"         ,cmnt                   ,"</contents>" );
120                        writer.printTag(   "<formSample>"       ,smplTags               ,"</formSample>" );
121
122                        map.clear();
123                        String className = classDoc.name();
124                        while(  ! "BodyTagSupport".equals( className ) &&
125                                        ! "TagSupport".equals( className ) ) {
126                                String extendFlag = "false";
127                                if( "HTMLTagSupport".equals( className ) ) {
128                                        extendFlag = "true" ;
129                                }
130                                MethodDoc[] methods = classDoc.methods();
131                                for(int j=0; j < methods.length; j++) {
132                                        if( ! methods[j].isPublic() ) { continue; }
133                                        Tag[] tags = methods[j].tags(OG_TAG_NAME);
134                                        if(tags.length > 0) {
135                                                String methodName = DocletUtil.removeSetter( methods[j].name() );
136                                                if( map.containsKey( methodName ) ) { continue; }
137                                                map.put( methodName,className );
138                                                Tag[] ftag = methods[j].firstSentenceTags();
139                                                cmnt = methods[j].inlineTags();                                                                 // 5.5.4.1 (2012/07/06)
140
141                        // 5.7.1.1 (2013/12/13) タグのインデントを止める。
142                                                writer.printTag(   "<method>" );
143                                                writer.printTag(     "<name>"           ,methodName     ,"</name>" );
144                                                writer.printTag(     "<htmlExtend>"     ,extendFlag     ,"</htmlExtend>" );
145                                                writer.printTag(     "<description>",ftag               ,"</description>" );
146                                                // 5.7.1.1 (2013/12/13) cmnt と tags の間に改行をセット
147                                                writer.printTag(     "<contents>"       ,cmnt           ,"" );
148                                                writer.printTag( ""                                     ,tags           ,"</contents>" );
149                                                writer.printTag(   "</method>");
150                                        }
151                                }
152                                Type type = classDoc.superclassType();
153                                if( type == null ) { break; }
154                                classDoc  = type.asClassDoc() ;
155                                className = classDoc.name();
156                        }
157                        writer.printTag( "  </classDoc>" );
158                }
159        }
160
161        /**
162         * タグ配列を受け取り、タグ出力します。
163         * 複数のタグを出力する場合に、カンマ区切り文字で連結します。
164         *
165         * @og.rev 5.5.4.1 (2012/07/06) DocletUtil.htmlFilter → StringUtil.htmlFilter に変更
166         * @og.rev 5.6.6.1 (2013/07/12) og.group の表示方法を変更する。
167         *
168         * @param       tag タグ配列
169         *
170         * @return      タグ出力文字列
171         */
172        private static String makeGroupTag( final Tag[] tag ) {
173                StringBuilder but = new StringBuilder( 200 );
174                for( int i=0; i<tag.length; i++ ) {
175                        String data = StringUtil.htmlFilter( tag[i].text() );           // 5.5.4.1 (2012/07/06) DocletUtil → StringUtil に変更
176                        if( i > 0 ) { but.append( "," ); }
177                        but.append( "【" ).append( data ).append( "】" );                 // 5.6.6.1 (2013/07/12) og.group の表示方法を変更
178                }
179                return but.toString() ;                                                                                 // 5.6.6.1 (2013/07/12)
180        }
181
182        /**
183         * カスタムオプションを使用するドックレットの必須メソッド optionLength(String) です。
184         *
185         * ドックレットに認識させる各カスタムオプションに、 optionLength がその
186         * オプションを構成する要素 (トークン) の数を返さなければなりません。
187         * このカスタムオプションでは、 -tag オプションそのものと
188         * その値の 2 つの要素で構成されるので、作成するドックレットの
189         * optionLengthメソッドは、 -tag オプションに対して 2 を返さなくては
190         * なりません。また、認識できないオプションに対しては、0 を返します。
191         *
192         * @param option カスタムオプションのキーワード
193         *
194         * @return 要素 (トークン) の数
195         */
196        public static int optionLength( final String option ) {
197                if("-version".equalsIgnoreCase(option)) {
198                        return 2;
199                }
200                else if("-outfile".equalsIgnoreCase(option)) {
201                        return 2;
202                }
203                return 0;
204        }
205}