| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| */ |
| |
| package org.apache.tools.ant.taskdefs; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.text.DateFormat; |
| import java.text.ParseException; |
| import java.text.SimpleDateFormat; |
| 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.FileList; |
| import org.apache.tools.ant.types.FileSet; |
| import org.apache.tools.ant.types.Mapper; |
| import org.apache.tools.ant.types.Resource; |
| import org.apache.tools.ant.types.ResourceCollection; |
| import org.apache.tools.ant.types.resources.FileProvider; |
| import org.apache.tools.ant.types.resources.FileResource; |
| import org.apache.tools.ant.types.resources.Touchable; |
| import org.apache.tools.ant.types.resources.Union; |
| import org.apache.tools.ant.util.FileNameMapper; |
| import org.apache.tools.ant.util.FileUtils; |
| |
| /** |
| * Touch a file and/or fileset(s) and/or filelist(s); |
| * corresponds to the Unix touch command. |
| * |
| * <p>If the file to touch doesn't exist, an empty one is created.</p> |
| * |
| * @since Ant 1.1 |
| * |
| * @ant.task category="filesystem" |
| */ |
| public class Touch extends Task { |
| |
| public interface DateFormatFactory { |
| DateFormat getPrimaryFormat(); |
| DateFormat getFallbackFormat(); |
| } |
| |
| public static final DateFormatFactory DEFAULT_DF_FACTORY |
| = new DateFormatFactory() { |
| |
| private ThreadLocal<DateFormat> primary = |
| new ThreadLocal<DateFormat>() { |
| @Override |
| protected DateFormat initialValue() { |
| return new SimpleDateFormat("MM/dd/yyyy hh:mm a", |
| Locale.US); |
| } |
| }; |
| private ThreadLocal<DateFormat> fallback = |
| new ThreadLocal<DateFormat>() { |
| @Override |
| protected DateFormat initialValue() { |
| return new SimpleDateFormat("MM/dd/yyyy hh:mm:ss a", |
| Locale.US); |
| } |
| }; |
| |
| public DateFormat getPrimaryFormat() { |
| return primary.get(); |
| } |
| public DateFormat getFallbackFormat() { |
| return fallback.get(); |
| } |
| }; |
| private static final FileUtils FILE_UTILS = FileUtils.getFileUtils(); |
| |
| private File file; |
| private long millis = -1; |
| private String dateTime; |
| private Vector filesets = new Vector(); |
| private Union resources; |
| private boolean dateTimeConfigured; |
| private boolean mkdirs; |
| private boolean verbose = true; |
| private FileNameMapper fileNameMapper = null; |
| private DateFormatFactory dfFactory = DEFAULT_DF_FACTORY; |
| |
| /** |
| * Construct a new <code>Touch</code> task. |
| */ |
| public Touch() { |
| } |
| |
| /** |
| * Sets a single source file to touch. If the file does not exist |
| * an empty file will be created. |
| * @param file the <code>File</code> to touch. |
| */ |
| public void setFile(File file) { |
| this.file = file; |
| } |
| |
| /** |
| * Set the new modification time of file(s) touched |
| * in milliseconds since midnight Jan 1 1970. Optional, default=now. |
| * @param millis the <code>long</code> timestamp to use. |
| */ |
| public void setMillis(long millis) { |
| this.millis = millis; |
| } |
| |
| /** |
| * Set the new modification time of file(s) touched |
| * in the format "MM/DD/YYYY HH:MM AM <i>or</i> PM" |
| * or "MM/DD/YYYY HH:MM:SS AM <i>or</i> PM". |
| * Optional, default=now. |
| * @param dateTime the <code>String</code> 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 <code>boolean</code> 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 <code>true</code>. |
| * @param verbose <code>boolean</code> flag. |
| * @since Ant 1.6.3 |
| */ |
| public void setVerbose(boolean verbose) { |
| this.verbose = verbose; |
| } |
| |
| /** |
| * Set the format of the datetime attribute. |
| * @param pattern the <code>SimpleDateFormat</code>-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 <code>Mapper</code>. |
| * @param mapper the <code>Mapper</code> to add. |
| * @since Ant 1.6.3 |
| */ |
| public void addConfiguredMapper(Mapper mapper) { |
| add(mapper.getImplementation()); |
| } |
| |
| /** |
| * Add a <code>FileNameMapper</code>. |
| * @param fileNameMapper the <code>FileNameMapper</code> to add. |
| * @since Ant 1.6.3 |
| * @throws BuildException if multiple mappers are added. |
| */ |
| public void add(FileNameMapper fileNameMapper) throws BuildException { |
| 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. |
| * @param set the <code>Fileset</code> to add. |
| */ |
| public void addFileset(FileSet set) { |
| filesets.add(set); |
| add(set); |
| } |
| |
| /** |
| * Add a filelist to touch. |
| * @param list the <code>Filelist</code> to add. |
| */ |
| public void addFilelist(FileList list) { |
| add(list); |
| } |
| |
| /** |
| * Add a collection of resources to touch. |
| * @param rc the collection to add. |
| * @since Ant 1.7 |
| */ |
| public synchronized void add(ResourceCollection rc) { |
| resources = resources == null ? new Union() : resources; |
| resources.add(rc); |
| } |
| |
| /** |
| * Check that this task has been configured properly. |
| * @throws BuildException if configuration errors are detected. |
| * @since Ant 1.6.3 |
| */ |
| protected synchronized void checkConfiguration() throws BuildException { |
| if (file == null && resources == null) { |
| throw new BuildException("Specify at least one source" |
| + "--a file or resource collection."); |
| } |
| if (file != null && file.exists() && file.isDirectory()) { |
| throw new BuildException("Use a resource collection to touch directories."); |
| } |
| if (dateTime != null && !dateTimeConfigured) { |
| long workmillis = millis; |
| if ("now".equalsIgnoreCase(dateTime)) { |
| workmillis = System.currentTimeMillis(); |
| } else { |
| 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 { |
| workmillis = df.parse(dateTime).getTime(); |
| } catch (ParseException peTwo) { |
| pe = peTwo; |
| } |
| } |
| } |
| 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; |
| } |
| } |
| |
| /** |
| * Execute the touch operation. |
| * |
| * @throws BuildException |
| * if an error occurs. |
| */ |
| public void execute() throws BuildException { |
| checkConfiguration(); |
| touch(); |
| } |
| |
| /** |
| * Does the actual work; assumes everything has been checked by now. |
| * @throws BuildException if an error occurs. |
| */ |
| protected void touch() throws BuildException { |
| long defaultTimestamp = getTimestamp(); |
| |
| if (file != null) { |
| touch(new FileResource(file.getParentFile(), file.getName()), |
| defaultTimestamp); |
| } |
| if (resources == null) { |
| return; |
| } |
| // deal with the resource collections |
| for (Resource r : resources) { |
| Touchable t = r.as(Touchable.class); |
| if (t == null) { |
| throw new BuildException("Can't touch " + r); |
| } |
| touch(r, defaultTimestamp); |
| } |
| |
| // deal with filesets in a special way since the task |
| // originally also used the directories and Union won't return |
| // them. |
| final int size = filesets.size(); |
| for (int i = 0; i < size; i++) { |
| FileSet fs = (FileSet) filesets.elementAt(i); |
| DirectoryScanner ds = fs.getDirectoryScanner(getProject()); |
| File fromDir = fs.getDir(getProject()); |
| |
| String[] srcDirs = ds.getIncludedDirectories(); |
| |
| for (int j = 0; j < srcDirs.length; j++) { |
| touch(new FileResource(fromDir, srcDirs[j]), defaultTimestamp); |
| } |
| } |
| } |
| |
| /** |
| * 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 on error |
| * @deprecated since 1.6.x. |
| */ |
| protected void touch(File file) { |
| touch(file, getTimestamp()); |
| } |
| |
| private long getTimestamp() { |
| return (millis < 0) ? System.currentTimeMillis() : millis; |
| } |
| |
| private void touch(Resource r, long defaultTimestamp) { |
| if (fileNameMapper == null) { |
| FileProvider fp = r.as(FileProvider.class); |
| if (fp != null) { |
| // use this to create file and deal with non-writable files |
| touch(fp.getFile(), defaultTimestamp); |
| } else { |
| r.as(Touchable.class).touch(defaultTimestamp); |
| } |
| } else { |
| String[] mapped = fileNameMapper.mapFileName(r.getName()); |
| if (mapped != null && mapped.length > 0) { |
| long modTime = defaultTimestamp; |
| if (millis < 0 && r.isExists()) { |
| modTime = r.getLastModified(); |
| } |
| 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, |
| ((verbose) ? Project.MSG_INFO : Project.MSG_VERBOSE)); |
| try { |
| FILE_UTILS.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); |
| } |
| FILE_UTILS.setFileLastModified(file, modTime); |
| } |
| |
| } |