001package org.opengion.fukurou.model;
002
003import java.io.ByteArrayInputStream;
004import java.io.ByteArrayOutputStream;
005import java.io.File;
006import java.io.FileFilter;
007import java.io.FileNotFoundException;
008import java.io.IOException;
009import java.io.InputStream;
010import java.net.URI;
011import java.util.ArrayList;
012import java.util.List;
013import java.util.regex.Matcher;
014import java.util.regex.Pattern;
015
016import org.opengion.fukurou.system.Closer;
017import org.opengion.fukurou.util.StringUtil;
018
019/**
020 * クラウドストレージ対応用の抽象クラスです。
021 * 各ベンダーのストレージに対応したプラグインを作成する場合はこのクラスを継承してください。
022 *
023 *
024 * @og.group ファイル操作
025 *
026 * @og.rev 5.10.8.0 (2019/02/01) 新規作成
027 * @og.rev 5.10.9.0 (2019/03/01) 変更対応
028 * @author oota
029 * @since JDK7.0
030 */
031public abstract class CloudFileOperation extends FileOperation {
032        //* このプログラムのVERSION文字列を設定します。{@VALUE} */
033        private static final String VERSION = "7.0.2.1 (2019/03/04)" ;
034        private static final long serialVersionUID = 702120190304L ;
035
036        /** バッファサイズ {@value} */
037        private static final int BUFFER_SIZE = 1024 * 4;
038        /** パス */
039        protected final String conPath;
040        /** バケット名 */
041        protected final String conBucket;
042
043        private static final String UNIMPLEMNTED_ERR="このクラスでは未実装のメソッドです。";
044        private static final char   FS = '/' ;
045        // 5.10.12.2 (2019/06/17) 相対パス対応「../」と1つ前のディレクトリ情報を抽出(1つ前が先頭の場合は、/ではなく^)
046        // 7.2.9.4 (2020/11/20) PMD:Variables that are final and static should be all capitals, 'ptnPreDir' is not all capitals.
047//      private static final Pattern ptnPreDir = Pattern.compile("(?<=/|^)[^/]+/\\.\\./");
048        private static final Pattern PTN_PRE_DIR = Pattern.compile("(?<=/|^)[^/]+/\\.\\./");
049
050        /**
051         * コンストラクタ
052         *
053         *
054         * @param bucket バケット名
055         * @param inPath ファイルパス
056         */
057        public CloudFileOperation(final String bucket, final String inPath) {
058                super(inPath);
059
060                this.conPath = editPath(replaceFileSeparetor(inPath));
061
062                this.conBucket = bucket;
063
064                if (StringUtil.isNull(conBucket)) {
065                        final String errMsg = "バケット未指定です。hayabusa利用ではシステム変数の「CLOUD_BUCKET」にバケット名を設定して下さい。";
066                        throw new RuntimeException(errMsg);
067                }
068        }
069
070        /**
071         * データ書き込み
072         *
073         * InputStreamのデータを書き込みます。
074         *
075         * @param is 書き込みデータのInputStream
076         * @throws IOException IO関連のエラー情報
077         */
078        @Override
079        public abstract void write(InputStream is) throws IOException;
080
081        /**
082         * データ読み込み
083         *
084         * データを読み込み、InputStreamを返します。
085         *
086         * @return 読み込みデータのInputStream
087         * @throws FileNotFoundException ファイル非存在エラー情報
088         */
089        @Override
090        public abstract InputStream read() throws FileNotFoundException;
091
092        /**
093         * ファイル削除
094         *
095         * ファイルを削除します。
096         *
097         * @return 成否フラグ
098         */
099        @Override
100        public abstract boolean delete();
101
102        /**
103         * ファイルコピー
104         *
105         * ファイルを指定先にコピーします。
106         *
107         * @param afPath コピー先
108         * @return 成否フラグ
109         */
110        @Override
111        public abstract boolean copy(String afPath);
112
113        /**
114         * ファイルサイズ取得
115         *
116         * ファイルサイズを返します。
117         *
118         * @return ファイルサイズ
119         */
120        @Override
121        public abstract long length();
122
123        /**
124         * 最終更新時刻取得
125         *
126         * 最終更新時刻を返します。
127         *
128         * @return 最終更新時刻
129         */
130        @Override
131        public abstract long lastModified();
132
133        /**
134         * ファイル判定
135         *
136         * ファイルの場合は、trueを返します。
137         *
138         * @return ファイルフラグ
139         */
140        @Override
141        public abstract boolean isFile();
142
143        /**
144         * ディレクトリ判定
145         *
146         * ディレクトリの場合は、trueを返します。
147         *
148         * @return ディレクトリフラグ
149         */
150        @Override
151        public abstract boolean isDirectory();
152
153        /**
154         * 一覧取得
155         *
156         * パスのファイルと、ディレクトリ一覧を取得します。
157         *
158         * @param filter ファイルフィルター
159         * @return ファイルとティレクトリ一覧
160         */
161        @Override
162        public abstract File[] listFiles(FileFilter filter);
163
164        /**
165         * 親ディレクトリの取得
166         *
167         * 親のディレクトリ情報を返します。
168         *
169         * @return 親のディレクトリ
170         */
171        @Override
172        public abstract File getParentFile();
173
174        /**
175         * ファイルパス取得
176         *
177         * ファイルパスを取得します。
178         *
179         * @return 設定パス
180         */
181        @Override
182        public String getPath() {
183                return conPath;
184        }
185
186        /**
187         * 絶対パス取得
188         *
189         * 絶対パスを取得します。
190         *
191         * @return 絶対パス
192         */
193        @Override
194        public String getAbsolutePath() {
195                return conPath;
196        }
197
198        /**
199         * ファイル名取得
200         *
201         * ファイル名を取得します。
202         *
203         * @return 名称
204         */
205        @Override
206        public String getName() {
207                return drawName(conPath);
208        }
209
210        /**
211         * 親のパス取得
212         *
213         * 親のパスを取得します。
214         *
215         * @return 親のパス
216         */
217        @Override
218        public String getParent() {
219                return drawParent(conPath);
220        }
221
222        /**
223         * ファイル移動
224         *
225         * ファイルを指定先に移動します。
226         *
227         * @param afPath 移動先
228         * @return 成否フラグ
229         */
230        @Override
231        public boolean move(final String afPath) {
232                boolean flgRtn = false;
233
234                flgRtn = copy(afPath);
235                if (flgRtn) {
236                        flgRtn = delete();
237                }
238
239                return flgRtn;
240        }
241
242        /**
243         * 存在チェック
244         *
245         * 存在する場合は、trueを返します。
246         *
247         * @return 存在フラグ
248         */
249        @Override
250        public boolean exists() {
251                return isDirectory() | isFile();
252        }
253
254        /**
255         * ディレクトリの作成
256         *
257         * ※1つのディレクトリのみ作成します。
258         * クラウドストレージにはディレクトリの概念が無いため、
259         * 作成は行わず、trueを返します。
260         *
261         * @return 成否フラグ
262         */
263        @Override
264        public boolean mkdir() {
265                return true;
266        }
267
268        /**
269         * ディレクトリの作成(複数)
270         *
271         * ※複数のディレクトリを作成します。
272         * クラウドストレージにはディレクトリの概念が無いため、
273         * 作成は行わず、trueを返します。
274         *
275         *
276         * @return 成否フラグ
277         */
278        @Override
279        public boolean mkdirs() {
280                return true;
281        }
282
283        /**
284         * ファイル名変更
285         *
286         * 指定のファイル情報のファイル名に変更します。
287         *
288         * @param dest 変更後のファイル情報
289         * @return 成否フラグ
290         */
291        @Override
292        public boolean renameTo(final File dest) {
293                return move(dest.getPath());
294        }
295
296        /**
297         * 書き込み可能フラグ
298         *
299         * ※クラウドストレージの場合は、
300         * 存在すればtrueを返します。
301         *
302         * @return 書き込み可能フラグ
303         */
304        @Override
305        public boolean canWrite() {
306                return exists();
307        }
308
309        /**
310         * 読み取り可能フラグ
311         *
312         * ※クラウドストレージの場合は、
313         * 存在すればtrueを返します。
314         *
315         * @return 読み取り可能フラグ
316         */
317        @Override
318        public boolean canRead() {
319                return exists();
320        }
321
322        /**
323         * 隠しファイルフラグ
324         *
325         * ※クラウドストレージの場合は、
326         * 必ずfalseを返します。
327         *
328         * @return 隠しファイルフラグ
329         */
330        @Override
331        public boolean isHidden() {
332                return false;
333        }
334
335        /**
336         * 新規ファイル作成
337         *
338         * 既にファイルが存在しない場合のみ、
339         * 空のファイルを作成します。
340         *
341         * @return 成否フラグ
342         * @throws IOException ファイル関連エラー情報
343         */
344        @Override
345        public boolean createNewFile() throws IOException {
346                boolean rtn = false;
347
348                if (!exists()) {
349                        InputStream is = null;
350                        try {
351                                is = new ByteArrayInputStream(new byte[0]);
352                                write(is);
353                                rtn = true;
354                        } finally {
355                                Closer.ioClose(is);
356                        }
357                }
358
359                return rtn;
360        }
361
362        /**
363         * 最終更新時刻の更新
364         *
365         * 最終更新時刻の更新を行います。
366         * ※クラウドストレージの場合は、
367         * 最終更新時刻の更新を行えません。
368         *
369         * @param time 更新する最終更新時刻
370         * @return 成否フラグ
371         */
372        @Override
373        public boolean setLastModified(final long time) {
374                // クラウドストレージでは、setLastModifiedによる、
375                // 最終更新時刻の設定はできないので、
376                // 処理を行わずにtrueを返します。
377                return true;
378        }
379
380        /**
381         * カノニカルファイル情報の取得
382         *
383         * ※ローカルサーバのみ通常ファイルと、
384         * カノニカルファイルで異なります。
385         *
386         * @return カノニカルファイル情報
387         * @throws IOException ファイル関連エラー情報
388         */
389        @Override
390        public FileOperation getCanonicalFile() throws IOException {
391                return this;
392        }
393
394        /**
395         * toString
396         *
397         * パスを返します。
398         *
399         * @return ファイルパス
400         */
401        @Override
402        public String toString() {
403                return conPath;
404        }
405
406        /** 共通関数 **/
407        /**
408         * ファイルパスの編集
409         *
410         * パスの先頭が「/」の場合は「/」の除去と、「//」を「/」に置換処理の追加。
411         *
412         * @og.rev 5.10.12.2 (2019/06/17) 相対パス対応
413         *
414         * @param path ファイルパス
415         * @return 変更後パス
416         */
417        protected String editPath(final String path) {
418                if (StringUtil.isNull(path)) {
419                        return "";
420                }
421                String rtn = path;
422
423                // 「//+」は「/」に置換
424                rtn = rtn.replaceAll("//+", "/");
425                // 先頭が「/」の場合は除去
426//              if ("/".equals(rtn.substring(0, 1))) {
427                if( FS == rtn.charAt(0) ) {
428                        rtn = rtn.substring(1);
429                }
430                // 後尾の「.」は除去
431                rtn = rTrim(rtn, '.');
432                // 後尾の「/」は除去
433                rtn = rTrim(rtn, FS);
434
435                // 5.10.12.2 (2019/06/17)
436                // 「../」の文字列は1つ上のディレクトリに変換を行います。
437                Matcher mtc = PTN_PRE_DIR.matcher(rtn);
438
439                // 「../」が無くなるまで、1つずづ変換します。
440                while(mtc.find()) {
441                        rtn = mtc.replaceFirst("");
442                        mtc = PTN_PRE_DIR.matcher(rtn);
443                }
444
445                return rtn;
446        }
447
448        /**
449         * 親のパスを抽出
450         *
451         * キーから親のパスを抽出します。
452         *
453         * @param key キー
454         * @return 親のパス
455         */
456        protected String drawParent(final String key) {
457                final int k = key.lastIndexOf(FS);
458
459                String rtn = "";
460                if (k > 0) {
461                        rtn = key.substring(0, key.lastIndexOf(FS));
462                }
463                if ("/".equals(File.separator)) {
464                        rtn = File.separator + rtn;
465                }
466
467                return rtn;
468        }
469
470        /**
471         * 名称の抽出
472         *
473         * 引数のkeyから名称を抽出します。
474         *
475         * @param key キー(パス)
476         * @return 名称
477         */
478        protected String drawName(final String key) {
479                final int k = key.lastIndexOf(FS);
480
481                String rtn = key;
482                if (k > 0) {
483                        rtn = key.substring(key.lastIndexOf(FS) + 1);
484                }
485                return rtn;
486        }
487
488        /**
489         * ディレクトリ用のパス編集
490         *
491         * 後尾に「/」がない場合は、付与します。
492         *
493         * @param path パス
494         * @return 後尾に「/」ありのパス
495         */
496        protected String setDirTail(final String path) {
497                if (StringUtil.isNull(path)) {
498                        return path;
499                }
500
501                final StringBuilder sb = new StringBuilder(path);
502//              if (!"/".equals(path.substring(path.length() - 1))) {
503                if ( FS != path.charAt(path.length() - 1) ) {
504                        sb.append(FS);
505                }
506                return sb.toString();
507        }
508
509        /**
510         * 右側トリム処理
511         *
512         * 右側の文字が、指定の文字の場合、除去します。
513         *
514         * @param str 対象文字列
515         * @param chr 指定文字
516         * @return 右側から指定文字を除去後の文字列
517         */
518        protected String rTrim(final String str, final char chr) {
519                String rtn = str;
520                int trgPos = 0;
521                for( int i = str.length() - 1; i >= 0; i--) {
522                        if (str.charAt(i) == chr) {
523                                trgPos = i;
524                                // すべて合致した場合は、から文字を返す
525                                if (trgPos == 0) {
526                                        rtn = "";
527                                }
528                        } else {
529                                break;
530                        }
531                }
532
533                if (trgPos > 0) {
534                        rtn = str.substring(0, trgPos);
535                }
536
537                return rtn;
538        }
539
540        /**
541         * ファイル区切り文字変換
542         *
543         * ファイル区切り文字を変換します。
544         *
545         * @param path 変換前文字列
546         * @return 返還後文字列
547         */
548        protected String replaceFileSeparetor(final String path) {
549                if (StringUtil.isNull(path)) {
550                        return "";
551                }
552
553                return path.replaceAll("\\\\", "/");
554        }
555
556        /**
557         * フィルター処理
558         *
559         * フィルター処理を行います。
560         *
561         * @param list フィルタを行うリスト
562         * @param filter フィルタ情報
563         * @return フィルタ後のリスト
564         */
565        protected File[] filter(final List<File> list, final FileFilter filter) {
566                final List<File> files = new ArrayList<File>();
567                for( final File file : list ) {
568                        if (filter.accept(file)) {
569                                files.add(file);
570                        }
571                }
572                return files.toArray(new File[files.size()]);
573        }
574
575        /**
576         * ストリームの変換処理
577         *
578         * InputStreamをbyte[]に変換。
579         * InputStreamのサイズ計算に利用。
580         *
581         * @param is byte配列変換するInputStream
582         * @return InpusStreamをbyte配列に変換した値
583         * @throws IOException ファイル関連エラー情報
584         */
585        protected byte[] toByteArray(final InputStream is) throws IOException {
586                final ByteArrayOutputStream output = new ByteArrayOutputStream();
587                try {
588                        // 7.2.9.4 (2020/11/20) Avoid variables with short names like b   b → bt , n → no
589                        final byte[] bt = new byte[BUFFER_SIZE];
590                        int no = 0;
591                        while ((no = is.read(bt)) != -1) {
592                                output.write(bt, 0, no);
593                        }
594                        return output.toByteArray();
595                } finally {
596                        output.close();
597                }
598        }
599
600        /**
601         * ローカル実行フラグ判定
602         *
603         * このabstract クラスの継承クラスはクラウド上で実行されるため、
604         * falseを返します。
605         *
606         * @return ローカル実行フラグ
607         */
608        @Override
609        public boolean isLocal() {
610                return false;
611        }
612
613        /** java.io.Fileに実装されており、クラウド用ファイルクラスに未実装のメソッドの対応 */
614        /**
615         * canExecuteの実行
616         *
617         * クラウド側では未実装のメソッドです。
618         *
619         * @return フラグ
620         */
621        @Override
622        public boolean canExecute() {
623                throw new RuntimeException(UNIMPLEMNTED_ERR);
624        }
625
626        /**
627         * deleteOnExitの実行
628         *
629         * クラウド側では未実装のメソッドです。
630         *
631         */
632        @Override
633        public void deleteOnExit() {
634                throw new RuntimeException(UNIMPLEMNTED_ERR);
635        }
636
637        /**
638         * getAbsoluteFileの実行
639         *
640         * クラウド側では未実装のメソッドです。
641         *
642         * @return Fileオブジェクト
643         */
644        @Override
645        public File getAbsoluteFile() {
646                throw new RuntimeException(UNIMPLEMNTED_ERR);
647        }
648
649        /**
650         * getFreeSpaceの実行
651         *
652         * クラウド側では未実装のメソッドです。
653         *
654         * @return 数値
655         */
656        @Override
657        public long getFreeSpace() {
658                throw new RuntimeException(UNIMPLEMNTED_ERR);
659        }
660
661        /**
662         * getTotalSpaceの実行
663         *
664         * クラウド側では未実装のメソッドです。
665         *
666         * @return 数値
667         */
668        @Override
669        public long getTotalSpace() {
670                throw new RuntimeException(UNIMPLEMNTED_ERR);
671        }
672
673        /**
674         * getUsableSpaceの実行
675         *
676         * クラウド側では未実装のメソッドです。
677         *
678         * @return 数値
679         */
680        @Override
681        public long getUsableSpace() {
682                throw new RuntimeException(UNIMPLEMNTED_ERR);
683        }
684
685        /**
686         * isAbsoluteの実行
687         *
688         * クラウド側では未実装のメソッドです。
689         *
690         * @return フラグ
691         */
692        @Override
693        public boolean isAbsolute() {
694                throw new RuntimeException(UNIMPLEMNTED_ERR);
695        }
696
697        /**
698         * setReadableの実行
699         *
700         * クラウド側では未実装のメソッドです。
701         *
702         * @param readable フラグ
703         * @return フラグ
704         */
705        @Override
706        public boolean setReadable(final boolean readable) {
707                throw new RuntimeException(UNIMPLEMNTED_ERR);
708        }
709
710        /**
711         * setReadableの実行
712         *
713         * クラウド側では未実装のメソッドです。
714         *
715         * @param readable フラグ
716         * @param ownerOnly フラグ
717         * @return フラグ
718         */
719        @Override
720        public boolean setReadable(final boolean readable, final boolean ownerOnly) {
721                throw new RuntimeException(UNIMPLEMNTED_ERR);
722        }
723
724        /**
725         * setWritableの実行
726         *
727         * クラウド側では未実装のメソッドです。
728         *
729         * @param writable フラグ
730         * @return フラグ
731         */
732        @Override
733        public boolean setWritable(final boolean writable) {
734                throw new RuntimeException(UNIMPLEMNTED_ERR);
735        }
736
737        /**
738         * canExecuteの実行
739         *
740         * クラウド側では未実装のメソッドです。
741         *
742         * @param writable フラグ
743         * @param ownerOnly フラグ
744         * @return フラグ
745         */
746        @Override
747        public boolean setWritable(final boolean writable, final boolean ownerOnly) {
748                throw new RuntimeException(UNIMPLEMNTED_ERR);
749        }
750
751        /**
752         * canExecuteの実行
753         *
754         * クラウド側では未実装のメソッドです。
755         *
756         * @return URI情報
757         */
758        @Override
759        public URI toURI() {
760                throw new RuntimeException(UNIMPLEMNTED_ERR);
761        }
762}