#undef DEBUG /* * 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. * * Included in SQLite3 port to C# for use in testharness only; 2008 Noah B Hart * * RCS @(#) $Id: FileUtil.java,v 1.6 2003/02/02 00:59:16 mdejong Exp $ * */ using System; using System.Text; using System.IO; namespace tcl.lang { /* * This class implements utility methods for file-related operations. */ public class FileUtil { internal const int PATH_RELATIVE = 0; internal const int PATH_VOLUME_RELATIVE = 1; internal const 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, bool stopEarly, StringBuilder 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[pIndex] != '/' ) { break; } } if ( pIndex < 2 ) { absBuf.Length = 0; return 0; } firstNonSlash = pIndex; // The next 1 or more chars may not be slashes. for ( ; pIndex < path.Length; pIndex++ ) { if ( path[pIndex] == '/' ) { break; } } if ( pIndex == firstNonSlash ) { absBuf.Length = 0; return 0; } absBuf.EnsureCapacity( absBuf.Length + path.Length ); absBuf.Append( "//" ); absBuf.Append( path.Substring( firstNonSlash, ( pIndex ) - ( firstNonSlash ) ) ); // The next 1 or more chars must be slashes. oldIndex = pIndex; for ( ; pIndex < path.Length; pIndex++ ) { if ( path[pIndex] != '/' ) { if ( pIndex == oldIndex ) { absBuf.Length = 0; return 0; } // We know that the path fits the pattern. if ( stopEarly ) { absBuf.Length = 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[pIndex] == '/' ) { break; } } absBuf.Append( '/' ); absBuf.Append( path.Substring( firstNonSlash, ( pIndex ) - ( firstNonSlash ) ) ); return pIndex; } } absBuf.Length = 0; return 0; } private static int beginsWithLetterColon( string path ) // Path to check start pattern. { if ( ( path.Length > 1 ) && ( System.Char.IsLetter( path[0] ) ) && ( path[1] == ':' ) ) { int pIndex; for ( pIndex = 2; pIndex < path.Length; pIndex++ ) { if ( path[pIndex] != '/' ) { break; } } return pIndex; } return 0; } private static int getWinAbsPath( string path, StringBuilder absBuf ) // Buffer to store side effect. { absBuf.Length = 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 ) - ( 0 ) ) ); } else { absBuf.Append( path.Substring( 0, ( 2 ) - ( 0 ) ) ); } return colonIndex; } else { int absIndex = getWinHomePath( path, false, absBuf ); if ( absIndex > 0 ) { return absIndex; } else if ( path[0] == '/' ) { int pIndex; for ( pIndex = 1; pIndex < path.Length; pIndex++ ) { if ( path[pIndex] != '/' ) { break; } } absBuf.Append( "/" ); return pIndex; } } return 0; } private static int getDegenerateUnixPath( string path ) // Path to check. { int pIndex = 0; while ( ( pIndex < path.Length ) && ( path[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[pIndex] == '/' ) ) { ++pIndex; } } if ( ( pIndex < path.Length ) && ( path[pIndex] == '.' ) ) { ++pIndex; } if ( ( pIndex < path.Length ) && ( path[pIndex] == '.' ) ) { ++pIndex; } // pIndex may be 1 past the end of "path". return pIndex; } internal 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[0]; if ( c == '~' ) { return PATH_ABSOLUTE; } if ( c == '/' ) { StringBuilder absBuf = new StringBuilder( 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[0] == '~' ) { return PATH_ABSOLUTE; } switch ( path.IndexOf( (System.Char)':' ) ) { case -1: if ( ( path[0] == '/' ) && ( getDegenerateUnixPath( path ) < path.Length ) ) { return PATH_ABSOLUTE; } break; case 0: return PATH_RELATIVE; default: return PATH_ABSOLUTE; } return PATH_RELATIVE; default: c = path[0]; if ( ( c == '/' ) || ( c == '~' ) ) { return PATH_ABSOLUTE; } break; } return PATH_RELATIVE; } internal static FileInfo getNewFileObj( Interp interp, string fileName ) { fileName = translateFileName( interp, fileName ); System.Diagnostics.Debug.WriteLine( "File name is \"" + fileName + "\"" ); switch ( getPathType( fileName ) ) { case PATH_RELATIVE: if ( fileName == ":memory:" ) return null; System.Diagnostics.Debug.WriteLine( "File name is PATH_RELATIVE" ); return new FileInfo( interp.getWorkingDir().FullName + "\\" + fileName ); case PATH_VOLUME_RELATIVE: System.Diagnostics.Debug.WriteLine( "File name is PATH_VOLUME_RELATIVE" ); // Something is very wrong if interp.getWorkingDir() // does not start with C: or another drive letter string cwd = interp.getWorkingDir().ToString(); int index = beginsWithLetterColon( cwd ); if ( index == 0 ) { throw new TclRuntimeError( "interp working directory \"" + cwd + "\" does not start with a drive letter" ); } // We can not use the joinPath() method because joing("D:/", "/f.txt") // returns "/f.txt" for some wacky reason. Just do it ourselves. StringBuilder buff = new StringBuilder(); buff.Append( cwd.Substring( 0, ( 2 ) - ( 0 ) ) ); buff.Append( '\\' ); for ( int i = 0; i < fileName.Length; i++ ) { if ( fileName[i] != '\\' ) { // Once we skip all the \ characters at the front // append the rest of the fileName onto the buffer buff.Append( fileName.Substring( i ) ); break; } } fileName = buff.ToString(); System.Diagnostics.Debug.WriteLine( "After PATH_VOLUME_RELATIVE join \"" + fileName + "\"" ); return new FileInfo( fileName ); case PATH_ABSOLUTE: System.Diagnostics.Debug.WriteLine( "File name is PATH_ABSOLUTE" ); return new FileInfo( fileName ); default: throw new TclRuntimeError( "type for fileName \"" + fileName + "\" not matched in case statement" ); } } private static void appendComponent( string component, int compIndex, int compSize, StringBuilder buf ) // Buffer to append the component. { for ( ; compIndex < component.Length; compIndex++ ) { char c = component[compIndex]; if ( c == '/' ) { // Eliminate duplicate slashes. while ( ( compIndex < compSize ) && ( component[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 ); } } } internal static string joinPath( Interp interp, TclObject[] argv, int startIndex, int endIndex ) { StringBuilder result = new StringBuilder( 10 ); switch ( JACL.PLATFORM ) { case JACL.PLATFORM_WINDOWS: 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; } StringBuilder absBuf = new StringBuilder( 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[0] == '~' ) { // If the path begins with '~', reset the result buffer to "". result.Length = 0; } else { // This is a relative path. Remove the ./ from tilde prefixed // elements unless it is the first component. if ( ( result.Length != 0 ) && ( String.Compare( p, pIndex, "./~", 0, 3 ) == 0 ) ) { pIndex = 2; } // Check to see if we need to append a separator before adding // this relative component. if ( result.Length != 0 ) { char c = result[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: bool 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[0] != ':' ) && ( p.IndexOf( (System.Char)':' ) != -1 ) ) { result.Length = 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[1]; } if ( p[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: 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[pIndex] == '/' ) { // If the path is absolute (except those beginning with '~'), // reset the result buffer to the absolute substring. while ( ( pIndex <= pLastIndex ) && ( p[pIndex] == '/' ) ) { pIndex++; } result.Length = 0; result.Append( '/' ); } else if ( p[pIndex] == '~' ) { // If the path begins with '~', reset the result buffer to "". result.Length = 0; } else { // This is a relative path. Remove the ./ from tilde prefixed // elements unless it is the first component. if ( ( result.Length != 0 ) && ( String.Compare( p, pIndex, "./~", 0, 3 ) == 0 ) ) { pIndex += 2; } // Append a separator if needed. if ( ( result.Length != 0 ) && ( result[result.Length - 1] != '/' ) ) { result.EnsureCapacity( result.Length + 1 ); result.Append( '/' ); } } // Append the element. appendComponent( p, pIndex, pLastIndex, result ); pIndex = p.Length; } break; } return result.ToString(); } internal static TclObject splitPath( Interp interp, string path ) { TclObject resultListObj = TclList.newInstance(); TclObject componentObj; string component = ""; string tmpPath; bool foundComponent = false; bool convertDotToColon = false; bool isColonSeparator = false; bool appendColon = false; bool 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( '\\', '/' ); StringBuilder absBuf = new StringBuilder( 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( (System.Char)':' ) ) { case -1: if ( path[0] != '/' ) { tmpPath = path; convertDotToColon = true; if ( path[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: 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: tmpPath = path; appendColon = true; prependColon = true; isColonSeparator = true; break; } break; default: if ( path[0] == '/' ) { componentObj = TclString.newInstance( "/" ); TclList.append( interp, resultListObj, componentObj ); tmpPath = path.Substring( 1 ); foundComponent = true; } else { tmpPath = path; } break; } // 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 ) - ( 0 ) ); } if ( convertDotToColon && ( component.Equals( "." ) || component.Equals( ".." ) ) ) { // If platform = MAC, convert .. to :: or . to : component = component.Replace( '.', ':' ); } if ( foundComponent ) { if ( component[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( (System.Char)'/' ) == -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; } internal static string doTildeSubst( Interp interp, string user ) { string dir; if ( user.Length == 0 ) { try { dir = interp.getVar( "env", "HOME", TCL.VarFlag.GLOBAL_ONLY ).ToString(); } catch ( System.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 ( (System.Object)dir == null ) { throw new TclException( interp, "user \"" + user + "\" doesn't exist" ); } return dir; } public static string translateFileName( Interp interp, string path ) { string fileName = ""; if ( ( path.Length == 0 ) || ( path[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 ) - ( 0 ) ); } 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; } internal static TclObject splitAndTranslate( Interp interp, string path ) { TclObject splitResult = splitPath( interp, path ); int len = TclList.getLength( interp, splitResult ); if ( len == 1 ) { string fileName = TclList.index( interp, splitResult, 0 ).ToString(); if ( fileName[0] == '~' ) { string user = translateFileName( interp, fileName ); splitResult = splitPath( interp, user ); } } return splitResult; } } // end FileUtil class }