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