0001: /*
0002: * FileCmd.java --
0003: *
0004: * This file contains the Jacl implementation of the built-in Tcl "file"
0005: * command.
0006: *
0007: * Copyright (c) 1997 Cornell University.
0008: * Copyright (c) 1997 Sun Microsystems, Inc.
0009: *
0010: * See the file "license.terms" for information on usage and
0011: * redistribution of this file, and for a DISCLAIMER OF ALL
0012: * WARRANTIES.
0013: *
0014: * RCS: @(#) $Id: FileCmd.java,v 1.10 2005/11/07 07:41:51 mdejong Exp $
0015: *
0016: */
0017:
0018: package tcl.lang;
0019:
0020: import java.io.*;
0021: import java.util.*;
0022: import java.lang.reflect.Array;
0023: import java.lang.reflect.Method;
0024: import java.lang.reflect.InvocationTargetException;
0025:
0026: /*
0027: * This class implements the built-in "file" command in Tcl.
0028: */
0029:
0030: class FileCmd implements Command {
0031:
0032: /**
0033: * Reference to File.listRoots, null when JDK < 1.2
0034: */
0035: private static Method listRootsMethod;
0036:
0037: static {
0038: // File.listRoots()
0039: Class[] parameterTypes = new Class[0];
0040: try {
0041: listRootsMethod = File.class.getMethod("listRoots",
0042: parameterTypes);
0043: } catch (NoSuchMethodException e) {
0044: listRootsMethod = null;
0045: }
0046: }
0047:
0048: static Class procClass = null;
0049:
0050: static final private String validCmds[] = { "atime", "attributes",
0051: "channels", "copy", "delete", "dirname", "executable",
0052: "exists", "extension", "isdirectory", "isfile", "join",
0053: "link", "lstat", "mtime", "mkdir", "nativename",
0054: "normalize", "owned", "pathtype", "readable", "readlink",
0055: "rename", "rootname", "separator", "size", "split", "stat",
0056: "system", "tail", "type", "volumes", "writable" };
0057:
0058: private static final int OPT_ATIME = 0;
0059: private static final int OPT_ATTRIBUTES = 1;
0060: private static final int OPT_CHANNELS = 2;
0061: private static final int OPT_COPY = 3;
0062: private static final int OPT_DELETE = 4;
0063: private static final int OPT_DIRNAME = 5;
0064: private static final int OPT_EXECUTABLE = 6;
0065: private static final int OPT_EXISTS = 7;
0066: private static final int OPT_EXTENSION = 8;
0067: private static final int OPT_ISDIRECTORY = 9;
0068: private static final int OPT_ISFILE = 10;
0069: private static final int OPT_JOIN = 11;
0070: private static final int OPT_LINK = 12;
0071: private static final int OPT_LSTAT = 13;
0072: private static final int OPT_MTIME = 14;
0073: private static final int OPT_MKDIR = 15;
0074: private static final int OPT_NATIVENAME = 16;
0075: private static final int OPT_NORMALIZE = 17;
0076: private static final int OPT_OWNED = 18;
0077: private static final int OPT_PATHTYPE = 19;
0078: private static final int OPT_READABLE = 20;
0079: private static final int OPT_READLINK = 21;
0080: private static final int OPT_RENAME = 22;
0081: private static final int OPT_ROOTNAME = 23;
0082: private static final int OPT_SEPARATOR = 24;
0083: private static final int OPT_SIZE = 25;
0084: private static final int OPT_SPLIT = 26;
0085: private static final int OPT_STAT = 27;
0086: private static final int OPT_SYSTEM = 28;
0087: private static final int OPT_TAIL = 29;
0088: private static final int OPT_TYPE = 30;
0089: private static final int OPT_VOLUMES = 31;
0090: private static final int OPT_WRITABLE = 32;
0091:
0092: private static final String validOptions[] = { "-force", "--" };
0093:
0094: private static final int OPT_FORCE = 0;
0095: private static final int OPT_LAST = 1;
0096:
0097: /*
0098: *-----------------------------------------------------------------------------
0099: *
0100: * cmdProc --
0101: *
0102: * This procedure is invoked to process the "file" Tcl command.
0103: * See the user documentation for details on what it does.
0104: *
0105: * Results:
0106: * None.
0107: *
0108: * Side effects:
0109: * See the user documentation.
0110: *
0111: *-----------------------------------------------------------------------------
0112: */
0113:
0114: public void cmdProc(Interp interp, // Current interp to eval the file cmd.
0115: TclObject argv[]) // Args passed to the file command.
0116: throws TclException {
0117: if (argv.length < 2) {
0118: throw new TclNumArgsException(interp, 1, argv,
0119: "option ?arg ...?");
0120: }
0121:
0122: int opt = TclIndex.get(interp, argv[1], validCmds, "option", 0);
0123: String path;
0124: File fileObj = null;
0125:
0126: switch (opt) {
0127: case OPT_ATIME:
0128: if (argv.length != 3) {
0129: throw new TclNumArgsException(interp, 2, argv, "name");
0130: }
0131:
0132: // FIXME: Currently returns the same thing as MTIME.
0133: // Java does not support retrieval of access time.
0134:
0135: fileObj = FileUtil
0136: .getNewFileObj(interp, argv[2].toString());
0137: interp.setResult(getMtime(interp, argv[2].toString(),
0138: fileObj));
0139: return;
0140:
0141: case OPT_ATTRIBUTES:
0142: //FIXME: not implemented yet
0143:
0144: throw new TclException(interp,
0145: "sorry, \"file attributes\" is not implemented yet");
0146:
0147: //return;
0148: case OPT_CHANNELS:
0149: //FIXME: not implemented yet
0150:
0151: throw new TclException(interp,
0152: "sorry, \"file channels\" is not implemented yet");
0153:
0154: case OPT_COPY:
0155: fileCopyRename(interp, argv, true);
0156: return;
0157:
0158: case OPT_DELETE:
0159: fileDelete(interp, argv);
0160: return;
0161:
0162: case OPT_DIRNAME:
0163: if (argv.length != 3) {
0164: throw new TclNumArgsException(interp, 2, argv, "name");
0165: }
0166: path = argv[2].toString();
0167:
0168: // Return all but the last component. If there is only one
0169: // component, return it if the path was non-relative, otherwise
0170: // return the current directory.
0171:
0172: TclObject splitArrayObj[] = TclList.getElements(interp,
0173: FileUtil.splitAndTranslate(interp, path));
0174: if (false) {
0175: for (int ii = 0; ii < splitArrayObj.length; ii++) {
0176: System.out.println("file path index " + ii
0177: + " is \"" + splitArrayObj[ii] + "\"");
0178: }
0179: }
0180:
0181: if (splitArrayObj.length > 1) {
0182: interp.setResult(FileUtil.joinPath(interp,
0183: splitArrayObj, 0, splitArrayObj.length - 1));
0184: } else if ((splitArrayObj.length == 0)
0185: || (FileUtil.getPathType(path) == FileUtil.PATH_RELATIVE)) {
0186: if (JACL.PLATFORM == JACL.PLATFORM_MAC) {
0187: interp.setResult(":");
0188: } else {
0189: interp.setResult(".");
0190: }
0191: } else {
0192: interp.setResult(splitArrayObj[0].toString());
0193: }
0194: return;
0195:
0196: case OPT_EXECUTABLE:
0197: if (argv.length != 3) {
0198: throw new TclNumArgsException(interp, 2, argv, "name");
0199: }
0200: boolean isExe = false;
0201: fileObj = FileUtil
0202: .getNewFileObj(interp, argv[2].toString());
0203:
0204: // A file must exist to be executable. Directories are always
0205: // executable.
0206:
0207: if (fileObj.exists()) {
0208: isExe = fileObj.isDirectory();
0209: if (isExe) {
0210: interp.setResult(isExe);
0211: return;
0212: }
0213:
0214: if (Util.isWindows()) {
0215: // File that ends with .exe, .com, or .bat is executable.
0216:
0217: String fileName = argv[2].toString();
0218: isExe = (fileName.endsWith(".exe")
0219: || fileName.endsWith(".com") || fileName
0220: .endsWith(".bat"));
0221: } else if (Util.isMac()) {
0222: // FIXME: Not yet implemented on Mac. For now, return true.
0223: // Java does not support executability checking.
0224:
0225: isExe = true;
0226: } else {
0227: // FIXME: Not yet implemented on Unix. For now, return true.
0228: // Java does not support executability checking.
0229:
0230: isExe = true;
0231: }
0232: }
0233: interp.setResult(isExe);
0234: return;
0235:
0236: case OPT_EXISTS:
0237: if (argv.length != 3) {
0238: throw new TclNumArgsException(interp, 2, argv, "name");
0239: }
0240: fileObj = FileUtil
0241: .getNewFileObj(interp, argv[2].toString());
0242: interp.setResult(fileObj.exists());
0243: return;
0244:
0245: case OPT_EXTENSION:
0246: if (argv.length != 3) {
0247: throw new TclNumArgsException(interp, 2, argv, "name");
0248: }
0249: interp.setResult(getExtension(argv[2].toString()));
0250: return;
0251:
0252: case OPT_ISDIRECTORY:
0253: if (argv.length != 3) {
0254: throw new TclNumArgsException(interp, 2, argv, "name");
0255: }
0256: fileObj = FileUtil
0257: .getNewFileObj(interp, argv[2].toString());
0258: interp.setResult(fileObj.isDirectory());
0259: return;
0260:
0261: case OPT_ISFILE:
0262: if (argv.length != 3) {
0263: throw new TclNumArgsException(interp, 2, argv, "name");
0264: }
0265: fileObj = FileUtil
0266: .getNewFileObj(interp, argv[2].toString());
0267: interp.setResult(fileObj.isFile());
0268: return;
0269:
0270: case OPT_JOIN:
0271: if (argv.length < 3) {
0272: throw new TclNumArgsException(interp, 2, argv,
0273: "name ?name ...?");
0274: }
0275: interp.setResult(FileUtil.joinPath(interp, argv, 2,
0276: argv.length));
0277: return;
0278:
0279: case OPT_LINK:
0280: //FIXME: not implemented yet
0281:
0282: throw new TclException(interp,
0283: "sorry, \"file link\" is not implemented yet");
0284:
0285: case OPT_LSTAT:
0286: if (argv.length != 4) {
0287: throw new TclNumArgsException(interp, 2, argv,
0288: "name varName");
0289: }
0290:
0291: // FIXME: Not yet implemented.
0292: // Java does not support link access.
0293:
0294: throw new TclException(interp, "file command with opt "
0295: + argv[1].toString() + " is not yet implemented");
0296:
0297: case OPT_MTIME:
0298: if (argv.length != 3) {
0299: throw new TclNumArgsException(interp, 2, argv, "name");
0300: }
0301: fileObj = FileUtil
0302: .getNewFileObj(interp, argv[2].toString());
0303: interp.setResult(getMtime(interp, argv[2].toString(),
0304: fileObj));
0305: return;
0306:
0307: case OPT_MKDIR:
0308: fileMakeDirs(interp, argv);
0309: return;
0310:
0311: case OPT_NATIVENAME:
0312: if (argv.length != 3) {
0313: throw new TclNumArgsException(interp, 2, argv, "name");
0314: }
0315:
0316: interp.setResult(FileUtil.translateFileName(interp, argv[2]
0317: .toString()));
0318: return;
0319:
0320: case OPT_NORMALIZE:
0321: // FIXME: Not yet implemented.
0322:
0323: throw new TclException(interp,
0324: "sorry, \"file normalize\" is not implemented yet");
0325:
0326: case OPT_OWNED:
0327: if (argv.length != 3) {
0328: throw new TclNumArgsException(interp, 2, argv, "name");
0329: }
0330: fileObj = FileUtil
0331: .getNewFileObj(interp, argv[2].toString());
0332: interp.setResult(isOwner(interp, fileObj));
0333: return;
0334:
0335: case OPT_PATHTYPE:
0336: if (argv.length != 3) {
0337: throw new TclNumArgsException(interp, 2, argv, "name");
0338: }
0339: switch (FileUtil.getPathType(argv[2].toString())) {
0340: case FileUtil.PATH_RELATIVE:
0341: interp.setResult("relative");
0342: return;
0343: case FileUtil.PATH_VOLUME_RELATIVE:
0344: interp.setResult("volumerelative");
0345: return;
0346: case FileUtil.PATH_ABSOLUTE:
0347: interp.setResult("absolute");
0348: }
0349: return;
0350:
0351: case OPT_READABLE:
0352: if (argv.length != 3) {
0353: throw new TclNumArgsException(interp, 2, argv, "name");
0354: }
0355: fileObj = FileUtil
0356: .getNewFileObj(interp, argv[2].toString());
0357: interp.setResult(fileObj.canRead());
0358: return;
0359:
0360: case OPT_READLINK:
0361: if (argv.length != 3) {
0362: throw new TclNumArgsException(interp, 2, argv, "name");
0363: }
0364:
0365: // FIXME: Not yet implemented.
0366: // Java does not support link access.
0367:
0368: throw new TclException(interp, "file command with opt "
0369: + argv[1].toString() + " is not yet implemented");
0370:
0371: case OPT_RENAME:
0372: fileCopyRename(interp, argv, false);
0373: return;
0374:
0375: case OPT_ROOTNAME:
0376: if (argv.length != 3) {
0377: throw new TclNumArgsException(interp, 2, argv, "name");
0378: }
0379: String fileName = argv[2].toString();
0380: String extension = getExtension(fileName);
0381: int diffLength = fileName.length() - extension.length();
0382: interp.setResult(fileName.substring(0, diffLength));
0383: return;
0384:
0385: case OPT_SEPARATOR:
0386: // FIXME: Not yet implemented.
0387:
0388: throw new TclException(interp,
0389: "sorry, \"file separator\" is not implemented yet");
0390:
0391: case OPT_SIZE:
0392: if (argv.length != 3) {
0393: throw new TclNumArgsException(interp, 2, argv, "name");
0394: }
0395: fileObj = FileUtil
0396: .getNewFileObj(interp, argv[2].toString());
0397: if (!fileObj.exists()) {
0398: throw new TclPosixException(interp,
0399: TclPosixException.ENOENT, true,
0400: "could not read \"" + argv[2].toString() + "\"");
0401: }
0402: interp.setResult((int) fileObj.length());
0403: return;
0404:
0405: case OPT_SPLIT:
0406: if (argv.length != 3) {
0407: throw new TclNumArgsException(interp, 2, argv, "name");
0408: }
0409: interp.setResult(FileUtil.splitPath(interp, argv[2]
0410: .toString()));
0411: return;
0412:
0413: case OPT_STAT:
0414: if (argv.length != 4) {
0415: throw new TclNumArgsException(interp, 2, argv,
0416: "name varName");
0417: }
0418: getAndStoreStatData(interp, argv[2].toString(), argv[3]
0419: .toString());
0420: return;
0421:
0422: case OPT_SYSTEM:
0423: //FIXME: not implemented yet
0424:
0425: throw new TclException(interp,
0426: "sorry, \"file system\" is not implemented yet");
0427:
0428: case OPT_TAIL:
0429: if (argv.length != 3) {
0430: throw new TclNumArgsException(interp, 2, argv, "name");
0431: }
0432: interp.setResult(getTail(interp, argv[2].toString()));
0433: return;
0434:
0435: case OPT_TYPE:
0436: if (argv.length != 3) {
0437: throw new TclNumArgsException(interp, 2, argv, "name");
0438: }
0439: fileObj = FileUtil
0440: .getNewFileObj(interp, argv[2].toString());
0441: interp.setResult(getType(interp, argv[2].toString(),
0442: fileObj));
0443: return;
0444:
0445: case OPT_VOLUMES:
0446: if (argv.length != 2) {
0447: throw new TclNumArgsException(interp, 2, argv, null);
0448: }
0449:
0450: // use Java 1.2's File.listRoots() method if available
0451:
0452: if (listRootsMethod == null)
0453: throw new TclException(interp,
0454: "\"file volumes\" is not supported");
0455:
0456: try {
0457: File[] roots = (File[]) listRootsMethod.invoke(null,
0458: new Object[0]);
0459: if (roots != null) {
0460: TclObject list = TclList.newInstance();
0461: for (int i = 0; i < roots.length; i++) {
0462: String root = roots[i].getPath();
0463: TclList.append(interp, list, TclString
0464: .newInstance(root));
0465: }
0466: interp.setResult(list);
0467: }
0468: } catch (IllegalAccessException ex) {
0469: throw new TclRuntimeError(
0470: "IllegalAccessException in volumes cmd");
0471: } catch (IllegalArgumentException ex) {
0472: throw new TclRuntimeError(
0473: "IllegalArgumentException in volumes cmd");
0474: } catch (InvocationTargetException ex) {
0475: Throwable t = ex.getTargetException();
0476:
0477: if (t instanceof Error) {
0478: throw (Error) t;
0479: } else {
0480: throw new TclRuntimeError(
0481: "unexected exception in volumes cmd");
0482: }
0483: }
0484:
0485: return;
0486: case OPT_WRITABLE:
0487: if (argv.length != 3) {
0488: throw new TclNumArgsException(interp, 2, argv, "name");
0489: }
0490: fileObj = FileUtil
0491: .getNewFileObj(interp, argv[2].toString());
0492: interp.setResult(fileObj.canWrite());
0493: return;
0494: default:
0495: throw new TclRuntimeError("file command with opt "
0496: + argv[1].toString() + " is not implemented");
0497: }
0498: }
0499:
0500: /*-----------------------------------------------------------------------------
0501: *
0502: * isOwner --
0503: *
0504: * If "File" is owned by the uid associated with the program, return
0505: * true. Otherwise, return false.
0506: *
0507: * Results:
0508: * Boolean.
0509: *
0510: * Side effects:
0511: * None.
0512: *
0513: *-----------------------------------------------------------------------------
0514: */
0515:
0516: private static boolean isOwner(Interp interp, // Interpreter for error reports.
0517: File fileObj) // File obj whose owner to find.
0518: throws TclException // Thrown if unimplemented code segment
0519: // is reached
0520: {
0521: // If the file doesn't exist, return false;
0522:
0523: if (!fileObj.exists()) {
0524: return false;
0525: }
0526: boolean owner = true;
0527:
0528: // For Windows and Macintosh, there are no user ids
0529: // associated with a file, so we always return 1.
0530:
0531: if (Util.isUnix()) {
0532: // FIXME: Not yet implemented on Unix. Do no checking, for now.
0533: // Java does not support ownership checking.
0534: }
0535: return owner;
0536: }
0537:
0538: /*
0539: *-----------------------------------------------------------------------------
0540: *
0541: * getMtime --
0542: *
0543: * Finds the last modification of file in fileObj.
0544: *
0545: * Results:
0546: * Returns an int representation of modification time, in seconds.
0547: *
0548: * Side effects:
0549: * None.
0550: *
0551: *-----------------------------------------------------------------------------
0552: */
0553:
0554: private static int getMtime(Interp interp, // Interpreter for error reports.
0555: String fileName, // Name of file whose mtime to find.
0556: File fileObj) // File obj whose mtime to find.
0557: throws TclException // Exceptions thrown as a result of bad
0558: // user input.
0559: {
0560: if (!fileObj.exists()) {
0561: throw new TclPosixException(interp,
0562: TclPosixException.ENOENT, true, "could not read \""
0563: + fileName + "\"");
0564: }
0565: // Divide to convert msecs to seconds
0566: return (int) (fileObj.lastModified() / 1000);
0567: }
0568:
0569: /*
0570: *-----------------------------------------------------------------------------
0571: *
0572: * getType --
0573: *
0574: * Finds the type of file in fileObj.
0575: * WARNING: Only checks for file and directory status. If neither file
0576: * or direcotry, return link. Java only supports file and directory
0577: * checking.
0578: *
0579: * Results:
0580: * Returns a string "file", "directory", etc.
0581: *
0582: * Side effects:
0583: * None.
0584: *
0585: *-----------------------------------------------------------------------------
0586: */
0587:
0588: private static String getType(Interp interp, // Interpreter for error reports.
0589: String fileName, // Name of file whose owner to find.
0590: File fileObj) // File obj whose owner to find.
0591: throws TclException // Exceptions thrown as a result of bad
0592: // user input.
0593: {
0594: if (!fileObj.exists()) {
0595: throw new TclPosixException(interp,
0596: TclPosixException.ENOENT, true, "could not read \""
0597: + fileName + "\"");
0598: }
0599:
0600: if (fileObj.isFile()) {
0601: return "file";
0602: } else if (fileObj.isDirectory()) {
0603: return "directory";
0604: }
0605: return "link";
0606: }
0607:
0608: /*
0609: *-----------------------------------------------------------------------------
0610: *
0611: * getAndStoreStatData --
0612: *
0613: * This is a utility procedure that breaks out the fields of a
0614: * "stat" structure and stores them in textual form into the
0615: * elements of an associative array.
0616: * WARNING: skipping dev, gid, ino, mode, and nlink attributes.
0617: * WARNING: ctime and atime are the same as mtime.
0618: * Java does not support the above attributes.
0619: *
0620: * Results:
0621: * Returns a standard Tcl return value. If an error occurs then
0622: * a message is left in interp->result.
0623: *
0624: * Side effects:
0625: * Elements of the associative array given by "varName" are modified.
0626: *
0627: *-----------------------------------------------------------------------------
0628: */
0629:
0630: private static void getAndStoreStatData(Interp interp, // Interpreter for error reports.
0631: String fileName, // Name of file whose stats to find.
0632: String varName) // Name of associative array variable
0633: // in which to store stat results.
0634: throws TclException // Exceptions thrown as a result of bad
0635: // user input.
0636: {
0637: File fileObj = FileUtil.getNewFileObj(interp, fileName);
0638:
0639: if (!fileObj.exists()) {
0640: throw new TclPosixException(interp,
0641: TclPosixException.ENOENT, true, "could not read \""
0642: + fileName + "\"");
0643: }
0644:
0645: try {
0646: int mtime = getMtime(interp, fileName, fileObj);
0647: TclObject mtimeObj = TclInteger.newInstance(mtime);
0648: TclObject atimeObj = TclInteger.newInstance(mtime);
0649: TclObject ctimeObj = TclInteger.newInstance(mtime);
0650: interp.setVar(varName, "atime", atimeObj, 0);
0651: interp.setVar(varName, "ctime", ctimeObj, 0);
0652: interp.setVar(varName, "mtime", mtimeObj, 0);
0653: } catch (SecurityException e) {
0654: throw new TclException(interp, e.getMessage());
0655: } catch (TclException e) {
0656: throw new TclException(interp, "can't set \"" + varName
0657: + "(dev)\": variable isn't array");
0658: }
0659:
0660: try {
0661: TclObject sizeObj = TclInteger.newInstance((int) fileObj
0662: .length());
0663: interp.setVar(varName, "size", sizeObj, 0);
0664: } catch (Exception e) {
0665: // Do nothing.
0666: }
0667:
0668: try {
0669: TclObject typeObj = TclString.newInstance(getType(interp,
0670: fileName, fileObj));
0671: interp.setVar(varName, "type", typeObj, 0);
0672: } catch (Exception e) {
0673: }
0674:
0675: try {
0676: TclObject uidObj = TclBoolean.newInstance(isOwner(interp,
0677: fileObj));
0678: interp.setVar(varName, "uid", uidObj, 0);
0679: } catch (TclException e) {
0680: // Do nothing.
0681: }
0682: }
0683:
0684: /*
0685: *-----------------------------------------------------------------------------
0686: *
0687: * getExtension --
0688: *
0689: * Return the substring of "path" which represents the file's
0690: * extension. It is necessary to perform system specific
0691: * operations because different systems have different separators.
0692: *
0693: * Results:
0694: * Returns a file extension String.
0695: *
0696: * Side effects:
0697: * None.
0698: *
0699: *-----------------------------------------------------------------------------
0700: */
0701:
0702: private static String getExtension(String path) // Path for which we find extension.
0703: {
0704: if (path.length() < 1) {
0705: return "";
0706: }
0707:
0708: // Set lastSepIndex to the first index in the last component of the path.
0709:
0710: int lastSepIndex = -1;
0711: switch (JACL.PLATFORM) {
0712: case JACL.PLATFORM_WINDOWS:
0713: String tmpPath = path.replace('\\', '/').replace(':', '/');
0714: lastSepIndex = tmpPath.lastIndexOf('/');
0715: break;
0716: case JACL.PLATFORM_MAC:
0717: lastSepIndex = path.lastIndexOf(':');
0718: if (lastSepIndex == -1) {
0719: lastSepIndex = path.lastIndexOf('/');
0720: }
0721: break;
0722: default:
0723: lastSepIndex = path.lastIndexOf('/');
0724: }
0725: ++lastSepIndex;
0726:
0727: // Return "" if the last character is a separator.
0728:
0729: if (lastSepIndex >= path.length()) {
0730: return ("");
0731: }
0732:
0733: // Find the last dot in the last component of the path.
0734:
0735: String lastSep = path.substring(lastSepIndex);
0736: int dotIndex = lastSep.lastIndexOf('.');
0737:
0738: // Return "" if no dot was found in the file's name.
0739:
0740: if (dotIndex == -1) {
0741: return "";
0742: }
0743:
0744: // In earlier versions, we used to back up to the first period in a series
0745: // so that "foo..o" would be split into "foo" and "..o". This is a
0746: // confusing and usually incorrect behavior, so now we split at the last
0747: // period in the name.
0748:
0749: return (lastSep.substring(dotIndex));
0750: }
0751:
0752: /*
0753: *-----------------------------------------------------------------------------
0754: *
0755: * getTail --
0756: *
0757: * Return the substring of "path" which represents the file's
0758: * tail. It is necessary to perform system specific
0759: * operations because different systems have different separators.
0760: *
0761: * Results:
0762: * Returns a file tail String.
0763: *
0764: * Side effects:
0765: * None.
0766: *
0767: *-----------------------------------------------------------------------------
0768: */
0769:
0770: private static String getTail(Interp interp, // Current interpreter.
0771: String path) // Path for which to find the tail.
0772: throws TclException // Thrown if tilde subst, which may be
0773: // called by splitAndTranslate, fails.
0774: {
0775: // Split the path and return the string form of the last component,
0776: // unless there is only one component which is the root or an absolute
0777: // path.
0778:
0779: TclObject splitResult = FileUtil
0780: .splitAndTranslate(interp, path);
0781:
0782: int last = TclList.getLength(interp, splitResult) - 1;
0783:
0784: if (last >= 0) {
0785: if ((last > 0)
0786: || (FileUtil.getPathType(path) == FileUtil.PATH_RELATIVE)) {
0787: TclObject tailObj = TclList.index(interp, splitResult,
0788: last);
0789: return tailObj.toString();
0790: }
0791: }
0792: return "";
0793: }
0794:
0795: /*
0796: *-----------------------------------------------------------------------------
0797: *
0798: * fileMakeDirs --
0799: *
0800: * This procedure implements the "mkdir" subcommand of the "file"
0801: * command. Filename arguments need to be translated to native
0802: * format before being passed to platform-specific code that
0803: * implements mkdir functionality.
0804: * WARNING: ignoring links because Java does not support them.
0805: *
0806: * Results:
0807: * None.
0808: *
0809: * Side effects:
0810: * See user documentation.
0811: *
0812: *-----------------------------------------------------------------------------
0813: */
0814:
0815: private static void fileMakeDirs(Interp interp, // Current interpreter.
0816: TclObject[] argv) // Arguments to "file" command.
0817: throws TclException // Thrown as a result of bad user input.
0818: {
0819: boolean madeDir = false;
0820:
0821: for (int currentDir = 2; currentDir < argv.length; currentDir++) {
0822:
0823: String dirName = argv[currentDir].toString();
0824: if (dirName.length() == 0) {
0825: throw new TclPosixException(interp,
0826: TclPosixException.ENOENT, true,
0827: "can't create directory \"\"");
0828: }
0829: File dirObj = FileUtil.getNewFileObj(interp, dirName);
0830: if (dirObj.exists()) {
0831: // If the directory already exists, do nothing.
0832:
0833: if (dirObj.isDirectory()) {
0834: continue;
0835: }
0836: throw new TclPosixException(interp,
0837: TclPosixException.EEXIST, true,
0838: "can't create directory \"" + dirName + "\"");
0839: }
0840: try {
0841: madeDir = dirObj.mkdir();
0842: if (!madeDir) {
0843: madeDir = dirObj.mkdirs();
0844: }
0845: } catch (SecurityException e) {
0846: throw new TclException(interp, e.getMessage());
0847: }
0848: if (!madeDir) {
0849: throw new TclPosixException(interp,
0850: TclPosixException.EACCES, true,
0851: "can't create directory \"" + dirName
0852: + "\": best guess at reason");
0853: }
0854: }
0855: }
0856:
0857: /*
0858: *-----------------------------------------------------------------------------
0859: *
0860: * fileDelete --
0861: *
0862: * This procedure implements the "delete" subcommand of the "file"
0863: * command.
0864: *
0865: * Results:
0866: * None.
0867: *
0868: * Side effects:
0869: * See user documentation.
0870: *
0871: *-----------------------------------------------------------------------------
0872: */
0873:
0874: private static void fileDelete(Interp interp, // Current interpreter.
0875: TclObject[] argv) // Arguments to "file" command.
0876: throws TclException // Thrown as a result of bad user input.
0877: {
0878: boolean force = false;
0879: int firstSource = 2;
0880:
0881: for (boolean last = false; (firstSource < argv.length)
0882: && (!last); firstSource++) {
0883:
0884: if (!argv[firstSource].toString().startsWith("-")) {
0885: break;
0886: }
0887: int opt = TclIndex.get(interp, argv[firstSource],
0888: validOptions, "option", 1);
0889: switch (opt) {
0890: case OPT_FORCE:
0891: force = true;
0892: break;
0893: case OPT_LAST:
0894: last = true;
0895: break;
0896: default:
0897: throw new TclRuntimeError(
0898: "FileCmd.cmdProc: bad option " + opt
0899: + " index to validOptions");
0900: }
0901: }
0902:
0903: if (firstSource >= argv.length) {
0904: throw new TclNumArgsException(interp, 2, argv,
0905: "?options? file ?file ...?");
0906: }
0907:
0908: for (int i = firstSource; i < argv.length; i++) {
0909: deleteOneFile(interp, argv[i].toString(), force);
0910: }
0911: }
0912:
0913: /*
0914: *-----------------------------------------------------------------------------
0915: *
0916: * deleteOneFile --
0917: *
0918: * After performing error checking, deletes the specified file.
0919: * WARNING: ignoring links because Java does not support them.
0920: *
0921: * Results:
0922: * None.
0923: *
0924: * Side effects:
0925: * See user documentation.
0926: *
0927: *-----------------------------------------------------------------------------
0928: */
0929:
0930: private static void deleteOneFile(Interp interp, // Current interpreter.
0931: String fileName, // Name of file to delete.
0932: boolean force) // Flag tells whether to delete
0933: // recursively.
0934: throws TclException // Thrown as a result of bad user input.
0935: {
0936: boolean isDeleted = true;
0937: File fileObj = FileUtil.getNewFileObj(interp, fileName);
0938:
0939: // Trying to delete a file that does not exist is not
0940: // considered an error, just a no-op
0941:
0942: if ((!fileObj.exists()) || (fileName.length() == 0)) {
0943: return;
0944: }
0945:
0946: // If the file is a non-empty directory, recursively delete its children if
0947: // the -force option was chosen. Otherwise, throw an error.
0948:
0949: if (fileObj.isDirectory() && (fileObj.list().length > 0)) {
0950: if (force) {
0951: String fileList[] = fileObj.list();
0952: for (int i = 0; i < fileList.length; i++) {
0953:
0954: TclObject joinArrayObj[] = new TclObject[2];
0955: joinArrayObj[0] = TclString.newInstance(fileName);
0956: joinArrayObj[1] = TclString
0957: .newInstance(fileList[i]);
0958:
0959: String child = FileUtil.joinPath(interp,
0960: joinArrayObj, 0, 2);
0961: deleteOneFile(interp, child, force);
0962: }
0963: } else {
0964: throw new TclPosixException(interp,
0965: TclPosixException.ENOTEMPTY,
0966: "error deleting \"" + fileName
0967: + "\": directory not empty");
0968: }
0969: }
0970: try {
0971: isDeleted = fileObj.delete();
0972: } catch (SecurityException e) {
0973: throw new TclException(interp, e.getMessage());
0974: }
0975: if (!isDeleted) {
0976: throw new TclPosixException(interp,
0977: TclPosixException.EACCES, true, "error deleting \""
0978: + fileName + "\": best guess at reason");
0979: }
0980: }
0981:
0982: /*
0983: *-----------------------------------------------------------------------------
0984: *
0985: * fileCopyRename --
0986: *
0987: * This procedure implements the "copy" and "rename" subcommands of the
0988: * "file" command. Filename arguments need to be translated to native
0989: * format before being passed to platform-specific code that implements
0990: * copy functionality.
0991: *
0992: * Results:
0993: * None.
0994: *
0995: * Side effects:
0996: * Target is overwritten if the force flag is set. Attempting to
0997: * copy/rename a file onto a directory or a directory onto a file
0998: * will always result in an error.
0999: * See user documentation.
1000: *
1001: *-----------------------------------------------------------------------------
1002: */
1003:
1004: private static void fileCopyRename(Interp interp, // Current interpreter.
1005: TclObject[] argv, // Arguments to "file" command.
1006: boolean copyFlag) // Flag tells whether to copy or rename.
1007: throws TclException // Thrown as a result of bad user input.
1008: {
1009: int firstSource = 2;
1010: boolean force = false;
1011:
1012: for (boolean last = false; (firstSource < argv.length)
1013: && (!last); firstSource++) {
1014:
1015: if (!argv[firstSource].toString().startsWith("-")) {
1016: break;
1017: }
1018: int opt = TclIndex.get(interp, argv[firstSource],
1019: validOptions, "option", 1);
1020: switch (opt) {
1021: case OPT_FORCE:
1022: force = true;
1023: break;
1024: case OPT_LAST:
1025: last = true;
1026: break;
1027: default:
1028: throw new TclRuntimeError(
1029: "FileCmd.cmdProc: bad option " + opt
1030: + " index to validOptions");
1031: }
1032: }
1033:
1034: if (firstSource >= (argv.length - 1)) {
1035: throw new TclNumArgsException(interp, firstSource, argv,
1036: "?options? source ?source ...? target");
1037: }
1038:
1039: // WARNING: ignoring links because Java does not support them.
1040:
1041: int target = argv.length - 1;
1042: String targetName = argv[target].toString();
1043:
1044: File targetObj = FileUtil.getNewFileObj(interp, targetName);
1045: if (targetObj.isDirectory()) {
1046: // If the target is a directory, move each source file into target
1047: // directory. Extract the tailname from each source, and append it to
1048: // the end of the target path.
1049:
1050: for (int source = firstSource; source < target; source++) {
1051:
1052: String sourceName = argv[source].toString();
1053:
1054: if (targetName.length() == 0) {
1055: copyRenameOneFile(interp, sourceName, targetName,
1056: copyFlag, force);
1057: } else {
1058: String tailName = getTail(interp, sourceName);
1059:
1060: TclObject joinArrayObj[] = new TclObject[2];
1061: joinArrayObj[0] = TclString.newInstance(targetName);
1062: joinArrayObj[1] = TclString.newInstance(tailName);
1063:
1064: String fullTargetName = FileUtil.joinPath(interp,
1065: joinArrayObj, 0, 2);
1066:
1067: copyRenameOneFile(interp, sourceName,
1068: fullTargetName, copyFlag, force);
1069: }
1070: }
1071: } else {
1072: // If there is more than 1 source file and the target is not a
1073: // directory, then throw an exception.
1074:
1075: if (firstSource + 1 != target) {
1076: String action;
1077: if (copyFlag) {
1078: action = "copying";
1079: } else {
1080: action = "renaming";
1081: }
1082: throw new TclPosixException(interp,
1083: TclPosixException.ENOTDIR, "error " + action
1084: + ": target \""
1085: + argv[target].toString()
1086: + "\" is not a directory");
1087: }
1088: String sourceName = argv[firstSource].toString();
1089: copyRenameOneFile(interp, sourceName, targetName, copyFlag,
1090: force);
1091: }
1092: }
1093:
1094: /*
1095: *-----------------------------------------------------------------------------
1096: *
1097: * copyRenameOneFile --
1098: *
1099: * After performing error checking, performs the copy and rename commands.
1100: * WARNING: ignoring links because Java does not support them.
1101: *
1102: * Results:
1103: * None.
1104: *
1105: * Side effects:
1106: * See user documentation.
1107: *
1108: *-----------------------------------------------------------------------------
1109: */
1110:
1111: private static void copyRenameOneFile(Interp interp, // Current interpreter.
1112: String sourceName, // Name of source file.
1113: String targetName, // Name of target file.
1114: boolean copyFlag, // Flag tells whether to copy or rename.
1115: boolean force) // Flag tells whether to overwrite.
1116: throws TclException // Thrown as a result of bad user input.
1117: {
1118: // Copying or renaming a file onto itself is a no-op if force is chosen,
1119: // otherwise, it will be caught later as an EEXISTS error.
1120:
1121: if (force && sourceName.equals(targetName)) {
1122: return;
1123: }
1124:
1125: // Check that the source exists and that if -force was not specified, the
1126: // target doesn't exist.
1127: //
1128: // Prevent copying/renaming a file onto a directory and
1129: // vice-versa. This is a policy decision based on the fact that
1130: // existing implementations of copy and rename on all platforms
1131: // also prevent this.
1132:
1133: String action;
1134: if (copyFlag) {
1135: action = "copying";
1136: } else {
1137: action = "renaming";
1138: }
1139:
1140: File sourceFileObj = FileUtil.getNewFileObj(interp, sourceName);
1141: if ((!sourceFileObj.exists()) || (sourceName.length() == 0)) {
1142: throw new TclPosixException(interp,
1143: TclPosixException.ENOENT, true, "error " + action
1144: + " \"" + sourceName + "\"");
1145: }
1146:
1147: if (targetName.length() == 0) {
1148: throw new TclPosixException(interp,
1149: TclPosixException.ENOENT, true, "error " + action
1150: + " \"" + sourceName + "\" to \""
1151: + targetName + "\"");
1152: }
1153: File targetFileObj = FileUtil.getNewFileObj(interp, targetName);
1154: if (targetFileObj.exists() && !force) {
1155: throw new TclPosixException(interp,
1156: TclPosixException.EEXIST, true, "error " + action
1157: + " \"" + sourceName + "\" to \""
1158: + targetName + "\"");
1159: }
1160:
1161: if (sourceFileObj.isDirectory() && !targetFileObj.isDirectory()) {
1162: throw new TclPosixException(interp,
1163: TclPosixException.EISDIR, "can't overwrite file \""
1164: + targetName + "\" with directory \""
1165: + sourceName + "\"");
1166: }
1167: if (targetFileObj.isDirectory() && !sourceFileObj.isDirectory()) {
1168: throw new TclPosixException(interp,
1169: TclPosixException.EISDIR,
1170: "can't overwrite directory \"" + targetName
1171: + "\" with file \"" + sourceName + "\"");
1172: }
1173:
1174: if (!copyFlag) {
1175: // Perform the rename procedure.
1176:
1177: if (!sourceFileObj.renameTo(targetFileObj)) {
1178:
1179: if (targetFileObj.isDirectory()) {
1180: throw new TclPosixException(interp,
1181: TclPosixException.EEXIST, true,
1182: "error renaming \"" + sourceName
1183: + "\" to \"" + targetName + "\"");
1184: }
1185:
1186: throw new TclPosixException(interp,
1187: TclPosixException.EACCES, true,
1188: "error renaming \"" + sourceName + "\" to \""
1189: + targetName
1190: + "\": best guess at reason");
1191: }
1192: } else {
1193: // Perform the copy procedure.
1194:
1195: try {
1196: BufferedInputStream bin = new BufferedInputStream(
1197: new FileInputStream(sourceFileObj));
1198: BufferedOutputStream bout = new BufferedOutputStream(
1199: new FileOutputStream(targetFileObj));
1200:
1201: final int bsize = 1024;
1202: byte[] buff = new byte[bsize];
1203: int numChars = bin.read(buff, 0, bsize);
1204: while (numChars != -1) {
1205: bout.write(buff, 0, numChars);
1206: numChars = bin.read(buff, 0, bsize);
1207: }
1208: bin.close();
1209: bout.close();
1210: } catch (IOException e) {
1211: throw new TclException(interp, "error copying: "
1212: + e.getMessage());
1213: }
1214: }
1215: }
1216:
1217: } // end FileCmd class
|