diff --git a/WHATSNEW b/WHATSNEW index 6425ed0e3..40b3b7646 100644 --- a/WHATSNEW +++ b/WHATSNEW @@ -102,6 +102,21 @@ Other changes: * Added isfileselected condition. +* Overloaded FileUtils.createNewFile with a boolean mkdirs attribute + to create nonexistent parent directories. + +* Added support to the touch task for a mkdirs attribute to create + nonexistent parent directories before touching new files. + +* Added support to the touch task for a pattern attribute to allow + alternate datetime formats. + +* Added support to the touch task to map touched files using a nested + mapper element. + +* Added support to the touch task for a verbose attribute to suppress + logging of new file creation. + Changes from Ant 1.6.2 to current Ant 1.6 CVS version ===================================================== diff --git a/docs/manual/CoreTasks/touch.html b/docs/manual/CoreTasks/touch.html index b146e3a08..88a51f56f 100644 --- a/docs/manual/CoreTasks/touch.html +++ b/docs/manual/CoreTasks/touch.html @@ -14,8 +14,6 @@ the same time. In addition to working with a single file, this Task can also work a Fileset (which also includes directories) or a Filelist (since Ant 1.6).

-

For JDK 1.1 only the creation of new files with a modification time -of now works, all other cases will emit a warning.

Parameters

@@ -25,26 +23,49 @@ of now works, all other cases will emit a warning.

- - + + - - + + - + + + + + + + + + + + + + + +
filethe name of the fileunless a nested fileset element - or a nested filelist element - has been specified.The name of the file.Unless a nested fileset element + or a nested filelist element has been specified.
millisspecifies the new modification time of the file - in milliseconds since midnight Jan 1 1970NoSpecifies the new modification time of the file + in milliseconds since midnight Jan 1 1970.No--datetime takes + precedence, however if both are omitted the current time is assumed.
datetimespecifies the new modification time of the file - in the format MM/DD/YYYY HH:MM AM_or_PM or MM/DD/YYYY HH:MM:SS AM_or_PM.Specifies the new modification time of the file.
patternSimpleDateFormat-compatible pattern string. + Defaults to MM/DD/YYYY HH:MM AM_or_PM or MM/DD/YYYY HH:MM:SS AM_or_PM. + Since Ant 1.6.3 No
mkdirsWhether to create nonexistent parent + directories when touching new files. Since Ant 1.6.3No, default false.
verboseWhether to log the creation of new files. + Since Ant 1.6.3No, default true.
-

If both millis and datetime are omitted -the current time is assumed.

+

Parameters specified as nested elements

+

Since Ant 1.6.3, a nested + mapper can be specified. Files specified via nested + filesets, filelists, or the file + attribute are mapped using the specified mapper. For each file mapped, + the resulting files are touched. If the original file exists its + timestamp will be used. Otherwise the task settings (millis, + datetime) take effect.

Examples

  <touch file="myfile"/>

creates myfile if it doesn't exist and changes the @@ -63,6 +84,12 @@ hour times).

modification time to Jun, 28 2000 2:02:17 pm (14:02:17 for those used to 24 hour times), if the filesystem allows a precision of one second - a time close to it otherwise.

+
  <touch file="foo">
+    <mapper type="glob" from="foo" to="bar" />
+  </touch>
+
+

creates bar if it doesn't exist and changes the +modification time to that of foo.


Copyright © 2000-2001,2003-2004 The Apache Software Foundation. All rights Reserved.

