Clover coverage report - Ant Coverage
Coverage timestamp: Tue Apr 8 2003 20:43:55 EST
file stats: LOC: 812   Methods: 17
NCLOC: 387   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
Symlink.java 80.3% 83.2% 94.1% 83.2%
 1   
 /*
 2   
  * The Apache Software License, Version 1.1
 3   
  *
 4   
  * Copyright (c) 2002 The Apache Software Foundation.  All rights
 5   
  * reserved.
 6   
  *
 7   
  * Redistribution and use in source and binary forms, with or without
 8   
  * modification, are permitted provided that the following conditions
 9   
  * are met:
 10   
  *
 11   
  * 1. Redistributions of source code must retain the above copyright
 12   
  *    notice, this list of conditions and the following disclaimer.
 13   
  *
 14   
  * 2. Redistributions in binary form must reproduce the above copyright
 15   
  *    notice, this list of conditions and the following disclaimer in
 16   
  *    the documentation and/or other materials provided with the
 17   
  *    distribution.
 18   
  *
 19   
  * 3. The end-user documentation included with the redistribution, if
 20   
  *    any, must include the following acknowlegement:
 21   
  *       "This product includes software developed by the
 22   
  *        Apache Software Foundation (http://www.apache.org/)."
 23   
  *    Alternately, this acknowlegement may appear in the software itself,
 24   
  *    if and wherever such third-party acknowlegements normally appear.
 25   
  *
 26   
  * 4. The names "Ant" and "Apache Software
 27   
  *    Foundation" must not be used to endorse or promote products derived
 28   
  *    from this software without prior written permission. For written
 29   
  *    permission, please contact apache@apache.org.
 30   
  *
 31   
  * 5. Products derived from this software may not be called "Apache"
 32   
  *    nor may "Apache" appear in their names without prior written
 33   
  *    permission of the Apache Group.
 34   
  *
 35   
  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 36   
  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 37   
  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 38   
  * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 39   
  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 40   
  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 41   
  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 42   
  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 43   
  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 44   
  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 45   
  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 46   
  * SUCH DAMAGE.
 47   
  * ====================================================================
 48   
  *
 49   
  * This software consists of voluntary contributions made by many
 50   
  * individuals on behalf of the Apache Software Foundation.  For more
 51   
  * information on the Apache Software Foundation, please see
 52   
  * <http://www.apache.org/>.
 53   
  */
 54   
 
 55   
 /*
 56   
  * Since the initial version of this file was deveolped on the clock on
 57   
  * an NSF grant I should say the following boilerplate:
 58   
  *
 59   
  * This material is based upon work supported by the National Science
 60   
  * Foundaton under Grant No. EIA-0196404. Any opinions, findings, and
 61   
  * conclusions or recommendations expressed in this material are those
 62   
  * of the author and do not necessarily reflect the views of the
 63   
  * National Science Foundation.
 64   
  */
 65   
 
 66   
 package org.apache.tools.ant.taskdefs.optional.unix;
 67   
 
 68   
 import java.io.File;
 69   
 import java.io.FileWriter;
 70   
 import java.io.IOException;
 71   
 import java.io.StringWriter;
 72   
 import java.io.OutputStream;
 73   
 import java.io.FileInputStream;
 74   
 import java.io.FileOutputStream;
 75   
 import java.io.BufferedOutputStream;
 76   
 import java.io.FileNotFoundException;
 77   
 
 78   
 import java.util.Vector;
 79   
 import java.util.Properties;
 80   
 import java.util.Enumeration;
 81   
 import java.util.Hashtable;
 82   
 
 83   
 import java.lang.reflect.InvocationTargetException;
 84   
 import java.lang.reflect.Method;
 85   
 
 86   
 import org.apache.tools.ant.Task;
 87   
 import org.apache.tools.ant.BuildException;
 88   
 import org.apache.tools.ant.DirectoryScanner;
 89   
 
 90   
 import org.apache.tools.ant.util.FileUtils;
 91   
 
 92   
 import org.apache.tools.ant.types.FileSet;
 93   
 
 94   
 import org.apache.tools.ant.taskdefs.Delete;
 95   
 import org.apache.tools.ant.taskdefs.Execute;
 96   
 
 97   
 /**
 98   
  * Creates, Records and Restores Symlinks.
 99   
  *
 100   
  * <p> This task performs several related operations. In the most trivial,
 101   
  * and default usage, it creates a link specified in the link atribute to
 102   
  * a resource specified in the resource atribute. The second usage of this
 103   
  * task is to traverses a directory structure specified by a fileset, 
 104   
  * and write a properties file in each included directory describing the
 105   
  * links found in that directory. The third usage is to traverse a
 106   
  * directory structure specified by a fileset, looking for properties files
 107   
  * (also specified as included in the fileset) and recreate the links 
 108   
  * that have been previously recorded for each directory. Finally, it can be
 109   
  * used to remove a symlink without deleting the file or directory it points
 110   
  * to.
 111   
  *
 112   
  * <p> Examples of use:
 113   
  *
 114   
  * <p> Make a link named "foo" to a resource named "bar.foo" in subdir:
 115   
  * <pre>
 116   
  * &lt;symlink link="${dir.top}/foo" resource="${dir.top}/subdir/bar.foo"/&gt;
 117   
  * </pre>
 118   
  *
 119   
  * <p> Record all links in subdir and it's descendants in files named
 120   
  * "dir.links"
 121   
  * <pre>
 122   
  * &lt;symlink action="record" linkfilename="dir.links"&gt;
 123   
  *    &lt;fileset dir="${dir.top}" includes="subdir&#47;**" /&gt;
 124   
  * &lt;/symlink&gt;
 125   
  * </pre>
 126   
  *
 127   
  * <p> Recreate the links recorded in the previous example:
 128   
  * <pre>
 129   
  * &lt;symlink action="recreate"&gt;
 130   
  *    &lt;fileset dir="${dir.top}" includes="subdir&#47;**&#47;dir.links" /&gt;
 131   
  * &lt;/symlink&gt;
 132   
  * </pre>
 133   
  *
 134   
  * <p> Delete a link named "foo" to a resource named "bar.foo" in subdir:
 135   
  * <pre>
 136   
  * &lt;symlink action="delete" link="${dir.top}/foo"/&gt;
 137   
  * </pre>
 138   
  *
 139   
  * <p><strong>LIMITATIONS:</strong> Because Java has no direct support for 
 140   
  * handling symlinks this task divines them by comparing canoniacal and
 141   
  * absolute paths. On non-unix systems this may cause false positives. 
 142   
  * Furthermore, any operating system on which the command 
 143   
  * <code>ln -s link resource</code> is not a valid command on the comandline 
 144   
  * will not be able to use action= "delete", action="single" or 
 145   
  * action="recreate", but action="record" should still work. Finally, the 
 146   
  * lack of support for symlinks in Java means that all links are recorded 
 147   
  * as links to the <strong>canonical</strong> resource name. Therefore 
 148   
  * the link: <code>link --> subdir/dir/../foo.bar</code> will be recorded 
 149   
  * as <code>link=subdir/foo.bar</code> and restored as 
 150   
  * <code>link --> subdir/foo.bar</code>
 151   
  *
 152   
  * @version $Revision: 1.4 $
 153   
  * @author  <a href="mailto:gus.heck@olin.edu">Patrick G. Heck</a> 
 154   
  */
 155   
 
 156   
 public class Symlink extends Task {
 157   
 
 158   
     // Atributes with setter methods
 159   
     private String resource;
 160   
     private String link;
 161   
     private String action;
 162   
     private Vector fileSets = new Vector();
 163   
     private String linkFileName;
 164   
     private boolean overwrite;
 165   
     private boolean failonerror;
 166   
 
 167   
     /** Initialize the task. */
 168   
 
 169  19
     public void init() throws BuildException
 170   
     {
 171  19
         super.init();
 172  19
         failonerror = true;   // default behavior is to fail on an error
 173  19
         overwrite = false;    // devault behavior is to not overwrite
 174  19
         action = "single";      // default behavior is make a single link
 175  19
         fileSets = new Vector();
 176   
     }
 177   
 
 178   
     /** The standard method for executing any task. */
 179   
 
 180  19
     public void execute() throws BuildException {
 181  19
         try {
 182  19
             if (action.equals("single")) {
 183  10
                 doLink(resource, link);
 184  9
             } else if (action.equals("delete")) {
 185  6
                 try {
 186  6
                     log("Removing symlink: " + link);
 187  6
                     Symlink.deleteSymlink(link);
 188   
                 } catch (FileNotFoundException fnfe) {
 189  0
                     handleError(fnfe.toString());
 190   
                 } catch (IOException ioe) {
 191  1
                     handleError(ioe.toString());
 192   
                 }
 193  3
             } else if (action.equals("recreate")) { 
 194  1
                 Properties listOfLinks;
 195  1
                 Enumeration keys;
 196   
 
 197  1
                 if (fileSets.size() == 0){
 198  0
                     handleError("File set identifying link file(s) " +
 199   
                                 "required for action recreate");
 200  0
                     return;
 201   
                 }
 202   
 
 203  1
                 listOfLinks = loadLinks(fileSets);
 204   
                 
 205  1
                 keys = listOfLinks.keys();
 206   
                 
 207  1
                 while(keys.hasMoreElements()) {
 208  4
                     link = (String) keys.nextElement();
 209  4
                     resource = listOfLinks.getProperty(link);
 210  4
                     doLink(resource, link);
 211   
                 } 
 212  2
             } else if (action.equals("record")) {
 213  2
                 Vector vectOfLinks;
 214  2
                 Hashtable byDir = new Hashtable();
 215  2
                 Enumeration links, dirs;
 216   
             
 217   
             
 218  2
                 if (fileSets.size() == 0) {
 219  0
                     handleError("File set identifying links to " +
 220   
                                 "record required");
 221  0
                     return;
 222   
                 }
 223   
 
 224  2
                 if (linkFileName == null) {
 225  0
                     handleError("Name of file to record links in " +
 226   
                                 "required");
 227  0
                     return;
 228   
                 }
 229   
             
 230   
                 // fill our vector with file objects representing
 231   
                 // links (canonical)
 232  2
                 vectOfLinks = findLinks(fileSets);
 233   
                 
 234   
                     // create a hashtable to group them by parent directory
 235  2
                 links = vectOfLinks.elements();
 236  2
                 while (links.hasMoreElements()) {
 237  8
                     File thisLink = (File) links.nextElement();
 238  8
                     String parent = thisLink.getParent();
 239  8
                     if (byDir.containsKey(parent)) {
 240  4
                         ((Vector) byDir.get(parent)).addElement(thisLink);
 241   
                     } else {
 242  4
                         byDir.put(parent, new Vector());
 243  4
                         ((Vector) byDir.get(parent)).addElement(thisLink);
 244   
                     }
 245   
                 }
 246   
                 
 247   
                 // write a Properties file in each directory
 248  2
                 dirs = byDir.keys();
 249  2
                 while (dirs.hasMoreElements()) {
 250  4
                     String dir = (String) dirs.nextElement();
 251  4
                     Vector linksInDir = (Vector) byDir.get(dir);
 252  4
                     Properties linksToStore = new Properties();
 253  4
                     Enumeration eachlink = linksInDir.elements();
 254  4
                     File writeTo;
 255   
                     
 256   
                     // fill up a Properties object with link and resource
 257   
                     // names
 258  4
                     while(eachlink.hasMoreElements()) {
 259  8
                         File alink = (File) eachlink.nextElement();
 260  8
                         try {
 261  8
                             linksToStore.put(alink.getName(),
 262   
                                              alink.getCanonicalPath());
 263   
                         } catch (IOException ioe) {
 264  0
                             handleError("Couldn't get canonical "+
 265   
                                         "name of a parent link");
 266   
                         }
 267   
                     }
 268   
                     
 269   
 
 270   
                     // Get a place to record what we are about to write
 271  4
                     writeTo = new File(dir + File.separator + 
 272   
                                        linkFileName);
 273   
 
 274  4
                     writePropertyFile(linksToStore, writeTo,
 275   
                                       "Symlinks from " + writeTo.getParent());
 276   
 
 277   
                 }
 278   
 
 279   
             }
 280   
             else {
 281  0
                 handleError("Invalid action specified in symlink");
 282   
             }
 283   
         } finally {
 284   
             // return all variables to their default state,
 285   
             // ready for the next invocation.
 286  19
             resource = null;
 287  19
             link = null;
 288  19
             action = "single";
 289  19
             fileSets = new Vector();
 290  19
             linkFileName = null;
 291  19
             overwrite = false;
 292  19
             failonerror = true;
 293   
         }
 294   
     }
 295   
 
 296   
     /* ********************************************************** *
 297   
       *              Begin Atribute Setter Methods               *
 298   
      * ********************************************************** */
 299   
 
 300   
     /**
 301   
      * The setter for the overwrite atribute. If set to false (default)
 302   
      * the task will not overwrite existing links, and may stop the build
 303   
      * if a link already exists depending on the setting of failonerror.
 304   
      *
 305   
      * @param owrite If true overwrite existing links.
 306   
      */
 307  0
     public void setOverwrite(boolean owrite) {
 308  0
         this.overwrite = owrite;
 309   
     }
 310   
 
 311   
     /**
 312   
      * The setter for the failonerror atribute. If set to true (default)
 313   
      * the entire build fails upon error. If set to false, the error is
 314   
      * logged and the build will continue.
 315   
      *
 316   
      * @param foe    If true throw build exception on error else log it.
 317   
      */
 318  12
     public void setFailOnError(boolean foe) {
 319  12
         this.failonerror = foe;
 320   
     }
 321   
 
 322   
 
 323   
     /**
 324   
      * The setter for the "action" attribute. May be "single" "multi"
 325   
      * or "record"
 326   
      *
 327   
      * @param typ    The action of action to perform
 328   
      */
 329  9
     public void setAction(String typ) {
 330  9
         this.action = typ;
 331   
     }
 332   
    
 333   
     /**
 334   
      * The setter for the "link" attribute. Only used for action = single.
 335   
      * 
 336   
      * @param lnk     The name for the link
 337   
      */
 338  16
     public void setLink(String lnk) {
 339  16
         this.link = lnk;
 340   
     }
 341   
 
 342   
     /**
 343   
      * The setter for the "resource" attribute. Only used for action = single.
 344   
      *
 345   
      * @param src      The source of the resource to be linked.
 346   
      */
 347  10
     public void setResource(String src) {
 348  10
         this.resource = src;
 349   
     }
 350   
 
 351   
     /**
 352   
      * The setter for the "linkfilename" attribute. Only used for action=record.
 353   
      *
 354   
      * @param lf      The name of the file to write links to.
 355   
      */
 356  2
     public void setLinkfilename(String lf) {
 357  2
         this.linkFileName = lf;
 358   
     }
 359   
 
 360   
     /**
 361   
      * Adds a fileset to this task.
 362   
      *
 363   
      * @param set      The fileset to add.
 364   
      */
 365  3
     public void addFileset(FileSet set) {
 366  3
         fileSets.addElement(set);
 367   
     }
 368   
 
 369   
     /* ********************************************************** *
 370   
       *               Begin Public Utility Methods               *
 371   
      * ********************************************************** */
 372   
 
 373   
     /**
 374   
      * Deletes a symlink without deleteing the resource it points to.
 375   
      *
 376   
      * <p>This is a convenience method that simply invokes 
 377   
      * <code>deleteSymlink(java.io.File)</code>
 378   
      *
 379   
      * @param path    A string containing the path of the symlink to delete
 380   
      *
 381   
      * @throws FileNotFoundException   When the path results in a
 382   
      *                                   <code>File</code> that doesn't exist.
 383   
      * @throws IOException             If calls to <code>File.rename</code>
 384   
      *                                   or <code>File.delete</code> fail.
 385   
      */
 386   
     
 387  6
     public static void deleteSymlink(String path) 
 388   
         throws IOException, FileNotFoundException {
 389   
 
 390  6
         File linkfil = new File(path);
 391  6
         deleteSymlink(linkfil);
 392   
     }
 393   
 
 394   
     /**
 395   
      * Deletes a symlink without deleteing the resource it points to.
 396   
      *
 397   
      * <p>This is a utility method that removes a unix symlink without removing
 398   
      * the resource that the symlink points to. If it is accidentally invoked
 399   
      * on a real file, the real file will not be harmed, but an exception
 400   
      * will be thrown when the deletion is attempted. This method works by
 401   
      * getting the canonical path of the link, using the canonical path to 
 402   
      * rename the resource (breaking the link) and then deleting the link. 
 403   
      * The resource is then returned to it's original name inside a finally
 404   
      * block to ensure that the resource is unharmed even in the event of
 405   
      * an exception.
 406   
      *
 407   
      * @param linkfil    A <code>File</code> object of the symlink to delete
 408   
      *
 409   
      * @throws FileNotFoundException   When the path results in a
 410   
      *                                   <code>File</code> that doesn't exist.
 411   
      * @throws IOException             If calls to <code>File.rename</code>,
 412   
      *                                   <code>File.delete</code> or 
 413   
      *                                   <code>File.getCanonicalPath</code>
 414   
      *                                   fail.
 415   
      */
 416   
 
 417  6
     public static void deleteSymlink(File linkfil) 
 418   
         throws IOException, FileNotFoundException {
 419   
 
 420  6
         if (!linkfil.exists()) {
 421  0
             throw new FileNotFoundException("No such symlink: " + linkfil);
 422   
         }
 423   
 
 424   
         // find the resource of the existing link
 425  6
         String canstr = linkfil.getCanonicalPath();
 426  6
         File canfil = new File(canstr);
 427   
         
 428   
         // rename the resource, thus breaking the link
 429  6
         String parentStr = canfil.getParent();
 430  6
         File parentDir = new File(parentStr);
 431  6
         FileUtils fu = FileUtils.newFileUtils();
 432  6
         File temp = fu.createTempFile("symlink",".tmp", parentDir);
 433  6
         try {
 434  6
             if (!canfil.renameTo(temp)) {
 435  0
                 throw new IOException("Couldn't rename resource when " +
 436   
                                       "attempting to delete " + linkfil);
 437   
             }
 438   
 
 439   
             // delete the (now) broken link
 440  6
             if(!linkfil.delete()) {
 441  1
                 throw new IOException("Couldn't delete symlink: " + linkfil +
 442   
                                       " (was it a real file? is this not a " +
 443   
                                       "UNIX system?)");
 444   
             }
 445   
         } finally {
 446   
             // return the resource to its original name.
 447  6
             if (!temp.renameTo(canfil)) {
 448  0
                 throw new IOException("Couldn't return resource " + temp +
 449   
                                       " its original name: " + canstr +
 450   
                                       "\n THE RESOURCE'S NAME ON DISK HAS " +
 451   
                                       "BEEN CHANGED BY THIS ERROR!\n");
 452   
             }
 453   
         }
 454   
     }
 455   
 
 456   
 
 457   
     /* ********************************************************** *
 458   
       *                  Begin Private Methods                   *
 459   
      * ********************************************************** */
 460   
 
 461   
     /**
 462   
      * Writes a properties file.
 463   
      *
 464   
      * In jdk 1.2+ this method will use <code>Properties.store</code> 
 465   
      * and thus report exceptions that occur while writing the file.
 466   
      * In jdk 1.1 we are forced to use <code>Properties.save</code>
 467   
      * and therefore all exceptions are masked. This method was lifted
 468   
      * directly from the Proertyfile task with only slight editing.
 469   
      * sticking something like this in FileUtils might  be
 470   
      * a good idea to avoid duplication.
 471   
      *
 472   
      * @param properties     The properties object to be written.
 473   
      * @param propertyfile   The File to write to.
 474   
      * @param comment        The comment to place at the head of the file.
 475   
      */
 476   
 
 477  4
     private void writePropertyFile(Properties properties,
 478   
                                    File propertyfile, 
 479   
                                    String comment) 
 480   
         throws BuildException {
 481   
 
 482  4
         BufferedOutputStream bos = null;
 483  4
         try {
 484  4
             bos = new BufferedOutputStream(new FileOutputStream(propertyfile));
 485   
 
 486   
             // Properties.store is not available in JDK 1.1
 487  4
             Method m =
 488   
                 Properties.class.getMethod("store",
 489   
                                            new Class[] {
 490   
                                                OutputStream.class,
 491   
                                                String.class});
 492  4
             m.invoke(properties, new Object[] {bos, comment});
 493   
 
 494   
         } catch (NoSuchMethodException nsme) {
 495  0
             properties.save(bos, comment);
 496   
         } catch (InvocationTargetException ite) {
 497  0
             Throwable t = ite.getTargetException();
 498  0
             throw new BuildException(t, location);
 499   
         } catch (IllegalAccessException iae) {
 500   
             // impossible
 501  0
             throw new BuildException(iae, location);
 502   
         } catch (IOException ioe) {
 503  0
             throw new BuildException(ioe, location);
 504   
         } finally {
 505  4
             if (bos != null) {
 506  4
                 try {
 507  4
                     bos.close();
 508   
                 } catch (IOException ioex) {}
 509   
             }
 510   
         }
 511   
     }
 512   
 
 513   
     /**
 514   
      * Handles errors correctly based on the setting of failonerror.
 515   
      *
 516   
      * @param msg    The message to log, or include in the 
 517   
      *                  <code>BuildException</code>
 518   
      */
 519   
 
 520  1
     private void handleError(String msg) {
 521  1
         if (failonerror) {
 522  0
             throw new BuildException(msg);
 523   
         } else {
 524  1
             log(msg);
 525   
         }
 526   
     }
 527   
 
 528   
 
 529   
     /**
 530   
      * Conducts the actual construction of a link.
 531   
      *
 532   
      * <p> The link is constructed by calling <code>Execute.runCommand</code>.
 533   
      *
 534   
      * @param resource   The path of the resource we are linking to.
 535   
      * @param link       The name of the link we wish to make
 536   
      */
 537   
 
 538  14
     private void doLink(String resource, String link) throws BuildException {
 539   
 
 540  14
         if (resource == null) {
 541  0
             handleError("Must define the resource to symlink to!");
 542  0
             return;
 543   
         }
 544  14
         if (link == null) {
 545  0
             handleError("Must define the link " +
 546   
                         "name for symlink!");
 547  0
             return;
 548   
         }
 549   
 
 550  14
         File linkfil = new File(link);
 551   
 
 552  14
         String[] cmd = new String[4];
 553  14
         cmd[0] = "ln";
 554  14
         cmd[1] = "-s";
 555  14
         cmd[2] = resource;
 556  14
         cmd[3] = link;
 557   
 
 558  14
         try {
 559  14
             if (overwrite && linkfil.exists()) {
 560  0
                 deleteSymlink(linkfil);
 561   
             }
 562   
         } catch (FileNotFoundException fnfe) {
 563  0
             handleError("Symlink dissapeared before it was deleted:" + link);
 564   
         } catch (IOException ioe) {
 565  0
             handleError("Unable to overwrite preexisting link " + link);
 566   
         }
 567   
 
 568  14
         log(cmd[0]+" "+cmd[1]+" "+cmd[2]+" "+cmd[3]);
 569  14
         Execute.runCommand(this,cmd);
 570   
     }
 571   
 
 572   
 
 573   
     /**
 574   
      * Simultaneously get included directories and included files.
 575   
      *
 576   
      * @param ds   The scanner with which to get the files and directories.
 577   
      * @return     A vector of <code>String</code> objects containing the 
 578   
      *                included file names and directory names.
 579   
      */
 580   
 
 581  2
     private Vector scanDirsAndFiles(DirectoryScanner ds) {
 582  2
         String[] files, dirs;
 583  2
         Vector list = new Vector();
 584   
 
 585  2
         ds.scan();
 586   
 
 587  2
         files = ds.getIncludedFiles();
 588  2
         dirs = ds.getIncludedDirectories();
 589   
 
 590  2
         for (int i=0; i<files.length; i++) {
 591  22
             list.addElement(files[i]);
 592   
         }
 593  2
         for (int i=0; i<dirs.length; i++) {
 594  8
             list.addElement(dirs[i]);
 595   
         }
 596   
 
 597  2
         return list;
 598   
     }
 599   
 
 600   
     /**
 601   
      * Finds all the links in all supplied filesets.
 602   
      *
 603   
      * <p> This method is invoked when the action atribute is is "record".
 604   
      * This means that filesets are interpreted as the directories in
 605   
      * which links may be found.
 606   
      *
 607   
      * <p> The basic method follwed here is, for each file set:
 608   
      *   <ol>
 609   
      *      <li> Compile a list of all matches </li>
 610   
      *      <li> Convert matches to <code>File</code> objects </li>
 611   
      *      <li> Remove all non-symlinks using 
 612   
      *             <code>FileUtils.isSymbolicLink</code> </li>
 613   
      *      <li> Convert all parent directories to the canonical form </li>
 614   
      *      <li> Add the remaining links from each file set to a 
 615   
      *             master list of links unless the link is already recorded
 616   
      *             in the list</li>
 617   
      *   </ol>
 618   
      *
 619   
      * @param fileSets   The filesets specified by the user.
 620   
      * @return           A vector of <code>File</code> objects containing the 
 621   
      *                     links (with canonical parent directories)
 622   
      */
 623   
 
 624  2
     private Vector findLinks(Vector fileSets) {
 625  2
         Vector result = new Vector();
 626   
 
 627   
         // loop through the supplied file sets
 628  2
         FSLoop: for (int i=0; i<fileSets.size(); i++){
 629  2
             FileSet fsTemp = (FileSet) fileSets.elementAt(i);
 630  2
             String workingDir = null;
 631  2
             Vector links = new Vector();
 632  2
             Vector linksFiles = new Vector();
 633  2
             Enumeration enumLinks;
 634   
 
 635  2
             DirectoryScanner ds;
 636   
 
 637  2
             File tmpfil = null;
 638  2
             try {
 639  2
                 tmpfil = fsTemp.getDir(this.getProject());
 640  2
                 workingDir = tmpfil.getCanonicalPath();
 641   
             } catch (IOException ioe) {
 642  0
                 handleError("Exception caught getting "+
 643   
                             "canonical path of working dir " + tmpfil +
 644   
                             " in a FileSet passed to the symlink " +
 645   
                             "task. Further processing of this " +
 646   
                             "fileset skipped");
 647  0
                 continue FSLoop;
 648   
             }
 649   
 
 650   
             // Get a vector of String with file names that match
 651   
             // the pattern
 652  2
             ds = fsTemp.getDirectoryScanner(this.getProject());
 653  2
             links = scanDirsAndFiles(ds);
 654   
 
 655   
             // Now convert the strings to File Objects
 656   
             // using the canonical version of the working dir
 657  2
             enumLinks = links.elements();
 658   
             
 659  2
             while(enumLinks.hasMoreElements()) {
 660  30
                 linksFiles.addElement(new File(workingDir +
 661   
                     File.separator + (String) enumLinks.nextElement()));
 662   
             }
 663   
 
 664   
             // Now loop through and remove the non-links
 665   
 
 666  2
             enumLinks = linksFiles.elements();
 667   
 
 668  2
             File parentNext, next;
 669  2
             String nameParentNext;
 670  2
             FileUtils fu = FileUtils.newFileUtils();
 671  2
             Vector removals = new Vector();
 672  2
             while (enumLinks.hasMoreElements()) {
 673  30
                 next = (File) enumLinks.nextElement();
 674  30
                 nameParentNext = next.getParent();
 675   
 
 676  30
                 parentNext= new File(nameParentNext);
 677  30
                 try {
 678  30
                     if (!fu.isSymbolicLink(parentNext, next.getName())) {
 679  22
                         removals.addElement(next);
 680   
                     }
 681   
                 } catch (IOException ioe) {
 682  0
                     handleError("Failed checking " + next +
 683   
                                 " for symbolic link. FileSet skipped.");
 684  0
                     continue FSLoop;
 685   
                     // Otherwise this file will be falsely recorded as a link,
 686   
                     // if failonerror = false, hence the warn and skip.
 687   
                 }
 688   
             }
 689   
 
 690  2
             enumLinks = removals.elements();
 691   
 
 692  2
             while(enumLinks.hasMoreElements()) {
 693  22
                 linksFiles.removeElement(enumLinks.nextElement());
 694   
             }
 695   
 
 696   
             // Now we have what we want so add it to results, ensuring that
 697   
             // no link is returned twice and we have a canonical reference
 698   
             // to the link. (no symlinks in the parent dir)
 699   
 
 700  2
             enumLinks = linksFiles.elements();
 701  2
             while(enumLinks.hasMoreElements()) {
 702  8
                 File temp, parent;
 703  8
                 next = (File) enumLinks.nextElement();
 704  8
                 try{
 705  8
                     parent = new File(next.getParent());
 706  8
                     parent = new File(parent.getCanonicalPath());
 707  8
                     temp = new File(parent, next.getName());
 708  8
                     if (!result.contains(temp)) {
 709  8
                         result.addElement(temp);
 710   
                     }
 711   
                 } catch (IOException ioe) {
 712  0
                     handleError("IOException: " + next + " omitted");
 713   
                 }
 714   
             }
 715   
 
 716   
             // Note that these links are now specified with a full
 717   
             // canonical path irrespective of the working dir of the
 718   
             // file set so it is ok to mix them in the same vector.
 719   
 
 720   
         }
 721  2
         return result;
 722   
     }
 723   
 
 724   
     /**
 725   
      * Load the links from a properties file.
 726   
      *
 727   
      * <p> This method is only invoked when the action atribute is set to 
 728   
      * "multi". The filesets passed in are assumed to specify the names
 729   
      * of the property files with the link information and the
 730   
      * subdirectories in which to look for them.
 731   
      *
 732   
      * <p> The basic method follwed here is, for each file set:
 733   
      *   <ol>
 734   
      *      <li> Get the canonical version of the dir atribute </li>
 735   
      *      <li> Scan for properties files </li>
 736   
      *      <li> load the contents of each properties file found. </li>
 737   
      *   </ol>
 738   
      * 
 739   
      * @param fileSets    The <code>FileSet</code>s for this task
 740   
      * @return            The links to be made.
 741   
      */
 742   
 
 743  1
     private Properties loadLinks(Vector fileSets) {
 744  1
         Properties finalList = new Properties();
 745  1
         Enumeration keys;
 746  1
         String key, value;
 747  1
         String[] includedFiles;
 748   
 
 749   
         // loop through the supplied file sets
 750  1
         FSLoop: for (int i=0; i<fileSets.size(); i++){
 751  1
             String workingDir;
 752  1
             FileSet fsTemp = (FileSet) fileSets.elementAt(i);
 753   
             
 754  1
             DirectoryScanner ds;
 755   
 
 756  1
             try {
 757  1
                 File linelength = fsTemp.getDir(this.getProject());
 758  1
                 workingDir = linelength.getCanonicalPath();
 759   
             } catch (IOException ioe) {
 760  0
                 handleError("Exception caught getting "+
 761   
                             "canonical path of working dir " +
 762   
                             "of a FileSet passed to symlink " +
 763   
                             "task. FileSet skipped.");
 764  0
                 continue FSLoop;
 765   
             }
 766   
 
 767  1
             ds = fsTemp.getDirectoryScanner(this.getProject());
 768  1
             ds.setFollowSymlinks(false);
 769  1
             ds.scan();
 770  1
             includedFiles = ds.getIncludedFiles();
 771   
             
 772   
             // loop through the files identified by each file set
 773   
             // and load their contents.
 774  1
             for (int j=0; j<includedFiles.length; j++){
 775  2
                 File inc = new File(workingDir + File.separator +
 776   
                                     includedFiles[j]);
 777  2
                 String inDir;
 778  2
                 Properties propTemp = new Properties();
 779   
 
 780  2
                 try {
 781  2
                     propTemp.load(new FileInputStream(inc));
 782  2
                     inDir = inc.getParent();
 783  2
                     inDir = (new File(inDir)).getCanonicalPath();
 784   
                 } catch (FileNotFoundException fnfe) {
 785  0
                     handleError("Unable to find " + includedFiles[j] +
 786   
                                 "FileSet skipped.");
 787  0
                     continue FSLoop;
 788   
                 } catch (IOException ioe) {
 789  0
                     handleError("Unable to open " + includedFiles[j] +
 790   
                                 " or it's parent dir" +
 791   
                                 "FileSet skipped.");
 792  0
                     continue FSLoop;
 793   
                 }
 794   
                 
 795  2
                 keys = propTemp.keys();
 796  2
                 propTemp.list(System.out);
 797   
                 // Write the contents to our master list of links
 798   
 
 799   
                 // This method assumes that all links are defined in
 800   
                 // terms of absolute paths, or paths relative to the
 801   
                 // working directory
 802  2
                 while(keys.hasMoreElements()) {
 803  4
                     key = (String) keys.nextElement();
 804  4
                     value = propTemp.getProperty(key);
 805  4
                     finalList.put(inDir + File.separator + key, value);
 806   
                 }
 807   
             }
 808   
         }
 809  1
         return finalList;
 810   
     }
 811   
 }
 812