001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018:
019: package org.apache.tools.ant.taskdefs;
020:
021: import java.io.File;
022: import java.io.FileNotFoundException;
023: import java.io.FileOutputStream;
024: import java.io.IOException;
025: import java.io.InputStream;
026: import java.util.Date;
027: import java.util.Enumeration;
028: import java.util.HashSet;
029: import java.util.Iterator;
030: import java.util.Set;
031: import java.util.Vector;
032:
033: import org.apache.tools.ant.BuildException;
034: import org.apache.tools.ant.Project;
035: import org.apache.tools.ant.Task;
036: import org.apache.tools.ant.types.FileSet;
037: import org.apache.tools.ant.types.Mapper;
038: import org.apache.tools.ant.types.PatternSet;
039: import org.apache.tools.ant.types.Resource;
040: import org.apache.tools.ant.types.ResourceCollection;
041: import org.apache.tools.ant.types.resources.FileResource;
042: import org.apache.tools.ant.types.resources.Union;
043: import org.apache.tools.ant.types.selectors.SelectorUtils;
044: import org.apache.tools.ant.util.FileNameMapper;
045: import org.apache.tools.ant.util.FileUtils;
046: import org.apache.tools.ant.util.IdentityMapper;
047: import org.apache.tools.zip.ZipEntry;
048: import org.apache.tools.zip.ZipFile;
049:
050: /**
051: * Unzip a file.
052: *
053: * @since Ant 1.1
054: *
055: * @ant.task category="packaging"
056: * name="unzip"
057: * name="unjar"
058: * name="unwar"
059: */
060: public class Expand extends Task {
061: private File dest; //req
062: private File source; // req
063: private boolean overwrite = true;
064: private Mapper mapperElement = null;
065: private Vector patternsets = new Vector();
066: private Union resources = new Union();
067: private boolean resourcesSpecified = false;
068:
069: private static final String NATIVE_ENCODING = "native-encoding";
070:
071: private String encoding = "UTF8";
072: /** Error message when more that one mapper is defined */
073: public static final String ERROR_MULTIPLE_MAPPERS = "Cannot define more than one mapper";
074:
075: private static final FileUtils FILE_UTILS = FileUtils
076: .getFileUtils();
077:
078: /**
079: * Do the work.
080: *
081: * @exception BuildException Thrown in unrecoverable error.
082: */
083: public void execute() throws BuildException {
084: if ("expand".equals(getTaskType())) {
085: log("!! expand is deprecated. Use unzip instead. !!");
086: }
087:
088: if (source == null && !resourcesSpecified) {
089: throw new BuildException(
090: "src attribute and/or resources must be "
091: + "specified");
092: }
093:
094: if (dest == null) {
095: throw new BuildException("Dest attribute must be specified");
096: }
097:
098: if (dest.exists() && !dest.isDirectory()) {
099: throw new BuildException("Dest must be a directory.",
100: getLocation());
101: }
102:
103: if (source != null) {
104: if (source.isDirectory()) {
105: throw new BuildException("Src must not be a directory."
106: + " Use nested filesets instead.",
107: getLocation());
108: } else {
109: expandFile(FILE_UTILS, source, dest);
110: }
111: }
112: Iterator iter = resources.iterator();
113: while (iter.hasNext()) {
114: Resource r = (Resource) iter.next();
115: if (!r.isExists()) {
116: continue;
117: }
118:
119: if (r instanceof FileResource) {
120: expandFile(FILE_UTILS, ((FileResource) r).getFile(),
121: dest);
122: } else {
123: expandResource(r, dest);
124: }
125: }
126: }
127:
128: /**
129: * This method is to be overridden by extending unarchival tasks.
130: *
131: * @param fileUtils the fileUtils
132: * @param srcF the source file
133: * @param dir the destination directory
134: */
135: protected void expandFile(FileUtils fileUtils, File srcF, File dir) {
136: log("Expanding: " + srcF + " into " + dir, Project.MSG_INFO);
137: ZipFile zf = null;
138: FileNameMapper mapper = getMapper();
139: try {
140: zf = new ZipFile(srcF, encoding);
141: Enumeration e = zf.getEntries();
142: while (e.hasMoreElements()) {
143: ZipEntry ze = (ZipEntry) e.nextElement();
144: extractFile(fileUtils, srcF, dir,
145: zf.getInputStream(ze), ze.getName(), new Date(
146: ze.getTime()), ze.isDirectory(), mapper);
147: }
148:
149: log("expand complete", Project.MSG_VERBOSE);
150: } catch (IOException ioe) {
151: throw new BuildException("Error while expanding "
152: + srcF.getPath(), ioe);
153: } finally {
154: ZipFile.closeQuietly(zf);
155: }
156: }
157:
158: /**
159: * This method is to be overridden by extending unarchival tasks.
160: *
161: * @param srcR the source resource
162: * @param dir the destination directory
163: */
164: protected void expandResource(Resource srcR, File dir) {
165: throw new BuildException("only filesystem based resources are"
166: + " supported by this task.");
167: }
168:
169: /**
170: * get a mapper for a file
171: * @return a filenamemapper for a file
172: */
173: protected FileNameMapper getMapper() {
174: FileNameMapper mapper = null;
175: if (mapperElement != null) {
176: mapper = mapperElement.getImplementation();
177: } else {
178: mapper = new IdentityMapper();
179: }
180: return mapper;
181: }
182:
183: /**
184: * extract a file to a directory
185: * @param fileUtils a fileUtils object
186: * @param srcF the source file
187: * @param dir the destination directory
188: * @param compressedInputStream the input stream
189: * @param entryName the name of the entry
190: * @param entryDate the date of the entry
191: * @param isDirectory if this is true the entry is a directory
192: * @param mapper the filename mapper to use
193: * @throws IOException on error
194: */
195: protected void extractFile(FileUtils fileUtils, File srcF,
196: File dir, InputStream compressedInputStream,
197: String entryName, Date entryDate, boolean isDirectory,
198: FileNameMapper mapper) throws IOException {
199:
200: if (patternsets != null && patternsets.size() > 0) {
201: String name = entryName.replace('/', File.separatorChar)
202: .replace('\\', File.separatorChar);
203: boolean included = false;
204: Set includePatterns = new HashSet();
205: Set excludePatterns = new HashSet();
206: for (int v = 0, size = patternsets.size(); v < size; v++) {
207: PatternSet p = (PatternSet) patternsets.elementAt(v);
208: String[] incls = p.getIncludePatterns(getProject());
209: if (incls == null || incls.length == 0) {
210: // no include pattern implicitly means includes="**"
211: incls = new String[] { "**" };
212: }
213:
214: for (int w = 0; w < incls.length; w++) {
215: String pattern = incls[w].replace('/',
216: File.separatorChar).replace('\\',
217: File.separatorChar);
218: if (pattern.endsWith(File.separator)) {
219: pattern += "**";
220: }
221: includePatterns.add(pattern);
222: }
223:
224: String[] excls = p.getExcludePatterns(getProject());
225: if (excls != null) {
226: for (int w = 0; w < excls.length; w++) {
227: String pattern = excls[w].replace('/',
228: File.separatorChar).replace('\\',
229: File.separatorChar);
230: if (pattern.endsWith(File.separator)) {
231: pattern += "**";
232: }
233: excludePatterns.add(pattern);
234: }
235: }
236: }
237:
238: for (Iterator iter = includePatterns.iterator(); !included
239: && iter.hasNext();) {
240: String pattern = (String) iter.next();
241: included = SelectorUtils.matchPath(pattern, name);
242: }
243:
244: for (Iterator iter = excludePatterns.iterator(); included
245: && iter.hasNext();) {
246: String pattern = (String) iter.next();
247: included = !SelectorUtils.matchPath(pattern, name);
248: }
249:
250: if (!included) {
251: //Do not process this file
252: return;
253: }
254: }
255: String[] mappedNames = mapper.mapFileName(entryName);
256: if (mappedNames == null || mappedNames.length == 0) {
257: mappedNames = new String[] { entryName };
258: }
259: File f = fileUtils.resolveFile(dir, mappedNames[0]);
260: try {
261: if (!overwrite && f.exists()
262: && f.lastModified() >= entryDate.getTime()) {
263: log("Skipping " + f + " as it is up-to-date",
264: Project.MSG_DEBUG);
265: return;
266: }
267:
268: log("expanding " + entryName + " to " + f,
269: Project.MSG_VERBOSE);
270: // create intermediary directories - sometimes zip don't add them
271: File dirF = f.getParentFile();
272: if (dirF != null) {
273: dirF.mkdirs();
274: }
275:
276: if (isDirectory) {
277: f.mkdirs();
278: } else {
279: byte[] buffer = new byte[1024];
280: int length = 0;
281: FileOutputStream fos = null;
282: try {
283: fos = new FileOutputStream(f);
284:
285: while ((length = compressedInputStream.read(buffer)) >= 0) {
286: fos.write(buffer, 0, length);
287: }
288:
289: fos.close();
290: fos = null;
291: } finally {
292: FileUtils.close(fos);
293: }
294: }
295:
296: fileUtils.setFileLastModified(f, entryDate.getTime());
297: } catch (FileNotFoundException ex) {
298: log("Unable to expand to file " + f.getPath(),
299: Project.MSG_WARN);
300: }
301:
302: }
303:
304: /**
305: * Set the destination directory. File will be unzipped into the
306: * destination directory.
307: *
308: * @param d Path to the directory.
309: */
310: public void setDest(File d) {
311: this .dest = d;
312: }
313:
314: /**
315: * Set the path to zip-file.
316: *
317: * @param s Path to zip-file.
318: */
319: public void setSrc(File s) {
320: this .source = s;
321: }
322:
323: /**
324: * Should we overwrite files in dest, even if they are newer than
325: * the corresponding entries in the archive?
326: * @param b a <code>boolean</code> value
327: */
328: public void setOverwrite(boolean b) {
329: overwrite = b;
330: }
331:
332: /**
333: * Add a patternset.
334: * @param set a pattern set
335: */
336: public void addPatternset(PatternSet set) {
337: patternsets.addElement(set);
338: }
339:
340: /**
341: * Add a fileset
342: * @param set a file set
343: */
344: public void addFileset(FileSet set) {
345: add(set);
346: }
347:
348: /**
349: * Add a resource collection.
350: * @param rc a resource collection.
351: * @since Ant 1.7
352: */
353: public void add(ResourceCollection rc) {
354: resourcesSpecified = true;
355: resources.add(rc);
356: }
357:
358: /**
359: * Defines the mapper to map source entries to destination files.
360: * @return a mapper to be configured
361: * @exception BuildException if more than one mapper is defined
362: * @since Ant1.7
363: */
364: public Mapper createMapper() throws BuildException {
365: if (mapperElement != null) {
366: throw new BuildException(ERROR_MULTIPLE_MAPPERS,
367: getLocation());
368: }
369: mapperElement = new Mapper(getProject());
370: return mapperElement;
371: }
372:
373: /**
374: * A nested filenamemapper
375: * @param fileNameMapper the mapper to add
376: * @since Ant 1.6.3
377: */
378: public void add(FileNameMapper fileNameMapper) {
379: createMapper().add(fileNameMapper);
380: }
381:
382: /**
383: * Sets the encoding to assume for file names and comments.
384: *
385: * <p>Set to <code>native-encoding</code> if you want your
386: * platform's native encoding, defaults to UTF8.</p>
387: * @param encoding the name of the character encoding
388: * @since Ant 1.6
389: */
390: public void setEncoding(String encoding) {
391: if (NATIVE_ENCODING.equals(encoding)) {
392: encoding = null;
393: }
394: this.encoding = encoding;
395: }
396:
397: }
|