| @@ -60,6 +60,16 @@ Other changes: | |||||
| strings to be present in the line. | strings to be present in the line. | ||||
| Bugzilla Report 62313 | Bugzilla Report 62313 | ||||
| * <resourcelist> has a new basedir attribute that can be used to | |||||
| resolve relative names and provides a root for the FileResources | |||||
| generated. | |||||
| Bugzilla Report 62379 | |||||
| * The <includesfile> and <excludesfile> nested elements of | |||||
| <patternset> and <fileset> now support an encoding attribute that | |||||
| can be used to specify the file's encoding. | |||||
| Bugzilla Report 62379 | |||||
| Changes from Ant 1.10.2 TO Ant 1.10.3 | Changes from Ant 1.10.2 TO Ant 1.10.3 | ||||
| ===================================== | ===================================== | ||||
| @@ -122,6 +122,11 @@ attributes you can use to test the existence of a property.</p> | |||||
| is <strong>not</strong> set</a>.</td> | is <strong>not</strong> set</a>.</td> | ||||
| <td>No</td> | <td>No</td> | ||||
| </tr> | </tr> | ||||
| <tr> | |||||
| <td>encoding</td> | |||||
| <td>The encoding of the file. <em>Since Ant 1.10.4</em></td> | |||||
| <td>No, default is platform default</td> | |||||
| </tr> | |||||
| </table> | </table> | ||||
| <h4><code>patternset</code></h4> | <h4><code>patternset</code></h4> | ||||
| <p>Patternsets may be nested within one another, adding the nested patterns to the parent | <p>Patternsets may be nested within one another, adding the nested patterns to the parent | ||||
| @@ -1249,6 +1249,15 @@ of <a href="filelist.html"><code><filelist></code></a>.</p> | |||||
| <td>The encoding of the nested resources</td> | <td>The encoding of the nested resources</td> | ||||
| <td>No; default is default JVM character encoding</td> | <td>No; default is default JVM character encoding</td> | ||||
| </tr> | </tr> | ||||
| <tr> | |||||
| <td>basedir</td> | |||||
| <td>Base directory that is used to resolve relative file names | |||||
| against. Is also used to provide a base directory to the | |||||
| FileResources created by this resource collection. <em>Since Ant | |||||
| 1.10.4</em> | |||||
| </td> | |||||
| <td>No</td> | |||||
| </tr> | |||||
| </table> | </table> | ||||
| <h5>Parameters specified as nested elements</h5> | <h5>Parameters specified as nested elements</h5> | ||||
| @@ -0,0 +1,40 @@ | |||||
| <?xml version="1.0"?> | |||||
| <!-- | |||||
| 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. | |||||
| --> | |||||
| <project name="test"> | |||||
| <target name="no-encoding"> | |||||
| <echo file="${java.io.tmpdir}/foo">fileset.xml</echo> | |||||
| <pathconvert targetos="unix" property="property"> | |||||
| <fileset dir="${basedir}"> | |||||
| <includesfile name="${java.io.tmpdir}/foo"/> | |||||
| </fileset> | |||||
| <map from="${basedir}" to="/abc"/> | |||||
| </pathconvert> | |||||
| <echo>${property}</echo> | |||||
| </target> | |||||
| <target name="encoding"> | |||||
| <echo file="${java.io.tmpdir}/foo" encoding="UTF-16LE">fileset.xml</echo> | |||||
| <pathconvert targetos="unix" property="property"> | |||||
| <fileset dir="${basedir}"> | |||||
| <includesfile name="${java.io.tmpdir}/foo" encoding="UTF-16LE"/> | |||||
| </fileset> | |||||
| <map from="${basedir}" to="/abc"/> | |||||
| </pathconvert> | |||||
| <echo>${property}</echo> | |||||
| </target> | |||||
| </project> | |||||
| @@ -254,7 +254,7 @@ public class Delete extends MatchingTask { | |||||
| /** | /** | ||||
| * add a name entry on the include files list | * add a name entry on the include files list | ||||
| * @return a NameEntry object to be configured | |||||
| * @return a PatternFileNameEntry object to be configured | |||||
| */ | */ | ||||
| @Override | @Override | ||||
| public PatternSet.NameEntry createIncludesFile() { | public PatternSet.NameEntry createIncludesFile() { | ||||
| @@ -274,7 +274,7 @@ public class Delete extends MatchingTask { | |||||
| /** | /** | ||||
| * add a name entry on the include files list | * add a name entry on the include files list | ||||
| * @return a NameEntry object to be configured | |||||
| * @return a PatternFileNameEntry object to be configured | |||||
| */ | */ | ||||
| @Override | @Override | ||||
| public PatternSet.NameEntry createExcludesFile() { | public PatternSet.NameEntry createExcludesFile() { | ||||
| @@ -78,7 +78,7 @@ public abstract class MatchingTask extends Task implements SelectorContainer { | |||||
| /** | /** | ||||
| * add a name entry on the include files list | * add a name entry on the include files list | ||||
| * @return an NameEntry object to be configured | |||||
| * @return an PatternFileNameEntry object to be configured | |||||
| */ | */ | ||||
| public PatternSet.NameEntry createIncludesFile() { | public PatternSet.NameEntry createIncludesFile() { | ||||
| return fileset.createIncludesFile(); | return fileset.createIncludesFile(); | ||||
| @@ -94,7 +94,7 @@ public abstract class MatchingTask extends Task implements SelectorContainer { | |||||
| /** | /** | ||||
| * add a name entry on the include files list | * add a name entry on the include files list | ||||
| * @return an NameEntry object to be configured | |||||
| * @return an PatternFileNameEntry object to be configured | |||||
| */ | */ | ||||
| public PatternSet.NameEntry createExcludesFile() { | public PatternSet.NameEntry createExcludesFile() { | ||||
| return fileset.createExcludesFile(); | return fileset.createExcludesFile(); | ||||
| @@ -197,7 +197,7 @@ public abstract class AbstractFileSet extends DataType | |||||
| /** | /** | ||||
| * Add a name entry to the include files list. | * Add a name entry to the include files list. | ||||
| * @return <code>PatternSet.NameEntry</code>. | |||||
| * @return <code>PatternSet.PatternFileNameEntry</code>. | |||||
| */ | */ | ||||
| public synchronized PatternSet.NameEntry createIncludesFile() { | public synchronized PatternSet.NameEntry createIncludesFile() { | ||||
| if (isReference()) { | if (isReference()) { | ||||
| @@ -221,7 +221,7 @@ public abstract class AbstractFileSet extends DataType | |||||
| /** | /** | ||||
| * Add a name entry to the excludes files list. | * Add a name entry to the excludes files list. | ||||
| * @return <code>PatternSet.NameEntry</code>. | |||||
| * @return <code>PatternSet.PatternFileNameEntry</code>. | |||||
| */ | */ | ||||
| public synchronized PatternSet.NameEntry createExcludesFile() { | public synchronized PatternSet.NameEntry createExcludesFile() { | ||||
| if (isReference()) { | if (isReference()) { | ||||
| @@ -19,8 +19,11 @@ package org.apache.tools.ant.types; | |||||
| import java.io.BufferedReader; | import java.io.BufferedReader; | ||||
| import java.io.File; | import java.io.File; | ||||
| import java.io.FileInputStream; | |||||
| import java.io.FileReader; | import java.io.FileReader; | ||||
| import java.io.IOException; | import java.io.IOException; | ||||
| import java.io.InputStreamReader; | |||||
| import java.io.Reader; | |||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||
| import java.util.List; | import java.util.List; | ||||
| import java.util.Objects; | import java.util.Objects; | ||||
| @@ -41,8 +44,8 @@ import org.apache.tools.ant.PropertyHelper; | |||||
| public class PatternSet extends DataType implements Cloneable { | public class PatternSet extends DataType implements Cloneable { | ||||
| private List<NameEntry> includeList = new ArrayList<>(); | private List<NameEntry> includeList = new ArrayList<>(); | ||||
| private List<NameEntry> excludeList = new ArrayList<>(); | private List<NameEntry> excludeList = new ArrayList<>(); | ||||
| private List<NameEntry> includesFileList = new ArrayList<>(); | |||||
| private List<NameEntry> excludesFileList = new ArrayList<>(); | |||||
| private List<PatternFileNameEntry> includesFileList = new ArrayList<>(); | |||||
| private List<PatternFileNameEntry> excludesFileList = new ArrayList<>(); | |||||
| /** | /** | ||||
| * inner class to hold a name on list. "If" and "Unless" attributes | * inner class to hold a name on list. "If" and "Unless" attributes | ||||
| @@ -178,6 +181,45 @@ public class PatternSet extends DataType implements Cloneable { | |||||
| } | } | ||||
| } | } | ||||
| /** | |||||
| * Adds encoding support to {@link NameEntry}. | |||||
| * @since Ant 1.10.4 | |||||
| */ | |||||
| public class PatternFileNameEntry extends NameEntry { | |||||
| private String encoding; | |||||
| /** | |||||
| * Encoding to use when reading the file, defaults to the platform's default | |||||
| * encoding. | |||||
| * | |||||
| * <p> | |||||
| * For a list of possible values see | |||||
| * <a href="https://docs.oracle.com/javase/1.5.0/docs/guide/intl/encoding.doc.html"> | |||||
| * https://docs.oracle.com/javase/1.5.0/docs/guide/intl/encoding.doc.html</a>. | |||||
| * </p> | |||||
| * | |||||
| * @param encoding String | |||||
| */ | |||||
| public final void setEncoding(String encoding) { | |||||
| this.encoding = encoding; | |||||
| } | |||||
| /** | |||||
| * Encoding to use when reading the file, defaults to the platform's default | |||||
| * encoding. | |||||
| */ | |||||
| public final String getEncoding() { | |||||
| return encoding; | |||||
| } | |||||
| @Override | |||||
| public String toString() { | |||||
| String baseString = super.toString(); | |||||
| return encoding == null ? baseString | |||||
| : new StringBuilder(baseString).append(";encoding->").append(encoding).toString(); | |||||
| } | |||||
| } | |||||
| private static final class InvertedPatternSet extends PatternSet { | private static final class InvertedPatternSet extends PatternSet { | ||||
| private InvertedPatternSet(PatternSet p) { | private InvertedPatternSet(PatternSet p) { | ||||
| setProject(p.getProject()); | setProject(p.getProject()); | ||||
| @@ -260,7 +302,7 @@ public class PatternSet extends DataType implements Cloneable { | |||||
| if (isReference()) { | if (isReference()) { | ||||
| throw noChildrenAllowed(); | throw noChildrenAllowed(); | ||||
| } | } | ||||
| return addPatternToList(includesFileList); | |||||
| return addPatternFileToList(includesFileList); | |||||
| } | } | ||||
| /** | /** | ||||
| @@ -282,7 +324,7 @@ public class PatternSet extends DataType implements Cloneable { | |||||
| if (isReference()) { | if (isReference()) { | ||||
| throw noChildrenAllowed(); | throw noChildrenAllowed(); | ||||
| } | } | ||||
| return addPatternToList(excludesFileList); | |||||
| return addPatternFileToList(excludesFileList); | |||||
| } | } | ||||
| /** | /** | ||||
| @@ -330,6 +372,15 @@ public class PatternSet extends DataType implements Cloneable { | |||||
| return result; | return result; | ||||
| } | } | ||||
| /** | |||||
| * add a pattern file name entry to the given list | |||||
| */ | |||||
| private PatternFileNameEntry addPatternFileToList(List<PatternFileNameEntry> list) { | |||||
| PatternFileNameEntry result = new PatternFileNameEntry(); | |||||
| list.add(result); | |||||
| return result; | |||||
| } | |||||
| /** | /** | ||||
| * Sets the name of the file containing the includes patterns. | * Sets the name of the file containing the includes patterns. | ||||
| * | * | ||||
| @@ -360,11 +411,12 @@ public class PatternSet extends DataType implements Cloneable { | |||||
| * Reads path matching patterns from a file and adds them to the | * Reads path matching patterns from a file and adds them to the | ||||
| * includes or excludes list (as appropriate). | * includes or excludes list (as appropriate). | ||||
| */ | */ | ||||
| private void readPatterns(File patternfile, List<NameEntry> patternlist, Project p) | |||||
| private void readPatterns(File patternfile, String encoding, List<NameEntry> patternlist, Project p) | |||||
| throws BuildException { | throws BuildException { | ||||
| try (BufferedReader patternReader = | |||||
| new BufferedReader(new FileReader(patternfile))) { | |||||
| try (Reader r = encoding == null ? new FileReader(patternfile) | |||||
| : new InputStreamReader(new FileInputStream(patternfile), encoding); | |||||
| BufferedReader patternReader = new BufferedReader(r)) { | |||||
| // Create one NameEntry in the appropriate pattern list for each | // Create one NameEntry in the appropriate pattern list for each | ||||
| // line in the file. | // line in the file. | ||||
| @@ -470,7 +522,7 @@ public class PatternSet extends DataType implements Cloneable { | |||||
| */ | */ | ||||
| private void readFiles(Project p) { | private void readFiles(Project p) { | ||||
| if (!includesFileList.isEmpty()) { | if (!includesFileList.isEmpty()) { | ||||
| for (NameEntry ne : includesFileList) { | |||||
| for (PatternFileNameEntry ne : includesFileList) { | |||||
| String fileName = ne.evalName(p); | String fileName = ne.evalName(p); | ||||
| if (fileName != null) { | if (fileName != null) { | ||||
| File inclFile = p.resolveFile(fileName); | File inclFile = p.resolveFile(fileName); | ||||
| @@ -478,13 +530,13 @@ public class PatternSet extends DataType implements Cloneable { | |||||
| throw new BuildException("Includesfile " + inclFile.getAbsolutePath() | throw new BuildException("Includesfile " + inclFile.getAbsolutePath() | ||||
| + " not found."); | + " not found."); | ||||
| } | } | ||||
| readPatterns(inclFile, includeList, p); | |||||
| readPatterns(inclFile, ne.getEncoding(), includeList, p); | |||||
| } | } | ||||
| } | } | ||||
| includesFileList.clear(); | includesFileList.clear(); | ||||
| } | } | ||||
| if (!excludesFileList.isEmpty()) { | if (!excludesFileList.isEmpty()) { | ||||
| for (NameEntry ne : excludesFileList) { | |||||
| for (PatternFileNameEntry ne : excludesFileList) { | |||||
| String fileName = ne.evalName(p); | String fileName = ne.evalName(p); | ||||
| if (fileName != null) { | if (fileName != null) { | ||||
| File exclFile = p.resolveFile(fileName); | File exclFile = p.resolveFile(fileName); | ||||
| @@ -492,7 +544,7 @@ public class PatternSet extends DataType implements Cloneable { | |||||
| throw new BuildException("Excludesfile " + exclFile.getAbsolutePath() | throw new BuildException("Excludesfile " + exclFile.getAbsolutePath() | ||||
| + " not found."); | + " not found."); | ||||
| } | } | ||||
| readPatterns(exclFile, excludeList, p); | |||||
| readPatterns(exclFile, ne.getEncoding(), excludeList, p); | |||||
| } | } | ||||
| } | } | ||||
| excludesFileList.clear(); | excludesFileList.clear(); | ||||
| @@ -123,7 +123,7 @@ public class Files extends AbstractSelectorContainer | |||||
| /** | /** | ||||
| * Add a name entry to the include files list. | * Add a name entry to the include files list. | ||||
| * @return <code>PatternSet.NameEntry</code>. | |||||
| * @return <code>PatternSet.PatternFileNameEntry</code>. | |||||
| */ | */ | ||||
| public synchronized PatternSet.NameEntry createIncludesFile() { | public synchronized PatternSet.NameEntry createIncludesFile() { | ||||
| if (isReference()) { | if (isReference()) { | ||||
| @@ -147,7 +147,7 @@ public class Files extends AbstractSelectorContainer | |||||
| /** | /** | ||||
| * Add a name entry to the excludes files list. | * Add a name entry to the excludes files list. | ||||
| * @return <code>PatternSet.NameEntry</code>. | |||||
| * @return <code>PatternSet.PatternFileNameEntry</code>. | |||||
| */ | */ | ||||
| public synchronized PatternSet.NameEntry createExcludesFile() { | public synchronized PatternSet.NameEntry createExcludesFile() { | ||||
| if (isReference()) { | if (isReference()) { | ||||
| @@ -19,6 +19,7 @@ package org.apache.tools.ant.types.resources; | |||||
| import java.io.BufferedInputStream; | import java.io.BufferedInputStream; | ||||
| import java.io.BufferedReader; | import java.io.BufferedReader; | ||||
| import java.io.File; | |||||
| import java.io.IOException; | import java.io.IOException; | ||||
| import java.io.InputStreamReader; | import java.io.InputStreamReader; | ||||
| import java.io.Reader; | import java.io.Reader; | ||||
| @@ -49,6 +50,7 @@ public class ResourceList extends DataType implements ResourceCollection { | |||||
| private final Union cachedResources = new Union(); | private final Union cachedResources = new Union(); | ||||
| private volatile boolean cached = false; | private volatile boolean cached = false; | ||||
| private String encoding = null; | private String encoding = null; | ||||
| private File baseDir; | |||||
| public ResourceList() { | public ResourceList() { | ||||
| cachedResources.setCache(true); | cachedResources.setCache(true); | ||||
| @@ -99,6 +101,21 @@ public class ResourceList extends DataType implements ResourceCollection { | |||||
| this.encoding = encoding; | this.encoding = encoding; | ||||
| } | } | ||||
| /** | |||||
| * Basedir to use for file resources read from nested resources - | |||||
| * this allows the resources contained inside this collection to | |||||
| * be considered relative to a certain base directory. | |||||
| * | |||||
| * @param basedir the basedir | |||||
| * @since Ant 1.10.4 | |||||
| */ | |||||
| public final void setBasedir(File baseDir) { | |||||
| if (isReference()) { | |||||
| throw tooManyAttributes(); | |||||
| } | |||||
| this.baseDir = baseDir; | |||||
| } | |||||
| /** | /** | ||||
| * Makes this instance in effect a reference to another ResourceList | * Makes this instance in effect a reference to another ResourceList | ||||
| * instance. | * instance. | ||||
| @@ -242,6 +259,11 @@ public class ResourceList extends DataType implements ResourceCollection { | |||||
| // resource | // resource | ||||
| } | } | ||||
| } | } | ||||
| if (baseDir != null) { | |||||
| FileResource fr = new FileResource(baseDir, expandedLine); | |||||
| fr.setProject(getProject()); | |||||
| return fr; | |||||
| } | |||||
| return new FileResource(getProject(), expandedLine); | return new FileResource(getProject(), expandedLine); | ||||
| } | } | ||||
| @@ -476,4 +476,22 @@ public class NullByteStreamResource extends Resource { | |||||
| </copy> | </copy> | ||||
| <au:assertFileExists file="${output}/filea"/> | <au:assertFileExists file="${output}/filea"/> | ||||
| </target> | </target> | ||||
| <target name="testCopyOfResourceListDoesntFlatten" | |||||
| description="https://issues.apache.org/bugzilla/show_bug.cgi?id=62379"> | |||||
| <mkdir dir="${input}/dir"/> | |||||
| <touch file="${input}/a"/> | |||||
| <touch file="${input}/b"/> | |||||
| <touch file="${input}/c"/> | |||||
| <echo file="${input}/dir/c">Testfile</echo> | |||||
| <mkdir dir="${output}"/> | |||||
| <copy todir="${output}"> | |||||
| <resourcelist basedir="${input}"> | |||||
| <string value="${input}/dir/c"/> | |||||
| </resourcelist> | |||||
| </copy> | |||||
| <au:assertFileDoesntExist file="${output}/c"/> | |||||
| <au:assertFileExists file="${output}/dir/c"/> | |||||
| <au:assertFilesMatch expected="${input}/dir/c" actual="${output}/dir/c"/> | |||||
| </target> | |||||
| </project> | </project> | ||||
| @@ -18,17 +18,41 @@ | |||||
| package org.apache.tools.ant.types; | package org.apache.tools.ant.types; | ||||
| import org.apache.tools.ant.BuildFileRule; | |||||
| import org.junit.Before; | |||||
| import org.junit.Rule; | |||||
| import org.junit.Test; | |||||
| import static org.junit.Assert.assertEquals; | |||||
| /** | /** | ||||
| * JUnit 4 testcases for org.apache.tools.ant.types.FileSet. | * JUnit 4 testcases for org.apache.tools.ant.types.FileSet. | ||||
| * | |||||
| * <p>This doesn't actually test much, mainly reference handling.</p> | |||||
| */ | */ | ||||
| public class FileSetTest extends AbstractFileSetTest { | public class FileSetTest extends AbstractFileSetTest { | ||||
| @Rule | |||||
| public BuildFileRule buildRule = new BuildFileRule(); | |||||
| @Before | |||||
| public void buildFileRuleSetUp() { | |||||
| buildRule.configureProject("src/etc/testcases/types/fileset.xml"); | |||||
| } | |||||
| protected AbstractFileSet getInstance() { | protected AbstractFileSet getInstance() { | ||||
| return new FileSet(); | return new FileSet(); | ||||
| } | } | ||||
| @Test | |||||
| public void testNoEncoding() { | |||||
| buildRule.executeTarget("no-encoding"); | |||||
| assertEquals("/abc/fileset.xml", buildRule.getLog()); | |||||
| } | |||||
| @Test | |||||
| public void testEncoding() { | |||||
| buildRule.executeTarget("encoding"); | |||||
| assertEquals("/abc/fileset.xml", buildRule.getLog()); | |||||
| } | |||||
| } | } | ||||
| @@ -20,13 +20,20 @@ package org.apache.tools.ant.types; | |||||
| import org.apache.tools.ant.BuildException; | import org.apache.tools.ant.BuildException; | ||||
| import org.apache.tools.ant.Project; | import org.apache.tools.ant.Project; | ||||
| import org.apache.tools.ant.util.FileUtils; | |||||
| import org.junit.Before; | import org.junit.Before; | ||||
| import org.junit.Rule; | import org.junit.Rule; | ||||
| import org.junit.Test; | import org.junit.Test; | ||||
| import org.junit.rules.ExpectedException; | import org.junit.rules.ExpectedException; | ||||
| import java.io.File; | import java.io.File; | ||||
| import java.io.FileOutputStream; | |||||
| import java.io.IOException; | |||||
| import java.io.OutputStream; | |||||
| import java.io.OutputStreamWriter; | |||||
| import java.io.Writer; | |||||
| import static org.junit.Assert.assertArrayEquals; | |||||
| import static org.junit.Assert.assertEquals; | import static org.junit.Assert.assertEquals; | ||||
| /** | /** | ||||
| @@ -211,4 +218,26 @@ public class PatternSetTest { | |||||
| assertEquals("Includes", "**/*.java", includes[0]); | assertEquals("Includes", "**/*.java", includes[0]); | ||||
| assertEquals("Excludes", "**/*.class", excludes[0]); | assertEquals("Excludes", "**/*.class", excludes[0]); | ||||
| } | } | ||||
| @Test | |||||
| public void testEncodingOfIncludesFile() throws IOException { | |||||
| File testFile = File.createTempFile("ant-", ".pattern"); | |||||
| testFile.deleteOnExit(); | |||||
| OutputStream o = null; | |||||
| Writer w = null; | |||||
| try { | |||||
| o = new FileOutputStream(testFile); | |||||
| w = new OutputStreamWriter(o, "UTF-16LE"); | |||||
| w.write("\u00e4\n"); | |||||
| } finally { | |||||
| FileUtils.close(w); | |||||
| FileUtils.close(o); | |||||
| } | |||||
| PatternSet p = new PatternSet(); | |||||
| PatternSet.PatternFileNameEntry ne = | |||||
| (PatternSet.PatternFileNameEntry) p.createIncludesFile(); | |||||
| ne.setName(testFile.getAbsolutePath()); | |||||
| ne.setEncoding("UTF-16LE"); | |||||
| assertArrayEquals(new String[] { "\u00e4" }, p.getIncludePatterns(project)); | |||||
| } | |||||
| } | } | ||||