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