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 * {@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 * {@og.doc03Link queryType Query_****クラス} 形式のdoc03Linkタグをリンク文字列に置き換えます。 249 * 250 * <a href="/gf/jsp/DOC03/index.jsp?command=NEW&GAMENID=DOC03&VERNO=X.X.X.X&VALUENAME=queryType" 251 * target="CONTENTS" >Query_****クラス</a> 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 = "<a href=\"/gf/jsp/DOC03/index.jsp?command=NEW&GAMENID=DOC03" 288// + "&VERNO=" + VERSION_NO 289// + "&VALUENAME=" + valnm 290// + "\" target=\"CONTENTS\">" 291// + body 292// + "</a>" ; 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 * このタグレットがインラインタグで {@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}