/* * FileUtil.java -- * * This file contains utility methods for file-related operations. * * 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: FileUtil.java,v 1.3 1998/11/05 00:24:24 hylands Exp $ * */ package tcl.lang; import java.io.*; import java.util.*; /* * This class implements utility methods for file-related operations. */ class FileUtil { static final int PATH_RELATIVE = 0; static final int PATH_VOLUME_RELATIVE = 1; static final int PATH_ABSOLUTE = 2; /* *----------------------------------------------------------------------------- * * getWinHomePath -- * * In the Windows file system, one type of absolute path follows this * regular expression: ^(//+[a-zA-Z]+/+[a-zA-Z]+) * * If "path" doesn't fit the pattern, then return 0. * If the stopEarly bool is true, then return the index of the first * non-slash character in path, as soon as we know that path fits the * pattern. Otherwise, return the index of the slash (or end of string) * following the entire absolute path. * * Results: * Returns an integer index in path. * * Side effects: * If "path" fits the pattern, and "stopEarly" is not chosen, the absolute * path is coppied (without extra slashes) to "absBuf". Otherwise, absBuf * is set to "". * *----------------------------------------------------------------------------- */ private static int getWinHomePath( String path, // Path to compute home path of. boolean stopEarly, // Flag to skip side effect. StringBuffer absBuf) // Buffer to store side effect. { int pIndex, oldIndex, firstNonSlash; /* * The first 2 or more chars must be slashes. */ for(pIndex = 0; pIndex < path.length(); pIndex++) { if (path.charAt(pIndex) != '/') { break; } } if (pIndex < 2) { absBuf.setLength(0); return 0; } firstNonSlash = pIndex; /* * The next 1 or more chars may not be slashes. */ for (; pIndex < path.length(); pIndex++) { if (path.charAt(pIndex) == '/') { break; } } if (pIndex == firstNonSlash) { absBuf.setLength(0); return 0; } absBuf.ensureCapacity(absBuf.length() + path.length()); absBuf.append("//"); absBuf.append(path.substring(firstNonSlash, pIndex)); /* * The next 1 or more chars must be slashes. */ oldIndex = pIndex; for (; pIndex < path.length(); pIndex++) { if (path.charAt(pIndex) != '/') { if (pIndex == oldIndex) { absBuf.setLength(0); return 0; } /* * We know that the path fits the pattern. */ if (stopEarly) { absBuf.setLength(0); return firstNonSlash; } firstNonSlash = pIndex; /* * Traverse the path until a new slash (or end of string) is found. * Return the index of the new slash. */ pIndex++; for (; pIndex < path.length(); pIndex++) { if (path.charAt(pIndex) == '/') { break; } } absBuf.append('/'); absBuf.append(path.substring(firstNonSlash, pIndex)); return pIndex; } } absBuf.setLength(0); return 0; } /* *----------------------------------------------------------------------------- * * beginsWithLetterColon -- * * Determine whether a given windows path begins with [a-zA-Z]: * Return O if path doesn't begins with [a-zA-Z]: * Return 3 if path begins with [a-zA-Z]:/ * Otherwise, return 2. * * Results: * Returns an integer. * * Side effects: * None. * *----------------------------------------------------------------------------- */ private static int beginsWithLetterColon( String path) // Path to check start pattern. { if ((path.length() > 1) && (Character.isLetter(path.charAt(0))) && (path.charAt(1) == ':')) { int pIndex; for (pIndex = 2; pIndex < path.length(); pIndex++) { if (path.charAt(pIndex) != '/') { break; } } return pIndex; } return 0; } /* *----------------------------------------------------------------------------- * * getWinAbsPath -- * * If "path" begins with [A-Z]: or '/', return the index of the character * (or end of string) following the absolute (or volume realtive) path. * Otherwise, return 0. * * Results: * Returns an integer index in path. * * Side effects: * If "path" begins with [A-Z]: or '/', copy the absolute (or volume * realtive) path up to the index returned into absBuf, removing extra * slashes. * *----------------------------------------------------------------------------- */ private static int getWinAbsPath( String path, // Path for which we find abs path. StringBuffer absBuf) // Buffer to store side effect. { absBuf.setLength(0); if (path.length() < 1) { return 0; } absBuf.ensureCapacity(absBuf.length() + path.length()); int colonIndex = beginsWithLetterColon(path); if (colonIndex > 0) { if (colonIndex > 2) { absBuf.append(path.substring(0, 3)); } else { absBuf.append(path.substring(0, 2)); } return colonIndex; } else { int absIndex = getWinHomePath(path, false, absBuf); if (absIndex > 0) { return absIndex; } else if (path.charAt(0) == '/') { int pIndex; for (pIndex = 1; pIndex < path.length(); pIndex++) { if (path.charAt(pIndex) != '/') { break; } } absBuf.append("/"); return pIndex; } } return 0; } /* *----------------------------------------------------------------------------- * * getDegenerateUnixPath -- * * Returns the index of the 1st char (or end of string) which nolonger * follows the degenerate unix-style name pattern: * * ^(/+([.][.]?/+)*([.][.]?)?) * * Results: * Returns an int index to "path". * * Side effects: * None. * *----------------------------------------------------------------------------- */ private static int getDegenerateUnixPath( String path) // Path to check. { int pIndex = 0; while ((pIndex < path.length()) && (path.charAt(pIndex) == '/')) { ++pIndex; } /* * "path" doesn't begin with a '/'. */ if (pIndex == 0) { return 0; } while (pIndex < path.length()) { String tmpPath = path.substring(pIndex); if (tmpPath.startsWith("./")) { pIndex += 2; } else if (tmpPath.startsWith("../")) { pIndex += 3; } else { break; } while ((pIndex < path.length()) && (path.charAt(pIndex) == '/')) { ++pIndex; } } int lastSlashIndex = pIndex; if ((pIndex < path.length()) && (path.charAt(pIndex) == '.')) { ++pIndex; } if ((pIndex < path.length()) && (path.charAt(pIndex) == '.')) { ++pIndex; } /* * pIndex may be 1 past the end of "path". */ return pIndex; } /* *----------------------------------------------------------------------------- * * getPathType -- * * Determine whether "path" is absolute, volumerelative, or * relative. It is necessary to perform system specific * operations. * * Results: * Returns an integer value representing the path type. * * Side effects: * None. * *----------------------------------------------------------------------------- */ static int getPathType( String path) // Path for which we find pathtype. { char c; if (path.length() < 1) { return PATH_RELATIVE; } switch(JACL.PLATFORM) { case JACL.PLATFORM_WINDOWS: path = path.replace('\\', '/'); /* * Windows absolute pathes start with '~' or [a-zA-Z]:/ or home * path. */ c = path.charAt(0); if (c == '~') { return PATH_ABSOLUTE; } if (c == '/') { StringBuffer absBuf = new StringBuffer(0); if (getWinHomePath(path, true, absBuf) > 0) { return PATH_ABSOLUTE; } return PATH_VOLUME_RELATIVE; } int colonIndex = beginsWithLetterColon(path); if (colonIndex > 0) { if (colonIndex > 2) { return PATH_ABSOLUTE; } return PATH_VOLUME_RELATIVE; } return PATH_RELATIVE; case JACL.PLATFORM_MAC: if (path.charAt(0) == '~') { return PATH_ABSOLUTE; } switch (path.indexOf(':')) { case -1: /* * Unix-style name contains no colons. Return absolute iff "path" * begins with '/' and is not degenerate. Otherwise, return * relative. */ if ((path.charAt(0) == '/') && (getDegenerateUnixPath(path) < path.length())) { return PATH_ABSOLUTE; } break; case 0: /* * Mac-style name contains a colon in the first position. */ return PATH_RELATIVE; default: /* * Mac-style name contains a colon, but not in the first position. */ return PATH_ABSOLUTE; } return PATH_RELATIVE; default: /* * Unix absolute pathes start with either '/' or '~'. */ c = path.charAt(0); if ((c == '/') || (c == '~')) { return PATH_ABSOLUTE; } } return PATH_RELATIVE; } /* *----------------------------------------------------------------------------- * * getNewFileName -- * * Creatae a new File object with the name "fileName". * * Results: * Returns the newly created File object. * * Side effects: * None. * *----------------------------------------------------------------------------- */ static File getNewFileObj( Interp interp, // Current interpreter. String fileName) // File to create object for. throws TclException { File f; fileName = translateFileName(interp, fileName); if (getPathType(fileName) == PATH_ABSOLUTE) { f = new File(fileName); } else { f = new File (interp.getWorkingDir(), fileName); } return f; } /* *----------------------------------------------------------------------------- * * appendComponent -- * * Append "component" to "buf" while eliminating extra slashes. * * Results: * None. * * Side effects: * A mangled version of "component" is appended to "buf". * *----------------------------------------------------------------------------- */ private static void appendComponent( String component, // Component to append. int compIndex, // Current index in the component. int compSize, // Index following last in component. StringBuffer buf) // Buffer to append the component. { for (; compIndex < component.length(); compIndex++) { char c = component.charAt(compIndex); if (c == '/') { /* * Eliminate duplicate slashes. */ while ((compIndex < compSize) && (component.charAt(compIndex + 1) == '/')) { compIndex++; } /* * Only add a slash if following non-slash elements exist. */ if (compIndex < compSize) { buf.ensureCapacity(buf.length() + 1); buf.append('/'); } } else { buf.ensureCapacity(buf.length() + 1); buf.append(c); } } } /* *----------------------------------------------------------------------------- * * joinPath -- * * Combine a list of pathes into one path. It is necessary to perform * system specific operations. * * Results: * Returns a path String. * * Side effects: * None. * *----------------------------------------------------------------------------- */ static String joinPath( Interp interp, // Current interpreter for path join. TclObject argv[], // List of pathes to be joined. int startIndex, // 1st item in argv to join. int endIndex) // 1st item to ignore. throws TclException // Thrown if TclList ops fail. { StringBuffer result = new StringBuffer(10); switch (JACL.PLATFORM) { case JACL.PLATFORM_WINDOWS: /* * Iterate over all of the components. If a component is * absolute, then reset the result and start building the * path from the current component on. */ for (int i = startIndex; i < endIndex; i++) { String p = argv[i].toString().replace('\\', '/'); int pIndex = 0; int pLastIndex = p.length() - 1; if (p.length() == 0) { continue; } StringBuffer absBuf = new StringBuffer(0); pIndex = getWinAbsPath(p, absBuf); if (pIndex > 0) { /* * If the path is absolute or volume relative (except those * beginning with '~'), reset the result buffer to the absolute * substring. */ result = absBuf; } else if (p.charAt(0) == '~') { /* * If the path begins with '~', reset the result buffer to "". */ result.setLength(0); } else { /* * This is a relative path. Remove the ./ from tilde prefixed * elements unless it is the first component. */ if ((result.length() != 0) && (p.regionMatches(pIndex, "./~", 0, 3))) { pIndex = 2; } /* * Check to see if we need to append a separator before adding * this relative component. */ if (result.length() != 0) { char c = result.charAt(result.length() - 1); if ((c != '/') && (c != ':')) { result.ensureCapacity(result.length() + 1); result.append('/'); } } } /* * Append the element. */ appendComponent(p, pIndex, pLastIndex, result); pIndex = p.length(); } return result.toString(); case JACL.PLATFORM_MAC: /* * Iterate over all of the components. If a component is * absolute, then reset the result and start building the * path from the current component on. */ boolean needsSep = true; for (int i = startIndex; i < endIndex; i++) { TclObject splitArrayObj[] = TclList.getElements(interp, splitPath(interp, argv[i].toString())); if (splitArrayObj.length == 0) { continue; } /* * If 1st path element is absolute, reset the result to "" and * append the 1st path element to it. */ int start = 0; String p = splitArrayObj[0].toString(); if ((p.charAt(0) != ':') && (p.indexOf(':') != -1)) { result.setLength(0); result.append(p); start++; needsSep = false; } /* * Now append the rest of the path elements, skipping * : unless it is the first element of the path, and * watching out for :: et al. so we don't end up with * too many colons in the result. */ for (int j = start; j < splitArrayObj.length; j++) { p = splitArrayObj[j].toString(); if (p.equals(":")) { if (result.length() != 0) { continue; } else { needsSep = false; } } else { char c = 'o'; if (p.length() > 1) { c = p.charAt(1); } if (p.charAt(0) == ':') { if (!needsSep) { p = p.substring(1); } } else { if (needsSep) { result.append(':'); } } if (c == ':') { needsSep = false; } else { needsSep = true; } } result.append(p); } } return result.toString(); default: /* * Unix platform. */ for (int i = startIndex; i < endIndex; i++) { String p = argv[i].toString(); int pIndex = 0; int pLastIndex = p.length() - 1; if (p.length() == 0) { continue; } if (p.charAt(pIndex) == '/') { /* * If the path is absolute (except those beginning with '~'), * reset the result buffer to the absolute substring. */ while ((pIndex <= pLastIndex) && (p.charAt(pIndex) == '/')) { pIndex++; } result.setLength(0); result.append('/'); } else if (p.charAt(pIndex) == '~') { /* * If the path begins with '~', reset the result buffer to "". */ result.setLength(0); } else { /* * This is a relative path. Remove the ./ from tilde prefixed * elements unless it is the first component. */ if ((result.length() != 0) && (p.regionMatches(pIndex, "./~", 0, 3))) { pIndex += 2; } /* * Append a separator if needed. */ if ((result.length() != 0) && (result.charAt(result.length() - 1) != '/')) { result.ensureCapacity(result.length() + 1); result.append('/'); } } /* * Append the element. */ appendComponent(p, pIndex, pLastIndex, result); pIndex = p.length(); } } return result.toString(); } /* *----------------------------------------------------------------------------- * * splitPath -- * * Turn one path into a list of components. It is necessary to perform * system specific operations. * * Results: * Returns a Tcl List Object. * * Side effects: * None. * *----------------------------------------------------------------------------- */ static TclObject splitPath( Interp interp, // Current interpreter for path split. String path) // Path to be split. throws TclException // Thrown if TclList ops fail. { TclObject resultListObj = TclList.newInstance(); TclObject componentObj; String component = ""; String tmpPath; boolean foundComponent = false; boolean convertDotToColon = false; boolean isColonSeparator = false; boolean appendColon = false; boolean prependColon = false; String thisDir = "./"; /* * If the path is the empty string, returnan empty result list. */ if (path.length() == 0) { return resultListObj; } /* * Handling the 1st component is file system dependent. */ switch (JACL.PLATFORM) { case JACL.PLATFORM_WINDOWS: tmpPath = path.replace('\\', '/'); StringBuffer absBuf = new StringBuffer(0); int absIndex = getWinAbsPath(tmpPath, absBuf); if (absIndex > 0) { componentObj = TclString.newInstance(absBuf.toString()); TclList.append(interp, resultListObj, componentObj); tmpPath = tmpPath.substring(absIndex); foundComponent = true; } break; case JACL.PLATFORM_MAC: tmpPath = ""; thisDir = ":"; switch (path.indexOf(':')) { case -1: /* * Unix-style name contains no colons. */ if (path.charAt(0) != '/') { tmpPath = path; convertDotToColon = true; if (path.charAt(0) == '~') { /* * If '~' is the first char, then append a colon to end * of the 1st component. */ appendColon = true; } break; } int degenIndex = getDegenerateUnixPath(path); if (degenIndex < path.length()) { /* * First component of absolute unix path is followed by a ':', * instead of being preceded by a degenerate unix-style * pattern. */ tmpPath = path.substring(degenIndex); convertDotToColon = true; appendColon = true; break; } /* * Degenerate unix path can't be split. Return a list with one * element: ":" prepended to "path". */ componentObj = TclString.newInstance(":" + path); TclList.append(interp, resultListObj, componentObj); return resultListObj; case 0: /* * Relative mac-style name contains a colon in the first position. */ if (path.length() == 1) { /* * If path == ":", then return a list with ":" as its only * element. */ componentObj = TclString.newInstance(":"); TclList.append(interp, resultListObj, componentObj); return resultListObj; } /* * For each component, if slashes exist in the remaining filename, * prepend a colon to the component. Since this path is relative, * pretend that we have already processed 1 components so a * tilde-prefixed 1st component will have ":" prepended to it. */ tmpPath = path.substring(1); foundComponent = true; prependColon = true; isColonSeparator = true; break; default: /* * Absolute mac-style name contains a colon, but not in the first * position. Append a colon to the first component, and, for each * following component, if slashes exist in the remaining filename, * prepend a colon to the component. */ tmpPath = path; appendColon = true; prependColon = true; isColonSeparator = true; break; } break; default: /* * Unix file name: if the first char is a "/", append "/" to the result * list. */ if (path.charAt(0) == '/') { componentObj = TclString.newInstance("/"); TclList.append(interp, resultListObj, componentObj); tmpPath = path.substring(1); foundComponent = true; } else { tmpPath = path; } } /* * Iterate over all of the components of the path. */ int sIndex = 0; while (sIndex != -1) { if (isColonSeparator) { sIndex = tmpPath.indexOf(":"); /* * process adjacent ':' */ if (sIndex == 0) { componentObj = TclString.newInstance("::"); TclList.append(interp, resultListObj, componentObj); foundComponent = true; tmpPath = tmpPath.substring(sIndex + 1); continue; } } else { sIndex = tmpPath.indexOf("/"); /* * Ignore a redundant '/' */ if (sIndex == 0) { tmpPath = tmpPath.substring(sIndex + 1); continue; } } if (sIndex == -1) { /* * Processing the last component. If it is empty, exit loop. */ if (tmpPath.length() == 0) { break; } component = tmpPath; } else { component = tmpPath.substring(0, sIndex); } if (convertDotToColon && (component.equals(".") || component.equals(".."))) { /* * If platform = MAC, convert .. to :: or . to :: */ component = component.replace('.',':'); } if (foundComponent) { if (component.charAt(0) == '~') { /* * If a '~' preceeds a component (other than the 1st one), then * prepend "./" or ":" to the component. */ component = thisDir + component; } else if (prependColon) { /* * If the prependColon flag is set, either unset it or prepend * ":" to the component, depending on whether any '/'s remain * in tmpPath. */ if (tmpPath.indexOf('/') == -1) { prependColon = false; } else { component = ":" + component; } } } else if (appendColon) { /* * If platform = MAC, append a ':' to the first component. */ component = component + ":"; } componentObj = TclString.newInstance(component); TclList.append(interp, resultListObj, componentObj); foundComponent = true; tmpPath = tmpPath.substring(sIndex + 1); } return resultListObj; } /* *----------------------------------------------------------------------------- * * doTildeSubst -- * * Given a string following a tilde, this routine returns the * corresponding home directory. * * Results: * The result is a string containing the home directory in native format. * Throws an error if it can't find the env(HOME) variable or the * specified user doesn't exist.. * * Side effects: * None. * *----------------------------------------------------------------------------- */ static String doTildeSubst( Interp interp, // Current interpreter. String user) // User whose home we must find. throws TclException // Thrown if env(HOME) is not set or if // another user is requested. { String dir; if (user.length() == 0) { try { dir = interp.getVar("env", "HOME", 0).toString(); } catch (Exception e) { throw new TclException(interp, "couldn't find HOME environment variable to expand path"); } return dir; } /* * WARNING: Java does not support other users. "dir" is always null, * but it should be the home directory (corresponding to the user name), as * specified in the password file. */ dir = null; if (dir == null) { throw new TclException(interp, "user \"" + user + "\" doesn't exist"); } return dir; } /* *----------------------------------------------------------------------------- * * translateFileName -- * * If the path starts with a tilde, do tilde substitution on the first * component and join it with the remainder of the path. * Otherwise, do nothing. * * Results: * Returns the tilde-substituted path. * * Side effects: * None. * *----------------------------------------------------------------------------- */ static String translateFileName( Interp interp, // Current interpreter for path split. String path) // Path to be split. throws TclException // Thrown if tilde subst fails. { String fileName = ""; if ((path.length() == 0) || (path.charAt(0) != '~')) { // fileName = path; TclObject joinArrayObj[] = new TclObject[1]; joinArrayObj[0] = TclString.newInstance(path); fileName = joinPath(interp, joinArrayObj, 0, 1); } else { TclObject splitArrayObj[] = TclList.getElements(interp, splitPath(interp, path)); String user = splitArrayObj[0].toString().substring(1); /* * Strip the trailing ':' off of a Mac path * before passing the user name to DoTildeSubst. */ if ((JACL.PLATFORM == JACL.PLATFORM_MAC) && (user.endsWith(":"))) { user = user.substring(0, user.length() - 1); } user = doTildeSubst(interp, user); // if (splitArrayObj.length < 2) { // fileName = user; // } else { splitArrayObj[0] = TclString.newInstance(user); fileName = joinPath(interp, splitArrayObj, 0, splitArrayObj.length); // } } /* * Convert forward slashes to backslashes in Windows paths because * some system interfaces don't accept forward slashes. */ if (JACL.PLATFORM == JACL.PLATFORM_WINDOWS) { fileName = fileName.replace('/','\\'); } return fileName; } /* *----------------------------------------------------------------------------- * * splitAndTranslate -- * * Split the path. If there is only one component, and it starts with a * tilde, do tilde substitution and split its result. * * Results: * Returns a Tcl List Object. * * Side effects: * None. * *----------------------------------------------------------------------------- */ static TclObject splitAndTranslate( Interp interp, // Current interpreter for path split. String path) // Path to be split. throws TclException // Thrown if tilde subst, which may be // called by translateFileName, fails. { TclObject splitResult = splitPath(interp, path); int len = TclList.getLength(interp, splitResult); if (len == 1) { String fileName = TclList.index(interp, splitResult, 0).toString(); if (fileName.charAt(0) == '~') { String user = translateFileName(interp, fileName); splitResult = splitPath(interp, user); } } return splitResult; } } // end FileUtil class