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.taglet2; 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; 030import java.security.AccessController; // 6.1.0.0 (2014/12/26) findBugs 031import 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 * 123 * @param str1 第一文字列 124 * @param doc DocTreeリスト 125 * @param str3 第三文字列 126 */ 127 public void printTag( final String str1,final List<? extends DocTree> doc, final String str3 ) { 128 final StringBuilder tmp = new StringBuilder( 1000 ); 129 for( final DocTree dt : doc ) { 130 final StringBuilder buf = new StringBuilder( String.valueOf(dt) ); 131 valueTag( buf ); 132 doc03LinkTag( buf ); 133 linkTag( buf ); 134 135 tmp.append( buf ); 136 } 137 138 outFile.print( str1 ); 139 outFile.print( convertToOiginal( tmp.toString() ) ); 140 outFile.println( str3 ); 141 } 142 143 /** 144 * Unicode文字列から元の文字列に変換する ("¥u3042" -> "あ")。 145 * 146 * @og.rev 7.3.0.0 (2021/01/06) 新しいJavaDoc対応 147 * 148 * @param unicode Unicode文字列("\u3042") 149 * 150 * @return 通常の文字列 151 */ 152 private static String convertToOiginal( final String unicode ) { 153 final StringBuilder rtn = new StringBuilder( unicode ); 154 155 int st = rtn.indexOf( "\\u" ); 156 while( st >= 0 ) { 157 final int ch = Integer.parseInt( rtn.substring( st+2,st+6 ),16 ); 158 rtn.replace( st,st+6, Character.toString( (char)ch ) ); 159 160 st = rtn.indexOf( "\\u",st + 1 ); 161 } 162 163 return StringUtil.htmlFilter( rtn.toString() ).trim(); 164 } 165 166 /** 167 * {@og.value package.class#field} 形式のvalueタグを文字列に置き換えます。 168 * 169 * 処理的には、リフレクションで、値を取得します。値は、staticフィールドのみ取得可能です。 170 * 171 * @og.rev 7.3.0.0 (2021/01/06) 新しいJavaDoc対応 172 * 173 * @param buf Tagテキストを連結させるStringBuilder 174 * 175 * @return valueタグの解析結果のStringBuilder 176 */ 177 private StringBuilder valueTag( final StringBuilder buf ) { 178 int st = buf.indexOf( OG_VALUE ); 179 while( st >= 0 ) { 180 final int ed = buf.indexOf( "}", st+OG_VALUE.length() ); // 終了の "}" を探す 181 if( ed < 0 ) { 182 final String errMsg = "警告:{@og.value package.class#field} 形式の終了マーカー'}'がありません。" + CR 183 + "[" + clsName + "],[" + buf + "]" ; 184 System.err.println( errMsg ); 185 break ; 186 } 187 188 final String val = buf.substring( st+OG_VALUE.length(),ed ).trim(); 189 190 String cls = null; // package.class 191 String fld = null; // field 192 // package.class#field 形式の解析。 193 final int adrs = val.indexOf( '#' ) ; 194 if( adrs > 0 ) { 195 cls = val.substring( 0,adrs ); // package.class 196 fld = val.substring( adrs+1 ); // field 197 198 if( cls.indexOf( '.' ) < 0 ) { // cls に . がない場合は、特殊な定数クラスか、自身のパッケージ内のクラス 199 if( "HybsSystem".equals( cls ) || "SystemData".equals( cls ) ) { 200 cls = "org.opengion.hayabusa.common." + cls ; 201 } 202 else if( "HybsConst".equals( cls ) ) { 203 cls = "org.opengion.fukurou.system." + cls ; 204 } 205 else { 206 final int sep = clsName.lastIndexOf( '.' ); 207 if( sep > 0 ) { 208 cls = clsName.substring( 0,sep+1 ) + cls ; // ピリオドも含めるので、sep+1 とする。 209 } 210 } 211 } 212 } 213 else if( adrs == 0 ) { 214 cls = clsName; // 現在処理中のクラス名 215 fld = val.substring( 1 ); // #field 216 } 217 else { 218 final String errMsg = "警告:{@og.value package.class#field} 形式のフィールド名 #field がありません。" + CR 219 + "[" + clsName + "],[" + val + "]" ; 220 System.err.println( errMsg ); 221 222 // # を付け忘れたと考え、自分自身のクラスを利用 223 cls = clsName; // 現在処理中のクラス名 224 fld = val; // field 225 } 226 227 final String text = getStaticField( cls,fld ); 228 buf.replace( st,ed+1,text ); 229 st = buf.indexOf( OG_VALUE,st+text.length() ); // 置き換えた文字列の長さ文だけ、後ろから再検索開始 230 } 231 return buf ; 232 } 233 234 235 /** 236 * {@og.doc03Link queryType Query_****クラス} 形式のdoc03Linkタグをリンク文字列に置き換えます。 237 * 238 * <a href="/gf/jsp/DOC03/index.jsp?command=NEW&GAMENID=DOC03&VERNO=X.X.X.X&VALUENAME=queryType" 239 * target="CONTENTS" >Query_****クラス</a> 240 * のようなリンクを作成します。 241 * 第一引数は、VALUENAME の引数です。 242 * それ以降のテキストは、リンク文字列のドキュメントになります。 243 * DOC03 画面へのリンクを作成するに当たり、バージョンが必要です。 244 * org.opengion.fukurou.system.BuildNumber#VERSION_NO から取得しますが、 245 * パッケージの優先順の関係で、リフレクションを使用します。 246 * 247 * @og.rev 7.3.0.0 (2021/01/06) 新しいJavaDoc対応 248 * 249 * @param buf Tagテキストを連結させるStringBuilder 250 * 251 * @return valueタグの解析結果のStringBuilder 252 * @og.rtnNotNull 253 */ 254 private StringBuilder doc03LinkTag( final StringBuilder buf ) { 255 int st = buf.indexOf( OG_DOCLNK ); 256 while( st >= 0 ) { 257 final int ed = buf.indexOf( "}", st+OG_DOCLNK.length() ); // 終了の "}" を探す 258 if( ed < 0 ) { 259 final String errMsg = "警告:{@og.doc03Link queryType Query_****クラス} 形式の終了マーカー'}'がありません。" + CR 260 + "[" + clsName + "],[" + buf + "]" ; 261 System.err.println( errMsg ); 262 break ; 263 } 264 265 final String val = buf.substring( st+OG_DOCLNK.length(),ed ).trim(); 266 267 String link = "" ; 268 final int adrs = val.indexOf(' ') ; // 最初のスペースで分離します。 269 if( adrs > 0 ) { 270 final String valnm = val.substring( 0,adrs ).trim(); // VALUENAME 271 final String body = val.substring( adrs+1 ).trim(); // ドキュメント 272 273 link = "<a href=\"/gf/jsp/DOC03/index.jsp?command=NEW&GAMENID=DOC03" 274 + "&VERNO=" + VERSION_NO 275 + "&VALUENAME=" + valnm 276 + "\" target=\"CONTENTS\">" 277 + body 278 + "</a>" ; 279 } 280 else { 281 link = OG_DOCLNK + " 【不明】:" + val ; 282 final String errMsg = "[" + clsName + "],[" + link + "]" ; 283 System.err.println( errMsg ); 284 } 285 286 buf.replace( st,ed+1,link ); 287 st = buf.indexOf( OG_DOCLNK,st+link.length() ); // 置き換えた文字列の長さ文だけ、後ろから再検索開始 288 } 289 return buf ; 290 } 291 292 /** 293 * このタグレットがインラインタグで {@link XXXX YYYY} を処理するように 294 * 用意された、カスタムメソッドです。 295 * 296 * @og.rev 7.3.0.0 (2021/01/06) 新しいJavaDoc対応 297 * 298 * @param buf Tagテキストを連結させるStringBuilder 299 * 300 * @return valueタグの解析結果のStringBuilder 301 * @og.rtnNotNull 302 */ 303 private StringBuilder linkTag( final StringBuilder buf ) { 304 int st = buf.indexOf( TAG_LNK ); 305 while( st >= 0 ) { 306 final int ed = buf.indexOf( "}", st+TAG_LNK.length() ); // 終了の "}" を探す 307 if( ed < 0 ) { 308 final String errMsg = "警告:{@link XXXX YYYY} 形式の終了マーカー'}'がありません。" + CR 309 + "[" + clsName + "],[" + buf + "]" ; 310 System.err.println( errMsg ); 311 break ; 312 } 313 314 final String val = buf.substring( st+TAG_LNK.length(),ed ).trim(); 315 316 String link = "" ; 317 final int adrs = val.indexOf(' ') ; // 最初のスペースで分離します。 318 if( adrs > 0 ) { 319 final String xxx = val.substring( 0,adrs ).trim(); // 前半:アドレス変換 320 final String yyy = val.substring( adrs ).trim(); // 後半:ラベル 321 final String zzz = xxx.replace( '.','/' ); 322 323 link = "<a href=\"../../../../" + zzz + ".html\" " + "title=\"" + xxx + "\">" + yyy + "</a>" ; 324 } 325 else { 326 link = TAG_LNK + " " + val ; // 元に戻す 327 // final String errMsg = "[" + clsName + "],[" + link + "]" ; 328 // System.err.println( errMsg ); 329 } 330 331 buf.replace( st,ed+1,link ); 332 st = buf.indexOf( TAG_LNK,st+link.length() ); // 置き換えた文字列の長さ文だけ、後ろから再検索開始 333 } 334 return buf ; 335 } 336 337 /** 338 * パッケージ.クラス名 と、フィールド名 から、staticフィールドの値を取得します。 339 * 340 * Field fldObj = Class.forName( cls ).getDeclaredField( fld ); で、Fieldオブジェクトを呼出し、 341 * String.valueOf( fldObj.get( null ) ); で、値を取得しています。 342 * static フィールドは、引数 null で値を取得できます。 343 * 344 * ※ 超特殊処理として、cls名が、HybsSystem とSystemData のみ、パッケージ無しで処理 345 * できるように対応します。 346 * 347 * 例; 348 * String cls = "org.opengion.fukurou.system.BuildNumber"; // package.class 349 * String fld = "VERSION_NO"; // field 350 * 351 * @og.rev 7.3.0.0 (2021/01/06) 新しいJavaDoc対応 352 * 353 * @param cls パッケージ.クラス名 354 * @param fld フィールド名 355 * @return 取得値 356 */ 357 private static String getStaticField( final String cls , final String fld ) { 358 String txt = ""; 359 try { 360 final Field fldObj = Class.forName( cls ).getDeclaredField( fld ); 361 if( !fldObj.canAccess( null ) ) { 362 AccessController.doPrivileged( new PrivilegedAction<DocTreeWriter>() { 363 /** 364 * 特権を有効にして実行する PrivilegedAction<T> の run() メソッドです。 365 * 366 * このメソッドは、特権を有効にしたあとに AccessController.doPrivileged によって呼び出されます。 367 * 368 * @return DocTreeWriterオブジェクト 369 */ 370 public DocTreeWriter run() { 371 // privileged code goes here 372 fldObj.setAccessible( true ); 373 return null; // nothing to return 374 } 375 }); 376 } 377 txt = String.valueOf( fldObj.get( null ) ); // static フィールドは、引数 null で値を取得 378 } 379 catch( final Throwable th ) { 380 final String errMsg = "package.class=[" + cls + "],field=[" + fld + "] の取得に失敗しました。" + CR 381 + th ; 382 System.err.println( errMsg ); 383 } 384 385 return txt ; 386 } 387}