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