FileDocCategorySizeDatePackage
Symlink.javaAPI DocApache Ant 1.7022330Wed Dec 13 06:16:22 GMT 2006org.apache.tools.ant.taskdefs.optional.unix

Symlink

public class Symlink extends org.apache.tools.ant.dispatch.DispatchTask
Creates, Deletes, Records and Restores Symlinks.

This task performs several related operations. In the most trivial and default usage, it creates a link specified in the link attribute to a resource specified in the resource attribute. The second usage of this task is to traverse a directory structure specified by a fileset, and write a properties file in each included directory describing the links found in that directory. The third usage is to traverse a directory structure specified by a fileset, looking for properties files (also specified as included in the fileset) and recreate the links that have been previously recorded for each directory. Finally, it can be used to remove a symlink without deleting the associated resource.

Usage examples:

Make a link named "foo" to a resource named "bar.foo" in subdir:

<symlink link="${dir.top}/foo" resource="${dir.top}/subdir/bar.foo"/>

Record all links in subdir and its descendants in files named "dir.links":

<symlink action="record" linkfilename="dir.links">
<fileset dir="${dir.top}" includes="subdir/**" />
</symlink>

Recreate the links recorded in the previous example:

<symlink action="recreate">
<fileset dir="${dir.top}" includes="subdir/**/dir.links" />
</symlink>

Delete a link named "foo" to a resource named "bar.foo" in subdir:

<symlink action="delete" link="${dir.top}/foo"/>

LIMITATIONS: Because Java has no direct support for handling symlinks this task divines them by comparing canonical and absolute paths. On non-unix systems this may cause false positives. Furthermore, any operating system on which the command ln -s link resource is not a valid command on the command line will not be able to use action="delete", action="single" or action="recreate", but action="record" should still work. Finally, the lack of support for symlinks in Java means that all links are recorded as links to the canonical resource name. Therefore the link: link --> subdir/dir/../foo.bar will be recorded as link=subdir/foo.bar and restored as link --> subdir/foo.bar.

Fields Summary
private static final org.apache.tools.ant.util.FileUtils
FILE_UTILS
private String
resource
private String
link
private Vector
fileSets
private String
linkFileName
private boolean
overwrite
private boolean
failonerror
private boolean
executing
Constructors Summary
Methods Summary
public voidaddFileset(org.apache.tools.ant.types.FileSet set)
Add a fileset to this task.

param
set The fileset to add.

        fileSets.addElement(set);
    
public voiddelete()
Delete a symlink.

throws
BuildException on error.
since
Ant 1.7

        try {
            if (link == null) {
                handleError("Must define the link name for symlink!");
                return;
            }
            log("Removing symlink: " + link);
            deleteSymlink(link);
        } catch (FileNotFoundException fnfe) {
            handleError(fnfe.toString());
        } catch (IOException ioe) {
            handleError(ioe.toString());
        } finally {
            setDefaults();
        }
    
public static voiddeleteSymlink(java.lang.String path)
Delete a symlink (without deleting the associated resource).

This is a convenience method that simply invokes deleteSymlink(java.io.File).

param
path A string containing the path of the symlink to delete.
throws
FileNotFoundException When the path results in a File that doesn't exist.
throws
IOException If calls to File.rename or File.delete fail.

        deleteSymlink(new File(path));
    
public static voiddeleteSymlink(java.io.File linkfil)
Delete a symlink (without deleting the associated resource).

This is a utility method that removes a unix symlink without removing the resource that the symlink points to. If it is accidentally invoked on a real file, the real file will not be harmed, but an exception will be thrown when the deletion is attempted. This method works by getting the canonical path of the link, using the canonical path to rename the resource (breaking the link) and then deleting the link. The resource is then returned to its original name inside a finally block to ensure that the resource is unharmed even in the event of an exception.

param
linkfil A File object of the symlink to delete.
throws
FileNotFoundException When the path results in a File that doesn't exist.
throws
IOException If calls to File.rename, File.delete or File.getCanonicalPath fail.

        if (!linkfil.exists()) {
            throw new FileNotFoundException("No such symlink: " + linkfil);
        }
        // find the resource of the existing link:
        File canfil = linkfil.getCanonicalFile();

        // rename the resource, thus breaking the link:
        File temp = FILE_UTILS.createTempFile("symlink", ".tmp",
                                              canfil.getParentFile());
        try {
            try {
                FILE_UTILS.rename(canfil, temp);
            } catch (IOException e) {
                throw new IOException(
                    "Couldn't rename resource when attempting to delete "
                    + linkfil);
            }
            // delete the (now) broken link:
            if (!linkfil.delete()) {
                throw new IOException("Couldn't delete symlink: " + linkfil
                    + " (was it a real file? is this not a UNIX system?)");
            }
        } finally {
            // return the resource to its original name:
            try {
                FILE_UTILS.rename(temp, canfil);
            } catch (IOException e) {
                throw new IOException("Couldn't return resource " + temp
                    + " to its original name: " + canfil.getAbsolutePath()
                    + "\n THE RESOURCE'S NAME ON DISK HAS "
                    + "BEEN CHANGED BY THIS ERROR!\n");
            }
        }
    
