antoine 2003/08/21 09:56:12 Modified: src/main/org/apache/tools/ant/taskdefs/optional/net FTP.java Log: Optimize scanning in FTP.FTPDirectoryScanner, using similar algorithms to the ones introduced in DirectoryScanner. There is a gain when - the include patterns look like some/very/long/path - the remote file system is case sensitive - the casesensitive and followsymlinks options of the fileset are set to true (the default) PR: 20103 Revision Changes Path 1.50 +613 -31 ant/src/main/org/apache/tools/ant/taskdefs/optional/net/FTP.java Index: FTP.java =================================================================== RCS file: /home/cvs/ant/src/main/org/apache/tools/ant/taskdefs/optional/net/FTP.java,v retrieving revision 1.49 retrieving revision 1.50 diff -u -r1.49 -r1.50 --- FTP.java 14 Aug 2003 21:01:43 -0000 1.49 +++ FTP.java 21 Aug 2003 16:56:12 -0000 1.50 @@ -66,9 +66,16 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.HashSet; import java.util.Locale; +import java.util.Map; +import java.util.Set; import java.util.StringTokenizer; import java.util.Vector; + import org.apache.tools.ant.BuildException; import org.apache.tools.ant.DirectoryScanner; import org.apache.tools.ant.Project; @@ -76,6 +83,7 @@ import org.apache.tools.ant.taskdefs.Delete; import org.apache.tools.ant.types.EnumeratedAttribute; import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.selectors.SelectorUtils; import org.apache.tools.ant.util.FileUtils; /** @@ -185,7 +193,13 @@ */ protected class FTPDirectoryScanner extends DirectoryScanner { protected FTPClient ftp = null; - + private String rootPath = null; + /** + * since ant 1.6 + * this flag should be set to true on UNIX and can save scanning time + */ + private boolean remoteSystemCaseSensitive = false; + private boolean remoteSensitivityChecked = false; /** * constructor @@ -223,7 +237,8 @@ String cwd = ftp.printWorkingDirectory(); // always start from the current ftp working dir - scandir(".", "", true); + checkIncludePatterns(); + clearCaches(); ftp.changeWorkingDirectory(cwd); } catch (IOException e) { throw new BuildException("Unable to scan FTP server: ", e); @@ -232,6 +247,106 @@ /** + * this routine is actually checking all the include patterns in + * order to avoid scanning everything under base dir + * @since ant1.6 + */ + private void checkIncludePatterns() { + Hashtable newroots = new Hashtable(); + // put in the newroots vector the include patterns without + // wildcard tokens + for (int icounter = 0; icounter < includes.length; icounter++) { + String newpattern = + SelectorUtils.rtrimWildcardTokens(includes[icounter]); + newroots.put(newpattern, includes[icounter]); + } + if (remotedir == null) { + try { + remotedir = ftp.printWorkingDirectory(); + } catch (IOException e) { + throw new BuildException("could not read current ftp directory", + getLocation()); + } + } + AntFTPFile baseFTPFile = new AntFTPRootFile(ftp, remotedir); + rootPath = baseFTPFile.getAbsolutePath(); + // construct it + if (newroots.containsKey("")) { + // we are going to scan everything anyway + scandir(remotedir, "", true); + } else { + // only scan directories that can include matched files or + // directories + Enumeration enum2 = newroots.keys(); + + while (enum2.hasMoreElements()) { + String currentelement = (String) enum2.nextElement(); + String originalpattern = (String) newroots.get(currentelement); + AntFTPFile myfile = new AntFTPFile(baseFTPFile, currentelement); + boolean isOK = true; + boolean traversesSymlinks = false; + String path = null; + + if (myfile.exists()) { + if (remoteSensitivityChecked + && remoteSystemCaseSensitive && isFollowSymlinks()) { + // cool case, + //we do not need to scan all the subdirs in the relative path + path = myfile.getFastRelativePath(); + } else { + // may be on a case insensitive file system. We want + // the results to show what's really on the disk, so + // we need to double check. + try { + path = myfile.getRelativePath(); + traversesSymlinks = myfile.isTraverseSymlinks(); + } catch (IOException be) { + throw new BuildException(be, getLocation()); + } catch (BuildException be) { + isOK = false; + + } + } + } else { + isOK = false; + } + if (isOK) { + currentelement = path.replace(remoteFileSep.charAt(0), File.separatorChar); + if (!isFollowSymlinks() + && traversesSymlinks) { + continue; + } + + if (myfile.isDirectory()) { + if (isIncluded(currentelement) + && currentelement.length() > 0) { + accountForIncludedDir(currentelement, myfile, true); + } else { + if (currentelement.length() > 0) { + if (currentelement.charAt(currentelement + .length() - 1) + != File.separatorChar) { + currentelement = + currentelement + File.separatorChar; + } + } + scandir(myfile.getAbsolutePath(), currentelement, true); + } + } else { + if (isCaseSensitive + && originalpattern.equals(currentelement)) { + accountForIncludedFile(currentelement); + } else if (!isCaseSensitive + && originalpattern + .equalsIgnoreCase(currentelement)) { + accountForIncludedFile(currentelement); + } + } + } + } + } + } + /** * scans a particular directory * @param dir directory to scan * @param vpath relative path to the base directory of the remote fileset @@ -239,12 +354,22 @@ * @param fast seems to be always true in practice */ protected void scandir(String dir, String vpath, boolean fast) { + // avoid double scanning of directories, can only happen in fast mode + if (fast && hasBeenScanned(vpath)) { + return; + } try { if (!ftp.changeWorkingDirectory(dir)) { return; } - - FTPFile[] newfiles = ftp.listFiles(); + String completePath = null; + if (!vpath.equals("")) { + completePath = rootPath + remoteFileSep + + vpath.replace(File.separatorChar, remoteFileSep.charAt(0)); + } else { + completePath = rootPath; + } + FTPFile[] newfiles = listFiles(completePath, false); if (newfiles == null) { ftp.changeToParentDirectory(); @@ -261,24 +386,8 @@ dirsExcluded.addElement(name); slowScanAllowed = false; } else if (isIncluded(name)) { - if (!isExcluded(name)) { - if (fast) { - if (file.isSymbolicLink()) { - scandir(file.getLink(), - name + File.separator, fast); - } else { - scandir(file.getName(), - name + File.separator, fast); - } - } - dirsIncluded.addElement(name); - } else { - dirsExcluded.addElement(name); - if (fast && couldHoldIncluded(name)) { - scandir(file.getName(), - name + File.separator, fast); - } - } + accountForIncludedDir(name, + new AntFTPFile(ftp, file, completePath) , fast); } else { dirsNotIncluded.addElement(name); if (fast && couldHoldIncluded(name)) { @@ -295,15 +404,7 @@ if (!isFollowSymlinks() && file.isSymbolicLink()) { filesExcluded.addElement(name); } else if (isFunctioningAsFile(ftp, dir, file)) { - if (isIncluded(name)) { - if (!isExcluded(name)) { - filesIncluded.addElement(name); - } else { - filesExcluded.addElement(name); - } - } else { - filesNotIncluded.addElement(name); - } + accountForIncludedFile(name); } } } @@ -312,6 +413,487 @@ } catch (IOException e) { throw new BuildException("Error while communicating with FTP " + "server: ", e); + } + } + /** + * process included file + * @param name path of the file relative to the directory of the fileset + */ + private void accountForIncludedFile(String name) { + if (!filesIncluded.contains(name) + && !filesExcluded.contains(name)) { + + if (isIncluded(name)) { + if (!isExcluded(name)) { + filesIncluded.addElement(name); + } else { + filesExcluded.addElement(name); + } + } else { + filesNotIncluded.addElement(name); + } + } + } + + /** + * + * @param name path of the directory relative to the directory of + * the fileset + * @param file directory as file + * @param fast + */ + private void accountForIncludedDir(String name, AntFTPFile file, boolean fast) { + if (!dirsIncluded.contains(name) + && !dirsExcluded.contains(name)) { + + if (!isExcluded(name)) { + if (fast) { + if (file.isSymbolicLink()) { + try { + file.getClient().changeWorkingDirectory(file.curpwd); + } catch (IOException ioe) { + throw new BuildException("could not change directory to curpwd"); + } + scandir(file.getLink(), + name + File.separator, fast); + } else { + try { + file.getClient().changeWorkingDirectory(file.curpwd); + } catch (IOException ioe) { + throw new BuildException("could not change directory to curpwd"); + } + scandir(file.getName(), + name + File.separator, fast); + } + } + dirsIncluded.addElement(name); + } else { + dirsExcluded.addElement(name); + if (fast && couldHoldIncluded(name)) { + try { + file.getClient().changeWorkingDirectory(file.curpwd); + } catch (IOException ioe) { + throw new BuildException("could not change directory to curpwd"); + } + scandir(file.getName(), + name + File.separator, fast); + } + } + } + } + /** + * temporary table to speed up the various scanning methods below + * + * @since Ant 1.6 + */ + private Map fileListMap = new HashMap(); + /** + * List of all scanned directories. + * + * @since Ant 1.6 + */ + private Set scannedDirs = new HashSet(); + + /** + * Has the directory with the given path relative to the base + * directory already been scanned? + * + * <p>Registers the given directory as scanned as a side effect.</p> + * + * @since Ant 1.6 + */ + private boolean hasBeenScanned(String vpath) { + return !scannedDirs.add(vpath); + } + + /** + * Clear internal caches. + * + * @since Ant 1.6 + */ + private void clearCaches() { + fileListMap.clear(); + scannedDirs.clear(); + } + /** + * list the files present in one directory. + * @param directory full path on the remote side + * @param changedir if true change to directory directory before listing + * @return array of FTPFile + */ + public FTPFile[] listFiles(String directory, boolean changedir) { + //getProject().log("listing files in directory " + directory, Project.MSG_DEBUG); + String currentPath = directory; + if (changedir) { + try { + boolean result = ftp.changeWorkingDirectory(directory); + if (!result) { + return null; + } + currentPath = ftp.printWorkingDirectory(); + } catch (IOException ioe) { + throw new BuildException(ioe, getLocation()); + } + } + if (fileListMap.containsKey(currentPath)) { + getProject().log("filelist map used in listing files", Project.MSG_DEBUG); + return ((FTPFile[]) fileListMap.get(currentPath)); + } + FTPFile[] result = null; + try { + result = ftp.listFiles(); + } catch (IOException ioe) { + throw new BuildException(ioe, getLocation()); + } + fileListMap.put(currentPath, result); + if (!remoteSensitivityChecked) { + checkRemoteSensitivity(result, directory); + } + return result; + } + /** + * cd into one directory and + * list the files present in one directory. + * @param directory full path on the remote side + * @return array of FTPFile + */ + public FTPFile[] listFiles(String directory) { + return listFiles(directory, true); + } + private void checkRemoteSensitivity(FTPFile[] array, String directory) { + boolean candidateFound = false; + String target = null; + for (int icounter = 0; icounter < array.length; icounter++) { + if (array[icounter].isDirectory()) { + if (!array[icounter].getName().equals(".") + && !array[icounter].getName().equals("..")) { + candidateFound = true; + target = fiddleName(array[icounter].getName()); + getProject().log("will try to cd to " + + target + " where a directory called " + array[icounter].getName() + + " exists", Project.MSG_DEBUG); + for (int pcounter = 0; pcounter < array.length; pcounter++) { + if (array[pcounter].getName().equals(target) && pcounter != icounter) { + candidateFound = false; + } + } + if (candidateFound) { + break; + } + } + } + } + if (candidateFound) { + try { + getProject().log("testing case sensitivity, attempting to cd to " + + target, Project.MSG_DEBUG); + remoteSystemCaseSensitive = !ftp.changeWorkingDirectory(target); + } catch (IOException ioe) { + remoteSystemCaseSensitive = true; + } finally { + try { + ftp.changeWorkingDirectory(directory); + } catch (IOException ioe) { + throw new BuildException(ioe, getLocation()); + } + } + getProject().log("remote system is case sensitive : " + remoteSystemCaseSensitive, + Project.MSG_VERBOSE); + remoteSensitivityChecked = true; + } + } + private String fiddleName(String origin) { + StringBuffer result = new StringBuffer(); + for (int icounter = 0; icounter < origin.length(); icounter++) { + if (Character.isLowerCase(origin.charAt(icounter))) { + result.append(Character.toUpperCase(origin.charAt(icounter))); + } else if (Character.isUpperCase(origin.charAt(icounter))) { + result.append(Character.toLowerCase(origin.charAt(icounter))); + } else { + result.append(origin.charAt(icounter)); + } + } + return result.toString(); + } + /** + * an AntFTPFile is a representation of a remote file + * @since Ant 1.6 + */ + protected class AntFTPFile { + /** + * ftp client + */ + private FTPClient client; + /** + * parent directory of the file + */ + private String curpwd; + /** + * the file itself + */ + private FTPFile ftpFile; + /** + * + */ + private AntFTPFile parent = null; + private boolean relativePathCalculated = false; + private boolean traversesSymlinks = false; + private String relativePath = ""; + /** + * constructor + * @param client ftp client variable + * @param ftpFile the file + * @param curpwd absolute remote path where the file is found + */ + public AntFTPFile(FTPClient client, FTPFile ftpFile, String curpwd) { + this.client = client; + this.ftpFile = ftpFile; + this.curpwd = curpwd; + } + /** + * other constructor + * @param parent the parent file + * @param path a relative path to the parent file + */ + public AntFTPFile(AntFTPFile parent, String path) { + this.parent = parent; + this.client = parent.client; + Vector pathElements = SelectorUtils.tokenizePath(path); + try { + this.client.changeWorkingDirectory(parent.getAbsolutePath()); + this.curpwd = parent.getAbsolutePath(); + } catch (IOException ioe) { + throw new BuildException("could not change working dir to " + + parent.curpwd); + } + for (int fcount = 0; fcount < pathElements.size() - 1; fcount++) { + try { + this.client.changeWorkingDirectory((String) pathElements.elementAt(fcount)); + this.curpwd = this.curpwd + remoteFileSep + + (String) pathElements.elementAt(fcount); + } catch (IOException ioe) { + throw new BuildException("could not change working dir to " + + (String) pathElements.elementAt(fcount) + + " from " + this.curpwd); + } + + } + String lastpathelement = (String) pathElements.elementAt(pathElements.size() - 1); + FTPFile [] theFiles = listFiles(this.curpwd); + this.ftpFile = getFile(theFiles, lastpathelement); + } + /** + * find out if the file exists + * @return true if the file exists + */ + public boolean exists() { + return (ftpFile != null); + } + /** + * if the file is a symbolic link, find out to what it is pointing + * @return the target of the symbolic link + */ + public String getLink() { + return ftpFile.getLink(); + } + /** + * get the name of the file + * @return the name of the file + */ + public String getName() { + return ftpFile.getName(); + } + /** + * find out the absolute path of the file + * @return absolute path as string + */ + public String getAbsolutePath() { + return curpwd + remoteFileSep + ftpFile.getName(); + } + /** + * find out the relative path assuming that the path used to construct + * this AntFTPFile was spelled properly with regards to case. + * This is OK on a case sensitive system such as UNIX + * @return relative path + */ + public String getFastRelativePath() { + String absPath = getAbsolutePath(); + if (absPath.indexOf(rootPath + remoteFileSep) == 0) { + return absPath.substring(rootPath.length() + remoteFileSep.length()); + } + return null; + } + /** + * find out the relative path to the rootPath of the enclosing scanner. + * this relative path is spelled exactly like on disk, + * for instance if the AntFTPFile has been instantiated as ALPHA, + * but the file is really called alpha, this method will return alpha. + * If a symbolic link is encountered, it is followed, but the name of the link + * rather than the name of the target is returned. + * (ie does not behave like File.getCanonicalPath()) + * @return relative path, separated by remoteFileSep + * @throws IOException if a change directory fails, ... + * @throws BuildException if one of the components of the relative path cannot + * be found. + */ + public String getRelativePath() throws IOException, BuildException { + if (!relativePathCalculated) { + if (parent != null) { + traversesSymlinks = parent.isTraverseSymlinks(); + relativePath = getRelativePath(parent.getAbsolutePath(), + parent.getRelativePath()); + } else { + relativePath = getRelativePath(rootPath, ""); + relativePathCalculated = true; + } + } + return relativePath; + } + /** + * get thge relative path of this file + * @param currentPath base path + * @param currentRelativePath relative path of the base path with regards to remote dir + * @return relative path + */ + private String getRelativePath(String currentPath, String currentRelativePath) { + Vector pathElements = SelectorUtils.tokenizePath(getAbsolutePath(), remoteFileSep); + Vector pathElements2 = SelectorUtils.tokenizePath(currentPath, remoteFileSep); + String relPath = currentRelativePath; + for (int pcount = pathElements2.size(); pcount < pathElements.size(); pcount++) { + String currentElement = (String) pathElements.elementAt(pcount); + FTPFile[] theFiles = listFiles(currentPath); + FTPFile theFile = null; + if (theFiles != null) { + theFile = getFile(theFiles, currentElement); + } + if (theFile == null) { + throw new BuildException("could not find " + currentElement + + " from " + currentPath); + } else { + traversesSymlinks = traversesSymlinks || theFile.isSymbolicLink(); + if (!relPath.equals("")) { + relPath = relPath + remoteFileSep; + } + relPath = relPath + theFile.getName(); + currentPath = currentPath + remoteFileSep + theFile.getName(); + } + } + return relPath; + } + /** + * find a file matching a string in an array of FTPFile. + * This method will find "alpha" when requested for "ALPHA" + * if and only if the caseSensitive attribute is set to false. + * When caseSensitive is set to true, only the exact match is returned. + * @param theFiles array of files + * @param lastpathelement the file name being sought + * @return null if the file cannot be found, otherwise return the matching file. + */ + public FTPFile getFile(FTPFile[] theFiles, String lastpathelement) { + if (theFiles == null) { + return null; + } + for (int fcount = 0; fcount < theFiles.length; fcount++) { + if (theFiles[fcount].getName().equals(lastpathelement)) { + return theFiles[fcount]; + } else if (!isCaseSensitive() + && theFiles[fcount].getName().equalsIgnoreCase(lastpathelement)) { + return theFiles[fcount]; + } + } + return null; + } + /** + * tell if a file is a directory. + * note that it will return false for symbolic links pointing to directories. + * @return <code>true</code> for directories + */ + public boolean isDirectory() { + return ftpFile.isDirectory(); + } + /** + * tell if a file is a symbolic link + * @return <code>true</code> for symbolic links + */ + public boolean isSymbolicLink() { + return ftpFile.isSymbolicLink(); + } + /** + * return the attached FTP client object. + * Warning : this instance is really shared with the enclosing class. + * @return FTP client + */ + protected FTPClient getClient() { + return client; + } + + /** + * sets the current path of an AntFTPFile + * @param curpwd the current path one wants to set + */ + protected void setCurpwd(String curpwd) { + this.curpwd = curpwd; + } + /** + * returns the path of the directory containing the AntFTPFile. + * of the full path of the file itself in case of AntFTPRootFile + * @return parent directory of the AntFTPFile + */ + public String getCurpwd() { + return curpwd; + } + /** + * find out if a symbolic link is encountered in the relative path of this file + * from rootPath. + * @return <code>true</code> if a symbolic link is encountered in the relative path. + * @throws IOException if one of the change directory or directory listing operations + * fails + * @throws BuildException if a path component in the relative path cannot be found. + */ + public boolean isTraverseSymlinks() throws IOException, BuildException { + if (!relativePathCalculated) { + // getRelativePath also finds about symlinks + String relpath = getRelativePath(); + } + return traversesSymlinks; + } + } + /** + * special class to represent the remote directory itself + * @since Ant 1.6 + */ + protected class AntFTPRootFile extends AntFTPFile { + private String remotedir; + /** + * constructor + * @param aclient FTP client + * @param remotedir remote directory + */ + public AntFTPRootFile(FTPClient aclient, String remotedir) { + super(aclient, null, remotedir); + this.remotedir = remotedir; + try { + this.getClient().changeWorkingDirectory(this.remotedir); + this.setCurpwd(this.getClient().printWorkingDirectory()); + } catch (IOException ioe) { + throw new BuildException(ioe, getLocation()); + } + } + /** + * find the absolute path + * @return absolute path + */ + public String getAbsolutePath() { + return this.getCurpwd(); + } + /** + * find out the relative path to root + * @return empty string + * @throws BuildException actually never + * @throws IOException actually never + */ + public String getRelativePath() throws BuildException, IOException { + return ""; } } }
--------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]