1 | /*
|
---|
2 | * Copyright 2002-2005 The Apache Software Foundation
|
---|
3 | *
|
---|
4 | * Licensed under the Apache License, Version 2.0 (the "License");
|
---|
5 | * you may not use this file except in compliance with the License.
|
---|
6 | * You may obtain a copy of the License at
|
---|
7 | *
|
---|
8 | * http://www.apache.org/licenses/LICENSE-2.0
|
---|
9 | *
|
---|
10 | * Unless required by applicable law or agreed to in writing, software
|
---|
11 | * distributed under the License is distributed on an "AS IS" BASIS,
|
---|
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
---|
13 | * See the License for the specific language governing permissions and
|
---|
14 | * limitations under the License.
|
---|
15 | *
|
---|
16 | */
|
---|
17 |
|
---|
18 | /*
|
---|
19 | * Since the initial version of this file was developed on the clock on
|
---|
20 | * an NSF grant I should say the following boilerplate:
|
---|
21 | *
|
---|
22 | * This material is based upon work supported by the National Science
|
---|
23 | * Foundaton under Grant No. EIA-0196404. Any opinions, findings, and
|
---|
24 | * conclusions or recommendations expressed in this material are those
|
---|
25 | * of the author and do not necessarily reflect the views of the
|
---|
26 | * National Science Foundation.
|
---|
27 | */
|
---|
28 |
|
---|
29 | package org.apache.tools.ant.taskdefs.optional.unix;
|
---|
30 |
|
---|
31 | import java.io.File;
|
---|
32 | import java.io.IOException;
|
---|
33 | import java.io.FileInputStream;
|
---|
34 | import java.io.FileOutputStream;
|
---|
35 | import java.io.FileNotFoundException;
|
---|
36 |
|
---|
37 | import java.util.Vector;
|
---|
38 | import java.util.Properties;
|
---|
39 | import java.util.Enumeration;
|
---|
40 | import java.util.Hashtable;
|
---|
41 |
|
---|
42 | import org.apache.tools.ant.Task;
|
---|
43 | import org.apache.tools.ant.BuildException;
|
---|
44 | import org.apache.tools.ant.DirectoryScanner;
|
---|
45 |
|
---|
46 | import org.apache.tools.ant.util.FileUtils;
|
---|
47 |
|
---|
48 | import org.apache.tools.ant.types.FileSet;
|
---|
49 |
|
---|
50 | import org.apache.tools.ant.taskdefs.Execute;
|
---|
51 |
|
---|
52 | /**
|
---|
53 | * Creates, Records and Restores Symlinks.
|
---|
54 | *
|
---|
55 | * <p> This task performs several related operations. In the most trivial
|
---|
56 | * and default usage, it creates a link specified in the link attribute to
|
---|
57 | * a resource specified in the resource attribute. The second usage of this
|
---|
58 | * task is to traverse a directory structure specified by a fileset,
|
---|
59 | * and write a properties file in each included directory describing the
|
---|
60 | * links found in that directory. The third usage is to traverse a
|
---|
61 | * directory structure specified by a fileset, looking for properties files
|
---|
62 | * (also specified as included in the fileset) and recreate the links
|
---|
63 | * that have been previously recorded for each directory. Finally, it can be
|
---|
64 | * used to remove a symlink without deleting the file or directory it points
|
---|
65 | * to.
|
---|
66 | *
|
---|
67 | * <p> Examples of use:
|
---|
68 | *
|
---|
69 | * <p> Make a link named "foo" to a resource named
|
---|
70 | * "bar.foo" in subdir:
|
---|
71 | * <pre>
|
---|
72 | * <symlink link="${dir.top}/foo" resource="${dir.top}/subdir/bar.foo"/>
|
---|
73 | * </pre>
|
---|
74 | *
|
---|
75 | * <p> Record all links in subdir and its descendants in files named
|
---|
76 | * "dir.links"
|
---|
77 | * <pre>
|
---|
78 | * <symlink action="record" linkfilename="dir.links">
|
---|
79 | * <fileset dir="${dir.top}" includes="subdir/**" />
|
---|
80 | * </symlink>
|
---|
81 | * </pre>
|
---|
82 | *
|
---|
83 | * <p> Recreate the links recorded in the previous example:
|
---|
84 | * <pre>
|
---|
85 | * <symlink action="recreate">
|
---|
86 | * <fileset dir="${dir.top}" includes="subdir/**/dir.links" />
|
---|
87 | * </symlink>
|
---|
88 | * </pre>
|
---|
89 | *
|
---|
90 | * <p> Delete a link named "foo" to a resource named
|
---|
91 | * "bar.foo" in subdir:
|
---|
92 | * <pre>
|
---|
93 | * <symlink action="delete" link="${dir.top}/foo"/>
|
---|
94 | * </pre>
|
---|
95 | *
|
---|
96 | * <p><strong>LIMITATIONS:</strong> Because Java has no direct support for
|
---|
97 | * handling symlinks this task divines them by comparing canonical and
|
---|
98 | * absolute paths. On non-unix systems this may cause false positives.
|
---|
99 | * Furthermore, any operating system on which the command
|
---|
100 | * <code>ln -s link resource</code> is not a valid command on the command line
|
---|
101 | * will not be able to use action= "delete", action="single"
|
---|
102 | * or action="recreate", but action="record" should still
|
---|
103 | * work. Finally, the lack of support for symlinks in Java means that all links
|
---|
104 | * are recorded as links to the <strong>canonical</strong> resource name.
|
---|
105 | * Therefore the link: <code>link --> subdir/dir/../foo.bar</code> will be
|
---|
106 | * recorded as <code>link=subdir/foo.bar</code> and restored as
|
---|
107 | * <code>link --> subdir/foo.bar</code>.
|
---|
108 | *
|
---|
109 | */
|
---|
110 | public class Symlink extends Task {
|
---|
111 |
|
---|
112 | // Attributes with setter methods:
|
---|
113 | private String resource;
|
---|
114 | private String link;
|
---|
115 | private String action;
|
---|
116 | private Vector fileSets = new Vector();
|
---|
117 | private String linkFileName;
|
---|
118 | private boolean overwrite;
|
---|
119 | private boolean failonerror;
|
---|
120 |
|
---|
121 | private static final FileUtils FILE_UTILS = FileUtils.getFileUtils();
|
---|
122 |
|
---|
123 | /**
|
---|
124 | * Initialize the task.
|
---|
125 | * @throws BuildException on error.
|
---|
126 | */
|
---|
127 | public void init() throws BuildException {
|
---|
128 | super.init();
|
---|
129 | failonerror = true; // default behavior is to fail on an error
|
---|
130 | overwrite = false; // default behavior is to not overwrite
|
---|
131 | action = "single"; // default behavior is make a single link
|
---|
132 | fileSets = new Vector();
|
---|
133 | }
|
---|
134 |
|
---|
135 | /**
|
---|
136 | * The standard method for executing any task.
|
---|
137 | * @throws BuildException on error.
|
---|
138 | */
|
---|
139 | public void execute() throws BuildException {
|
---|
140 | try {
|
---|
141 | if (action.equals("single")) {
|
---|
142 | doLink(resource, link);
|
---|
143 | } else if (action.equals("delete")) {
|
---|
144 | try {
|
---|
145 | log("Removing symlink: " + link);
|
---|
146 | Symlink.deleteSymlink(link);
|
---|
147 | } catch (FileNotFoundException fnfe) {
|
---|
148 | handleError(fnfe.toString());
|
---|
149 | } catch (IOException ioe) {
|
---|
150 | handleError(ioe.toString());
|
---|
151 | }
|
---|
152 | } else if (action.equals("recreate")) {
|
---|
153 | Properties listOfLinks;
|
---|
154 | Enumeration keys;
|
---|
155 |
|
---|
156 | if (fileSets.size() == 0) {
|
---|
157 | handleError("File set identifying link file(s) "
|
---|
158 | + "required for action recreate");
|
---|
159 | return;
|
---|
160 | }
|
---|
161 | listOfLinks = loadLinks(fileSets);
|
---|
162 |
|
---|
163 | keys = listOfLinks.keys();
|
---|
164 |
|
---|
165 | while (keys.hasMoreElements()) {
|
---|
166 | link = (String) keys.nextElement();
|
---|
167 | resource = listOfLinks.getProperty(link);
|
---|
168 | // handle the case where the link exists
|
---|
169 | // and points to a directory (bug 25181)
|
---|
170 | try {
|
---|
171 | File test = new File(link);
|
---|
172 | File testRes = new File(resource);
|
---|
173 | if (!FILE_UTILS.isSymbolicLink(test.getParentFile(),
|
---|
174 | test.getName())) {
|
---|
175 | doLink(resource, link);
|
---|
176 | } else {
|
---|
177 | if (!test.getCanonicalPath().
|
---|
178 | equals(testRes.getCanonicalPath())) {
|
---|
179 | Symlink.deleteSymlink(link);
|
---|
180 | doLink(resource, link);
|
---|
181 | } // else the link exists, do nothing
|
---|
182 | }
|
---|
183 | } catch (IOException ioe) {
|
---|
184 | handleError("IO exception while creating "
|
---|
185 | + "link");
|
---|
186 | }
|
---|
187 | }
|
---|
188 | } else if (action.equals("record")) {
|
---|
189 | Vector vectOfLinks;
|
---|
190 | Hashtable byDir = new Hashtable();
|
---|
191 | Enumeration links, dirs;
|
---|
192 |
|
---|
193 | if (fileSets.size() == 0) {
|
---|
194 | handleError("Fileset identifying links to "
|
---|
195 | + "record required");
|
---|
196 | return;
|
---|
197 | }
|
---|
198 | if (linkFileName == null) {
|
---|
199 | handleError("Name of file to record links in "
|
---|
200 | + "required");
|
---|
201 | return;
|
---|
202 | }
|
---|
203 | // fill our vector with file objects representing
|
---|
204 | // links (canonical)
|
---|
205 | vectOfLinks = findLinks(fileSets);
|
---|
206 |
|
---|
207 | // create a hashtable to group them by parent directory
|
---|
208 | links = vectOfLinks.elements();
|
---|
209 | while (links.hasMoreElements()) {
|
---|
210 | File thisLink = (File) links.nextElement();
|
---|
211 | String parent = thisLink.getParent();
|
---|
212 | if (byDir.containsKey(parent)) {
|
---|
213 | ((Vector) byDir.get(parent)).addElement(thisLink);
|
---|
214 | } else {
|
---|
215 | byDir.put(parent, new Vector());
|
---|
216 | ((Vector) byDir.get(parent)).addElement(thisLink);
|
---|
217 | }
|
---|
218 | }
|
---|
219 | // write a Properties file in each directory
|
---|
220 | dirs = byDir.keys();
|
---|
221 | while (dirs.hasMoreElements()) {
|
---|
222 | String dir = (String) dirs.nextElement();
|
---|
223 | Vector linksInDir = (Vector) byDir.get(dir);
|
---|
224 | Properties linksToStore = new Properties();
|
---|
225 | Enumeration eachlink = linksInDir.elements();
|
---|
226 | File writeTo;
|
---|
227 |
|
---|
228 | // fill up a Properties object with link and resource
|
---|
229 | // names
|
---|
230 | while (eachlink.hasMoreElements()) {
|
---|
231 | File alink = (File) eachlink.nextElement();
|
---|
232 | try {
|
---|
233 | linksToStore.put(alink.getName(),
|
---|
234 | alink.getCanonicalPath());
|
---|
235 | } catch (IOException ioe) {
|
---|
236 | handleError("Couldn't get canonical "
|
---|
237 | + "name of a parent link");
|
---|
238 | }
|
---|
239 | }
|
---|
240 | // Get a place to record what we are about to write
|
---|
241 | writeTo = new File(dir + File.separator
|
---|
242 | + linkFileName);
|
---|
243 |
|
---|
244 | writePropertyFile(linksToStore, writeTo,
|
---|
245 | "Symlinks from " + writeTo.getParent());
|
---|
246 | }
|
---|
247 | } else {
|
---|
248 | handleError("Invalid action specified in symlink");
|
---|
249 | }
|
---|
250 | } finally {
|
---|
251 | // return all variables to their default state,
|
---|
252 | // ready for the next invocation.
|
---|
253 | resource = null;
|
---|
254 | link = null;
|
---|
255 | action = "single";
|
---|
256 | fileSets = new Vector();
|
---|
257 | linkFileName = null;
|
---|
258 | overwrite = false;
|
---|
259 | failonerror = true;
|
---|
260 | }
|
---|
261 | }
|
---|
262 |
|
---|
263 | /* ********************************************************** *
|
---|
264 | * Begin Attribute Setter Methods *
|
---|
265 | * ********************************************************** */
|
---|
266 |
|
---|
267 | /**
|
---|
268 | * The setter for the overwrite attribute. If set to false (default)
|
---|
269 | * the task will not overwrite existing links, and may stop the build
|
---|
270 | * if a link already exists depending on the setting of failonerror.
|
---|
271 | *
|
---|
272 | * @param owrite If true overwrite existing links.
|
---|
273 | */
|
---|
274 | public void setOverwrite(boolean owrite) {
|
---|
275 | this.overwrite = owrite;
|
---|
276 | }
|
---|
277 |
|
---|
278 | /**
|
---|
279 | * The setter for the failonerror attribute. If set to true (default)
|
---|
280 | * the entire build fails upon error. If set to false, the error is
|
---|
281 | * logged and the build will continue.
|
---|
282 | *
|
---|
283 | * @param foe If true throw BuildException on error else log it.
|
---|
284 | */
|
---|
285 | public void setFailOnError(boolean foe) {
|
---|
286 | this.failonerror = foe;
|
---|
287 | }
|
---|
288 |
|
---|
289 | /**
|
---|
290 | * Set the action to be performed. May be
|
---|
291 | * "single", "multi" or "record".
|
---|
292 | *
|
---|
293 | * @param typ The action to perform.
|
---|
294 | */
|
---|
295 | public void setAction(String typ) {
|
---|
296 | this.action = typ;
|
---|
297 | }
|
---|
298 |
|
---|
299 | /**
|
---|
300 | * Set the same of the link.
|
---|
301 | * Only used when action = "single".
|
---|
302 | *
|
---|
303 | * @param lnk The name for the link.
|
---|
304 | */
|
---|
305 | public void setLink(String lnk) {
|
---|
306 | this.link = lnk;
|
---|
307 | }
|
---|
308 |
|
---|
309 | /**
|
---|
310 | * Set the name of the resource to which a link should be created.
|
---|
311 | * Only used when action = "single".
|
---|
312 | *
|
---|
313 | * @param src The resource to be linked.
|
---|
314 | */
|
---|
315 | public void setResource(String src) {
|
---|
316 | this.resource = src;
|
---|
317 | }
|
---|
318 |
|
---|
319 | /**
|
---|
320 | * Set the name of the file to which links will be written.
|
---|
321 | * Only used when action = "record".
|
---|
322 | *
|
---|
323 | * @param lf The name of the file to write links to.
|
---|
324 | */
|
---|
325 | public void setLinkfilename(String lf) {
|
---|
326 | this.linkFileName = lf;
|
---|
327 | }
|
---|
328 |
|
---|
329 | /**
|
---|
330 | * Add a fileset to this task.
|
---|
331 | *
|
---|
332 | * @param set The fileset to add.
|
---|
333 | */
|
---|
334 | public void addFileset(FileSet set) {
|
---|
335 | fileSets.addElement(set);
|
---|
336 | }
|
---|
337 |
|
---|
338 | /* ********************************************************** *
|
---|
339 | * Begin Public Utility Methods *
|
---|
340 | * ********************************************************** */
|
---|
341 |
|
---|
342 | /**
|
---|
343 | * Deletes a symlink without deleting the resource it points to.
|
---|
344 | *
|
---|
345 | * <p>This is a convenience method that simply invokes
|
---|
346 | * <code>deleteSymlink(java.io.File)</code>.
|
---|
347 | *
|
---|
348 | * @param path A string containing the path of the symlink to delete.
|
---|
349 | *
|
---|
350 | * @throws FileNotFoundException When the path results in a
|
---|
351 | * <code>File</code> that doesn't exist.
|
---|
352 | * @throws IOException If calls to <code>File.rename</code>
|
---|
353 | * or <code>File.delete</code> fail.
|
---|
354 | */
|
---|
355 | public static void deleteSymlink(String path)
|
---|
356 | throws IOException, FileNotFoundException {
|
---|
357 | deleteSymlink(new File(path));
|
---|
358 | }
|
---|
359 |
|
---|
360 | /**
|
---|
361 | * Deletes a symlink without deleting the resource it points to.
|
---|
362 | *
|
---|
363 | * <p>This is a utility method that removes a unix symlink without removing
|
---|
364 | * the resource that the symlink points to. If it is accidentally invoked
|
---|
365 | * on a real file, the real file will not be harmed, but an exception
|
---|
366 | * will be thrown when the deletion is attempted. This method works by
|
---|
367 | * getting the canonical path of the link, using the canonical path to
|
---|
368 | * rename the resource (breaking the link) and then deleting the link.
|
---|
369 | * The resource is then returned to its original name inside a finally
|
---|
370 | * block to ensure that the resource is unharmed even in the event of
|
---|
371 | * an exception.
|
---|
372 | *
|
---|
373 | * @param linkfil A <code>File</code> object of the symlink to delete.
|
---|
374 | *
|
---|
375 | * @throws FileNotFoundException When the path results in a
|
---|
376 | * <code>File</code> that doesn't exist.
|
---|
377 | * @throws IOException If calls to <code>File.rename</code>,
|
---|
378 | * <code>File.delete</code> or
|
---|
379 | * <code>File.getCanonicalPath</code>
|
---|
380 | * fail.
|
---|
381 | */
|
---|
382 |
|
---|
383 | public static void deleteSymlink(File linkfil)
|
---|
384 | throws IOException, FileNotFoundException {
|
---|
385 |
|
---|
386 | if (!linkfil.exists()) {
|
---|
387 | throw new FileNotFoundException("No such symlink: " + linkfil);
|
---|
388 | }
|
---|
389 | // find the resource of the existing link:
|
---|
390 | String canstr = linkfil.getCanonicalPath();
|
---|
391 | File canfil = new File(canstr);
|
---|
392 |
|
---|
393 | // rename the resource, thus breaking the link:
|
---|
394 | String parentStr = canfil.getParent();
|
---|
395 | File parentDir = new File(parentStr);
|
---|
396 |
|
---|
397 | File temp = FILE_UTILS.createTempFile("symlink", ".tmp", parentDir);
|
---|
398 | temp.deleteOnExit();
|
---|
399 | try {
|
---|
400 | try {
|
---|
401 | FILE_UTILS.rename(canfil, temp);
|
---|
402 | } catch (IOException e) {
|
---|
403 | throw new IOException("Couldn't rename resource when "
|
---|
404 | + "attempting to delete " + linkfil);
|
---|
405 | }
|
---|
406 | // delete the (now) broken link:
|
---|
407 | if (!linkfil.delete()) {
|
---|
408 | throw new IOException("Couldn't delete symlink: " + linkfil
|
---|
409 | + " (was it a real file? is this not a "
|
---|
410 | + "UNIX system?)");
|
---|
411 | }
|
---|
412 | } finally {
|
---|
413 | // return the resource to its original name:
|
---|
414 | try {
|
---|
415 | FILE_UTILS.rename(temp, canfil);
|
---|
416 | } catch (IOException e) {
|
---|
417 | throw new IOException("Couldn't return resource " + temp
|
---|
418 | + " to its original name: " + canstr
|
---|
419 | + "\n THE RESOURCE'S NAME ON DISK HAS "
|
---|
420 | + "BEEN CHANGED BY THIS ERROR!\n");
|
---|
421 | }
|
---|
422 | }
|
---|
423 | }
|
---|
424 |
|
---|
425 | /* ********************************************************** *
|
---|
426 | * Begin Private Methods *
|
---|
427 | * ********************************************************** */
|
---|
428 |
|
---|
429 | /**
|
---|
430 | * Writes a properties file.
|
---|
431 | *
|
---|
432 | * This method use <code>Properties.store</code>
|
---|
433 | * and thus report exceptions that occur while writing the file.
|
---|
434 | *
|
---|
435 | * @param properties The properties object to be written.
|
---|
436 | * @param propertyfile The File to write to.
|
---|
437 | * @param comment The comment to place at the head of the file.
|
---|
438 | */
|
---|
439 |
|
---|
440 | private void writePropertyFile(Properties properties,
|
---|
441 | File propertyfile,
|
---|
442 | String comment)
|
---|
443 | throws BuildException {
|
---|
444 |
|
---|
445 | FileOutputStream fos = null;
|
---|
446 | try {
|
---|
447 | fos = new FileOutputStream(propertyfile);
|
---|
448 | properties.store(fos, comment);
|
---|
449 | } catch (IOException ioe) {
|
---|
450 | throw new BuildException(ioe, getLocation());
|
---|
451 | } finally {
|
---|
452 | FILE_UTILS.close(fos);
|
---|
453 | }
|
---|
454 | }
|
---|
455 |
|
---|
456 | /**
|
---|
457 | * Handles errors correctly based on the setting of failonerror.
|
---|
458 | *
|
---|
459 | * @param msg The message to log, or include in the
|
---|
460 | * <code>BuildException</code>.
|
---|
461 | */
|
---|
462 | private void handleError(String msg) {
|
---|
463 | if (failonerror) {
|
---|
464 | throw new BuildException(msg);
|
---|
465 | }
|
---|
466 | log(msg);
|
---|
467 | }
|
---|
468 |
|
---|
469 | /**
|
---|
470 | * Conducts the actual construction of a link.
|
---|
471 | *
|
---|
472 | * <p> The link is constructed by calling <code>Execute.runCommand</code>.
|
---|
473 | *
|
---|
474 | * @param resource The path of the resource we are linking to.
|
---|
475 | * @param link The name of the link we wish to make.
|
---|
476 | */
|
---|
477 |
|
---|
478 | private void doLink(String resource, String link) throws BuildException {
|
---|
479 |
|
---|
480 | if (resource == null) {
|
---|
481 | handleError("Must define the resource to symlink to!");
|
---|
482 | return;
|
---|
483 | }
|
---|
484 | if (link == null) {
|
---|
485 | handleError("Must define the link name for symlink!");
|
---|
486 | return;
|
---|
487 | }
|
---|
488 | File linkfil = new File(link);
|
---|
489 |
|
---|
490 | String[] cmd = new String[] {"ln", "-s", resource, link};
|
---|
491 |
|
---|
492 | try {
|
---|
493 | if (overwrite && linkfil.exists()) {
|
---|
494 | deleteSymlink(linkfil);
|
---|
495 | }
|
---|
496 | } catch (FileNotFoundException fnfe) {
|
---|
497 | handleError("Symlink disappeared before it was deleted: " + link);
|
---|
498 | } catch (IOException ioe) {
|
---|
499 | handleError("Unable to overwrite preexisting link: " + link);
|
---|
500 | }
|
---|
501 | log(cmd[0] + " " + cmd[1] + " " + cmd[2] + " " + cmd[3]);
|
---|
502 | Execute.runCommand(this, cmd);
|
---|
503 | }
|
---|
504 |
|
---|
505 | /**
|
---|
506 | * Simultaneously get included directories and included files.
|
---|
507 | *
|
---|
508 | * @param ds The scanner with which to get the files and directories.
|
---|
509 | * @return A vector of <code>String</code> objects containing the
|
---|
510 | * included file names and directory names.
|
---|
511 | */
|
---|
512 | private Vector scanDirsAndFiles(DirectoryScanner ds) {
|
---|
513 | String[] files, dirs;
|
---|
514 | Vector list = new Vector();
|
---|
515 |
|
---|
516 | ds.scan();
|
---|
517 |
|
---|
518 | files = ds.getIncludedFiles();
|
---|
519 | dirs = ds.getIncludedDirectories();
|
---|
520 |
|
---|
521 | for (int i = 0; i < files.length; i++) {
|
---|
522 | list.addElement(files[i]);
|
---|
523 | }
|
---|
524 | for (int i = 0; i < dirs.length; i++) {
|
---|
525 | list.addElement(dirs[i]);
|
---|
526 | }
|
---|
527 | return list;
|
---|
528 | }
|
---|
529 |
|
---|
530 | /**
|
---|
531 | * Finds all the links in all supplied filesets.
|
---|
532 | *
|
---|
533 | * <p> This method is invoked when the action attribute is
|
---|
534 | * "record". This means that filesets are interpreted
|
---|
535 | * as the directories in which links may be found.
|
---|
536 | *
|
---|
537 | * <p> The basic method followed here is, for each fileset:
|
---|
538 | * <ol>
|
---|
539 | * <li> Compile a list of all matches </li>
|
---|
540 | * <li> Convert matches to <code>File</code> objects </li>
|
---|
541 | * <li> Remove all non-symlinks using
|
---|
542 | * <code>FileUtils.isSymbolicLink</code> </li>
|
---|
543 | * <li> Convert all parent directories to the canonical form </li>
|
---|
544 | * <li> Add the remaining links from each file set to a
|
---|
545 | * master list of links unless the link is already recorded
|
---|
546 | * in the list</li>
|
---|
547 | * </ol>
|
---|
548 | *
|
---|
549 | * @param fileSets The filesets specified by the user.
|
---|
550 | * @return A Vector of <code>File</code> objects containing the
|
---|
551 | * links (with canonical parent directories).
|
---|
552 | */
|
---|
553 |
|
---|
554 | private Vector findLinks(Vector fileSets) {
|
---|
555 | Vector result = new Vector();
|
---|
556 |
|
---|
557 | // loop through the supplied file sets:
|
---|
558 | FSLoop: for (int i = 0; i < fileSets.size(); i++) {
|
---|
559 | FileSet fsTemp = (FileSet) fileSets.elementAt(i);
|
---|
560 | String workingDir = null;
|
---|
561 | Vector links = new Vector();
|
---|
562 | Vector linksFiles = new Vector();
|
---|
563 | Enumeration enumLinks;
|
---|
564 |
|
---|
565 | DirectoryScanner ds;
|
---|
566 |
|
---|
567 | File tmpfil = null;
|
---|
568 | try {
|
---|
569 | tmpfil = fsTemp.getDir(this.getProject());
|
---|
570 | workingDir = tmpfil.getCanonicalPath();
|
---|
571 | } catch (IOException ioe) {
|
---|
572 | handleError("Exception caught getting "
|
---|
573 | + "canonical path of working dir " + tmpfil
|
---|
574 | + " in a FileSet passed to the symlink "
|
---|
575 | + "task. Further processing of this "
|
---|
576 | + "fileset skipped");
|
---|
577 | continue FSLoop;
|
---|
578 | }
|
---|
579 | // Get a vector of String with file names that match the pattern:
|
---|
580 | ds = fsTemp.getDirectoryScanner(this.getProject());
|
---|
581 | links = scanDirsAndFiles(ds);
|
---|
582 |
|
---|
583 | // Now convert the strings to File Objects
|
---|
584 | // using the canonical version of the working dir:
|
---|
585 | enumLinks = links.elements();
|
---|
586 |
|
---|
587 | while (enumLinks.hasMoreElements()) {
|
---|
588 | linksFiles.addElement(new File(workingDir
|
---|
589 | + File.separator
|
---|
590 | + (String) enumLinks
|
---|
591 | .nextElement()));
|
---|
592 | }
|
---|
593 | // Now loop through and remove the non-links:
|
---|
594 |
|
---|
595 | enumLinks = linksFiles.elements();
|
---|
596 |
|
---|
597 | File parentNext, next;
|
---|
598 | String nameParentNext;
|
---|
599 | Vector removals = new Vector();
|
---|
600 | while (enumLinks.hasMoreElements()) {
|
---|
601 | next = (File) enumLinks.nextElement();
|
---|
602 | nameParentNext = next.getParent();
|
---|
603 |
|
---|
604 | parentNext = new File(nameParentNext);
|
---|
605 | try {
|
---|
606 | if (!FILE_UTILS.isSymbolicLink(parentNext, next.getName())) {
|
---|
607 | removals.addElement(next);
|
---|
608 | }
|
---|
609 | } catch (IOException ioe) {
|
---|
610 | handleError("Failed checking " + next
|
---|
611 | + " for symbolic link. FileSet skipped.");
|
---|
612 | continue FSLoop;
|
---|
613 | // Otherwise this file will be falsely recorded as a link,
|
---|
614 | // if failonerror = false, hence the warn and skip.
|
---|
615 | }
|
---|
616 | }
|
---|
617 | enumLinks = removals.elements();
|
---|
618 |
|
---|
619 | while (enumLinks.hasMoreElements()) {
|
---|
620 | linksFiles.removeElement(enumLinks.nextElement());
|
---|
621 | }
|
---|
622 | // Now we have what we want so add it to results, ensuring that
|
---|
623 | // no link is returned twice and we have a canonical reference
|
---|
624 | // to the link. (no symlinks in the parent dir)
|
---|
625 |
|
---|
626 | enumLinks = linksFiles.elements();
|
---|
627 | while (enumLinks.hasMoreElements()) {
|
---|
628 | File temp, parent;
|
---|
629 | next = (File) enumLinks.nextElement();
|
---|
630 | try {
|
---|
631 | parent = new File(next.getParent());
|
---|
632 | parent = new File(parent.getCanonicalPath());
|
---|
633 | temp = new File(parent, next.getName());
|
---|
634 | if (!result.contains(temp)) {
|
---|
635 | result.addElement(temp);
|
---|
636 | }
|
---|
637 | } catch (IOException ioe) {
|
---|
638 | handleError("IOException: " + next + " omitted");
|
---|
639 | }
|
---|
640 | }
|
---|
641 | // Note that these links are now specified with a full
|
---|
642 | // canonical path irrespective of the working dir of the
|
---|
643 | // file set so it is ok to mix them in the same vector.
|
---|
644 | }
|
---|
645 | return result;
|
---|
646 | }
|
---|
647 |
|
---|
648 | /**
|
---|
649 | * Load the links from a properties file.
|
---|
650 | *
|
---|
651 | * <p> This method is only invoked when the action attribute is set to
|
---|
652 | * "multi". The filesets passed in are assumed to specify the
|
---|
653 | * names of the property files with the link information and the
|
---|
654 | * subdirectories in which to look for them.
|
---|
655 | *
|
---|
656 | * <p> The basic method follwed here is, for each file set:
|
---|
657 | * <ol>
|
---|
658 | * <li> Get the canonical version of the dir attribute </li>
|
---|
659 | * <li> Scan for properties files </li>
|
---|
660 | * <li> load the contents of each properties file found. </li>
|
---|
661 | * </ol>
|
---|
662 | *
|
---|
663 | * @param fileSets The <code>FileSet</code>s for this task
|
---|
664 | * @return The links to be made.
|
---|
665 | */
|
---|
666 | private Properties loadLinks(Vector fileSets) {
|
---|
667 | Properties finalList = new Properties();
|
---|
668 | Enumeration keys;
|
---|
669 | String key, value;
|
---|
670 | String[] includedFiles;
|
---|
671 |
|
---|
672 | // loop through the supplied file sets:
|
---|
673 | FSLoop: for (int i = 0; i < fileSets.size(); i++) {
|
---|
674 | String workingDir;
|
---|
675 | FileSet fsTemp = (FileSet) fileSets.elementAt(i);
|
---|
676 |
|
---|
677 | DirectoryScanner ds;
|
---|
678 |
|
---|
679 | try {
|
---|
680 | File linelength = fsTemp.getDir(this.getProject());
|
---|
681 | workingDir = linelength.getCanonicalPath();
|
---|
682 | } catch (IOException ioe) {
|
---|
683 | handleError("Exception caught getting "
|
---|
684 | + "canonical path of working dir "
|
---|
685 | + "of a FileSet passed to symlink "
|
---|
686 | + "task. FileSet skipped.");
|
---|
687 | continue FSLoop;
|
---|
688 | }
|
---|
689 | ds = fsTemp.getDirectoryScanner(this.getProject());
|
---|
690 | ds.setFollowSymlinks(false);
|
---|
691 | ds.scan();
|
---|
692 | includedFiles = ds.getIncludedFiles();
|
---|
693 |
|
---|
694 | // loop through the files identified by each file set
|
---|
695 | // and load their contents.
|
---|
696 | for (int j = 0; j < includedFiles.length; j++) {
|
---|
697 | File inc = new File(workingDir + File.separator
|
---|
698 | + includedFiles[j]);
|
---|
699 | String inDir;
|
---|
700 | Properties propTemp = new Properties();
|
---|
701 |
|
---|
702 | try {
|
---|
703 | propTemp.load(new FileInputStream(inc));
|
---|
704 | inDir = inc.getParent();
|
---|
705 | inDir = (new File(inDir)).getCanonicalPath();
|
---|
706 | } catch (FileNotFoundException fnfe) {
|
---|
707 | handleError("Unable to find " + includedFiles[j]
|
---|
708 | + "FileSet skipped.");
|
---|
709 | continue FSLoop;
|
---|
710 | } catch (IOException ioe) {
|
---|
711 | handleError("Unable to open " + includedFiles[j]
|
---|
712 | + " or its parent dir"
|
---|
713 | + "FileSet skipped.");
|
---|
714 | continue FSLoop;
|
---|
715 | }
|
---|
716 | keys = propTemp.keys();
|
---|
717 | propTemp.list(System.out);
|
---|
718 | // Write the contents to our master list of links
|
---|
719 | // This method assumes that all links are defined in
|
---|
720 | // terms of absolute paths, or paths relative to the
|
---|
721 | // working directory
|
---|
722 | while (keys.hasMoreElements()) {
|
---|
723 | key = (String) keys.nextElement();
|
---|
724 | value = propTemp.getProperty(key);
|
---|
725 | finalList.put(inDir + File.separator + key, value);
|
---|
726 | }
|
---|
727 | }
|
---|
728 | }
|
---|
729 | return finalList;
|
---|
730 | }
|
---|
731 | }
|
---|