private voiddoLink(java.lang.String res, java.lang.String lnk)
Conduct the actual construction of a link.

The link is constructed by calling Execute.runCommand.

param
res The path of the resource we are linking to.
param
lnk The name of the link we wish to make.

        File linkfil = new File(lnk);
        if (overwrite && linkfil.exists()) {
            try {
                deleteSymlink(linkfil);
            } catch (FileNotFoundException fnfe) {
                handleError("Symlink disappeared before it was deleted: " + lnk);
            } catch (IOException ioe) {
                handleError("Unable to overwrite preexisting link: " + lnk);
            }
        }
        String[] cmd = new String[] {"ln", "-s", res, lnk};
        log(Commandline.toString(cmd));
        Execute.runCommand(this, cmd);
    
public synchronized voidexecute()
The standard method for executing any task.

throws
BuildException on error.

        if (executing) {
            throw new BuildException(
                "Infinite recursion detected in Symlink.execute()");
        }
        try {
            executing = true;
            DispatchUtils.execute(this);
        } finally {
            executing = false;
        }
    
private java.util.HashSetfindLinks(java.util.Vector v)
Find all the links in all supplied filesets.

This method is invoked when the action attribute is "record". This means that filesets are interpreted as the directories in which links may be found.

param
v The filesets specified by the user.
return
A HashSet of File objects containing the links (with canonical parent directories).

        HashSet result = new HashSet();
        for (int i = 0; i < v.size(); i++) {
            FileSet fs = (FileSet) v.get(i);
            DirectoryScanner ds = fs.getDirectoryScanner(getProject());
            String[][] fnd = new String[][]
                {ds.getIncludedFiles(), ds.getIncludedDirectories()};
            File dir = fs.getDir(getProject());
            for (int j = 0; j < fnd.length; j++) {
                for (int k = 0; k < fnd[j].length; k++) {
                    try {
                        File f = new File(dir, fnd[j][k]);
                        File pf = f.getParentFile();
                        String name = f.getName();
                        if (FILE_UTILS.isSymbolicLink(pf, name)) {
                            result.add(new File(pf.getCanonicalFile(), name));
                        }
                    } catch (IOException e) {
                        handleError("IOException: " + fnd[j][k] + " omitted");
                    }
                }
            }
        }
        return result;
    
private voidhandleError(java.lang.String msg)
Handle errors based on the setting of failonerror.

param
msg The message to log, or include in the BuildException.

        if (failonerror) {
            throw new BuildException(msg);
        }
        log(msg);
    
public voidinit()
Initialize the task.

throws
BuildException on error.


                
         
        super.init();
        setDefaults();
    
private java.util.PropertiesloadLinks(java.util.Vector v)
Load links from properties files included in one or more FileSets.

This method is only invoked when the action attribute is set to "recreate". The filesets passed in are assumed to specify the names of the property files with the link information and the subdirectories in which to look for them.

param
v The FileSets for this task.
return
The links to be made.

        Properties finalList = new Properties();
        // loop through the supplied file sets:
        for (int i = 0; i < v.size(); i++) {
            FileSet fs = (FileSet) v.elementAt(i);
            DirectoryScanner ds = new DirectoryScanner();
            fs.setupDirectoryScanner(ds, getProject());
            ds.setFollowSymlinks(false);
            ds.scan();
            String[] incs = ds.getIncludedFiles();
            File dir = fs.getDir(getProject());

            // load included files as properties files:
            for (int j = 0; j < incs.length; j++) {
                File inc = new File(dir, incs[j]);
                File pf = inc.getParentFile();
                Properties lnks = new Properties();
                try {
                    lnks.load(new BufferedInputStream(new FileInputStream(inc)));
                    pf = pf.getCanonicalFile();
                } catch (FileNotFoundException fnfe) {
                    handleError("Unable to find " + incs[j] + "; skipping it.");
                    continue;
                } catch (IOException ioe) {
                    handleError("Unable to open " + incs[j]
                                + " or its parent dir; skipping it.");
                    continue;
                }
                lnks.list(new PrintStream(
                    new LogOutputStream(this, Project.MSG_INFO)));
                // Write the contents to our master list of links
                // This method assumes that all links are defined in
                // terms of absolute paths, or paths relative to the
                // working directory:
                for (Iterator kitr = lnks.keySet().iterator(); kitr.hasNext();) {
                    String key = (String) kitr.next();
                    finalList.put(new File(pf, key).getAbsolutePath(),
                        lnks.getProperty(key));
                }
            }
        }
        return finalList;
    
public voidrecord()
Record symlinks.

