    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;            // 7.4.4.0 (2021/06/30) openGionV8事前準備(taglet2→taglet)
    017
    018import com.sun.source.doctree.DocTree;
    019
    020import org.opengion.fukurou.util.FileUtil;
    021import org.opengion.fukurou.util.StringUtil;
    022import static org.opengion.fukurou.system.HybsConst.CR;                         // 6.1.0.0 (2014/12/26) refactoring
    023
    024import java.io.File;
    025import java.io.PrintWriter;
    026import java.io.IOException;
    027import java.util.List;
    028
    029import java.lang.reflect.Field;
    030// import java.security.AccessController;                               // 6.1.0.0 (2014/12/26) findBugs
    031// import java.security.PrivilegedAction;                               // 6.1.0.0 (2014/12/26) findBugs
    032
    033/**
    034 * DocTree 情報を出力する PrintWriter 相当クラスです。
    035 *
    036 * @version  7.3
    037 * @author      Kazuhiko Hasegawa
    038 * @since        JDK11.0,
    039 */
    040public final class DocTreeWriter implements AutoCloseable {
    041        private static final String OG_VALUE  = "{@og.value" ;
    042        private static final String OG_DOCLNK = "{@og.doc03Link" ;
    043        private static final String TAG_LNK   = "{@link" ;
    044
    045//      private static final String CLS = "org.opengion.fukurou.system.BuildNumber";            // package.class
    046//      private static final String FLD = "VERSION_NO";                                                                         // field
    047//      private static final String VERSION_NO = getStaticField( CLS , FLD );                           // 6.4.1.1 (2016/01/16) versionNo → VERSION_NO refactoring
    048
    049        // 8.0.2.1 (2021/12/10) JavaDocのVersionは、最後の一桁が X になっている。
    050        private static final String VERSION_NO ;
    051        static {
    052                final String CLS = "org.opengion.fukurou.system.BuildNumber";   // package.class
    053                final String FLD = "VERSION_NO";                                                                // field
    054                final String VER = getStaticField( CLS , FLD );                                 // VERSION_NO refactoring
    055                VERSION_NO = VER.substring( 0,VER.length()-1 ) + "X" ;                  // 1桁取り除いて、X を追加
    056        }
    057
    058        private String clsName ;
    059
    060        private final PrintWriter outFile ;
    061
    062        /**
    063         * Doclet のエントリポイントメソッドです。
    064         *
    065         * @og.rev 7.3.0.0 (2021/01/06) 新しいJavaDoc対応
    066         *
    067         * @param file          出力ファイル名
    068         * @param encode        エンコード
    069         * @throws IOException なんらかのエラーが発生した場合。
    070         */
    071        public DocTreeWriter( final String file,final String encode ) throws IOException {
    072                outFile = FileUtil.getPrintWriter( new File( file ),encode );
    073        }
    074
    075        /**
    076         * try-with-resourcesブロックで、自動的に呼ばれる AutoCloseable の実装。
    077         *
    078         * コンストラクタで渡された ResultSet を close() します。
    079         *
    080         * @og.rev 7.3.0.0 (2021/01/06) 新しいJavaDoc対応
    081         *
    082         * @see         java.lang.AutoCloseable#close()
    083         */
    084        @Override
    085        public void close() {
    086                if( outFile != null ) {
    087                        outFile.close();
    088                }
    089        }
    090
    091        /**
    092         * 現在処理中のクラス名をセットします。
    093         * これは、og.value の値所得時の自身のクラス名を求める場合に使用します。
    094         *
    095         * @og.rev 7.3.0.0 (2021/01/06) 新しいJavaDoc対応
    096         *
    097         * @param str 現在処理中のクラス名
    098         */
    099        public void setClassName( final String str ) {
    100                clsName = str;
    101        }
    102
    103        /**
    104         * 可変長の文字列引数を取り、文字列を出力します。
    105         * 文字列の最後に改行が入ります。
    106         *
    107         * @og.rev 7.3.0.0 (2021/01/06) 新しいJavaDoc対応
    108         *
    109         * @param str String...
    110         */
    111        public void printTag( final String... str ) {
    112                if( str.length == 3 ) {                                                         // 3つの場合だけ、真ん中をconvertToOiginal 処理する。
    113                        final StringBuilder buf = new StringBuilder( str[1] );
    114                        valueTag(               buf );
    115                        doc03LinkTag(   buf );
    116                        linkTag(                buf );
    117
    118                        outFile.print( str[0] );
    119                        outFile.print( convertToOiginal( buf.toString() ) );
    120                        outFile.println( str[2] );
    121                }
    122                else {
    123                        outFile.println( String.join( "",str ) );               // それ以外は単純な連結
    124                }
    125        }
    126
    127        /**
    128         * 文字列引数を ２つと、タグ配列を受け取り、タグ出力します。
    129         *
    130         * @og.rev 7.3.0.0 (2021/01/06) 新しいJavaDoc対応
    131         * @og.rev 8.0.0.0 (2021/07/31) Avoid instantiating new objects inside loops
    132         *
    133         * @param str1  第一文字列
    134         * @param doc   DocTreeリスト
    135         * @param str3  第三文字列
    136         */
    137        public void printTag( final String str1,final List<? extends DocTree> doc, final String str3 ) {
    138                final StringBuilder tmp = new StringBuilder( 1000 );
    139                final StringBuilder buf = new StringBuilder();                                  // 8.0.0.0 (2021/07/31) Avoid instantiating new objects inside loops
    140                for( final DocTree dt : doc ) {
    141//                      final StringBuilder buf = new StringBuilder( String.valueOf(dt) );
    142                        buf.setLength( 0 );                                                                                     // 8.0.0.0 (2021/07/31)
    143                        buf.append( String.valueOf(dt) );                                                       // 8.0.0.0 (2021/07/31)
    144                        valueTag(               buf );
    145                        doc03LinkTag(   buf );
    146                        linkTag(                buf );
    147
    148                        tmp.append( buf );
    149                }
    150
    151                outFile.print( str1 );
    152                outFile.print( convertToOiginal( tmp.toString() ) );
    153                outFile.println( str3 );
    154        }
    155
    156        /**
    157         * Unicode文字列から元の文字列に変換する ("￥u3042" → "あ")。
    158         *
    159         * @og.rev 7.3.0.0 (2021/01/06) 新しいJavaDoc対応
    160         *
    161         * @param unicode Unicode文字列("\u3042")
    162         *
    163         * @return      通常の文字列
    164         */
    165        /* default */ String convertToOiginal( final String unicode ) {
    166                final StringBuilder rtn = new StringBuilder( unicode );
    167
    168                int st = rtn.indexOf( "\\u" );
    169                while( st >= 0 ) {
    170                        final int ch = Integer.parseInt( rtn.substring( st+2,st+6 ),16 );
    171                        rtn.replace( st,st+6, Character.toString( (char)ch ) );
    172
    173                        st = rtn.indexOf( "\\u",st + 1 );
    174                }
    175
    176                return StringUtil.htmlFilter( rtn.toString() ).trim();
    177        }
    178
    179        /**
    180         * {&#064;og.value package.class#field} 形式のvalueタグを文字列に置き換えます。
    181         *
    182         * 処理的には、リフレクションで、値を取得します。値は、staticフィールドのみ取得可能です。
    183         *
    184         * @og.rev 7.3.0.0 (2021/01/06) 新しいJavaDoc対応
    185         *
    186         * @param buf Tagテキストを連結させるStringBuilder
    187         *
    188         * @return valueタグの解析結果のStringBuilder
    189         */
    190        private StringBuilder valueTag( final StringBuilder buf ) {
    191                int st = buf.indexOf( OG_VALUE );
    192                while( st >= 0 ) {
    193                        final int ed = buf.indexOf( "}", st+OG_VALUE.length() );                // 終了の "}" を探す
    194                        if( ed < 0 ) {
    195                                final String errMsg = "警告:{@og.value package.class#field} 形式の終了マーカー'}'がありません。" + CR
    196                                                                                + "[" + clsName + "],[" + buf + "]" ;
    197                                System.err.println( errMsg );
    198                                break ;
    199                        }
    200
    201                        final String val = buf.substring( st+OG_VALUE.length(),ed ).trim();
    202
    203                        String cls = null;              // package.class
    204                        String fld = null;              // field
    205                        // package.class#field 形式の解析。
    206                        final int adrs = val.indexOf( '#' ) ;
    207                        if( adrs > 0 ) {
    208                                cls = val.substring( 0,adrs );          // package.class
    209                                fld = val.substring( adrs+1 );          // field
    210
    211                                if( cls.indexOf( '.' ) < 0 ) {          // cls に . がない場合は、特殊な定数クラスか、自身のパッケージ内のクラス
    212                                        if( "HybsSystem".equals( cls ) || "SystemData".equals( cls ) ) {
    213                                                cls = "org.opengion.hayabusa.common." + cls ;
    214                                        }
    215                                        else if( "HybsConst".equals( cls ) ) {
    216                                                cls = "org.opengion.fukurou.system." + cls ;
    217                                        }
    218                                        else {
    219                                                final int sep = clsName.lastIndexOf( '.' );
    220                                                if( sep > 0 ) {
    221                                                        cls = clsName.substring( 0,sep+1 ) + cls ;              // ピリオドも含めるので、sep+1 とする。
    222                                                }
    223                                        }
    224                                }
    225                        }
    226                        else if( adrs == 0 ) {
    227                                cls = clsName;                                          // 現在処理中のクラス名
    228                                fld = val.substring( 1 );                       // #field
    229                        }
    230                        else {
    231                                final String errMsg = "警告:{@og.value package.class#field} 形式のフィールド名 #field がありません。" + CR
    232                                                                                + "[" + clsName + "],[" + val + "]" ;
    233                                System.err.println( errMsg );
    234
    235                                // # を付け忘れたと考え、自分自身のクラスを利用
    236                                cls = clsName;                                          // 現在処理中のクラス名
    237                                fld = val;                                                      // field
    238                        }
    239
    240                        final String text = getStaticField( cls,fld );
    241                        buf.replace( st,ed+1,text );
    242                        st = buf.indexOf( OG_VALUE,st+text.length() );          // 置き換えた文字列の長さ文だけ、後ろから再検索開始
    243                }
    244                return buf ;
    245        }
    246
    247        /**
    248         * {&#064;og.doc03Link queryType Query_****クラス} 形式のdoc03Linkタグをリンク文字列に置き換えます。
    249         *
    250         * &lt;a href="/gf/jsp/DOC03/index.jsp?command=NEW&amp;GAMENID=DOC03&amp;VERNO=X.X.X.X&amp;VALUENAME=queryType"
    251         *          target="CONTENTS" &gt;Query_****クラス&lt;/a&gt;
    252         * のようなリンクを作成します。
    253         * 第一引数は、VALUENAME の引数です。
    254         * それ以降のテキストは、リンク文字列のドキュメントになります。
    255         * DOC03 画面へのリンクを作成するに当たり、バージョンが必要です。
    256         * org.opengion.fukurou.system.BuildNumber#VERSION_NO から取得しますが、
    257         * パッケージの優先順の関係で、リフレクションを使用します。
    258         *
    259         * @og.rev 7.3.0.0 (2021/01/06) 新しいJavaDoc対応
    260         * @og.rev 8.0.2.1 (2021/12/10) 後で、htmlFilter処理するので、通常のhtmlにしておく。
    261         *
    262         * @param buf Tagテキストを連結させるStringBuilder
    263         *
    264         * @return valueタグの解析結果のStringBuilder
    265         * @og.rtnNotNull
    266         */
    267        private StringBuilder doc03LinkTag( final StringBuilder buf ) {
    268                int st = buf.indexOf( OG_DOCLNK );
    269                while( st >= 0 ) {
    270                        final int ed = buf.indexOf( "}", st+OG_DOCLNK.length() );               // 終了の "}" を探す
    271                        if( ed < 0 ) {
    272                                final String errMsg = "警告:{@og.doc03Link queryType Query_****クラス} 形式の終了マーカー'}'がありません。" + CR
    273                                                                                + "[" + clsName + "],[" + buf + "]" ;
    274                                System.err.println( errMsg );
    275                                break ;
    276                        }
    277
    278                        final String val = buf.substring( st+OG_DOCLNK.length(),ed ).trim();
    279
    280                        String link = "" ;
    281                        final int adrs = val.indexOf(' ') ;                     // 最初のスペースで分離します。
    282                        if( adrs > 0 ) {
    283                                final String valnm = val.substring( 0,adrs ).trim();    // VALUENAME
    284                                final String body  = val.substring( adrs+1 ).trim();    // ドキュメント
    285
    286                                // 8.0.2.1 (2021/12/10) 後で、htmlFilter処理するので、通常のhtmlにしておく。
    287//                              link = "&lt;a href=\"/gf/jsp/DOC03/index.jsp?command=NEW&amp;GAMENID=DOC03"
    288//                                              + "&amp;VERNO="     + VERSION_NO
    289//                                              + "&amp;VALUENAME=" + valnm
    290//                                              + "\" target=\"CONTENTS\"&gt;"
    291//                                              + body
    292//                                              + "&lt;/a&gt;" ;
    293
    294                                link = "<a href=\"/gf/jsp/DOC03/index.jsp?command=NEW&GAMENID=DOC03"
    295                                                + "&VERNO="     + VERSION_NO
    296                                                + "&VALUENAME=" + valnm
    297                                                + "\" target=\"CONTENTS\">"
    298                                                + body
    299                                                + "</a>" ;
    300                        }
    301                        else {
    302                                link = OG_DOCLNK + " 【不明】:" + val ;
    303                                final String errMsg = "[" + clsName + "],[" + link + "]" ;
    304                                System.err.println( errMsg );
    305                        }
    306
    307                        buf.replace( st,ed+1,link );
    308                        st = buf.indexOf( OG_DOCLNK,st+link.length() );         // 置き換えた文字列の長さ文だけ、後ろから再検索開始
    309                }
    310                return buf ;
    311        }
    312
    313        /**
    314         * このタグレットがインラインタグで {&#064;link XXXX YYYY} を処理するように
    315         * 用意された、カスタムメソッドです。
    316         *
    317         * @og.rev 7.3.0.0 (2021/01/06) 新しいJavaDoc対応
    318         *
    319         * @param buf Tagテキストを連結させるStringBuilder
    320         *
    321         * @return valueタグの解析結果のStringBuilder
    322         * @og.rtnNotNull
    323         */
    324        private StringBuilder linkTag( final StringBuilder buf ) {
    325                int st = buf.indexOf( TAG_LNK );
    326                while( st >= 0 ) {
    327                        final int ed = buf.indexOf( "}", st+TAG_LNK.length() );         // 終了の "}" を探す
    328                        if( ed < 0 ) {
    329                                final String errMsg = "警告:{@link XXXX YYYY} 形式の終了マーカー'}'がありません。" + CR
    330                                                                                + "[" + clsName + "],[" + buf + "]" ;
    331                                System.err.println( errMsg );
    332                                break ;
    333                        }
    334
    335                        final String val = buf.substring( st+TAG_LNK.length(),ed ).trim();
    336
    337                        String link = "" ;
    338                        final int adrs = val.indexOf(' ') ;                     // 最初のスペースで分離します。
    339                        if( adrs > 0 ) {
    340                                final String xxx = val.substring( 0,adrs ).trim();      // 前半：アドレス変換
    341                                final String yyy = val.substring( adrs   ).trim();      // 後半：ラベル
    342                                final String zzz = xxx.replace( '.','/' );
    343
    344                                link = "<a href=\"../../../../" + zzz + ".html\" " + "title=\"" + xxx + "\">" + yyy + "</a>" ;
    345                        }
    346                        else {
    347                                link = TAG_LNK + " " + val ;            // 元に戻す
    348        //                      final String errMsg = "[" + clsName + "],[" + link + "]" ;
    349        //                      System.err.println( errMsg );
    350                        }
    351
    352                        buf.replace( st,ed+1,link );
    353                        st = buf.indexOf( TAG_LNK,st+link.length() );           // 置き換えた文字列の長さ文だけ、後ろから再検索開始
    354                }
    355                return buf ;
    356        }
    357
    358        /**
    359         * パッケージ.クラス名 と、フィールド名 から、staticフィールドの値を取得します。
    360         *
    361         * Field fldObj = Class.forName( cls ).getDeclaredField( fld ); で、Fieldオブジェクトを呼出し、
    362         * String.valueOf( fldObj.get( null ) ); で、値を取得しています。
    363         * static フィールドは、引数 null で値を取得できます。
    364         *
    365         * ※ 超特殊処理として、cls名が、HybsSystem とSystemData のみ、パッケージ無しで処理
    366         *    できるように対応します。
    367         *
    368         * 例；
    369     *      String cls = "org.opengion.fukurou.system.BuildNumber";        // package.class
    370     *      String fld = "VERSION_NO";                                     // field
    371         *
    372         * @og.rev 7.3.0.0 (2021/01/06) 新しいJavaDoc対応
    373         *
    374         * @param cls パッケージ.クラス名
    375         * @param fld フィールド名
    376         * @return 取得値
    377         */
    378        private static String getStaticField( final String cls , final String fld ) {
    379                String txt = "";
    380                try {
    381                        final Field fldObj = Class.forName( cls ).getDeclaredField( fld );
    382                        if( !fldObj.canAccess( null ) ) {
    383        // JDK1.8 警告:[removal] java.securityのAccessControllerは推奨されておらず、削除用にマークされています
    384        //                      AccessController.doPrivileged( new PrivilegedAction<DocTreeWriter>() {
    385        //                              /**
    386        //                               * 特権を有効にして実行する PrivilegedAction<T> の run() メソッドです。
    387        //                               *
    388        //                               * このメソッドは、特権を有効にしたあとに AccessController.doPrivileged によって呼び出されます。
    389        //                               *
    390        //                               * @return  DocTreeWriterオブジェクト
    391        //                               */
    392        //                              public DocTreeWriter run() {
    393        //                                      // privileged code goes here
    394                                                fldObj.setAccessible( true );
    395        //                                      return null; // nothing to return
    396        //                              }
    397        //                      });
    398                        }
    399                        txt = String.valueOf( fldObj.get( null ) );             // static フィールドは、引数 null で値を取得
    400                }
    401                catch( final Throwable th ) {
    402                        final String errMsg = "package.class=[" + cls + "],field=[" + fld + "] の取得に失敗しました。" + CR
    403                                                        + th ;
    404                        System.err.println( errMsg );
    405                }
    406
    407                return txt ;
    408        }
    409}