KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > tools > ant > taskdefs > optional > unix > Symlink


1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements. See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License. You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  *
17  */

18
19 /*
20  * Since the initial version of this file was developed on the clock on
21  * an NSF grant I should say the following boilerplate:
22  *
23  * This material is based upon work supported by the National Science
24  * Foundaton under Grant No. EIA-0196404. Any opinions, findings, and
25  * conclusions or recommendations expressed in this material are those
26  * of the author and do not necessarily reflect the views of the
27  * National Science Foundation.
28  */

29
30 package org.apache.tools.ant.taskdefs.optional.unix;
31
32 import java.io.File JavaDoc;
33 import java.io.IOException JavaDoc;
34 import java.io.PrintStream JavaDoc;
35 import java.io.FileInputStream JavaDoc;
36 import java.io.FileOutputStream JavaDoc;
37 import java.io.BufferedInputStream JavaDoc;
38 import java.io.BufferedOutputStream JavaDoc;
39 import java.io.FileNotFoundException JavaDoc;
40
41 import java.util.Vector JavaDoc;
42 import java.util.HashSet JavaDoc;
43 import java.util.Iterator JavaDoc;
44 import java.util.Hashtable JavaDoc;
45 import java.util.Properties JavaDoc;
46
47 import org.apache.tools.ant.Project;
48 import org.apache.tools.ant.BuildException;
49 import org.apache.tools.ant.DirectoryScanner;
50 import org.apache.tools.ant.dispatch.DispatchTask;
51 import org.apache.tools.ant.dispatch.DispatchUtils;
52 import org.apache.tools.ant.taskdefs.Execute;
53 import org.apache.tools.ant.taskdefs.LogOutputStream;
54 import org.apache.tools.ant.types.FileSet;
55 import org.apache.tools.ant.types.Commandline;
56 import org.apache.tools.ant.util.FileUtils;
57
58 /**
59  * Creates, Deletes, Records and Restores Symlinks.
60  *
61  * <p> This task performs several related operations. In the most trivial
62  * and default usage, it creates a link specified in the link attribute to
63  * a resource specified in the resource attribute. The second usage of this
64  * task is to traverse a directory structure specified by a fileset,
65  * and write a properties file in each included directory describing the
66  * links found in that directory. The third usage is to traverse a
67  * directory structure specified by a fileset, looking for properties files
68  * (also specified as included in the fileset) and recreate the links
69  * that have been previously recorded for each directory. Finally, it can be
70  * used to remove a symlink without deleting the associated resource.
71  *
72  * <p> Usage examples:
73  *
74  * <p> Make a link named &quot;foo&quot; to a resource named
75  * &quot;bar.foo&quot; in subdir:
76  * <pre>
77  * &lt;symlink link=&quot;${dir.top}/foo&quot; resource=&quot;${dir.top}/subdir/bar.foo&quot;/&gt;
78  * </pre>
79  *
80  * <p> Record all links in subdir and its descendants in files named
81  * &quot;dir.links&quot;:
82  * <pre>
83  * &lt;symlink action=&quot;record&quot; linkfilename=&quot;dir.links&quot;&gt;
84  * &lt;fileset dir=&quot;${dir.top}&quot; includes=&quot;subdir&#47;**&quot; /&gt;
85  * &lt;/symlink&gt;
86  * </pre>
87  *
88  * <p> Recreate the links recorded in the previous example:
89  * <pre>
90  * &lt;symlink action=&quot;recreate&quot;&gt;
91  * &lt;fileset dir=&quot;${dir.top}&quot; includes=&quot;subdir&#47;**&#47;dir.links&quot; /&gt;
92  * &lt;/symlink&gt;
93  * </pre>
94  *
95  * <p> Delete a link named &quot;foo&quot; to a resource named
96  * &quot;bar.foo&quot; in subdir:
97  * <pre>
98  * &lt;symlink action=&quot;delete&quot; link=&quot;${dir.top}/foo&quot;/&gt;
99  * </pre>
100  *
101  * <p><strong>LIMITATIONS:</strong> Because Java has no direct support for
102  * handling symlinks this task divines them by comparing canonical and
103  * absolute paths. On non-unix systems this may cause false positives.
104  * Furthermore, any operating system on which the command
105  * <code>ln -s link resource</code> is not a valid command on the command line
106  * will not be able to use action=&quot;delete&quot;, action=&quot;single&quot;
107  * or action=&quot;recreate&quot;, but action=&quot;record&quot; should still
108  * work. Finally, the lack of support for symlinks in Java means that all links
109  * are recorded as links to the <strong>canonical</strong> resource name.
110  * Therefore the link: <code>link --> subdir/dir/../foo.bar</code> will be
111  * recorded as <code>link=subdir/foo.bar</code> and restored as
112  * <code>link --> subdir/foo.bar</code>.
113  *
114  */

