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;
017
018import org.opengion.fukurou.system.ThrowUtil;
019import static org.opengion.fukurou.system.HybsConst.CR;                         // 6.1.0.0 (2014/12/26) refactoring
020import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE;      // 6.1.0.0 (2014/12/26) refactoring
021
022import java.lang.reflect.Field;
023import java.security.AccessController;                          // 6.1.0.0 (2014/12/26) findBugs
024import java.security.PrivilegedAction;                          // 6.1.0.0 (2014/12/26) findBugs
025
026import com.sun.javadoc.Tag;
027import com.sun.javadoc.Doc;
028import com.sun.javadoc.ClassDoc;
029import com.sun.javadoc.ProgramElementDoc;
030
031/**
032 * Doclet を処理するプログラムで共通して使用される簡易メソッド群(ユーティリティクラス)です。
033 *
034 * @version  4.0
035 * @author   Kazuhiko Hasegawa
036 * @since    JDK5.0,
037 */
038@SuppressWarnings(value={"deprecation","removal"})                      // Ver7.0.0.0 , 7.2.2.0 (2020/03/27)
039public final class DocletUtil {
040
041        private static final String CLS = "org.opengion.fukurou.system.BuildNumber";            // package.class
042        private static final String FLD = "VERSION_NO";                                                                         // field
043        private static final String VERSION_NO = getStaticField( CLS , FLD );                           // 6.4.1.1 (2016/01/16) versionNo → VERSION_NO refactoring
044
045        /**
046         * すべてが staticメソッドなので、コンストラクタを呼び出さなくしておきます。
047         *
048         */
049        private DocletUtil() {}
050
051        /**
052         * target 文字列に含まれる from 文字列を to 文字列に置き換えます。
053         *
054         * @param       target  元の文字列
055         * @param       from    置換元FROM
056         * @param       to              置換先TO
057         *
058         * @return      変換後文字列
059         */
060        public static String replace( final String target,final String from,final String to ) {
061                if( target == null || from == null || to == null ) { return target; }
062                final StringBuilder strBuf = new StringBuilder( BUFFER_MIDDLE );
063
064                int start = 0;
065                int end   = target.indexOf( from,start );
066                while( end >= 0  ) {
067                        strBuf.append( target.substring( start,end ) );
068                        strBuf.append( to );
069                        start = end + from.length();
070                        end   = target.indexOf( from,start );
071                }
072                strBuf.append( target.substring( start ) );
073
074                return strBuf.toString();
075        }
076
077        /**
078         * セッターメソッドの setXXXX の set を削除し、次の文字を小文字化します。
079         * つまり、セッターメソッドから属性値を推測します。
080         * (超特殊処理)セッターメソッドのset以下2文字目が大文字の場合は、
081         * 1文字目も大文字と考えて小文字化を行いません。
082         * 例えば、setSYS や setUSER など、RequestValueTag.javaに使用するケースです。
083         *
084         * @param target 処理対象となる文字列
085         *
086         * @return オプション文字列
087         */
088        public static String removeSetter( final String target ) {
089                if( target != null && target.startsWith( "set" ) ) {
090                        char[] chs = target.toCharArray();
091                        if( chs.length > 4 && !( chs[4] >= 'A' && chs[4] <= 'Z' ) ) {
092                                chs[3] = Character.toLowerCase( chs[3] ) ;
093                        }
094                        return new String( chs,3,chs.length-3 );
095                }
096                return target;
097        }
098
099        /**
100         * オプション配列文字列より、指定のキーに対応するオプション値を返します。
101         *
102         * @param       key             キー
103         * @param       options オプション配列文字列
104         *
105         * @return      オプション文字列
106         */
107        public static String getOption( final String key , final String[][] options ) {
108                String rtn = "";
109                if( key == null || options == null ) { return rtn; }
110
111                for( int i=0; i<options.length; i++ ) {
112                        if( key.equalsIgnoreCase( options[i][0] ) ) {
113                                rtn = options[i][1];
114                                break ;
115                        }
116                }
117                return rtn ;
118        }
119
120        /**
121         * {&#064;og.value package.class#field} 形式のvalueタグを文字列に置き換えます。
122         *
123         * 処理的には、リフレクションで、値を取得します。値は、staticフィールドのみ取得可能です。
124         *
125         * @og.rev 5.5.4.1 (2012/07/06) 新規追加
126         * @og.rev 5.5.5.6 (2012/08/31) クラス名の取得で、ProgramElementDoc で処理するように変更
127         *
128         * @param tag Tagオブジェクト
129         *
130         * @return valueタグの解析結果の文字列
131         */
132        public static String valueTag( final Tag tag ) {
133                return valueTag( tag.text() , tag );
134        }
135
136        /**
137         * {&#064;og.value package.class#field} 形式のvalueタグを文字列に置き換えます。
138         *
139         * 処理的には、リフレクションで、値を取得します。値は、staticフィールドのみ取得可能です。
140         *
141         * @og.rev 5.5.4.1 (2012/07/06) 新規追加
142         * @og.rev 5.5.5.6 (2012/08/31) クラス名の取得で、ProgramElementDoc で処理するように変更
143         * @og.rev 6.3.5.1 (2015/08/12) HybsSystem とSystemData のみパッケージ無しでも処理できるように対応
144         *
145         * @param       txt     Tagテキスト (og.value 以下の文字列のみ)
146         * @param       tag     Tagオブジェクト (正確な class 名を求める場合に使用)
147         *
148         * @return valueタグの解析結果の文字列
149         */
150        public static String valueTag( final String txt , final Tag tag ) {
151                String text = txt;
152                if( txt != null ) {
153                        String cls = null;              // package.class
154                        String fld = null;              // field
155                        // package.class#field 形式の解析。
156                        final int adrs = txt.indexOf('#') ;
157                        if( adrs > 0 ) {
158                                cls = txt.substring( 0,adrs );          // package.class
159                                fld = txt.substring( adrs+1 );          // field
160                        }
161                        else if( adrs == 0 ) {
162                                fld = txt.substring( 1 );                       // #field
163                        }
164                        else {
165                                final String errMsg = "警告:{@value package.class#field} 形式のフィールド名 #field がありません。" + CR
166                                                                + txt + CR ;
167                                System.err.println( errMsg );
168                                // # を付け忘れたと考え、自分自身のクラスを利用
169                                fld = txt;                                                      // field
170                        }
171
172                        // package.class をきちんと作成する。
173                        final Doc doc = tag.holder();
174
175                        // 5.5.5.6 (2012/08/31) ProgramElementDoc で処理するように変更
176                        if( doc instanceof ProgramElementDoc ) {
177                                final ProgramElementDoc pdoc = (ProgramElementDoc)doc;
178                                final ClassDoc cdoc = pdoc.containingClass();
179                                // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..;
180                                if( cdoc == null ) {
181                                        if( cls == null ) {                                     // package.class が登録されていない場合。
182                                                cls = pdoc.qualifiedName();
183                                        }
184                                        else if( cls.indexOf('.') < 0 ) {
185                                                // 6.3.5.1 (2015/08/12) HybsSystem とSystemData のみパッケージ無しでも処理できるように対応
186                                                if( "HybsSystem".equals( cls ) || "SystemData".equals( cls ) ) {
187                                                        cls = "org.opengion.hayabusa.common." + cls ;
188                                                }
189                                                else {                                                                          // 同一パッケージ内と仮定
190                                                        final String tmp = pdoc.qualifiedName();
191                                                        final int ad = tmp.lastIndexOf('.');
192                                                        if( ad > 0 ) {
193                                                                cls = tmp.substring( 0,ad+1 ) + cls ;
194                                                        }
195                                                }
196                                        }
197                                }
198                                else {
199                                        if( cls == null ) {                                     // package.class が登録されていない場合。
200                                                cls = cdoc.qualifiedName() ;
201                                        }
202                                        else if( cls.indexOf('.') < 0 ) {       // class のみが登録されている場合。findClass で、package 込の正式クラス名を検索する。
203                                                final ClassDoc fdoc = cdoc.findClass( cls );
204                                                if( fdoc != null ) {
205                                                        cls = fdoc.qualifiedName() ;
206                                                }
207                                        }
208                                }
209                        }
210
211                        // 6.3.5.1 (2015/08/12) HybsSystem とSystemData のみパッケージ無しでも処理できるように対応
212                        if( "HybsSystem".equals( cls ) || "SystemData".equals( cls ) ) {
213                                cls = "org.opengion.hayabusa.common." + cls ;
214                        }
215
216                        //  5.6.3.3 (2013/04/19) メソッド化で共有します。
217                        text = getStaticField( cls,fld );
218                }
219                return text ;
220        }
221
222        /**
223         * {&#064;og.doc03Link queryType Query_**** クラス} 形式のdoc03Linkタグをリンク文字列に置き換えます。
224         *
225         * <a href="/gf/jsp/DOC03/index.jsp?command=NEW&map;GAMENID=DOC03&amp;VERNO=X.X.X.X&amp;VALUENAME=queryType" target="CONTENTS">Query_**** クラス</a>
226         * のようなリンクを作成します。
227         * 第一引数は、VALUENAME の引数です。
228         * それ以降のテキストは、リンク文字列のドキュメントになります。
229         * DOC03 画面へのリンクを作成するに当たり、バージョンが必要です。org.opengion.fukurou.system.BuildNumber#VERSION_NO から取得しますが、
230         * パッケージの優先順の関係で、リフレクションを使用します。
231         *
232         * @og.rev 5.6.3.3 (2013/04/19) 新規作成
233         * @og.rev 6.0.2.0 (2014/09/19) 処理を #doc03LinkTag( String ) で行う。
234         *
235         * @param tag Tagオブジェクト
236         *
237         * @return valueタグの解析結果の文字列
238         * @og.rtnNotNull
239         */
240        public static String doc03LinkTag( final Tag tag ) {
241                return doc03LinkTag( tag.text() );
242        }
243
244        /**
245         * {&#064;og.doc03Link queryType Query_****クラス} 形式のdoc03Linkタグをリンク文字列に置き換えます。
246         *
247         * &lt;a href="/gf/jsp/DOC03/index.jsp?command=NEW&amp;GAMENID=DOC03&amp;VERNO=X.X.X.X&amp;VALUENAME=queryType"
248         *          target="CONTENTS" &gt;Query_****クラス&lt;/a&gt;
249         * のようなリンクを作成します。
250         * 第一引数は、VALUENAME の引数です。
251         * それ以降のテキストは、リンク文字列のドキュメントになります。
252         * DOC03 画面へのリンクを作成するに当たり、バージョンが必要です。
253         * org.opengion.fukurou.system.BuildNumber#VERSION_NO から取得しますが、
254         * パッケージの優先順の関係で、リフレクションを使用します。
255         *
256         * @og.rev 6.0.2.0 (2014/09/19) 新規追加
257         *
258         * @param txt Tagテキスト(og.doc03Link 以下の文字列のみ)
259         *
260         * @return valueタグの解析結果の文字列
261         * @og.rtnNotNull
262         */
263        public static String doc03LinkTag( final String txt ) {
264                if( txt != null ) {
265                        final String text = txt.trim();
266
267                        final int adrs = text.indexOf(' ') ;                    // 最初のスペースで分離します。
268                        if( adrs > 0 ) {
269                                final String valnm = text.substring( 0,adrs ).trim();   // VALUENAME
270                                final String body  = text.substring( adrs+1 ).trim();   // ドキュメント
271
272                                return "&lt;a href=\"/gf/jsp/DOC03/index.jsp?command=NEW&amp;GAMENID=DOC03"
273                                                + "&amp;VERNO="     + VERSION_NO
274                                                + "&amp;VALUENAME=" + valnm
275                                                + "\" target=\"CONTENTS\"&gt;"
276                                                + body
277                                                + "&lt;/a&gt;" ;
278                        }
279                }
280                return "doc03Link 不明:" + txt ;
281        }
282
283        /**
284         * このタグレットがインラインタグで {&#064;link XXXX YYYY} を処理するように
285         * 用意された、カスタムメソッドです。
286         *
287         * @og.rev 6.0.2.0 (2014/09/19) 新規追加
288         *
289         * @param       txt     オリジナルの文字列
290         *
291         * @return      インラインタグの link を処理した結果の文字列
292         * @og.rtnNotNull
293         */
294        public static String linkTag( final String txt ) {
295                // {@link XXXX YY} の XXXX YY部分を処理
296                if( txt != null ) {
297                        final String text = txt.trim() ;
298
299                        final int adrs = text.indexOf( ' ' );
300                        if( adrs > 0 ) {
301                                final String xxx = text.substring( 0,adrs ).trim();     // 前半:アドレス変換
302                                final String yyy = text.substring( adrs   ).trim();     // 後半:ラベル
303                                final String zzz = xxx.replace( '.','/' );
304
305                                return "<a href=\"../../../../" + zzz + ".html\" "
306                                                        + "title=\"" + xxx + "\">" + yyy + "</a>" ;
307                        }
308                }
309
310                return "";
311        }
312
313        /**
314         * パッケージ.クラス名 と、フィールド名 から、staticフィールドの値を取得します。
315         *
316         * Field fldObj = Class.forName( cls ).getDeclaredField( fld ); で、Fieldオブジェクトを呼出し、
317         * String.valueOf( fldObj.get( null ) ); で、値を取得しています。
318         * static フィールドは、引数 null で値を取得できます。
319         *
320         * ※ 超特殊処理として、cls名が、HybsSystem とSystemData のみ、パッケージ無しで処理
321         *    できるように対応します。
322         *
323         * 例;
324     *      String cls = "org.opengion.fukurou.system.BuildNumber";        // package.class
325     *      String fld = "VERSION_NO";                                      // field
326         *
327         * @og.rev 5.6.3.3 (2013/04/19) 新規作成
328         * @og.rev 6.4.2.0 (2016/01/29) StringUtil#ogStackTrace(Throwable) を、ThrowUtil#ogStackTrace(String,Throwable) に置き換え。
329         *
330         * @param cls パッケージ.クラス名
331         * @param fld フィールド名
332         * @return 取得値
333         */
334        public static String getStaticField( final String cls , final String fld ) {
335                String txt = null;
336                try {
337                        final Field fldObj = Class.forName( cls ).getDeclaredField( fld );
338                        // privateフィールドへのアクセス。(セキュリティーマネージャーによってアクセス制限がかけられていない場合)
339                        // 6.1.0.0 (2014/12/26) findBugs: Bug type DP_DO_INSIDE_DO_PRIVILEGED (click for details)
340                        // reflect.Field.setAccessible(boolean) の呼び出しは doPrivileged ブロックの中から呼び出すべきです。
341                        if( !fldObj.isAccessible() ) {
342                                AccessController.doPrivileged( new PrivilegedAction<DocletUtil>() {
343                                        /**
344                                         * 特権を有効にして実行する PrivilegedAction<T> の run() メソッドです。
345                                         *
346                                         * このメソッドは、特権を有効にしたあとに AccessController.doPrivileged によって呼び出されます。
347                                         *
348                                         * @return  DocletUtilオブジェクト
349                                         */
350                                        public DocletUtil run() {
351                                                // privileged code goes here
352                                                fldObj.setAccessible( true );
353                                                return null; // nothing to return
354                                        }
355                                });
356                        }
357                        txt = String.valueOf( fldObj.get( null ) );             // static フィールドは、引数 null で値を取得
358                }
359                // 6.0.2.5 (2014/10/31) findBugs:Bug type REC_CATCH_EXCEPTION 対応
360                catch( final Throwable th ) {
361                        // 6.4.2.0 (2016/01/29) StringUtil#ogStackTrace(Throwable) を、ThrowUtil#ogStackTrace(String,Throwable) に置き換え。
362                        final String errMsg = "package.class=[" + cls + "],field=[" + fld + "] の取得に失敗しました。" + CR ;
363                        System.err.println( ThrowUtil.ogStackTrace( errMsg , th ) );
364                }
365
366                return txt ;
367        }
368}