001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018:
019: package org.apache.tools.ant.taskdefs;
020:
021: import java.io.File;
022: import java.io.IOException;
023: import java.text.DateFormat;
024: import java.text.ParseException;
025: import java.text.SimpleDateFormat;
026: import java.util.Iterator;
027: import java.util.Locale;
028: import java.util.Vector;
029: import org.apache.tools.ant.BuildException;
030: import org.apache.tools.ant.DirectoryScanner;
031: import org.apache.tools.ant.Project;
032: import org.apache.tools.ant.Task;
033: import org.apache.tools.ant.types.Mapper;
034: import org.apache.tools.ant.types.FileSet;
035: import org.apache.tools.ant.types.FileList;
036: import org.apache.tools.ant.types.Resource;
037: import org.apache.tools.ant.types.ResourceCollection;
038: import org.apache.tools.ant.types.resources.FileResource;
039: import org.apache.tools.ant.types.resources.Touchable;
040: import org.apache.tools.ant.types.resources.Union;
041: import org.apache.tools.ant.util.FileUtils;
042: import org.apache.tools.ant.util.FileNameMapper;
043:
044: /**
045: * Touch a file and/or fileset(s) and/or filelist(s);
046: * corresponds to the Unix touch command.
047: *
048: * <p>If the file to touch doesn't exist, an empty one is created.</p>
049: *
050: * @since Ant 1.1
051: *
052: * @ant.task category="filesystem"
053: */
054: public class Touch extends Task {
055:
056: private interface DateFormatFactory {
057: DateFormat getPrimaryFormat();
058:
059: DateFormat getFallbackFormat();
060: }
061:
062: private static final DateFormatFactory DEFAULT_DF_FACTORY = new DateFormatFactory() {
063: /*
064: * The initial version used DateFormat.SHORT for the
065: * time format, which ignores seconds. If we want
066: * seconds as well, we need DateFormat.MEDIUM, which
067: * in turn would break all old build files.
068: *
069: * First try to parse with DateFormat.SHORT and if
070: * that fails with MEDIUM - throw an exception if both
071: * fail.
072: */
073: public DateFormat getPrimaryFormat() {
074: return DateFormat.getDateTimeInstance(DateFormat.SHORT,
075: DateFormat.SHORT, Locale.US);
076: }
077:
078: public DateFormat getFallbackFormat() {
079: return DateFormat.getDateTimeInstance(DateFormat.SHORT,
080: DateFormat.MEDIUM, Locale.US);
081: }
082: };
083: private static final FileUtils FILE_UTILS = FileUtils
084: .getFileUtils();
085:
086: private File file;
087: private long millis = -1;
088: private String dateTime;
089: private Vector filesets = new Vector();
090: private Union resources = new Union();
091: private boolean dateTimeConfigured;
092: private boolean mkdirs;
093: private boolean verbose = true;
094: private FileNameMapper fileNameMapper = null;
095: private DateFormatFactory dfFactory = DEFAULT_DF_FACTORY;
096:
097: /**
098: * Construct a new <code>Touch</code> task.
099: */
100: public Touch() {
101: }
102:
103: /**
104: * Sets a single source file to touch. If the file does not exist
105: * an empty file will be created.
106: * @param file the <code>File</code> to touch.
107: */
108: public void setFile(File file) {
109: this .file = file;
110: }
111:
112: /**
113: * Set the new modification time of file(s) touched
114: * in milliseconds since midnight Jan 1 1970. Optional, default=now.
115: * @param millis the <code>long</code> timestamp to use.
116: */
117: public void setMillis(long millis) {
118: this .millis = millis;
119: }
120:
121: /**
122: * Set the new modification time of file(s) touched
123: * in the format "MM/DD/YYYY HH:MM AM <i>or</i> PM"
124: * or "MM/DD/YYYY HH:MM:SS AM <i>or</i> PM".
125: * Optional, default=now.
126: * @param dateTime the <code>String</code> date in the specified format.
127: */
128: public void setDatetime(String dateTime) {
129: if (this .dateTime != null) {
130: log("Resetting datetime attribute to " + dateTime,
131: Project.MSG_VERBOSE);
132: }
133: this .dateTime = dateTime;
134: dateTimeConfigured = false;
135: }
136:
137: /**
138: * Set whether nonexistent parent directories should be created
139: * when touching new files.
140: * @param mkdirs <code>boolean</code> whether to create parent directories.
141: * @since Ant 1.6.3
142: */
143: public void setMkdirs(boolean mkdirs) {
144: this .mkdirs = mkdirs;
145: }
146:
147: /**
148: * Set whether the touch task will report every file it creates;
149: * defaults to <code>true</code>.
150: * @param verbose <code>boolean</code> flag.
151: * @since Ant 1.6.3
152: */
153: public void setVerbose(boolean verbose) {
154: this .verbose = verbose;
155: }
156:
157: /**
158: * Set the format of the datetime attribute.
159: * @param pattern the <code>SimpleDateFormat</code>-compatible format pattern.
160: * @since Ant 1.6.3
161: */
162: public void setPattern(final String pattern) {
163: dfFactory = new DateFormatFactory() {
164: public DateFormat getPrimaryFormat() {
165: return new SimpleDateFormat(pattern);
166: }
167:
168: public DateFormat getFallbackFormat() {
169: return null;
170: }
171: };
172: }
173:
174: /**
175: * Add a <code>Mapper</code>.
176: * @param mapper the <code>Mapper</code> to add.
177: * @since Ant 1.6.3
178: */
179: public void addConfiguredMapper(Mapper mapper) {
180: add(mapper.getImplementation());
181: }
182:
183: /**
184: * Add a <code>FileNameMapper</code>.
185: * @param fileNameMapper the <code>FileNameMapper</code> to add.
186: * @since Ant 1.6.3
187: * @throws BuildException if multiple mappers are added.
188: */
189: public void add(FileNameMapper fileNameMapper)
190: throws BuildException {
191: if (this .fileNameMapper != null) {
192: throw new BuildException(
193: "Only one mapper may be added to the "
194: + getTaskName() + " task.");
195: }
196: this .fileNameMapper = fileNameMapper;
197: }
198:
199: /**
200: * Add a set of files to touch.
201: * @param set the <code>Fileset</code> to add.
202: */
203: public void addFileset(FileSet set) {
204: filesets.add(set);
205: add(set);
206: }
207:
208: /**
209: * Add a filelist to touch.
210: * @param list the <code>Filelist</code> to add.
211: */
212: public void addFilelist(FileList list) {
213: add(list);
214: }
215:
216: /**
217: * Add a collection of resources to touch.
218: * @param rc the collection to add.
219: * @since Ant 1.7
220: */
221: public void add(ResourceCollection rc) {
222: resources.add(rc);
223: }
224:
225: /**
226: * Check that this task has been configured properly.
227: * @throws BuildException if configuration errors are detected.
228: * @since Ant 1.6.3
229: */
230: protected synchronized void checkConfiguration()
231: throws BuildException {
232: if (file == null && resources.size() == 0) {
233: throw new BuildException("Specify at least one source"
234: + "--a file or resource collection.");
235: }
236: if (file != null && file.exists() && file.isDirectory()) {
237: throw new BuildException(
238: "Use a resource collection to touch directories.");
239: }
240: if (dateTime != null && !dateTimeConfigured) {
241: long workmillis = millis;
242: DateFormat df = dfFactory.getPrimaryFormat();
243: ParseException pe = null;
244: try {
245: workmillis = df.parse(dateTime).getTime();
246: } catch (ParseException peOne) {
247: df = dfFactory.getFallbackFormat();
248: if (df == null) {
249: pe = peOne;
250: } else {
251: try {
252: workmillis = df.parse(dateTime).getTime();
253: } catch (ParseException peTwo) {
254: pe = peTwo;
255: }
256: }
257: }
258: if (pe != null) {
259: throw new BuildException(pe.getMessage(), pe,
260: getLocation());
261: }
262: if (workmillis < 0) {
263: throw new BuildException("Date of " + dateTime
264: + " results in negative "
265: + "milliseconds value " + "relative to epoch "
266: + "(January 1, 1970, " + "00:00:00 GMT).");
267: }
268: log("Setting millis to " + workmillis
269: + " from datetime attribute",
270: ((millis < 0) ? Project.MSG_DEBUG
271: : Project.MSG_VERBOSE));
272: setMillis(workmillis);
273: //only set if successful to this point:
274: dateTimeConfigured = true;
275: }
276: }
277:
278: /**
279: * Execute the touch operation.
280: * @throws BuildException if an error occurs.
281: */
282: public void execute() throws BuildException {
283: checkConfiguration();
284: touch();
285: }
286:
287: /**
288: * Does the actual work; assumes everything has been checked by now.
289: * @throws BuildException if an error occurs.
290: */
291: protected void touch() throws BuildException {
292: long defaultTimestamp = getTimestamp();
293:
294: if (file != null) {
295: touch(
296: new FileResource(file.getParentFile(), file
297: .getName()), defaultTimestamp);
298: }
299: // deal with the resource collections
300: Iterator iter = resources.iterator();
301: while (iter.hasNext()) {
302: Resource r = (Resource) iter.next();
303: if (!(r instanceof Touchable)) {
304: throw new BuildException("Can't touch " + r);
305: }
306: touch(r, defaultTimestamp);
307: }
308:
309: // deal with filesets in a special way since the task
310: // originally also used the directories and Union won't return
311: // them.
312: for (int i = 0; i < filesets.size(); i++) {
313: FileSet fs = (FileSet) filesets.elementAt(i);
314: DirectoryScanner ds = fs.getDirectoryScanner(getProject());
315: File fromDir = fs.getDir(getProject());
316:
317: String[] srcDirs = ds.getIncludedDirectories();
318:
319: for (int j = 0; j < srcDirs.length; j++) {
320: touch(new FileResource(fromDir, srcDirs[j]),
321: defaultTimestamp);
322: }
323: }
324: }
325:
326: /**
327: * Touch a single file with the current timestamp (this.millis). This method
328: * does not interact with any nested mappers and remains for reasons of
329: * backwards-compatibility only.
330: * @param file file to touch
331: * @throws BuildException on error
332: * @deprecated since 1.6.x.
333: */
334: protected void touch(File file) {
335: touch(file, getTimestamp());
336: }
337:
338: private long getTimestamp() {
339: return (millis < 0) ? System.currentTimeMillis() : millis;
340: }
341:
342: private void touch(Resource r, long defaultTimestamp) {
343: if (fileNameMapper == null) {
344: if (r instanceof FileResource) {
345: // use this to create file and deal with non-writable files
346: touch(((FileResource) r).getFile(), defaultTimestamp);
347: } else {
348: ((Touchable) r).touch(defaultTimestamp);
349: }
350: } else {
351: String[] mapped = fileNameMapper.mapFileName(r.getName());
352: if (mapped != null && mapped.length > 0) {
353: long modTime = (r.isExists()) ? r.getLastModified()
354: : defaultTimestamp;
355: for (int i = 0; i < mapped.length; i++) {
356: touch(getProject().resolveFile(mapped[i]), modTime);
357: }
358: }
359: }
360: }
361:
362: private void touch(File file, long modTime) {
363: if (!file.exists()) {
364: log("Creating " + file, ((verbose) ? Project.MSG_INFO
365: : Project.MSG_VERBOSE));
366: try {
367: FILE_UTILS.createNewFile(file, mkdirs);
368: } catch (IOException ioe) {
369: throw new BuildException("Could not create " + file,
370: ioe, getLocation());
371: }
372: }
373: if (!file.canWrite()) {
374: throw new BuildException(
375: "Can not change modification date of "
376: + "read-only file " + file);
377: }
378: FILE_UTILS.setFileLastModified(file, modTime);
379: }
380:
381: }
|