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