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         * @og.rev 8.5.5.1 (2024/02/29) dir のみ nullチェック(エラーで止める)
133         *
134         * @param  dir ディレクトリ
135         * @param  path ファイル名に付与するパス文字列
136         * @param  resourcePaths リソースパス
137         */
138//      public void init( final String dir , final String path , final Set<?> resourcePaths ) {
139        public void init( final String dir , final String path , final Set<String> resourcePaths ) {
140                // 7.3.2.0 (2021/03/19) dir と path の nullチェック(エラーでも止めない)
141//              if( dir == null || path == null ) {
142//                      final String errMsg1 = "指定のディレクトリかパスが、nullです。dir=[" + dir + "] , path=[" + path + "]" ;
143                if( dir == null ) {
144                        final String errMsg1 = "指定のディレクトリが、nullです。path=[" + path + "]" ;
145                        System.err.println( ThrowUtil.ogThrowMsg( errMsg1 ) );
146                        return ;                        // 8.5.5.1 (2024/02/29) spotbugs NP_GUARANTEED_DEREF
147                }
148
149                if( resourcePaths == null ) {
150                        final File directory = new File( dir );
151                        if( ! directory.exists() ) {
152                                final String errMsg = "指定のディレクトリは、存在しません。dir=[" + directory + "] , path=[" + path + "]" ;
153                                // 6.3.8.5 (2015/10/16) コンストラクターで、Exception を throw しないようにします。
154//                              System.out.println( errMsg );
155                                System.err.println( ThrowUtil.ogThrowMsg( errMsg ) );           // 7.3.2.0 (2021/03/19) フォルダなしは、積極的なエラーにする。
156        //                      // 7.3.2.0 (2021/03/19) フォルダを作成する必要はない。あくまでイメージを用意しておいて利用するだけでよい。
157        //                      // 6.4.3.2 (2016/02/19) 指定のフォルダが存在しない場合、作成します。
158        //                      if( directory.mkdirs() ) {
159        //                              final String errMsg2 = "指定のディレクトリを自動作成しました。[" + directory + "]";
160        //                              System.out.println( errMsg2 );
161        //                      }
162        //                      else {
163        //                              final String errMsg3 = "指定のディレクトリの自動作成に失敗しました。[" + directory + "]";
164        //                              System.err.println( ThrowUtil.ogThrowMsg( errMsg3 ) );
165        //                      }
166                                return ;
167                        }
168
169                        if( ! directory.isDirectory() ) {
170                                final String errMsg = "指定のキーは、ディレクトリではありません。[" + directory + "]";
171                                // 6.3.8.5 (2015/10/16) コンストラクターで、Exception を throw しないようにします。
172                                System.err.println( ThrowUtil.ogThrowMsg( errMsg ) );
173                                return ;
174                        }
175                        // 6.3.8.4 (2015/10/09) ファイルのみ取り込む
176                        // 6.3.9.0 (2015/11/06) null になっている可能性があるメソッドの戻り値のnullチェックを追加。
177                        final File[] files = directory.listFiles();
178                        if( files != null ) {
179                                for( final File file : files ) {
180                                        if( file != null && file.isFile() ) {
181                                                dataSet( file.getName() , path );
182                                        }
183                                }
184                        }
185                }
186                else {
187                        final int len = dir.length() ;
188//                      for( final Object rpath : resourcePaths ) {
189//                              final String fname = String.valueOf( rpath ).substring( len );  // ファイル名
190//                              dataSet( fname , path );
191//                      }
192                        for( final String rpath : resourcePaths ) {                     // 7.3.2.0 (2021/03/19)
193                                final String fname = rpath.substring( len );    // ファイル名
194                                dataSet( fname , path );
195                        }
196                }
197        }
198
199        /**
200         * ファイルマップを構築する内部処理。
201         *
202         * これは、ServletContext を利用した、META-INF/resources からの読み取り対応と、
203         * 通常のフォルダスキャンの読み取りの共通処理をまとめた目疎度です。
204         *
205         * @og.rev 6.3.8.4 (2015/10/09) 新規作成
206         * @og.rev 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。変数名も変えておきます。
207         *
208         * @param  fname ファイル名
209         * @param  path  ファイル名に付与するパス文字列
210         */
211        private void dataSet( final String fname , final String path ) {
212                final String upkey = fname.toUpperCase( Locale.JAPAN ) ;
213                String tmpName = fname;
214
215                // path が、nullやゼロ文字列以外の場合は、最後の文字を判定して、ファイル名に連結します。
216                if( path != null && !path.isEmpty() ) {
217                        final char ch = path.charAt( path.length()-1 ) ;
218                        if( ch == '/' || ch == '\\' ) {
219                                tmpName = path + fname;
220                        }
221                        else {
222                                tmpName = path + '/' + fname;
223                        }
224                }
225
226                // 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。変数名も変えておきます。
227                final int idx = upkey.lastIndexOf( '.' );
228                if( idx >= 0 ) {
229                        fMap.put( upkey.substring( 0,idx ), tmpName );
230                }
231                else {
232                        fMap.put( upkey, tmpName );
233                }
234        }
235
236        /**
237         * 指定のキーのファイルが存在しているかどうかを返します。
238         * 存在している場合は、true , 存在していない場合は、false になります。
239         *
240         * @og.rev 6.3.8.5 (2015/10/16) Exception を throw しないようにします。
241         * @og.rev 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。
242         *
243         * @param   key 指定のキー
244         *
245         * @return      存在しているかどうか(true:存在する/false:存在しない)
246         */
247        public boolean exists( final String key ) {
248                // 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。変数名も変えておきます。
249                return key != null && fMap.containsKey( key.toUpperCase( Locale.JAPAN ) );
250        }
251
252        /**
253         * キーに対応したファイル名を返します。
254         * 指定のキーに対するファイル名が存在しない場合は、null を返します。
255         *
256         * 引数を可変長引数にして、前から順番にMapを調べます。最初に、null でない
257         * 値を返します。最後まで一致しなければ、null を返します。
258         * 引数のキーが、nullや、ゼロ配列の場合も、nullになります。
259         *
260         * @og.rev 6.3.8.4 (2015/10/09) FileMap のコンストラクタ変更に伴う対応。
261         * @og.rev 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。
262         *
263         * @param   keys 指定のキー配列(可変長引数)
264         *
265         * @return      ファイル名(ディレクトリパスは含まず)、存在しない場合は、null
266         */
267        public String getFilename( final String... keys ) {
268                // 8.5.5.1 (2024/02/29) PMD 7.0.0 OnlyOneReturn メソッドには終了ポイントが 1 つだけ必要
269                String rtn = null;
270                if( keys != null ) {
271                        for( final String key : keys ) {
272                                // 6.3.8.4 (2015/10/09) 最初に見つけた値を返す。
273                                if( key != null ) {
274                                        // 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。変数名も変えておきます。
275//                                      final String rtn = fMap.get( key.toUpperCase( Locale.JAPAN ) );
276//                                      if( rtn != null ) { return rtn; }
277                                        rtn = fMap.get( key.toUpperCase( Locale.JAPAN ) );
278                                        if( rtn != null ) { break; }
279                                }
280                        }
281                }
282//              return null;
283                return rtn;
284        }
285
286        /**
287         * 初期化が完了しているかどうかを、返します。
288         * 完了している場合は、true を返します。未完了、または、clear() 実行後は、falseです。
289         *
290         * インスタンスは、init処理が完了するまでは、false が返る為、簡易的な同期処理は
291         * 行われています。
292         * (内部のMapへの書き込みは、init処理でのみ行われます。)
293         *
294         * @og.rev 6.3.9.0 (2015/11/06) 新規作成。
295         * @og.rev 6.4.3.2 (2016/02/19) initFlagを廃止し、直接 Mapが空かどうかで判定します。
296         *
297         * @return      初期化が完了していればtrue
298         */
299        public boolean isInit() { return !fMap.isEmpty(); }
300
301        /**
302         * 初期化(クリア)します。
303         *
304         * @og.rev 6.3.9.0 (2015/11/06) 新規作成。
305         * @og.rev 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。
306         */
307        @Override       // Cleanable
308        public void clear() {
309                // 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。変数名も変えておきます。
310                fMap.clear();
311        }
312}