| /* |
| * 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.ant.compress.taskdefs; |
| |
| import java.io.BufferedOutputStream; |
| import java.io.File; |
| import java.io.InputStream; |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.zip.ZipException; |
| |
| import org.apache.ant.compress.resources.ArFileSet; |
| import org.apache.ant.compress.resources.CommonsCompressArchiveResource; |
| import org.apache.ant.compress.resources.CpioFileSet; |
| import org.apache.ant.compress.resources.TarFileSet; |
| import org.apache.ant.compress.resources.TarResource; |
| import org.apache.ant.compress.resources.ZipFileSet; |
| import org.apache.ant.compress.resources.ZipResource; |
| import org.apache.ant.compress.util.ArchiveStreamFactory; |
| import org.apache.ant.compress.util.EntryHelper; |
| import org.apache.ant.compress.util.StreamHelper; |
| |
| import org.apache.commons.compress.archivers.ArchiveEntry; |
| import org.apache.commons.compress.archivers.ArchiveOutputStream; |
| import org.apache.commons.compress.archivers.zip.ExtraFieldUtils; |
| import org.apache.commons.compress.archivers.zip.ZipExtraField; |
| import org.apache.commons.compress.archivers.zip.ZipShort; |
| import org.apache.commons.compress.utils.IOUtils; |
| |
| import org.apache.tools.ant.BuildException; |
| import org.apache.tools.ant.Project; |
| import org.apache.tools.ant.Task; |
| import org.apache.tools.ant.taskdefs.Expand; |
| import org.apache.tools.ant.types.ArchiveFileSet; |
| import org.apache.tools.ant.types.EnumeratedAttribute; |
| import org.apache.tools.ant.types.Resource; |
| import org.apache.tools.ant.types.ResourceCollection; |
| import org.apache.tools.ant.types.resources.ArchiveResource; |
| import org.apache.tools.ant.types.resources.FileResource; |
| import org.apache.tools.ant.types.resources.MappedResource; |
| import org.apache.tools.ant.types.resources.Resources; |
| import org.apache.tools.ant.types.resources.Restrict; |
| import org.apache.tools.ant.types.resources.selectors.Name; |
| import org.apache.tools.ant.types.resources.selectors.Not; |
| import org.apache.tools.ant.types.resources.selectors.Or; |
| import org.apache.tools.ant.util.FileUtils; |
| import org.apache.tools.ant.util.IdentityMapper; |
| import org.apache.tools.ant.util.MergingMapper; |
| import org.apache.tools.ant.util.ResourceUtils; |
| import org.apache.tools.zip.UnixStat; |
| |
| /** |
| * Base implementation of tasks creating archives. |
| */ |
| public abstract class ArchiveBase extends Task { |
| private ArchiveStreamFactory factory; |
| private FileSetBuilder fileSetBuilder; |
| private EntryBuilder entryBuilder; |
| |
| private Resource dest; |
| private List/*<ResourceCollection>*/ sources = new ArrayList(); |
| private Mode mode = new Mode(); |
| private String encoding; |
| private boolean filesOnly = true; |
| private boolean preserve0permissions = false; |
| private boolean roundUp = true; |
| private boolean preserveLeadingSlashes = false; |
| private Duplicate duplicate = new Duplicate(); |
| private WhenEmpty emptyBehavior = new WhenEmpty(); |
| |
| private static final String NO_SOURCES_MSG = "No sources, nothing to do."; |
| |
| private static final FileUtils FILE_UTILS = FileUtils.getFileUtils(); |
| |
| protected ArchiveBase() {} |
| |
| protected final void setFactory(ArchiveStreamFactory factory) { |
| this.factory = factory; |
| } |
| |
| protected final ArchiveStreamFactory getFactory() { |
| return factory; |
| } |
| |
| protected final void setEntryBuilder(EntryBuilder builder) { |
| this.entryBuilder = builder; |
| } |
| |
| protected final EntryBuilder getEntryBuilder() { |
| return entryBuilder; |
| } |
| |
| protected final void setFileSetBuilder(FileSetBuilder builder) { |
| this.fileSetBuilder = builder; |
| } |
| |
| protected final FileSetBuilder getFileSetBuilder() { |
| return fileSetBuilder; |
| } |
| |
| /** |
| * The archive to create. |
| */ |
| public void setDestfile(File f) { |
| setDest(new FileResource(f)); |
| } |
| |
| /** |
| * The archive to create. |
| */ |
| public void addConfiguredDest(Resources r) { |
| for (Iterator it = r.iterator(); it.hasNext(); ) { |
| setDest((Resource) it.next()); |
| } |
| } |
| |
| /** |
| * The archive to create. |
| */ |
| public void setDest(Resource r) { |
| if (dest != null) { |
| throw new BuildException("Can only have one destination resource" |
| + " for archive."); |
| } |
| dest = r; |
| } |
| |
| protected Resource getDest() { |
| return dest; |
| } |
| |
| /** |
| * Sources for the archive. |
| */ |
| public void add(ResourceCollection c) { |
| sources.add(c); |
| } |
| |
| /** |
| * How to treat the target archive. |
| */ |
| public void setMode(Mode m) { |
| mode = m; |
| } |
| |
| protected Mode getMode() { |
| return mode; |
| } |
| |
| /** |
| * Encoding of file names. |
| */ |
| public void setEncoding(String e) { |
| encoding = e; |
| } |
| |
| /** |
| * Encoding of file names. |
| */ |
| public String getEncoding() { |
| return encoding; |
| } |
| |
| /** |
| * Whether only file entries should be added to the archive. |
| */ |
| public void setFilesOnly(boolean b) { |
| filesOnly = b; |
| } |
| |
| /** |
| * Whether only file entries should be added to the archive. |
| */ |
| protected boolean isFilesOnly() { |
| return filesOnly; |
| } |
| |
| /** |
| * Whether 0 permissions read from an archive should be considered |
| * real permissions (that should be preserved) or missing |
| * permissions (which is the default). |
| */ |
| public void setPreserve0permissions(boolean b) { |
| preserve0permissions = b; |
| } |
| |
| /** |
| * Whether the file modification times will be rounded up to the |
| * next timestamp (second or even second depending on the archive |
| * format). |
| * |
| * <p>Zip archives store file modification times with a |
| * granularity of two seconds, ar, tar and cpio use a granularity |
| * of one second. Times will either be rounded up or down. If |
| * you round down, the archive will always seem out-of-date when |
| * you rerun the task, so the default is to round up. Rounding up |
| * may lead to a different type of problems like JSPs inside a web |
| * archive that seem to be slightly more recent than precompiled |
| * pages, rendering precompilation useless.</p> |
| * @param r a <code>boolean</code> value |
| */ |
| public void setRoundUp(boolean r) { |
| roundUp = r; |
| } |
| |
| /** |
| * Flag to indicates whether leading `/'s should |
| * be preserved in the file names. |
| * Optional, default is <code>false</code>. |
| * @param b the leading slashes flag. |
| */ |
| public void setPreserveLeadingSlashes(boolean b) { |
| this.preserveLeadingSlashes = b; |
| } |
| |
| /** |
| * Flag to indicates whether leading `/'s should |
| * be preserved in the file names. |
| * @since Apache Compress Antlib 1.1 |
| */ |
| public boolean getPreserveLeadingSlashes() { |
| return preserveLeadingSlashes; |
| } |
| |
| /** |
| * Sets behavior for when a duplicate file is about to be added - |
| * one of <code>add</code>, <code>preserve</code> or <code>fail</code>. |
| * Possible values are: <code>add</code> (keep both |
| * of the files); <code>preserve</code> (keep the first version |
| * of the file found); <code>fail</code> halt a problem |
| * Default is <code>fail</code> |
| * @param df a <code>Duplicate</code> enumerated value |
| */ |
| public void setDuplicate(Duplicate df) { |
| duplicate = df; |
| } |
| |
| /** |
| * Sets behavior of the task when no resources are to be added. |
| * Possible values are: <code>fail</code> (throw an exception |
| * and halt the build); <code>skip</code> (do not create |
| * any archive, but issue a warning);. |
| * Default is <code>fail</code>; |
| * @param we a <code>WhenEmpty</code> enumerated value |
| */ |
| public void setWhenempty(WhenEmpty we) { |
| emptyBehavior = we; |
| } |
| |
| public void execute() { |
| validate(); |
| final Resource targetArchive = getDest(); |
| if (!targetArchive.isExists()) { |
| // force create mode |
| mode = new Mode(); |
| mode.setValue(Mode.FORCE_CREATE); |
| } |
| Collection sourceResources; |
| try { |
| sourceResources = findSources(); |
| } catch (IOException ioex) { |
| throw new BuildException("Failed to read sources", ioex); |
| } |
| if (sourceResources.size() == 0) { |
| if (WhenEmpty.SKIP.equals(emptyBehavior.getValue())) { |
| log(NO_SOURCES_MSG, Project.MSG_WARN); |
| } else { |
| throw new BuildException(NO_SOURCES_MSG); |
| } |
| } else { |
| File copyOfDest = maybeCopyTarget(); |
| Resource destOrCopy = copyOfDest == null |
| ? targetArchive |
| : new FileResource(copyOfDest); |
| ArchiveFileSet existingEntries = |
| fileSetBuilder.buildFileSet(destOrCopy); |
| existingEntries.setProject(getProject()); |
| try { |
| |
| List/*<ResourceWithFlags>*/ toAdd |
| = new ArrayList/*<ResourceWithFlags>*/(); |
| toAdd.addAll(sourceResources); |
| |
| if (checkAndLogUpToDate(toAdd, targetArchive, |
| existingEntries)) { |
| return; |
| } |
| |
| addResourcesToKeep(toAdd, existingEntries, sourceResources); |
| sort(toAdd); |
| |
| try { |
| writeArchive(toAdd); |
| } catch (IOException ioex) { |
| throw new BuildException("Failed to write archive", ioex); |
| } |
| } finally { |
| if (copyOfDest != null) { |
| FILE_UTILS.tryHardToDelete(copyOfDest); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Argument validation. |
| */ |
| protected void validate() throws BuildException { |
| if (factory == null) { |
| throw new BuildException("subclass didn't provide a factory" |
| + " instance"); |
| } |
| if (entryBuilder == null) { |
| throw new BuildException("subclass didn't provide an entryBuilder" |
| + " instance"); |
| } |
| if (fileSetBuilder == null) { |
| throw new BuildException("subclass didn't provide a fileSetBuilder" |
| + " instance"); |
| } |
| if (getDest() == null) { |
| throw new BuildException("must provide a destination resource"); |
| } |
| if (sources.size() == 0) { |
| throw new BuildException("must provide sources"); |
| } |
| } |
| |
| /** |
| * Find all the resources with their flags that should be added to |
| * the archive. |
| */ |
| protected Collection/*<ResourceWithFlags>*/ findSources() |
| throws IOException { |
| |
| List/*<ResourceWithFlags>*/ l = new ArrayList/*<ResourceWithFlags>*/(); |
| Set/*<String>*/ addedNames = new HashSet/*<String>*/(); |
| for (Iterator rcs = sources.iterator(); rcs.hasNext(); ) { |
| ResourceCollection rc = (ResourceCollection) rcs.next(); |
| ResourceCollectionFlags rcFlags = getFlags(rc); |
| for (Iterator rs = rc.iterator(); rs.hasNext(); ) { |
| Resource r = (Resource) rs.next(); |
| if (!isFilesOnly() || !r.isDirectory()) { |
| ResourceWithFlags rwf = |
| new ResourceWithFlags(r, rcFlags, getFlags(r)); |
| String name = rwf.getName(); |
| if (!"".equals(name) && !"/".equals(name)) { |
| boolean isDup = !addedNames.add(name); |
| if (!isDup || addDuplicate(name)) { |
| l.add(rwf); |
| } |
| } |
| } |
| } |
| } |
| return l; |
| } |
| |
| private boolean checkAndLogUpToDate(Collection/*<ResourceWithFlags>*/ src, |
| Resource targetArchive, |
| ArchiveFileSet existingEntries) { |
| try { |
| if (!Mode.FORCE_CREATE.equals(getMode().getValue()) |
| && !Mode.FORCE_REPLACE.equals(getMode().getValue()) |
| && isUpToDate(src, existingEntries)) { |
| log(targetArchive + " is up-to-date, nothing to do."); |
| return true; |
| } |
| } catch (IOException ioex) { |
| throw new BuildException("Failed to read target archive", ioex); |
| } |
| return false; |
| } |
| |
| /** |
| * Checks whether the target is more recent than the resources |
| * that shall be added to it. |
| * |
| * <p>Will only ever be invoked if the target exists.</p> |
| * |
| * @param src the resources that have been found as sources, may |
| * be modified in "update" mode to remove entries that are up to |
| * date |
| * @param existingEntries the target archive as fileset |
| * |
| * @return true if the target is up-to-date |
| */ |
| protected boolean isUpToDate(Collection/*<ResourceWithFlags>*/ src, |
| ArchiveFileSet existingEntries) |
| throws IOException { |
| |
| final Resource[] srcResources = new Resource[src.size()]; |
| int index = 0; |
| for (Iterator i = src.iterator(); i.hasNext(); ) { |
| ResourceWithFlags r = (ResourceWithFlags) i.next(); |
| srcResources[index++] = |
| new MappedResource(r.getResource(), |
| new MergingMapper(r.getName())); |
| } |
| Resource[] outOfDate = ResourceUtils |
| .selectOutOfDateSources(this, srcResources, |
| new IdentityMapper(), |
| existingEntries |
| .getDirectoryScanner(getProject())); |
| if (outOfDate.length > 0 && Mode.UPDATE.equals(getMode().getValue())) { |
| HashSet/*<String>*/ oodNames = new HashSet/*<String>*/(); |
| for (int i = 0; i < outOfDate.length; i++) { |
| oodNames.add(outOfDate[i].getName()); |
| } |
| List/*<ResourceWithFlags>*/ copy = |
| new LinkedList/*<ResourceWithFlags>*/(src); |
| src.clear(); |
| for (Iterator i = copy.iterator(); i.hasNext(); ) { |
| ResourceWithFlags r = (ResourceWithFlags) i.next(); |
| if (oodNames.contains(r.getName())) { |
| src.add(r); |
| } |
| } |
| } |
| return outOfDate.length == 0; |
| } |
| |
| /** |
| * Add the resources of the target archive that shall be kept when |
| * creating the new one. |
| */ |
| private void addResourcesToKeep(Collection/*<ResourceWithFlags>*/ toAdd, |
| ArchiveFileSet target, |
| Collection/*<ResourceWithFlags>*/ src) { |
| if (!Mode.FORCE_CREATE.equals(getMode().getValue()) |
| && !Mode.CREATE.equals(getMode().getValue())) { |
| try { |
| toAdd.addAll(findUnmatchedTargets(target, src)); |
| } catch (IOException ioex) { |
| throw new BuildException("Failed to read target archive", ioex); |
| } |
| } |
| } |
| |
| /** |
| * Find the resources from the target archive that don't have a |
| * matching resource in the sources to be added. |
| */ |
| protected Collection/*<ResourceWithFlags>*/ |
| findUnmatchedTargets(ArchiveFileSet target, |
| Collection/*<ResourceWithFlags>*/ src) |
| throws IOException { |
| |
| List/*<ResourceWithFlags>*/ l = new ArrayList/*<ResourceWithFlags>*/(); |
| ResourceCollectionFlags rcFlags = getFlags(target); |
| |
| Restrict res = new Restrict(); |
| res.setProject(getProject()); |
| res.add(target); |
| |
| Not not = new Not(); |
| Or or = new Or(); |
| not.add(or); |
| for (Iterator i = src.iterator(); i.hasNext(); ) { |
| ResourceWithFlags r = (ResourceWithFlags) i.next(); |
| Name name = new Name(); |
| name.setName(r.getName()); |
| or.add(name); |
| } |
| res.add(not); |
| |
| for (Iterator rs = res.iterator(); rs.hasNext(); ) { |
| Resource r = (Resource) rs.next(); |
| String name = r.getName(); |
| if ("".equals(name) || "/".equals(name)) { |
| continue; |
| } |
| if (!isFilesOnly() || !r.isDirectory()) { |
| l.add(new ResourceWithFlags(r, rcFlags, getFlags(r))); |
| } |
| } |
| |
| return l; |
| } |
| |
| /** |
| * Sorts the list of resources to add. |
| */ |
| protected void sort(List/*<ResourceWithFlags>*/ l) { |
| Collections.sort(l, new Comparator/*<ResourceWithFlags>*/() { |
| public int compare(Object o1, Object o2) { |
| ResourceWithFlags r1 = (ResourceWithFlags) o1; |
| ResourceWithFlags r2 = (ResourceWithFlags) o2; |
| return r1.getName().compareTo(r2.getName()); |
| } |
| }); |
| } |
| |
| /** |
| * Creates the archive archiving the given resources. |
| */ |
| protected void writeArchive(Collection/*<ResourceWithFlags>*/ src) |
| throws IOException { |
| ArchiveOutputStream out = null; |
| Set addedDirectories = new HashSet(); |
| try { |
| String enc = Expand.NATIVE_ENCODING.equals(getEncoding()) |
| ? null : getEncoding(); |
| out = StreamHelper.getOutputStream(factory, getDest(), enc); |
| if (out == null) { |
| out = |
| factory.getArchiveStream(new BufferedOutputStream(getDest() |
| .getOutputStream()), |
| enc); |
| } |
| for (Iterator i = src.iterator(); i.hasNext(); ) { |
| ResourceWithFlags r = (ResourceWithFlags) i.next(); |
| |
| if (!isFilesOnly()) { |
| ensureParentDirs(out, r, addedDirectories); |
| } |
| |
| ArchiveEntry ent = entryBuilder.buildEntry(r); |
| out.putArchiveEntry(ent); |
| if (!r.getResource().isDirectory()) { |
| InputStream in = null; |
| try { |
| in = r.getResource().getInputStream(); |
| IOUtils.copy(in, out); |
| } finally { |
| FILE_UTILS.close(in); |
| } |
| } else { |
| addedDirectories.add(r.getName()); |
| } |
| out.closeArchiveEntry(); |
| |
| } |
| } finally { |
| FILE_UTILS.close(out); |
| } |
| } |
| |
| /** |
| * Adds records for all parent directories of the given resource |
| * that haven't already been added. |
| * |
| * <p>Flags for the "missing" directories will be taken from the |
| * ResourceCollection that contains the resource to be added.</p> |
| */ |
| protected void ensureParentDirs(ArchiveOutputStream out, |
| ResourceWithFlags r, |
| Set directoriesAdded) |
| throws IOException { |
| |
| String[] parentStack = FileUtils.getPathStack(r.getName()); |
| String currentParent = ""; |
| int skip = r.getName().endsWith("/") ? 2 : 1; |
| for (int i = 0; i < parentStack.length - skip; i++) { |
| if ("".equals(parentStack[i])) continue; |
| currentParent += parentStack[i] + "/"; |
| if (directoriesAdded.add(currentParent)) { |
| Resource dir = new Resource(currentParent, true, |
| System.currentTimeMillis(), |
| true); |
| ResourceWithFlags artifical = |
| new ResourceWithFlags(currentParent, |
| dir, r.getCollectionFlags(), |
| new ResourceFlags()); |
| ArchiveEntry ent = entryBuilder.buildEntry(artifical); |
| out.putArchiveEntry(ent); |
| out.closeArchiveEntry(); |
| } |
| } |
| } |
| |
| /** |
| * Extracts flags from a resource. |
| * |
| * <p>ZipExceptions are only here for the code that translates |
| * Ant's ZipExtraFields to CC ZipExtraFields and should never |
| * actually be thrown.</p> |
| */ |
| protected ResourceFlags getFlags(Resource r) throws ZipException { |
| if (r instanceof ArchiveResource) { |
| if (r instanceof CommonsCompressArchiveResource) { |
| if (r instanceof TarResource) { |
| TarResource tr = (TarResource) r; |
| return new ResourceFlags(tr.getMode(), tr.getUid(), |
| tr.getGid(), tr.getUserName(), |
| tr.getGroup()); |
| } else if (r instanceof ZipResource) { |
| ZipResource zr = (ZipResource) r; |
| return new ResourceFlags(zr.getMode(), zr.getExtraFields(), |
| zr.getMethod()); |
| } else { |
| CommonsCompressArchiveResource cr = |
| (CommonsCompressArchiveResource) r; |
| return new ResourceFlags(cr.getMode(), cr.getUid(), |
| cr.getGid()); |
| } |
| } else if (r instanceof |
| org.apache.tools.ant.types.resources.TarResource) { |
| org.apache.tools.ant.types.resources.TarResource tr = |
| (org.apache.tools.ant.types.resources.TarResource) r; |
| return new ResourceFlags(tr.getMode(), tr.getUid(), |
| tr.getGid(), tr.getUserName(), |
| tr.getGroup()); |
| } else if (r instanceof |
| org.apache.tools.ant.types.resources.ZipResource) { |
| org.apache.tools.ant.types.resources.ZipResource zr = |
| (org.apache.tools.ant.types.resources.ZipResource) r; |
| |
| org.apache.tools.zip.ZipExtraField[] extra = zr.getExtraFields(); |
| ZipExtraField[] ex = |
| new ZipExtraField[extra == null ? 0 : extra.length]; |
| if (extra != null && extra.length > 0) { |
| for (int i = 0; i < extra.length; i++) { |
| try { |
| ex[i] = ExtraFieldUtils |
| .createExtraField(new ZipShort(extra[i] |
| .getHeaderId() |
| .getValue())); |
| } catch (InstantiationException e) { |
| throw new BuildException(e); |
| } catch (IllegalAccessException e) { |
| throw new BuildException(e); |
| } |
| byte[] b = extra[i].getCentralDirectoryData(); |
| ex[i].parseFromCentralDirectoryData(b, 0, b.length); |
| b = extra[i].getLocalFileDataData(); |
| ex[i].parseFromLocalFileData(b, 0, b.length); |
| } |
| } |
| |
| return new ResourceFlags(zr.getMode(), ex, zr.getMethod()); |
| } else { |
| ArchiveResource ar = (ArchiveResource) r; |
| return new ResourceFlags(ar.getMode()); |
| } |
| } |
| return new ResourceFlags(); |
| } |
| |
| /** |
| * Extracts flags from a resource collection. |
| */ |
| protected ResourceCollectionFlags getFlags(ResourceCollection rc) { |
| if (rc instanceof ArchiveFileSet) { |
| if (rc instanceof ArFileSet) { |
| ArFileSet ar = (ArFileSet) rc; |
| return new ResourceCollectionFlags(ar.getPrefix(getProject()), |
| ar.getFullpath(getProject()), |
| ar.hasFileModeBeenSet() |
| ? ar.getFileMode(getProject()) |
| : -1, |
| ar.hasDirModeBeenSet() |
| ? ar.getDirMode(getProject()) |
| : -1, |
| ar.hasUserIdBeenSet() |
| ? ar.getUid() |
| : EntryHelper.UNKNOWN_ID, |
| ar.hasGroupIdBeenSet() |
| ? ar.getGid() |
| : EntryHelper.UNKNOWN_ID); |
| } else if (rc instanceof CpioFileSet) { |
| CpioFileSet cr = (CpioFileSet) rc; |
| return new ResourceCollectionFlags(cr.getPrefix(getProject()), |
| cr.getFullpath(getProject()), |
| cr.hasFileModeBeenSet() |
| ? cr.getFileMode(getProject()) |
| : -1, |
| cr.hasDirModeBeenSet() |
| ? cr.getDirMode(getProject()) |
| : -1, |
| cr.hasUserIdBeenSet() |
| ? cr.getUid() |
| : EntryHelper.UNKNOWN_ID, |
| cr.hasGroupIdBeenSet() |
| ? cr.getGid() |
| : EntryHelper.UNKNOWN_ID); |
| } else if (rc instanceof TarFileSet) { |
| TarFileSet tr = (TarFileSet) rc; |
| return new ResourceCollectionFlags(tr.getPrefix(getProject()), |
| tr.getFullpath(getProject()), |
| tr.hasFileModeBeenSet() |
| ? tr.getFileMode(getProject()) |
| : -1, |
| tr.hasDirModeBeenSet() |
| ? tr.getDirMode(getProject()) |
| : -1, |
| tr.hasUserIdBeenSet() |
| ? tr.getUid() |
| : EntryHelper.UNKNOWN_ID, |
| tr.hasGroupIdBeenSet() |
| ? tr.getGid() |
| : EntryHelper.UNKNOWN_ID, |
| tr.hasUserNameBeenSet() |
| ? tr.getUserName() : null, |
| tr.hasGroupBeenSet() |
| ? tr.getGroup() : null); |
| } else if (rc instanceof |
| org.apache.tools.ant.types.TarFileSet) { |
| org.apache.tools.ant.types.TarFileSet tr = |
| (org.apache.tools.ant.types.TarFileSet) rc; |
| return new ResourceCollectionFlags(tr.getPrefix(getProject()), |
| tr.getFullpath(getProject()), |
| tr.hasFileModeBeenSet() |
| ? tr.getFileMode(getProject()) |
| : -1, |
| tr.hasDirModeBeenSet() |
| ? tr.getDirMode(getProject()) |
| : -1, |
| tr.hasUserIdBeenSet() |
| ? tr.getUid() |
| : EntryHelper.UNKNOWN_ID, |
| tr.hasGroupIdBeenSet() |
| ? tr.getGid() |
| : EntryHelper.UNKNOWN_ID, |
| tr.hasUserNameBeenSet() |
| ? tr.getUserName() : null, |
| tr.hasGroupBeenSet() |
| ? tr.getGroup() : null); |
| } else { |
| ArchiveFileSet ar = (ArchiveFileSet) rc; |
| return new ResourceCollectionFlags(ar.getPrefix(getProject()), |
| ar.getFullpath(getProject()), |
| ar.hasFileModeBeenSet() |
| ? ar.getFileMode(getProject()) |
| : -1, |
| ar.hasDirModeBeenSet() |
| ? ar.getDirMode(getProject()) |
| : -1); |
| } |
| } |
| return new ResourceCollectionFlags(); |
| } |
| |
| /** |
| * Ensures a forward slash is used as file separator and strips |
| * leading slashes if preserveLeadingSlashes is false. |
| */ |
| protected String bendSlashesForward(String s) { |
| if (s != null) { |
| s = s.replace('\\', '/'); |
| if (File.separatorChar != '/' && File.separatorChar != '\\') { |
| s = s.replace(File.separatorChar, '/'); |
| } |
| while (!preserveLeadingSlashes && s.startsWith("/")) { |
| s = s.substring(1); |
| } |
| } |
| return s; |
| } |
| |
| /** |
| * Modify last modified timestamp based on the roundup attribute. |
| * |
| * @param millis the timestamp |
| * @param granularity the granularity of timestamps in the archive |
| * format in millis |
| */ |
| protected long round(long millis, long granularity) { |
| return roundUp ? millis + granularity - 1 : millis; |
| } |
| |
| /** |
| * Is invoked if a duplicate entry is found, decides whether the |
| * entry shall be added regardless. |
| */ |
| protected boolean addDuplicate(String name) { |
| if (duplicate.getValue().equals(Duplicate.PRESERVE)) { |
| log(name + " already added, skipping.", Project.MSG_INFO); |
| return false; |
| } else if (duplicate.getValue().equals(Duplicate.FAIL)) { |
| throw new BuildException("Duplicate entry " + name |
| + " was found and the duplicate " |
| + "attribute is 'fail'."); |
| } |
| // duplicate equal to add, so we continue |
| log("duplicate entry " + name + " found, adding.", Project.MSG_VERBOSE); |
| return true; |
| } |
| |
| /** |
| * Creates a copy of the target archive in update or recreate mode |
| * because some entries may later be read and archived from it. |
| */ |
| private File maybeCopyTarget() { |
| File copyOfDest = null; |
| try { |
| if (!Mode.FORCE_CREATE.equals(getMode().getValue()) |
| && !Mode.CREATE.equals(getMode().getValue())) { |
| copyOfDest = FILE_UTILS.createTempFile(getTaskName(), ".tmp", |
| null, true, false); |
| ResourceUtils.copyResource(getDest(), |
| new FileResource(copyOfDest)); |
| } |
| } catch (IOException ioex) { |
| if (copyOfDest != null && copyOfDest.exists()) { |
| FILE_UTILS.tryHardToDelete(copyOfDest); |
| } |
| throw new BuildException("Failed to copy target archive", ioex); |
| } |
| return copyOfDest; |
| } |
| |
| /** |
| * Valid Modes for create/update/replace. |
| */ |
| public static final class Mode extends EnumeratedAttribute { |
| |
| /** |
| * Create a new archive. |
| */ |
| public static final String CREATE = "create"; |
| /** |
| * Create a new archive even if the target exists and seems |
| * up-to-date. |
| */ |
| public static final String FORCE_CREATE = "force-create"; |
| /** |
| * Update an existing archive. |
| */ |
| public static final String UPDATE = "update"; |
| /** |
| * Update an existing archive, replacing all existing entries |
| * with those from sources. |
| */ |
| public static final String REPLACE = "replace"; |
| /** |
| * Update an existing archive - replacing all existing entries |
| * with those from sources - even if the target exists and |
| * seems up-to-date. |
| */ |
| public static final String FORCE_REPLACE = "force-replace"; |
| |
| public Mode() { |
| super(); |
| setValue(CREATE); |
| } |
| |
| public String[] getValues() { |
| return new String[] {CREATE, UPDATE, REPLACE, |
| FORCE_CREATE, FORCE_REPLACE}; |
| } |
| |
| } |
| |
| /** |
| * Possible behaviors when a duplicate file is added: |
| * "add", "preserve" or "fail" |
| */ |
| public static class Duplicate extends EnumeratedAttribute { |
| private static String ADD = "add"; |
| private static String PRESERVE = "preserve"; |
| private static String FAIL = "fail"; |
| |
| public Duplicate() { |
| setValue(FAIL); |
| } |
| |
| /** |
| * @see EnumeratedAttribute#getValues() |
| */ |
| /** {@inheritDoc} */ |
| public String[] getValues() { |
| return new String[] {ADD, PRESERVE, FAIL}; |
| } |
| } |
| |
| /** |
| * Possible behaviors when there are no matching files for the task: |
| * "fail", "skip". |
| */ |
| public static class WhenEmpty extends EnumeratedAttribute { |
| private static String FAIL = "fail"; |
| private static String SKIP = "skip"; |
| |
| public WhenEmpty() { |
| setValue(FAIL); |
| } |
| |
| /** |
| * The string values for the enumerated value |
| * @return the values |
| */ |
| public String[] getValues() { |
| return new String[] {FAIL, SKIP}; |
| } |
| } |
| |
| /** |
| * Various flags a (archive) resource may hold in addition to |
| * being a plain resource. |
| */ |
| public class ResourceFlags { |
| private final int mode; |
| private final boolean modeSet; |
| private final int gid; |
| private final int uid; |
| private final ZipExtraField[] extraFields; |
| private final String userName; |
| private final String groupName; |
| private final int compressionMethod; |
| |
| public ResourceFlags() { |
| this(-1); |
| } |
| |
| public ResourceFlags(int mode) { |
| this(mode, new ZipExtraField[0], -1); |
| } |
| |
| public ResourceFlags(int mode, ZipExtraField[] extraFields, |
| int compressionMethod) { |
| this(mode, extraFields, EntryHelper.UNKNOWN_ID, |
| EntryHelper.UNKNOWN_ID, null, null, |
| compressionMethod); |
| } |
| |
| public ResourceFlags(int mode, int uid, int gid) { |
| this(mode, new ZipExtraField[0], uid, gid, null, null, -1); |
| } |
| |
| public ResourceFlags(int mode, int uid, int gid, String userName, |
| String groupName) { |
| this(mode, new ZipExtraField[0], uid, gid, userName, groupName, -1); |
| } |
| |
| private ResourceFlags(int mode, ZipExtraField[] extraFields, |
| int uid, int gid, |
| String userName, String groupName, |
| int compressionMethod) { |
| this.mode = mode; |
| this.extraFields = extraFields; |
| this.gid = gid; |
| this.uid = uid; |
| this.userName = userName; |
| this.groupName = groupName; |
| int m = mode & UnixStat.PERM_MASK; |
| modeSet = mode >= 0 && (m > 0 || (m == 0 && preserve0permissions)); |
| this.compressionMethod = compressionMethod; |
| } |
| |
| public boolean hasModeBeenSet() { return modeSet; } |
| public int getMode() { return mode; } |
| |
| public ZipExtraField[] getZipExtraFields() { return extraFields; } |
| |
| public boolean hasUserIdBeenSet() { |
| return uid != EntryHelper.UNKNOWN_ID; |
| } |
| public int getUserId() { return uid; } |
| |
| public boolean hasGroupIdBeenSet() { |
| return gid != EntryHelper.UNKNOWN_ID; |
| } |
| public int getGroupId() { return gid; } |
| |
| public boolean hasUserNameBeenSet() { return userName != null; } |
| public String getUserName() { return userName; } |
| |
| public boolean hasGroupNameBeenSet() { return groupName != null; } |
| public String getGroupName() { return groupName; } |
| |
| public boolean hasCompressionMethod() { return compressionMethod >= 0; } |
| public int getCompressionMethod() { return compressionMethod; } |
| } |
| |
| /** |
| * Various flags a (archive) resource collection may hold. |
| */ |
| public class ResourceCollectionFlags extends ResourceFlags { |
| private final String prefix, fullpath; |
| private final int dirMode; |
| |
| public ResourceCollectionFlags() { |
| this(null, null); |
| } |
| |
| public ResourceCollectionFlags(String prefix, String fullpath) { |
| this(prefix, fullpath, -1, -1); |
| } |
| |
| public ResourceCollectionFlags(String prefix, String fullpath, |
| int fileMode, int dirMode) { |
| this(prefix, fullpath, fileMode, dirMode, EntryHelper.UNKNOWN_ID, |
| EntryHelper.UNKNOWN_ID); |
| } |
| |
| public ResourceCollectionFlags(String prefix, String fullpath, |
| int fileMode, int dirMode, |
| int uid, int gid) { |
| this(prefix, fullpath, fileMode, dirMode, uid, gid, null, null); |
| } |
| |
| public ResourceCollectionFlags(String prefix, String fullpath, |
| int fileMode, int dirMode, |
| int uid, int gid, |
| String userName, String groupName) { |
| super(fileMode, uid, gid, userName, groupName); |
| this.dirMode = dirMode; |
| this.prefix = bendSlashesForward(prefix); |
| this.fullpath = bendSlashesForward(fullpath); |
| } |
| |
| public boolean hasDirModeBeenSet() { return dirMode >= 0; } |
| public int getDirMode() { return dirMode; } |
| |
| public boolean hasPrefix() { |
| return prefix != null && !"".equals(prefix); |
| } |
| public String getPrefix() { return prefix; } |
| |
| public boolean hasFullpath() { |
| return fullpath != null && !"".equals(fullpath); |
| } |
| public String getFullpath() { return fullpath; } |
| } |
| |
| /** |
| * Binds a resource to additional data that may be present. |
| */ |
| public class ResourceWithFlags { |
| private final Resource r; |
| private final ResourceCollectionFlags rcFlags; |
| private final ResourceFlags rFlags; |
| private final String name; |
| |
| public ResourceWithFlags(Resource r, ResourceCollectionFlags rcFlags, |
| ResourceFlags rFlags) { |
| this(null, r, rcFlags, rFlags); |
| } |
| |
| public ResourceWithFlags(String name, Resource r, |
| ResourceCollectionFlags rcFlags, |
| ResourceFlags rFlags) { |
| this.r = r; |
| this.rcFlags = rcFlags; |
| this.rFlags = rFlags; |
| |
| if (name == null) { |
| name = r.getName(); |
| if (rcFlags.hasFullpath()) { |
| name = rcFlags.getFullpath(); |
| } else if (rcFlags.hasPrefix()) { |
| String prefix = rcFlags.getPrefix(); |
| if (!prefix.endsWith("/")) { |
| prefix = prefix + "/"; |
| } |
| name = prefix + name; |
| } |
| name = bendSlashesForward(name); |
| } |
| if (r.isDirectory() && !name.endsWith("/")) { |
| name += "/"; |
| } else if (!r.isDirectory() && name.endsWith("/")) { |
| name = name.substring(0, name.length() - 1); |
| } |
| |
| this.name = name; |
| } |
| |
| public Resource getResource() { return r; } |
| public ResourceCollectionFlags getCollectionFlags() { return rcFlags; } |
| public ResourceFlags getResourceFlags() { return rFlags; } |
| |
| /** |
| * The name the target entry will have. |
| * |
| * <p>Already takes fullpath and prefix into account.</p> |
| * |
| * <p>Ensures directory names end in slashes while file names |
| * never will.</p> |
| */ |
| public String getName() { |
| return name; |
| } |
| } |
| |
| /** |
| * Creates an archive entry for the concrete format. |
| */ |
| public static interface EntryBuilder { |
| ArchiveEntry buildEntry(ResourceWithFlags resource); |
| } |
| |
| /** |
| * Creates an archive fileset to read the target archive. |
| */ |
| public static interface FileSetBuilder { |
| ArchiveFileSet buildFileSet(Resource dest); |
| } |
| } |