throws
BuildException on error.
since
Ant 1.7

        try {
            if (fileSets.isEmpty()) {
                handleError("Fileset identifying links to record required");
                return;
            }
            if (linkFileName == null) {
                handleError("Name of file to record links in required");
                return;
            }
            // create a hashtable to group them by parent directory:
            Hashtable byDir = new Hashtable();

            // get an Iterator of file objects representing links (canonical):
            for (Iterator litr = findLinks(fileSets).iterator();
                litr.hasNext();) {
                File thisLink = (File) litr.next();
                File parent = thisLink.getParentFile();
                Vector v = (Vector) byDir.get(parent);
                if (v == null) {
                    v = new Vector();
                    byDir.put(parent, v);
                }
                v.addElement(thisLink);
            }
            // write a Properties file in each directory:
            for (Iterator dirs = byDir.keySet().iterator(); dirs.hasNext();) {
                File dir = (File) dirs.next();
                Vector linksInDir = (Vector) byDir.get(dir);
                Properties linksToStore = new Properties();

                // fill up a Properties object with link and resource names:
                for (Iterator dlnk = linksInDir.iterator(); dlnk.hasNext();) {
                    File lnk = (File) dlnk.next();
                    try {
                        linksToStore.put(lnk.getName(), lnk.getCanonicalPath());
                    } catch (IOException ioe) {
                        handleError("Couldn't get canonical name of parent link");
                    }
                }
                writePropertyFile(linksToStore, dir);
            }
        } finally {
            setDefaults();
        }
    
public voidrecreate()
Restore symlinks.

throws
BuildException on error.
since
Ant 1.7

        try {
            if (fileSets.isEmpty()) {
                handleError("File set identifying link file(s) "
                            + "required for action recreate");
                return;
            }
            Properties links = loadLinks(fileSets);

            for (Iterator kitr = links.keySet().iterator(); kitr.hasNext();) {
                String lnk = (String) kitr.next();
                String res = links.getProperty(lnk);
                // handle the case where lnk points to a directory (bug 25181)
                try {
                    File test = new File(lnk);
                    if (!FILE_UTILS.isSymbolicLink(null, lnk)) {
                        doLink(res, lnk);
                    } else if (!test.getCanonicalPath().equals(
                        new File(res).getCanonicalPath())) {
                        deleteSymlink(lnk);
                        doLink(res, lnk);
                    } // else lnk exists, do nothing
                } catch (IOException ioe) {
                    handleError("IO exception while creating link");
                }
            }
        } finally {
            setDefaults();
        }
    
public voidsetAction(java.lang.String action)
Set the action to be performed. May be "single", "delete", "recreate" or "record".

param
action The action to perform.

        super.setAction(action);
    
private voidsetDefaults()
Return all variables to their default state for the next invocation.

since
Ant 1.7

        resource = null;
        link = null;
        linkFileName = null;
        failonerror = true;   // default behavior is to fail on an error
        overwrite = false;    // default behavior is to not overwrite
        setAction("single");      // default behavior is make a single link
        fileSets.clear();
    
public voidsetFailOnError(boolean foe)
Set failonerror mode. If set to true (default) the entire build fails upon error; otherwise the error is logged and the build will continue.

param
foe If true throw BuildException on error, else log it.

        this.failonerror = foe;
    
public voidsetLink(java.lang.String lnk)
Set the name of the link. Used when action = "single".

param
lnk The name for the link.

        this.link = lnk;
    
public voidsetLinkfilename(java.lang.String lf)
Set the name of the file to which links will be written. Used when action = "record".

param
lf The name of the file to write links to.

        this.linkFileName = lf;
    
public voidsetOverwrite(boolean owrite)
Set overwrite mode. If set to false (default) the task will not overwrite existing links, and may stop the build if a link already exists depending on the setting of failonerror.

param
owrite If true overwrite existing links.

        this.overwrite = owrite;
    
public voidsetResource(java.lang.String src)
Set the name of the resource to which a link should be created. Used when action = "single".

param
src The resource to be linked.

        this.resource = src;
    
public voidsingle()
Create a symlink.

throws
BuildException on error.
since
Ant 1.7

        try {
            if (resource == null) {
                handleError("Must define the resource to symlink to!");
                return;
            }
            if (link == null) {
                handleError("Must define the link name for symlink!");
                return;
            }
            doLink(resource, link);
        } finally {
            setDefaults();
        }
    
private voidwritePropertyFile(java.util.Properties properties, java.io.File dir)
Write a properties file. This method uses Properties.store and thus may throw exceptions that occur while writing the file.

param
properties The properties object to be written.
param
dir The directory for which we are writing the links.

        BufferedOutputStream bos = null;
        try {
            bos = new BufferedOutputStream(
                new FileOutputStream(new File(dir, linkFileName)));
            properties.store(bos, "Symlinks from " + dir);
        } catch (IOException ioe) {
            throw new BuildException(ioe, getLocation());
        } finally {
            FileUtils.close(bos);
        }