/* * 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.types; import java.io.File; import java.util.Iterator; import java.util.Stack; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.DirectoryScanner; import org.apache.tools.ant.Project; import org.apache.tools.ant.types.resources.FileProvider; import org.apache.tools.ant.types.resources.FileResource; import org.apache.tools.zip.UnixStat; /** * A ArchiveFileSet is a FileSet with extra attributes useful in the * context of archiving tasks. * * It includes a prefix attribute which is prepended to each entry in * the output archive file as well as a fullpath attribute. It also * supports Unix file permissions for files and directories. * * @since Ant 1.7 */ public abstract class ArchiveFileSet extends FileSet { private static final int BASE_OCTAL = 8; /** * Default value for the dirmode attribute. * * @since Ant 1.5.2 */ public static final int DEFAULT_DIR_MODE = UnixStat.DIR_FLAG | UnixStat.DEFAULT_DIR_PERM; /** * Default value for the filemode attribute. * * @since Ant 1.5.2 */ public static final int DEFAULT_FILE_MODE = UnixStat.FILE_FLAG | UnixStat.DEFAULT_FILE_PERM; private Resource src = null; private String prefix = ""; private String fullpath = ""; private boolean hasDir = false; private int fileMode = DEFAULT_FILE_MODE; private int dirMode = DEFAULT_DIR_MODE; private boolean fileModeHasBeenSet = false; private boolean dirModeHasBeenSet = false; private static final String ERROR_DIR_AND_SRC_ATTRIBUTES = "Cannot set both dir and src attributes"; private static final String ERROR_PATH_AND_PREFIX = "Cannot set both fullpath and prefix attributes"; private boolean errorOnMissingArchive = true; private String encoding = null; /** Constructor for ArchiveFileSet */ public ArchiveFileSet() { super(); } /** * Constructor using a fileset argument. * @param fileset the fileset to use */ protected ArchiveFileSet(FileSet fileset) { super(fileset); } /** * Constructor using a archive fileset argument. * @param fileset the archivefileset to use */ protected ArchiveFileSet(ArchiveFileSet fileset) { super(fileset); src = fileset.src; prefix = fileset.prefix; fullpath = fileset.fullpath; hasDir = fileset.hasDir; fileMode = fileset.fileMode; dirMode = fileset.dirMode; fileModeHasBeenSet = fileset.fileModeHasBeenSet; dirModeHasBeenSet = fileset.dirModeHasBeenSet; errorOnMissingArchive = fileset.errorOnMissingArchive; encoding = fileset.encoding; } /** * Set the directory for the fileset. * @param dir the directory for the fileset * @throws BuildException on error */ @Override public void setDir(File dir) throws BuildException { checkAttributesAllowed(); if (src != null) { throw new BuildException(ERROR_DIR_AND_SRC_ATTRIBUTES); } super.setDir(dir); hasDir = true; } /** * Set the source Archive file for the archivefileset. Prevents both * "dir" and "src" from being specified. * @param a the archive as a single element Resource collection. */ public void addConfigured(ResourceCollection a) { checkChildrenAllowed(); if (a.size() != 1) { throw new BuildException("only single argument resource collections" + " are supported as archives"); } setSrcResource(a.iterator().next()); } /** * Set the source Archive file for the archivefileset. Prevents both * "dir" and "src" from being specified. * * @param srcFile The archive from which to extract entries. */ public void setSrc(File srcFile) { setSrcResource(new FileResource(srcFile)); } /** * Set the source Archive file for the archivefileset. Prevents both * "dir" and "src" from being specified. * * @param src The archive from which to extract entries. */ public void setSrcResource(Resource src) { checkArchiveAttributesAllowed(); if (hasDir) { throw new BuildException(ERROR_DIR_AND_SRC_ATTRIBUTES); } this.src = src; setChecked(false); } /** * Get the archive from which entries will be extracted. * @param p the project to use * @return the source file */ public File getSrc(Project p) { if (isReference()) { return ((ArchiveFileSet) getRef(p)).getSrc(p); } return getSrc(); } /** * Sets whether an error is thrown if an archive does not exist. * * @param errorOnMissingArchive true if missing archives cause errors, * false if not. * @since Ant 1.8.0 */ public void setErrorOnMissingArchive(boolean errorOnMissingArchive) { checkAttributesAllowed(); this.errorOnMissingArchive = errorOnMissingArchive; } /** * Get the archive file from which entries will be extracted. * @return the archive in case the archive is a file, null otherwise. */ public File getSrc() { if (isReference()) { return ((ArchiveFileSet) getCheckedRef()).getSrc(); } dieOnCircularReference(); if (src == null) { return null; } return src.asOptional(FileProvider.class).map(FileProvider::getFile).orElse(null); } /** * Performs the check for circular references and returns the * referenced object. * This is an override which does not delegate to the superclass; instead it invokes * {@link #getRef(Project)}, because that contains the special support for fileset * references, which can be handled by all ArchiveFileSets. * @param p the Ant Project instance against which to resolve references. * @return the dereferenced object. * @throws BuildException if the reference is invalid (circular ref, wrong class, etc). * @since Ant 1.8 */ // TODO is the above true? AFAICT the calls look circular :/ @Override protected Object getCheckedRef(Project p) { return getRef(p); } /** * Prepend this prefix to the path for each archive entry. * Prevents both prefix and fullpath from being specified * * @param prefix The prefix to prepend to entries in the archive file. */ public void setPrefix(String prefix) { checkArchiveAttributesAllowed(); if (!"".equals(prefix) && !"".equals(fullpath)) { throw new BuildException(ERROR_PATH_AND_PREFIX); } this.prefix = prefix; } /** * Return the prefix prepended to entries in the archive file. * @param p the project to use * @return the prefix */ public String getPrefix(Project p) { if (isReference()) { return ((ArchiveFileSet) getRef(p)).getPrefix(p); } dieOnCircularReference(p); return prefix; } /** * Set the full pathname of the single entry in this fileset. * Prevents both prefix and fullpath from being specified * * @param fullpath the full pathname of the single entry in this fileset. */ public void setFullpath(String fullpath) { checkArchiveAttributesAllowed(); if (!"".equals(prefix) && !"".equals(fullpath)) { throw new BuildException(ERROR_PATH_AND_PREFIX); } this.fullpath = fullpath; } /** * Return the full pathname of the single entry in this fileset. * @param p the project to use * @return the full path */ public String getFullpath(Project p) { if (isReference()) { return ((ArchiveFileSet) getRef(p)).getFullpath(p); } dieOnCircularReference(p); return fullpath; } /** * Set the encoding used for this ZipFileSet. * @param enc encoding as String. * @since Ant 1.9.5 */ public void setEncoding(String enc) { checkAttributesAllowed(); this.encoding = enc; } /** * Get the encoding used for this ZipFileSet. * @return String encoding. * @since Ant 1.9.5 */ public String getEncoding() { if (isReference()) { AbstractFileSet ref = getRef(getProject()); return ref instanceof ArchiveFileSet ? ((ArchiveFileSet) ref).getEncoding() : null; } return encoding; } /** * Creates a scanner for this type of archive. * @return the scanner. */ protected abstract ArchiveScanner newArchiveScanner(); /** * Return the DirectoryScanner associated with this FileSet. * If the ArchiveFileSet defines a source Archive file, then an ArchiveScanner * is returned instead. * @param p the project to use * @return a directory scanner */ @Override public DirectoryScanner getDirectoryScanner(Project p) { if (isReference()) { return getRef(p).getDirectoryScanner(p); } dieOnCircularReference(); if (src == null) { return super.getDirectoryScanner(p); } if (!src.isExists() && errorOnMissingArchive) { throw new BuildException( "The archive " + src.getName() + " doesn't exist"); } if (src.isDirectory()) { throw new BuildException("The archive " + src.getName() + " can't be a directory"); } ArchiveScanner as = newArchiveScanner(); as.setErrorOnMissingArchive(errorOnMissingArchive); as.setSrc(src); super.setDir(p.getBaseDir()); setupDirectoryScanner(as, p); as.init(); return as; } /** * Fulfill the ResourceCollection contract. * @return Iterator of Resources. * @since Ant 1.7 */ @Override public Iterator<Resource> iterator() { if (isReference()) { return ((ResourceCollection) (getRef(getProject()))).iterator(); } if (src == null) { return super.iterator(); } ArchiveScanner as = (ArchiveScanner) getDirectoryScanner(getProject()); return as.getResourceFiles(getProject()); } /** * Fulfill the ResourceCollection contract. * @return size of the collection as int. * @since Ant 1.7 */ @Override public int size() { if (isReference()) { return ((ResourceCollection) (getRef(getProject()))).size(); } if (src == null) { return super.size(); } ArchiveScanner as = (ArchiveScanner) getDirectoryScanner(getProject()); return as.getIncludedFilesCount(); } /** * Indicate whether this ResourceCollection is composed entirely of * Resources accessible via local filesystem conventions. If true, * all Resources returned from this ResourceCollection should be * instances of FileResource. * @return whether this is a filesystem-only resource collection. * @since Ant 1.7 */ @Override public boolean isFilesystemOnly() { if (isReference()) { return ((ArchiveFileSet) getCheckedRef()).isFilesystemOnly(); } dieOnCircularReference(); return src == null; } /** * A 3 digit octal string, specify the user, group and * other modes in the standard Unix fashion; * optional, default=0644 * @param octalString a <code>String</code> value */ public void setFileMode(String octalString) { checkArchiveAttributesAllowed(); integerSetFileMode(Integer.parseInt(octalString, BASE_OCTAL)); } /** * specify the user, group and * other modes in the standard Unix fashion; * optional, default=0644 * * <p>We use the strange name so this method doesn't appear in * IntrospectionHelpers list of attribute setters.</p> * @param mode a <code>int</code> value * @since Ant 1.7 */ public void integerSetFileMode(int mode) { fileModeHasBeenSet = true; this.fileMode = UnixStat.FILE_FLAG | mode; } /** * Get the mode of the archive fileset * @param p the project to use * @return the mode */ public int getFileMode(Project p) { if (isReference()) { return ((ArchiveFileSet) getRef(p)).getFileMode(p); } dieOnCircularReference(); return fileMode; } /** * Whether the user has specified the mode explicitly. * @return true if it has been set */ public boolean hasFileModeBeenSet() { if (isReference()) { return ((ArchiveFileSet) getRef(getProject())).hasFileModeBeenSet(); } dieOnCircularReference(); return fileModeHasBeenSet; } /** * A 3 digit octal string, specify the user, group and * other modes in the standard Unix fashion; * optional, default=0755 * @param octalString a <code>String</code> value */ public void setDirMode(String octalString) { checkArchiveAttributesAllowed(); integerSetDirMode(Integer.parseInt(octalString, BASE_OCTAL)); } /** * specify the user, group and * other modes in the standard Unix fashion; * optional, default=0755 * <p>We use the strange name so this method doesn't appear in * IntrospectionHelpers list of attribute setters.</p> * @param mode a <code>int</code> value * @since Ant 1.7 */ public void integerSetDirMode(int mode) { dirModeHasBeenSet = true; this.dirMode = UnixStat.DIR_FLAG | mode; } /** * Get the dir mode of the archive fileset * @param p the project to use * @return the mode */ public int getDirMode(Project p) { if (isReference()) { return ((ArchiveFileSet) getRef(p)).getDirMode(p); } dieOnCircularReference(); return dirMode; } /** * Whether the user has specified the mode explicitly. * * @return true if it has been set */ public boolean hasDirModeBeenSet() { if (isReference()) { return ((ArchiveFileSet) getRef(getProject())).hasDirModeBeenSet(); } dieOnCircularReference(); return dirModeHasBeenSet; } /** * A ArchiveFileset accepts another ArchiveFileSet or a FileSet as reference * FileSets are often used by the war task for the lib attribute * @param zfs the project to use */ protected void configureFileSet(ArchiveFileSet zfs) { zfs.setPrefix(prefix); zfs.setFullpath(fullpath); zfs.fileModeHasBeenSet = fileModeHasBeenSet; zfs.fileMode = fileMode; zfs.dirModeHasBeenSet = dirModeHasBeenSet; zfs.dirMode = dirMode; } /** * Return a ArchiveFileSet that has the same properties * as this one. * @return the cloned archiveFileSet * @since Ant 1.6 */ @Override public ArchiveFileSet clone() { if (isReference()) { return getCheckedRef(ArchiveFileSet.class, getDataTypeName(), getProject()).clone(); } return (ArchiveFileSet) super.clone(); } /** * For file-based archivefilesets, return the same as for normal filesets; * else just return the path of the zip. * @return for file based archivefilesets, included files as a list * of semicolon-separated filenames. else just the name of the zip. */ @Override public String toString() { if (hasDir && getProject() != null) { return super.toString(); } return src == null ? null : src.getName(); } /** * Return the prefix prepended to entries in the archive file. * @return the prefix. * @deprecated since 1.7. */ @Deprecated public String getPrefix() { return prefix; } /** * Return the full pathname of the single entryZ in this fileset. * @return the full pathname. * @deprecated since 1.7. */ @Deprecated public String getFullpath() { return fullpath; } /** * @return the file mode. * @deprecated since 1.7. */ @Deprecated public int getFileMode() { return fileMode; } /** * @return the dir mode. * @deprecated since 1.7. */ @Deprecated public int getDirMode() { return dirMode; } /** * A check attributes for archiveFileSet. * If there is a reference, and * it is a ArchiveFileSet, the archive fileset attributes * cannot be used. * (Note, we can only see if the reference is an archive * fileset if the project has been set). */ private void checkArchiveAttributesAllowed() { if (getProject() == null || (isReference() && (getRefid().getReferencedObject( getProject()) instanceof ArchiveFileSet))) { checkAttributesAllowed(); } } @Override protected synchronized void dieOnCircularReference(Stack<Object> stk, Project p) throws BuildException { if (isChecked()) { return; } // takes care of nested selectors super.dieOnCircularReference(stk, p); if (!isReference()) { if (src != null) { pushAndInvokeCircularReferenceCheck(src, stk, p); } setChecked(true); } } }