001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.vfs2.impl;
018
019import java.io.File;
020import java.lang.reflect.Constructor;
021import java.net.URI;
022import java.net.URISyntaxException;
023import java.net.URL;
024import java.net.URLStreamHandler;
025import java.net.URLStreamHandlerFactory;
026import java.util.ArrayList;
027import java.util.Collection;
028import java.util.HashMap;
029import java.util.List;
030import java.util.Map;
031
032import org.apache.commons.logging.Log;
033import org.apache.commons.logging.LogFactory;
034import org.apache.commons.vfs2.CacheStrategy;
035import org.apache.commons.vfs2.Capability;
036import org.apache.commons.vfs2.FileContentInfoFactory;
037import org.apache.commons.vfs2.FileName;
038import org.apache.commons.vfs2.FileObject;
039import org.apache.commons.vfs2.FileSystem;
040import org.apache.commons.vfs2.FileSystemConfigBuilder;
041import org.apache.commons.vfs2.FileSystemException;
042import org.apache.commons.vfs2.FileSystemManager;
043import org.apache.commons.vfs2.FileSystemOptions;
044import org.apache.commons.vfs2.FileType;
045import org.apache.commons.vfs2.FilesCache;
046import org.apache.commons.vfs2.NameScope;
047import org.apache.commons.vfs2.VFS;
048import org.apache.commons.vfs2.cache.SoftRefFilesCache;
049import org.apache.commons.vfs2.operations.FileOperationProvider;
050import org.apache.commons.vfs2.provider.AbstractFileName;
051import org.apache.commons.vfs2.provider.AbstractFileProvider;
052import org.apache.commons.vfs2.provider.DefaultURLStreamHandler;
053import org.apache.commons.vfs2.provider.FileProvider;
054import org.apache.commons.vfs2.provider.FileReplicator;
055import org.apache.commons.vfs2.provider.LocalFileProvider;
056import org.apache.commons.vfs2.provider.TemporaryFileStore;
057import org.apache.commons.vfs2.provider.UriParser;
058import org.apache.commons.vfs2.provider.VfsComponent;
059
060/**
061 * The default file system manager implementation.
062 */
063public class DefaultFileSystemManager implements FileSystemManager
064{
065    /**
066     * Mapping from URI scheme to FileProvider.
067     */
068    private final Map<String, FileProvider> providers = new HashMap<String, FileProvider>();
069
070    /**
071     * All components used by this manager.
072     */
073    private final ArrayList<Object> components = new ArrayList<Object>();
074
075    /**
076     * The context to pass to providers.
077     */
078    private final DefaultVfsComponentContext context = new DefaultVfsComponentContext(this);
079
080    /**
081     * Operations providers added to this manager.
082     */
083    private final Map<String, List<FileOperationProvider>> operationProviders =
084            new HashMap<String, List<FileOperationProvider>>();
085
086    /**
087     * Mappings of file types.
088     */
089    private final FileTypeMap typeMap = new FileTypeMap();
090
091
092    /**
093     * The provider for local files.
094     */
095    private LocalFileProvider localFileProvider;
096
097    /**
098     * The default provider.
099     */
100    private FileProvider defaultProvider;
101
102    /**
103     * The file replicator to use.
104     */
105    private FileReplicator fileReplicator;
106
107    /**
108     * The base file to use for relative URI.
109     */
110    private FileObject baseFile;
111
112    /**
113     * The files cache
114     */
115    private FilesCache filesCache;
116
117    /**
118     * The cache strategy
119     */
120    private CacheStrategy fileCacheStrategy;
121
122    /**
123     * Class which decorates all returned fileObjects
124     */
125    private Class<?> fileObjectDecorator;
126    /**
127     * Reflection constructor extracted from {@link #fileObjectDecorator}
128     */
129    private Constructor<?> fileObjectDecoratorConst;
130
131    /**
132     * The class to use to determine the content-type (mime-type)
133     */
134    private FileContentInfoFactory fileContentInfoFactory;
135
136    /**
137     * The logger to use. Default implementation.
138     */
139    private Log log = LogFactory.getLog(getClass());
140
141    /**
142     * The temporary file store to use.
143     */
144    private TemporaryFileStore tempFileStore;
145
146    /**
147     * The virtual file provider.
148     */
149    private VirtualFileProvider vfsProvider;
150
151    /**
152     * Flag, if manager is initialized (after init() and before close()).
153     */
154    private boolean init;
155
156
157    /**
158     * Returns the logger used by this manager.
159     * @return the Logger.
160     */
161    protected Log getLogger()
162    {
163        return log;
164    }
165
166    /**
167     * Registers a file system provider.
168     * <p>
169     * The manager takes care of all lifecycle management.
170     * A provider may be registered multiple times.
171     * The first {@link LocalFileProvider} added will be
172     * remembered for {@link #getLocalFileProvider()}.
173     *
174     * @param urlScheme
175     *            The scheme the provider will handle.
176     * @param provider
177     *            The provider.
178     * @throws FileSystemException if an error occurs adding the provider.
179     */
180    public void addProvider(final String urlScheme, final FileProvider provider)
181            throws FileSystemException
182    {
183        addProvider(new String[] {urlScheme}, provider);
184    }
185
186    /**
187     * Registers a file system provider.
188     * <p>
189     * The manager takes care of all lifecycle management.
190     * A provider may be registered multiple times.
191     * The first {@link LocalFileProvider} added will be
192     * remembered for {@link #getLocalFileProvider()}.
193     *
194     * @param urlSchemes
195     *            The schemes the provider will handle.
196     * @param provider
197     *            The provider.
198     * @throws FileSystemException if an error occurs adding the provider.
199     */
200    public void addProvider(final String[] urlSchemes,
201            final FileProvider provider) throws FileSystemException
202    {
203        // fail duplicate schemes
204        for (final String scheme : urlSchemes)
205        {
206            if (providers.containsKey(scheme))
207            {
208                throw new FileSystemException(
209                        "vfs.impl/multiple-providers-for-scheme.error", scheme);
210            }
211        }
212
213        // Contextualise the component (if not already)
214        setupComponent(provider);
215
216        // Add to map
217        for (final String scheme : urlSchemes)
218        {
219            providers.put(scheme, provider);
220        }
221
222        if (provider instanceof LocalFileProvider && localFileProvider == null)
223        {
224            localFileProvider = (LocalFileProvider) provider;
225        }
226    }
227
228    /**
229     * Returns true if this manager has a provider for a particular scheme.
230     *
231     * @param scheme The scheme to check.
232     * @return true if a provider is configured for this scheme, false otherwise.
233     */
234    @Override
235    public boolean hasProvider(final String scheme)
236    {
237        return providers.containsKey(scheme);
238    }
239
240    /**
241     * Adds an filename extension mapping.
242     *
243     * @param extension The file name extension.
244     * @param scheme The scheme to use for files with this extension.
245     */
246    public void addExtensionMap(final String extension, final String scheme)
247    {
248        typeMap.addExtension(extension, scheme);
249    }
250
251    /**
252     * Adds a mime type mapping.
253     *
254     * @param mimeType The mime type.
255     * @param scheme The scheme to use for files with this mime type.
256     */
257    public void addMimeTypeMap(final String mimeType, final String scheme)
258    {
259        typeMap.addMimeType(mimeType, scheme);
260    }
261
262    /**
263     * Sets the default provider. This is the provider that will handle URI with
264     * unknown schemes. The manager takes care of all lifecycle management.
265     * @param provider The FileProvider.
266     * @throws FileSystemException if an error occurs setting the provider.
267     */
268    public void setDefaultProvider(final FileProvider provider)
269            throws FileSystemException
270    {
271        setupComponent(provider);
272        defaultProvider = provider;
273    }
274
275    /**
276     * Returns the filesCache implementation used to cache files.
277     * @return The FilesCache.
278     */
279    @Override
280    public FilesCache getFilesCache()
281    {
282        return filesCache;
283    }
284
285    /**
286     * Sets the filesCache implementation used to cache files.
287     * <p>
288     * Can only be set before the FileSystemManager is initialized.
289     * <p>
290     * The manager takes care of the lifecycle. If none is set, a default is picked
291     * in {@link #init()}.
292     *
293     * @param filesCache The FilesCache.
294     * @throws FileSystemException if an error occurs setting the cache..
295     */
296    public void setFilesCache(final FilesCache filesCache)
297            throws FileSystemException
298    {
299        if (init)
300        {
301            throw new FileSystemException("vfs.impl/already-inited.error");
302        }
303
304        this.filesCache = filesCache;
305    }
306
307    /**
308     * Set the cache strategy to use when dealing with file object data.
309     * <p>
310     * Can only be set before the FileSystemManager is initialized.
311     * <p>
312     * The default is {@link CacheStrategy#ON_RESOLVE}
313     *
314     * @param fileCacheStrategy The CacheStrategy to use.
315     * @throws FileSystemException
316     *             if this is not possible. e.g. it is already set.
317     */
318    public void setCacheStrategy(final CacheStrategy fileCacheStrategy)
319            throws FileSystemException
320    {
321        if (init)
322        {
323            throw new FileSystemException("vfs.impl/already-inited.error");
324        }
325
326        this.fileCacheStrategy = fileCacheStrategy;
327    }
328
329    /**
330     * Get the cache strategy used.
331     * @return The CacheStrategy.
332     */
333    @Override
334    public CacheStrategy getCacheStrategy()
335    {
336        return fileCacheStrategy;
337    }
338
339    /**
340     * Get the file object decorator used.
341     * @return The decorator.
342     */
343    @Override
344    public Class<?> getFileObjectDecorator()
345    {
346        return fileObjectDecorator;
347    }
348
349    /**
350     * The constructor associated to the fileObjectDecorator.
351     * We cache it here for performance reasons.
352     * @return The decorator's Constructor.
353     */
354    @Override
355    public Constructor<?> getFileObjectDecoratorConst()
356    {
357        return fileObjectDecoratorConst;
358    }
359
360    /**
361     * Set a fileObject decorator to be used for ALL returned file objects.
362     * <p>
363     * Can only be set before the FileSystemManager is initialized.
364     *
365     * @param fileObjectDecorator must be inherted from {@link DecoratedFileObject} a has to provide a
366     * constructor with a single {@link FileObject} as argument
367     * @throws FileSystemException if an error occurs setting the decorator.
368     */
369    public void setFileObjectDecorator(final Class<?> fileObjectDecorator) throws FileSystemException
370    {
371        if (init)
372        {
373            throw new FileSystemException("vfs.impl/already-inited.error");
374        }
375        if (!DecoratedFileObject.class.isAssignableFrom(fileObjectDecorator))
376        {
377            throw new FileSystemException("vfs.impl/invalid-decorator.error", fileObjectDecorator.getName());
378        }
379
380        try
381        {
382            fileObjectDecoratorConst = fileObjectDecorator.getConstructor(new Class[]{FileObject.class});
383        }
384        catch (final NoSuchMethodException e)
385        {
386            throw new FileSystemException("vfs.impl/invalid-decorator.error", fileObjectDecorator.getName(), e);
387        }
388
389        this.fileObjectDecorator = fileObjectDecorator;
390    }
391
392    /**
393     * get the fileContentInfoFactory used to determine the infos of a file
394     * content.
395     * @return The FileContentInfoFactory.
396     */
397    @Override
398    public FileContentInfoFactory getFileContentInfoFactory()
399    {
400        return fileContentInfoFactory;
401    }
402
403    /**
404     * set the fileContentInfoFactory used to determine the infos of a file
405     * content.
406     * <p>
407     * Can only be set before the FileSystemManager is initialized.
408     *
409     * @param fileContentInfoFactory The FileContentInfoFactory.
410     * @throws FileSystemException if an error occurs setting the FileContentInfoFactory.
411     */
412    public void setFileContentInfoFactory(final FileContentInfoFactory fileContentInfoFactory)
413            throws FileSystemException
414    {
415        if (init)
416        {
417            throw new FileSystemException("vfs.impl/already-inited.error");
418        }
419
420        this.fileContentInfoFactory = fileContentInfoFactory;
421    }
422
423    /**
424     * Sets the file replicator to use.
425     * <p>
426     * The manager takes care of all lifecycle management.
427     *
428     * @param replicator The FileReplicator.
429     * @throws FileSystemException if an error occurs setting the replicator.
430     */
431    public void setReplicator(final FileReplicator replicator)
432            throws FileSystemException
433    {
434        setupComponent(replicator);
435        fileReplicator = replicator;
436    }
437
438    /**
439     * Sets the temporary file store to use.
440     * <p>
441     * The manager takes care of all lifecycle management.
442     *
443     * @param tempFileStore The temporary FileStore.
444     * @throws FileSystemException if an error occurs adding the file store.
445     */
446    public void setTemporaryFileStore(final TemporaryFileStore tempFileStore)
447            throws FileSystemException
448    {
449        setupComponent(tempFileStore);
450        this.tempFileStore = tempFileStore;
451    }
452
453    /**
454     * Sets the logger to use.
455     * <p>
456     * This overwrites the default logger for this manager and is not reset in {@link #close()}.
457     *
458     * @param log The Logger to use.
459     */
460    @Override
461    public void setLogger(final Log log)
462    {
463        this.log = log;
464    }
465
466    /**
467     * Initializes a component, if it has not already been initialised.
468     *
469     * @param component The component to setup.
470     * @throws FileSystemException if an error occurs.
471     */
472    private void setupComponent(final Object component)
473            throws FileSystemException
474    {
475        if (!components.contains(component))
476        {
477            if (component instanceof VfsComponent)
478            {
479                final VfsComponent vfsComponent = (VfsComponent) component;
480                vfsComponent.setLogger(getLogger());
481                vfsComponent.setContext(context);
482                vfsComponent.init();
483            }
484            components.add(component);
485        }
486    }
487
488    /**
489     * Closes a component, if it has not already been closed.
490     *
491     * @param component The component to close.
492     */
493    private void closeComponent(final Object component)
494    {
495        if (component != null && components.contains(component))
496        {
497            if (component instanceof VfsComponent)
498            {
499                final VfsComponent vfsComponent = (VfsComponent) component;
500                vfsComponent.close();
501            }
502            components.remove(component);
503        }
504    }
505
506    /**
507     * Returns the file replicator.
508     *
509     * @return The file replicator. Never returns null.
510     * @throws FileSystemException if there is no FileReplicator.
511     */
512    public FileReplicator getReplicator() throws FileSystemException
513    {
514        if (fileReplicator == null)
515        {
516            throw new FileSystemException("vfs.impl/no-replicator.error");
517        }
518        return fileReplicator;
519    }
520
521    /**
522     * Returns the temporary file store.
523     *
524     * @return The file store. Never returns null.
525     * @throws FileSystemException if there is no TemporaryFileStore.
526     */
527    public TemporaryFileStore getTemporaryFileStore()
528            throws FileSystemException
529    {
530        if (tempFileStore == null)
531        {
532            throw new FileSystemException("vfs.impl/no-temp-file-store.error");
533        }
534        return tempFileStore;
535    }
536
537    /**
538     * Initializes this manager.
539     * <p>
540     * If no value for the following properties was specified, it will
541     * use the following defaults:
542     * <ul>
543     * <li>fileContentInfoFactory = new FileContentInfoFilenameFactory()</li>
544     * <li>filesCache = new SoftRefFilesCache()</li>
545     * <li>fileCacheStrategy = CacheStrategy.ON_RESOLVE</li>
546     * </ul>
547     *
548     * @throws FileSystemException if an error occurs during initialization.
549     */
550    public void init() throws FileSystemException
551    {
552        if (fileContentInfoFactory == null)
553        {
554            fileContentInfoFactory = new FileContentInfoFilenameFactory();
555        }
556
557        if (filesCache == null)
558        {
559            // filesCache = new DefaultFilesCache();
560            filesCache = new SoftRefFilesCache();
561        }
562        if (fileCacheStrategy == null)
563        {
564            fileCacheStrategy = CacheStrategy.ON_RESOLVE;
565        }
566        setupComponent(filesCache);
567
568        vfsProvider = new VirtualFileProvider();
569        setupComponent(vfsProvider);
570
571        init = true;
572    }
573
574    /**
575     * Closes the manager.
576     * <p>
577     * This will close all providers (all files), it will also close
578     * all managed components including temporary files, replicator,
579     * file cache and file operations.
580     * <p>
581     * The manager is in uninitialized state after this method.
582     */
583    public void close()
584    {
585        if (!init)
586        {
587            return;
588        }
589
590        // make sure all discovered components in
591        // org.apache.commons.vfs2.impl.StandardFileSystemManager.configure(Element)
592        // are closed here
593
594        // Close the file system providers.
595        for (final FileProvider provider : providers.values())
596        {
597            closeComponent(provider);
598        }
599        // unregister all
600        providers.clear();
601
602        // Close the other components
603        closeComponent(vfsProvider);
604        closeComponent(fileReplicator);
605        closeComponent(tempFileStore);
606        closeComponent(filesCache);
607        closeComponent(defaultProvider);
608
609        // FileOperations are components, too
610        for (final List<FileOperationProvider> opproviders : operationProviders.values())
611        {
612            for (final FileOperationProvider p : opproviders)
613            {
614                closeComponent(p);
615            }
616        }
617        // unregister all
618        operationProviders.clear();
619
620        // collections with add()
621        typeMap.clear();
622
623        // should not happen, but make debugging easier:
624        if (!components.isEmpty())
625        {
626            log.warn("DefaultFilesystemManager.close: not all components are closed: " + components.toString());
627        }
628        components.clear();
629
630        // managed components
631        vfsProvider = null;
632
633        // setters and derived state
634        defaultProvider = null;
635        baseFile = null;
636        fileObjectDecorator = null;
637        fileObjectDecoratorConst = null;
638        localFileProvider = null;
639        fileReplicator = null;
640        tempFileStore = null;
641        // setters with init() defaults
642        filesCache = null;
643        fileCacheStrategy = null;
644        fileContentInfoFactory = null;
645
646        init = false;
647    }
648
649    /**
650     * Free all resources used by unused filesystems created by this manager.
651     */
652    public void freeUnusedResources()
653    {
654        if (!init)
655        {
656            return;
657        }
658
659        // Close the providers.
660        for (final FileProvider fileProvider : providers.values())
661        {
662            final AbstractFileProvider provider = (AbstractFileProvider) fileProvider;
663            provider.freeUnusedResources();
664        }
665        // vfsProvider does not need to free resources
666    }
667
668    /**
669     * Sets the base file to use when resolving relative URI.
670     * @param baseFile The new base FileObject.
671     * @throws FileSystemException if an error occurs.
672     */
673    public void setBaseFile(final FileObject baseFile)
674            throws FileSystemException
675    {
676        this.baseFile = baseFile;
677    }
678
679    /**
680     * Sets the base file to use when resolving relative URI.
681     * @param baseFile The new base FileObject.
682     * @throws FileSystemException if an error occurs.
683     */
684    public void setBaseFile(final File baseFile) throws FileSystemException
685    {
686        this.baseFile = getLocalFileProvider().findLocalFile(baseFile);
687    }
688
689    /**
690     * Returns the base file used to resolve relative URI.
691     * @return The FileObject that represents the base file.
692     * @throws FileSystemException if an error occurs.
693     */
694    @Override
695    public FileObject getBaseFile() throws FileSystemException
696    {
697        return baseFile;
698    }
699
700    /**
701     * Locates a file by URI.
702     * @param uri The URI of the file to locate.
703     * @return The FileObject for the located file.
704     * @throws FileSystemException if the file cannot be located or an error occurs.
705     */
706    @Override
707    public FileObject resolveFile(final String uri) throws FileSystemException
708    {
709        return resolveFile(getBaseFile(), uri);
710    }
711
712    /**
713     * Locate a file by URI, use the FileSystemOptions for file-system creation.
714     * @param uri The URI of the file to locate.
715     * @param fileSystemOptions The options for the FileSystem.
716     * @return The FileObject for the located file.
717     * @throws FileSystemException if the file cannot be located or an error occurs.
718     */
719
720    @Override
721    public FileObject resolveFile(final String uri,
722            final FileSystemOptions fileSystemOptions)
723            throws FileSystemException
724    {
725        // return resolveFile(baseFile, uri, fileSystemOptions);
726        return resolveFile(getBaseFile(), uri, fileSystemOptions);
727    }
728
729    /**
730     * Resolves a URI, relative to base file.
731     * <p>
732     * Uses the {@linkplain #getLocalFileProvider() local file provider} to
733     * locate the system file.
734     *
735     * @param baseFile The base File to use to locate the file.
736     * @param uri The URI of the file to locate.
737     * @return The FileObject for the located file.
738     * @throws FileSystemException if the file cannot be located or an error occurs.
739     */
740    @Override
741    public FileObject resolveFile(final File baseFile, final String uri)
742            throws FileSystemException
743    {
744        final FileObject baseFileObj = getLocalFileProvider().findLocalFile(
745                baseFile);
746        return resolveFile(baseFileObj, uri);
747    }
748
749    /**
750     * Resolves a URI, relative to a base file.
751     * @param baseFile The base FileOjbect to use to locate the file.
752     * @param uri The URI of the file to locate.
753     * @return The FileObject for the located file.
754     * @throws FileSystemException if the file cannot be located or an error occurs.
755     */
756    @Override
757    public FileObject resolveFile(final FileObject baseFile, final String uri)
758            throws FileSystemException
759    {
760        return resolveFile(baseFile, uri, baseFile == null ? null : baseFile
761                .getFileSystem().getFileSystemOptions());
762    }
763
764    /**
765     * Resolves a URI, relative to a base file with specified FileSystem
766     * configuration.
767     * @param baseFile The base file.
768     * @param uri The file name. May be a fully qualified or relative path or a url.
769     * @param fileSystemOptions Options to pass to the file system.
770     * @return A FileObject representing the target file.
771     * @throws FileSystemException if an error occurs accessing the file.
772     */
773    public FileObject resolveFile(final FileObject baseFile, final String uri,
774            final FileSystemOptions fileSystemOptions)
775            throws FileSystemException
776    {
777        final FileObject realBaseFile;
778        if (baseFile != null && VFS.isUriStyle()
779                && baseFile.getName().isFile())
780        {
781            realBaseFile = baseFile.getParent();
782        }
783        else
784        {
785            realBaseFile = baseFile;
786        }
787        // TODO: use resolveName and use this name to resolve the fileObject
788
789        UriParser.checkUriEncoding(uri);
790
791        if (uri == null)
792        {
793            throw new IllegalArgumentException();
794        }
795
796        // Extract the scheme
797        final String scheme = UriParser.extractScheme(uri);
798        if (scheme != null)
799        {
800            // An absolute URI - locate the provider
801            final FileProvider provider = providers.get(scheme);
802            if (provider != null)
803            {
804                return provider.findFile(realBaseFile, uri, fileSystemOptions);
805            }
806            // Otherwise, assume a local file
807        }
808
809        // Handle absolute file names
810        if (localFileProvider != null
811                && localFileProvider.isAbsoluteLocalName(uri))
812        {
813            return localFileProvider.findLocalFile(uri);
814        }
815
816        if (scheme != null)
817        {
818            // An unknown scheme - hand it to the default provider
819            if (defaultProvider == null)
820            {
821                throw new FileSystemException("vfs.impl/unknown-scheme.error", scheme, uri);
822            }
823            return defaultProvider.findFile(realBaseFile, uri, fileSystemOptions);
824        }
825
826        // Assume a relative name - use the supplied base file
827        if (realBaseFile == null)
828        {
829            throw new FileSystemException("vfs.impl/find-rel-file.error", uri);
830        }
831
832        return realBaseFile.resolveFile(uri);
833    }
834
835    /**
836     * Resolves a name, relative to the file. If the supplied name is an
837     * absolute path, then it is resolved relative to the root of the file
838     * system that the file belongs to. If a relative name is supplied, then it
839     * is resolved relative to this file name.
840     * @param root The base FileName.
841     * @param path The path to the file relative to the base FileName or an absolute path.
842     * @return The constructed FileName.
843     * @throws FileSystemException if an error occurs constructing the FileName.
844     */
845    @Override
846    public FileName resolveName(final FileName root, final String path)
847            throws FileSystemException
848    {
849        return resolveName(root, path, NameScope.FILE_SYSTEM);
850    }
851
852    /**
853     * Resolves a name, relative to the root.
854     *
855     * @param base the base filename
856     * @param name the name
857     * @param scope the {@link NameScope}
858     * @return The FileName of the file.
859     * @throws FileSystemException if an error occurs.
860     */
861    @Override
862    public FileName resolveName(final FileName base, final String name,
863            final NameScope scope) throws FileSystemException
864    {
865        final FileName realBase;
866        if (base != null && VFS.isUriStyle() && base.isFile())
867        {
868            realBase = base.getParent();
869        }
870        else
871        {
872            realBase = base;
873        }
874
875        final StringBuilder buffer = new StringBuilder(name);
876
877        // Adjust separators
878        UriParser.fixSeparators(buffer);
879        String scheme = UriParser.extractScheme(buffer.toString());
880
881        // Determine whether to prepend the base path
882        if (name.length() == 0 || (scheme == null && buffer.charAt(0) != FileName.SEPARATOR_CHAR))
883        {
884            // Supplied path is not absolute
885            if (!VFS.isUriStyle())
886            {
887                // when using uris the parent already do have the trailing "/"
888                buffer.insert(0, FileName.SEPARATOR_CHAR);
889            }
890            buffer.insert(0, realBase.getPath());
891        }
892
893        // Normalise the path
894        final FileType fileType = UriParser.normalisePath(buffer);
895
896        // Check the name is ok
897        final String resolvedPath = buffer.toString();
898        if (!AbstractFileName
899                .checkName(realBase.getPath(), resolvedPath, scope))
900        {
901            throw new FileSystemException(
902                    "vfs.provider/invalid-descendent-name.error", name);
903        }
904
905        String fullPath;
906        if (scheme != null)
907        {
908            fullPath = resolvedPath;
909        }
910        else
911        {
912            scheme = realBase.getScheme();
913            fullPath = realBase.getRootURI() + resolvedPath;
914        }
915        final FileProvider provider = providers.get(scheme);
916        if (provider != null)
917        {
918            // TODO: extend the filename parser to be able to parse
919            // only a pathname and take the missing informations from
920            // the base. Then we can get rid of the string operation.
921            // // String fullPath = base.getRootURI() +
922            // resolvedPath.substring(1);
923
924            return provider.parseUri(realBase, fullPath);
925        }
926
927        // An unknown scheme - hand it to the default provider - if possible
928        if (scheme != null && defaultProvider != null)
929        {
930            return defaultProvider.parseUri(realBase, fullPath);
931        }
932
933        // TODO: avoid fallback to this point
934        // this happens if we have a virtual filesystem (no provider for scheme)
935        return ((AbstractFileName) realBase).createName(resolvedPath, fileType);
936    }
937
938    /**
939     * Resolve the uri to a filename.
940     * @param uri The URI to resolve.
941     * @return The FileName of the file.
942     * @throws FileSystemException if an error occurs.
943     */
944    @Override
945    public FileName resolveURI(final String uri) throws FileSystemException
946    {
947        UriParser.checkUriEncoding(uri);
948
949        if (uri == null)
950        {
951            throw new IllegalArgumentException();
952        }
953
954        // Extract the scheme
955        final String scheme = UriParser.extractScheme(uri);
956        if (scheme != null)
957        {
958            // An absolute URI - locate the provider
959            final FileProvider provider = providers.get(scheme);
960            if (provider != null)
961            {
962                return provider.parseUri(null, uri);
963            }
964
965            // Otherwise, assume a local file
966        }
967
968        // Handle absolute file names
969        if (localFileProvider != null
970                && localFileProvider.isAbsoluteLocalName(uri))
971        {
972            return localFileProvider.parseUri(null, uri);
973        }
974
975        if (scheme != null)
976        {
977            // An unknown scheme - hand it to the default provider
978            if (defaultProvider == null)
979            {
980                throw new FileSystemException("vfs.impl/unknown-scheme.error", scheme, uri);
981            }
982            return defaultProvider.parseUri(null, uri);
983        }
984
985        // Assume a relative name - use the supplied base file
986        if (baseFile == null)
987        {
988            throw new FileSystemException("vfs.impl/find-rel-file.error", uri);
989        }
990
991        return resolveName(baseFile.getName(), uri, NameScope.FILE_SYSTEM);
992    }
993
994    /**
995     * Converts a local file into a {@link FileObject}.
996     * @param file The input File.
997     * @return the create FileObject
998     * @throws FileSystemException if an error occurs creating the file.
999     */
1000    @Override
1001    public FileObject toFileObject(final File file) throws FileSystemException
1002    {
1003        return getLocalFileProvider().findLocalFile(file);
1004    }
1005
1006    /**
1007     * Creates a layered file system.
1008     * @param scheme The scheme to use.
1009     * @param file The FileObject.
1010     * @return The layered FileObject.
1011     * @throws FileSystemException if an error occurs.
1012     */
1013    @Override
1014    public FileObject createFileSystem(final String scheme,
1015            final FileObject file) throws FileSystemException
1016    {
1017        final FileProvider provider = providers.get(scheme);
1018        if (provider == null)
1019        {
1020            throw new FileSystemException("vfs.impl/unknown-provider.error", scheme, file);
1021        }
1022        return provider.createFileSystem(scheme, file, file.getFileSystem().getFileSystemOptions());
1023    }
1024
1025    /**
1026     * Creates a layered file system.
1027     * @param file The FileObject to use.
1028     * @return The layered FileObject.
1029     * @throws FileSystemException if an error occurs.
1030     */
1031    @Override
1032    public FileObject createFileSystem(final FileObject file)
1033            throws FileSystemException
1034    {
1035        final String scheme = typeMap.getScheme(file);
1036        if (scheme == null)
1037        {
1038            throw new FileSystemException(
1039                    "vfs.impl/no-provider-for-file.error", file);
1040        }
1041
1042        return createFileSystem(scheme, file);
1043    }
1044
1045    /**
1046     * Determines if a layered file system can be created for a given file.
1047     *
1048     * @param file The file to check for.
1049     * @return true if the FileSystem can be created.
1050     * @throws FileSystemException if an error occurs.
1051     */
1052    @Override
1053    public boolean canCreateFileSystem(final FileObject file)
1054            throws FileSystemException
1055    {
1056        return typeMap.getScheme(file) != null;
1057    }
1058
1059    /**
1060     * Creates a virtual file system.
1061     * @param rootFile The FileObject to use.
1062     * @return The FileObject in the VirtualFileSystem.
1063     * @throws FileSystemException if an error occurs creating the file.
1064     */
1065    @Override
1066    public FileObject createVirtualFileSystem(final FileObject rootFile)
1067            throws FileSystemException
1068    {
1069        return vfsProvider.createFileSystem(rootFile);
1070    }
1071
1072    /**
1073     * Creates an empty virtual file system.
1074     * @param rootUri The URI to use as the root of the FileSystem.
1075     * @return A FileObject in the virtual FileSystem.
1076     * @throws FileSystemException if an error occurs.
1077     */
1078    @Override
1079    public FileObject createVirtualFileSystem(final String rootUri)
1080            throws FileSystemException
1081    {
1082        return vfsProvider.createFileSystem(rootUri);
1083    }
1084
1085    /**
1086     * Locates the local file provider.
1087     * <p>
1088     * The local file provider is the first
1089     * {@linkplain #addProvider(String[], FileProvider) provider added}
1090     * implementing {@link LocalFileProvider}.
1091     *
1092     * @return The LocalFileProvider.
1093     * @throws FileSystemException if no local file provider was set.
1094     */
1095    private LocalFileProvider getLocalFileProvider() throws FileSystemException
1096    {
1097        if (localFileProvider == null)
1098        {
1099            throw new FileSystemException(
1100                    "vfs.impl/no-local-file-provider.error");
1101        }
1102        return localFileProvider;
1103    }
1104
1105    /**
1106     * Get the URLStreamHandlerFactory.
1107     * @return The URLStreamHandlerFactory.
1108     */
1109    @Override
1110    public URLStreamHandlerFactory getURLStreamHandlerFactory()
1111    {
1112        return new VfsStreamHandlerFactory();
1113    }
1114
1115    /**
1116     * Closes the given filesystem.
1117     * <p>
1118     * If you use VFS as singleton it is VERY dangerous to call this method.
1119     *
1120     * @param filesystem The FileSystem to close.
1121     */
1122    @Override
1123    public void closeFileSystem(final FileSystem filesystem)
1124    {
1125        // inform the cache ...
1126        getFilesCache().clear(filesystem);
1127
1128        // just in case the cache didnt call _closeFileSystem
1129        _closeFileSystem(filesystem);
1130    }
1131
1132    /**
1133     * Closes the given file system.
1134     * <p>
1135     * If you use VFS as singleton it is VERY dangerous to call this method
1136     * </p>
1137     *
1138     * @param filesystem The FileSystem to close.
1139     */
1140    public void _closeFileSystem(final FileSystem filesystem)
1141    {
1142        final FileProvider provider = providers.get(filesystem.getRootName().getScheme());
1143        if (provider != null)
1144        {
1145            ((AbstractFileProvider) provider).closeFileSystem(filesystem);
1146        }
1147        else if (filesystem instanceof VirtualFileSystem)
1148        {
1149            // vfsProvider does not implement AbstractFileProvider
1150            vfsProvider.closeFileSystem(filesystem);
1151        }
1152    }
1153
1154    /**
1155     * This is an internal class because it needs access to the private member
1156     * providers.
1157     */
1158    final class VfsStreamHandlerFactory implements URLStreamHandlerFactory
1159    {
1160        @Override
1161        public URLStreamHandler createURLStreamHandler(final String protocol)
1162        {
1163            final FileProvider provider = providers.get(protocol);
1164            if (provider != null)
1165            {
1166                return new DefaultURLStreamHandler(context);
1167            }
1168
1169            // Route all other calls to the default URLStreamHandlerFactory
1170            return new URLStreamHandlerProxy();
1171        }
1172    }
1173
1174    /**
1175     * Get the schemes currently available.
1176     * @return The array of scheme names.
1177     */
1178    @Override
1179    public String[] getSchemes()
1180    {
1181        final String[] schemes = new String[providers.size()];
1182        providers.keySet().toArray(schemes);
1183        return schemes;
1184    }
1185
1186    /**
1187     * Get the capabilities for a given scheme.
1188     *
1189     * @param scheme The scheme to located.
1190     * @return A Collection of capabilities.
1191     * @throws FileSystemException if the given scheme is not konwn
1192     */
1193    @Override
1194    public Collection<Capability> getProviderCapabilities(final String scheme)
1195            throws FileSystemException
1196    {
1197        final FileProvider provider = providers.get(scheme);
1198        if (provider == null)
1199        {
1200            throw new FileSystemException("vfs.impl/unknown-scheme.error", scheme);
1201        }
1202
1203        return provider.getCapabilities();
1204    }
1205
1206    /**
1207     * Get the configuration builder for the given scheme.
1208     * @param scheme The scheme to locate.
1209     * @return The FileSystemConfigBuilder for the scheme.
1210     * @throws FileSystemException if the given scheme is not konwn
1211     */
1212    @Override
1213    public FileSystemConfigBuilder getFileSystemConfigBuilder(final String scheme)
1214            throws FileSystemException
1215    {
1216        final FileProvider provider = providers.get(scheme);
1217        if (provider == null)
1218        {
1219            throw new FileSystemException("vfs.impl/unknown-scheme.error", scheme);
1220        }
1221
1222        return provider.getConfigBuilder();
1223    }
1224
1225    // -- OPERATIONS --
1226
1227    /**
1228     * Adds the specified FileOperationProvider for the specified scheme.
1229     * Several FileOperationProvider's might be registered for the same scheme.
1230     * For example, for "file" scheme we can register SvnWsOperationProvider and
1231     * CvsOperationProvider.
1232     *
1233     * @param scheme The scheme the provider should be registered for.
1234     * @param operationProvider The FileOperationProvider.
1235     * @throws FileSystemException if an error occurs adding the provider.
1236     */
1237    @Override
1238    public void addOperationProvider(final String scheme,
1239            final FileOperationProvider operationProvider)
1240            throws FileSystemException
1241    {
1242        addOperationProvider(new String[] {scheme}, operationProvider);
1243    }
1244
1245    /**
1246     * @see FileSystemManager#addOperationProvider(String,
1247     *      org.apache.commons.vfs2.operations.FileOperationProvider)
1248     *
1249     * @param schemes The array of schemes the provider should apply to.
1250     * @param operationProvider The FileOperationProvider.
1251     * @throws FileSystemException if an error occurs.
1252     */
1253    @Override
1254    public void addOperationProvider(final String[] schemes,
1255            final FileOperationProvider operationProvider)
1256            throws FileSystemException
1257    {
1258        for (final String scheme : schemes)
1259        {
1260            if (!operationProviders.containsKey(scheme))
1261            {
1262                final List<FileOperationProvider> providers = new ArrayList<FileOperationProvider>();
1263                operationProviders.put(scheme, providers);
1264            }
1265
1266            final List<FileOperationProvider> providers = operationProviders.get(scheme);
1267
1268            if (providers.contains(operationProvider))
1269            {
1270                throw new FileSystemException(
1271                        "vfs.operation/operation-provider-already-added.error", scheme);
1272            }
1273
1274            setupComponent(operationProvider);
1275
1276            providers.add(operationProvider);
1277        }
1278    }
1279
1280    /**
1281     * @param scheme
1282     *            the scheme for wich we want to get the list af registered
1283     *            providers.
1284     *
1285     * @return the registered FileOperationProviders for the specified scheme.
1286     *         If there were no providers registered for the scheme, it returns
1287     *         null.
1288     *
1289     * @throws FileSystemException if an error occurs.
1290     */
1291    @Override
1292    public FileOperationProvider[] getOperationProviders(final String scheme)
1293            throws FileSystemException
1294    {
1295
1296        final List<?> providers = operationProviders.get(scheme);
1297        if (providers == null || providers.size() == 0)
1298        {
1299            return null;
1300        }
1301        return providers.toArray(new FileOperationProvider[] {});
1302    }
1303
1304    /**
1305     * Converts a URI into a {@link FileObject}.
1306     *
1307     * @param uri The URI to convert.
1308     * @return The {@link FileObject} that represents the URI.  Never
1309     *         returns null.
1310     * @throws FileSystemException On error converting the URI.
1311     * @since 2.1
1312     */
1313    @Override
1314    public FileObject resolveFile(final URI uri) throws FileSystemException
1315    {
1316        // TODO Push the URI deeper into VFS
1317        return resolveFile(baseFile, uri.toString(), null);
1318    }
1319
1320    /**
1321     * Converts a URL into a {@link FileObject}.
1322     *
1323     * @param url The URL to convert.
1324     * @return The {@link FileObject} that represents the URL.  Never
1325     *         returns null.
1326     * @throws FileSystemException On error converting the URL.
1327     * @since 2.1
1328     */
1329    @Override
1330    public FileObject resolveFile(final URL url) throws FileSystemException
1331    {
1332        try
1333        {
1334            return this.resolveFile(url.toURI());
1335        }
1336        catch (final URISyntaxException e)
1337        {
1338            throw new FileSystemException(e);
1339        }
1340    }
1341}