001: /*
002: * Copyright (C) 2006 Methodhead Software LLC. All rights reserved.
003: *
004: * This file is part of TransferCM.
005: *
006: * TransferCM is free software; you can redistribute it and/or modify it under the
007: * terms of the GNU General Public License as published by the Free Software
008: * Foundation; either version 2 of the License, or (at your option) any later
009: * version.
010: *
011: * TransferCM is distributed in the hope that it will be useful, but WITHOUT ANY
012: * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
013: * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
014: * details.
015: *
016: * You should have received a copy of the GNU General Public License along with
017: * TransferCM; if not, write to the Free Software Foundation, Inc., 51 Franklin St,
018: * Fifth Floor, Boston, MA 02110-1301 USA
019: */
020:
021: package com.methodhead.res;
022:
023: import java.io.File;
024: import java.util.Map;
025: import java.util.HashMap;
026: import java.util.Arrays;
027: import java.util.Comparator;
028: import java.util.Iterator;
029: import java.util.List;
030: import java.util.ArrayList;
031: import org.apache.commons.lang.StringUtils;
032: import java.io.IOException;
033: import org.apache.commons.io.FileUtils;
034: import org.apache.commons.io.IOUtils;
035: import org.apache.commons.lang.exception.ExceptionUtils;
036: import java.io.InputStream;
037: import java.io.FileOutputStream;
038:
039: /**
040: * Provides a logical file system and methods to manage it.
041: */
042: public class FileManager {
043:
044: // constructors /////////////////////////////////////////////////////////////
045:
046: // constants ////////////////////////////////////////////////////////////////
047:
048: /**
049: * The destination path is blank, invalid (e.g., contains ".."), or
050: * non-existant.
051: */
052: public static final String VALIDATE_INVALIDDESTPATH = "res.invaliddestpath";
053:
054: /**
055: * The destination path is a subdirectory of one of the files being moved or
056: * copied.
057: */
058: public static final String VALIDATE_SUBDIROFSELF = "res.subdirofself";
059:
060: /**
061: * The move or copy would result in overwriting a directory or overwriting a
062: * file with a directory.
063: */
064: public static final String VALIDATE_CANTOVERWRITE = "res.cantoverwrite";
065:
066: /**
067: * The destination file name is invalid.
068: */
069: public static final String VALIDATE_INVALIDDESTNAME = "res.invaliddestname";
070:
071: // classes //////////////////////////////////////////////////////////////////
072:
073: // methods //////////////////////////////////////////////////////////////////
074:
075: /**
076: * Returns the directory specified by <tt>path</tt>, or <tt>null</tt> if no
077: * such directory (if a file exists, but is not a directory, <tt>null</tt> is
078: * still returned).
079: */
080: protected File getFileForPath(String path) throws ResException {
081:
082: //
083: // make sure path is valid
084: //
085: if (!ResUtils.isValidPath(path))
086: throw new ResException("Path is invalid \"" + path + "\"");
087:
088: //
089: // figure out the root directory
090: //
091: String[] parts = path.split("/", 2);
092:
093: String rootPart = parts[0];
094: String pathPart = "";
095:
096: if (parts.length > 1)
097: pathPart = parts[1];
098:
099: Directory dir = (Directory) directories_.get(rootPart);
100:
101: if (dir == null)
102: return null;
103:
104: //
105: // create a file for the requested path and verify it
106: //
107: File file = new File(dir.getFile(), pathPart);
108:
109: if (!file.exists() || !file.isDirectory())
110: return null;
111:
112: return file;
113: }
114:
115: /**
116: * Adds a directory to the file system. An exception is thrown if
117: * <tt>dir</tt> is not a valid directory, or if a directory named
118: * <tt>name</tt> has already been added.
119: */
120: public void addDirectory(String name, File dir) throws ResException {
121:
122: if (!dir.exists() || !dir.isDirectory())
123: throw new ResException(dir.getName()
124: + " does not exist or is not a directory.");
125:
126: if (directories_.containsKey(name))
127: throw new ResException("\"" + name
128: + "\" has already been added.");
129:
130: Directory d = new Directory();
131: d.setName(name);
132: d.setFile(dir);
133:
134: directories_.put(name, d);
135: }
136:
137: /**
138: * Returns the directories being managed by the file manager, sorted by the
139: * directory name.
140: */
141: public Directory[] getDirectories() {
142: Directory[] directories = new Directory[directories_.keySet()
143: .size()];
144:
145: //
146: // add directories to the array
147: //
148: int i = 0;
149: for (Iterator iter = directories_.keySet().iterator(); iter
150: .hasNext();) {
151: directories[i++] = (Directory) directories_
152: .get(iter.next());
153: }
154:
155: //
156: // sort the directories
157: //
158: Arrays.sort(directories, new Comparator() {
159: public int compare(Object o1, Object o2) {
160: return ((Directory) o1).getName().compareToIgnoreCase(
161: ((Directory) o2).getName());
162: }
163: });
164:
165: return directories;
166: }
167:
168: /**
169: * Returns the file specified by <tt>path</tt> and <tt>name</tt>, or
170: * <tt>null</tt> if no such file exists.
171: */
172: public File getFile(String path, String name) throws ResException {
173:
174: //
175: // get the actual directory
176: //
177: File dir = getFileForPath(path);
178:
179: if (dir == null)
180: return null;
181:
182: //
183: // create a file for the requested file and verify it
184: //
185: File file = new File(dir, name);
186:
187: if (!file.exists())
188: return null;
189:
190: return file;
191: }
192:
193: /**
194: * Returns the files in the directory specified by <tt>path</tt>, sorted in
195: * alphabetical order, or <tt>null</tt> if no such path exists.
196: */
197: public File[] getFiles(String path) throws ResException {
198:
199: //
200: // get the actual directory
201: //
202: File dir = getFileForPath(path);
203:
204: if (dir == null)
205: return null;
206:
207: //
208: // get the files
209: //
210: File[] files = dir.listFiles();
211:
212: if (files == null)
213: throw new ResException("Could list files for path \""
214: + path + "\".");
215:
216: //
217: // sort the files
218: //
219: Arrays.sort(files, new Comparator() {
220: public int compare(Object o1, Object o2) {
221: return ((File) o1).getName().compareToIgnoreCase(
222: ((File) o2).getName());
223: }
224: });
225:
226: return files;
227: }
228:
229: /**
230: * Returns <tt>true</tt> if <tt>destPath</tt> is a subdirectory of any files
231: * specified by <tt>srcPath</tt> and <tt>srcFiles</tt>.
232: */
233: protected boolean isDestSubdir(String srcPath, String[] srcFiles,
234: String destPath) {
235:
236: for (int i = 0; i < srcFiles.length; i++) {
237: String filePath = srcPath + "/" + srcFiles[i];
238:
239: if (ResUtils.isPathDescendent(ResUtils.cleanPath(filePath),
240: ResUtils.cleanPath(destPath)))
241: return true;
242: }
243:
244: return false;
245: }
246:
247: /**
248: * Returns <tt>true</tt> if <tt>src</tt> can overwrite <tt>dest</tt>.
249: * Directories cannot be overwritten at all, and files can't be overwritten
250: * by directories.
251: */
252: protected boolean canOverwrite(File src, File dest) {
253:
254: return canOverwrite(dest, src.isDirectory());
255:
256: /*
257: if ( dest != null ) {
258: if ( dest.isDirectory() ) {
259: return false;
260: }
261: else {
262: if ( src.isDirectory() ) {
263: return false;
264: }
265: }
266: }
267:
268: return true;
269: */
270: }
271:
272: /**
273: * Returns <tt>true</tt> if <tt>dest</tt> can be overwritten by a file, where
274: * <tt>isDir</tt> specifies whether that file is a file or a directory. If
275: * <tt>dest</tt> is <tt>null</tt>, it is assumed the file doesn't exist, and
276: * <tt>true</tt> is returned. The same rules described in {@link
277: * #canOverwrite(java.io.File,java.io.File)} apply.
278: */
279: protected boolean canOverwrite(File dest, boolean isDir) {
280:
281: if (dest != null) {
282: if (dest.isDirectory()) {
283: return false;
284: } else {
285: if (isDir) {
286: return false;
287: }
288: }
289: }
290:
291: return true;
292: }
293:
294: /**
295: * Validates moving <tt>srcFiles</tt> from <tt>srcPath</tt> to
296: * <tt>destPath</tt>. <tt>srcFiles</tt> is expected to contain a list of
297: * file names as <tt>String</tt>s. If <tt>srcFiles</tt> contains a single
298: * file name, <tt>destFile</tt> is validated as a destination file name.
299: * Returns <tt>null</tt> if the move is valid. A <tt>VALIDATE_</tt> (e.g.
300: * {@link #VALIDATE_INVALIDDESTPATH}) error code is returned if the move is
301: * invalid. Note that these error codes are designed to be used as a key in
302: * a resource bundle.
303: */
304: public String validateMove(String srcPath, String[] srcFiles,
305: String destPath, String destFile) {
306:
307: //
308: // blank?
309: //
310: if (StringUtils.isBlank(destPath))
311: return VALIDATE_INVALIDDESTPATH;
312:
313: //
314: // valid?
315: //
316: if (!ResUtils.isValidPath(destPath))
317: return VALIDATE_INVALIDDESTPATH;
318:
319: //
320: // exists
321: //
322: if (getFileForPath(destPath) == null)
323: return VALIDATE_INVALIDDESTPATH;
324:
325: //
326: // subdir of self?
327: //
328: if (isDestSubdir(srcPath, srcFiles, destPath))
329: return VALIDATE_SUBDIROFSELF;
330:
331: if (srcFiles.length == 1) {
332:
333: //
334: // blank name?
335: //
336: if (StringUtils.isBlank(destFile))
337: return VALIDATE_INVALIDDESTNAME;
338:
339: //
340: // valid name?
341: //
342: if (!ResUtils.isValidFileName(destFile))
343: return VALIDATE_INVALIDDESTNAME;
344:
345: //
346: // exists but can't overwrite?
347: //
348: if (!canOverwrite(getFile(srcPath, srcFiles[0]), getFile(
349: destPath, destFile)))
350: return VALIDATE_CANTOVERWRITE;
351: } else {
352:
353: //
354: // a file in the set exists but can't overwrite?
355: //
356: for (int i = 0; i < srcFiles.length; i++) {
357: if (!canOverwrite(getFile(srcPath, srcFiles[i]),
358: getFile(destPath, srcFiles[i])))
359: return VALIDATE_CANTOVERWRITE;
360: }
361: }
362:
363: return null;
364: }
365:
366: /**
367: * Validates whether the files specified by <tt>srcPath</tt> and
368: * <tt>srcFile</tt> can be created. A file may not be created in some
369: * circumstances: if the file exists and is a directory, it cannot be
370: * overwritten; if the file exists and is a normal file, it cannot be
371: * overwritten by a directory. Returns <tt>null</tt> if the create is valid.
372: * A <tt>VALIDATE_</tt> (e.g. {@link #VALIDATE_CANTOVERWRITE}) error code is
373: * returned if the create is invalid. Note that these error codes are
374: * designed to be used as a key in a resource bundle.
375: */
376: public String validateCreate(String srcPath, String srcFile,
377: boolean isDir) {
378:
379: File dest = getFile(srcPath, srcFile);
380:
381: //
382: // does the file exist?
383: //
384: if ((dest != null) && (!canOverwrite(dest, isDir)))
385: return VALIDATE_CANTOVERWRITE;
386:
387: return null;
388: }
389:
390: /**
391: * Returns the names of files in <tt>destPath</tt> that would be overwritten
392: * by files specified by <tt>srcPath</tt> and <tt>srcFiles</tt> in a move or
393: * copy operation. If <tt>srcFiles</tt> contains only one file name,
394: * <tt>destFile</tt> is considered the destination file name.
395: */
396: public String[] findOverwriteFiles(String srcPath,
397: String[] srcFiles, String destPath, String destFile) {
398:
399: if (srcFiles.length == 1) {
400: if (getFile(destPath, destFile) != null)
401: return new String[] { destFile };
402: } else {
403: List l = new ArrayList();
404: for (int i = 0; i < srcFiles.length; i++) {
405: if (getFile(destPath, srcFiles[i]) != null)
406: l.add(srcFiles[i]);
407: }
408:
409: if (l.size() > 0) {
410: String[] fileNames = new String[l.size()];
411: int i = 0;
412: for (Iterator iter = l.iterator(); iter.hasNext();)
413: fileNames[i++] = (String) iter.next();
414:
415: return fileNames;
416: }
417: }
418:
419: return null;
420: }
421:
422: /**
423: * Copies <tt>from</tt> to <tt>to</tt>; if <tt>from</tt> is a directory, it
424: * is recursively copied. If <tt>to</tt> is exists and is not a directory,
425: * it is overwritten. If <tt>to</tt> is exists and is a directory, the
426: * contents of <tt>from</tt> are copied over the contents of <tt>to</tt>. If
427: * <tt>to</tt> exists, but is a directory when <tt>from</tt> is not (or vice
428: * versa), an exception is thrown.
429: */
430: protected static void copyFile(File from, File to)
431: throws ResException {
432:
433: try {
434: if (from.isDirectory()) {
435: if (to.exists()) {
436: if (!to.isDirectory())
437: throw new ResException(
438: "Can't copy directory over non-directory: "
439: + to.getAbsolutePath());
440: } else {
441: to.mkdir();
442: }
443:
444: //
445: // recurse into directory
446: //
447: File[] files = from.listFiles();
448:
449: for (int i = 0; i < files.length; i++)
450: copyFile(files[i], new File(to, files[i].getName()));
451: } else {
452: if (to.exists() && to.isDirectory())
453: throw new ResException(
454: "Can't copy non-directory over directory: "
455: + to.getAbsolutePath());
456:
457: FileUtils.copyFile(from, to);
458: }
459: } catch (IOException e) {
460: throw new ResException("Unexpected IOException copying \""
461: + from + "\" to \"" + to + "\": "
462: + ExceptionUtils.getStackTrace(e));
463: }
464: }
465:
466: /**
467: * Moves the files specified by <tt>srcPath</tt> and <tt>srcFiles</tt> to
468: * <tt>destPath</tt>. If <tt>srcFiles</tt> contains exactly one file name,
469: * <tt>destFile</tt> is used as the destination file name.
470: */
471: public void move(String srcPath, String[] srcFiles,
472: String destPath, String destFile) {
473:
474: if (srcFiles.length == 1) {
475: File src = getFile(srcPath, srcFiles[0]);
476: File dest = new File(getFileForPath(destPath), destFile);
477: src.renameTo(dest);
478: } else {
479: for (int i = 0; i < srcFiles.length; i++) {
480: File src = getFile(srcPath, srcFiles[i]);
481: File dest = new File(getFileForPath(destPath),
482: srcFiles[i]);
483: src.renameTo(dest);
484: }
485: }
486: }
487:
488: /**
489: * Copies the files specified by <tt>srcPath</tt> and <tt>srcFiles</tt> to
490: * <tt>destPath</tt>. If <tt>srcFiles</tt> contains exactly one file name,
491: * <tt>destFile</tt> is used as the destination file name. Any directories
492: * are recursively copied.
493: */
494: public void copy(String srcPath, String[] srcFiles,
495: String destPath, String destFile) {
496:
497: if (srcFiles.length == 1) {
498: File src = getFile(srcPath, srcFiles[0]);
499: File dest = new File(getFileForPath(destPath), destFile);
500: copyFile(src, dest);
501: } else {
502: for (int i = 0; i < srcFiles.length; i++) {
503: File src = getFile(srcPath, srcFiles[i]);
504: File dest = new File(getFileForPath(destPath),
505: srcFiles[i]);
506: copyFile(src, dest);
507: }
508: }
509: }
510:
511: /**
512: * Deletes the files specified by <tt>srcPath</tt> and <tt>srcFiles</tt>. An
513: * exception is thrown if file(s) do not already exist.
514: */
515: public void delete(String srcPath, String[] srcFiles) {
516:
517: try {
518: //
519: // make sure all the files exist
520: //
521: for (int i = 0; i < srcFiles.length; i++) {
522: if (StringUtils.isBlank(srcFiles[i]))
523: throw new ResException(
524: "Can't delete a file with a blank name.");
525:
526: File f = getFile(srcPath, srcFiles[i]);
527: if (f == null)
528: throw new ResException("\"" + srcPath + "/"
529: + srcFiles[i] + "\" doesn't exist");
530: }
531:
532: //
533: // delete the files
534: //
535: for (int i = 0; i < srcFiles.length; i++) {
536: File f = getFile(srcPath, srcFiles[i]);
537: if (f.isDirectory())
538: FileUtils.deleteDirectory(f);
539: else
540: f.delete();
541: }
542: } catch (IOException e) {
543: throw new ResException("Unexpected exception: "
544: + ExceptionUtils.getStackTrace(e));
545: }
546: }
547:
548: /**
549: * Returns a file specified by <tt>path</tt> and <tt>name</tt>, such that the
550: * returned file can be used to create a new file. An exception is thrown if
551: * the file already exists.
552: */
553: public File getNewFile(String path, String name)
554: throws ResException {
555:
556: //
557: // Make sure the file doesn't already exist
558: //
559: if (getFile(path, name) != null)
560: throw new ResException("File exists \"" + path + "/" + name
561: + "\"");
562:
563: //
564: // verify the path
565: //
566: File dir = getFileForPath(path);
567:
568: if (dir == null)
569: throw new ResException("Invalid path \"" + path + "\"");
570:
571: //
572: // create a file for the requested file and return it
573: //
574: return new File(dir, name);
575: }
576:
577: /**
578: * Creates the file specified by <tt>srcPath</tt> and <tt>srcFile</tt>; a
579: * directory is created if <tt>isDir</tt> is <tt>true</tt>, otherwise an
580: * empty file is created. If the file exists, an a file is being created,
581: * the existing file is overwritten.
582: */
583: public void create(String path, String file, boolean isDir) {
584:
585: try {
586: //
587: // verify the path
588: //
589: File dir = getFileForPath(path);
590:
591: if (dir == null)
592: throw new ResException("Invalid path \"" + path + "\"");
593:
594: //
595: // create a file for the requested file and return it
596: //
597: File f = new File(dir, file);
598:
599: if (f.exists()) {
600: if (f.isDirectory())
601: throw new ResException("Can't overwrite directory.");
602: else {
603: f.delete();
604: }
605: }
606:
607: if (isDir)
608: f.mkdir();
609: else
610: f.createNewFile();
611: } catch (IOException e) {
612: throw new ResException("Unexpected IOException: "
613: + ExceptionUtils.getStackTrace(e));
614: }
615: }
616:
617: /**
618: * Creates the file specified by <tt>srcPath</tt> and <tt>srcFile</tt>,
619: * writing the contents of <tt>in</tt> to the file. If the file exists, the
620: * existing file is overwritten.
621: */
622: public void create(String path, String file, InputStream in) {
623:
624: try {
625: //
626: // verify the path
627: //
628: File dir = getFileForPath(path);
629:
630: if (dir == null)
631: throw new ResException("Invalid path \"" + path + "\"");
632:
633: //
634: // create a file for the requested file and return it
635: //
636: File f = new File(dir, file);
637:
638: if (f.exists()) {
639: if (f.isDirectory())
640: throw new ResException("Can't overwrite directory.");
641: else {
642: f.delete();
643: }
644: }
645:
646: FileOutputStream out = new FileOutputStream(f);
647: IOUtils.copy(in, out);
648: out.close();
649: } catch (IOException e) {
650: throw new ResException("Unexpected IOException: "
651: + ExceptionUtils.getStackTrace(e));
652: }
653: }
654:
655: // properties ///////////////////////////////////////////////////////////////
656:
657: // attributes ///////////////////////////////////////////////////////////////
658:
659: protected Map directories_ = new HashMap();
660: }
|