|  | /* | 
|  | * Copyright 2008 Google Inc. | 
|  | * | 
|  | * Licensed 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 com.google.gwt.ant.taskdefs; | 
|  |  | 
|  | import org.apache.tools.ant.BuildException; | 
|  | import org.apache.tools.ant.taskdefs.Tar; | 
|  | import org.apache.tools.ant.types.EnumeratedAttribute; | 
|  | import org.apache.tools.bzip2.CBZip2InputStream; | 
|  | import org.apache.tools.tar.TarEntry; | 
|  | import org.apache.tools.tar.TarInputStream; | 
|  | import org.apache.tools.tar.TarOutputStream; | 
|  |  | 
|  | import java.io.BufferedInputStream; | 
|  | import java.io.File; | 
|  | import java.io.FileInputStream; | 
|  | import java.io.IOException; | 
|  | import java.io.InputStream; | 
|  | import java.util.IdentityHashMap; | 
|  | import java.util.Map; | 
|  | import java.util.zip.GZIPInputStream; | 
|  |  | 
|  | /** | 
|  | * An extension to the Ant Tar task that supports slurping in other tar files | 
|  | * without loss of information (such as permissions or symlinks). It behaves in | 
|  | * all respects like the basic Tar task, but adds the nested element | 
|  | * {@code <includetar>} which declares another tar file whose <i>contents</i> | 
|  | * should be added to the file being created. | 
|  | * | 
|  | * In addition to preserving permissions and symlinks no matter what the host | 
|  | * operating system is, there are performance advantages to this approach. | 
|  | * Bypassing the file system cuts the disk activity to 50% or less. The | 
|  | * intermediate files normally generated require data the size of the tar itself | 
|  | * to be both written and read, not to mention the overhead of creating the | 
|  | * individual files, which will generally have to be deleted later. Furthurmore, | 
|  | * since the source and target are often zipped, the savings can be well over | 
|  | * 50%. | 
|  | * | 
|  | * Example use: | 
|  | * | 
|  | * <pre> | 
|  | * <taskdef name="tar.cat" | 
|  | *     classname="com.google.gwt.ant.taskdefs.TarCat" | 
|  | *     classpath="${gwt.build.lib}/ant-gwt.jar" /> | 
|  | * <tar.cat destfile="foo.tar.gz" compression="gzip" longfile="gnu"> | 
|  | *   <!-- all normal tar attributes and elements supported --> | 
|  | *   <tarfileset dir="foo/src"> | 
|  | *     <include name="*.class" /> | 
|  | *   </tarfileset> | 
|  | *   <!-- tar.cat adds the ability to directly slurp in other tar files --> | 
|  | *   <includetar src="bar.tar.gz" compression="gzip" prefix="bar/" /> | 
|  | * </tar.cat> | 
|  | * </pre> | 
|  | */ | 
|  | public class TarCat extends Tar { | 
|  |  | 
|  | /** | 
|  | * This is a tar file that should be included into a tar operation. | 
|  | */ | 
|  | public static class IncludeTar { | 
|  | /** | 
|  | * The compression method to use to access the included tar file. | 
|  | */ | 
|  | private UntarCompressionMethod compression = new UntarCompressionMethod(); | 
|  |  | 
|  | /** | 
|  | * This instance's peer object that is a member of {@link TarCat}'s | 
|  | * superclass. | 
|  | */ | 
|  | private TarFileSet peer; | 
|  |  | 
|  | /** | 
|  | * Constructs a new IncludeTar. | 
|  | * | 
|  | * @param peer this instance's peer object that is a member of | 
|  | *          {@link TarCat}'s superclass | 
|  | */ | 
|  | public IncludeTar(TarFileSet peer) { | 
|  | this.peer = peer; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Set decompression algorithm to use; default=none. | 
|  | * | 
|  | * Allowable values are | 
|  | * <ul> | 
|  | * <li>none - no compression | 
|  | * <li>gzip - Gzip compression | 
|  | * <li>bzip2 - Bzip2 compression | 
|  | * </ul> | 
|  | * | 
|  | * @param method compression method | 
|  | */ | 
|  | public void setCompression(UntarCompressionMethod method) { | 
|  | compression = method; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * If the prefix attribute is set, all files in the fileset are prefixed | 
|  | * with that path in the archive. optional. | 
|  | * | 
|  | * @param prefix the path prefix. | 
|  | */ | 
|  | public void setPrefix(String prefix) { | 
|  | peer.setPrefix(prefix); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Set the name/location of a tar file to add to a tar operation. | 
|  | * | 
|  | * @param tarFile the tar file to read | 
|  | */ | 
|  | public void setSrc(File tarFile) { | 
|  | peer.setFile(tarFile); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Straight copy from | 
|  | * {@link org.apache.tools.ant.taskdefs.Untar.UntarCompressionMethod} due to | 
|  | * access restrictions. | 
|  | */ | 
|  | public static final class UntarCompressionMethod extends EnumeratedAttribute { | 
|  |  | 
|  | private static final String BZIP2 = "bzip2"; | 
|  | private static final String GZIP = "gzip"; | 
|  | private static final String NONE = "none"; | 
|  |  | 
|  | public UntarCompressionMethod() { | 
|  | super(); | 
|  | setValue(NONE); | 
|  | } | 
|  |  | 
|  | public String[] getValues() { | 
|  | return new String[] {NONE, GZIP, BZIP2}; | 
|  | } | 
|  |  | 
|  | private InputStream decompress(final File file, final InputStream istream) | 
|  | throws IOException, BuildException { | 
|  | final String value = getValue(); | 
|  | if (GZIP.equals(value)) { | 
|  | return new GZIPInputStream(istream); | 
|  | } else { | 
|  | if (BZIP2.equals(value)) { | 
|  | final char[] magic = new char[] {'B', 'Z'}; | 
|  | for (int i = 0; i < magic.length; i++) { | 
|  | if (istream.read() != magic[i]) { | 
|  | throw new BuildException("Invalid bz2 file." + file.toString()); | 
|  | } | 
|  | } | 
|  | return new CBZip2InputStream(istream); | 
|  | } | 
|  | } | 
|  | return istream; | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * A map to the set of {@link IncludeTar}s from their peer objects. | 
|  | */ | 
|  | private final Map<TarFileSet, IncludeTar> peerMap = new IdentityHashMap<TarFileSet, IncludeTar>(); | 
|  |  | 
|  | /** | 
|  | * Creates a task instance. | 
|  | */ | 
|  | public TarCat() { | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Add a new tar to include in this tar operation. | 
|  | */ | 
|  | public IncludeTar createIncludeTar() { | 
|  | /* | 
|  | * Create a peer TarFileSet corresponding to one of our own IncludeTars. | 
|  | * This causes the superclass to call us back during execution. | 
|  | */ | 
|  | TarFileSet peer = super.createTarFileSet(); | 
|  | IncludeTar includeTar = new IncludeTar(peer); | 
|  | peerMap.put(peer, includeTar); | 
|  | return includeTar; | 
|  | } | 
|  |  | 
|  | protected void tarFile(File file, TarOutputStream tOut, String vPath, | 
|  | TarFileSet tarFileSet) throws IOException { | 
|  | if (!peerMap.containsKey(tarFileSet)) { | 
|  | // Not one of ours, punt to superclass. | 
|  | super.tarFile(file, tOut, vPath, tarFileSet); | 
|  | return; | 
|  | } | 
|  |  | 
|  | IncludeTar includeTar = peerMap.get(tarFileSet); | 
|  | String prefix = tarFileSet.getPrefix(); | 
|  | if (prefix.length() > 0 && !prefix.endsWith("/")) { | 
|  | // '/' is appended for compatibility with the zip task. | 
|  | prefix = prefix + "/"; | 
|  | } | 
|  | TarInputStream tIn = null; | 
|  | try { | 
|  | tIn = new TarInputStream(includeTar.compression.decompress(file, | 
|  | new BufferedInputStream(new FileInputStream(file)))); | 
|  | TarEntry te = null; | 
|  | while ((te = tIn.getNextEntry()) != null) { | 
|  | vPath = te.getName(); | 
|  |  | 
|  | // don't add "" to the archive | 
|  | if (vPath.length() <= 0) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | if (te.isDirectory() && !vPath.endsWith("/")) { | 
|  | vPath += "/"; | 
|  | } | 
|  |  | 
|  | vPath = prefix + vPath; | 
|  |  | 
|  | te.setName(vPath); | 
|  | tOut.putNextEntry(te); | 
|  |  | 
|  | if (te.getSize() > 0) { | 
|  | byte[] buffer = new byte[8 * 1024]; | 
|  | while (true) { | 
|  | int count = tIn.read(buffer, 0, buffer.length); | 
|  | if (count < 0) { | 
|  | break; | 
|  | } | 
|  | tOut.write(buffer, 0, count); | 
|  | } | 
|  | } | 
|  | tOut.closeEntry(); | 
|  | } | 
|  |  | 
|  | } catch (IOException e) { | 
|  | throw new BuildException("Error while expanding " + file.getPath(), e, | 
|  | getLocation()); | 
|  | } finally { | 
|  | if (tIn != null) { | 
|  | try { | 
|  | tIn.close(); | 
|  | } catch (IOException ignored) { | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } |