/*
 * FileCmd.java --
 *
 *	This file contains the Jacl implementation of the built-in Tcl "file"
 *	command.
 *
 * Copyright (c) 1997 Cornell University.
 * Copyright (c) 1997 Sun Microsystems, Inc.
 *
 * See the file "license.terms" for information on usage and
 * redistribution of this file, and for a DISCLAIMER OF ALL
 * WARRANTIES.
 * 
 * RCS: @(#) $Id: FileCmd.java,v 1.1.1.1 1998/10/14 21:09:20 cvsadmin Exp $
 *
 */

package tcl.lang;

import java.io.*;
import java.util.*;

/*
 * This class implements the built-in "file" command in Tcl.
 */

class FileCmd implements Command {

static Class procClass = null;

static final private String validCmds[] = {
    "atime",
    "attributes",
    "copy",
    "delete",
    "dirname",
    "executable",
    "exists",
    "extension",
    "isdirectory",
    "isfile",
    "join",
    "lstat",
    "mtime",
    "mkdir",
    "nativename",
    "owned",
    "pathtype",
    "readable",
    "readlink",
    "rename",
    "rootname",
    "size",
    "split",
    "stat",
    "tail",
    "type",
    "volumes",
    "writable"
};


private static final int OPT_ATIME 		=  0;
private static final int OPT_ATTRIBUTES         =  1;
private static final int OPT_COPY 		=  2;
private static final int OPT_DELETE 		=  3;
private static final int OPT_DIRNAME 		=  4;
private static final int OPT_EXECUTABLE 	=  5;
private static final int OPT_EXISTS 		=  6;
private static final int OPT_EXTENSION 		=  7;
private static final int OPT_ISDIRECTORY 	=  8;
private static final int OPT_ISFILE 		=  9;
private static final int OPT_JOIN 		= 10;
private static final int OPT_LSTAT 		= 11;
private static final int OPT_MTIME 		= 12;
private static final int OPT_MKDIR 		= 13;
private static final int OPT_NATIVENAME 	= 14;
private static final int OPT_OWNED 		= 15;
private static final int OPT_PATHTYPE 		= 16;
private static final int OPT_READABLE 		= 17;
private static final int OPT_READLINK 		= 18;
private static final int OPT_RENAME 		= 19;
private static final int OPT_ROOTNAME 		= 20;
private static final int OPT_SIZE 		= 21;
private static final int OPT_SPLIT 		= 22;
private static final int OPT_STAT 		= 23;
private static final int OPT_TAIL 		= 24;
private static final int OPT_TYPE 		= 25;
private static final int OPT_VOLUMES 		= 26;
private static final int OPT_WRITABLE 		= 27;

private static final String validOptions[] = {
    "-force",
    "--"
};

private static final int OPT_FORCE 	= 0;
private static final int OPT_LAST 	= 1;


/*
 *-----------------------------------------------------------------------------
 *
 * cmdProc --
 *
 *	This procedure is invoked to process the "file" Tcl command.
 *	See the user documentation for details on what it does.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	See the user documentation.
 *
 *-----------------------------------------------------------------------------
 */

public void 
cmdProc(
    Interp interp,  			// Current interp to eval the file cmd.
    TclObject argv[])			// Args passed to the file command.
throws
    TclException
{
    if (argv.length < 3) {
	throw new TclNumArgsException(interp, 1, argv, 
		"option name ?arg ...?");
    }

    int opt = TclIndex.get(interp, argv[1], validCmds, "option", 0);
    String path;
    File fileObj = null;

    switch (opt) {
    case OPT_ATIME:
	if (argv.length != 3) {
	    throw new TclNumArgsException(interp, 2, argv, "name");
	}

	/*
	 * WARNING:  Currently returns the same thing as MTIME.
	 * Java does not support retrieval of access time.
	 */

	fileObj = FileUtil.getNewFileObj(interp, argv[2].toString());
	interp.setResult(getMtime(interp, argv[2].toString(), fileObj));
	return;

    case OPT_ATTRIBUTES:
        //WARNING:   not implemented yet

        throw new TclException(interp,
		"sorry, \"file attributes\" is not implemented yet");

        //return;
    case OPT_COPY:
	fileCopyRename(interp, argv, true);
	return;

    case OPT_DELETE:
	fileDelete(interp, argv);
	return;

    case OPT_DIRNAME:
	if (argv.length != 3) {
	    throw new TclNumArgsException(interp, 2, argv, "name");
	}
	path = argv[2].toString();

	/*
	 * Return all but the last component.  If there is only one
	 * component, return it if the path was non-relative, otherwise
	 * return the current directory.
	 */

 	TclObject splitArrayObj[] = TclList.getElements(interp,
 		FileUtil.splitAndTranslate(interp, path));

	if (splitArrayObj.length > 1) {
	    interp.setResult(FileUtil.joinPath(interp, splitArrayObj, 0, 
		    splitArrayObj.length - 1));
	} else if ((splitArrayObj.length == 0) ||
		(FileUtil.getPathType(path) == FileUtil.PATH_RELATIVE)) {
	    if (JACL.PLATFORM == JACL.PLATFORM_MAC) {
		interp.setResult(":");
	    } else {
		interp.setResult(".");
	    }
	} else {
	    interp.setResult(splitArrayObj[0].toString());
	}
	return;

    case OPT_EXECUTABLE:
	if (argv.length != 3) {
	    throw new TclNumArgsException(interp, 2, argv, "name");
	}
	boolean isExe = false;
	fileObj = FileUtil.getNewFileObj(interp, argv[2].toString());

	/* 
	 * A file must exist to be executable.  Directories are always
	 * executable. 
	 */

	if (fileObj.exists()) {
	    isExe = fileObj.isDirectory();
	    if (isExe) {
		interp.setResult(isExe);
		return;
	    }

	    if (Util.isWindows()) {
		/*
		 * File that ends with .exe, .com, or .bat is executable.
		 */

		String fileName = argv[2].toString();
		isExe = (fileName.endsWith(".exe") ||
			fileName.endsWith(".com") ||
			fileName.endsWith(".bat"));
	    } else if (Util.isMac()) {
		/*
		 * WARNING:  Not yet implemented on Mac.  For now, return true.
		 * Java does not support executability checking.
		 */

		isExe = true;
	    } else {
		/*
		 * WARNING:  Not yet implemented on Unix.  For now, return true.
		 * Java does not support executability checking.
		 */

		isExe = true;
	    }
	}
	interp.setResult(isExe);
	return;

    case OPT_EXISTS:
	if (argv.length != 3) {
	    throw new TclNumArgsException(interp, 2, argv, "name");
	}
	fileObj = FileUtil.getNewFileObj(interp, argv[2].toString());
	interp.setResult(fileObj.exists());
	return;
	
    case OPT_EXTENSION:
	if (argv.length != 3) {
	    throw new TclNumArgsException(interp, 2, argv, "name");
	}
	interp.setResult(getExtension(argv[2].toString()));
	return;
	
    case OPT_ISDIRECTORY:
	if (argv.length != 3) {
	    throw new TclNumArgsException(interp, 2, argv, "name");
	}
	fileObj = FileUtil.getNewFileObj(interp, argv[2].toString());
	interp.setResult(fileObj.isDirectory());
	return;

    case OPT_ISFILE:
	if (argv.length != 3) {
	    throw new TclNumArgsException(interp, 2, argv, "name");
	}
	fileObj = FileUtil.getNewFileObj(interp, argv[2].toString());
	interp.setResult(fileObj.isFile());
	return;
	
    case OPT_JOIN:
	interp.setResult(FileUtil.joinPath(interp, argv, 2, argv.length));
	return;

    case OPT_LSTAT:
	if (argv.length != 4) {
	    throw new TclNumArgsException(interp, 2, argv, "name varName");
	}

	/*
	 * WARNING:  Not yet implemented.
	 * Java does not support link access.
	 */

	throw new TclException(interp, "file command with opt " + 
		argv[1].toString() + " is not yet implemented");

	
    case OPT_MTIME:
	if (argv.length != 3) {
	    throw new TclNumArgsException(interp, 2, argv, "name");
	}
	fileObj = FileUtil.getNewFileObj(interp, argv[2].toString());
	interp.setResult(getMtime(interp, argv[2].toString(), fileObj));
	return;

    case OPT_MKDIR:
	fileMakeDirs(interp, argv);
	return;

    case OPT_NATIVENAME:
        if (argv.length != 3) {
	    throw new TclNumArgsException(interp, 2, argv, "name");
	}

	interp.setResult(
	      FileUtil.translateFileName(interp, argv[2].toString()));
        return;
    case OPT_OWNED:
	if (argv.length != 3) {
	    throw new TclNumArgsException(interp, 2, argv, "name");
	}
	fileObj = FileUtil.getNewFileObj(interp, argv[2].toString());
	interp.setResult(isOwner(interp, fileObj));
	return;

    case OPT_PATHTYPE:
	if (argv.length != 3) {
	    throw new TclNumArgsException(interp, 2, argv, "name");
	}
	switch (FileUtil.getPathType(argv[2].toString())) {
	case FileUtil.PATH_RELATIVE:
	    interp.setResult("relative");
	    return;
	case FileUtil.PATH_VOLUME_RELATIVE:
	    interp.setResult("volumerelative");
	    return;
	case FileUtil.PATH_ABSOLUTE:
	    interp.setResult("absolute");
	}
	return;

    case OPT_READABLE:
	if (argv.length != 3) {
	    throw new TclNumArgsException(interp, 2, argv, "name");
	}
	fileObj = FileUtil.getNewFileObj(interp, argv[2].toString());
	interp.setResult(fileObj.canRead());
	return;

    case OPT_READLINK:
	if (argv.length != 3) {
	    throw new TclNumArgsException(interp, 2, argv, "name");
	}

	/*
	 * WARNING:  Not yet implemented.
	 * Java does not support link access.
	 */

	throw new TclException(interp, "file command with opt " + 
		argv[1].toString() + " is not yet implemented");

    case OPT_RENAME:
	fileCopyRename(interp, argv, false);
	return;
	
    case OPT_ROOTNAME:
	if (argv.length != 3) {
	    throw new TclNumArgsException(interp, 2, argv, "name");
	}
	String fileName = argv[2].toString();
	String extension = getExtension(fileName);
	int diffLength = fileName.length() - extension.length();
	interp.setResult(fileName.substring(0, diffLength));
	return;
	
    case OPT_SIZE:
	if (argv.length != 3) {
	    throw new TclNumArgsException(interp, 2, argv, "name");
	}
	fileObj = FileUtil.getNewFileObj(interp, argv[2].toString());
	if (!fileObj.exists()) {
	    throw new TclPosixException(interp, TclPosixException.ENOENT, true, 
		"could not read \"" + argv[2].toString() + "\"");
	}
	interp.setResult((int) fileObj.length());
	return;
	
    case OPT_SPLIT:
	if (argv.length != 3) {
	    throw new TclNumArgsException(interp, 2, argv, "name");
	}
	interp.setResult(FileUtil.splitPath(interp, argv[2].toString()));
	return;
	
    case OPT_STAT:
	if (argv.length != 4) {
	    throw new TclNumArgsException(interp, 2, argv, "name varName");
	}
	getAndStoreStatData(interp, argv[2].toString(), argv[3].toString());
	return;
	
    case OPT_TAIL:
	if (argv.length != 3) {
	    throw new TclNumArgsException(interp, 2, argv, "name");
	}
	interp.setResult(getTail(interp, argv[2].toString()));
	return;

    case OPT_TYPE:
	if (argv.length != 3) {
	    throw new TclNumArgsException(interp, 2, argv, "name");
	}
	fileObj = FileUtil.getNewFileObj(interp, argv[2].toString());
	interp.setResult(getType(interp, argv[2].toString(), fileObj));
	return;

    case OPT_VOLUMES:
        //WARNING:   not implemented yet

        //Java 1.2 has a new getRoots() method that would work here

        throw new TclException(interp,
		"sorry, \"file volumes\" is not implemented yet");

        //return; 
    case OPT_WRITABLE:
	if (argv.length != 3) {
	    throw new TclNumArgsException(interp, 2, argv, "name");
	}
	fileObj = FileUtil.getNewFileObj(interp, argv[2].toString());
	interp.setResult(fileObj.canWrite());
	return;
    default:
	throw new TclRuntimeError("file command with opt " + 
		argv[1].toString() + " is not implemented");
    }
}

/*-----------------------------------------------------------------------------
 *
 * isOwner --
 *
 *	If "File" is owned by the uid associated with the program, return
 *	true.  Otherwise, return false.
 *
 * Results:
 *	Boolean.
 *
 * Side effects:
 *	None.
 *
 *-----------------------------------------------------------------------------
 */

private static boolean
isOwner(
    Interp interp, 			// Interpreter for error reports. 
    File fileObj) 			// File obj whose owner to find.
throws 
    TclException 			// Thrown if unimplemented code segment
					// is reached
{
    /*
     * If the file doesn't exist, return false;
     */

    if (!fileObj.exists()) {
	return false;
    }
    boolean owner = true;

    /*
     * For Windows and Macintosh, there are no user ids 
     * associated with a file, so we always return 1.
     */

    if (Util.isUnix()) {
	/*
	 * WARNING:  Not yet implemented on Unix.  Do no checking, for now.
	 * Java does not support ownership checking.
	 */
    }
    return owner;
}

/*
 *-----------------------------------------------------------------------------
 *
 * getMtime --
 *
 *	Finds the last modification of file in fileObj.
 *	WARNING:   The return value of lastModified() is system dependent and
 *	should only be used to compare with other values returned by last
 *	modified. It should not be interpreted as an absolute time. 
 *
 * Results:
 *	Returns an int representation of modification time.
 *
 * Side effects:
 *	None.
 *
 *-----------------------------------------------------------------------------
 */

private static int
getMtime(
    Interp interp, 			// Interpreter for error reports. 
    String fileName, 			// Name of file whose mtime to find.
    File fileObj) 			// File obj whose mtime to find.
throws 
    TclException 			// Exceptions thrown as a result of bad
					//   user input.
{
    if (!fileObj.exists()) {
	throw new TclPosixException(interp, TclPosixException.ENOENT, true, 
		"could not read \"" + fileName + "\"");
    }
    return (int) fileObj.lastModified();
}

/*
 *-----------------------------------------------------------------------------
 *
 * getType --
 *
 *	Finds the type of file in fileObj.
 *	WARNING:  Only checks for file and directory status.  If neither file
 *	or direcotry, return link.  Java only supports file and directory
 *	checking.
 *
 * Results:
 *	Returns a string "file", "directory", etc.
 *
 * Side effects:
 *	None.
 *
 *-----------------------------------------------------------------------------
 */

private static String
getType(
    Interp interp, 			// Interpreter for error reports. 
    String fileName, 			// Name of file whose owner to find.
    File fileObj) 			// File obj whose owner to find.
throws 
    TclException 			// Exceptions thrown as a result of bad
					//   user input.
{
    if (!fileObj.exists()) {
	throw new TclPosixException(interp, TclPosixException.ENOENT, true, 
		"could not read \"" + fileName + "\"");
    }

    if (fileObj.isFile()) {
	return "file";
    } else if (fileObj.isDirectory()) {
	return "directory";
    }
    return "link";
}

/*
 *-----------------------------------------------------------------------------
 *
 * getAndStoreStatData --
 *
 *	This is a utility procedure that breaks out the fields of a
 *	"stat" structure and stores them in textual form into the
 *	elements of an associative array.
 *	WARNING: skipping dev, gid, ino, mode, and nlink attributes.
 *	WARNING: ctime and atime are the same as mtime.
 *	Java does not support the above attributes.
 *
 * Results:
 *	Returns a standard Tcl return value.  If an error occurs then
 *	a message is left in interp->result.
 *
 * Side effects:
 *	Elements of the associative array given by "varName" are modified.
 *
 *-----------------------------------------------------------------------------
 */

private static void
getAndStoreStatData(
    Interp interp, 			// Interpreter for error reports. 
    String fileName, 			// Name of file whose stats to find.
    String varName) 			// Name of associative array variable
 		 			//   in which to store stat results.
throws 
    TclException 			// Exceptions thrown as a result of bad
					//   user input.
{
    File fileObj = FileUtil.getNewFileObj(interp, fileName);

    if (!fileObj.exists()) {
	throw new TclPosixException(interp, TclPosixException.ENOENT, true, 
		"could not read \"" + fileName + "\"");
    }

    try {
	int mtime = getMtime(interp, fileName, fileObj);
	TclObject mtimeObj = TclInteger.newInstance(mtime);
	TclObject atimeObj = TclInteger.newInstance(mtime);
	TclObject ctimeObj = TclInteger.newInstance(mtime);
	interp.setVar(varName, "atime", atimeObj, 0);
	interp.setVar(varName, "ctime", ctimeObj, 0);
	interp.setVar(varName, "mtime", mtimeObj, 0);
    } catch (SecurityException e) {
	throw new TclException(interp, e.getMessage());
    } catch (TclException e) {
	throw new TclException(interp, "can't set \"" + varName + 
		"(dev)\": variable isn't array");
    }

    try {
	TclObject sizeObj = TclInteger.newInstance((int) fileObj.length());
	interp.setVar(varName, "size", sizeObj, 0);
    } catch (Exception e) {
	/* Do nothing. */
    }

    try {
	TclObject typeObj = TclString.newInstance(
	    getType(interp, fileName, fileObj));
	interp.setVar(varName, "type", typeObj, 0);
    } catch (Exception e) {
    }

    try {
	TclObject uidObj = TclBoolean.newInstance(isOwner(interp, fileObj));
	interp.setVar(varName, "uid", uidObj, 0);
    } catch (TclException e) {
	/* Do nothing. */
    }
}

/*
 *-----------------------------------------------------------------------------
 *
 * getExtension --
 *
 *	Return the substring of "path" which represents the file's
 *	extension.  It is necessary to perform system specific 
 *	operations because different systems have different separators.
 *
 * Results:
 *	Returns a file extension String.
 *
 * Side effects:
 *	None.
 *
 *-----------------------------------------------------------------------------
 */

private static String
getExtension(
    String path) 			// Path for which we find extension.
{	
    if (path.length() < 1) {
	return "";
    }

    /*
     * Set lastSepIndex to the first index in the last component of the path.
     */

    int lastSepIndex = -1;
    switch (JACL.PLATFORM) {
    case JACL.PLATFORM_WINDOWS:
	String tmpPath = path.replace('\\', '/').replace(':', '/');
	lastSepIndex = tmpPath.lastIndexOf('/');
	break;
    case JACL.PLATFORM_MAC:
	lastSepIndex = path.lastIndexOf(':');
	if (lastSepIndex == -1) {
	    lastSepIndex = path.lastIndexOf('/');
	}
	break;
    default:
	lastSepIndex = path.lastIndexOf('/');
    }
    ++lastSepIndex;

    /*
     * Return "" if the last character is a separator.
     */

    if (lastSepIndex >= path.length()) {
	return ("");
    }

    /*
     * Find the last dot in the last component of the path.
     */
    
    String lastSep = path.substring(lastSepIndex);
    int dotIndex = lastSep.lastIndexOf('.');

    /*
     * Return "" if no dot was found in the file's name.
     */

    if (dotIndex == -1) {
	return "";
    }		

    /*
     * Back up to the first period in a series of contiguous dots.
     * This is needed so foo..o will be split on the first dot.
     */

    while ((dotIndex > 0) && (lastSep.charAt(dotIndex - 1) == '.')) {
	--dotIndex;
    }

    return(lastSep.substring(dotIndex));
}

/*
 *-----------------------------------------------------------------------------
 *
 * getTail --
 *
 *	Return the substring of "path" which represents the file's
 *	tail.  It is necessary to perform system specific 
 *	operations because different systems have different separators.
 *
 * Results:
 *	Returns a file tail String.
 *
 * Side effects:
 *	None.
 *
 *-----------------------------------------------------------------------------
 */

private static String
getTail(
    Interp interp, 			// Current interpreter. 
    String path)  			// Path for which to find the tail.
throws 
    TclException 			// Thrown if tilde subst, which may be
					//   called by splitAndTranslate, fails. 
{
    /*
     * Split the path and return the string form of the last component,
     * unless there is only one component which is the root or an absolute
     * path. 
     */

    TclObject splitResult = FileUtil.splitAndTranslate(interp, path);

    int last = TclList.getLength(interp, splitResult) - 1;

    if (last >= 0) {
	if ((last > 0) || 
		(FileUtil.getPathType(path) == FileUtil.PATH_RELATIVE)) {
	    TclObject tailObj = TclList.index(interp, splitResult, last);
	    return tailObj.toString();
	}
    }
    return "";
}

/*
 *-----------------------------------------------------------------------------
 *
 * fileMakeDirs --
 *
 *	This procedure implements the "mkdir" subcommand of the "file"
 *      command.  Filename arguments need to be translated to native
 *	format before being passed to platform-specific code that
 *	implements mkdir functionality.
 *	WARNING:  ignoring links because Java does not support them.
 *	
 * Results:
 *	None.
 *
 * Side effects:
 *	See user documentation.
 *
 *-----------------------------------------------------------------------------
 */

private static void
fileMakeDirs(
    Interp interp,  			// Current interpreter. 
    TclObject[] argv)			// Arguments to "file" command.
throws 
    TclException 			// Thrown as a result of bad user input.
{
    boolean madeDir = false;

    for (int currentDir = 2; currentDir < argv.length; currentDir++) {

	String dirName = argv[currentDir].toString();
	if (dirName.length() == 0) {
	    throw new TclPosixException(interp, TclPosixException.ENOENT,
		    true, "can't create directory \"\"");
	}
	File dirObj = FileUtil.getNewFileObj(interp, dirName);
	if (dirObj.exists()) {
	    /*
	     * If the directory already exists, do nothing.
	     */

	    if (dirObj.isDirectory()) {
		continue;
	    }
	    throw new TclPosixException(interp, TclPosixException.EEXIST,
		    true, "can't create directory \"" + dirName + "\"");
	}
	try {
	    madeDir = dirObj.mkdir();
	    if (!madeDir) {
		madeDir = dirObj.mkdirs();
	    }
	} catch (SecurityException e) {
	    throw new TclException(interp, e.getMessage());
	}
	if (!madeDir) {
	    throw new TclPosixException(interp, TclPosixException.EACCES, true,
		    "can't create directory \"" + dirName +
		    "\":  best guess at reason");
	}
    }
}

/*
 *-----------------------------------------------------------------------------
 *
 * fileDelete --
 *
 *	This procedure implements the "delete" subcommand of the "file"
 *      command.
 *	
 * Results:
 *	None.
 *
 * Side effects:
 *	See user documentation.
 *
 *-----------------------------------------------------------------------------
 */

private static void
fileDelete(
    Interp interp,  			// Current interpreter. 
    TclObject[] argv) 			// Arguments to "file" command.
throws 
    TclException 			// Thrown as a result of bad user input.
{
    boolean force = false;
    int firstSource = 2;
    
    for (boolean last = false; (firstSource < argv.length) && (!last);
	 firstSource++) { 

	if (!argv[firstSource].toString().startsWith("-")) {
	    break;
	}
	int opt = TclIndex.get(interp, argv[firstSource],
		validOptions, "option", 1);
	switch (opt) {
	case OPT_FORCE:
	    force = true;
	    break;
	case OPT_LAST:
	    last = true;
	    break;
	default:
	    throw new TclRuntimeError("FileCmd.cmdProc: bad option " + opt 
		    + " index to validOptions");
	}
    }
    
    if (firstSource >= argv.length) {
	throw new TclNumArgsException(interp, 2, argv,
		"?options? file ?file ...?");
    }

    for (int i = firstSource; i < argv.length; i++) {
	deleteOneFile(interp, argv[i].toString(), force);
    }
}

/*
 *-----------------------------------------------------------------------------
 *
 * deleteOneFile --
 *
 *	After performing error checking, deletes the specified file.
 *	WARNING:  ignoring links because Java does not support them.
 *	
 * Results:
 *	None.
 *
 * Side effects:
 *	See user documentation.
 *
 *-----------------------------------------------------------------------------
 */

private static void
deleteOneFile(
    Interp interp,  			// Current interpreter. 
    String fileName,  			// Name of file to delete.
    boolean force) 			// Flag tells whether to delete
					//   recursively.
throws
    TclException 			// Thrown as a result of bad user input.
{
    boolean isDeleted = true;
    File fileObj = FileUtil.getNewFileObj(interp, fileName);
	
    /*
     * Trying to delete a file that does not exist is not
     * considered an error, just a no-op
     */
	
    if ((!fileObj.exists()) || (fileName.length() == 0)) {
	return;
    }
	
    /*
     * If the file is a non-empty directory, recursively delete its children if
     * the -force option was chosen.  Otherwise, throw an error.
     */

    if (fileObj.isDirectory() && (fileObj.list().length > 0)) {
	if (force) {
	    String fileList[] = fileObj.list();
	    for (int i = 0; i < fileList.length; i++) {

		TclObject joinArrayObj[] = new TclObject[2];
		joinArrayObj[0] = TclString.newInstance(fileName);
		joinArrayObj[1] = TclString.newInstance(fileList[i]);

		String child = FileUtil.joinPath(interp, joinArrayObj, 0, 2);
		deleteOneFile(interp, child, force);
	    }
	} else {
	    throw new TclPosixException(interp,
		    TclPosixException.ENOTEMPTY, "error deleting \"" +
		    fileName + "\": directory not empty");
	}
    }
    try {
	isDeleted = fileObj.delete();
    } catch (SecurityException e) {
	throw new TclException(interp, e.getMessage());
    }
    if (!isDeleted) {
	throw new TclPosixException(interp, TclPosixException.EACCES, true,
		"error deleting \"" + fileName + "\":  best guess at reason");
    }
}

/*
 *-----------------------------------------------------------------------------
 *
 * fileCopyRename --
 *
 *	This procedure implements the "copy" and "rename" subcommands of the
 *	"file" command.  Filename arguments need to be translated to native
 *	format before being passed to platform-specific code that implements 
 *	copy functionality.
 *	
 * Results:
 *	None.
 *
 * Side effects:
 *	Target is overwritten if the force flag is set.  Attempting to
 *	copy/rename a file onto a directory or a directory onto a file
 *	will always result in an error.  
 *	See user documentation.
 *
 *-----------------------------------------------------------------------------
 */

private static void
fileCopyRename(
    Interp interp,  			// Current interpreter. 
    TclObject[] argv, 			// Arguments to "file" command.
    boolean copyFlag)			// Flag tells whether to copy or rename.
throws 
    TclException 			// Thrown as a result of bad user input.
{
    int firstSource = 2;
    boolean force = false;

    for (boolean last = false; (firstSource < argv.length) && (!last);
	 firstSource++) { 

	if (!argv[firstSource].toString().startsWith("-")) {
	    break;
	}
	int opt = TclIndex.get(interp, argv[firstSource],
		validOptions, "option", 1);
	switch (opt) {
	case OPT_FORCE:
	    force = true;
	    break;
	case OPT_LAST:
	    last = true;
	    break;
	default:
	    throw new TclRuntimeError("FileCmd.cmdProc: bad option " + opt 
		    + " index to validOptions");
	}
    }

    if (firstSource >= (argv.length - 1)) {
	throw new TclNumArgsException(interp, firstSource, argv,
		"?options? source ?source ...? target");
    }

    /*
     * WARNING:  ignoring links because Java does not support them.
     */

    int target = argv.length - 1;
    String targetName = argv[target].toString();

    File targetObj = FileUtil.getNewFileObj(interp, targetName);
    if (targetObj.isDirectory()) {
	/*
	 * If the target is a directory, move each source file into target
	 * directory.  Extract the tailname from each source, and append it to
	 * the end of the target path.  
	 */
    
	for (int source = firstSource; source < target; source++) {

	    String sourceName = argv[source].toString();
	    
	    if (targetName.length() == 0) {
		copyRenameOneFile(interp, sourceName, targetName,
			copyFlag, force);
	    } else {
		String tailName = getTail(interp, sourceName);
		
		TclObject joinArrayObj[] = new TclObject[2];
		joinArrayObj[0] = TclString.newInstance(targetName);
		joinArrayObj[1] = TclString.newInstance(tailName);

		String fullTargetName = FileUtil.joinPath(interp, 
			joinArrayObj, 0, 2);

		copyRenameOneFile(interp, sourceName, fullTargetName, 
			copyFlag, force);
	    }
	}
    } else {
	/*
	 * If there is more than 1 source file and the target is not a
	 * directory, then throw an exception.
	 */
	    
	if (firstSource + 1 != target) {
	    String action;
	    if (copyFlag) {
		action = "copying";
	    } else {
		action = "renaming";
	    }
	    throw new TclPosixException(interp, TclPosixException.ENOTDIR, 
		    "error " + action + ": target \"" + 
		    argv[target].toString() + "\" is not a directory");
	}
	String sourceName = argv[firstSource].toString();
	copyRenameOneFile(interp, sourceName, targetName, copyFlag, force);
    }
}

/*
 *-----------------------------------------------------------------------------
 *
 * copyRenameOneFile --
 *
 *	After performing error checking, performs the copy and rename commands.
 *	WARNING:  ignoring links because Java does not support them.
 *	
 * Results:
 *	None.
 *
 * Side effects:
 *	See user documentation.
 *
 *-----------------------------------------------------------------------------
 */

private static void 
copyRenameOneFile(
    Interp interp,  			// Current interpreter. 
    String sourceName,  		// Name of source file.
    String targetName,  		// Name of target file.
    boolean copyFlag,  			// Flag tells whether to copy or rename.
    boolean force) 			// Flag tells whether to overwrite.
throws 
    TclException 			// Thrown as a result of bad user input.
{		
    /* 
     * Copying or renaming a file onto itself is a no-op if force is chosen,
     * otherwise, it will be caught later as an EEXISTS error.
     */

    if (force && sourceName.equals(targetName)) {
	return;
    }

    /*
     * Check that the source exists and that if -force was not specified, the
     * target doesn't exist.
     *
     * Prevent copying/renaming a file onto a directory and
     * vice-versa.  This is a policy decision based on the fact that
     * existing implementations of copy and rename on all platforms
     * also prevent this.
     */

    String action;
    if (copyFlag) {
	action = "copying";
    } else {
	action = "renaming";
    }

    File sourceFileObj = FileUtil.getNewFileObj(interp, sourceName);
    if ((!sourceFileObj.exists()) || (sourceName.length() == 0)) {
	throw new TclPosixException(interp, TclPosixException.ENOENT, true, 
		"error " + action + " \"" + sourceName + "\"");
    }

    if (targetName.length() == 0) {
	throw new TclPosixException(interp, TclPosixException.ENOENT, true, 
		"error " + action + " \"" + sourceName + "\" to \"" +
		targetName + "\"");
    }
    File targetFileObj = FileUtil.getNewFileObj(interp, targetName);
    if (targetFileObj.exists() && !force) {
	throw new TclPosixException(interp, TclPosixException.EEXIST, true, 
		"error " + action + " \"" + sourceName + "\" to \"" +
		targetName + "\"");
    }

    if (sourceFileObj.isDirectory() &&
	    !targetFileObj.isDirectory()) {
	throw new TclPosixException(interp, TclPosixException.EISDIR,
		"can't overwrite file \"" + targetName + 
		"\" with directory \"" + sourceName + "\"");
    }
    if (targetFileObj.isDirectory() &&
	   !sourceFileObj.isDirectory()) {
	throw new TclPosixException(interp, TclPosixException.EISDIR,
		"can't overwrite directory \"" + targetName + 
		"\" with file \"" + sourceName + "\"");
    }

    if (!copyFlag) {
	/*
	 * Perform the rename procedure.
	 */

	if (!sourceFileObj.renameTo(targetFileObj)) {
	    throw new TclPosixException(interp, TclPosixException.EACCES, true,
		    "error renaming \"" + sourceName + "\" to \"" + targetName
		    + "\":  best guess at reason");
	}
    } else {
	/*
	 * Perform the copy procedure.
	 */

	try {
	    /*
	     * Read source to "cbuf" char array one line at a time.
	     * For each line, Write cbuf to target.
	     */

	    BufferedReader reader = 
		new BufferedReader(new FileReader(sourceFileObj));
	    BufferedWriter writer = 
		new BufferedWriter(new FileWriter(targetFileObj));
	    char cbuf[] = new char[256];
	    int currentIndex = 0;
	    int numChars = reader.read(cbuf, 0, 256);
	    while (numChars != -1) {
		writer.write(cbuf, currentIndex, numChars); 
		currentIndex += 256;
		numChars = reader.read(cbuf, currentIndex, 256);
	    }
	    reader.close();
	    writer.close();
	} catch (Exception e) {
	    throw new TclException(interp, "error copying: " + e.getMessage());
	}
    }
}

} // end FileCmd class
