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 * 文字列引数を 2つと、タグ配列を受け取り、タグ出力します。 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 * {@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 * {@og.doc03Link queryType Query_****クラス} 形式のdoc03Linkタグをリンク文字列に置き換えます。 240 * 241 * <a href="/gf/jsp/DOC03/index.jsp?command=NEW&GAMENID=DOC03&VERNO=X.X.X.X&VALUENAME=queryType" 242 * target="CONTENTS" >Query_****クラス</a> 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 = "<a href=\"/gf/jsp/DOC03/index.jsp?command=NEW&GAMENID=DOC03" 277 + "&VERNO=" + VERSION_NO 278 + "&VALUENAME=" + valnm 279 + "\" target=\"CONTENTS\">" 280 + body 281 + "</a>" ; 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 * このタグレットがインラインタグで {@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}