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         * 文字列引数を 2つと、タグ配列を受け取り、タグ出力します。
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}