001: /*
002: * Copyright 2006 Google Inc.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005: * use this file except in compliance with the License. You may obtain a copy of
006: * the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013: * License for the specific language governing permissions and limitations under
014: * the License.
015: */
016: package com.google.gwt.ant.taskdefs;
017:
018: import org.apache.tools.ant.BuildException;
019: import org.apache.tools.ant.taskdefs.Tar;
020: import org.apache.tools.ant.types.EnumeratedAttribute;
021: import org.apache.tools.bzip2.CBZip2InputStream;
022: import org.apache.tools.tar.TarEntry;
023: import org.apache.tools.tar.TarInputStream;
024: import org.apache.tools.tar.TarOutputStream;
025:
026: import java.io.BufferedInputStream;
027: import java.io.File;
028: import java.io.FileInputStream;
029: import java.io.IOException;
030: import java.io.InputStream;
031: import java.util.Vector;
032: import java.util.zip.GZIPInputStream;
033:
034: /**
035: * An extension to the Ant Tar task that supports slurping in other tar files
036: * without loss of information (such as permissions or symlinks). It behaves in
037: * all respects like the basic Tar task, but adds the nested element
038: * <includetar> which declares another tar file whose <i>contents</i>
039: * should be added to the file being created.
040: *
041: * In addition to preserving permissions and symlinks no matter what the host
042: * operating system is, there are performance advantages to this approach.
043: * Bypassing the file system cuts the disk activity to 50% or less. The
044: * intermediate files normally generated require data the size of the tar itself
045: * to be both written and read, not to mention the overhead of creating the
046: * individual files, which will generally have to be deleted later. Furthurmore,
047: * since the source and target are often zipped, the savings can be well over
048: * 50%.
049: *
050: * Example use:
051: *
052: * <pre>
053: * <taskdef name="tar.cat"
054: * classname="com.google.gwt.ant.taskdefs.TarCat"
055: * classpath="${gwt.build.lib}/ant-gwt.jar" />
056: * <tar.cat destfile="foo.tar.gz" compression="gzip" longfile="gnu">
057: * <!-- all normal tar attributes and elements supported -->
058: * <tarfileset dir="foo/src">
059: * <include name="*.class" />
060: * </tarfileset>
061: * <!-- tar.cat adds the ability to directly slurp in other tar files -->
062: * <includetar src="bar.tar.gz" compression="gzip" prefix="bar/" />
063: * </tar.cat>
064: * </pre>
065: */
066: public class TarCat extends Tar {
067:
068: /**
069: * This is a tar file that should be included into a tar operation.
070: */
071: public static class IncludeTar {
072: /**
073: * The compression method to use to access the included tar file.
074: */
075: private UntarCompressionMethod compression = new UntarCompressionMethod();
076:
077: /**
078: * An association from a super Tar to a derived TarCat.
079: */
080: private TarFileSet wrapper;
081:
082: /**
083: * Constructs a new IncludeTar.
084: *
085: * @param wrapper the association from a super Tar to a derived TarExt
086: */
087: public IncludeTar(TarFileSet wrapper) {
088: this .wrapper = wrapper;
089: }
090:
091: /**
092: * Set decompression algorithm to use; default=none.
093: *
094: * Allowable values are
095: * <ul>
096: * <li>none - no compression
097: * <li>gzip - Gzip compression
098: * <li>bzip2 - Bzip2 compression
099: * </ul>
100: *
101: * @param method compression method
102: */
103: public void setCompression(UntarCompressionMethod method) {
104: compression = method;
105: }
106:
107: /**
108: * If the prefix attribute is set, all files in the fileset are prefixed
109: * with that path in the archive. optional.
110: *
111: * @param prefix the path prefix.
112: */
113: public void setPrefix(String prefix) {
114: wrapper.setPrefix(prefix);
115: }
116:
117: /**
118: * Set the name/location of a tar file to add to a tar operation.
119: *
120: * @param tarFile the tar file to read
121: */
122: public void setSrc(File tarFile) {
123: wrapper.setFile(tarFile);
124: }
125: }
126:
127: /**
128: * Straight copy from
129: * {@link org.apache.tools.ant.taskdefs.Untar.UntarCompressionMethod} due to
130: * access restrictions.
131: */
132: public static final class UntarCompressionMethod extends
133: EnumeratedAttribute {
134:
135: private static final String BZIP2 = "bzip2";
136: private static final String GZIP = "gzip";
137: private static final String NONE = "none";
138:
139: public UntarCompressionMethod() {
140: super ();
141: setValue(NONE);
142: }
143:
144: public String[] getValues() {
145: return new String[] { NONE, GZIP, BZIP2 };
146: }
147:
148: private InputStream decompress(final File file,
149: final InputStream istream) throws IOException,
150: BuildException {
151: final String value = getValue();
152: if (GZIP.equals(value)) {
153: return new GZIPInputStream(istream);
154: } else {
155: if (BZIP2.equals(value)) {
156: final char[] magic = new char[] { 'B', 'Z' };
157: for (int i = 0; i < magic.length; i++) {
158: if (istream.read() != magic[i]) {
159: throw new BuildException(
160: "Invalid bz2 file."
161: + file.toString());
162: }
163: }
164: return new CBZip2InputStream(istream);
165: }
166: }
167: return istream;
168: }
169: }
170:
171: /**
172: * The set of tars to include in this tar operation.
173: */
174: Vector includeTars = new Vector();
175:
176: /**
177: * A set of tarfileset wrappers mapped to includeTars.
178: */
179: Vector includeTarWrappers = new Vector();
180:
181: /**
182: * Creates a TarExt task instance.
183: */
184: public TarCat() {
185: }
186:
187: /**
188: * Add a new tar to include in this tar operation.
189: */
190: public IncludeTar createIncludeTar() {
191: /*
192: * Create a dummy tarfileset to hold our own includeTars and add it to the
193: * super class. This is how we get the super class to call us back during
194: * execution.
195: */
196: TarFileSet wrapper = super .createTarFileSet();
197: IncludeTar includeTar = new IncludeTar(wrapper);
198: includeTars.addElement(includeTar);
199: includeTarWrappers.add(wrapper);
200: return includeTar;
201: }
202:
203: protected void tarFile(File file, TarOutputStream tOut,
204: String vPath, TarFileSet tarFileSet) throws IOException {
205: // See if it's one of ours
206: int index = includeTarWrappers.indexOf(tarFileSet);
207: if (index < 0) {
208: super .tarFile(file, tOut, vPath, tarFileSet);
209: return;
210: }
211: IncludeTar includeTar = (IncludeTar) includeTars.get(index);
212: TarInputStream tIn = null;
213: try {
214: tIn = new TarInputStream(includeTar.compression.decompress(
215: file, new BufferedInputStream(new FileInputStream(
216: file))));
217: TarEntry te = null;
218: while ((te = tIn.getNextEntry()) != null) {
219: vPath = te.getName();
220:
221: // don't add "" to the archive
222: if (vPath.length() <= 0) {
223: continue;
224: }
225:
226: if (te.isDirectory() && !vPath.endsWith("/")) {
227: vPath += "/";
228: }
229:
230: String prefix = tarFileSet.getPrefix();
231: // '/' is appended for compatibility with the zip task.
232: if (prefix.length() > 0 && !prefix.endsWith("/")) {
233: prefix = prefix + "/";
234: }
235: vPath = prefix + vPath;
236:
237: te.setName(vPath);
238: tOut.putNextEntry(te);
239:
240: if (te.getSize() > 0) {
241: byte[] buffer = new byte[8 * 1024];
242: while (true) {
243: int count = tIn.read(buffer, 0, buffer.length);
244: if (count < 0) {
245: break;
246: }
247: tOut.write(buffer, 0, count);
248: }
249: }
250: tOut.closeEntry();
251: }
252:
253: } catch (IOException ioe) {
254: throw new BuildException("Error while expanding "
255: + file.getPath(), ioe, getLocation());
256: } finally {
257: if (tIn != null) {
258: try {
259: tIn.close();
260: } catch (IOException e) {
261: // ignore
262: }
263: }
264: }
265: }
266: }
|