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