1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 package org.objectledge.filesystem;
30
31 import java.io.ByteArrayOutputStream;
32 import java.io.IOException;
33 import java.io.InputStream;
34 import java.io.InputStreamReader;
35 import java.io.OutputStream;
36 import java.io.OutputStreamWriter;
37 import java.io.Reader;
38 import java.io.StringWriter;
39 import java.io.UnsupportedEncodingException;
40 import java.io.Writer;
41 import java.net.MalformedURLException;
42 import java.net.URL;
43 import java.util.ArrayList;
44 import java.util.Arrays;
45 import java.util.HashSet;
46 import java.util.Iterator;
47 import java.util.List;
48 import java.util.Set;
49 import java.util.StringTokenizer;
50
51 /***
52 * Provides an abstration of files available in the local file system, in the ClassLoader, web
53 * application context, or through java.net.URL mechanism.
54 *
55 * @author <a href="rafal@caltha.pl">Rafal.Krzewski</a>
56 * @version $Id: FileSystem.java,v 1.31 2006/02/08 18:22:33 zwierzem Exp $
57 */
58 public class FileSystem
59 {
60 private String protocol = "ledge";
61
62 /*** The providers. */
63 private List<FileSystemProvider> providers = new ArrayList<FileSystemProvider>();
64
65 /*** The size of the buffer. */
66 private int bufferSize;
67
68 /*** Maximum size of a file that is loaded into memory in one chunk by the.
69 * read() methods. */
70 private int maxReadSize;
71
72 /***
73 * Protected no-arg constructor to allow mocking.
74 */
75 protected FileSystem()
76 {
77
78 }
79
80 /***
81 * Creates a new instance of the File System.
82 *
83 * @param providers the backend implementations of the service.
84 * @param bufferSize the size of the buffers using for reading/writing files.
85 * @param maxReadSize maximum size of file that is loaded into memory as a single chunk.
86 */
87 public FileSystem(FileSystemProvider[] providers, int bufferSize, int maxReadSize)
88 {
89 this.providers = Arrays.asList(providers);
90 this.bufferSize = bufferSize;
91 this.maxReadSize = maxReadSize;
92 }
93
94
95
96 /***
97 * Return the URL protocol name for this FileSystem.
98 *
99 * @return the protocol prefix used by the service.
100 */
101 public String getProtocol()
102 {
103 return protocol;
104 }
105
106 /***
107 * Return the installed providers.
108 *
109 * <p>The provider objects are returned in the same order in which they are
110 * accessed.</p>
111 *
112 * @return the installed providers.
113 */
114 public FileSystemProvider[] getProviders()
115 {
116 FileSystemProvider[] result = new FileSystemProvider[providers.size()];
117 providers.toArray(result);
118 return result;
119 }
120
121 /***
122 * Return the provider with the specified name.
123 *
124 * @param name the name of the provider
125 * @return the provider.
126 * @throws IllegalArgumentException if the requested provider is not installed.
127 */
128 public FileSystemProvider getProvider(String name) throws IllegalArgumentException
129 {
130 for (int i = 0; i < providers.size(); i++)
131 {
132 FileSystemProvider fp = (FileSystemProvider)providers.get(i);
133 if (fp.getName().equals(name))
134 {
135 return fp;
136 }
137 }
138 throw new IllegalArgumentException("provider " + name + " is not installed");
139 }
140
141 /***
142 * Returns an URL poiting to a file.
143 *
144 * @param path the abstract pathname
145 * @return an URL of <code>null</code> if the file is not found.
146 * @throws MalformedURLException if the pathname is not valid.
147 */
148 public URL getResource(String path)
149 throws MalformedURLException
150 {
151 for (Iterator i = providers.iterator(); i.hasNext();)
152 {
153 FileSystemProvider fp = (FileSystemProvider)i.next();
154 URL url = fp.getResource(path);
155 if (url != null)
156 {
157 return url;
158 }
159 }
160 return null;
161 }
162
163 /***
164 * Returns an InputStream for reading the file.
165 *
166 * @param path the abstract pathname.
167 * @return an InputStream, or <code>null</code> if the file is not found.
168 */
169 public InputStream getInputStream(String path)
170 {
171 for (Iterator i = providers.iterator(); i.hasNext();)
172 {
173 FileSystemProvider fp = (FileSystemProvider)i.next();
174 InputStream is = fp.getInputStream(path);
175 if (is != null)
176 {
177 return is;
178 }
179 }
180 return null;
181 }
182
183 /***
184 * Returns a Reader for reading the file.
185 *
186 * @param path the abstract pathname.
187 * @param encoding character encoding to use.
188 * @return a Reader, or <code>null</code> if the file is not found.
189 * @throws UnsupportedEncodingException if the requested encoding is not supported.
190 */
191 public Reader getReader(String path, String encoding)
192 throws UnsupportedEncodingException
193 {
194 InputStream is = getInputStream(path);
195 if(is != null)
196 {
197 return new InputStreamReader(is, encoding);
198 }
199 else
200 {
201 return null;
202 }
203 }
204
205 /***
206 * Returns an OutputStream for writing the file, or appending to it.
207 *
208 * @param path the abstract pathname.
209 * @param append <code>false</code> to truncate the file,
210 * <code>true</code> to append.
211 * @return an OutputStream, or <code>null</code> if the operation is not
212 * supported, or the file could not be opened for writing.
213 */
214 public OutputStream getOutputStream(String path, boolean append)
215 {
216 for (Iterator i = providers.iterator(); i.hasNext();)
217 {
218 FileSystemProvider fp = (FileSystemProvider)i.next();
219 OutputStream os = fp.getOutputStream(path, append);
220 if (os != null)
221 {
222 return os;
223 }
224 }
225 return null;
226 }
227
228 /***
229 * Returns an OutputStream for writing the file.
230 *
231 * @param path the abstract pathname.
232 * @return an OutputStream, or <code>null</code> if the operation is not
233 * supported, or the file could not be opened for writing.
234 */
235 public OutputStream getOutputStream(String path)
236 {
237 return getOutputStream(path, false);
238 }
239
240 /***
241 * Returns a Writer for writing the file, or appendig to it.
242 *
243 * @param path the abstract pathname.
244 * @param encoding the character encoding to use.
245 * @param append <code>false</code> to truncate the file,
246 * <code>true</code> to append.
247 * @return a Writer, or <code>null</code> if the operation is not
248 * supported, or the file could not be opened for writing.
249 * @throws UnsupportedEncodingException if the requested encoding is not supported.
250 */
251 public Writer getWriter(String path, String encoding, boolean append)
252 throws UnsupportedEncodingException
253 {
254 OutputStream os = getOutputStream(path, append);
255 if(os != null)
256 {
257 return new OutputStreamWriter(os, encoding);
258 }
259 else
260 {
261 return null;
262 }
263 }
264
265 /***
266 * Returns a Writer for writing the file, or appendig to it.
267 *
268 * @param path the abstract pathname.
269 * @param encoding the character encoding to use.
270 * @return a Writer, or <code>null</code> if the operation is not
271 * supported, or the file could not be opened for writing.
272 * @throws UnsupportedEncodingException if the requested encoding is not supported.
273 */
274 public Writer getWriter(String path, String encoding)
275 throws UnsupportedEncodingException
276 {
277 return getWriter(path, encoding, false);
278 }
279
280 /***
281 * Returns a <code>RandomAccess</code> interface implementation for accessing the file at
282 * arbitrary positions.
283 *
284 * @param path the abstract pathname.
285 * @param mode the string which defines the opening mode for this random access file,
286 * the form of this string is equal to the <code>mode</code> parameter in
287 * <code>java.io.RandomAccessFile</code> constructor.
288 * @return an RandomAccess interface implementation, or <code>null</code>
289 * if the operation is not supported.
290 */
291 public RandomAccessFile getRandomAccess(String path, String mode)
292 {
293 for (Iterator i = providers.iterator(); i.hasNext();)
294 {
295 FileSystemProvider fp = (FileSystemProvider)i.next();
296 RandomAccessFile ra = fp.getRandomAccess(path, mode);
297 if (ra != null)
298 {
299 return ra;
300 }
301 }
302 return null;
303 }
304
305 /***
306 * Returns <code>true</code> if the file specified by the abstract
307 * pathname exists.
308 *
309 * @param path the abstract pathname
310 * @return <code>true</code> if the file specified by the abstract
311 * pathname exists.
312 */
313 public boolean exists(String path)
314 {
315 for (Iterator i = providers.iterator(); i.hasNext();)
316 {
317 FileSystemProvider fp = (FileSystemProvider)i.next();
318 if (fp.exists(path))
319 {
320 return true;
321 }
322 }
323 return false;
324 }
325
326 /***
327 * Returns <code>true</code> if the abstract pathname points to an
328 * ordinary file.
329 *
330 * @param path the abstract pathname
331 * @return <code>true</code> if the file specified by the abstract
332 * pathname exists.
333 */
334 public boolean isFile(String path)
335 {
336 for (Iterator i = providers.iterator(); i.hasNext();)
337 {
338 FileSystemProvider fp = (FileSystemProvider)i.next();
339 if (fp.exists(path))
340 {
341 return fp.isFile(path);
342 }
343 }
344 return false;
345 }
346
347 /***
348 * Returns <code>true</code> if the abstract pathname points to a
349 * directory.
350 *
351 * @param path the abstract pathname
352 * @return <code>true</code> if the file specified by the abstract
353 * pathname exists.
354 */
355 public boolean isDirectory(String path)
356 {
357 for (Iterator i = providers.iterator(); i.hasNext();)
358 {
359 FileSystemProvider fp = (FileSystemProvider)i.next();
360 if (fp.exists(path))
361 {
362 return fp.isDirectory(path);
363 }
364 }
365 return false;
366 }
367
368 /***
369 * Returns <code>true</code> if the file specified by the abstract
370 * pathname can be read.
371 *
372 * @param path the abstract pathname
373 * @return <code>true</code> if the file specified by the abstract
374 * pathname can be read.
375 */
376 public boolean canRead(String path)
377 {
378 for (Iterator i = providers.iterator(); i.hasNext();)
379 {
380 FileSystemProvider fp = (FileSystemProvider)i.next();
381 if (fp.exists(path))
382 {
383 return fp.canRead(path);
384 }
385 }
386 return false;
387 }
388
389 /***
390 * Returns <code>true</code> if the file specified by the abstract
391 * pathname can be written.
392 *
393 * @param path the abstract pathname
394 * @return <code>true</code> if the file specified by the abstract
395 * pathname can be written.
396 */
397 public boolean canWrite(String path)
398 {
399 for (Iterator i = providers.iterator(); i.hasNext();)
400 {
401 FileSystemProvider fp = (FileSystemProvider)i.next();
402 if (fp.exists(path))
403 {
404 return fp.canWrite(path);
405 }
406 }
407 return false;
408 }
409
410 /***
411 * Returns the time of the last modificaion of the specified file.
412 *
413 * <p>The time of the modification is returned as the number of
414 * milliseconds sice the epoch (Jan 1 1970), or -1L if the feature is not
415 * supported.</p>
416 *
417 * @param path the abstract pathname.
418 * @return the time of last modification of the specified file.
419 */
420 public long lastModified(String path)
421 {
422 for (Iterator i = providers.iterator(); i.hasNext();)
423 {
424 FileSystemProvider fp = (FileSystemProvider)i.next();
425 if (fp.exists(path))
426 {
427 return fp.lastModified(path);
428 }
429 }
430 return -1L;
431 }
432
433 /***
434 * Returns the size of the specified file.
435 *
436 * <p>If the operation is not supported -1L is returned.</p>
437 *
438 * @param path an abstract pathname.
439 * @return the lenght of the file
440 */
441 public long length(String path)
442 {
443 for (Iterator i = providers.iterator(); i.hasNext();)
444 {
445 FileSystemProvider fp = (FileSystemProvider)i.next();
446 if (fp.exists(path))
447 {
448 return fp.length(path);
449 }
450 }
451 return -1L;
452 }
453
454 /***
455 * Lists the contents of a directory.
456 *
457 * @param path the directory to list.
458 * @return array of names of the contents of a directory.
459 * @throws IOException if the pathname does not point to a directory.
460 */
461 public String[] list(String path)
462 throws IOException
463 {
464 if (!exists(path)) {
465 throw new IOException(path+" does not exist");
466 }
467 Set<String> results = new HashSet<String>();
468 for (Iterator i = providers.iterator(); i.hasNext();)
469 {
470 FileSystemProvider fp = (FileSystemProvider)i.next();
471 if (fp.exists(path))
472 {
473 if (fp.isDirectory(path))
474 {
475 if(fp.canRead(path))
476 {
477 results.addAll(fp.list(path));
478 }
479 else
480 {
481 throw new IOException(fp.getName() + " provider has " + path
482 + " but it's not readable.");
483 }
484 }
485 else
486 {
487 throw new IOException(fp.getName() + " provider has " + path
488 + " but it's not a directory.");
489 }
490 }
491 }
492 return results.toArray(new String[0]);
493 }
494
495 /***
496 * Atomically creates a new, empty file named by this abstract pathname if
497 * and only if a file with this name does not yet exist.
498 *
499 * <p>The check for the existence of the file and the creation of the file
500 * if it does not exist are a single operation that is atomic with respect
501 * to all other filesystem activities that might affect the file.</p>
502 *
503 * @param path the pathname of the file to create.
504 * @return <code>true</code> if the named file does not exist and was
505 * successfully created; <code>false</code> if the named file
506 * already exists.
507 * @throws UnsupportedCharactersInFilePathException if the given path contains
508 * characters incompatible with underlying filesystem.
509 * @throws IOException if the operation fails.
510 */
511 public boolean createNewFile(String path)
512 throws IOException, UnsupportedCharactersInFilePathException
513 {
514 String dir = directoryPath(path);
515 if (!exists(dir))
516 {
517 throw new IOException(dir + " does not exist");
518 }
519 if (!isDirectory(dir))
520 {
521 throw new IOException(dir + " is not a directory");
522 }
523 if (!canWrite(dir))
524 {
525 throw new IOException(dir + ": access denied");
526 }
527 for (Iterator i = providers.iterator(); i.hasNext();)
528 {
529 FileSystemProvider fp = (FileSystemProvider)i.next();
530 if (fp.exists(dir))
531 {
532 return fp.createNewFile(path);
533 }
534 }
535 throw new IOException("internal error");
536 }
537
538 /***
539 * Creates a directory and all neccessary parent directories.
540 *
541 * @param path the directory name.
542 * @throws UnsupportedCharactersInFilePathException if the given path contains
543 * characters incompatible with underlying filesystem.
544 * @throws IOException if the operation fails.
545 */
546 public void mkdirs(String path)
547 throws IOException, UnsupportedCharactersInFilePathException
548 {
549 path = normalizedPath(path);
550 StringTokenizer st = new StringTokenizer(path, "/");
551 StringBuilder sb = new StringBuilder();
552 String parent = "/";
553 while (st.hasMoreTokens())
554 {
555 sb.append('/').append(st.nextToken());
556 String dir = sb.toString();
557 if (exists(dir))
558 {
559 if (!isDirectory(dir))
560 {
561 throw new IOException(dir + " exists and is not a directory");
562 }
563 }
564 inner : for (Iterator i = providers.iterator(); i.hasNext();)
565 {
566 FileSystemProvider fp = (FileSystemProvider)i.next();
567 if (fp.canWrite(parent) && !fp.exists(dir))
568 {
569 fp.mkdirs(dir);
570 break inner;
571 }
572 }
573 parent = dir;
574 }
575 }
576
577 /***
578 * Deletes a file or directory.
579 *
580 * <p>A directory must be empty at the time it is deleted.</p>
581 *
582 * @param path the path of the file or directory
583 * @throws IOException if the operation fails.
584 */
585 public void delete(String path) throws IOException
586 {
587 if (!exists(path))
588 {
589 throw new IOException(path + " does not exist");
590 }
591 String dir = directoryPath(path);
592 if (!canWrite(dir))
593 {
594 throw new IOException(dir + " : access denied");
595 }
596 for (Iterator i = providers.iterator(); i.hasNext();)
597 {
598 FileSystemProvider fp = (FileSystemProvider)i.next();
599 if (fp.exists(dir))
600 {
601 fp.delete(path);
602 return;
603 }
604 }
605 }
606
607 /***
608 * Deletes recursive a directory.
609 *
610 * @param path the path of the directory.
611 * @throws IOException if the operation fails.
612 */
613 public void deleteRecursive(String path) throws IOException
614 {
615 if (!exists(path))
616 {
617 throw new IOException(path + " does not exist");
618 }
619 String[] files = list(path);
620 for(int i = 0; i < files.length;i++)
621 {
622 if(isDirectory(path+"/"+files[i]))
623 {
624 deleteRecursive(path + "/" + files[i]);
625 }
626 else
627 {
628 delete(path+"/"+files[i]);
629 }
630 }
631 String dir = directoryPath(path);
632 if (!canWrite(dir))
633 {
634 throw new IOException(dir + " : access denied");
635 }
636 for (Iterator i = providers.iterator(); i.hasNext();)
637 {
638 FileSystemProvider fp = (FileSystemProvider)i.next();
639 if (fp.exists(dir))
640 {
641 fp.delete(path);
642 return;
643 }
644 }
645 }
646
647 /***
648 * Atomically renames a file or directory.
649 *
650 * @param from source path.
651 * @param to destination path.
652 * @throws UnsupportedCharactersInFilePathException if the given destination path contains
653 * characters incompatible with underlying filesystem.
654 * @throws IOException if the operation fails.
655 */
656 public void rename(String from, String to)
657 throws IOException, UnsupportedCharactersInFilePathException
658 {
659 if (!exists(from))
660 {
661 throw new IOException("source file " + from + " does not exist");
662 }
663 String dir = directoryPath(to);
664 if (!exists(dir))
665 {
666 throw new IOException("destination directory " + dir + " does not exist");
667 }
668 if (!canWrite(dir))
669 {
670 throw new IOException(dir + " : access denied");
671 }
672 for (Iterator i = providers.iterator(); i.hasNext();)
673 {
674 FileSystemProvider fp = (FileSystemProvider)i.next();
675 if (fp.exists(dir))
676 {
677 fp.rename(from, to);
678 return;
679 }
680 }
681 }
682
683 /***
684 * Copies a file from one location to another.
685 *
686 * @param from the source path.
687 * @param to the destination path.
688 * @throws UnsupportedCharactersInFilePathException if the given destination path contains
689 * characters incompatible with underlying filesystem.
690 * @throws IOException if the operation fails.
691 */
692 public void copyFile(String from, String to)
693 throws IOException, UnsupportedCharactersInFilePathException
694 {
695 InputStream in = getInputStream(from);
696 if (in == null)
697 {
698 throw new IOException("failed to open source file " + from);
699 }
700 String dir = directoryPath(to);
701 mkdirs(dir);
702 OutputStream out = getOutputStream(to);
703 if (out == null)
704 {
705 throw new IOException("failed to open destination file " + to);
706 }
707 byte[] buffer = new byte[bufferSize];
708 int count = 0;
709 do
710 {
711 count = in.read(buffer, 0, bufferSize);
712 if (count > 0)
713 {
714 out.write(buffer, 0, count);
715 }
716 }
717 while (count > 0);
718 out.flush();
719 }
720
721 /***
722 * Copies the contents of a directory to another location.
723 *
724 * @param src source directory.
725 * @param dst destination directory.
726 * @throws UnsupportedCharactersInFilePathException if the given destination path contains
727 * characters incompatible with underlying filesystem.
728 * @throws IOException if the operation fails.
729 */
730 public void copyDir(String src, String dst)
731 throws IOException, UnsupportedCharactersInFilePathException
732 {
733 if (!exists(src))
734 {
735 throw new IOException("source directory " + src + " does not exist");
736 }
737 if (!canRead(src))
738 {
739 throw new IOException("source directory " + src + " is not readable");
740 }
741 if (!isDirectory(src))
742 {
743 throw new IOException(src + " is not a directory");
744 }
745
746 mkdirs(dst);
747 String[] srcFiles = list(src);
748 for (int i = 0; i < srcFiles.length; i++)
749 {
750 String name = srcFiles[i];
751 if (isDirectory(src + "/" + name))
752 {
753 copyDir(src + "/" + name, dst + "/" + name);
754 }
755 else
756 {
757 copyFile(src + "/" + name, dst + "/" + name);
758 }
759 }
760 }
761
762 /***
763 * Read the contents of a file and write them into an OutputStream.
764 *
765 * @param path the pathname of the file.
766 * @param out the stream to write file contents to.
767 * @throws IOException if the operation fails.
768 */
769 public void read(String path, OutputStream out) throws IOException
770 {
771 InputStream ins = getInputStream(path);
772 if (ins == null)
773 {
774 throw new IOException(path + " does not exist");
775 }
776 byte[] buffer = new byte[bufferSize];
777 int count = 0;
778 do
779 {
780 count = ins.read(buffer, 0, bufferSize);
781 if (count > 0)
782 {
783 out.write(buffer, 0, count);
784 }
785 }
786 while (count > 0);
787 out.flush();
788 }
789
790 /***
791 * Read the contents of a file into a byte array.
792 *
793 * @param path the pathnamame of the file.
794 * @return the contents of the file.
795 * @throws IOException if the operation fails.
796 */
797 public byte[] read(String path) throws IOException
798 {
799 InputStream in = getInputStream(path);
800 if (in == null)
801 {
802 throw new IOException(path + " does not exist");
803 }
804 long length = length(path);
805 if (length < 0)
806 {
807 length = bufferSize;
808 }
809 if (length > maxReadSize)
810 {
811 throw new IOException(path + " is too large (" + length + "b)");
812 }
813 ByteArrayOutputStream out = new ByteArrayOutputStream((int)length);
814 read(path, out);
815 return out.toByteArray();
816 }
817
818 /***
819 * Read the contents of a file into a String.
820 *
821 * @param path the pathnamame of the file.
822 * @param encoding the character encoding to use for decoding bytes into
823 * Unicode characters.
824 * @return the contents of the file.
825 * @throws IOException if the file cannot be read, or the specified encoding
826 * is not supported
827 */
828 public String read(String path, String encoding)
829 throws IOException
830 {
831 if(!exists(path))
832 {
833 throw new IOException(path + " does not exist");
834 }
835 long length = length(path);
836 if (length < 0)
837 {
838 length = bufferSize;
839 }
840 if (length > maxReadSize)
841 {
842 throw new IOException(path + " is too large (" + length + "b)");
843 }
844 Reader in = null;
845 try
846 {
847 InputStream ins = getInputStream(path);
848 in = new InputStreamReader(ins, encoding);
849 StringWriter out = new StringWriter((int)length);
850 char[] buffer = new char[bufferSize];
851 int count = 0;
852 do
853 {
854 count = in.read(buffer, 0, bufferSize);
855 if (count > 0)
856 {
857 out.write(buffer, 0, count);
858 }
859 }
860 while (count > 0);
861 return out.toString();
862 }
863 finally
864 {
865 if(in != null)
866 {
867 in.close();
868 }
869 }
870 }
871
872 /***
873 * Write the data read from an InputStream into a file.
874 *
875 * @param path the pathname of the file
876 * @param in the steram to read data from.
877 * @throws IOException if the opreation fails.
878 */
879 public void write(String path, InputStream in) throws IOException
880 {
881 OutputStream out = getOutputStream(path);
882 if (out == null)
883 {
884 throw new IOException("failed to open output file " + path);
885 }
886 try
887 {
888 byte[] buffer = new byte[bufferSize];
889 int count = 0;
890 do
891 {
892 count = in.read(buffer, 0, bufferSize);
893 if (count > 0)
894 {
895 out.write(buffer, 0, count);
896 }
897 }
898 while (count > 0);
899 }
900 finally
901 {
902 out.close();
903 }
904 }
905
906 /***
907 * Write contents of a byte array into a file.
908 *
909 * @param path the pathname of the file.
910 * @param bytes the bytes to be written
911 * @throws IOException if the operation fails.
912 */
913 public void write(String path, byte[] bytes) throws IOException
914 {
915 OutputStream out = getOutputStream(path);
916 if (out == null)
917 {
918 throw new IOException("failed to open output file " + path);
919 }
920 try
921 {
922 out.write(bytes);
923 }
924 finally
925 {
926 out.close();
927 }
928 }
929
930 /***
931 * Write contents of a byte array into a file.
932 *
933 * @param path the pathname of the file.
934 * @param string the String to be written
935 * @param encoding the character encoding to use for encoding Unicode
936 * characters into bytes.
937 * @throws IOException if the file cannot be written to, or the specified encoding is not
938 * supported.
939 */
940 public void write(String path, String string, String encoding)
941 throws IOException
942 {
943 OutputStream outs = getOutputStream(path);
944 if (outs == null)
945 {
946 throw new IOException("failed to open output file " + path);
947 }
948 Writer out = new OutputStreamWriter(outs, encoding);
949 try
950 {
951 out.write(string);
952 }
953 finally
954 {
955 out.close();
956 }
957 }
958
959
960
961 /***
962 * Normalizes a pathname.
963 *
964 * <p>This method removes redundant / characters, removes . and .. path elements,
965 * taking care that the paths dont reach outside filesystem root, removes trailing /
966 * from directories and adding leading / as neccessary.</p>
967 *
968 * @param path the path.
969 * @return normalized path.
970 * @throws IllegalArgumentException if the path reaches outside the filesystem root.
971 */
972 public static String normalizedPath(String path)
973 throws IllegalArgumentException
974 {
975 if(path.length()==0 || path.equals("/"))
976 {
977 return "/";
978 }
979 StringTokenizer st = new StringTokenizer(path, "/");
980 ArrayList<String> temp = new ArrayList<String>(st.countTokens());
981 while(st.hasMoreTokens())
982 {
983 String t = st.nextToken();
984 if(t.equals("."))
985 {
986 continue;
987 }
988 else if(t.equals(".."))
989 {
990 if(temp.isEmpty())
991 {
992 throw new IllegalArgumentException("path outside filesystem root: "+path);
993 }
994 else
995 {
996 temp.remove(temp.size()-1);
997 }
998 }
999 else
1000 {
1001 temp.add(t);
1002 }
1003 }
1004 StringBuilder sb = new StringBuilder();
1005 for(int i=0; i<temp.size(); i++)
1006 {
1007 sb.append('/').append((String)temp.get(i));
1008 }
1009 return sb.toString();
1010 }
1011
1012 /***
1013 * Returns the base name of a file.
1014 *
1015 * <p>This method returns the contents of the pathname after the last '/'
1016 * character. </p>
1017 *
1018 * @param path the pathname of the file.
1019 * @return the basename of the file.
1020 */
1021 public static String basePath(String path)
1022 {
1023 int pos = path.lastIndexOf('/');
1024 if(pos < 0)
1025 {
1026 return path;
1027 }
1028 else
1029 {
1030 return path.substring(pos+1);
1031 }
1032 }
1033
1034 /***
1035 * Returns hte directory name of a file.
1036 *
1037 * <p>This method returns the normalized path before the last '/' character
1038 * in the path.</p>
1039 *
1040 * @param path the pathname of the file.
1041 * @return the directory name of the file.
1042 */
1043 public static String directoryPath(String path)
1044 {
1045 path = normalizedPath(path);
1046 return path.substring(0, path.lastIndexOf('/'));
1047 }
1048
1049 /***
1050 * Returns the relative pathname of a file with respect to given
1051 * base directory.
1052 *
1053 * @param path the pathname of a file.
1054 * @param base the base pathname.
1055 * @return the relative pathname.
1056 * @throws IllegalArgumentException if the file is contained
1057 * outside of base.
1058 */
1059 public static String relativePath(String path, String base)
1060 throws IllegalArgumentException
1061 {
1062 base = normalizedPath(base);
1063 path = normalizedPath(path);
1064 if(!path.startsWith(base))
1065 {
1066 throw new IllegalArgumentException(path+" is not contained in "+base);
1067 }
1068 return path.substring(base.length());
1069 }
1070
1071 /***
1072 * Returns <code>true</code> if the given path contains acceptable characters.
1073 *
1074 * @param path the path
1075 * @return <code>true</code> if the given path contains acceptable characters.
1076 */
1077 public boolean checkPathChars(String path)
1078 {
1079 if(path == null || path.length() == 0)
1080 {
1081 return false;
1082 }
1083 for (Iterator i = providers.iterator(); i.hasNext();)
1084 {
1085 FileSystemProvider fp = (FileSystemProvider)i.next();
1086 if (!fp.checkPathChars(path))
1087 {
1088 return false;
1089 }
1090 }
1091 return true;
1092 }
1093
1094
1095
1096 /***
1097 * Creates a standard file system.
1098 *
1099 * <p>The file system is composed of a local file system, with the specified root, augmented
1100 * with a classpath file system.</p>
1101 *
1102 * @param root the local filesystem root.
1103 * @return a standard file system.
1104 */
1105 public static FileSystem getStandardFileSystem(String root)
1106 {
1107 FileSystemProvider lfs = new org.objectledge.filesystem.
1108 LocalFileSystemProvider("local", root);
1109 FileSystemProvider cfs = new org.objectledge.filesystem.
1110 ClasspathFileSystemProvider("classpath",
1111 FileSystem.class.getClassLoader());
1112 return new FileSystem(new FileSystemProvider[] { lfs, cfs }, 4096, 65536);
1113 }
1114
1115 /***
1116 * Creates a classpath file system.
1117 *
1118 * @return a classpath file system.
1119 */
1120 public static FileSystem getClasspathFileSystem()
1121 {
1122 FileSystemProvider cfs = new org.objectledge.filesystem.
1123 ClasspathFileSystemProvider("classpath",
1124 FileSystem.class.getClassLoader());
1125 return new FileSystem(new FileSystemProvider[] { cfs }, 4096, 65536);
1126 }
1127 }