/*
 *  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.taskdefs;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Vector;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.types.FileSet;
import org.apache.tools.ant.types.Mapper;
import org.apache.tools.ant.types.PatternSet;
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.Union;
import org.apache.tools.ant.types.selectors.SelectorUtils;
import org.apache.tools.ant.util.FileNameMapper;
import org.apache.tools.ant.util.FileUtils;
import org.apache.tools.ant.util.IdentityMapper;
import org.apache.tools.zip.ZipEntry;
import org.apache.tools.zip.ZipFile;

Unzip a file.
Since:Ant 1.1
@ant.taskcategory="packaging" name="unzip" name="unjar" name="unwar"
/** * Unzip a file. * * @since Ant 1.1 * * @ant.task category="packaging" * name="unzip" * name="unjar" * name="unwar" */
public class Expand extends Task { public static final String NATIVE_ENCODING = "native-encoding";
Error message when more that one mapper is defined
/** Error message when more that one mapper is defined */
public static final String ERROR_MULTIPLE_MAPPERS = "Cannot define more than one mapper"; private static final FileUtils FILE_UTILS = FileUtils.getFileUtils(); private static final int BUFFER_SIZE = 1024; private File dest; //req private File source; // req private boolean overwrite = true; private Mapper mapperElement = null; private List<PatternSet> patternsets = new Vector<>(); private Union resources = new Union(); private boolean resourcesSpecified = false; private boolean failOnEmptyArchive = false; private boolean stripAbsolutePathSpec = true; private boolean scanForUnicodeExtraFields = true; private Boolean allowFilesToEscapeDest = null; private String encoding;
Creates an Expand instance and sets encoding to UTF-8.
/** * Creates an Expand instance and sets encoding to UTF-8. */
public Expand() { this("UTF8"); }
Creates an Expand instance and sets the given encoding.
Params:
  • encoding – String
Since:Ant 1.9.5
/** * Creates an Expand instance and sets the given encoding. * * @param encoding String * @since Ant 1.9.5 */
protected Expand(String encoding) { this.encoding = encoding; }
Whether try ing to expand an empty archive would be an error.
Params:
  • b – boolean
Since:Ant 1.8.0
/** * Whether try ing to expand an empty archive would be an error. * * @param b boolean * @since Ant 1.8.0 */
public void setFailOnEmptyArchive(boolean b) { failOnEmptyArchive = b; }
Whether try ing to expand an empty archive would be an error.
Returns:boolean
Since:Ant 1.8.0
/** * Whether try ing to expand an empty archive would be an error. * * @return boolean * @since Ant 1.8.0 */
public boolean getFailOnEmptyArchive() { return failOnEmptyArchive; }
Do the work.
Throws:
  • BuildException – Thrown in unrecoverable error.
/** * Do the work. * * @exception BuildException Thrown in unrecoverable error. */
@Override public void execute() throws BuildException { if ("expand".equals(getTaskType())) { log("!! expand is deprecated. Use unzip instead. !!"); } if (source == null && !resourcesSpecified) { throw new BuildException( "src attribute and/or resources must be specified"); } if (dest == null) { throw new BuildException( "Dest attribute must be specified"); } if (dest.exists() && !dest.isDirectory()) { throw new BuildException("Dest must be a directory.", getLocation()); } if (source != null) { if (source.isDirectory()) { throw new BuildException("Src must not be a directory." + " Use nested filesets instead.", getLocation()); } if (!source.exists()) { throw new BuildException("src '" + source + "' doesn't exist."); } if (!source.canRead()) { throw new BuildException("src '" + source + "' cannot be read."); } expandFile(FILE_UTILS, source, dest); } for (Resource r : resources) { if (!r.isExists()) { log("Skipping '" + r.getName() + "' because it doesn't exist."); continue; } FileProvider fp = r.as(FileProvider.class); if (fp != null) { expandFile(FILE_UTILS, fp.getFile(), dest); } else { expandResource(r, dest); } } }
This method is to be overridden by extending unarchival tasks.
Params:
  • fileUtils – the fileUtils
  • srcF – the source file
  • dir – the destination directory
/** * This method is to be overridden by extending unarchival tasks. * * @param fileUtils the fileUtils * @param srcF the source file * @param dir the destination directory */
protected void expandFile(FileUtils fileUtils, File srcF, File dir) { log("Expanding: " + srcF + " into " + dir, Project.MSG_INFO); FileNameMapper mapper = getMapper(); if (!srcF.exists()) { throw new BuildException("Unable to expand " + srcF + " as the file does not exist", getLocation()); } try (ZipFile zf = new ZipFile(srcF, encoding, scanForUnicodeExtraFields)) { boolean empty = true; Enumeration<ZipEntry> entries = zf.getEntries(); while (entries.hasMoreElements()) { ZipEntry ze = entries.nextElement(); empty = false; InputStream is = null; log("extracting " + ze.getName(), Project.MSG_DEBUG); try { extractFile(fileUtils, srcF, dir, is = zf.getInputStream(ze), //NOSONAR ze.getName(), new Date(ze.getTime()), ze.isDirectory(), mapper); } finally { FileUtils.close(is); } } if (empty && getFailOnEmptyArchive()) { throw new BuildException("archive '%s' is empty", srcF); } log("expand complete", Project.MSG_VERBOSE); } catch (IOException ioe) { throw new BuildException( "Error while expanding " + srcF.getPath() + "\n" + ioe.toString(), ioe); } }
This method is to be overridden by extending unarchival tasks.
Params:
  • srcR – the source resource
  • dir – the destination directory
/** * This method is to be overridden by extending unarchival tasks. * * @param srcR the source resource * @param dir the destination directory */
protected void expandResource(Resource srcR, File dir) { throw new BuildException( "only filesystem based resources are supported by this task."); }
get a mapper for a file
Returns:a filenamemapper for a file
/** * get a mapper for a file * @return a filenamemapper for a file */
protected FileNameMapper getMapper() { if (mapperElement != null) { return mapperElement.getImplementation(); } return new IdentityMapper(); } // CheckStyle:ParameterNumberCheck OFF - bc
extract a file to a directory
Params:
  • fileUtils – a fileUtils object
  • srcF – the source file
  • dir – the destination directory
  • compressedInputStream – the input stream
  • entryName – the name of the entry
  • entryDate – the date of the entry
  • isDirectory – if this is true the entry is a directory
  • mapper – the filename mapper to use
Throws:
/** * extract a file to a directory * @param fileUtils a fileUtils object * @param srcF the source file * @param dir the destination directory * @param compressedInputStream the input stream * @param entryName the name of the entry * @param entryDate the date of the entry * @param isDirectory if this is true the entry is a directory * @param mapper the filename mapper to use * @throws IOException on error */
protected void extractFile(FileUtils fileUtils, File srcF, File dir, InputStream compressedInputStream, String entryName, Date entryDate, boolean isDirectory, FileNameMapper mapper) throws IOException { final boolean entryNameStartsWithPathSpec = !entryName.isEmpty() && (entryName.charAt(0) == File.separatorChar || entryName.charAt(0) == '/' || entryName.charAt(0) == '\\'); if (stripAbsolutePathSpec && entryNameStartsWithPathSpec) { log("stripped absolute path spec from " + entryName, Project.MSG_VERBOSE); entryName = entryName.substring(1); } boolean allowedOutsideOfDest = Boolean.TRUE == getAllowFilesToEscapeDest() || null == getAllowFilesToEscapeDest() && !stripAbsolutePathSpec && entryNameStartsWithPathSpec; if (patternsets != null && !patternsets.isEmpty()) { String name = entryName.replace('/', File.separatorChar) .replace('\\', File.separatorChar); Set<String> includePatterns = new HashSet<>(); Set<String> excludePatterns = new HashSet<>(); for (PatternSet p : patternsets) { String[] incls = p.getIncludePatterns(getProject()); if (incls == null || incls.length == 0) { // no include pattern implicitly means includes="**" incls = new String[]{"**"}; } for (String incl : incls) { String pattern = incl.replace('/', File.separatorChar) .replace('\\', File.separatorChar); if (pattern.endsWith(File.separator)) { pattern += "**"; } includePatterns.add(pattern); } String[] excls = p.getExcludePatterns(getProject()); if (excls != null) { for (String excl : excls) { String pattern = excl.replace('/', File.separatorChar) .replace('\\', File.separatorChar); if (pattern.endsWith(File.separator)) { pattern += "**"; } excludePatterns.add(pattern); } } } boolean included = false; for (String pattern : includePatterns) { if (SelectorUtils.matchPath(pattern, name)) { included = true; break; } } for (String pattern : excludePatterns) { if (SelectorUtils.matchPath(pattern, name)) { included = false; break; } } if (!included) { // Do not process this file log("skipping " + entryName + " as it is excluded or not included.", Project.MSG_VERBOSE); return; } } String[] mappedNames = mapper.mapFileName(entryName); if (mappedNames == null || mappedNames.length == 0) { mappedNames = new String[] {entryName}; } File f = fileUtils.resolveFile(dir, mappedNames[0]); if (!allowedOutsideOfDest && !fileUtils.isLeadingPath(dir, f, true)) { log("skipping " + entryName + " as its target " + f.getCanonicalPath() + " is outside of " + dir.getCanonicalPath() + ".", Project.MSG_VERBOSE); return; } try { if (!overwrite && f.exists() && f.lastModified() >= entryDate.getTime()) { log("Skipping " + f + " as it is up-to-date", Project.MSG_DEBUG); return; } log("expanding " + entryName + " to " + f, Project.MSG_VERBOSE); // create intermediary directories - sometimes zip don't add them File dirF = f.getParentFile(); if (dirF != null) { dirF.mkdirs(); } if (isDirectory) { f.mkdirs(); } else { byte[] buffer = new byte[BUFFER_SIZE]; try (OutputStream fos = Files.newOutputStream(f.toPath())) { int length; while ((length = compressedInputStream.read(buffer)) >= 0) { fos.write(buffer, 0, length); } } } fileUtils.setFileLastModified(f, entryDate.getTime()); } catch (FileNotFoundException ex) { log("Unable to expand to file " + f.getPath(), ex, Project.MSG_WARN); } } // CheckStyle:ParameterNumberCheck ON
Set the destination directory. File will be unzipped into the destination directory.
Params:
  • d – Path to the directory.
/** * Set the destination directory. File will be unzipped into the * destination directory. * * @param d Path to the directory. */
public void setDest(File d) { this.dest = d; }
Set the path to zip-file.
Params:
  • s – Path to zip-file.
/** * Set the path to zip-file. * * @param s Path to zip-file. */
public void setSrc(File s) { this.source = s; }
Should we overwrite files in dest, even if they are newer than the corresponding entries in the archive?
Params:
  • b – a boolean value
/** * Should we overwrite files in dest, even if they are newer than * the corresponding entries in the archive? * @param b a <code>boolean</code> value */
public void setOverwrite(boolean b) { overwrite = b; }
Add a patternset.
Params:
  • set – a pattern set
/** * Add a patternset. * @param set a pattern set */
public void addPatternset(PatternSet set) { patternsets.add(set); }
Add a fileset
Params:
  • set – a file set
/** * Add a fileset * @param set a file set */
public void addFileset(FileSet set) { add(set); }
Add a resource collection.
Params:
  • rc – a resource collection.
Since:Ant 1.7
/** * Add a resource collection. * @param rc a resource collection. * @since Ant 1.7 */
public void add(ResourceCollection rc) { resourcesSpecified = true; resources.add(rc); }
Defines the mapper to map source entries to destination files.
Throws:
Returns:a mapper to be configured
Since:Ant1.7
/** * Defines the mapper to map source entries to destination files. * @return a mapper to be configured * @exception BuildException if more than one mapper is defined * @since Ant1.7 */
public Mapper createMapper() throws BuildException { if (mapperElement != null) { throw new BuildException(ERROR_MULTIPLE_MAPPERS, getLocation()); } mapperElement = new Mapper(getProject()); return mapperElement; }
A nested filenamemapper
Params:
  • fileNameMapper – the mapper to add
Since:Ant 1.6.3
/** * A nested filenamemapper * @param fileNameMapper the mapper to add * @since Ant 1.6.3 */
public void add(FileNameMapper fileNameMapper) { createMapper().add(fileNameMapper); }
Sets the encoding to assume for file names and comments.

Set to native-encoding if you want your platform's native encoding, defaults to UTF8.

Params:
  • encoding – the name of the character encoding
Since:Ant 1.6
/** * Sets the encoding to assume for file names and comments. * * <p>Set to <code>native-encoding</code> if you want your * platform's native encoding, defaults to UTF8.</p> * @param encoding the name of the character encoding * @since Ant 1.6 */
public void setEncoding(String encoding) { internalSetEncoding(encoding); }
Supports grand-children that want to support the attribute where the child-class doesn't (i.e. Unzip in the compress Antlib).
Params:
  • encoding – String
Since:Ant 1.8.0
/** * Supports grand-children that want to support the attribute * where the child-class doesn't (i.e. Unzip in the compress * Antlib). * * @param encoding String * @since Ant 1.8.0 */
protected void internalSetEncoding(String encoding) { if (NATIVE_ENCODING.equals(encoding)) { encoding = null; } this.encoding = encoding; }
Returns:String
Since:Ant 1.8.0
/** * @return String * @since Ant 1.8.0 */
public String getEncoding() { return encoding; }
Whether leading path separators should be stripped.
Params:
  • b – boolean
Since:Ant 1.8.0
/** * Whether leading path separators should be stripped. * * @param b boolean * @since Ant 1.8.0 */
public void setStripAbsolutePathSpec(boolean b) { stripAbsolutePathSpec = b; }
Whether unicode extra fields will be used if present.
Params:
  • b – boolean
Since:Ant 1.8.0
/** * Whether unicode extra fields will be used if present. * * @param b boolean * @since Ant 1.8.0 */
public void setScanForUnicodeExtraFields(boolean b) { internalSetScanForUnicodeExtraFields(b); }
Supports grand-children that want to support the attribute where the child-class doesn't (i.e. Unzip in the compress Antlib).
Params:
  • b – boolean
Since:Ant 1.8.0
/** * Supports grand-children that want to support the attribute * where the child-class doesn't (i.e. Unzip in the compress * Antlib). * * @param b boolean * @since Ant 1.8.0 */
protected void internalSetScanForUnicodeExtraFields(boolean b) { scanForUnicodeExtraFields = b; }
Returns:boolean
Since:Ant 1.8.0
/** * @return boolean * @since Ant 1.8.0 */
public boolean getScanForUnicodeExtraFields() { return scanForUnicodeExtraFields; }
Whether to allow the extracted file or directory to be outside of the dest directory.
Params:
  • b – the flag
Since:Ant 1.10.4
/** * Whether to allow the extracted file or directory to be outside of the dest directory. * * @param b the flag * @since Ant 1.10.4 */
public void setAllowFilesToEscapeDest(boolean b) { allowFilesToEscapeDest = b; }
Whether to allow the extracted file or directory to be outside of the dest directory.
Returns:null if the flag hasn't been set explicitly, otherwise the value set by the user.
Since:Ant 1.10.4
/** * Whether to allow the extracted file or directory to be outside of the dest directory. * * @return {@code null} if the flag hasn't been set explicitly, * otherwise the value set by the user. * @since Ant 1.10.4 */
public Boolean getAllowFilesToEscapeDest() { return allowFilesToEscapeDest; } }