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;
019
020import com.sun.javadoc.RootDoc;
021import com.sun.javadoc.ClassDoc;
022import com.sun.javadoc.Type;
023import com.sun.javadoc.Tag;
024
025import java.util.Map;
026import java.util.HashMap;
027import java.io.IOException;
028
029/**
030 * ソースコメントから、属性情報を取り出す Doclet クラスです。
031 *
032 * @version  4.0
033 * @author   Kazuhiko Hasegawa
034 * @since    JDK5.0,
035 */
036@SuppressWarnings(value={"deprecation","removal"})                      // Ver7.0.0.0 , 7.2.2.0 (2020/03/27)
037public final class DocletPlugin {
038        /** 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。  */
039        // 6.4.3.3 (2016/03/04) 匿名クラスとインスタンス初期化子による、Mapの初期化処理。
040        /** staticイニシャライザ後、読み取り専用にするので、ConcurrentHashMap を使用しません。 */
041        private static final Map<String,AttKeySet> ATT_KEY_MAP = new HashMap<>();
042        static {
043                int no = 0;                             // 7.2.2.0 (2020/03/27) 連番の自動生成
044                ATT_KEY_MAP.put( "org.opengion.hayabusa.db.Query"                                       , new AttKeySet( "Query"                        ,no++, "queryType"              ));
045                ATT_KEY_MAP.put( "org.opengion.hayabusa.db.CellRenderer"                        , new AttKeySet( "Renderer"                     ,no++, "renderer"               ));
046                ATT_KEY_MAP.put( "org.opengion.hayabusa.db.CellEditor"                          , new AttKeySet( "Editor"                       ,no++, "editor"                 ));
047                ATT_KEY_MAP.put( "org.opengion.hayabusa.db.DBType"                                      , new AttKeySet( "DBType"                       ,no++, "dbType"                 ));
048                ATT_KEY_MAP.put( "org.opengion.hayabusa.db.TableFilter"                         , new AttKeySet( "TableFilter"          ,no++, "tableFilter"    ));
049                ATT_KEY_MAP.put( "org.opengion.hayabusa.db.Selection"                           , new AttKeySet( "Selection"            ,no++, "selection"              ));
050                ATT_KEY_MAP.put( "org.opengion.hayabusa.db.DBConstValue"                        , new AttKeySet( "DBConstValue"         ,no++, "cnstVal"                ));             // 5.6.3.3 (2013/04/19)
051                ATT_KEY_MAP.put( "org.opengion.hayabusa.html.ViewForm"                          , new AttKeySet( "ViewForm"                     ,no++, "viewFormType"   ));
052                ATT_KEY_MAP.put( "org.opengion.hayabusa.io.TableWriter"                         , new AttKeySet( "TableWriter"          ,no++, "writerClass"    ));
053                ATT_KEY_MAP.put( "org.opengion.hayabusa.io.TableReader"                         , new AttKeySet( "TableReader"          ,no++, "readerClass"    ));
054//              ATT_KEY_MAP.put( "org.opengion.hayabusa.report.DBTableReport"           , new AttKeySet( "DBTableReport"        ,no++, "tableReport"    ));             // 7.0.1.5 (2018/12/10) 削除
055                ATT_KEY_MAP.put( "org.opengion.hayabusa.resource.CalendarQuery"         , new AttKeySet( "CalendarQuery"        ,no++, "calDB"                  ));
056                ATT_KEY_MAP.put( "org.opengion.hayabusa.resource.CalendarData"          , new AttKeySet( "CalendarData"         ,no++, "calData"                ));             // 5.6.3.3 (2013/04/19)
057                ATT_KEY_MAP.put( "org.opengion.fukurou.process.HybsProcess"                     , new AttKeySet( "Process"                      ,no++, "process"                ));
058//              ATT_KEY_MAP.put( "org.opengion.fukurou.transfer.TransferExec"           , new AttKeySet( "TransferExec"         ,no++, "kbExec"                 ));             // 5.5.3.5 (2012/06/21)
059//              ATT_KEY_MAP.put( "org.opengion.fukurou.transfer.TransferRead"           , new AttKeySet( "TransferRead"         ,no++, "kbRead"                 ));             // 5.5.3.5 (2012/06/21)
060                ATT_KEY_MAP.put( "org.opengion.fukurou.util.HybsTimerTask"                      , new AttKeySet( "Daemon"                       ,no++, "daemon"                 ));             // 4.3.4.4 (2009/01/01)
061                ATT_KEY_MAP.put( "org.opengion.fukurou.util.ConnectIF   "                       , new AttKeySet( "ConnectIF"            ,no++, "connIF"                 ));             // 5.6.3.3 (2013/04/19)
062                ATT_KEY_MAP.put( "org.opengion.fukurou.xml.JspParserFilter"                     , new AttKeySet( "JspCreate"            ,no++, "jspParser"              ));             // 5.6.3.3 (2013/04/19)
063        }
064
065        private static final String OG_FOR_SMPL  = "og.formSample";
066        private static final String ENCODE = "UTF-8";
067
068        /**
069         * すべてが staticメソッドなので、コンストラクタを呼び出さなくしておきます。
070         *
071         */
072        private DocletPlugin() {}
073
074        /**
075         * Doclet のエントリポイントメソッドです。
076         *
077         * @og.rev 5.7.1.1 (2013/12/13) タグのインデントを止める。
078         *
079         * @param root ドキュメントルートオブジェクト
080         *
081         * @return 正常実行時 true
082         */
083        public static boolean start( final RootDoc root ) {
084                final String version = DocletUtil.getOption( "-version" , root.options() );
085                final String file    = DocletUtil.getOption( "-outfile" , root.options() );
086
087                DocletTagWriter writer = null;
088                try {
089                        writer = new DocletTagWriter( file,ENCODE );
090
091                        // 5.7.1.1 (2013/12/13) タグのインデントを止める。
092                        writer.printTag( "<?xml version=\"1.0\" encoding=\"", ENCODE, "\" ?>" );
093                        writer.printTag( "<javadoc>" );
094                        writer.printTag( "  <version>",version,"</version>" );
095                        writer.printTag( "  <description></description>" );
096                        writeContents( root.classes(),writer );
097                        writer.printTag( "</javadoc>" );
098                }
099                catch( final IOException ex ) {
100                        LogWriter.log( ex );
101                }
102                finally {
103                        if( writer != null ) { writer.close(); }
104                }
105                return true;
106        }
107
108        /**
109         * ClassDoc 配列よりコンテンツを作成します。
110         * インターフェースも処理の対象とします。
111         *
112         * @og.rev 5.5.4.1 (2012/07/06) コメントは文字列でなく、Tag配列として処理させる。
113         * @og.rev 5.7.1.1 (2013/12/13) タグのインデントを止める。
114         *
115         * @param classes       ClassDoc配列
116         * @param writer        DocletTagWriterオブジェクト
117         */
118        private static void writeContents( final ClassDoc[] classes,final DocletTagWriter writer ) {
119                for( int i=0; i< classes.length; i++ ) {
120                        final ClassDoc classDoc      = classes[i] ;
121                        if( ! classDoc.isPublic() ) { continue; }
122
123                        final AttKeySet attSet = getAttGroupName( classDoc ) ;
124                        if( attSet == null ) { continue; }              // map に登録されていない。
125
126                        final String attKey   = attSet.getAttKey( classDoc.name() );
127                        if( attKey == null ) { continue; }              // 対象クラス名が、一致しない。
128
129                        final String attClass = classDoc.qualifiedName() ;      // Class Full Name
130                        final Tag[]  desc     = classDoc.firstSentenceTags();
131                        final Tag[]  cmnt     = classDoc.inlineTags();                                                                  // 5.5.4.1 (2012/07/06)
132                        final Tag[] smplTags  = classDoc.tags(OG_FOR_SMPL);
133
134                        // 5.7.1.1 (2013/12/13) タグのインデントを止める。
135                        writer.printTag( "<classDoc>" );
136                        writer.printTag( "  <attClass>"         ,attClass                               ,"</attClass>"          );
137                        writer.printTag( "  <seq>"                      ,attSet.getSeq()                ,"</seq>"                       );
138                        writer.printTag( "  <attKey>"           ,attKey                                 ,"</attKey>"            );
139                        writer.printTag( "  <valueName>"        ,attSet.getValueName()  ,"</valueName>"         );
140                        writer.printTag( "  <description>"      ,desc                                   ,"</description>"       );
141                        writer.printTag( "  <contents>"         ,cmnt                                   ,"</contents>"          );
142                        writer.printTag( "  <formSample>"       ,smplTags                               ,"</formSample>"        );
143                        writer.printTag( "</classDoc>" );
144                }
145        }
146
147        /**
148         * 指定の ClassDoc が、処理する属性クラスのMapに含まれている場合、
149         * その AttKeySet クラスのオブジェクトを返します。
150         * 存在しない場合、null を返します。
151         *
152         * @og.rev 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。変数名も変えておきます。
153         *
154         * @param       classDoc ClassDocオブジェクト
155         *
156         * @return      ClassDocに対応する AttKeySetオブジェクト
157         */
158        private static AttKeySet getAttGroupName( final ClassDoc classDoc ) {
159                if( classDoc == null ) { return null; }
160
161                final String classFullName = classDoc.qualifiedName() ;
162                AttKeySet attKey = ATT_KEY_MAP.get( classFullName );            //6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。変数名も変えておきます。
163                if( attKey == null ) {
164                        final Type type = classDoc.superclassType();                    // 親クラスタイプ
165                        if( type != null ) {
166                                attKey = getAttGroupName( type.asClassDoc() );          // 親クラス
167                        }
168
169                        if( attKey == null ) {
170                                final Type[] itface = classDoc.interfaceTypes();        // 直近インターフェース
171                                for( int i=0; i<itface.length; i++ ) {
172                                        attKey = getAttGroupName( itface[i].asClassDoc() );
173                                        if( attKey != null ) { break; }
174                                }
175                        }
176                }
177                return attKey;
178        }
179
180        /**
181         * カスタムオプションを使用するドックレットの必須メソッド optionLength(String) です。
182         *
183         * ドックレットに認識させる各カスタムオプションに、 optionLength がその
184         * オプションを構成する要素 (トークン) の数を返さなければなりません。
185         * このカスタムオプションでは、 -tag オプションそのものと
186         * その値の 2 つの要素で構成されるので、作成するドックレットの
187         * optionLengthメソッドは、 -tag オプションに対して 2 を返さなくては
188         * なりません。また、認識できないオプションに対しては、0 を返します。
189         *
190         * @param option オプション文字列
191         *
192         * @return 要素 (トークン) の数
193         */
194        public static int optionLength( final String option ) {
195                if( "-version".equalsIgnoreCase(option) ) {
196                        return 2;
197                }
198                else if( "-outfile".equalsIgnoreCase(option) ) {
199                        return 2;
200                }
201                return 0;
202        }
203
204        /**
205         * 属性情報を管理する、AttKeySet クラスです。
206         *
207         * @og.rev 6.3.9.1 (2015/11/27) 別ファイルで管理していた、パッケージプライベートclass を、内部staticクラスに移動。
208         *
209         * @version  4.0
210         * @author   Kazuhiko Hasegawa
211         * @since    JDK5.0,
212         */
213        private static final class AttKeySet {
214                private final String searchKey ;
215                private final int    len ;
216                private final String seq ;
217                private final String valueName ;
218
219                /**
220                 * コンストラクター
221                 *
222                 * @param searchKey  検索キー
223                 * @param seq        シーケンス番号
224                 * @param valueName  属性名
225                 *
226                 */
227                AttKeySet( final String searchKey,final int seq,final String valueName ) {
228                        this.searchKey          = searchKey ;
229                        this.seq                = String.valueOf( seq );
230                        this.valueName          = valueName ;
231
232                        len = searchKey.length();
233                }
234
235                /**
236                 * シーケンス番号を返します。
237                 *
238                 * @return シーケンス番号
239                 *
240                 */
241                /* default */ String getSeq() {
242                        return seq;
243                }
244
245                /**
246                 * 属性名を返します。
247                 *
248                 * @return 属性名
249                 *
250                 */
251                /* default */ String getValueName() {
252                        return valueName;
253                }
254
255                /**
256                 * クラス名の先頭一致の場合の、**** 部分を返します。
257                 * インターフェースも扱えるように修正しましたので、先頭が _ の場合は、
258                 * _ を削除して返します。
259                 *
260                 * @param name クラスの名称 (例:DBCellEditor_**** , ViewForm_****)
261                 * @return クラス名の**** 部分
262                 */
263                /* default */ String getAttKey( final String name ) {
264                        // 6.1.0.0 (2014/12/26) refactoring
265                        String rtn = null;                                                              // 一致しなかった。
266
267                        if( name.equals( searchKey ) ) {                                // 完全一致:インターフェース
268                                rtn =  "(Interface)" + name ;
269                        }
270                        else if( name.indexOf( searchKey ) == 0 ) {             // 先頭一致した。
271                                rtn = name.substring( len );
272                                if( rtn.charAt(0) == '_' ) { rtn = rtn.substring( 1 ); }        // 先頭が _ の場合は、_ を削除
273                        }
274
275                        return rtn ;
276                }
277        }
278}