View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.vfs.impl;
18  
19  import java.io.File;
20  import java.net.URLStreamHandler;
21  import java.net.URLStreamHandlerFactory;
22  import java.util.ArrayList;
23  import java.util.Collection;
24  import java.util.HashMap;
25  import java.util.Iterator;
26  import java.util.List;
27  import java.util.Map;
28  import java.lang.reflect.Constructor;
29  
30  import org.apache.commons.logging.Log;
31  import org.apache.commons.vfs.CacheStrategy;
32  import org.apache.commons.vfs.FileContentInfoFactory;
33  import org.apache.commons.vfs.FileName;
34  import org.apache.commons.vfs.FileObject;
35  import org.apache.commons.vfs.FileSystem;
36  import org.apache.commons.vfs.FileSystemConfigBuilder;
37  import org.apache.commons.vfs.FileSystemException;
38  import org.apache.commons.vfs.FileSystemManager;
39  import org.apache.commons.vfs.FileSystemOptions;
40  import org.apache.commons.vfs.FileType;
41  import org.apache.commons.vfs.FilesCache;
42  import org.apache.commons.vfs.NameScope;
43  import org.apache.commons.vfs.VFS;
44  import org.apache.commons.vfs.cache.SoftRefFilesCache;
45  import org.apache.commons.vfs.operations.FileOperationProvider;
46  import org.apache.commons.vfs.provider.AbstractFileName;
47  import org.apache.commons.vfs.provider.AbstractFileProvider;
48  import org.apache.commons.vfs.provider.DefaultURLStreamHandler;
49  import org.apache.commons.vfs.provider.FileProvider;
50  import org.apache.commons.vfs.provider.FileReplicator;
51  import org.apache.commons.vfs.provider.LocalFileProvider;
52  import org.apache.commons.vfs.provider.TemporaryFileStore;
53  import org.apache.commons.vfs.provider.UriParser;
54  import org.apache.commons.vfs.provider.VfsComponent;
55  
56  /***
57   * A default file system manager implementation.
58   *
59   * @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a>
60   * @version $Revision: 484648 $ $Date: 2006-03-30 21:16:24 +0200 (Do, 30 Mrz
61   *          2006) $
62   */
63  public class DefaultFileSystemManager implements FileSystemManager
64  {
65  	/***
66  	 * The provider for local files.
67  	 */
68  	private LocalFileProvider localFileProvider;
69  
70  	/***
71  	 * The default provider.
72  	 */
73  	private FileProvider defaultProvider;
74  
75  	/***
76  	 * The file replicator to use.
77  	 */
78  	private FileReplicator fileReplicator;
79  
80  	/***
81  	 * Mapping from URI scheme to FileProvider.
82  	 */
83  	private final Map providers = new HashMap();
84  
85  	/***
86  	 * All components used by this manager.
87  	 */
88  	private final ArrayList components = new ArrayList();
89  
90  	/***
91  	 * The base file to use for relative URI.
92  	 */
93  	private FileObject baseFile;
94  
95      /***
96  	 * The files cache
97  	 */
98  	private FilesCache filesCache;
99  
100 	/***
101 	 * The cache strategy
102 	 */
103 	private CacheStrategy fileCacheStrategy;
104 
105     /***
106      * Class which decorates all returned fileObjects
107      */
108     private Class fileObjectDecorator;
109     private Constructor fileObjectDecoratorConst;
110 
111     /***
112 	 * The class to use to determine the content-type (mime-type)
113 	 */
114 	private FileContentInfoFactory fileContentInfoFactory;
115 
116 	/***
117 	 * The logger to use.
118 	 */
119 	private Log log;
120 
121 	/***
122 	 * The context to pass to providers.
123 	 */
124 	private final DefaultVfsComponentContext context = new DefaultVfsComponentContext(
125 			this);
126 
127 	private TemporaryFileStore tempFileStore;
128 	private final FileTypeMap map = new FileTypeMap();
129 	private final VirtualFileProvider vfsProvider = new VirtualFileProvider();
130 	private boolean init;
131 
132 	/***
133 	 * Returns the logger used by this manager.
134 	 */
135 	protected Log getLogger()
136 	{
137 		return log;
138 	}
139 
140 	/***
141 	 * Registers a file system provider. The manager takes care of all lifecycle
142 	 * management. A provider may be registered multiple times.
143 	 *
144 	 * @param urlScheme
145 	 *            The scheme the provider will handle.
146 	 * @param provider
147 	 *            The provider.
148 	 */
149 	public void addProvider(final String urlScheme, final FileProvider provider)
150 			throws FileSystemException
151 	{
152 		addProvider(new String[]
153 		{ urlScheme }, provider);
154 	}
155 
156 	/***
157 	 * Registers a file system provider. The manager takes care of all lifecycle
158 	 * management. A provider may be registered multiple times.
159 	 *
160 	 * @param urlSchemes
161 	 *            The schemes the provider will handle.
162 	 * @param provider
163 	 *            The provider.
164 	 */
165 	public void addProvider(final String[] urlSchemes,
166 			final FileProvider provider) throws FileSystemException
167 	{
168 		// Warn about duplicate providers
169 		for (int i = 0; i < urlSchemes.length; i++)
170 		{
171 			final String scheme = urlSchemes[i];
172 			if (providers.containsKey(scheme))
173 			{
174 				throw new FileSystemException(
175 						"vfs.impl/multiple-providers-for-scheme.error", scheme);
176 			}
177 		}
178 
179 		// Contextualise the component (if not already)
180 		setupComponent(provider);
181 
182 		// Add to map
183 		for (int i = 0; i < urlSchemes.length; i++)
184 		{
185 			final String scheme = urlSchemes[i];
186 			providers.put(scheme, provider);
187 		}
188 
189 		if (provider instanceof LocalFileProvider)
190 		{
191 			localFileProvider = (LocalFileProvider) provider;
192 		}
193 	}
194 
195 	/***
196 	 * Returns true if this manager has a provider for a particular scheme.
197 	 */
198 	public boolean hasProvider(final String scheme)
199 	{
200 		return providers.containsKey(scheme);
201 	}
202 
203 	/***
204 	 * Adds an filename extension mapping.
205 	 *
206 	 * @param extension
207 	 *            The file name extension.
208 	 * @param scheme
209 	 *            The scheme to use for files with this extension.
210 	 */
211 	public void addExtensionMap(final String extension, final String scheme)
212 	{
213 		map.addExtension(extension, scheme);
214 	}
215 
216 	/***
217 	 * Adds a mime type mapping.
218 	 *
219 	 * @param mimeType
220 	 *            The mime type.
221 	 * @param scheme
222 	 *            The scheme to use for files with this mime type.
223 	 */
224 	public void addMimeTypeMap(final String mimeType, final String scheme)
225 	{
226 		map.addMimeType(mimeType, scheme);
227 	}
228 
229 	/***
230 	 * Sets the default provider. This is the provider that will handle URI with
231 	 * unknown schemes. The manager takes care of all lifecycle management.
232 	 */
233 	public void setDefaultProvider(final FileProvider provider)
234 			throws FileSystemException
235 	{
236 		setupComponent(provider);
237 		defaultProvider = provider;
238 	}
239 
240 	/***
241 	 * Returns the filesCache implementation used to cache files
242 	 */
243 	public FilesCache getFilesCache()
244 	{
245 		return filesCache;
246 	}
247 
248 	/***
249 	 * Sets the filesCache implementation used to cache files
250 	 */
251 	public void setFilesCache(final FilesCache filesCache)
252 			throws FileSystemException
253 	{
254 		if (init)
255 		{
256 			throw new FileSystemException("vfs.impl/already-inited.error");
257 		}
258 
259 		this.filesCache = filesCache;
260 	}
261 
262 	/***
263 	 * <p>
264 	 * Set the cache strategy to use when dealing with file object data. You can
265 	 * set it only once before the FileSystemManager is initialized.
266 	 * <p />
267 	 * <p>
268 	 * The default is {@link CacheStrategy#ON_RESOLVE}
269 	 * </p>
270 	 *
271 	 * @throws FileSystemException
272 	 *             if this is not possible. e.g. it is already set.
273 	 */
274 	public void setCacheStrategy(final CacheStrategy fileCacheStrategy)
275 			throws FileSystemException
276 	{
277 		if (init)
278 		{
279 			throw new FileSystemException("vfs.impl/already-inited.error");
280 		}
281 
282 		this.fileCacheStrategy = fileCacheStrategy;
283 	}
284 
285 	/***
286 	 * Get the cache strategy used
287 	 */
288 	public CacheStrategy getCacheStrategy()
289 	{
290 		return fileCacheStrategy;
291 	}
292 
293     /***
294      * Get the file object decorator used
295      */
296     public Class getFileObjectDecorator()
297     {
298         return fileObjectDecorator;
299     }
300 
301     /***
302      * The constructor associated to the fileObjectDecorator.
303      * We cache it here for performance reasons.
304      */
305     public Constructor getFileObjectDecoratorConst()
306     {
307         return fileObjectDecoratorConst;
308     }
309 
310     /***
311      * set a fileObject decorator to be used for ALL returned file objects
312      *
313      * @param fileObjectDecorator must be inherted from {@link DecoratedFileObject} a has to provide a
314      * constructor with a single {@link FileObject} as argument
315      */
316     public void setFileObjectDecorator(Class fileObjectDecorator) throws FileSystemException
317     {
318         if (init)
319         {
320             throw new FileSystemException("vfs.impl/already-inited.error");
321         }
322         if (!DecoratedFileObject.class.isAssignableFrom(fileObjectDecorator))
323         {
324             throw new FileSystemException("vfs.impl/invalid-decorator.error", fileObjectDecorator.getName());
325         }
326 
327         try
328         {
329             fileObjectDecoratorConst = fileObjectDecorator.getConstructor(new Class[]{FileObject.class});
330         }
331         catch (NoSuchMethodException e)
332         {
333             throw new FileSystemException("vfs.impl/invalid-decorator.error", fileObjectDecorator.getName(), e);
334         }
335 
336         this.fileObjectDecorator = fileObjectDecorator;
337     }
338 
339     /***
340 	 * get the fileContentInfoFactory used to determine the infos of a file
341 	 * content.
342 	 */
343 	public FileContentInfoFactory getFileContentInfoFactory()
344 	{
345 		return fileContentInfoFactory;
346 	}
347 
348 	/***
349 	 * set the fileContentInfoFactory used to determine the infos of a file
350 	 * content.
351 	 */
352 	public void setFileContentInfoFactory(
353 			FileContentInfoFactory fileContentInfoFactory)
354 			throws FileSystemException
355 	{
356 		if (init)
357 		{
358 			throw new FileSystemException("vfs.impl/already-inited.error");
359 		}
360 
361 		this.fileContentInfoFactory = fileContentInfoFactory;
362 	}
363 
364 	/***
365 	 * Sets the file replicator to use. The manager takes care of all lifecycle
366 	 * management.
367 	 */
368 	public void setReplicator(final FileReplicator replicator)
369 			throws FileSystemException
370 	{
371 		setupComponent(replicator);
372 		fileReplicator = replicator;
373 	}
374 
375 	/***
376 	 * Sets the temporary file store to use. The manager takes care of all
377 	 * lifecycle management.
378 	 */
379 	public void setTemporaryFileStore(final TemporaryFileStore tempFileStore)
380 			throws FileSystemException
381 	{
382 		setupComponent(tempFileStore);
383 		this.tempFileStore = tempFileStore;
384 	}
385 
386 	/***
387 	 * Sets the logger to use.
388 	 */
389 	public void setLogger(final Log log)
390 	{
391 		this.log = log;
392 	}
393 
394 	/***
395 	 * Initialises a component, if it has not already been initialised.
396 	 */
397 	private void setupComponent(final Object component)
398 			throws FileSystemException
399 	{
400 		if (!components.contains(component))
401 		{
402 			if (component instanceof VfsComponent)
403 			{
404 				final VfsComponent vfsComponent = (VfsComponent) component;
405 				vfsComponent.setLogger(getLogger());
406 				vfsComponent.setContext(context);
407 				vfsComponent.init();
408 			}
409 			components.add(component);
410 		}
411 	}
412 
413 	/***
414 	 * Closes a component, if it has not already been closed.
415 	 */
416 	private void closeComponent(final Object component)
417 	{
418 		if (component != null && components.contains(component))
419 		{
420 			if (component instanceof VfsComponent)
421 			{
422 				final VfsComponent vfsComponent = (VfsComponent) component;
423 				vfsComponent.close();
424 			}
425 			components.remove(component);
426 		}
427 	}
428 
429 	/***
430 	 * Returns the file replicator.
431 	 *
432 	 * @return The file replicator. Never returns null.
433 	 */
434 	public FileReplicator getReplicator() throws FileSystemException
435 	{
436 		if (fileReplicator == null)
437 		{
438 			throw new FileSystemException("vfs.impl/no-replicator.error");
439 		}
440 		return fileReplicator;
441 	}
442 
443 	/***
444 	 * Returns the temporary file store.
445 	 *
446 	 * @return The file store. Never returns null.
447 	 */
448 	public TemporaryFileStore getTemporaryFileStore()
449 			throws FileSystemException
450 	{
451 		if (tempFileStore == null)
452 		{
453 			throw new FileSystemException("vfs.impl/no-temp-file-store.error");
454 		}
455 		return tempFileStore;
456 	}
457 
458 	/***
459 	 * Initialises this manager.
460 	 */
461 	public void init() throws FileSystemException
462 	{
463 		if (filesCache == null)
464 		{
465 			// filesCache = new DefaultFilesCache();
466 			filesCache = new SoftRefFilesCache();
467 		}
468 		if (fileContentInfoFactory == null)
469 		{
470 			fileContentInfoFactory = new FileContentInfoFilenameFactory();
471 		}
472 
473 		if (fileCacheStrategy == null)
474 		{
475 			fileCacheStrategy = CacheStrategy.ON_RESOLVE;
476 		}
477 
478 		setupComponent(filesCache);
479 		setupComponent(vfsProvider);
480 
481 		init = true;
482 	}
483 
484 	/***
485 	 * Closes all files created by this manager, and cleans up any temporary
486 	 * files. Also closes all providers and the replicator.
487 	 */
488 	public void close()
489 	{
490 		if (!init)
491 		{
492 			return;
493 		}
494 
495 		// Close the providers.
496 		for (Iterator iterator = providers.values().iterator(); iterator
497 				.hasNext();)
498 		{
499 			final Object provider = iterator.next();
500 			closeComponent(provider);
501 		}
502 
503 		// Close the other components
504 		closeComponent(defaultProvider);
505 		closeComponent(fileReplicator);
506 		closeComponent(tempFileStore);
507 
508 		components.clear();
509 		providers.clear();
510 		filesCache.close();
511 		localFileProvider = null;
512 		defaultProvider = null;
513 		fileReplicator = null;
514 		tempFileStore = null;
515 		init = false;
516 	}
517 
518 	/***
519 	 * Free all resources used by unused filesystems created by this manager.
520 	 */
521 	public void freeUnusedResources()
522 	{
523 		if (!init)
524 		{
525 			return;
526 		}
527 
528 		// Close the providers.
529 		for (Iterator iterator = providers.values().iterator(); iterator
530 				.hasNext();)
531 		{
532 			final AbstractFileProvider provider = (AbstractFileProvider) iterator
533 					.next();
534 			provider.freeUnusedResources();
535 		}
536 	}
537 
538 	/***
539 	 * Sets the base file to use when resolving relative URI.
540 	 */
541 	// public void setBaseFile(final FileObject baseFile)
542 	public void setBaseFile(final FileObject baseFile)
543 			throws FileSystemException
544 	{
545 		this.baseFile = baseFile;
546 	}
547 
548 	/***
549 	 * Sets the base file to use when resolving relative URI.
550 	 */
551 	public void setBaseFile(final File baseFile) throws FileSystemException
552 	{
553 		this.baseFile = getLocalFileProvider().findLocalFile(baseFile);
554 	}
555 
556 	/***
557 	 * Returns the base file used to resolve relative URI.
558 	 */
559 	public FileObject getBaseFile() throws FileSystemException
560 	{
561 		return baseFile;
562 	}
563 
564 	/***
565 	 * Locates a file by URI.
566 	 */
567 	public FileObject resolveFile(final String uri) throws FileSystemException
568 	{
569 		// return resolveFile(baseFile, uri);
570 		return resolveFile(getBaseFile(), uri);
571 	}
572 
573 	/***
574 	 * Locate a file by URI, use the FileSystemOptions for file-system creation
575 	 */
576 
577 	public FileObject resolveFile(final String uri,
578 			final FileSystemOptions fileSystemOptions)
579 			throws FileSystemException
580 	{
581 		// return resolveFile(baseFile, uri, fileSystemOptions);
582 		return resolveFile(getBaseFile(), uri, fileSystemOptions);
583 	}
584 
585 	/***
586 	 * Locates a file by URI.
587 	 */
588 	public FileObject resolveFile(final File baseFile, final String uri)
589 			throws FileSystemException
590 	{
591 		final FileObject baseFileObj = getLocalFileProvider().findLocalFile(
592 				baseFile);
593 		return resolveFile(baseFileObj, uri);
594 	}
595 
596 	/***
597 	 * Resolves a URI, relative to a base file.
598 	 */
599 	public FileObject resolveFile(final FileObject baseFile, final String uri)
600 			throws FileSystemException
601 	{
602 		return resolveFile(baseFile, uri, baseFile == null ? null : baseFile
603 				.getFileSystem().getFileSystemOptions());
604 	}
605 
606 	/***
607 	 * Resolves a URI, realtive to a base file with specified FileSystem
608 	 * configuration
609 	 */
610 	public FileObject resolveFile(final FileObject baseFile, final String uri,
611 			final FileSystemOptions fileSystemOptions)
612 			throws FileSystemException
613 	{
614 		final FileObject realBaseFile;
615 		if (baseFile != null && VFS.isUriStyle()
616 				&& baseFile.getName().getType() == FileType.FILE)
617 		{
618 			realBaseFile = baseFile.getParent();
619 		}
620 		else
621 		{
622 			realBaseFile = baseFile;
623 		}
624 		// TODO: use resolveName and use this name to resolve the fileObject
625 
626 		UriParser.checkUriEncoding(uri);
627 
628 		if (uri == null)
629 		{
630 			throw new IllegalArgumentException();
631 		}
632 
633 		// Extract the scheme
634 		final String scheme = UriParser.extractScheme(uri);
635 		if (scheme != null)
636 		{
637 			// An absolute URI - locate the provider
638 			final FileProvider provider = (FileProvider) providers.get(scheme);
639 			if (provider != null)
640 			{
641 				return provider.findFile(realBaseFile, uri, fileSystemOptions);
642 			}
643 			// Otherwise, assume a local file
644 		}
645 
646 		// Handle absolute file names
647 		if (localFileProvider != null
648 				&& localFileProvider.isAbsoluteLocalName(uri))
649 		{
650 			return localFileProvider.findLocalFile(uri);
651 		}
652 
653 		if (scheme != null)
654 		{
655 			// An unknown scheme - hand it to the default provider
656 			if (defaultProvider == null)
657 			{
658 				throw new FileSystemException("vfs.impl/unknown-scheme.error",
659 						new Object[]
660 						{ scheme, uri });
661 			}
662 			return defaultProvider.findFile(realBaseFile, uri,
663 					fileSystemOptions);
664 		}
665 
666 		// Assume a relative name - use the supplied base file
667 		if (realBaseFile == null)
668 		{
669 			throw new FileSystemException("vfs.impl/find-rel-file.error", uri);
670 		}
671 
672 		return realBaseFile.resolveFile(uri);
673 	}
674 
675 	/***
676 	 * Resolves a name, relative to the file. If the supplied name is an
677 	 * absolute path, then it is resolved relative to the root of the file
678 	 * system that the file belongs to. If a relative name is supplied, then it
679 	 * is resolved relative to this file name.
680 	 */
681 	public FileName resolveName(final FileName root, final String path)
682 			throws FileSystemException
683 	{
684 		return resolveName(root, path, NameScope.FILE_SYSTEM);
685 	}
686 
687 	/***
688 	 * Resolves a name, relative to the root.
689 	 *
690 	 * @param base
691 	 *            the base filename
692 	 * @param name
693 	 *            the name
694 	 * @param scope
695 	 *            the {@link NameScope}
696 	 * @throws FileSystemException
697 	 */
698 	public FileName resolveName(final FileName base, final String name,
699 			final NameScope scope) throws FileSystemException
700 	{
701 		final FileName realBase;
702 		if (base != null && VFS.isUriStyle() && base.getType() == FileType.FILE)
703 		{
704 			realBase = base.getParent();
705 		}
706 		else
707 		{
708 			realBase = base;
709 		}
710 
711 		final StringBuffer buffer = new StringBuffer(name);
712 
713 		// Adjust separators
714 		UriParser.fixSeparators(buffer);
715 
716 		// Determine whether to prepend the base path
717 		if (name.length() == 0 || name.charAt(0) != FileName.SEPARATOR_CHAR)
718 		{
719 			// Supplied path is not absolute
720 			if (!VFS.isUriStyle())
721 			{
722 				// when using uris the parent already do have the trailing "/"
723 				buffer.insert(0, FileName.SEPARATOR_CHAR);
724 			}
725 			buffer.insert(0, realBase.getPath());
726 		}
727 
728 		// // UriParser.canonicalizePath(buffer, 0, name.length());
729 
730 		// Normalise the path
731 		FileType fileType = UriParser.normalisePath(buffer);
732 
733 		// Check the name is ok
734 		final String resolvedPath = buffer.toString();
735 		if (!AbstractFileName
736 				.checkName(realBase.getPath(), resolvedPath, scope))
737 		{
738 			throw new FileSystemException(
739 					"vfs.provider/invalid-descendent-name.error", name);
740 		}
741 
742 		String scheme = realBase.getScheme();
743 		String fullPath = realBase.getRootURI() + resolvedPath;
744 		final FileProvider provider = (FileProvider) providers.get(scheme);
745 		if (provider != null)
746 		{
747 			// todo: extend the filename parser to be able to parse
748 			// only a pathname and take the missing informations from
749 			// the base. Then we can get rid of the string operation.
750 			// // String fullPath = base.getRootURI() +
751 			// resolvedPath.substring(1);
752 
753 			return provider.parseUri(realBase, fullPath);
754 		}
755 
756 		if (scheme != null)
757 		{
758 			// An unknown scheme - hand it to the default provider - if possible
759 			if (defaultProvider != null)
760 			{
761 				return defaultProvider.parseUri(realBase, fullPath);
762 			}
763 		}
764 
765 		// todo: avoid fallback to this point
766 		// this happens if we have a virtual filesystem (no provider for scheme)
767 		return ((AbstractFileName) realBase).createName(resolvedPath, fileType);
768 	}
769 
770 	/***
771 	 * resolve the uri to a filename
772 	 *
773 	 * @throws FileSystemException
774 	 */
775 	public FileName resolveURI(String uri) throws FileSystemException
776 	{
777 		UriParser.checkUriEncoding(uri);
778 
779 		if (uri == null)
780 		{
781 			throw new IllegalArgumentException();
782 		}
783 
784 		// Extract the scheme
785 		final String scheme = UriParser.extractScheme(uri);
786 		if (scheme != null)
787 		{
788 			// An absolute URI - locate the provider
789 			final FileProvider provider = (FileProvider) providers.get(scheme);
790 			if (provider != null)
791 			{
792 				return provider.parseUri(null, uri);
793 			}
794 
795 			// Otherwise, assume a local file
796 		}
797 
798 		// Handle absolute file names
799 		if (localFileProvider != null
800 				&& localFileProvider.isAbsoluteLocalName(uri))
801 		{
802 			return localFileProvider.parseUri(null, uri);
803 		}
804 
805 		if (scheme != null)
806 		{
807 			// An unknown scheme - hand it to the default provider
808 			if (defaultProvider == null)
809 			{
810 				throw new FileSystemException("vfs.impl/unknown-scheme.error",
811 						new Object[]
812 						{ scheme, uri });
813 			}
814 			return defaultProvider.parseUri(null, uri);
815 		}
816 
817 		// Assume a relative name - use the supplied base file
818 		if (baseFile == null)
819 		{
820 			throw new FileSystemException("vfs.impl/find-rel-file.error", uri);
821 		}
822 
823 		return resolveName(baseFile.getName(), uri, NameScope.FILE_SYSTEM);
824 	}
825 
826 	/***
827 	 * Converts a local file into a {@link FileObject}.
828 	 */
829 	public FileObject toFileObject(final File file) throws FileSystemException
830 	{
831 		return getLocalFileProvider().findLocalFile(file);
832 	}
833 
834 	/***
835 	 * Creates a layered file system.
836 	 */
837 	public FileObject createFileSystem(final String scheme,
838 			final FileObject file) throws FileSystemException
839 	{
840 		final FileProvider provider = (FileProvider) providers.get(scheme);
841 		if (provider == null)
842 		{
843 			throw new FileSystemException("vfs.impl/unknown-provider.error",
844 					new Object[]
845 					{ scheme, file });
846 		}
847 		return provider.createFileSystem(scheme, file, file.getFileSystem()
848 				.getFileSystemOptions());
849 	}
850 
851 	/***
852 	 * Creates a layered file system.
853 	 */
854 	public FileObject createFileSystem(final FileObject file)
855 			throws FileSystemException
856 	{
857 		final String scheme = map.getScheme(file);
858 		if (scheme == null)
859 		{
860 			throw new FileSystemException(
861 					"vfs.impl/no-provider-for-file.error", file);
862 		}
863 
864 		return createFileSystem(scheme, file);
865 	}
866 
867 	/***
868 	 * Determines if a layered file system can be created for a given file.
869 	 *
870 	 * @param file
871 	 *            The file to check for.
872 	 */
873 	public boolean canCreateFileSystem(final FileObject file)
874 			throws FileSystemException
875 	{
876 		return (map.getScheme(file) != null);
877 	}
878 
879 	/***
880 	 * Creates a virtual file system.
881 	 */
882 	public FileObject createVirtualFileSystem(final FileObject rootFile)
883 			throws FileSystemException
884 	{
885 		return vfsProvider.createFileSystem(rootFile);
886 	}
887 
888 	/***
889 	 * Creates an empty virtual file system.
890 	 */
891 	public FileObject createVirtualFileSystem(final String rootUri)
892 			throws FileSystemException
893 	{
894 		return vfsProvider.createFileSystem(rootUri);
895 	}
896 
897 	/***
898 	 * Locates the local file provider.
899 	 */
900 	private LocalFileProvider getLocalFileProvider() throws FileSystemException
901 	{
902 		if (localFileProvider == null)
903 		{
904 			throw new FileSystemException(
905 					"vfs.impl/no-local-file-provider.error");
906 		}
907 		return localFileProvider;
908 	}
909 
910 	/***
911 	 * Get the URLStreamHandlerFactory.
912 	 */
913 	public URLStreamHandlerFactory getURLStreamHandlerFactory()
914 	{
915 		return new VfsStreamHandlerFactory();
916 	}
917 
918 	/***
919 	 * Closes the given filesystem.<br />
920 	 * If you use VFS as singleton it is VERY dangerous to call this method
921 	 */
922 	public void closeFileSystem(FileSystem filesystem)
923 	{
924 		// inform the cache ...
925 		getFilesCache().clear(filesystem);
926 
927 		// just in case the cache didnt call _closeFileSystem
928 		_closeFileSystem(filesystem);
929 	}
930 
931 	/***
932 	 * Closes the given filesystem.<br />
933 	 * If you use VFS as singleton it is VERY dangerous to call this method
934 	 */
935 	public void _closeFileSystem(FileSystem filesystem)
936 	{
937 		FileProvider provider = (FileProvider) providers.get(filesystem
938 				.getRootName().getScheme());
939 		if (provider != null)
940 		{
941 			((AbstractFileProvider) provider).closeFileSystem(filesystem);
942 		}
943 	}
944 
945 	/***
946 	 * This is an internal class because it needs access to the private member
947 	 * providers.
948 	 */
949 	final class VfsStreamHandlerFactory implements URLStreamHandlerFactory
950 	{
951 		public URLStreamHandler createURLStreamHandler(final String protocol)
952 		{
953 			FileProvider provider = (FileProvider) providers.get(protocol);
954 			if (provider != null)
955 			{
956 				return new DefaultURLStreamHandler(context);
957 			}
958 
959 			// Route all other calls to the default URLStreamHandlerFactory
960 			return new URLStreamHandlerProxy();
961 		}
962 	}
963 
964 	/***
965 	 * Get the schemes currently available.
966 	 */
967 	public String[] getSchemes()
968 	{
969 		String[] schemes = new String[providers.size()];
970 		providers.keySet().toArray(schemes);
971 		return schemes;
972 	}
973 
974 	/***
975 	 * Get the capabilities for a given scheme.
976 	 *
977 	 * @throws FileSystemException
978 	 *             if the given scheme is not konwn
979 	 */
980 	public Collection getProviderCapabilities(final String scheme)
981 			throws FileSystemException
982 	{
983 		FileProvider provider = (FileProvider) providers.get(scheme);
984 		if (provider == null)
985 		{
986 			throw new FileSystemException("vfs.impl/unknown-scheme.error",
987 					new Object[]
988 					{ scheme });
989 		}
990 
991 		return provider.getCapabilities();
992 	}
993 
994 	/***
995 	 * Get the configuration builder for the given scheme
996 	 *
997 	 * @throws FileSystemException
998 	 *             if the given scheme is not konwn
999 	 */
1000 	public FileSystemConfigBuilder getFileSystemConfigBuilder(
1001 			final String scheme) throws FileSystemException
1002 	{
1003 		FileProvider provider = (FileProvider) providers.get(scheme);
1004 		if (provider == null)
1005 		{
1006 			throw new FileSystemException("vfs.impl/unknown-scheme.error",
1007 					new Object[]
1008 					{ scheme });
1009 		}
1010 
1011 		return provider.getConfigBuilder();
1012 	}
1013 
1014 	// -- OPERATIONS --
1015 
1016 	private final Map operationProviders = new HashMap();
1017 
1018 	/***
1019 	 * Adds the specified FileOperationProvider for the specified scheme.
1020 	 * Several FileOperationProvider's might be registered for the same scheme.
1021 	 * For example, for "file" scheme we can register SvnWsOperationProvider and
1022 	 * CvsOperationProvider.
1023 	 *
1024 	 * @param scheme
1025 	 * @param operationProvider
1026 	 * @throws FileSystemException
1027 	 */
1028 	public void addOperationProvider(final String scheme,
1029 			final FileOperationProvider operationProvider)
1030 			throws FileSystemException
1031 	{
1032 		addOperationProvider(new String[]
1033 		{ scheme }, operationProvider);
1034 	}
1035 
1036 	/***
1037 	 * @see FileSystemManager#addOperationProvider(String,
1038 	 *      org.apache.commons.vfs.operations.FileOperationProvider)
1039 	 *
1040 	 * @param schemes
1041 	 * @param operationProvider
1042 	 * @throws FileSystemException
1043 	 */
1044 	public void addOperationProvider(final String[] schemes,
1045 			final FileOperationProvider operationProvider)
1046 			throws FileSystemException
1047 	{
1048 		for (int i = 0; i < schemes.length; i++)
1049 		{
1050 			final String scheme = schemes[i];
1051 
1052 			if (!operationProviders.containsKey(scheme))
1053 			{
1054 				final List providers = new ArrayList();
1055 				operationProviders.put(scheme, providers);
1056 			}
1057 
1058 			final List providers = (List) operationProviders.get(scheme);
1059 
1060 			if (providers.contains(operationProvider))
1061 			{
1062 				throw new FileSystemException(
1063 						"vfs.operation/operation-provider-already-added.error",
1064 						scheme);
1065 			}
1066 
1067 			setupComponent(operationProvider);
1068 
1069 			providers.add(operationProvider);
1070 		}
1071 	}
1072 
1073 	/***
1074 	 * @param scheme
1075 	 *            the scheme for wich we want to get the list af registered
1076 	 *            providers.
1077 	 *
1078 	 * @return the registered FileOperationProviders for the specified scheme.
1079 	 *         If there were no providers registered for the scheme, it returns
1080 	 *         null.
1081 	 *
1082 	 * @throws FileSystemException
1083 	 */
1084 	public FileOperationProvider[] getOperationProviders(final String scheme)
1085 			throws FileSystemException
1086 	{
1087 
1088 		List providers = (List) operationProviders.get(scheme);
1089 		if (providers == null || providers.size() == 0)
1090 		{
1091 			return null;
1092 		}
1093 		return (FileOperationProvider[]) providers
1094 				.toArray(new FileOperationProvider[] {});
1095 	}
1096 }