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.util;
017
018import java.io.File;
019import java.util.concurrent.ConcurrentMap;                                                      // 6.4.3.3 (2016/03/04)
020import java.util.concurrent.ConcurrentHashMap;                          // 6.4.3.1 (2016/02/12) refactoring
021import java.util.Locale ;
022import java.util.Set;
023
024import org.opengion.fukurou.system.ThrowUtil;                           // 6.4.2.0 (2016/01/29) package変更 fukurou.util → fukurou.system
025
026/**
027 * FileMap は、ファイルを読み取って、キー情報から、ファイルへのリンクを作成するための
028 * 情報を返します。
029 * ファイルそのものは、指定のディレクトリをすべて読み取り、拡張子以外の部分を、キーとして
030 * 登録します。(キーは大文字に統一されます。)
031 * 実際のファイルの拡張子は、リンク作成時の処理で付与されます。
032 * 例えば、HELPファイルを、XXXX.html や、XXXX.htm 、XXXX.pdf など、色々な形態で作成した
033 * 場合でも、キーとしては、XXXX で存在チェックをかけることができるようになります。
034 *
035 * ファイルは、一旦すべて読み取ってメモリ上で管理されます。
036 * ディレクトリの再読取が必要な場合は、オブジェクトを再作成する必要があります。
037 *
038 * @version  4.0
039 * @author   Kazuhiko Hasegawa
040 * @since    JDK5.0,
041 */
042public final class FileMap implements Cleanable {
043        /** 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。  */
044        private final ConcurrentMap<String,String> fMap = new ConcurrentHashMap<>();                            // 6.4.3.1 (2016/02/12) 変数名も変えておきます。
045
046        /**
047         * デフォルトコンストラクター
048         *
049         * @og.rev 6.4.2.0 (2016/01/29) PMD refactoring. Each class should declare at least one constructor.
050         */
051        public FileMap() { super(); }           // これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。
052
053//      /**
054//       * 読み取るディレクトリを指定して、ファイルマップを構築します。
055//       *
056//       * このディレクトリは、OSに対する物理アドレスになります。
057//       *
058//       * @og.rev 5.5.4.2 (2012/07/13) makeFileMap() を直接コンストラクターとして使用
059//       * @og.rev 6.3.8.4 (2015/10/09) 別のコンストラクターを呼ぶようにします。
060//       * @og.rev 6.3.8.5 (2015/10/16) コンストラクターで、Exception を throw しないようにします。
061//       * @og.rev 6.3.9.0 (2015/11/06) コンストラクターを止めて、初期化メソッドに変更する。
062//       * @og.rev 7.3.2.0 (2021/03/19) 廃止
063//       *
064//       * @param  dir ディレクトリ
065//       */
066//      public void init( final String dir ) {
067//              init( dir , null , null );
068//      }
069
070        /**
071         * 読み取るディレクトリを指定して、ファイルマップを構築します。
072         *
073         * このディレクトリは、OSに対する物理アドレスになります。
074         *
075         * @og.rev 5.5.4.2 (2012/07/13) makeFileMap() を直接コンストラクターとして使用
076         * @og.rev 6.3.8.4 (2015/10/09) 別のコンストラクターを呼ぶようにします。
077         * @og.rev 6.3.8.5 (2015/10/16) コンストラクターで、Exception を throw しないようにします。
078         * @og.rev 6.3.9.0 (2015/11/06) コンストラクターを止めて、初期化メソッドに変更する。
079         *
080         * @param  dir ディレクトリ
081         * @param  path ファイル名に付与するパス文字列
082         */
083        public void init( final String dir , final String path ) {
084                init( dir , path , null );
085        }
086
087//      /**
088//       * すでに読み取った Set オブジェクトを指定して、ファイルマップを構築します。
089//       *
090//       * これは、ServletContext を利用した、META-INF/resources からの読み取り対応になります。
091//       * 一覧を取得するのは、ServletContext 関連の実装が必要になるため、fukurou では
092//       * java の一般的なオブジェクトである Set を処理するだけとします。
093//       *
094//       * ファイル名は、dir を削除した残りで構築します。フォルダ階層を含みます。
095//       * Mapのキーは、フォルダ階層を含まない、ファイル名のみとします。
096//       * つまり、フォルダ階層を持ってリソースを用意しておいても、キーとしては、
097//       * ファイル名のみを使用します。
098//       *
099//       * @og.rev 5.5.4.2 (2012/07/13) 新規作成
100//       * @og.rev 6.3.8.4 (2015/10/09) 別のコンストラクターを呼ぶようにします。
101//       * @og.rev 6.3.8.5 (2015/10/16) コンストラクターで、Exception を throw しないようにします。
102//       * @og.rev 6.3.9.0 (2015/11/06) コンストラクターを止めて、初期化メソッドに変更する。
103//       * @og.rev 7.3.2.0 (2021/03/19) 廃止
104//       *
105//       * @param  dir ディレクトリ
106//       * @param  resourcePaths リソースパス
107//       */
108//      public void init( final String dir , final Set<?> resourcePaths ) {
109//              init( dir , null , resourcePaths );
110//      }
111
112        /**
113         * すでに読み取った Set オブジェクトを指定して、ファイルマップを構築します。
114         *
115         * これは、ServletContext を利用した、META-INF/resources からの読み取り対応になります。
116         * 一覧を取得するのは、ServletContext 関連の実装が必要になるため、fukurou では
117         * java の一般的なオブジェクトである Set を処理するだけとします。
118         *
119         * ファイル名は、dir を削除した残りで構築します。フォルダ階層を含みます。
120         * Mapのキーは、フォルダ階層を含まない、ファイル名のみとします。
121         * つまり、フォルダ階層を持ってリソースを用意しておいても、キーとしては、
122         * ファイル名のみを使用します。
123         *
124         * @og.rev 5.5.4.2 (2012/07/13) 新規作成
125         * @og.rev 6.3.8.4 (2015/10/09) 別のコンストラクターを呼ぶようにします。
126         * @og.rev 6.3.8.5 (2015/10/16) コンストラクターで、Exception を throw しないようにします。
127         * @og.rev 6.3.9.0 (2015/11/06) null になっている可能性があるメソッドの戻り値のnullチェックを追加。
128         * @og.rev 6.3.9.0 (2015/11/06) コンストラクターを止めて、初期化メソッドに変更する。
129         * @og.rev 6.4.2.0 (2016/01/29) StringUtil にあったメソッドを移動するとともに、メソッド名を、ogThrowMsgPrint → ogThrowMsgPrint に変更。
130         * @og.rev 6.4.3.2 (2016/02/19) 指定のフォルダが存在しない場合、作成します。
131         * @og.rev 7.3.2.0 (2021/03/19) dir と path の nullチェック(エラーでも止めない)
132         *
133         * @param  dir ディレクトリ
134         * @param  path ファイル名に付与するパス文字列
135         * @param  resourcePaths リソースパス
136         */
137//      public void init( final String dir , final String path , final Set<?> resourcePaths ) {
138        public void init( final String dir , final String path , final Set<String> resourcePaths ) {
139                // 7.3.2.0 (2021/03/19) dir と path の nullチェック(エラーでも止めない)
140                if( dir == null || path == null ) {
141                        final String errMsg1 = "指定のディレクトリかパスが、nullです。dir=[" + dir + "] , path=[" + path + "]" ;
142                        System.err.println( ThrowUtil.ogThrowMsg( errMsg1 ) );
143                }
144
145                if( resourcePaths == null ) {
146                        final File directory = new File( dir );
147                        if( ! directory.exists() ) {
148                                final String errMsg = "指定のディレクトリは、存在しません。dir=[" + directory + "] , path=[" + path + "]" ;
149                                // 6.3.8.5 (2015/10/16) コンストラクターで、Exception を throw しないようにします。
150//                              System.out.println( errMsg );
151                                System.err.println( ThrowUtil.ogThrowMsg( errMsg ) );           // 7.3.2.0 (2021/03/19) フォルダなしは、積極的なエラーにする。
152        //                      // 7.3.2.0 (2021/03/19) フォルダを作成する必要はない。あくまでイメージを用意しておいて利用するだけでよい。
153        //                      // 6.4.3.2 (2016/02/19) 指定のフォルダが存在しない場合、作成します。
154        //                      if( directory.mkdirs() ) {
155        //                              final String errMsg2 = "指定のディレクトリを自動作成しました。[" + directory + "]";
156        //                              System.out.println( errMsg2 );
157        //                      }
158        //                      else {
159        //                              final String errMsg3 = "指定のディレクトリの自動作成に失敗しました。[" + directory + "]";
160        //                              System.err.println( ThrowUtil.ogThrowMsg( errMsg3 ) );
161        //                      }
162                                return ;
163                        }
164
165                        if( ! directory.isDirectory() ) {
166                                final String errMsg = "指定のキーは、ディレクトリではありません。[" + directory + "]";
167                                // 6.3.8.5 (2015/10/16) コンストラクターで、Exception を throw しないようにします。
168                                System.err.println( ThrowUtil.ogThrowMsg( errMsg ) );
169                                return ;
170                        }
171                        // 6.3.8.4 (2015/10/09) ファイルのみ取り込む
172                        // 6.3.9.0 (2015/11/06) null になっている可能性があるメソッドの戻り値のnullチェックを追加。
173                        final File[] files = directory.listFiles();
174                        if( files != null ) {
175                                for( final File file : files ) {
176                                        if( file != null && file.isFile() ) {
177                                                dataSet( file.getName() , path );
178                                        }
179                                }
180                        }
181                }
182                else {
183                        final int len = dir.length() ;
184//                      for( final Object rpath : resourcePaths ) {
185//                              final String fname = String.valueOf( rpath ).substring( len );  // ファイル名
186//                              dataSet( fname , path );
187//                      }
188                        for( final String rpath : resourcePaths ) {                     // 7.3.2.0 (2021/03/19)
189                                final String fname = rpath.substring( len );    // ファイル名
190                                dataSet( fname , path );
191                        }
192                }
193        }
194
195        /**
196         * ファイルマップを構築する内部処理。
197         *
198         * これは、ServletContext を利用した、META-INF/resources からの読み取り対応と、
199         * 通常のフォルダスキャンの読み取りの共通処理をまとめた目疎度です。
200         *
201         * @og.rev 6.3.8.4 (2015/10/09) 新規作成
202         * @og.rev 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。変数名も変えておきます。
203         *
204         * @param  fname ファイル名
205         * @param  path  ファイル名に付与するパス文字列
206         */
207        private void dataSet( final String fname , final String path ) {
208                final String upkey = fname.toUpperCase( Locale.JAPAN ) ;
209                String tmpName = fname;
210
211                // path が、nullやゼロ文字列以外の場合は、最後の文字を判定して、ファイル名に連結します。
212                if( path != null && !path.isEmpty() ) {
213                        final char ch = path.charAt( path.length()-1 ) ;
214                        if( ch == '/' || ch == '\\' ) {
215                                tmpName = path + fname;
216                        }
217                        else {
218                                tmpName = path + '/' + fname;
219                        }
220                }
221
222                // 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。変数名も変えておきます。
223                final int idx = upkey.lastIndexOf( '.' );
224                if( idx >= 0 ) {
225                        fMap.put( upkey.substring( 0,idx ), tmpName );
226                }
227                else {
228                        fMap.put( upkey, tmpName );
229                }
230        }
231
232        /**
233         * 指定のキーのファイルが存在しているかどうかを返します。
234         * 存在している場合は、true , 存在していない場合は、false になります。
235         *
236         * @og.rev 6.3.8.5 (2015/10/16) Exception を throw しないようにします。
237         * @og.rev 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。
238         *
239         * @param   key 指定のキー
240         *
241         * @return      存在しているかどうか(true:存在する/false:存在しない)
242         */
243        public boolean exists( final String key ) {
244                // 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。変数名も変えておきます。
245                return key != null && fMap.containsKey( key.toUpperCase( Locale.JAPAN ) );
246        }
247
248        /**
249         * キーに対応したファイル名を返します。
250         * 指定のキーに対するファイル名が存在しない場合は、null を返します。
251         *
252         * 引数を可変長引数にして、前から順番にMapを調べます。最初に、null でない
253         * 値を返します。最後まで一致しなければ、null を返します。
254         * 引数のキーが、nullや、ゼロ配列の場合も、nullになります。
255         *
256         * @og.rev 6.3.8.4 (2015/10/09) FileMap のコンストラクタ変更に伴う対応。
257         * @og.rev 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。
258         *
259         * @param   keys 指定のキー配列(可変長引数)
260         *
261         * @return      ファイル名(ディレクトリパスは含まず)、存在しない場合は、null
262         */
263        public String getFilename( final String... keys ) {
264                if( keys != null ) {
265                        for( final String key : keys ) {
266                                // 6.3.8.4 (2015/10/09) 最初に見つけた値を返す。
267                                if( key != null ) {
268                                        // 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。変数名も変えておきます。
269                                        final String rtn = fMap.get( key.toUpperCase( Locale.JAPAN ) );
270                                        if( rtn != null ) { return rtn; }
271                                }
272                        }
273                }
274                return null;
275        }
276
277        /**
278         * 初期化が完了しているかどうかを、返します。
279         * 完了している場合は、true を返します。未完了、または、clear() 実行後は、falseです。
280         *
281         * インスタンスは、init処理が完了するまでは、false が返る為、簡易的な同期処理は
282         * 行われています。
283         * (内部のMapへの書き込みは、init処理でのみ行われます。)
284         *
285         * @og.rev 6.3.9.0 (2015/11/06) 新規作成。
286         * @og.rev 6.4.3.2 (2016/02/19) initFlagを廃止し、直接 Mapが空かどうかで判定します。
287         *
288         * @return      初期化が完了していればtrue
289         */
290        public boolean isInit() { return !fMap.isEmpty(); }
291
292        /**
293         * 初期化(クリア)します。
294         *
295         * @og.rev 6.3.9.0 (2015/11/06) 新規作成。
296         * @og.rev 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。
297         */
298        @Override       // Cleanable
299        public void clear() {
300                // 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。変数名も変えておきます。
301                fMap.clear();
302        }
303}