package org.marketcetera.util.file;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import org.marketcetera.util.misc.ClassVersion;
/**
* A file type. Files on NTFS are limited to the following types:
* {@link #NONEXISTENT}, {@link #FILE}, {@link #DIR} (folder), and
* {@link #UNKNOWN} (the file may or may not exist, and, if it does,
* its type cannot be determined). On Linux, {@link #LINK_FILE} and
* {@link #LINK_DIR} also apply to symbolic links that point to
* symbolic links (and so on) that eventually point to a file or
* directory, respectively.
*
* @author tlerios@marketcetera.com
* @since 0.5.0
* @version $Id: FileType.java 16154 2012-07-14 16:34:05Z colin $
*/
/* $License$ */
@ClassVersion("$Id: FileType.java 16154 2012-07-14 16:34:05Z colin $")
public enum FileType
{
NONEXISTENT,
LINK_FILE,
FILE,
LINK_DIR,
DIR,
LINK_UNKNOWN,
UNKNOWN;
// CLASS METHODS.
/**
* Returns the enumerated constant representing the type of the
* given file.
*
* @param file The file. It may be null, in which case
* {@link #UNKNOWN} is returned.
*
* @return The enumerated constant.
*/
public static final FileType get
(File file)
{
if (file==null) {
return UNKNOWN;
}
try {
// Obtain parent safely.
File absFile=file.getAbsoluteFile();
if (absFile==null) {
return UNKNOWN;
}
File parent=absFile.getParentFile();
// Nonexistent files (per Java).
if (!file.exists()) {
// Java reports unresolvable (dangling or recursive)
// links as not existing. However, we can distinguish
// such a bad link from a truly nonexistent file by
// checking whether its parent lists it as a child.
if (parent!=null) {
String[] children=parent.list();
if ((children!=null) &&
(Arrays.asList(children).contains(file.getName()))) {
return LINK_UNKNOWN;
}
}
return NONEXISTENT;
}
// EXTREME TEST 1: comment out the next line.
// if (true) throw new IOException();
// Ensure parent's path within file's path does not
// contain symlinks or other aliases.
// For example, if d is a directory, l -> d is a link and
// d/f is a file, then l/f is a file. Its absolute path is
// 'xxx/l/f' and its canonical is 'xxx/d/f'. They are
// distinct, hence we first need to canonicalize the path
// of the parent l before comparing the absolute and
// canonical paths of f itself.
// Relative names for f result in additional complexity. f
// may have no parent in this case (meaning that it may
// have a parent directory, but getParentFile() may return
// null), yet its abolute and canonical paths may be
// distinct: under Windows XP, the canonical path will
// choose full names for ancestral directories, while the
// absolute path will choose DOS short names. As a result,
// we still need to canonicalize the path of f's parent
// before comparing f's absolute and canonical names. We
// do that by obtaining the parent not of f, but of the
// absolute f (we don't want the parent of the canonical f
// because, if f is a link f -> d/f2, the parent of the
// canonical f could be f2's parent, which is a wholly
// different directory, the subdirectory d in this
// example).
if (parent!=null) {
File pCanFile=parent.getCanonicalFile();
if (pCanFile==null) {
return UNKNOWN;
}
file=new File(pCanFile,file.getName());
}
// Compare file's absolute name against canonical name.
absFile=file.getAbsoluteFile();
File canFile=file.getCanonicalFile();
if ((canFile==null) || (absFile==null)) {
return UNKNOWN;
}
if (canFile.equals(absFile)) {
if (file.isDirectory()) {
return DIR;
}
if (file.isFile()) {
return FILE;
}
} else {
if (file.isDirectory()) {
return LINK_DIR;
}
if (file.isFile()) {
return LINK_FILE;
}
}
} catch (IOException ex) {
Messages.CANNOT_GET_TYPE.warn
(FileType.class,ex,file.getAbsolutePath());
}
return UNKNOWN;
}
/**
* Returns the enumerated constant representing the type of the
* file with the given name.
*
* @param name The file name. It may be null, in which case {@link
* #UNKNOWN} is returned.
*
* @return The enumerated constant.
*/
public static final FileType get
(String name)
{
if (name==null) {
return UNKNOWN;
}
return get(new File(name));
}
// INSTANCE METHODS.
/**
* Returns true if the receiver represents a symbolic link.
*
* @return True if so.
*/
public boolean isSymbolicLink()
{
return ((this==LINK_FILE) ||
(this==LINK_DIR) ||
(this==LINK_UNKNOWN));
}
/**
* Returns true if the receiver represents a directory (possibly
* via a symbolic link).
*
* @return True if so.
*/
public boolean isDirectory()
{
return ((this==LINK_DIR) ||
(this==DIR));
}
/**
* Returns true if the receiver represents a plain file (possibly
* via a symbolic link).
*
* @return True if so.
*/
public boolean isFile()
{
return ((this==LINK_FILE) ||
(this==FILE));
}
}