blob: 748fb17a54a7082acebe49875e326e6d4b15eb63 [file] [log] [blame]
/*
* 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
*
* https://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.util;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.nio.file.StandardOpenOption;
import java.util.Arrays;
import java.util.Vector;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.ProjectComponent;
import org.apache.tools.ant.filters.util.ChainReaderHelper;
import org.apache.tools.ant.types.FilterChain;
import org.apache.tools.ant.types.FilterSetCollection;
import org.apache.tools.ant.types.Resource;
import org.apache.tools.ant.types.ResourceCollection;
import org.apache.tools.ant.types.ResourceFactory;
import org.apache.tools.ant.types.TimeComparison;
import org.apache.tools.ant.types.resources.Appendable;
import org.apache.tools.ant.types.resources.FileProvider;
import org.apache.tools.ant.types.resources.FileResource;
import org.apache.tools.ant.types.resources.Resources;
import org.apache.tools.ant.types.resources.Restrict;
import org.apache.tools.ant.types.resources.StringResource;
import org.apache.tools.ant.types.resources.Touchable;
import org.apache.tools.ant.types.resources.Union;
import org.apache.tools.ant.types.resources.selectors.Date;
import org.apache.tools.ant.types.resources.selectors.ResourceSelector;
import org.apache.tools.ant.types.selectors.SelectorUtils;
// CheckStyle:HideUtilityClassConstructorCheck OFF - bc
/**
* This class provides utility methods to process Resources.
*
* @since Ant 1.5.2
*/
public class ResourceUtils {
/** Utilities used for file operations */
private static final FileUtils FILE_UTILS = FileUtils.getFileUtils();
/**
* Name of charset "ISO Latin Alphabet No. 1, a.k.a. ISO-LATIN-1".
*
* @deprecated use StandardCharsets.ISO_8859_1
* @since Ant 1.8.1
*/
@Deprecated
public static final String ISO_8859_1 = "ISO-8859-1";
private static final long MAX_IO_CHUNK_SIZE = 16 * 1024 * 1024L; // 16 MB
/**
* Tells which source files should be reprocessed based on the
* last modification date of target files.
* @param logTo where to send (more or less) interesting output.
* @param source array of resources bearing relative path and last
* modification date.
* @param mapper filename mapper indicating how to find the target
* files.
* @param targets object able to map as a resource a relative path
* at <b>destination</b>.
* @return array containing the source files which need to be
* copied or processed, because the targets are out of date or do
* not exist.
*/
public static Resource[] selectOutOfDateSources(final ProjectComponent logTo,
final Resource[] source,
final FileNameMapper mapper,
final ResourceFactory targets) {
return selectOutOfDateSources(logTo, source, mapper, targets,
FILE_UTILS.getFileTimestampGranularity());
}
/**
* Tells which source files should be reprocessed based on the
* last modification date of target files.
* @param logTo where to send (more or less) interesting output.
* @param source array of resources bearing relative path and last
* modification date.
* @param mapper filename mapper indicating how to find the target
* files.
* @param targets object able to map as a resource a relative path
* at <b>destination</b>.
* @param granularity The number of milliseconds leeway to give
* before deciding a target is out of date.
* @return array containing the source files which need to be
* copied or processed, because the targets are out of date or do
* not exist.
* @since Ant 1.6.2
*/
public static Resource[] selectOutOfDateSources(final ProjectComponent logTo,
final Resource[] source,
final FileNameMapper mapper,
final ResourceFactory targets,
final long granularity) {
final Union u = new Union();
u.addAll(Arrays.asList(source));
final ResourceCollection rc
= selectOutOfDateSources(logTo, u, mapper, targets, granularity);
return rc.size() == 0 ? new Resource[0] : ((Union) rc).listResources();
}
/**
* Tells which sources should be reprocessed based on the
* last modification date of targets.
* @param logTo where to send (more or less) interesting output.
* @param source ResourceCollection.
* @param mapper filename mapper indicating how to find the target Resources.
* @param targets object able to map a relative path as a Resource.
* @param granularity The number of milliseconds leeway to give
* before deciding a target is out of date.
* @return ResourceCollection.
* @since Ant 1.7
*/
public static ResourceCollection selectOutOfDateSources(final ProjectComponent logTo,
final ResourceCollection source,
final FileNameMapper mapper,
final ResourceFactory targets,
final long granularity) {
logFuture(logTo, source, granularity);
return selectSources(logTo, source, mapper, targets,
sr -> target -> SelectorUtils.isOutOfDate(sr, target, granularity));
}
/**
* Tells which sources should be reprocessed because the given
* selector selects at least one target.
*
* @param logTo where to send (more or less) interesting output.
* @param source ResourceCollection.
* @param mapper filename mapper indicating how to find the target Resources.
* @param targets object able to map a relative path as a Resource.
* @param selector returns a selector that is applied to target
* files. If it selects at least one target the source will be
* added to the returned collection.
* @return ResourceCollection.
* @since Ant 1.8.0
*/
public static ResourceCollection selectSources(final ProjectComponent logTo,
ResourceCollection source,
final FileNameMapper mapper,
final ResourceFactory targets,
final ResourceSelectorProvider selector) {
if (source.isEmpty()) {
logTo.log("No sources found.", Project.MSG_VERBOSE);
return Resources.NONE;
}
source = Union.getInstance(source);
final Union result = new Union();
for (final Resource sr : source) {
String srName = sr.getName();
if (srName != null) {
srName = srName.replace('/', File.separatorChar);
}
String[] targetnames = null;
try {
targetnames = mapper.mapFileName(srName);
} catch (final Exception e) {
logTo.log("Caught " + e + " mapping resource " + sr,
Project.MSG_VERBOSE);
}
if (targetnames == null || targetnames.length == 0) {
logTo.log(sr + " skipped - don't know how to handle it",
Project.MSG_VERBOSE);
continue;
}
final Union targetColl = new Union();
for (String targetname : targetnames) {
if (targetname == null) {
targetname = "(no name)";
}
targetColl.add(targets.getResource(
targetname.replace(File.separatorChar, '/')));
}
//find the out-of-date targets:
final Restrict r = new Restrict();
r.add(selector.getTargetSelectorForSource(sr));
r.add(targetColl);
if (r.size() > 0) {
result.add(sr);
final Resource t = r.iterator().next();
logTo.log(sr.getName() + " added as " + t.getName()
+ (t.isExists() ? " is outdated." : " doesn't exist."),
Project.MSG_VERBOSE);
continue;
}
//log uptodateness of all targets:
logTo.log(sr.getName()
+ " omitted as " + targetColl.toString()
+ (targetColl.size() == 1 ? " is" : " are ")
+ " up to date.", Project.MSG_VERBOSE);
}
return result;
}
/**
* Convenience method to copy content from one Resource to another.
* No filtering is performed.
*
* @param source the Resource to copy from.
* Must not be <code>null</code>.
* @param dest the Resource to copy to.
* Must not be <code>null</code>.
*
* @throws IOException if the copying fails.
*
* @since Ant 1.7
*/
public static void copyResource(final Resource source, final Resource dest) throws IOException {
copyResource(source, dest, null);
}
/**
* Convenience method to copy content from one Resource to another.
* No filtering is performed.
*
* @param source the Resource to copy from.
* Must not be <code>null</code>.
* @param dest the Resource to copy to.
* Must not be <code>null</code>.
* @param project the project instance.
*
* @throws IOException if the copying fails.
*
* @since Ant 1.7
*/
public static void copyResource(final Resource source, final Resource dest, final Project project)
throws IOException {
copyResource(source, dest, null, null, false,
false, null, null, project);
}
// CheckStyle:ParameterNumberCheck OFF - bc
/**
* Convenience method to copy content from one Resource to another
* specifying whether token filtering must be used, whether filter chains
* must be used, whether newer destination files may be overwritten and
* whether the last modified time of <code>dest</code> file should be made
* equal to the last modified time of <code>source</code>.
*
* @param source the Resource to copy from.
* Must not be <code>null</code>.
* @param dest the Resource to copy to.
* Must not be <code>null</code>.
* @param filters the collection of filters to apply to this copy.
* @param filterChains filterChains to apply during the copy.
* @param overwrite Whether or not the destination Resource should be
* overwritten if it already exists.
* @param preserveLastModified Whether or not the last modified time of
* the destination Resource should be set to that
* of the source.
* @param inputEncoding the encoding used to read the files.
* @param outputEncoding the encoding used to write the files.
* @param project the project instance.
*
* @throws IOException if the copying fails.
*
* @since Ant 1.7
*/
public static void copyResource(final Resource source, final Resource dest,
final FilterSetCollection filters, final Vector<FilterChain> filterChains,
final boolean overwrite, final boolean preserveLastModified,
final String inputEncoding, final String outputEncoding,
final Project project)
throws IOException {
copyResource(source, dest, filters, filterChains, overwrite, preserveLastModified, false, inputEncoding, outputEncoding, project);
}
// CheckStyle:ParameterNumberCheck OFF - bc
/**
* Convenience method to copy content from one Resource to another
* specifying whether token filtering must be used, whether filter chains
* must be used, whether newer destination files may be overwritten and
* whether the last modified time of <code>dest</code> file should be made
* equal to the last modified time of <code>source</code>.
*
* @param source the Resource to copy from.
* Must not be <code>null</code>.
* @param dest the Resource to copy to.
* Must not be <code>null</code>.
* @param filters the collection of filters to apply to this copy.
* @param filterChains filterChains to apply during the copy.
* @param overwrite Whether or not the destination Resource should be
* overwritten if it already exists.
* @param preserveLastModified Whether or not the last modified time of
* the destination Resource should be set to that
* of the source.
* @param append Whether to append to an Appendable Resource.
* @param inputEncoding the encoding used to read the files.
* @param outputEncoding the encoding used to write the files.
* @param project the project instance.
*
* @throws IOException if the copying fails.
*
* @since Ant 1.8
*/
public static void copyResource(final Resource source, final Resource dest,
final FilterSetCollection filters, final Vector<FilterChain> filterChains,
final boolean overwrite, final boolean preserveLastModified,
final boolean append,
final String inputEncoding, final String outputEncoding,
final Project project)
throws IOException {
copyResource(source, dest, filters, filterChains, overwrite,
preserveLastModified, append, inputEncoding,
outputEncoding, project, /* force: */ false);
}
/**
* Convenience method to copy content from one Resource to another
* specifying whether token filtering must be used, whether filter chains
* must be used, whether newer destination files may be overwritten and
* whether the last modified time of <code>dest</code> file should be made
* equal to the last modified time of <code>source</code>.
*
* @param source the Resource to copy from.
* Must not be <code>null</code>.
* @param dest the Resource to copy to.
* Must not be <code>null</code>.
* @param filters the collection of filters to apply to this copy.
* @param filterChains filterChains to apply during the copy.
* @param overwrite Whether or not the destination Resource should be
* overwritten if it already exists.
* @param preserveLastModified Whether or not the last modified time of
* the destination Resource should be set to that
* of the source.
* @param append Whether to append to an Appendable Resource.
* @param inputEncoding the encoding used to read the files.
* @param outputEncoding the encoding used to write the files.
* @param project the project instance.
* @param force whether read-only target files will be overwritten
*
* @throws IOException if the copying fails.
*
* @since Ant 1.8.2
*/
public static void copyResource(final Resource source, final Resource dest,
final FilterSetCollection filters, final Vector<FilterChain> filterChains,
final boolean overwrite, final boolean preserveLastModified,
final boolean append,
final String inputEncoding, final String outputEncoding,
final Project project, final boolean force)
throws IOException {
if (!overwrite && !SelectorUtils.isOutOfDate(source, dest,
FileUtils.getFileUtils().getFileTimestampGranularity())) {
return;
}
final boolean filterSetsAvailable = (filters != null
&& filters.hasFilters());
final boolean filterChainsAvailable = (filterChains != null
&& !filterChains.isEmpty());
String effectiveInputEncoding;
if (source instanceof StringResource) {
effectiveInputEncoding = ((StringResource) source).getEncoding();
} else {
effectiveInputEncoding = inputEncoding;
}
File destFile = null;
if (dest.as(FileProvider.class) != null) {
destFile = dest.as(FileProvider.class).getFile();
}
if (destFile != null && destFile.isFile() && !destFile.canWrite()) {
if (!force) {
throw new ReadOnlyTargetFileException(destFile);
}
if (!FILE_UTILS.tryHardToDelete(destFile)) {
throw new IOException(
"failed to delete read-only destination file " + destFile);
}
}
if (filterSetsAvailable) {
copyWithFilterSets(source, dest, filters, filterChains,
append, effectiveInputEncoding,
outputEncoding, project);
} else if (filterChainsAvailable
|| (effectiveInputEncoding != null
&& !effectiveInputEncoding.equals(outputEncoding))
|| (effectiveInputEncoding == null && outputEncoding != null)) {
copyWithFilterChainsOrTranscoding(source, dest, filterChains,
append, effectiveInputEncoding,
outputEncoding,
project);
} else {
boolean copied = false;
if (source.as(FileProvider.class) != null
&& destFile != null && !append) {
final File sourceFile =
source.as(FileProvider.class).getFile();
try {
copyUsingFileChannels(sourceFile, destFile, project);
copied = true;
} catch (final IOException ex) {
String msg = "Attempt to copy " + sourceFile
+ " to " + destFile + " using NIO Channels"
+ " failed due to '" + ex.getMessage()
+ "'. Falling back to streams.";
if (project != null) {
project.log(msg, Project.MSG_WARN);
} else {
System.err.println(msg);
}
}
}
if (!copied) {
copyUsingStreams(source, dest, append, project);
}
}
if (preserveLastModified) {
final Touchable t = dest.as(Touchable.class);
if (t != null) {
setLastModified(t, source.getLastModified());
}
}
}
// CheckStyle:ParameterNumberCheck ON
/**
* Set the last modified time of an object implementing
* org.apache.tools.ant.types.resources.Touchable .
*
* @param t the Touchable whose modified time is to be set.
* @param time the time to which the last modified time is to be set.
* if this is -1, the current time is used.
* @since Ant 1.7
*/
public static void setLastModified(final Touchable t, final long time) {
t.touch((time < 0) ? System.currentTimeMillis() : time);
}
/**
* Compares the contents of two Resources.
*
* @param r1 the Resource whose content is to be compared.
* @param r2 the other Resource whose content is to be compared.
* @param text true if the content is to be treated as text and
* differences in kind of line break are to be ignored.
*
* @return true if the content of the Resources is the same.
*
* @throws IOException if the Resources cannot be read.
* @since Ant 1.7
*/
public static boolean contentEquals(final Resource r1, final Resource r2, final boolean text) throws IOException {
if (r1.isExists() != r2.isExists()) {
return false;
}
if (!r1.isExists()) {
// two not existing files are equal
return true;
}
// should the following two be switched? If r1 and r2 refer to the same file,
// isn't their content equal regardless of whether that file is a directory?
if (r1.isDirectory() || r2.isDirectory()) {
// don't want to compare directory contents for now
return false;
}
if (r1.equals(r2)) {
return true;
}
if (!text) {
final long s1 = r1.getSize();
final long s2 = r2.getSize();
if (s1 != Resource.UNKNOWN_SIZE && s2 != Resource.UNKNOWN_SIZE
&& s1 != s2) {
return false;
}
}
return compareContent(r1, r2, text) == 0;
}
/**
* Compare the content of two Resources. A nonexistent Resource's
* content is "less than" that of an existing Resource; a directory-type
* Resource's content is "less than" that of a file-type Resource.
* @param r1 the Resource whose content is to be compared.
* @param r2 the other Resource whose content is to be compared.
* @param text true if the content is to be treated as text and
* differences in kind of line break are to be ignored.
* @return a negative integer, zero, or a positive integer as the first
* argument is less than, equal to, or greater than the second.
* @throws IOException if the Resources cannot be read.
* @since Ant 1.7
*/
public static int compareContent(final Resource r1, final Resource r2, final boolean text) throws IOException {
if (r1.equals(r2)) {
return 0;
}
final boolean e1 = r1.isExists();
final boolean e2 = r2.isExists();
if (!e1 && !e2) {
return 0;
}
if (e1 != e2) {
return e1 ? 1 : -1;
}
final boolean d1 = r1.isDirectory();
final boolean d2 = r2.isDirectory();
if (d1 && d2) {
return 0;
}
if (d1 || d2) {
return d1 ? -1 : 1;
}
return text ? textCompare(r1, r2) : binaryCompare(r1, r2);
}
/**
* Convenience method to turn any fileProvider into a basic
* FileResource with the file's immediate parent as the basedir,
* for tasks that need one.
* @param fileProvider input
* @return fileProvider if it is a FileResource instance, or a new
* FileResource with fileProvider's file.
* @since Ant 1.8
*/
public static FileResource asFileResource(final FileProvider fileProvider) {
if (fileProvider instanceof FileResource || fileProvider == null) {
return (FileResource) fileProvider;
}
return new FileResource(Project.getProject(fileProvider),
fileProvider.getFile());
}
/**
* Binary compares the contents of two Resources.
* <p>
* simple but sub-optimal comparison algorithm. written for working
* rather than fast. Better would be a block read into buffers followed
* by long comparisons apart from the final 1-7 bytes.
* </p>
*
* @param r1 the Resource whose content is to be compared.
* @param r2 the other Resource whose content is to be compared.
* @return a negative integer, zero, or a positive integer as the first
* argument is less than, equal to, or greater than the second.
* @throws IOException if the Resources cannot be read.
* @since Ant 1.7
*/
private static int binaryCompare(final Resource r1, final Resource r2) throws IOException {
try (InputStream in1 = new BufferedInputStream(r1.getInputStream());
InputStream in2 =
new BufferedInputStream(r2.getInputStream())) {
for (int b1 = in1.read(); b1 != -1; b1 = in1.read()) {
final int b2 = in2.read();
if (b1 != b2) {
return b1 > b2 ? 1 : -1;
}
}
return in2.read() == -1 ? 0 : -1;
}
}
/**
* Text compares the contents of two Resources.
* Ignores different kinds of line endings.
* @param r1 the Resource whose content is to be compared.
* @param r2 the other Resource whose content is to be compared.
* @return a negative integer, zero, or a positive integer as the first
* argument is less than, equal to, or greater than the second.
* @throws IOException if the Resources cannot be read.
* @since Ant 1.7
*/
private static int textCompare(final Resource r1, final Resource r2) throws IOException {
try (BufferedReader in1 =
new BufferedReader(new InputStreamReader(r1.getInputStream()));
BufferedReader in2 = new BufferedReader(
new InputStreamReader(r2.getInputStream()))) {
String expected = in1.readLine();
while (expected != null) {
final String actual = in2.readLine();
if (!expected.equals(actual)) {
if (actual == null) {
return 1;
}
return expected.compareTo(actual);
}
expected = in1.readLine();
}
return in2.readLine() == null ? 0 : -1; //NOSONAR
}
}
/**
* Log which Resources (if any) have been modified in the future.
* @param logTo the ProjectComponent to do the logging.
* @param rc the collection of Resources to check.
* @param granularity the timestamp granularity to use.
* @since Ant 1.7
*/
private static void logFuture(final ProjectComponent logTo,
final ResourceCollection rc, final long granularity) {
final long now = System.currentTimeMillis() + granularity;
final Date sel = new Date();
sel.setMillis(now);
sel.setWhen(TimeComparison.AFTER);
final Restrict future = new Restrict();
future.add(sel);
future.add(rc);
for (final Resource r : future) {
logTo.log("Warning: " + r.getName() + " modified in the future.", Project.MSG_WARN);
}
}
private static void copyWithFilterSets(final Resource source, final Resource dest,
final FilterSetCollection filters,
final Vector<FilterChain> filterChains,
final boolean append,
final String inputEncoding, final String outputEncoding,
final Project project)
throws IOException {
if (areSame(source, dest)) {
// copying the "same" file to itself will corrupt the file, so we skip it
log(project, "Skipping (self) copy of " + source + " to " + dest);
return;
}
try (Reader in = filterWith(project, inputEncoding, filterChains,
source.getInputStream());
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(
getOutputStream(dest, append, project),
charsetFor(outputEncoding)))) {
final LineTokenizer lineTokenizer = new LineTokenizer();
lineTokenizer.setIncludeDelims(true);
String line = lineTokenizer.getToken(in);
while (line != null) {
if (line.isEmpty()) {
// this should not happen, because the lines are
// returned with the end of line delimiter
out.newLine();
} else {
out.write(filters.replaceTokens(line));
}
line = lineTokenizer.getToken(in);
}
}
}
private static Reader filterWith(Project project, String encoding,
Vector<FilterChain> filterChains, InputStream input) {
Reader r = new InputStreamReader(input, charsetFor(encoding));
if (filterChains != null && !filterChains.isEmpty()) {
final ChainReaderHelper crh = new ChainReaderHelper();
crh.setBufferSize(FileUtils.BUF_SIZE);
crh.setPrimaryReader(r);
crh.setFilterChains(filterChains);
crh.setProject(project);
r = crh.getAssembledReader();
}
return new BufferedReader(r);
}
private static Charset charsetFor(String encoding) {
return encoding == null ? Charset.defaultCharset() : Charset.forName(encoding);
}
private static void copyWithFilterChainsOrTranscoding(final Resource source,
final Resource dest,
final Vector<FilterChain> filterChains,
final boolean append,
final String inputEncoding,
final String outputEncoding,
final Project project)
throws IOException {
if (areSame(source, dest)) {
// copying the "same" file to itself will corrupt the file, so we skip it
log(project, "Skipping (self) copy of " + source + " to " + dest);
return;
}
try (Reader in = filterWith(project, inputEncoding, filterChains,
source.getInputStream());
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(
getOutputStream(dest, append, project),
charsetFor(outputEncoding)))) {
final char[] buffer = new char[FileUtils.BUF_SIZE];
while (true) {
final int nRead = in.read(buffer, 0, buffer.length);
if (nRead == -1) {
break;
}
out.write(buffer, 0, nRead);
}
}
}
private static void copyUsingFileChannels(final File sourceFile,
final File destFile, final Project project)
throws IOException {
if (FileUtils.getFileUtils().areSame(sourceFile, destFile)) {
// copying the "same" file to itself will corrupt the file, so we skip it
log(project, "Skipping (self) copy of " + sourceFile + " to " + destFile);
return;
}
final File parent = destFile.getParentFile();
if (parent != null && !parent.isDirectory()
&& !(parent.mkdirs() || parent.isDirectory())) {
throw new IOException("failed to create the parent directory"
+ " for " + destFile);
}
try (FileChannel srcChannel =
FileChannel.open(sourceFile.toPath(), StandardOpenOption.READ);
FileChannel destChannel = FileChannel.open(destFile.toPath(),
StandardOpenOption.CREATE,
StandardOpenOption.TRUNCATE_EXISTING,
StandardOpenOption.WRITE)) {
long position = 0;
final long count = srcChannel.size();
while (position < count) {
final long chunk =
Math.min(MAX_IO_CHUNK_SIZE, count - position);
position +=
destChannel.transferFrom(srcChannel, position, chunk);
}
}
}
private static void copyUsingStreams(final Resource source, final Resource dest,
final boolean append, final Project project)
throws IOException {
if (areSame(source, dest)) {
// copying the "same" file to itself will corrupt the file, so we skip it
log(project, "Skipping (self) copy of " + source + " to " + dest);
return;
}
try (InputStream in = source.getInputStream();
OutputStream out = getOutputStream(dest, append, project)) {
final byte[] buffer = new byte[FileUtils.BUF_SIZE];
int count = 0;
do {
out.write(buffer, 0, count);
count = in.read(buffer, 0, buffer.length);
} while (count != -1);
}
}
private static OutputStream getOutputStream(final Resource resource, final boolean append, final Project project)
throws IOException {
if (append) {
final Appendable a = resource.as(Appendable.class);
if (a != null) {
return a.getAppendOutputStream();
}
String msg = "Appendable OutputStream not available for non-appendable resource "
+ resource + "; using plain OutputStream";
if (project != null) {
project.log(msg, Project.MSG_VERBOSE);
} else {
System.out.println(msg);
}
}
return resource.getOutputStream();
}
private static boolean areSame(final Resource resource1, final Resource resource2) throws IOException {
if (resource1 == null || resource2 == null) {
return false;
}
final FileProvider fileResource1 = resource1.as(FileProvider.class);
final FileProvider fileResource2 = resource2.as(FileProvider.class);
return fileResource1 != null && fileResource2 != null
&& FileUtils.getFileUtils().areSame(fileResource1.getFile(), fileResource2.getFile());
}
private static void log(final Project project, final String message) {
log(project, message, Project.MSG_VERBOSE);
}
private static void log(final Project project, final String message, final int level) {
if (project == null) {
System.out.println(message);
} else {
project.log(message, level);
}
}
public interface ResourceSelectorProvider {
ResourceSelector getTargetSelectorForSource(Resource source);
}
/**
* @since Ant 1.9.4
*/
public static class ReadOnlyTargetFileException extends IOException {
private static final long serialVersionUID = 1L;
public ReadOnlyTargetFileException(final File destFile) {
super("can't write to read-only destination file " + destFile);
}
}
}