diff --git a/src/etc/testcases/taskdefs/touch.xml b/src/etc/testcases/taskdefs/touch.xml index d6f0b3d48..ea8d55d66 100644 --- a/src/etc/testcases/taskdefs/touch.xml +++ b/src/etc/testcases/taskdefs/touch.xml @@ -1,16 +1,24 @@ + + + + + + - + + + - + - + @@ -45,5 +53,69 @@ - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/org/apache/tools/ant/taskdefs/Touch.java b/src/main/org/apache/tools/ant/taskdefs/Touch.java index 38aca7ef4..3438f9c82 100644 --- a/src/main/org/apache/tools/ant/taskdefs/Touch.java +++ b/src/main/org/apache/tools/ant/taskdefs/Touch.java @@ -21,26 +21,26 @@ import java.io.File; import java.io.IOException; import java.text.DateFormat; import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; import java.util.Locale; import java.util.Vector; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.DirectoryScanner; import org.apache.tools.ant.Project; import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.Mapper; import org.apache.tools.ant.types.FileSet; import org.apache.tools.ant.types.FileList; import org.apache.tools.ant.util.FileUtils; +import org.apache.tools.ant.util.FileNameMapper; import org.apache.tools.ant.util.JavaEnvUtils; /** * Touch a file and/or fileset(s) and/or filelist(s); * corresponds to the Unix touch command. * - *

If the file to touch doesn't exist, an empty one is - * created.

- * - *

Note: Setting the modification time of files is not supported in - * JDK 1.1.

+ *

If the file to touch doesn't exist, an empty one is created.

* * @since Ant 1.1 * @@ -48,13 +48,48 @@ import org.apache.tools.ant.util.JavaEnvUtils; */ public class Touch extends Task { + private interface DateFormatFactory { + DateFormat getPrimaryFormat(); + DateFormat getFallbackFormat(); + } + + private static final DateFormatFactory DEFAULT_DF_FACTORY + = new DateFormatFactory() { + /* + * The initial version used DateFormat.SHORT for the + * time format, which ignores seconds. If we want + * seconds as well, we need DateFormat.MEDIUM, which + * in turn would break all old build files. + * + * First try to parse with DateFormat.SHORT and if + * that fails with MEDIUM - throw an exception if both + * fail. + */ + public DateFormat getPrimaryFormat() { + return DateFormat.getDateTimeInstance(DateFormat.SHORT, + DateFormat.SHORT, Locale.US); + } + public DateFormat getFallbackFormat() { + return DateFormat.getDateTimeInstance(DateFormat.SHORT, + DateFormat.MEDIUM, Locale.US); + } + }; + private File file; private long millis = -1; private String dateTime; private Vector filesets = new Vector(); private Vector filelists = new Vector(); private FileUtils fileUtils; + private boolean dateTimeConfigured; + private boolean mkdirs; + private boolean verbose = true; + private FileNameMapper fileNameMapper = null; + private DateFormatFactory dfFactory = DEFAULT_DF_FACTORY; + /** + * Construct a new Touch task. + */ public Touch() { fileUtils = FileUtils.newFileUtils(); } @@ -62,122 +97,179 @@ public class Touch extends Task { /** * Sets a single source file to touch. If the file does not exist * an empty file will be created. + * @param file the File to touch. */ public void setFile(File file) { this.file = file; } /** - * the new modification time of the file - * in milliseconds since midnight Jan 1 1970. - * Optional, default=now + * Set the new modification time of file(s) touched + * in milliseconds since midnight Jan 1 1970. Optional, default=now. + * @param millis the long timestamp to use. */ public void setMillis(long millis) { this.millis = millis; } /** - * the new modification time of the file + * Set the new modification time of file(s) touched * in the format "MM/DD/YYYY HH:MM AM or PM" * or "MM/DD/YYYY HH:MM:SS AM or PM". - * Optional, default=now + * Optional, default=now. + * @param dateTime the String date in the specified format. */ public void setDatetime(String dateTime) { + if (this.dateTime != null) { + log("Resetting datetime attribute to " + dateTime, Project.MSG_VERBOSE); + } this.dateTime = dateTime; + dateTimeConfigured = false; + } + + /** + * Set whether nonexistent parent directories should be created + * when touching new files. + * @param mkdirs boolean whether to create parent directories. + * @since Ant 1.6.3 + */ + public void setMkdirs(boolean mkdirs) { + this.mkdirs = mkdirs; + } + + /** + * Set whether the touch task will report every file it creates; + * defaults to true. + * @param verbose boolean flag. + * @since Ant 1.6.3 + */ + public void setVerbose(boolean verbose) { + this.verbose = verbose; + } + + /** + * Set the format of the datetime attribute. + * @param pattern the SimpleDateFormat-compatible format pattern. + * @since Ant 1.6.3 + */ + public void setPattern(final String pattern) { + dfFactory = new DateFormatFactory() { + public DateFormat getPrimaryFormat() { + return new SimpleDateFormat(pattern); + } + public DateFormat getFallbackFormat() { + return null; + } + }; + } + + /** + * Add a Mapper. + * @param mapper the Mapper to add. + * @since Ant 1.6.3 + */ + public void addMapper(Mapper mapper) { + add(mapper.getImplementation()); + } + + /** + * Add a FileNameMapper. + * @param mapper the FileNameMapper to add. + * @since Ant 1.6.3 + */ + public void add(FileNameMapper fileNameMapper) { + if (this.fileNameMapper != null) { + throw new BuildException( "Only one mapper may be added to the " + + getTaskName() + " task."); + } + this.fileNameMapper = fileNameMapper; } /** - * Add a set of files to touch + * Add a set of files to touch. + * @param set the Fileset to add. */ public void addFileset(FileSet set) { filesets.addElement(set); } /** - * Add a filelist to touch + * Add a filelist to touch. + * @param list the Filelist to add. */ public void addFilelist(FileList list) { filelists.addElement(list); } /** - * Execute the touch operation. + * Check that this task has been configured properly. + * @throws BuildException if configuration errors are detected. + * @since Ant 1.6.3 */ - public void execute() throws BuildException { - long savedMillis = millis; - - if (file == null && filesets.size() == 0 && filelists.size() == 0) { - throw - new BuildException("Specify at least one source - a file, filelist or " - + "a fileset."); + protected synchronized void checkConfiguration() { + if (file == null && filesets.size() + filelists.size() == 0) { + throw new BuildException("Specify at least one source" + + "--a file, filelist or a fileset."); } - if (file != null && file.exists() && file.isDirectory()) { throw new BuildException("Use a fileset to touch directories."); } - - try { - if (dateTime != null) { - /* - * The initial version used DateFormat.SHORT for the - * time format, which ignores seconds. If we want - * seconds as well, we need DateFormat.MEDIUM, which - * in turn would break all old build files. - * - * First try to parse with DateFormat.SHORT and if - * that fails with MEDIUM - throw an exception if both - * fail. - */ - DateFormat df = - DateFormat.getDateTimeInstance(DateFormat.SHORT, - DateFormat.SHORT, - Locale.US); - try { - setMillis(df.parse(dateTime).getTime()); - } catch (ParseException pe) { - df = - DateFormat.getDateTimeInstance(DateFormat.SHORT, - DateFormat.MEDIUM, - Locale.US); + if (dateTime != null && !dateTimeConfigured) { + long workmillis = millis; + DateFormat df = dfFactory.getPrimaryFormat(); + ParseException pe = null; + try { + workmillis = df.parse(dateTime).getTime(); + } catch (ParseException peOne) { + df = dfFactory.getFallbackFormat(); + if (df == null) { + pe = peOne; + } else { try { - setMillis(df.parse(dateTime).getTime()); - } catch (ParseException pe2) { - throw new BuildException(pe2.getMessage(), pe, - getLocation()); + workmillis = df.parse(dateTime).getTime(); + } catch (ParseException peTwo) { + pe = peTwo; } } - - if (millis < 0) { - throw new BuildException("Date of " + dateTime - + " results in negative " - + "milliseconds value " - + "relative to epoch " - + "(January 1, 1970, " - + "00:00:00 GMT)."); - } } - - touch(); - } finally { - millis = savedMillis; + if (pe != null) { + throw new BuildException(pe.getMessage(), pe, getLocation()); + } + if (workmillis < 0) { + throw new BuildException("Date of " + dateTime + + " results in negative " + + "milliseconds value " + + "relative to epoch " + + "(January 1, 1970, " + + "00:00:00 GMT)."); + } + log("Setting millis to " + workmillis + " from datetime attribute", + ((millis < 0) ? Project.MSG_DEBUG : Project.MSG_VERBOSE)); + setMillis(workmillis); + //only set if successful to this point: + dateTimeConfigured = true; } } /** - * Does the actual work; assumes everything has been checked by now. + * Execute the touch operation. + * @throws BuildException if an error occurs. */ - protected void touch() throws BuildException { + public void execute() { + checkConfiguration(); + touch(); + } - boolean resetMillis = false; - if (millis < 0) { - resetMillis = true; - millis = System.currentTimeMillis(); - } + /** + * Does the actual work; assumes everything has been checked by now. + * @throws BuildException if an error occurs. + */ + protected void touch() { + long defaultTimestamp = getTimestamp(); if (file != null) { - touch(file); + touch(fileUtils.getParentFile(file), file.getName(), defaultTimestamp); } - // deal with the filesets for (int i = 0; i < filesets.size(); i++) { FileSet fs = (FileSet) filesets.elementAt(i); @@ -188,14 +280,12 @@ public class Touch extends Task { String[] srcDirs = ds.getIncludedDirectories(); for (int j = 0; j < srcFiles.length; j++) { - touch(new File(fromDir, srcFiles[j])); + touch(fromDir, srcFiles[j], defaultTimestamp); } - for (int j = 0; j < srcDirs.length; j++) { - touch(new File(fromDir, srcDirs[j])); + touch(fromDir, srcDirs[j], defaultTimestamp); } } - // deal with the filelists for (int i = 0; i < filelists.size(); i++) { FileList fl = (FileList) filelists.elementAt(i); @@ -204,36 +294,58 @@ public class Touch extends Task { String[] srcFiles = fl.getFiles(getProject()); for (int j = 0; j < srcFiles.length; j++) { - touch(new File(fromDir, srcFiles[j])); + touch(fromDir, srcFiles[j], defaultTimestamp); } } - - if (resetMillis) { - millis = -1; - } } /** - * touch a single file with the current timestamp (this.millis) + * Touch a single file with the current timestamp (this.millis). This method + * does not interact with any nested mappers and remains for reasons of + * backwards-compatibility only. * @param file file to touch * @throws BuildException + * @deprecated */ - protected void touch(File file) throws BuildException { + protected void touch(File file) { + touch(file, getTimestamp()); + } + + private long getTimestamp() { + return (millis < 0) ? System.currentTimeMillis() : millis; + } + + private void touch(File fromDir, String filename, long defaultTimestamp) { + File file = fileUtils.resolveFile(fromDir, filename); + if (fileNameMapper == null) { + touch(file, defaultTimestamp); + } else { + String[] mapped = fileNameMapper.mapFileName(filename); + if (mapped != null && mapped.length > 0) { + long modTime = (file.exists()) ? file.lastModified() : defaultTimestamp; + for (int i = 0; i < mapped.length ; i++) { + touch(getProject().resolveFile(mapped[i]), modTime); + } + } + } + } + + private void touch(File file, long modTime) { if (!file.exists()) { - log("Creating " + file, Project.MSG_INFO); + log("Creating " + file, + ((verbose) ? Project.MSG_INFO : Project.MSG_VERBOSE)); try { - fileUtils.createNewFile(file); + fileUtils.createNewFile(file, mkdirs); } catch (IOException ioe) { throw new BuildException("Could not create " + file, ioe, getLocation()); } } - if (!file.canWrite()) { throw new BuildException("Can not change modification date of " + "read-only file " + file); } - fileUtils.setFileLastModified(file, millis); + fileUtils.setFileLastModified(file, modTime); } } diff --git a/src/main/org/apache/tools/ant/util/FileUtils.java b/src/main/org/apache/tools/ant/util/FileUtils.java index 22c401ee3..ec5941cf4 100644 --- a/src/main/org/apache/tools/ant/util/FileUtils.java +++ b/src/main/org/apache/tools/ant/util/FileUtils.java @@ -1097,6 +1097,23 @@ public class FileUtils { * @since Ant 1.5 */ public boolean createNewFile(File f) throws IOException { + return createNewFile(f, false); + } + + /** + * Create a new file, optionally creating parent directories. + * + * @param f the file to be created. + * @param mkdirs boolean whether to create parent directories. + * @return true if the file did not exist already. + * @throws IOException on error. + * @since Ant 1.6.3 + */ + public boolean createNewFile(File f, boolean mkdirs) throws IOException { + File parent = f.getParentFile(); + if (mkdirs && !(parent.exists())) { + parent.mkdirs(); + } return f.createNewFile(); } diff --git a/src/testcases/org/apache/tools/ant/taskdefs/TouchTest.java b/src/testcases/org/apache/tools/ant/taskdefs/TouchTest.java index 46386fed6..fe206724c 100644 --- a/src/testcases/org/apache/tools/ant/taskdefs/TouchTest.java +++ b/src/testcases/org/apache/tools/ant/taskdefs/TouchTest.java @@ -102,6 +102,35 @@ public class TouchTest extends BuildFileTest { touchFile("testFileset", 946080000000L); } + /** + * test the mapped file set + */ + public void testMappedFileset() { + executeTarget("testMappedFileset"); + } + + /** + * test the mapped file list + */ + public void testMappedFilelist() { + executeTarget("testMappedFilelist"); + } + + /** + * test the pattern attribute + */ + public void testGoodPattern() { + executeTarget("testGoodPattern"); + } + + /** + * test the pattern attribute again + */ + public void testBadPattern() { + expectBuildExceptionContaining("testBadPattern", + "No parsing exception thrown", "Unparseable"); + } + /** * run a target to touch the test file; verify the timestamp is as expected * @param targetName @@ -110,7 +139,7 @@ public class TouchTest extends BuildFileTest { private void touchFile(String targetName, long timestamp) { executeTarget(targetName); long time = getTargetTime(); - assertTimesNearlyMatch(timestamp,time); + assertTimesNearlyMatch(timestamp, time); } /** @@ -130,7 +159,7 @@ public class TouchTest extends BuildFileTest { * @param range */ private void assertTimesNearlyMatch(long timestamp, long time, long range) { - assertTrue("Time "+timestamp+" is not within "+range+" ms of "+time, - Math.abs(time-timestamp)<=range); + assertTrue("Time " + timestamp + " is not within " + range + " ms of " + + time, (Math.abs(time - timestamp) <= range)); } }