You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@ant.apache.org by bo...@apache.org on 2002/11/06 16:07:23 UTC

cvs commit: jakarta-ant/src/testcases/org/apache/tools/ant/taskdefs/optional/unix SymlinkTest.java

bodewig     2002/11/06 07:07:23

  Modified:    .        WHATSNEW
               docs/manual optionaltasklist.html
               src/main/org/apache/tools/ant/taskdefs defaults.properties
  Added:       docs/manual/OptionalTasks symlink.html
               src/etc/testcases/taskdefs/optional symlink.xml
               src/main/org/apache/tools/ant/taskdefs/optional/unix
                        Symlink.java
               src/testcases/org/apache/tools/ant/taskdefs/optional/unix
                        SymlinkTest.java
  Log:
  New optional task <symlink>.
  
  PR: 9808
  Submitted by:	Gus Heck <gus.heck at olin.edu>
  
  Revision  Changes    Path
  1.309     +2 -0      jakarta-ant/WHATSNEW
  
  Index: WHATSNEW
  ===================================================================
  RCS file: /home/cvs/jakarta-ant/WHATSNEW,v
  retrieving revision 1.308
  retrieving revision 1.309
  diff -u -r1.308 -r1.309
  --- WHATSNEW	6 Nov 2002 12:39:53 -0000	1.308
  +++ WHATSNEW	6 Nov 2002 15:07:22 -0000	1.309
  @@ -85,6 +85,8 @@
     Checkouts now have the option of using repository timestamps, instead
     of current.
   
  +* new task <symlink> that creates and maintains symbolic links.
  +
   Changes from Ant 1.5.1Beta1 to 1.5.1
   ====================================
   
  
  
  
  1.32      +1 -0      jakarta-ant/docs/manual/optionaltasklist.html
  
  Index: optionaltasklist.html
  ===================================================================
  RCS file: /home/cvs/jakarta-ant/docs/manual/optionaltasklist.html,v
  retrieving revision 1.31
  retrieving revision 1.32
  diff -u -r1.31 -r1.32
  --- optionaltasklist.html	9 Jul 2002 21:05:50 -0000	1.31
  +++ optionaltasklist.html	6 Nov 2002 15:07:23 -0000	1.32
  @@ -58,6 +58,7 @@
   <a href="OptionalTasks/splash.html">Splash</a><br>
   <a href="OptionalTasks/starteam.html">Starteam Tasks</a><br>
   <a href="OptionalTasks/stylebook.html">Stylebook</a><br>
  +<a href="OptionalTasks/symlink.html">Symlink</a><br>
   <a href="OptionalTasks/telnet.html">Telnet</a><br>
   <a href="OptionalTasks/test.html">Test</a><br>
   <a href="OptionalTasks/translate.html">Translate</a><br>
  
  
  
  1.1                  jakarta-ant/docs/manual/OptionalTasks/symlink.html
  
  Index: symlink.html
  ===================================================================
  <html>
  
  <head>
  <meta http-equiv="Content-Language" content="en-us">
  <title>Symlink Task</title>
  </head>
  
  <body>
  
  <h2><a name="symlink">Symlink</a></h2>
  <h3>Description</h3>
  <p> Manages symbolic links on Unix based platforms. Can be used to
  make an individual link, delete a link, create multiple links from properties files, 
  or create properties files describing links in the specified directories.
  Existing links are not overwritten by default.
  
  <p><a href="../CoreTypes/fileset.html">FileSet</a>s are used to select a
  set of links to record, or a set of property files to create links from. </p>
  <h3>Parameters</h3>
  <table border="1" cellpadding="2" cellspacing="0">
    <tr>
      <td valign="top"><b>Attribute</b></td>
      <td valign="top"><b>Description</b></td>
      <td align="center" valign="top"><b>Required</b></td>
    </tr>
    <tr>
      <td valign="top">action</td>
      <td valign="top">The type of action to perform, may be "single", 
      "record", "recreate" or "delete".</td>
      <td valign="top" align="center">No, defaults to single.</td>
    </tr>
    <tr>
      <td valign="top">link</td>
      <td valign="top">The name of the link to be created or deleted.</td>
      <td valign="center" align="center" >required for 
      action="single" or "delete". Ignored in other actions.</td>
    </tr>
    <tr>
      <td valign="top">resource</td>
      <td valign="top">The resource the link should point to.</td>
      <td valign="top" align="center">required for action="single". Ignored in other actions.</td>
    </tr>
    <tr>
      <td valign="top">linkfilename</td>
      <td valign="top">The name of the properties file to create in 
      each included directory.</td>
      <td valign="top" align="center">required for action="record". 
      Ignored in other actions.</td>
    </tr>
    <tr>
      <td valign="top">overwrite</td>
      <td valign="top">Overwrite existing links or not.</td>
      <td valign="top" align="center">No; defaults to false.</td>
    </tr>
    <tr>
      <td valign="top">failonerror</td>
       <td valign="top">Stop build if true, log a warning message, but do not stop the build,
         when the an error occurs if false.
       </td>
       <td valign="top" align="center">No; defaults to true.</td>
    </tr>
  </table>
  <h3>Parameters specified as nested elements</h3>
   
  <h4>fileset</h4>
   <p><a href="../CoreTypes/fileset.html">FileSet</a>s
   are used when action = "record" to select directories and linknames to be recorded. 
   They are also used when action = "recreate" to specify both the name of the property 
   files to be processed, and the directories in which they can be found. At least one 
   fileset is required for each case.</p>
   
  <h3>Examples</h3>
  
    <p> Make a link named "foo" to a resource named "bar.foo" in subdir:</p>
    <pre>
    &lt;symlink link="${dir.top}/foo" resource="${dir.top}/subdir/bar.foo"/&gt;
    </pre>
   
    <p> Record all links in subdir and it's descendants in files named
    "dir.links"</p>
    <pre>
    &lt;symlink action="record" linkfilename="dir.links"&gt;
       &lt;fileset dir="${dir.top}" includes="subdir&#47;**" /&gt;
    &lt;/symlink&gt;
    </pre>
   
    <p> Recreate the links recorded in the previous example:</p>
    <pre>
    &lt;symlink action="recreate"&gt;
       &lt;fileset dir="${dir.top}" includes="subdir&#47;**&#47;dir.links" /&gt;  
    &lt;/symlink&gt;
    </pre>
   
   <p> Delete a link named "foo":
   <pre>
   &lt;symlink action="delete" link="${dir.top}/foo"/&gt;
   </pre>
  
    <p><strong>Java 1.2 and earlier:</strong> Due to limitations on executing system
    level comands in Java versions earlier than 1.3 this task may have difficulty
    operating with a relative path in ANT_HOME. The typical symptom is an 
    IOException where ant can't find /some/working/directory${ANT_HOME}/bin/antRun
    or something similar. The workaround is to change your ANT_HOME environment
    variable to an absolute path, which will remove the /some/working/directory portion
    of the above path and allow ant to find the correct commandline execution script.
   
    <p><strong>LIMITATIONS:</strong> 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
    <code>ln -s &lt;linkname&gt; &lt;resourcename&gt;</code> is not a valid 
    command on the comand line will not be able to use action="single" or 
    action="recreate". Action="record" and action=delete should still work. Finally, 
    the lack of support for symlinks in Java means that all links are recorded as 
    links to the <strong>canonical</strong> resource name. Therefore the link:
    <code>link --> subdir/dir/../foo.bar</code> will be recorded as
    <code>link=subdir/foo.bar</code> and restored as
    <code>link --> subdir/foo.bar</code></p>
  
  <hr><p align="center">Copyright &copy; 2002 Apache Software
  Foundation.  All rights Reserved.</p>
  
  </body>
  </html>
  
  
  
  1.1                  jakarta-ant/src/etc/testcases/taskdefs/optional/symlink.xml
  
  Index: symlink.xml
  ===================================================================
  <?xml version="1.0"?>
  
  <!--
  
  /*
   * Since the initial version of this file was deveolped on the clock on
   * an NSF grant I should say the following boilerplate:
   *
   * This material is based upon work supported by the National Science
   * Foundaton under Grant No. EIA-0196404. Any opinions, findings, and
   * conclusions or recommendations expressed in this material are those
   * of the author and do not necessarily reflect the views of the
   * National Science Foundation.
   */
  
  -->
  
  <project name="symlink-test" basedir="." default="all">
  
    <!-- 
         Since the symlink task and some of these targets rely on
         calls to exec, it may be possible for the JVM to outrun the 
         execution of the command line  system calls, so this value is
         the number of seconds we give the operating system to
         catch up before executing a task that depends on the 
         completion of previous tasks. This delay is also added to
         the end of each target so junit doesn't go testing things
         before they have finnished (hopefully). Tweak if needed.
    -->
  
    <property name="delay" value="3"/>
   
    <property name="tdir" value="${basedir}/test-working"/>
  
    <target name="setup">
        <delete dir="${tdir}"/>
        <mkdir dir="${tdir}"/>
    </target>
  
    <!-- test for action = single -->
    <!-- 
      Creates:
           File: ${tdir}/symlink.test
           Link: ${tdir}/singletest
    -->
    <target name="test-single">
      <touch file="${tdir}/symlink.test"/>
      <symlink resource="${tdir}/symlink.test" 
               link="${tdir}/singletest" 
               failonerror="yes"/>
      <sleep seconds="${delay}"/> <!-- make sure OS has time to catch up -->
      <available file="${tdir}/symlink.test" 
                 property="test.single.file.created"/>
      <available file="${tdir}/singletest" 
                 property="test.single.link.created"/>
    </target>
  
  
  
    <!-- test for action = delete  (no calls to command line so no sleep) -->
  
    <!-- 
      Creates:
           (none)
      Deletes:
           Link: ${tdir}/singletest
    -->
  
    <target name="test-delete">
      <touch file="${tdir}/symlink.test"/>
      <symlink resource="${tdir}/symlink.test" 
               link="${tdir}/singletest" 
               failonerror="yes"/>
      <sleep seconds="${delay}"/> <!-- make sure OS has time to catch up -->
  
      <symlink action="delete" link="${tdir}/singletest" failonerror="yes"/>
      <symlink action="delete" link="${tdir}/symlink.test" failonerror="no"/>
      <sleep seconds="${delay}"/> <!-- make sure OS has time to catch up -->
  
      <available file="${tdir}/symlink.test" 
                 property="test.delete.file.still.there"/>
      <available file="${tdir}/singletest" 
                 property="test.delete.link.still.there"
                 value="ERROR: link deletion failed"/>
      
    </target>
  
  
  
    <!-- test for action = record -->
  
    <!-- 
      Creates:
           Dir:  ${tdir}/symtest1
           Dir:  ${tdir}/symtest1/symtest2
           Dir:  ${tdir}/symtest1/symtest3
           File: ${tdir}/symtest1/file1
           File: ${tdir}/symtest1/symtest2/file2
           File: ${tdir}/symtest1/symtest3/fileA
           File: ${tdir}/symtest1/symtest3/fileB
           File: ${tdir}/symtest1/symtest3/fileC
           Link: ${tdir}/symtest1/link1==>${tdir}/symtest1/file1
           Link: ${tdir}/symtest1/link2==>${tdir}/symtest1/symtest2/file2
           Link: ${tdir}/symtest1/symtest2/link3==>
                                             ${tdir}/symtest1/symtest2/file2
           Link: ${tdir}/symtest1/dirlink==>${tdir}/symtest1/symtest3
           File: ${tdir}/symtest1/recorded.links
           File: ${tdir}/symtest1/symtest2/recorded.links
      Deletes:
           (none)
    -->
  
    <target name="test-record">
  
      <mkdir dir="${tdir}/symtest1"/>
      <mkdir dir="${tdir}/symtest1/symtest2"/>
      <mkdir dir="${tdir}/symtest1/symtest3"/>
      <touch file="${tdir}/symtest1/file1"/>
      <touch file="${tdir}/symtest1/symtest2/file2"/>
  
      <touch file="${tdir}/symtest1/symtest3/fileA"/>
      <touch file="${tdir}/symtest1/symtest3/fileB"/>
      <touch file="${tdir}/symtest1/symtest3/fileC"/>
  
      <symlink resource="${tdir}/symtest1/file1" 
               link="${tdir}/symtest1/link1" 
               failonerror="no" />
      <symlink resource="${tdir}/symtest1/symtest2/file2" 
               link="${tdir}/symtest1/link2" 
               failonerror="no" />
      <symlink resource="${tdir}/symtest1/symtest2/file2" 
               link="${tdir}/symtest1/symtest2/link3" 
               failonerror="no" />
      <symlink resource="${tdir}/symtest1/symtest3"
               link="${tdir}/symtest1/dirlink"
               failonerror="no" />
  
      <sleep seconds="${delay}"/> <!-- make sure OS has time to catch up -->
  
      <symlink action="record" linkfilename="recorded.links">
         <fileset dir="${tdir}/symtest1" includes="**/**"/>
      </symlink>
  
      <sleep seconds="${delay}"/> <!-- make sure OS has time to catch up -->
  
      <!-- Test to see if the directories were created -->
  
      <available file="${tdir}/symtest1"
                 type="dir"
                 property="test.record.dir1.created"/>
  
      <available file="${tdir}/symtest1/symtest2"
                 type="dir"
                 property="test.record.dir2.created"/>
  
      <available file="${tdir}/symtest1/symtest3"
                 type="dir"
                 property="test.record.dir3.created"/>
  
      <!-- Test to see if the Files were created -->
  
      <available file="${tdir}/symtest1/file1"
                 property="test.record.file1.created"/>
  
      <available file="${tdir}/symtest1/symtest2/file2"
                 property="test.record.file2.created"/>
  
      <available file="${tdir}/symtest1/symtest3/fileA"
                 property="test.record.fileA.created"/>
  
      <available file="${tdir}/symtest1/symtest3/fileB"
                 property="test.record.fileB.created"/>
  
      <available file="${tdir}/symtest1/symtest3/fileC"
                 property="test.record.fileC.created"/>
  
      <!-- Test to see if the links were created -->
  
      <available file="${tdir}/symtest1/link1"
                 property="test.record.link1.created"/>
  
      <available file="${tdir}/symtest1/link2"
                 property="test.record.link2.created"/>
  
      <available file="${tdir}/symtest1/symtest2/link3"
                 property="test.record.link3.created"/>
  
      <available file="${tdir}/symtest1/dirlink"
                 property="test.record.dirlink.created"/>
  
      <!-- Test to see if the linkfiles were created -->
  
      <available file="${tdir}/symtest1/recorded.links"
                 property="test.record.dir1.recorded"/>
  
      <available file="${tdir}/symtest1/symtest2/recorded.links"
                 property="test.record.dir2.recorded"/>
  
      <!-- THIS should not be set -->
  
      <available file="${tdir}/symtest1/symtest3/recorded.links"
                 property="test.record.dir3.recorded"
                 value="ERROR: symtest3/recorded.links should not exist"/>
  
  
    </target>
  
    <!-- test for action = recreate -->
  
    <!-- 
      Deletes:
           Link: ${tdir}/symtest1/link1==>${tdir}/symtest1/file1
           Link: ${tdir}/symtest1/link2==>${tdir}/symtest1/symtest2/file2
           Link: ${tdir}/symtest1/symtest2/link3==>
                                             ${tdir}/symtest1/symtest2/file2
           Link: ${tdir}/sumtest1/dirlink==>${tdir}/symtest1/symtest3
  
      Recreates:
           Link: ${tdir}/symtest1/link1==>${tdir}/symtest1/file1
           Link: ${tdir}/symtest1/link2==>${tdir}/symtest1/symtest2/file2
           Link: ${tdir}/symtest1/symtest2/link3==>
                                             ${tdir}/symtest1/symtest2/file2
           Link: ${tdir}/sumtest1/dirlink==>${tdir}/symtest1/symtest3
    -->
  
    <target name="test-recreate" depends="test-record">
  
      <symlink action="delete" link="${tdir}/symtest1/link1"/>
      <symlink action="delete" link="${tdir}/symtest1/link2"/>
      <symlink action="delete" link="${tdir}/symtest1/symtest2/link3"/>
      <symlink action="delete" link="${tdir}/symtest1/dirlink"/>
  
      <available file="${tdir}/symtest1/link1"
                 property="test.recreate.link1.not.removed"
                 value="ERROR: rm -f symtest1/link1 failed"/>
  
      <available file="${tdir}/symtest1/link2"
                 property="test.recreate.link2.not.removed"
                 value="ERROR: rm -f symtest1/link2 failed"/>
  
      <available file="${tdir}/symtest1/symtest2/link3"
                 property="test.recreate.link3.not.removed"
                 value="ERROR: rm -f symtest1/symtest2/link3 failed"/>
  
       <available file="${tdir}/symtest1/zdirlink"
                 property="test.recreate.zdirlink.not.removed"
                 value="ERROR: rm -f symtest1/zdirlink failed"/>
  
      <sleep seconds="${delay}"/>  <!-- make sure OS has time to do the execs -->
  
      <symlink action="recreate">
        <fileset dir="${tdir}/symtest1" includes="**/recorded.links"/>
      </symlink>
  
      <sleep seconds="${delay}"/>  <!-- make sure OS has time to catch up -->
  
      <available file="${tdir}/symtest1/link1"
                 property="test.recreate.link1.recreated"/>
  
      <available file="${tdir}/symtest1/link2"
                 property="test.recreate.link2.recreated"/>
  
      <available file="${tdir}/symtest1/symtest2/link3"
                 property="test.recreate.link3.recreated"/>
  
      <available file="${tdir}/symtest1/dirlink"
                 property="test.recreate.dirlink.recreated"/>
    </target>
  
  
  <!-- CALL THIS to clean things up afterwards -->
  
    <target name="teardown">
      <delete dir="${tdir}"/>
    </target>
  
  </project>
  
  
  
  1.133     +1 -0      jakarta-ant/src/main/org/apache/tools/ant/taskdefs/defaults.properties
  
  Index: defaults.properties
  ===================================================================
  RCS file: /home/cvs/jakarta-ant/src/main/org/apache/tools/ant/taskdefs/defaults.properties,v
  retrieving revision 1.132
  retrieving revision 1.133
  diff -u -r1.132 -r1.133
  --- defaults.properties	8 Oct 2002 05:55:54 -0000	1.132
  +++ defaults.properties	6 Nov 2002 15:07:23 -0000	1.133
  @@ -171,6 +171,7 @@
   jarlib-resolve=org.apache.tools.ant.taskdefs.optional.extension.JarLibResolveTask
   setproxy=org.apache.tools.ant.taskdefs.optional.net.SetProxy
   vbc=org.apache.tools.ant.taskdefs.optional.dotnet.VisualBasicCompile
  +symlink=org.apache.tools.ant.taskdefs.optional.unix.Symlink
   
   # deprecated ant tasks (kept for back compatibility)
   starteam=org.apache.tools.ant.taskdefs.optional.scm.AntStarTeamCheckOut
  
  
  
  1.1                  jakarta-ant/src/main/org/apache/tools/ant/taskdefs/optional/unix/Symlink.java
  
  Index: Symlink.java
  ===================================================================
  /*
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 2002 The Apache Software Foundation.  All rights
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   *
   * 3. The end-user documentation included with the redistribution, if
   *    any, must include the following acknowlegement:
   *       "This product includes software developed by the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowlegement may appear in the software itself,
   *    if and wherever such third-party acknowlegements normally appear.
   *
   * 4. The names "The Jakarta Project", "Ant", and "Apache Software
   *    Foundation" must not be used to endorse or promote products derived
   *    from this software without prior written permission. For written
   *    permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache"
   *    nor may "Apache" appear in their names without prior written
   *    permission of the Apache Group.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   */
  
  /*
   * Since the initial version of this file was deveolped on the clock on
   * an NSF grant I should say the following boilerplate:
   *
   * This material is based upon work supported by the National Science
   * Foundaton under Grant No. EIA-0196404. Any opinions, findings, and
   * conclusions or recommendations expressed in this material are those
   * of the author and do not necessarily reflect the views of the
   * National Science Foundation.
   */
  
  package org.apache.tools.ant.taskdefs.optional.unix;
  
  import java.io.File;
  import java.io.FileWriter;
  import java.io.IOException;
  import java.io.StringWriter;
  import java.io.OutputStream;
  import java.io.FileInputStream;
  import java.io.FileOutputStream;
  import java.io.BufferedOutputStream;
  import java.io.FileNotFoundException;
  
  import java.util.Vector;
  import java.util.Properties;
  import java.util.Enumeration;
  import java.util.Hashtable;
  
  import java.lang.reflect.InvocationTargetException;
  import java.lang.reflect.Method;
  
  import org.apache.tools.ant.Task;
  import org.apache.tools.ant.BuildException;
  import org.apache.tools.ant.DirectoryScanner;
  
  import org.apache.tools.ant.util.FileUtils;
  
  import org.apache.tools.ant.types.FileSet;
  
  import org.apache.tools.ant.taskdefs.Delete;
  import org.apache.tools.ant.taskdefs.Execute;
  
  /**
   * Creates, Records and Restores Symlinks.
   *
   * <p> This task performs several related operations. In the most trivial,
   * and default usage, it creates a link specified in the link atribute to
   * a resource specified in the resource atribute. The second usage of this
   * task is to traverses 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 file or directory it points
   * to.
   *
   * <p> Examples of use:
   *
   * <p> Make a link named "foo" to a resource named "bar.foo" in subdir:
   * <pre>
   * &lt;symlink link="${dir.top}/foo" resource="${dir.top}/subdir/bar.foo"/&gt;
   * </pre>
   *
   * <p> Record all links in subdir and it's descendants in files named
   * "dir.links"
   * <pre>
   * &lt;symlink action="record" linkfilename="dir.links"&gt;
   *    &lt;fileset dir="${dir.top}" includes="subdir&#47;**" /&gt;
   * &lt;/symlink&gt;
   * </pre>
   *
   * <p> Recreate the links recorded in the previous example:
   * <pre>
   * &lt;symlink action="recreate"&gt;
   *    &lt;fileset dir="${dir.top}" includes="subdir&#47;**&#47;dir.links" /&gt;
   * &lt;/symlink&gt;
   * </pre>
   *
   * <p> Delete a link named "foo" to a resource named "bar.foo" in subdir:
   * <pre>
   * &lt;symlink action="delete" link="${dir.top}/foo"/&gt;
   * </pre>
   *
   * <p><strong>LIMITATIONS:</strong> Because Java has no direct support for 
   * handling symlinks this task divines them by comparing canoniacal and
   * absolute paths. On non-unix systems this may cause false positives. 
   * Furthermore, any operating system on which the command 
   * <code>ln -s link resource</code> is not a valid command on the comandline 
   * will not be able to use action="single" or action="multi" action="record"
   * should still work. Finally, the lack of support for symlinks in Java
   * means that all links are recorded as links to the 
   * <strong>canonical</strong> resource name. Therefore the link:
   * <code>link --> subdir/dir/../foo.bar</code> will be recorded as
   * <code>link=subdir/foo.bar</code> and restored as 
   * <code>link --> subdir/foo.bar</code>
   *
   * @version $Revision: 1.1 $
   * @author  <a href="mailto:gus.heck@olin.edu">Patrick G. Heck</a> 
   */
  
  public class Symlink extends Task {
  
      // Atributes with setter methods
      private String resource;
      private String link;
      private String action;
      private Vector fileSets = new Vector();
      private String linkFileName;
      private boolean overwrite;
      private boolean failonerror;
  
      /** Initialize the task. */
  
      public void init() throws BuildException
      {
          super.init();
          failonerror = true;   // default behavior is to fail on an error
          overwrite = false;    // devault behavior is to not overwrite
          action = "single";      // default behavior is make a single link
          fileSets = new Vector();
      }
  
      /** The standard method for executing any task. */
  
      public void execute() throws BuildException {
          try {
              if (action.equals("single")) {
                  doLink(resource, link);
              } else if (action.equals("delete")) {
                  try {
                      log("Removing symlink: " + link);
                      Symlink.deleteSymlink(link);
                  } catch (FileNotFoundException fnfe) {
                      handleError(fnfe.toString());
                  } catch (IOException ioe) {
                      handleError(ioe.toString());
                  }
              } else if (action.equals("recreate")) { 
                  Properties listOfLinks;
                  Enumeration keys;
  
                  if (fileSets.size() == 0){
                      handleError("File set identifying link file(s) " +
                                  "required for action recreate");
                      return;
                  }
  
                  listOfLinks = loadLinks(fileSets);
                  
                  keys = listOfLinks.keys();
                  
                  while(keys.hasMoreElements()) {
                      link = (String) keys.nextElement();
                      resource = listOfLinks.getProperty(link);
                      doLink(resource, link);
                  } 
              } else if (action.equals("record")) {
                  Vector vectOfLinks;
                  Hashtable byDir = new Hashtable();
                  Enumeration links, dirs;
              
              
                  if (fileSets.size() == 0) {
                      handleError("File set identifying links to " +
                                  "record required");
                      return;
                  }
  
                  if (linkFileName == null) {
                      handleError("Name of file to record links in " +
                                  "required");
                      return;
                  }
              
                  // fill our vector with file objects representing
                  // links (canonical)
                  vectOfLinks = findLinks(fileSets);
                  
                      // create a hashtable to group them by parent directory
                  links = vectOfLinks.elements();
                  while (links.hasMoreElements()) {
                      File thisLink = (File) links.nextElement();
                      String parent = thisLink.getParent();
                      if (byDir.containsKey(parent)) {
                          ((Vector) byDir.get(parent)).addElement(thisLink);
                      } else {
                          byDir.put(parent, new Vector());
                          ((Vector) byDir.get(parent)).addElement(thisLink);
                      }
                  }
                  
                  // write a Properties file in each directory
                  dirs = byDir.keys();
                  while (dirs.hasMoreElements()) {
                      String dir = (String) dirs.nextElement();
                      Vector linksInDir = (Vector) byDir.get(dir);
                      Properties linksToStore = new Properties();
                      Enumeration eachlink = linksInDir.elements();
                      File writeTo;
                      
                      // fill up a Properties object with link and resource
                      // names
                      while(eachlink.hasMoreElements()) {
                          File alink = (File) eachlink.nextElement();
                          try {
                              linksToStore.put(alink.getName(),
                                               alink.getCanonicalPath());
                          } catch (IOException ioe) {
                              handleError("Couldn't get canonical "+
                                          "name of a parent link");
                          }
                      }
                      
  
                      // Get a place to record what we are about to write
                      writeTo = new File(dir + File.separator + 
                                         linkFileName);
  
                      writePropertyFile(linksToStore, writeTo,
                                        "Symlinks from " + writeTo.getParent());
  
                  }
  
              }
              else {
                  handleError("Invalid action specified in symlink");
              }
          } finally {
              // return all variables to their default state,
              // ready for the next invocation.
              resource = null;
              link = null;
              action = "single";
              fileSets = new Vector();
              linkFileName = null;
              overwrite = false;
              failonerror = true;
          }
      }
  
      /* ********************************************************** *
        *              Begin Atribute Setter Methods               *
       * ********************************************************** */
  
      /**
       * The setter for the overwrite atribute. 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.
       */
      public void setOverwrite(boolean owrite) {
          this.overwrite = owrite;
      }
  
      /**
       * The setter for the failonerror atribute. If set to true (default)
       * the entire build fails upon error. If set to false, the error is
       * logged and the build will continue.
       *
       * @param foe    If true throw build exception on error else log it.
       */
      public void setFailOnError(boolean foe) {
          this.failonerror = foe;
      }
  
  
      /**
       * The setter for the "action" attribute. May be "single" "multi"
       * or "record"
       *
       * @param typ    The action of action to perform
       */
      public void setAction(String typ) {
          this.action = typ;
      }
     
      /**
       * The setter for the "link" attribute. Only used for action = single.
       * 
       * @param lnk     The name for the link
       */
      public void setLink(String lnk) {
          this.link = lnk;
      }
  
      /**
       * The setter for the "resource" attribute. Only used for action = single.
       *
       * @param src      The source of the resource to be linked.
       */
      public void setResource(String src) {
          this.resource = src;
      }
  
      /**
       * The setter for the "linkfilename" attribute. Only used for action=record.
       *
       * @param lf      The name of the file to write links to.
       */
      public void setLinkfilename(String lf) {
          this.linkFileName = lf;
      }
  
      /**
       * Adds a fileset to this task.
       *
       * @param set      The fileset to add.
       */
      public void addFileset(FileSet set) {
          fileSets.addElement(set);
      }
  
      /* ********************************************************** *
        *               Begin Public Utility Methods               *
       * ********************************************************** */
  
      /**
       * Deletes a symlink without deleteing the resource it points to.
       *
       * <p>This is a convenience method that simply invokes 
       * <code>deleteSymlink(java.io.File)</code>
       *
       * @param path    A string containing the path of the symlink to delete
       *
       * @throws FileNotFoundException   When the path results in a
       *                                   <code>File</code> that doesn't exist.
       * @throws IOException             If calls to <code>File.rename</code>
       *                                   or <code>File.delete</code> fail.
       */
      
      public static void deleteSymlink(String path) 
          throws IOException, FileNotFoundException {
  
          File linkfil = new File(path);
          deleteSymlink(linkfil);
      }
  
      /**
       * Deletes a symlink without deleteing the resource it points to.
       *
       * <p>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 it's original name inside a finally
       * block to ensure that the resource is unharmed even in the event of
       * an exception.
       *
       * @param linkfil    A <code>File</code> object of the symlink to delete
       *
       * @throws FileNotFoundException   When the path results in a
       *                                   <code>File</code> that doesn't exist.
       * @throws IOException             If calls to <code>File.rename</code>,
       *                                   <code>File.delete</code> or 
       *                                   <code>File.getCanonicalPath</code>
       *                                   fail.
       */
  
      public static void deleteSymlink(File linkfil) 
          throws IOException, FileNotFoundException {
  
          if (!linkfil.exists()) {
              throw new FileNotFoundException("No such symlink: " + linkfil);
          }
  
          // find the resource of the existing link
          String canstr = linkfil.getCanonicalPath();
          File canfil = new File(canstr);
          
          // rename the resource, thus breaking the link
          String parentStr = canfil.getParent();
          File parentDir = new File(parentStr);
          FileUtils fu = FileUtils.newFileUtils();
          File temp = fu.createTempFile("symlink",".tmp", parentDir);
          try {
              if (!canfil.renameTo(temp)) {
                  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 {
              File old = new File(canstr);
  
              // return the resource to its original name.
              if (!temp.renameTo(canfil)) {
                  throw new IOException("Couldn't return resource " + temp +
                                        " its original name: " + canstr +
                                        "\n THE RESOURCE'S NAME ON DISK HAS " +
                                        "BEEN CHANGED BY THIS ERROR!\n");
              }
          }
      }
  
  
      /* ********************************************************** *
        *                  Begin Private Methods                   *
       * ********************************************************** */
  
      /**
       * Writes a properties file.
       *
       * In jdk 1.2+ this method will use <code>Properties.store</code> 
       * and thus report exceptions that occur while writing the file.
       * In jdk 1.1 we are forced to use <code>Properties.save</code>
       * and therefore all exceptions are masked. This method was lifted
       * directly from the Proertyfile task with only slight editing.
       * sticking something like this in FileUtils might  be
       * a good idea to avoid duplication.
       *
       * @param properties     The properties object to be written.
       * @param propertyfile   The File to write to.
       * @param comment        The comment to place at the head of the file.
       */
  
      private void writePropertyFile(Properties properties,
                                     File propertyfile, 
                                     String comment) 
          throws BuildException {
  
          BufferedOutputStream bos = null;
          try {
              bos = new BufferedOutputStream(new FileOutputStream(propertyfile));
  
              // Properties.store is not available in JDK 1.1
              Method m =
                  Properties.class.getMethod("store",
                                             new Class[] {
                                                 OutputStream.class,
                                                 String.class});
              m.invoke(properties, new Object[] {bos, comment});
  
          } catch (NoSuchMethodException nsme) {
              properties.save(bos, comment);
          } catch (InvocationTargetException ite) {
              Throwable t = ite.getTargetException();
              throw new BuildException(t, location);
          } catch (IllegalAccessException iae) {
              // impossible
              throw new BuildException(iae, location);
          } catch (IOException ioe) {
              throw new BuildException(ioe, location);
          } finally {
              if (bos != null) {
                  try {
                      bos.close();
                  } catch (IOException ioex) {}
              }
          }
      }
  
      /**
       * Handles errors correctly based on the setting of failonerror.
       *
       * @param msg    The message to log, or include in the 
       *                  <code>BuildException</code>
       */
  
      private void handleError(String msg) {
          if (failonerror) {
              throw new BuildException(msg);
          } else {
              log(msg);
          }
      }
  
  
      /**
       * Conducts the actual construction of a link.
       *
       * <p> The link is constructed by calling <code>Execute.runCommand</code>.
       *
       * @param resource   The path of the resource we are linking to.
       * @param link       The name of the link we wish to make
       */
  
      private void doLink(String resource, String link) throws BuildException {
  
          if (resource == null) {
              handleError("Must define the resource to symlink to!");
              return;
          }
          if (link == null) {
              handleError("Must define the link " +
                          "name for symlink!");
              return;
          }
  
          File linkfil = new File(link);
  
          String[] cmd = new String[4];
          cmd[0] = "ln";
          cmd[1] = "-s";
          cmd[2] = resource;
          cmd[3] = link;
  
          try {
              if (overwrite && linkfil.exists()) {
                  deleteSymlink(linkfil);
              }
          } catch (FileNotFoundException fnfe) {
              handleError("Symlink dissapeared before it was deleted:" + link);
          } catch (IOException ioe) {
              handleError("Unable to overwrite preexisting link " + link);
          }
  
          log(cmd[0]+" "+cmd[1]+" "+cmd[2]+" "+cmd[3]);
          Execute.runCommand(this,cmd);
      }
  
  
      /**
       * Simultaneously get included directories and included files.
       *
       * @param ds   The scanner with which to get the files and directories.
       * @return     A vector of <code>String</code> objects containing the 
       *                included file names and directory names.
       */
  
      private Vector scanDirsAndFiles(DirectoryScanner ds) {
          String[] files, dirs;
          Vector list = new Vector();
  
          ds.scan();
  
          files = ds.getIncludedFiles();
          dirs = ds.getIncludedDirectories();
  
          for (int i=0; i<files.length; i++) {
              list.addElement(files[i]);
          }
          for (int i=0; i<dirs.length; i++) {
              list.addElement(dirs[i]);
          }
  
          return list;
      }
  
      /**
       * Finds all the links in all supplied filesets.
       *
       * <p> This method is invoked when the action atribute is is "record".
       * This means that filesets are interpreted as the directories in
       * which links may be found.
       *
       * <p> The basic method follwed here is, for each file set:
       *   <ol>
       *      <li> Compile a list of all matches </li>
       *      <li> Convert matches to <code>File</code> objects </li>
       *      <li> Remove all non-symlinks using 
       *             <code>FileUtils.isSymbolicLink</code> </li>
       *      <li> Convert all parent directories to the canonical form </li>
       *      <li> Add the remaining links from each file set to a 
       *             master list of links unless the link is already recorded
       *             in the list</li>
       *   </ol>
       *
       * @param fileSets   The filesets specified by the user.
       * @return           A vector of <code>File</code> objects containing the 
       *                     links (with canonical parent directories)
       */
  
      private Vector findLinks(Vector fileSets) {
          Vector result = new Vector();
  
          // loop through the supplied file sets
          FSLoop: for (int i=0; i<fileSets.size(); i++){
              FileSet fsTemp = (FileSet) fileSets.elementAt(i);
              String workingDir = null;
              Vector links = new Vector();
              Vector nolinks = new Vector();
              Vector linksFiles = new Vector();
              Enumeration enumNoLinks, enumLinks;
  
              DirectoryScanner ds;
  
              File tmpfil = null;
              try {
                  tmpfil = fsTemp.getDir(this.getProject());
                  workingDir = tmpfil.getCanonicalPath();
              } catch (IOException ioe) {
                  handleError("Exception caught getting "+
                              "canonical path of working dir " + tmpfil +
                              " in a FileSet passed to the symlink " +
                              "task. Further processing of this " +
                              "fileset skipped");
                  continue FSLoop;
              }
  
              // Get a vector of String with file names that match
              // the pattern
              ds = fsTemp.getDirectoryScanner(this.getProject());
              links = scanDirsAndFiles(ds);
  
              // Now convert the strings to File Objects
              // using the canonical version of the working dir
              enumLinks = links.elements();
              
              while(enumLinks.hasMoreElements()) {
                  linksFiles.addElement(new File(workingDir +
                      File.separator + (String) enumLinks.nextElement()));
              }
  
              // Now loop through and remove the non-links
  
              enumLinks = linksFiles.elements();
  
              File parentNext, next;
              String nameParentNext;
              FileUtils fu = FileUtils.newFileUtils();
              Vector removals = new Vector();
              while (enumLinks.hasMoreElements()) {
                  next = (File) enumLinks.nextElement();
                  nameParentNext = next.getParent();
  
                  parentNext= new File(nameParentNext);
                  try {
                      if (!fu.isSymbolicLink(parentNext, next.getName())) {
                          removals.addElement(next);
                      }
                  } catch (IOException ioe) {
                      handleError("Failed checking " + next +
                                  " for symbolic link. FileSet skipped.");
                      continue FSLoop;
                      // Otherwise this file will be falsely recorded as a link,
                      // if failonerror = false, hence the warn and skip.
                  }
              }
  
              enumLinks = removals.elements();
  
              while(enumLinks.hasMoreElements()) {
                  linksFiles.removeElement(enumLinks.nextElement());
              }
  
              // Now we have what we want so add it to results, ensuring that
              // no link is returned twice and we have a canonical reference
              // to the link. (no symlinks in the parent dir)
  
              enumLinks = linksFiles.elements();
              while(enumLinks.hasMoreElements()) {
                  File temp, parent;
                  next = (File) enumLinks.nextElement();
                  try{
                      parent = new File(next.getParent());
                      parent = new File(parent.getCanonicalPath());
                      temp = new File(parent, next.getName());
                      if (!result.contains(temp)) {
                          result.addElement(temp);
                      }
                  } catch (IOException ioe) {
                      handleError("IOException: " + next + " omitted");
                  }
              }
  
              // Note that these links are now specified with a full
              // canonical path irrespective of the working dir of the
              // file set so it is ok to mix them in the same vector.
  
          }
          return result;
      }
  
      /**
       * Load the links from a properties file.
       *
       * <p> This method is only invoked when the action atribute is set to 
       * "multi". 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.
       *
       * <p> The basic method follwed here is, for each file set:
       *   <ol>
       *      <li> Get the canonical version of the dir atribute </li>
       *      <li> Scan for properties files </li>
       *      <li> load the contents of each properties file found. </li>
       *   </ol>
       * 
       * @param fileSets    The <code>FileSet</code>s for this task
       * @return            The links to be made.
       */
  
      private Properties loadLinks(Vector fileSets) {
          Properties finalList = new Properties();
          Enumeration keys;
          String key, value;
          String[] includedFiles;
  
          // loop through the supplied file sets
          FSLoop: for (int i=0; i<fileSets.size(); i++){
              String workingDir;
              FileSet fsTemp = (FileSet) fileSets.elementAt(i);
              
              DirectoryScanner ds;
  
              try {
                  File linelength = fsTemp.getDir(this.getProject());
                  workingDir = linelength.getCanonicalPath();
              } catch (IOException ioe) {
                  handleError("Exception caught getting "+
                              "canonical path of working dir " +
                              "of a FileSet passed to symlink " +
                              "task. FileSet skipped.");
                  continue FSLoop;
              }
  
              ds = fsTemp.getDirectoryScanner(this.getProject());
              ds.setFollowSymlinks(false);
              ds.scan();
              includedFiles = ds.getIncludedFiles();
              
              // loop through the files identified by each file set
              // and load their contents.
              for (int j=0; j<includedFiles.length; j++){
                  File inc = new File(workingDir + File.separator +
                                      includedFiles[j]);
                  String inDir;
                  Properties propTemp = new Properties();
  
                  try {
                      propTemp.load(new FileInputStream(inc));
                      inDir = inc.getParent();
                      inDir = (new File(inDir)).getCanonicalPath();
                  } catch (FileNotFoundException fnfe) {
                      handleError("Unable to find " + includedFiles[j] +
                                  "FileSet skipped.");
                      continue FSLoop;
                  } catch (IOException ioe) {
                      handleError("Unable to open " + includedFiles[j] +
                                  " or it's parent dir" +
                                  "FileSet skipped.");
                      continue FSLoop;
                  }
                  
                  keys = propTemp.keys();
                  propTemp.list(System.out);
                  // 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
                  while(keys.hasMoreElements()) {
                      key = (String) keys.nextElement();
                      value = propTemp.getProperty(key);
                      finalList.put(inDir + File.separator + key, value);
                  }
              }
          }
          return finalList;
      }
  }
  
  
  
  1.1                  jakarta-ant/src/testcases/org/apache/tools/ant/taskdefs/optional/unix/SymlinkTest.java
  
  Index: SymlinkTest.java
  ===================================================================
  /*
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 2002 The Apache Software Foundation.  All rights
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   *
   * 3. The end-user documentation included with the redistribution, if
   *    any, must include the following acknowlegement:
   *       "This product includes software developed by the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowlegement may appear in the software itself,
   *    if and wherever such third-party acknowlegements normally appear.
   *
   * 4. The names "The Jakarta Project", "Ant", and "Apache Software
   *    Foundation" must not be used to endorse or promote products derived
   *    from this software without prior written permission. For written
   *    permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache"
   *    nor may "Apache" appear in their names without prior written
   *    permission of the Apache Group.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   */
  
  /*
   * Since the initial version of this file was deveolped on the clock on
   * an NSF grant I should say the following boilerplate:
   *
   * This material is based upon work supported by the National Science
   * Foundaton under Grant No. EIA-0196404. Any opinions, findings, and
   * conclusions or recommendations expressed in this material are those
   * of the author and do not necessarily reflect the views of the
   * National Science Foundation.
   */
  
  package org.apache.tools.ant.taskdefs.optional.unix;
  
  import org.apache.tools.ant.taskdefs.condition.Os;
  
  import org.apache.tools.ant.BuildFileTest;
  import org.apache.tools.ant.Project;
  
  /**
   * Test cases for the Symlink task. Link creation, link deletion, recording
   * of links in multiple directories, and restoration of links recorded are
   * all tested. A separate test for the utility method Symlink.deleteSymlink
   * is not included because action="delete" only prints a message and calls
   * Symlink.deleteSymlink, making a separate test redundant.
   *
   * @version $Revision: 1.1 $
   * @author <a href="mailto:gus.heck@olin.edu">Patrick G. Heck</a> 
   */
  
  public class SymlinkTest extends BuildFileTest {
  
      private Project p;
      private boolean supportsSymlinks = Os.isFamily("unix");
      private boolean testfail = false;
  
      public SymlinkTest(String name) {
          super(name);
      }
  
      public void setUp() {
          if (supportsSymlinks) {
              configureProject("src/etc/testcases/taskdefs/optional/symlink.xml");
              executeTarget("setup");
          }
      }
  
  
      public void testSingle() {
          testfail = true;
          if (supportsSymlinks) {
              executeTarget("test-single");
              p = getProject();
              assertNotNull("Failed to create file", 
                            p.getProperty("test.single.file.created"));
              assertNotNull("Failed to create link",
                            p.getProperty("test.single.link.created"));
          }
          testfail = false;
      }
  
      public void testDelete() {
          testfail = true;
          if (supportsSymlinks) {
              executeTarget("test-delete");
              p = getProject();
              String linkDeleted = p.getProperty("test.delete.link.still.there");
              assertNotNull("Actual file deleted by symlink",
                            p.getProperty("test.delete.file.still.there"));
              if (linkDeleted != null) {
                  fail(linkDeleted);
              }
          }
          testfail = false;
      }
  
      public void testRecord() {
          testfail = true;
          if (supportsSymlinks) {
              executeTarget("test-record");
              p = getProject();
  
              assertNotNull("Failed to create dir1",
                            p.getProperty("test.record.dir1.created"));
  
              assertNotNull("Failed to create dir2",
                            p.getProperty("test.record.dir2.created"));
  
              assertNotNull("Failed to create file1",
                            p.getProperty("test.record.file1.created"));
  
              assertNotNull("Failed to create file2",
                            p.getProperty("test.record.file2.created"));
  
              assertNotNull("Failed to create fileA",
                            p.getProperty("test.record.fileA.created"));
  
              assertNotNull("Failed to create fileB",
                            p.getProperty("test.record.fileB.created"));
  
              assertNotNull("Failed to create fileC",
                            p.getProperty("test.record.fileC.created"));
  
              assertNotNull("Failed to create link1",
                            p.getProperty("test.record.link1.created"));
  
              assertNotNull("Failed to create link2",
                            p.getProperty("test.record.link2.created"));
  
              assertNotNull("Failed to create link3",
                            p.getProperty("test.record.link3.created"));
  
              assertNotNull("Failed to create dirlink",
                            p.getProperty("test.record.dirlink.created"));
  
              assertNotNull("Couldn't record links in dir1",
                            p.getProperty("test.record.dir1.recorded"));
  
              assertNotNull("Couldn't record links in dir2",
                            p.getProperty("test.record.dir2.recorded"));
  
              String dir3rec = p.getProperty("test.record.dir3.recorded");
  
              if (dir3rec != null) {
                  fail(dir3rec);
              }
  
          }
          testfail = false;
      }
  
      public void testRecreate() {
          testfail = true;
          if (supportsSymlinks) {
              executeTarget("test-recreate");
              p = getProject();
              String link1Rem = p.getProperty("test.recreate.link1.not.removed");
              String link2Rem = p.getProperty("test.recreate.link2.not.removed");
              String link3Rem = p.getProperty("test.recreate.link3.not.removed");
              String dirlinkRem = p.getProperty("test.recreate.dirlink.not.removed");
              if (link1Rem != null) {
                  fail(link1Rem);
              }
              if (link2Rem != null) {
                  fail(link2Rem);
              }
              if (link3Rem != null) {
                  fail(link3Rem);
              }
              if (dirlinkRem != null) {
                  fail(dirlinkRem);
              }
              assertNotNull("Failed to recreate link1",
                            p.getProperty("test.recreate.link1.recreated"));
              assertNotNull("Failed to recreate link2",
                            p.getProperty("test.recreate.link2.recreated"));
              assertNotNull("Failed to recreate link3",
                            p.getProperty("test.recreate.link3.recreated"));
              assertNotNull("Failed to recreate dirlink",
                            p.getProperty("test.recreate.dirlink.recreated"));
          }
          testfail = false;
      }
  
      public void tearDown() {
          if (supportsSymlinks && !testfail) {
              executeTarget("teardown");
          }
      }
  
  }
  
  
  

--
To unsubscribe, e-mail:   <ma...@jakarta.apache.org>
For additional commands, e-mail: <ma...@jakarta.apache.org>