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.hayabusa.taglib;
017
018import org.opengion.hayabusa.common.HybsSystem;
019import org.opengion.hayabusa.common.HybsSystemException;
020import org.opengion.fukurou.util.XHTMLTag;
021import org.opengion.fukurou.util.Attributes;
022import org.opengion.fukurou.util.ToString;                                              // 6.1.1.0 (2015/01/17)
023import org.opengion.fukurou.util.ArraySet;                                              // 6.4.3.4 (2016/03/11)
024
025import static org.opengion.fukurou.util.StringUtil.nval ;
026
027import java.io.File;
028import java.io.FileFilter;
029import java.io.Serializable;
030import java.util.Set;                                                                                   // 6.4.3.4 (2016/03/11)
031import java.util.Arrays;
032import java.util.Comparator;
033
034/**
035 * ファイルのプルダウンリストの作成するタグです。
036 *
037 * SelectタグのBODY部に指定します。
038 * 並び替えについては、このタグで指定しますが、ファイルの選別は、
039 * BODY 部に記述する fileWhere タグで指定します。
040 *
041 * @og.formSample
042 * ●形式:<og:fileOption from="…" value="[…]" ・・・ >・・・</og:fileOption>
043 * ●body:あり(EVAL_BODY_BUFFERED:BODYを評価し、{@XXXX} を解析します)
044 *
045 * ●Tag定義:
046 *   <og:fileOption
047 *       from               【TAG】ファイルの検索元となるディレクトリを指定します (初期値:FILE_URL[=filetemp/])
048 *       value              【TAG】Optionの初期値で選ばれる値を指定します
049 *       useDir             【TAG】optionリストの作成を、ディレクトリの値で行います。
050 *       groupDir           【TAG】optgroupを、ディレクトリの値で作成します(1レベルのみ)。
051 *       orderBy            【TAG】検索した結果を表示する表示順をファイル属性名で指定します(初期値:自然順序)
052 *       desc               【TAG】表示順を逆転するかどうか[true/false]を指定します(初期値:false)
053 *       caseKey            【TAG】このタグ自体を利用するかどうかの条件キーを指定します(初期値:null) 6.8.0.0 (2017/06/02)
054 *       caseVal            【TAG】このタグ自体を利用するかどうかの条件値を指定します(初期値:null) 6.8.0.0 (2017/06/02)
055 *       caseNN             【TAG】指定の値が、null/ゼロ文字列 でない場合(Not Null=NN)は、このタグは使用されます(初期値:判定しない) 6.8.0.0 (2017/06/02)
056 *       caseNull           【TAG】指定の値が、null/ゼロ文字列 の場合は、このタグは使用されます(初期値:判定しない) 6.8.0.0 (2017/06/02)
057 *       caseIf             【TAG】指定の値が、true/TRUE文字列の場合は、このタグは使用されます(初期値:判定しない) 6.8.0.0 (2017/06/02)
058 *       debug              【TAG】デバッグ情報を出力するかどうか[true/false]を指定します(初期値:false)
059 *   >   ... Body ...
060 *   </og:fileOption>
061 *
062 * ●使用例
063 *      ・<og:fileOption val1="ABCD" val2="{@value}" >
064 *            <og:fileWhere startsWith="ABCD" ・・・ />
065 *        </og:fileOption>
066 *
067 * @og.rev 2.1.1.0 (2002/11/11) 新規作成
068 * @og.rev 4.0.0.0 (2005/01/31) 内部ロジック改定
069 * @og.group その他入力
070 *
071 * @version  4.0
072 * @author   Kazuhiko Hasegawa
073 * @since    JDK5.0,
074 */
075public class FileOptionTag extends CommonTagSupport {
076        /** このプログラムのVERSION文字列を設定します。   {@value} */
077        private static final String VERSION = "6.8.0.0 (2017/06/02)" ;
078        private static final long serialVersionUID = 680020170602L ;
079
080        private String          orderBy         ;                               // ソート項目
081        private boolean         useDir          ;                               // 6.3.4.0 (2015/08/01) 
082        private boolean         groupDir        ;                               // 6.3.4.0 (2015/08/01) 
083        private boolean         desc            ;                               // 降順フラグ
084        private String      from                = HybsSystem.sys( "FILE_URL" ); // 検索起点ファイル
085        private String          selValue        ;                               // 選択済み初期値にする場合
086        private transient       FileFilter      filter  ;               // FileWhere で指定したフィルター
087
088        // 6.4.3.4 (2016/03/11) String配列 から、Setに置き換えます。
089        private static final Set<String> ORDER_BY_SET = new ArraySet<>( "NAME","LASTMODIFIED","FILE_LENGTH","LENGTH" );
090
091        /**
092         * デフォルトコンストラクター
093         *
094         * @og.rev 6.4.2.0 (2016/01/29) PMD refactoring. Each class should declare at least one constructor.
095         */
096        public FileOptionTag() { super(); }             // これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。
097
098        /**
099         * Taglibの開始タグが見つかったときに処理する doStartTag() を オーバーライドします。
100         *
101         * @og.rev 6.8.0.0 (2017/06/02) caseKey,caseVal,caseNN,caseNull 属性を追加
102         *
103         * @return      後続処理の指示( EVAL_BODY_BUFFERED )
104         */
105        @Override
106        public int doStartTag() {
107                // 6.8.0.0 (2017/06/02) caseKey,caseVal,caseNN,caseNull 属性を追加
108                return useTag() ? EVAL_BODY_BUFFERED : SKIP_BODY ;
109        }
110
111        /**
112         * Taglibのタグ本体を処理する doAfterBody() を オーバーライドします。
113         *
114         * @return      後続処理の指示(SKIP_BODY)
115         */
116        @Override
117        public int doAfterBody() {
118                return SKIP_BODY ;
119        }
120
121        /**
122         * Taglibの終了タグが見つかったときに処理する doEndTag() を オーバーライドします。
123         *
124         * @og.rev 3.1.1.2 (2003/04/04) Tomcat4.1 対応。release2() を doEndTag()で呼ぶ。
125         * @og.rev 6.3.4.0 (2015/08/01) useDir 属性と、groupDir 属性 を追加します。
126         * @og.rev 6.8.0.0 (2017/06/02) caseKey,caseVal,caseNN,caseNull 属性を追加
127         *
128         * @return      後続処理の指示
129         */
130        @Override
131        public int doEndTag() {
132                debugPrint();           // 4.0.0 (2005/02/28)
133                // 6.8.0.0 (2017/06/02) caseKey,caseVal,caseNN,caseNull 属性を追加
134                if( useTag() ) {
135                        final OptionAncestorIF select = (OptionAncestorIF)findAncestorWithClass( this, OptionAncestorIF.class );
136                        if( select == null ) {
137                                final String errMsg = "<b>" + getTagName() + "タグは、SelectTag または、DatalistTag のBODY に記述する必要があります。</b>";
138                                throw new HybsSystemException( errMsg );
139                        }
140                        final Comparator<File> comp = makeComparator( orderBy,desc );
141        //              makeLabel( select,comp );
142                        makeLabel( new File( from ) , select , comp , groupDir );
143                }
144
145                return EVAL_PAGE ;
146        }
147
148        /**
149         * タグリブオブジェクトをリリースします。
150         * キャッシュされて再利用されるので、フィールドの初期設定を行います。
151         *
152         * @og.rev 3.1.1.2 (2003/04/04) Tomcat4.1 対応。release2() を doEndTag()で呼ぶ。
153         * @og.rev 6.3.4.0 (2015/08/01) useDir 属性と、groupDir 属性 を追加します。
154         */
155        @Override
156        protected void release2() {
157                super.release2();
158                orderBy         = null;         // ソート項目
159                useDir          = false;        // 6.3.4.0 (2015/08/01) 
160                groupDir        = false;        // 6.3.4.0 (2015/08/01) 
161                desc            = false;        // 降順フラグ
162                from            = HybsSystem.sys( "FILE_URL" );
163                filter          = null;
164                selValue        = null;
165        }
166
167        /**
168         * オプションを作成します。
169         *
170         * ファイル名を "value" に、
171         * BODY属性 に登録するOptionを作成します。
172         *
173         * @og.rev 5.3.4.0 (2011/04/01) FILE_LENGTH 追加
174         *
175         * @param       orderBy ソートする属性 [NAME/LASTMODIFIED/FILE_LENGTH/LENGTH]
176         * @param       desc    並び順 [true:昇順/false:降順]
177         *
178         * @return      ファイル比較用のComparatorオブジェクト
179         */
180        private Comparator<File> makeComparator( final String orderBy,final boolean desc ) {
181                if( orderBy == null ) { return null; }
182
183                Comparator<File> comp = null ;
184
185                if( "NAME".equalsIgnoreCase( orderBy ) ) {
186                        comp = new NameComparator( desc );
187                }
188                else if( "LASTMODIFIED".equalsIgnoreCase( orderBy ) ) {
189                        comp = new ModifiedComparator( desc );
190                }
191                // "LENGTH" を残すのは、互換性のため
192                else if( "FILE_LENGTH".equalsIgnoreCase( orderBy ) || "LENGTH".equalsIgnoreCase( orderBy ) ) {
193                        comp = new LengthComparator( desc );
194                }
195
196                return comp ;
197        }
198
199        /**
200         * オプションを作成します。
201         *
202         * ファイル名を "value" と、BODY属性 に登録するOptionを作成します。
203         *
204         * @og.rev 3.8.0.9 (2005/10/17) 複数選択可能時に全選択を設定する。
205         * @og.rev 6.3.4.0 (2015/08/01) useDir 属性と、groupDir 属性 を追加します。
206         * @og.rev 6.8.0.0 (2017/06/02) フォルダ内のファイルが無い場合は、階層に加えない。
207         *
208         * @param       path    処理の開始パス名(ディレクトリ)
209         * @param       select  SelectTagオブジェクト
210         * @param       comp    並び順を指定するためのComparatorオブジェクト
211         * @param       grpDir  フォルダ階層を、optgroup タグで表すかどうか [true:階層/false:1層]
212         * @return      フォルダ内のファイルが無い場合は階層に加えないためのフラグ [true:階層/false:ファイルなし]
213         */
214        private boolean makeLabel( final File path , final OptionAncestorIF select , final Comparator<File> comp , final boolean grpDir ) {
215
216                final File[] list = path.listFiles( filter );
217
218                final boolean multipleAll = select.isMultipleAll();             // 3.8.0.9 (2005/10/17)
219                if( list != null )  {
220                        Arrays.sort( list, comp );
221                        boolean isSet = false;                                                          // 6.8.0.0 (2017/06/02)
222                        for( int i=0; i<list.length; i++ ) {
223                                final String value = list[i].getName();
224                                // 6.3.4.0 (2015/08/01) useDir 属性と、groupDir 属性 を追加
225                                if( grpDir && list[i].isDirectory() ) {
226                                        select.addOption( "<optgroup label=\"" + value + "\">" );
227                                        if( makeLabel( list[i] , select , comp , false ) ) {    // リストに追加されたかどうかを判定します。
228                                                select.addOption( "</optgroup>" );
229                                                isSet = true;                                                           // 6.8.0.0 (2017/06/02)
230                                        }
231                                        else {
232                                                select.removeLast();                                            // 6.8.0.0 (2017/06/02) リストに追加されなかった場合は、最後に追加した optgroup を削除します。
233                                        }
234
235                                        continue;
236                                }
237                                else {
238                                        // ディレクトリ時のuseDir=true と、ファイル時useDir=false でない場合( XOR )時は、処理しない。
239                                        if( list[i].isDirectory() ^ useDir ) { continue; }
240                                }
241
242                                // 6.1.1.0 (2015/01/17) Attributesの連結記述
243                                final String selected = ( selValue != null && selValue.equalsIgnoreCase( value ) ) || multipleAll
244                                                                                        ? "selected" : null ;
245
246                                select.addOption(
247                                        XHTMLTag.option(
248                                                new Attributes()
249                                                        .set( "value"   , value )
250                                                        .set( "selected", selected )
251                                                        .set( "body"    , value )
252                                        )
253                                );
254                        }
255                        return isSet;   // 6.8.0.0 (2017/06/02)
256                }
257                return false;           // 6.8.0.0 (2017/06/02) フォルダ内のファイルが無い場合は、階層に加えない。
258        }
259
260        /**
261         * 【TAG】Optionの初期値で選ばれる値を指定します。
262         *
263         * @og.tag
264         * キーになるのは、ファイル属性の NAME です。(ディレクトリなしのファイル名)
265         * ここで value属性に指定した場合、このファイル名と(大文字小文字を無視して)
266         * 一致する場合に、プルダウンの初期値に表示されます。(selected 属性が設定される。)
267         *
268         * @param   val  初期値で選ばれる値
269         */
270        public void setValue( final String val ) {
271                selValue = nval( getRequestParameter( val ),selValue );
272        }
273
274        /**
275         * 【TAG】optionリストの作成を、ディレクトリの値で行います(初期値:false)。
276         *
277         * @og.tag
278         * ファイル検索で、ディレクトリ名のリストで、オプションを作成します。
279         * 初期値は、false (ファイル名でリスト) です。
280         *
281         * @og.rev 6.3.4.0 (2015/08/01) useDir 属性の追加
282         *
283         * @param       flag ディレクトリ名のリストで、オプションを作成するかどうか [true:ディレクトリ/false:ファイル]
284         */
285        public void setUseDir( final String flag ) {
286                useDir = nval( getRequestParameter( flag ),useDir );
287        }
288
289        /**
290         * 【TAG】optgroupを、ディレクトリの値で作成します(1レベルのみ)(初期値:false)。
291         *
292         * @og.tag
293         * optgroupをディレクトリで作成することで、階層のメニューを作成します。
294         * 初期値は、false(通常) です。
295         *
296         * @og.rev 6.3.4.0 (2015/08/01) groupDir 属性の追加
297         *
298         * @param       flag ディレクトリで階層メニューを作成するかどうか [true:階層/false:通常]
299         */
300        public void setGroupDir( final String flag ) {
301                groupDir = nval( getRequestParameter( flag ),groupDir );
302        }
303
304        /**
305         * 【TAG】ファイルの検索元となるディレクトリを指定します
306         *              (初期値:FILE_URL[={@og.value SystemData#FILE_URL}])。
307         *
308         * @og.tag ファイルの検索元となるディレクトリを指定します。
309         * (初期値:システム定数のFILE_URL[={@og.value SystemData#FILE_URL}])。
310         *
311         * @og.rev 4.0.0.0 (2007/11/20) 指定されたディレクトリ名の最後が"\"or"/"で終わっていない場合に、"/"を付加する。
312         * @og.rev 6.4.2.1 (2016/02/05) URLの最後に、"/" を追加する処理を廃止。
313         * @og.rev 6.4.2.1 (2016/02/05) HybsSystem.url2dir に引数追加。
314         *
315         * @param       url ファイルの検索元となるディレクトリ
316         * @see         org.opengion.hayabusa.common.SystemData#FILE_URL
317         */
318        public void setFrom( final String url ) {
319                final String furl = nval( getRequestParameter( url ),null );
320                from = HybsSystem.url2dir( from,furl,"." );                     // 6.4.2.1 (2016/02/05)
321        }
322
323        /**
324         * 【TAG】検索した結果を表示する表示順をファイル属性名[null/NAME/LASTMODIFIED/FILE_LENGTH]で指定します(初期値:自然順序)。
325         *
326         * @og.tag
327         * ファイルをソートする順(Comparator)を指定します。ソートに指定できる
328         * ファイル属性名は、"NAME","LASTMODIFIED","FILE_LENGTH" の内のどれかひとつです。
329         * 何も指定しない場合は、Fileオブジェクトの自然順序でのソートになります。
330         * (※ 下位互換性のため、LENGTH も残しますが、廃止予定です。)
331         *
332         * @og.rev 3.5.6.2 (2004/07/05) 文字列の連結にStringBuilderを使用します。
333         * @og.rev 4.0.0.0 (2005/01/31) 新規ロジックで改定
334         * @og.rev 5.3.4.0 (2011/04/01) ORDER_BYリストの出力方法 見直し
335         * @og.rev 6.3.4.0 (2015/08/01) Arrays.toString から String.join に置き換え。
336         * @og.rev 6.4.3.4 (2016/03/11) String配列 から、Setに置き換えます。
337         *
338         * @param       ordr  ソートキー [null/NAME/LASTMODIFIED/FILE_LENGTH]
339         */
340        public void setOrderBy( final String ordr ) {
341                orderBy = nval( getRequestParameter( ordr ),orderBy );
342
343                if( orderBy != null && ! check( orderBy, ORDER_BY_SET ) ) {
344                        final String errMsg = "orderBy 属性に、下記の属性名以外の値が設定されました。" + CR
345                                                        + "orderBy=[" + orderBy + "] "                                                          + CR
346                                                        + "orderBy List=" + String.join( ", " , ORDER_BY_SET ) ;
347                        throw new HybsSystemException( errMsg );
348
349                }
350        }
351
352        /**
353         * 【TAG】表示順を逆転するかどうか[true/false]を指定します(初期値:false)。
354         *
355         * @og.tag
356         * orderBy 属性で指定した表示順を、逆順にするかどうかを指定できます。
357         * 初期値は、false (昇順) です。
358         *
359         * @param       flag 表示順を逆転するかどうか [true:逆順/false:昇順]
360         */
361        public void setDesc( final String flag ) {
362                desc = nval( getRequestParameter( flag ),desc );
363        }
364
365        /**
366         * FileFilterオブジェクトをセットします。
367         * これは、BODY 部に登録した、FileWhereタグによって設定された
368         * ファイルフィルターです。
369         *
370         * @param       filter  オブジェクト
371         */
372        protected void setFileFilter( final FileFilter filter ) {
373                this.filter = filter;
374        }
375
376        /**
377         * 名前順でのソート順を指定する Comparator の実体内部クラス
378         *
379         * @og.rev 6.3.9.1 (2015/11/27) 修飾子を、なし → private static final class に変更。
380         *
381         * @version  4.0
382         * @author   Kazuhiko Hasegawa
383         * @since    JDK5.0,
384         */
385        private static final class NameComparator implements Comparator<File>,Serializable {
386                private static final long serialVersionUID = 400020050131L ;    // 4.0.0.0 (2005/01/31)
387
388                private final boolean desc ;
389
390                /**
391                 * 名前順での比較を行うオブジェクトを作成します。
392                 *
393                 * @param desc 表示順逆転 [true:昇順/false:降順]
394                 */
395                public NameComparator( final boolean desc ) { this.desc = desc; }
396
397                /**
398                 * Comparator インターフェースの compare( File,File ) メソッド。
399                 *
400                 * @param o1 比較元1のファイルオブジェクト
401                 * @param o2 比較元2のファイルオブジェクト
402                 * @return      最初の引数が 2 番目の引数より小さい場合は負の整数、両方が等しい場合は 0、最初の引数が 2 番目の引数より大きい場合は正の整数
403                 */
404                public int compare( final File o1, final File o2 ) {
405                        final File f1 = desc ? o2 : o1 ;
406                        final File f2 = desc ? o1 : o2 ;
407                        return f1.getName().compareTo( f2.getName() ) ;
408                }
409        }
410
411        /**
412         * 更新日順でのソート順を指定する Comparator の実体内部クラス
413         *
414         * @og.rev 6.3.9.1 (2015/11/27) 修飾子を、なし → private static final class に変更。
415         *
416         * @version  4.0
417         * @author   Kazuhiko Hasegawa
418         * @since    JDK5.0,
419         */
420        private static final class ModifiedComparator implements Comparator<File>,Serializable {
421                private static final long serialVersionUID = 400020050131L ;    // 4.0.0.0 (2005/01/31)
422
423                private final boolean desc ;
424
425                /**
426                 * 更新日順での比較を行うオブジェクトを作成します。
427                 *
428                 * @param desc 表示順逆順 [true:昇順/false:降順]
429                 */
430                public ModifiedComparator( final boolean desc ) { this.desc = desc; }
431
432                /**
433                 * Comparator インターフェースの compare( File,File ) メソッド。
434                 *
435                 * @param o1 比較元1のファイルオブジェクト
436                 * @param o2 比較元2のファイルオブジェクト
437                 * @return      最初の引数が 2 番目の引数より小さい場合は負の整数、両方が等しい場合は 0、最初の引数が 2 番目の引数より大きい場合は正の整数
438                 */
439                public int compare( final File o1, final File o2 ) {
440                        final File f1 = desc ? o2 : o1 ;
441                        final File f2 = desc ? o1 : o2 ;
442                        return (int)( f1.lastModified() - f2.lastModified() ) ;
443                }
444        }
445
446        /**
447         * ファイルサイズ順でのソート順を指定する Comparator の実体内部クラス
448         *
449         * @og.rev 6.3.9.1 (2015/11/27) 修飾子を、なし → private static final class に変更。
450         *
451         * @version  4.0
452         * @author   Kazuhiko Hasegawa
453         * @since    JDK5.0,
454         */
455        private static final class LengthComparator implements Comparator<File>,Serializable {
456                private static final long serialVersionUID = 400020050131L ;    // 4.0.0.0 (2005/01/31)
457
458                private final boolean desc ;
459
460                /**
461                 * ファイルサイズでの比較を行うオブジェクトを作成します。
462                 *
463                 * @param desc 表示順逆順 [true:昇順/false:降順]
464                 */
465                public LengthComparator( final boolean desc ) { this.desc = desc; }
466
467                /**
468                 * Comparator インターフェースの compare( File,File ) メソッド。
469                 *
470                 * @param o1 比較元1のファイルオブジェクト
471                 * @param o2 比較元2のファイルオブジェクト
472                 * @return      最初の引数が 2 番目の引数より小さい場合は負の整数、両方が等しい場合は 0、最初の引数が 2 番目の引数より大きい場合は正の整数
473                 */
474                public int compare( final File o1, final File o2 ) {
475                        final File f1 = desc ? o2 : o1 ;
476                        final File f2 = desc ? o1 : o2 ;
477                        return (int)( f1.length() - f2.length() ) ;
478                }
479        }
480
481        /**
482         * このオブジェクトの文字列表現を返します。
483         * 基本的にデバッグ目的に使用します。
484         *
485         * @return このクラスの文字列表現
486         * @og.rtnNotNull
487         */
488        @Override
489        public String toString() {
490                return ToString.title( this.getClass().getName() )
491                                .println( "VERSION"             ,VERSION        )
492                                .println( "orderBy"             ,orderBy        )
493                                .println( "desc"                ,desc           )
494                                .println( "from"                ,from           )
495                                .println( "selValue"    ,selValue       )
496                                .println( "Other..."    ,getAttributes().getAttribute() )
497                                .fixForm().toString() ;
498        }
499}