/**
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2016 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.mucommander.commons.file;
import java.io.IOException;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Random;
import java.util.Vector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.mucommander.commons.file.archive.AbstractArchiveFile;
import com.mucommander.commons.file.archive.ArchiveFormatProvider;
import com.mucommander.commons.file.icon.FileIconProvider;
import com.mucommander.commons.file.icon.impl.SwingFileIconProvider;
import com.mucommander.commons.file.protocol.FileProtocols;
import com.mucommander.commons.file.protocol.ProtocolProvider;
import com.mucommander.commons.file.protocol.local.LocalFile;
import com.mucommander.commons.file.protocol.local.LocalProtocolProvider;
import com.mucommander.commons.file.util.FilePool;
import com.mucommander.commons.file.util.PathTokenizer;
import com.mucommander.commons.file.util.PathUtils;
import com.mucommander.commons.runtime.JavaVersion;
import com.mucommander.commons.runtime.OsFamily;
/**
* FileFactory is an abstract class that provides static methods to get a {@link AbstractFile} instance for
* a specified path or {@link FileURL} location.
* <h3>Protocols</h3>
* <p>
* In order to allow the <code>com.mucommander.commons.file</code> API to access new file protocols, developers must create
* an implementation of {@link AbstractFile} that handles that protocol and register it to <code>FileFactory</code>.
* This registration requires an implementation of {@link ProtocolProvider}, an instance of which will be passed to
* {@link #registerProtocol(String,ProtocolProvider) registerProtocol}.
* </p>
* <p>
* Built-in file protocols are:
* <ul>
* <li>{@link FileProtocols#FILE Local} files.</li>
* <li>{@link FileProtocols#FTP FTP}.</li>
* <li>{@link FileProtocols#SFTP SFTP}.</li>
* <li>{@link FileProtocols#HTTP HTTP}.</li>
* <li>{@link FileProtocols#HTTPS HTTPS}.</li>
* <li>{@link FileProtocols#NFS NFS}.</li>
* <li>{@link FileProtocols#SMB SMB}.</li>
* </ul>
* </p>
* <h3>Archive formats</h3>
* <p>
* In order to allow the <code>com.mucommander.commons.file</code> API to access new archive formats, developers must create
* an implementation of {@link AbstractArchiveFile} that handles that format and register it to <code>FileFactory</code>.
* This registration requires an implementation of {@link ArchiveFormatProvider}, an instance of which will be passed to
* {@link #registerArchiveFormat(ArchiveFormatProvider)}.
* </p>
* <p>
* Built-in file file formats are:
* <ul>
* <li><code>ZIP</code>, registered to zip, jar, war, wal, wmz, xpi, ear, odt, ods and odp files.</li>
* <li><code>TAR</code>, registered to tar, tar.gz, tgz, tar.bz2 and tbz2 files.</li>
* <li><code>GZIP</code>, registered to gz files.</li>
* <li><code>BZip2</code>, registered to bz2 files.</li>
* <li><code>ISO</code>, registered to iso and nrg files.</li>
* <li><code>AR</code>, registered to ar, a and deb files.</li>
* <li><code>LST</code>, registered to lst files.</li>
* <li><code>RAR</code>, registered to rar files.</li>
* <li><code>SEVENZIP</code>, registered to 7z files.</li>
* </ul>
* </p>
* @author Maxence Bernard, Nicolas Rinaudo
*/
public class FileFactory {
private static final Logger LOGGER = LoggerFactory.getLogger(FileFactory.class);
/** All registered protocol providers. */
private static Hashtable<String, ProtocolProvider> protocolProviders = new Hashtable<String, ProtocolProvider>();
/** Local file provider to avoid hashtable lookups (faster). */
private static ProtocolProvider localFileProvider;
/** Vector of registered ArchiveFormatMapping instances */
private static Vector<ArchiveFormatProvider> archiveFormatProvidersV = new Vector<ArchiveFormatProvider>();
/** Array of registered FileProtocolMapping instances, for quicker access */
private static ArchiveFormatProvider[] archiveFormatProviders;
/** Contains a FilePool instance for each registered scheme */
private static final HashMap<String, FilePool> FILE_POOL_MAP = new HashMap<String, FilePool>();
/** System temp directory */
private static final AbstractFile TEMP_DIRECTORY;
/** Default file icon provider, initialized in static block */
private static FileIconProvider defaultFileIconProvider;
/** Default authenticator, used when none is specified */
private static Authenticator defaultAuthenticator;
static {
// Register built-in file protocols.
ProtocolProvider protocolProvider;
registerProtocol(FileProtocols.FILE, new LocalProtocolProvider());
registerProtocol(FileProtocols.SMB, new com.mucommander.commons.file.protocol.smb.SMBProtocolProvider());
registerProtocol(FileProtocols.HTTP, protocolProvider = new com.mucommander.commons.file.protocol.http.HTTPProtocolProvider());
registerProtocol(FileProtocols.HTTPS, protocolProvider);
registerProtocol(FileProtocols.FTP, new com.mucommander.commons.file.protocol.ftp.FTPProtocolProvider());
registerProtocol(FileProtocols.NFS, new com.mucommander.commons.file.protocol.nfs.NFSProtocolProvider());
registerProtocol(FileProtocols.SFTP, new com.mucommander.commons.file.protocol.sftp.SFTPProtocolProvider());
if(JavaVersion.JAVA_1_6.isCurrentOrHigher()) {
// Hadoop requires Java 1.6
registerProtocol(FileProtocols.HDFS, new com.mucommander.commons.file.protocol.hadoop.HDFSProtocolProvider());
// registerProtocol(FileProtocols.S3, new com.mucommander.commons.file.impl.hadoop.S3ProtocolProvider());
}
registerProtocol(FileProtocols.S3, new com.mucommander.commons.file.protocol.s3.S3ProtocolProvider());
registerProtocol(FileProtocols.VSPHERE, new com.mucommander.commons.file.protocol.vsphere.VSphereProtocolProvider());
// Register built-in archive file formats, order for TarArchiveFile and GzipArchiveFile/Bzip2ArchiveFile is important:
// TarArchiveFile must match 'tar.gz'/'tar.bz2' files before GzipArchiveFile/Bzip2ArchiveFile does.
registerArchiveFormat(new com.mucommander.commons.file.archive.zip.ZipFormatProvider());
registerArchiveFormat(new com.mucommander.commons.file.archive.tar.TarFormatProvider());
registerArchiveFormat(new com.mucommander.commons.file.archive.gzip.GzipFormatProvider());
registerArchiveFormat(new com.mucommander.commons.file.archive.bzip2.Bzip2FormatProvider());
registerArchiveFormat(new com.mucommander.commons.file.archive.iso.IsoFormatProvider());
registerArchiveFormat(new com.mucommander.commons.file.archive.ar.ArFormatProvider());
registerArchiveFormat(new com.mucommander.commons.file.archive.lst.LstFormatProvider());
registerArchiveFormat(new com.mucommander.commons.file.archive.rar.RarFormatProvider());
registerArchiveFormat(new com.mucommander.commons.file.archive.sevenzip.SevenZipFormatProvider());
// Set the default FileIconProvider instance
defaultFileIconProvider = new SwingFileIconProvider();
// Create the temp directory folder
TEMP_DIRECTORY = getFile(System.getProperty("java.io.tmpdir"));
}
/**
* Makes sure no instance of <code>FileFactory</code> is created.
*/
private FileFactory() {
}
/**
* Registers a new file protocol.
* <p>
* If a {@link ProtocolProvider} was already registered to the specified protocol, it will automatically be
* unregistered.
* </p>
* <p>
* The <code>protocol</code> argument is expected to be the protocol identifier, without the trailing <code>://</code>.
* For example, the identifier of the HTTP protocol would be <code>http</code>. This parameter's case is irrelevant,
* as it will be stored in all lower-case.
* </p>
* <p>
* After this call, the various {@link #getFile(String) getFile} methods will be able to resolve files using the
* specified protocol.
* </p>
* <p>
* Built-in file protocols are listed in {@link FileProtocols}.
* </p>
*
* @param protocol identifier of the protocol to register.
* @param provider object used to create instances of files using the specified protocol.
* @return the previously registered protocol provider if any, <code>null</code> otherwise.
*/
public static ProtocolProvider registerProtocol(String protocol, ProtocolProvider provider) {
protocol = protocol.toLowerCase();
// Create raw and archive file pools
FILE_POOL_MAP.put(protocol, new FilePool());
// Special case for local file provider.
// Note that the local file provider is also added to the provider hashtable.
if(protocol.equals(FileProtocols.FILE))
localFileProvider = provider;
return protocolProviders.put(protocol, provider);
}
/**
* Unregisters the provider associated with the specified protocol.
*
* @param protocol identifier of the protocol whose provider should be unregistered.
* @return the provider that has been unregistered, or <code>null</code> if none.
*/
public static ProtocolProvider unregisterProtocol(String protocol) {
protocol = protocol.toLowerCase();
// Remove raw and archive file pools
FILE_POOL_MAP.remove(protocol);
// Special case for local file provider
if(protocol.equals(FileProtocols.FILE))
localFileProvider = null;
return protocolProviders.remove(protocol);
}
/**
* Returns the protocol provider associated with the specified protocol identifier, or <code>null</code> if there
* is none.
*
* @param protocol identifier of the protocol whose provider should be retrieved.
* @return the protocol provider registered to the specified protocol identifier, or <code>null</code> if none.
*/
public static ProtocolProvider getProtocolProvider(String protocol) {
return protocolProviders.get(protocol.toLowerCase());
}
/**
* Returns <code>true</code> if the given protocol has a registered {@link ProtocolProvider}.
*
* @param protocol identifier of the protocol to test
* @return <code>true</code> if the given protocol has a registered {@link ProtocolProvider}.
*/
public static boolean isRegisteredProtocol(String protocol) {
return getProtocolProvider(protocol)!=null;
}
/**
* Returns an iterator on all known protocol names.
*
* <p>All objects returned by the iterator's <code>nextElement()</code> method will be string instances. These can
* then be passed to {@link #getProtocolProvider(String) getProtocolProvider} to retrieve the associated
* {@link ProtocolProvider}.</p>
*
* @return an iterator on all known protocol names.
*/
public static Iterator<String> protocols() {
return protocolProviders.keySet().iterator();
}
/**
* Registers a new <code>ArchiveFormatProvider</code>.
*
* @param provider the <code>ArchiveFormatProvider</code> to register.
*/
public static void registerArchiveFormat(ArchiveFormatProvider provider) {
archiveFormatProvidersV.add(provider);
updateArchiveFormatProviderArray();
}
/**
* Removes a previously-registered <code>ArchiveFormatProvider</code>.
* <p>
* To unregister the provider of a particular archive format without knowing the associated provider instance, use
* {@link #getArchiveFormatProvider(String)} with a known archive filename to retrieve the provider instance.
* For example, <code>FileFactory.unregisterArchiveFormat(FileFactory.getArchiveFormatProvider("file.zip"))</code>
* will unregister the (first, if any) Zip provider.
* </p>
*
* @param provider the <code>ArchiveFormatProvider</code> to unregister.
* @see #getArchiveFormatProvider(String)
*/
public static void unregisterArchiveFormat(ArchiveFormatProvider provider) {
int index = archiveFormatProvidersV.indexOf(provider);
if(index!=-1) {
archiveFormatProvidersV.removeElementAt(index);
updateArchiveFormatProviderArray();
}
}
/**
* Updates the <code>ArchiveFormatProvider</code> array to reflect the contents of the Vector.
*/
private static void updateArchiveFormatProviderArray() {
archiveFormatProviders = new ArchiveFormatProvider[archiveFormatProvidersV.size()];
archiveFormatProvidersV.toArray(archiveFormatProviders);
}
/**
* Returns the first <code>ArchiveFormatProvider</code> that matches the specified filename, <code>null</code>
* if there is none. Note that if a filename matches the {@link java.io.FilenameFilter} of several registered
* providers, the first provider matching the filename will be returned.
*
* @param filename an archive filename that potentially matches one of the registered <code>ArchiveFormatProvider</code>
* @return the first <code>ArchiveFormatProvider</code> that matches the specified filename, <code>null</code> if there is none
*/
public static ArchiveFormatProvider getArchiveFormatProvider(String filename) {
if(filename == null)
return null;
for (ArchiveFormatProvider provider : archiveFormatProviders) {
if (provider.getFilenameFilter().accept(filename))
return provider;
}
return null;
}
/**
* Returns an iterator on all known archive formats.
*
* @return an iterator on all known archive formats.
*/
public static Iterator<ArchiveFormatProvider> archiveFormats() {
return archiveFormatProvidersV.iterator();
}
/**
* Returns an instance of AbstractFile for the given absolute path.
*
* <p>This method does not throw any IOException but returns <code>null</code> if the file could not be created.</p>
*
* @param absPath the absolute path to the file
* @return <code>null</code> if the given path is not absolute or incorrect (doesn't correspond to any file) or
* if something went wrong during file creation.
*/
public static AbstractFile getFile(String absPath) {
try {return getFile(absPath, null);}
catch(IOException e) {
LOGGER.info("Caught an exception", e);
return null;
}
}
/**
* Returns an instance of AbstractFile for the given absolute path.
*
* <p>This method does not throw any IOException but returns <code>null</code> if the file could not be created.</p>
*
* @param absPath the absolute path to the file
* @param throwException if set to <code>true</code>, an IOException will be thrown if something went wrong during file creation
* @return <code>null</code> if the given path is not absolute or incorrect (doesn't correspond to any file)
* @throws java.io.IOException and throwException param was set to <code>true</code>.
* @throws AuthException if additional authentication information is required to create the file
*/
public static AbstractFile getFile(String absPath, boolean throwException) throws AuthException, IOException {
try {return getFile(absPath, null);}
catch(IOException e) {
LOGGER.info("Caught an exception", e);
if(throwException)
throw e;
return null;
}
}
/**
* Returns an instance of AbstractFile for the given absolute path and use the given parent for the new file if
* not null. AbstractFile subclasses should as much as possible call this method rather than {@link #getFile(String)}
* because it is more efficient.
*
* @param absPath the absolute path to the file
* @param parent the returned file's parent
* @return an instance of <code>AbstractFile</code> for the specified absolute path.
* @throws java.io.IOException if something went wrong during file or file url creation.
* @throws AuthException if additionnal authentication information is required to create the file
*/
public static AbstractFile getFile(String absPath, AbstractFile parent) throws AuthException, IOException {
return getFile(FileURL.getFileURL(absPath), parent);
}
/**
* Returns an instance of AbstractFile for the given FileURL instance.
*
* @param fileURL the file URL
* @return the created file or null if something went wrong during file creation
*/
public static AbstractFile getFile(FileURL fileURL) {
try {return getFile(fileURL, null);}
catch(IOException e) {
LOGGER.info("Caught an exception", e);
return null;
}
}
/**
* Returns an instance of AbstractFile for the given FileURL instance.
*
* @param fileURL the file URL
* @param throwException if set to <code>true</code>, an IOException will be thrown if something went wrong during file creation
* @return the created file
* @throws java.io.IOException if something went wrong during file creation
*/
public static AbstractFile getFile(FileURL fileURL, boolean throwException) throws IOException {
try {return getFile(fileURL, null);}
catch(IOException e) {
LOGGER.info("Caught an exception", e);
if(throwException)
throw e;
return null;
}
}
/**
* Shorthand for {@link #getFile(FileURL, AbstractFile, Authenticator, Object...)} called with the
* {@link #getDefaultAuthenticator() default authenticator}.
*
* @param fileURL the file URL representing the file to be created
* @param parent the parent AbstractFile to use as the created file's parent, can be <code>null</code>
* @return an instance of {@link AbstractFile} for the given {@link FileURL}.
* @throws java.io.IOException if something went wrong during file creation.
*/
public static AbstractFile getFile(FileURL fileURL, AbstractFile parent, Object... instantiationParams) throws IOException {
return getFile(fileURL, parent, defaultAuthenticator, instantiationParams);
}
/**
* Creates and returns an instance of AbstractFile for the given FileURL and uses the specified parent file (if any)
* as the created file's parent.
*
* <p>Specifying the file parent if an instance already exists allows to recycle the AbstractFile instance
* instead of creating a new one when the parent file is requested.
*
* @param fileURL the file URL representing the file to be created
* @param authenticator used to authenticate the specified location if its protocol
* {@link FileURL#getAuthenticationType() is authenticated} and the location contains no credentials already.
* If the value is <code>null</code>, no {@link Authenticator} will be used, not even the default one.
* @param parent the parent AbstractFile to use as the created file's parent, can be <code>null</code>
* @return an instance of {@link AbstractFile} for the given {@link FileURL}.
* @throws java.io.IOException if something went wrong during file creation.
*/
public static AbstractFile getFile(FileURL fileURL, AbstractFile parent, Authenticator authenticator, Object... instantiationParams) throws IOException {
String protocol = fileURL.getScheme();
if(!isRegisteredProtocol(protocol))
throw new IOException("Unsupported file protocol: "+protocol);
// Lookup the pool for an existing AbstractFile instance, only if there are no instantiationParams.
// If there are instantiationParams (the file was created by the AbstractFile implementation directly, that is
// by ls()), any existing file in the pool must be replaced with a new, more up-to-date one.
FilePool filePool = FILE_POOL_MAP.get(fileURL.getScheme().toLowerCase());
if(instantiationParams.length==0) {
// Note: FileURL#equals(Object) and #hashCode() take into account credentials and properties and are
// trailing slash insensitive (e.g. '/root' and '/root/' URLS are one and the same)
AbstractFile file = filePool.get(fileURL);
if(file!=null)
return file;
}
String filePath = fileURL.getPath();
// For local paths under Windows (e.g. "/C:\temp"), remove the leading '/' character
if(OsFamily.WINDOWS.isCurrent() && FileProtocols.FILE.equals(protocol))
filePath = PathUtils.removeLeadingSeparator(filePath, "/");
String pathSeparator = fileURL.getPathSeparator();
PathTokenizer pt = new PathTokenizer(filePath,
pathSeparator,
false);
AbstractFile currentFile = null;
boolean lastFileResolved = false;
// Extract every filename from the path from left to right and for each of them, see if it looks like an archive.
// If it does, create the appropriate protocol file and wrap it with an archive file.
while (pt.hasMoreFilenames()) {
// Test if the filename's extension looks like a supported archive format...
// Note that the archive can also be a directory with an archive extension.
if (isArchiveFilename(pt.nextFilename())) {
// Remove trailing separator of file, some file protocols such as SFTP don't like trailing separators.
// On the contrary, directories without a trailing slash are fine.
String currentPath = PathUtils.removeTrailingSeparator(pt.getCurrentPath(), pathSeparator);
// Test if current file is an archive and if it is, create an archive entry file instead of a raw
// protocol file
if (currentFile==null || !currentFile.isArchive()) {
// Create a fresh FileURL with the current path
FileURL clonedURL = (FileURL)fileURL.clone();
clonedURL.setPath(currentPath);
// Look for a cached file instance before creating a new one
currentFile = filePool.get(clonedURL);
if (currentFile==null) {
currentFile = wrapArchive(createRawFile(clonedURL, authenticator, instantiationParams));
// Add the intermediate file instance to the cache
filePool.put(clonedURL, currentFile);
}
lastFileResolved = true;
}
else { // currentFile is an AbstractArchiveFile
// Note: wrapArchive() is already called by AbstractArchiveFile#createArchiveEntryFile()
AbstractFile tempEntryFile = ((AbstractArchiveFile)currentFile).getArchiveEntryFile(PathUtils.removeLeadingSeparator(currentPath.substring(currentFile.getURL().getPath().length(), currentPath.length()), pathSeparator));
if (tempEntryFile.isArchive()) {
currentFile = tempEntryFile;
lastFileResolved = true;
}
else {
lastFileResolved = false;
}
// Note: don't cache the entry file
}
}
else {
lastFileResolved = false;
}
}
// Create last file if it hasn't been already (if the last filename was not an archive), same routine as above
// except that it doesn't wrap the file with an archive file
if(!lastFileResolved) {
// Note: DON'T strip out the trailing separator, as this would cause problems with root resources
String currentPath = pt.getCurrentPath();
if(currentFile==null || !currentFile.isArchive()) {
FileURL clonedURL = (FileURL)fileURL.clone();
clonedURL.setPath(currentPath);
// Note: no need to look a cached file instance, we have already looked for it at the very beginning.
currentFile = createRawFile(clonedURL, authenticator, instantiationParams);
// Add the final file instance to the cache
filePool.put(currentFile.getURL(), currentFile);
}
else { // currentFile is an AbstractArchiveFile
currentFile = ((AbstractArchiveFile)currentFile).getArchiveEntryFile(PathUtils.removeLeadingSeparator(currentPath.substring(currentFile.getURL().getPath().length(), currentPath.length()), pathSeparator));
// Note: don't cache the entry file
}
}
// Reuse existing parent file instance if one was specified
if(parent!=null)
currentFile.setParent(parent);
return currentFile;
}
private static AbstractFile createRawFile(FileURL fileURL, Authenticator authenticator, Object... instantiationParams) throws IOException {
String scheme = fileURL.getScheme().toLowerCase();
// Special case for local files to avoid provider hashtable lookup and other unnecessary checks
// (for performance reasons)
if(scheme.equals(FileProtocols.FILE)) {
if(localFileProvider == null)
throw new IOException("Unknown file protocol: " + scheme);
return localFileProvider.getFile(fileURL, instantiationParams);
// Uncomment this line and comment the previous one to simulate a slow filesystem
//file = new DebugFile(file, 0, 50);
}
// Use the protocol hashtable for any other file protocol
else {
// If an Authenticator has been specified and the specified FileURL's protocol is authenticated and the
// FileURL doesn't contain any credentials, use it to authenticate the FileURL.
if(authenticator!=null && fileURL.getAuthenticationType()!=AuthenticationType.NO_AUTHENTICATION && !fileURL.containsCredentials())
authenticator.authenticate(fileURL);
// Finds the right file protocol provider
ProtocolProvider provider = getProtocolProvider(scheme);
if(provider == null)
throw new IOException("Unknown file protocol: " + scheme);
return provider.getFile(fileURL, instantiationParams);
}
}
/**
* Returns a variation of the given filename, appending a pseudo-unique ID to the filename's prefix while keeping
* the same filename extension.
*
* @param filename base filename
*/
private static String getFilenameVariation(String filename) {
int lastDotPos = filename.lastIndexOf('.');
int len = filename.length();
String nameSuffix = "_"+System.currentTimeMillis()+(new Random().nextInt(10000));
if(lastDotPos==-1)
filename += nameSuffix;
else
filename = filename.substring(0, lastDotPos) + nameSuffix + filename.substring(lastDotPos, len);
return filename;
}
/**
* Creates and returns a temporary local file using the desired filename. If a file with this name already exists
* in the temp directory, the filename's prefix (name without extension) will be appended an ID. The filename's
* extension will however always be preserved.
*
* <p>The returned file may be a {@link LocalFile} or a {@link AbstractArchiveFile} if the extension corresponds
* to a registered archive format.</p>
*
* @param desiredFilename the desired filename for the temporary file. If a file with this name already exists
* in the temp directory, the filename's prefix (name without extension) will be appended an ID, but the filename's
* extension will always be preserved.
* @param deleteOnExit if <code>true</code>, the temporary file will be deleted upon normal termination of the JVM
* @return the temporary file, may be a LocalFile or an AbstractArchiveFile if the filename's extension corresponds
* to a registered archive format.
* @throws IOException if an error occurred while instantiating the temporary file. This should not happen under
* normal circumstances.
*/
public static AbstractFile getTemporaryFile(String desiredFilename, boolean deleteOnExit) throws IOException {
if(desiredFilename==null || desiredFilename.equals(""))
desiredFilename = "temp";
// Attempt to use the desired name
AbstractFile tempFile = TEMP_DIRECTORY.getDirectChild(desiredFilename);
if(tempFile.exists())
tempFile = TEMP_DIRECTORY.getDirectChild(getFilenameVariation(desiredFilename));
if(deleteOnExit)
((java.io.File)tempFile.getUnderlyingFileObject()).deleteOnExit();
return tempFile;
}
/**
* Creates a temporary file with a default filename. This method is a shorthand for
* {@link #getTemporaryFile(String, boolean)} called with a <code>null</code> name.
*
* @param deleteOnExit if <code>true</code>, the temporary file will be deleted upon normal termination of the JVM
* @return the temporary file, may be a LocalFile or an AbstractArchiveFile if the filename's extension corresponds
* to a registered archive format.
* @throws IOException if an error occurred while instantiating the temporary file. This should not happen under
* normal circumstances.
*/
public static AbstractFile getTemporaryFile(boolean deleteOnExit) throws IOException {
return getTemporaryFile(null, deleteOnExit);
}
/**
* Returns the temporary folder, i.e. the parent folder of temporary files returned by
* {@link #getTemporaryFile(String, boolean)}.
*
* @return the temporary folder
*/
public static AbstractFile getTemporaryFolder() {
return TEMP_DIRECTORY;
}
/**
* Returns true if the given filename's extension matches one of the registered archive formats.
*
* @param filename the filename to test
* @return <code>true</code> if the specified filename is a known archive file name, <code>false</code> otherwise.
*/
public static boolean isArchiveFilename(String filename) {
return getArchiveFormatProvider(filename) != null;
}
/**
* Tests if the given file's extension matches that of one of the registered archive formats.
* If it does, a corresponding {@link AbstractArchiveFile} instance is created on top of the provided file
* and returned. If it doesn't, the provided <code>AbstractFile</code> instance is simply returned.
* <p>
* Note that return {@link AbstractArchiveFile} instances may not actually be archives according to
* {@link AbstractFile#isArchive()}. An {@link AbstractArchiveFile} instance that is not currently an archive
* (either non-existent or a directory) will behave as a regular (non-archive) file. This allows file instances to
* go from being an archive to not being an archive (and vice-versa), without having to re-resolve the file.
* </p>
*/
public static AbstractFile wrapArchive(AbstractFile file) throws IOException {
String filename = file.getName();
// Looks for an archive FilenameFilter that matches the given filename.
// Comparing the filename against each and every archive extension has a cost, so we only perform the test if
// the filename contains a dot '.' character, since most of the time this method is called with a filename that
// doesn't match any of the filters.
if (filename.indexOf('.')!=-1) {
ArchiveFormatProvider provider = getArchiveFormatProvider(filename);
if (provider != null) {
return provider.getFile(file);
}
}
return file;
}
/**
* Same as wrapArchive(AbstractFile) but using the given extension rather than the file's extension.
*/
public static AbstractFile wrapArchive(AbstractFile file, String extension) throws IOException {
String filename = "tmp" + extension;
ArchiveFormatProvider provider = getArchiveFormatProvider(filename);
return provider != null ? provider.getFile(file) : file;
}
/**
* Returns the default {@link com.mucommander.commons.file.icon.FileIconProvider} instance. The default provider class
* (before {@link #setDefaultFileIconProvider(com.mucommander.commons.file.icon.FileIconProvider)} is called) is
* platform-dependent and as such may vary across platforms.
*
* <p>It is noteworthy that the provider returned by this method is used by {@link com.mucommander.commons.file.AbstractFile#getIcon()}
* to create and return the icon.</p>
*
* @return the default FileIconProvider implementation
*/
public static FileIconProvider getDefaultFileIconProvider() {
return defaultFileIconProvider;
}
/**
* Sets the default {@link com.mucommander.commons.file.icon.FileIconProvider} implementation.
*
* <p>It is noteworthy that the provider returned by this method is used by {@link com.mucommander.commons.file.AbstractFile#getIcon()}
* to create and return the icon.</p>
*
* @param fip the new value for the default FileIconProvider
*/
public static void setDefaultFileIconProvider(FileIconProvider fip) {
defaultFileIconProvider = fip;
}
/**
* Returns the default {@link Authenticator} that is used for authenticating {@link FileURL} instances prior
* to resolving the corresponding file.
*
* @return the default {@link Authenticator} that is used for authenticating {@link FileURL} instances prior
* to resolving the corresponding file
* @see Authenticator
*/
public static Authenticator getDefaultAuthenticator() {
return defaultAuthenticator;
}
/**
* Sets the default {@link Authenticator} that is used for authenticating {@link FileURL} instances prior
* to resolving the corresponding file.
*
* @param authenticator the new default {@link Authenticator}
* @see Authenticator
*/
public static void setDefaultAuthenticator(Authenticator authenticator) {
defaultAuthenticator = authenticator;
}
}