115 public class Symlink extends DispatchTask {
116     private static final FileUtils FILE_UTILS = FileUtils.getFileUtils();
117
118     private String JavaDoc resource;
119     private String JavaDoc link;
120     private Vector JavaDoc fileSets = new Vector JavaDoc();
121     private String JavaDoc linkFileName;
122     private boolean overwrite;
123     private boolean failonerror;
124     private boolean executing = false;
125
126     /**
127      * Initialize the task.
128      * @throws BuildException on error.
129      */

130     public void init() throws BuildException {
131         super.init();
132         setDefaults();
133     }
134
135     /**
136      * The standard method for executing any task.
137      * @throws BuildException on error.
138      */

139     public synchronized void execute() throws BuildException {
140         if (executing) {
141             throw new BuildException(
142                 "Infinite recursion detected in Symlink.execute()");
143         }
144         try {
145             executing = true;
146             DispatchUtils.execute(this);
147         } finally {
148             executing = false;
149         }
150     }
151
152     /**
153      * Create a symlink.
154      * @throws BuildException on error.
155      * @since Ant 1.7
156      */

157     public void single() throws BuildException {
158         try {
159             if (resource == null) {
160                 handleError("Must define the resource to symlink to!");
161                 return;
162             }
163             if (link == null) {
164                 handleError("Must define the link name for symlink!");
165                 return;
166             }
167             doLink(resource, link);
168         } finally {
169             setDefaults();
170         }
171     }
172
173     /**
174      * Delete a symlink.
175      * @throws BuildException on error.
176      * @since Ant 1.7
177      */

178     public void delete() throws BuildException {
179         try {
180             if (link == null) {
181                 handleError("Must define the link name for symlink!");
182                 return;
183             }
184             log("Removing symlink: " + link);
185             deleteSymlink(link);
186         } catch (FileNotFoundException JavaDoc fnfe) {
187             handleError(fnfe.toString());
188         } catch (IOException JavaDoc ioe) {
189             handleError(ioe.toString());
190         } finally {
191             setDefaults();
192         }
193     }
194
195     /**
196      * Restore symlinks.
197      * @throws BuildException on error.
198      * @since Ant 1.7
199      */

200     public void recreate() throws BuildException {
201         try {
202             if (fileSets.isEmpty()) {
203                 handleError("File set identifying link file(s) "
204                             + "required for action recreate");
205                 return;
206             }
207             Properties JavaDoc links = loadLinks(fileSets);
208
209             for (Iterator JavaDoc kitr = links.keySet().iterator(); kitr.hasNext();) {
210                 String JavaDoc lnk = (String JavaDoc) kitr.next();
211                 String JavaDoc res = links.getProperty(lnk);
212                 // handle the case where lnk points to a directory (bug 25181)
213
try {
214                     File JavaDoc test = new File JavaDoc(lnk);
215                     if (!FILE_UTILS.isSymbolicLink(null, lnk)) {
216                         doLink(res, lnk);
217                     } else if (!test.getCanonicalPath().equals(
218                         new File JavaDoc(res).getCanonicalPath())) {
219                         deleteSymlink(lnk);
220                         doLink(res, lnk);
221                     } // else lnk exists, do nothing
222
} catch (IOException JavaDoc ioe) {
223                     handleError("IO exception while creating link");
224                 }
225             }
226         } finally {
227             setDefaults();
228         }
229     }
230
231     /**
232      * Record symlinks.
233      * @throws BuildException on error.
234      * @since Ant 1.7
235      */

236     public void record() throws BuildException {
237         try {
238             if (fileSets.isEmpty()) {
239                 handleError("Fileset identifying links to record required");
240                 return;
241             }
242             if (linkFileName == null) {
243                 handleError("Name of file to record links in required");
244                 return;
245             }
246             // create a hashtable to group them by parent directory:
247
Hashtable JavaDoc byDir = new Hashtable JavaDoc();
248
249             // get an Iterator of file objects representing links (canonical):
250
for (Iterator JavaDoc litr = findLinks(fileSets).iterator();
251                 litr.hasNext();) {
252                 File JavaDoc thisLink = (File JavaDoc) litr.next();
253                 File JavaDoc parent = thisLink.getParentFile();
254                 Vector JavaDoc v = (Vector JavaDoc) byDir.get(parent);
255                 if (v == null) {
256                     v = new Vector JavaDoc();
257                     byDir.put(parent, v);
258                 }
259                 v.addElement(thisLink);
260             }
261             // write a Properties file in each directory:
262
for (Iterator JavaDoc dirs = byDir.keySet().iterator(); dirs.hasNext();) {
263                 File JavaDoc dir = (File JavaDoc) dirs.next();
264                 Vector JavaDoc linksInDir = (Vector JavaDoc) byDir.get(dir);
265                 Properties JavaDoc linksToStore = new Properties JavaDoc();
266
267                 // fill up a Properties object with link and resource names:
268
for (Iterator JavaDoc dlnk = linksInDir.iterator(); dlnk.hasNext();) {
269                     File JavaDoc lnk = (File JavaDoc) dlnk.next();
270                     try {
271                         linksToStore.put(lnk.getName(), lnk.getCanonicalPath());
272                     } catch (IOException JavaDoc ioe) {
273                         handleError("Couldn't get canonical name of parent link");
274                     }
275                 }
276                 writePropertyFile(linksToStore, dir);
277             }
278         } finally {
279             setDefaults();
280         }
281     }
282
283     /**
284      * Return all variables to their default state for the next invocation.
285      * @since Ant 1.7
286      */

287     private void setDefaults() {
288         resource = null;
289         link = null;
290         linkFileName = null;
291         failonerror = true; // default behavior is to fail on an error
292
overwrite = false; // default behavior is to not overwrite
293
setAction("single"); // default behavior is make a single link
294
fileSets.clear();
295     }
296
297     /**
298      * Set overwrite mode. If set to false (default)
299      * the task will not overwrite existing links, and may stop the build
300      * if a link already exists depending on the setting of failonerror.
301      *
302      * @param owrite If true overwrite existing links.
303      */

304     public void setOverwrite(boolean owrite) {
305         this.overwrite = owrite;
306     }
307
308     /**
309      * Set failonerror mode. If set to true (default) the entire build fails
310      * upon error; otherwise the error is logged and the build will continue.
311      *
312      * @param foe If true throw BuildException on error, else log it.
313      */

314     public void setFailOnError(boolean foe) {
315         this.failonerror = foe;
316     }
317
318     /**
319      * Set the action to be performed. May be &quot;single&quot;,
320      * &quot;delete&quot;, &quot;recreate&quot; or &quot;record&quot;.
321      *
322      * @param action The action to perform.
323      */

324     public void setAction(String JavaDoc action) {
325         super.setAction(action);
326     }
327
328     /**
329      * Set the name of the link. Used when action = &quot;single&quot;.
330      *
331      * @param lnk The name for the link.
332      */

333     public void setLink(String JavaDoc lnk) {
334         this.link = lnk;
335     }
336
337     /**
338      * Set the name of the resource to which a link should be created.
339      * Used when action = &quot;single&quot;.
340      *
341      * @param src The resource to be linked.
342      */

343     public void setResource(String JavaDoc src) {
344         this.resource = src;
345     }
346
347     /**
348      * Set the name of the file to which links will be written.
349      * Used when action = &quot;record&quot;.
350      *
351      * @param lf The name of the file to write links to.
352      */

353     public void setLinkfilename(String JavaDoc lf) {
354         this.linkFileName = lf;
355     }
356
357     /**
358      * Add a fileset to this task.
359      *
360      * @param set The fileset to add.
361      */

362     public void addFileset(FileSet set) {
363         fileSets.addElement(set);
364     }
365
366     /**
367      * Delete a symlink (without deleting the associated resource).
368      *
369      * <p>This is a convenience method that simply invokes
370      * <code>deleteSymlink(java.io.File)</code>.
371      *
372      * @param path A string containing the path of the symlink to delete.
373      *
374      * @throws FileNotFoundException When the path results in a
375      * <code>File</code> that doesn't exist.
376      * @throws IOException If calls to <code>File.rename</code>
377      * or <code>File.delete</code> fail.
378      */

379     public static void deleteSymlink(String JavaDoc path)
380         throws IOException JavaDoc, FileNotFoundException JavaDoc {
381         deleteSymlink(new File JavaDoc(path));
382     }
383
384     /**
385      * Delete a symlink (without deleting the associated resource).
386      *
387      * <p>This is a utility method that removes a unix symlink without removing
388      * the resource that the symlink points to. If it is accidentally invoked
389      * on a real file, the real file will not be harmed, but an exception
390      * will be thrown when the deletion is attempted. This method works by
391      * getting the canonical path of the link, using the canonical path to
392      * rename the resource (breaking the link) and then deleting the link.
393      * The resource is then returned to its original name inside a finally
394      * block to ensure that the resource is unharmed even in the event of
395      * an exception.
396      *
397      * @param linkfil A <code>File</code> object of the symlink to delete.
398      *
399      * @throws FileNotFoundException When the path results in a
400      * <code>File</code> that doesn't exist.
401      * @throws IOException If calls to <code>File.rename</code>,
402      * <code>File.delete</code> or
403      * <code>File.getCanonicalPath</code>
404      * fail.
405      */

406     public static void deleteSymlink(File JavaDoc linkfil)
407         throws IOException JavaDoc, FileNotFoundException JavaDoc {
408         if (!linkfil.exists()) {
409             throw new FileNotFoundException JavaDoc("No such symlink: " + linkfil);
410         }
411         // find the resource of the existing link:
412
File JavaDoc canfil = linkfil.getCanonicalFile();
413
414         // rename the resource, thus breaking the link:
415
File JavaDoc temp = FILE_UTILS.createTempFile("symlink", ".tmp",
416                                               canfil.getParentFile());
417         try {
418             try {
419                 FILE_UTILS.rename(canfil, temp);
420             } catch (IOException JavaDoc e) {
421                 throw new IOException JavaDoc(
422                     "Couldn't rename resource when attempting to delete "
423                     + linkfil);
424             }
425             // delete the (now) broken link:
426
if (!linkfil.delete()) {
427                 throw new IOException JavaDoc("Couldn't delete symlink: " + linkfil
428                     + " (was it a real file? is this not a UNIX system?)");
429             }
430         } finally {
431             // return the resource to its original name:
432
try {
433                 FILE_UTILS.rename(temp, canfil);
434             } catch (IOException JavaDoc e) {
435                 throw new IOException JavaDoc("Couldn't return resource " + temp
436                     + " to its original name: " + canfil.getAbsolutePath()
437                     + "\n THE RESOURCE'S NAME ON DISK HAS "
438                     + "BEEN CHANGED BY THIS ERROR!\n");
439             }
440         }
441     }
442
443     /**
444      * Write a properties file. This method uses <code>Properties.store</code>
445      * and thus may throw exceptions that occur while writing the file.
446      *
447      * @param properties The properties object to be written.
448      * @param dir The directory for which we are writing the links.
449      */

450     private void writePropertyFile(Properties JavaDoc properties, File JavaDoc dir)
451         throws BuildException {
452         BufferedOutputStream JavaDoc bos = null;
453         try {
454             bos = new BufferedOutputStream JavaDoc(
455                 new FileOutputStream JavaDoc(new File JavaDoc(dir, linkFileName)));
456             properties.store(bos, "Symlinks from " + dir);
457         } catch (IOException JavaDoc ioe) {
458             throw new BuildException(ioe, getLocation());
459         } finally {
460             FileUtils.close(bos);
461         }
462     }
463
464     /**
465      * Handle errors based on the setting of failonerror.
466      *
467      * @param msg The message to log, or include in the
468      * <code>BuildException</code>.
469      */

470     private void handleError(String JavaDoc msg) {
471         if (failonerror) {
472             throw new BuildException(msg);
473         }
474         log(msg);
475     }
476
477     /**
478      * Conduct the actual construction of a link.
479      *
480      * <p> The link is constructed by calling <code>Execute.runCommand</code>.
481      *
482      * @param res The path of the resource we are linking to.
483      * @param lnk The name of the link we wish to make.
484      */

485     private void doLink(String JavaDoc res, String JavaDoc lnk) throws BuildException {
486         File JavaDoc linkfil = new File JavaDoc(lnk);
487         if (overwrite && linkfil.exists()) {
488             try {
489                 deleteSymlink(linkfil);
490             } catch (FileNotFoundException JavaDoc fnfe) {
491                 handleError("Symlink disappeared before it was deleted: " + lnk);
492             } catch (IOException JavaDoc ioe) {
493                 handleError("Unable to overwrite preexisting link: " + lnk);
494             }
495         }
496         String JavaDoc[] cmd = new String JavaDoc[] {"ln", "-s", res, lnk};
497         log(Commandline.toString(cmd));
498         Execute.runCommand(this, cmd);
499     }
500
501     /**
502      * Find all the links in all supplied filesets.
503      *
504      * <p> This method is invoked when the action attribute is
505      * &quot;record&quot;. This means that filesets are interpreted
506      * as the directories in which links may be found.
507      *
508      * @param v The filesets specified by the user.
509      * @return A HashSet of <code>File</code> objects containing the
510      * links (with canonical parent directories).
511      */

512     private HashSet JavaDoc findLinks(Vector JavaDoc v) {
513         HashSet JavaDoc result = new HashSet JavaDoc();
514         for (int i = 0; i < v.size(); i++) {
515             FileSet fs = (FileSet) v.get(i);
516             DirectoryScanner ds = fs.getDirectoryScanner(getProject());
517             String JavaDoc[][] fnd = new String JavaDoc[][]
518                 {ds.getIncludedFiles(), ds.getIncludedDirectories()};
519             File JavaDoc dir = fs.getDir(getProject());
520             for (int j = 0; j < fnd.length; j++) {
521                 for (int k = 0; k < fnd[j].length; k++) {
522                     try {
523                         File JavaDoc f = new File JavaDoc(dir, fnd[j][k]);
524                         File JavaDoc pf = f.getParentFile();
525                         String JavaDoc name = f.getName();
526                         if (FILE_UTILS.isSymbolicLink(pf, name)) {
527                             result.add(new File JavaDoc(pf.getCanonicalFile(), name));
528                         }
529                     } catch (IOException JavaDoc e) {
530                         handleError("IOException: " + fnd[j][k] + " omitted");
531                     }
532                 }
533             }
534         }
535         return result;
536     }
537
538     /**
539      * Load links from properties files included in one or more FileSets.
540      *
541      * <p> This method is only invoked when the action attribute is set to
542      * &quot;recreate&quot;. The filesets passed in are assumed to specify the
543      * names of the property files with the link information and the
544      * subdirectories in which to look for them.
545      *
546      * @param v The <code>FileSet</code>s for this task.
547      * @return The links to be made.
548      */

549     private Properties JavaDoc loadLinks(Vector JavaDoc v) {
550         Properties JavaDoc finalList = new Properties JavaDoc();
551         // loop through the supplied file sets:
552
for (int i = 0; i < v.size(); i++) {
553             FileSet fs = (FileSet) v.elementAt(i);
554             DirectoryScanner ds = new DirectoryScanner();
555             fs.setupDirectoryScanner(ds, getProject());
556             ds.setFollowSymlinks(false);
557             ds.scan();
558             String JavaDoc[] incs = ds.getIncludedFiles();
559             File JavaDoc dir = fs.getDir(getProject());
560
561             // load included files as properties files:
562
for (int j = 0; j < incs.length; j++) {
563                 File JavaDoc inc = new File JavaDoc(dir, incs[j]);
564                 File JavaDoc pf = inc.getParentFile();
565                 Properties JavaDoc lnks = new Properties JavaDoc();
566                 try {
567                     lnks.load(new BufferedInputStream JavaDoc(new FileInputStream JavaDoc(inc)));
568                     pf = pf.getCanonicalFile();
569                 } catch (FileNotFoundException JavaDoc fnfe) {
570                     handleError("Unable to find " + incs[j] + "; skipping it.");
571                     continue;
572                 } catch (IOException JavaDoc ioe) {
573                     handleError("Unable to open " + incs[j]
574                                 + " or its parent dir; skipping it.");
575                     continue;
576                 }
577                 lnks.list(new PrintStream JavaDoc(
578                     new LogOutputStream(this, Project.MSG_INFO)));
579                 // Write the contents to our master list of links
580
// This method assumes that all links are defined in
581
// terms of absolute paths, or paths relative to the
582
// working directory:
583
for (Iterator JavaDoc kitr = lnks.keySet().iterator(); kitr.hasNext();) {
584                     String JavaDoc key = (String JavaDoc) kitr.next();
585                     finalList.put(new File JavaDoc(pf, key).getAbsolutePath(),
586                         lnks.getProperty(key));
587                 }
588             }
589         }
590         return finalList;
591     }
592 }
593
Popular Tags