001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU General
007: * Public License Version 2 only ("GPL") or the Common Development and Distribution
008: * License("CDDL") (collectively, the "License"). You may not use this file except in
009: * compliance with the License. You can obtain a copy of the License at
010: * http://www.netbeans.org/cddl-gplv2.html or nbbuild/licenses/CDDL-GPL-2-CP. See the
011: * License for the specific language governing permissions and limitations under the
012: * License. When distributing the software, include this License Header Notice in
013: * each file and include the License file at nbbuild/licenses/CDDL-GPL-2-CP. Sun
014: * designates this particular file as subject to the "Classpath" exception as
015: * provided by Sun in the GPL Version 2 section of the License file that
016: * accompanied this code. If applicable, add the following below the License Header,
017: * with the fields enclosed by brackets [] replaced by your own identifying
018: * information: "Portions Copyrighted [year] [name of copyright owner]"
019: *
020: * Contributor(s):
021: *
022: * The Original Software is NetBeans. The Initial Developer of the Original Software
023: * is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun Microsystems, Inc. All
024: * Rights Reserved.
025: *
026: * If you wish your version of this file to be governed by only the CDDL or only the
027: * GPL Version 2, indicate your decision by adding "[Contributor] elects to include
028: * this software in this distribution under the [CDDL or GPL Version 2] license." If
029: * you do not indicate a single choice of license, a recipient has the option to
030: * distribute your version of this file under either the CDDL, the GPL Version 2 or
031: * to extend the choice of license to its licensees as provided above. However, if
032: * you add GPL Version 2 code and therefore, elected the GPL Version 2 license, then
033: * the option applies only if the new code is made subject to such option by the
034: * copyright holder.
035: */
036:
037: package org.netbeans.installer.infra.build.ant;
038:
039: import java.io.File;
040: import java.io.FileInputStream;
041: import java.io.FileOutputStream;
042: import java.io.IOException;
043: import java.io.OutputStreamWriter;
044: import java.util.LinkedList;
045: import java.util.List;
046: import java.util.jar.JarEntry;
047: import java.util.jar.JarOutputStream;
048: import org.apache.tools.ant.BuildException;
049: import org.apache.tools.ant.Task;
050: import org.netbeans.installer.infra.build.ant.utils.Utils;
051: import org.netbeans.installer.infra.build.ant.utils.FileEntry;
052:
053: /**
054: * This class is an ant task which is capable of properly packaging a directory into
055: * an archive. In addition to simply jarring the directory, it pack200-packages the
056: * jar files present in the source directory and composes a files list which
057: * contains some useful metadata about the files in the archive, such as the
058: * checksums, sizes, etc.
059: *
060: * @author Kirill Sorokin
061: */
062: public class Package extends Task {
063: /////////////////////////////////////////////////////////////////////////////////
064: // Instance
065: /**
066: * The target archive file.
067: */
068: private File file;
069:
070: /**
071: * The sources directory.
072: */
073: private File directory;
074:
075: /**
076: * List of {@link FileEntry} objects, which represent the metadata for the files
077: * which should be included into the archive.
078: */
079: private List<FileEntry> entries;
080:
081: private long directoriesCount;
082: private long filesCount;
083:
084: // constructor //////////////////////////////////////////////////////////////////
085: /**
086: * Constructs a new instance of the {@link Package} task. It simply sets the
087: * default values for the attributes.
088: */
089: public Package() {
090: entries = new LinkedList<FileEntry>();
091: }
092:
093: // setters //////////////////////////////////////////////////////////////////////
094: /**
095: * Setter for the 'file' property.
096: *
097: * @param path New value for the 'file' property.
098: */
099: public void setFile(final String path) {
100: file = new File(path);
101: if (!file.equals(file.getAbsoluteFile())) {
102: file = new File(getProject().getBaseDir(), path);
103: }
104: }
105:
106: /**
107: * Setter for the 'directory' property.
108: *
109: * @param path New value for the 'directory' property.
110: */
111: public void setDirectory(final String path) {
112: directory = new File(path);
113: if (!directory.equals(directory.getAbsoluteFile())) {
114: directory = new File(getProject().getBaseDir(), path);
115: }
116: }
117:
118: // execution ////////////////////////////////////////////////////////////////////
119: /**
120: * Executes the task. The source directory is recursively browsed, its files are
121: * examined, packaged and added to the archive; some additional metadata is
122: * calculated and then added to the files list.
123: *
124: * @throws org.apache.tools.ant.BuildException if an I/O error occurs.
125: */
126: public void execute() throws BuildException {
127: Utils.setProject(getProject());
128:
129: JarOutputStream output = null;
130: try {
131: output = new JarOutputStream(new FileOutputStream(file));
132: output.setLevel(9);
133:
134: log("browsing, packing, archiving directory"); // NOI18N
135: browse(directory.getCanonicalFile(), output, directory
136: .getCanonicalPath().length());
137:
138: log("adding manifest and files list"); // NOI18N
139: output.putNextEntry(new JarEntry(METAINF_ENTRY));
140:
141: output.putNextEntry(new JarEntry(MANIFEST_ENTRY));
142: output.write("Manifest-Version: 1.0\n\n".getBytes("UTF-8")); // NOI18N
143:
144: output.putNextEntry(new JarEntry(FILES_LIST_ENTRY));
145: OutputStreamWriter writer = new OutputStreamWriter(output,
146: "UTF-8"); // NOI18N
147:
148: writer
149: .write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); // NOI18N
150: writer.write("<files-list>\n"); // NOI18N
151: for (FileEntry entry : entries) {
152: writer.write(" <entry "); // NOI18N
153: if (entry.isDirectory()) {
154: writer.write("type=\"directory\" "); // NOI18N
155: writer.write("empty=\"" + // NOI18N
156: entry.isEmpty() + "\" "); // NOI18N
157: writer.write("modified=\"" + // NOI18N
158: entry.getLastModified() + "\" "); // NOI18N
159: writer.write("permissions=\"" + // NOI18N
160: entry.getPermissions() + "\""); // NOI18N
161: } else {
162: writer.write("type=\"file\" "); // NOI18N
163: writer.write("size=\"" + // NOI18N
164: entry.getSize() + "\" "); // NOI18N
165: writer.write("md5=\"" + // NOI18N
166: entry.getMd5() + "\" "); // NOI18N
167: writer.write("jar=\"" + // NOI18N
168: entry.isJarFile() + "\" "); // NOI18N
169: writer.write("packed=\"" + // NOI18N
170: entry.isPackedJarFile() + "\" "); // NOI18N
171: writer.write("signed=\"" + // NOI18N
172: entry.isSignedJarFile() + "\" "); // NOI18N
173: writer.write("modified=\"" + // NOI18N
174: entry.getLastModified() + "\" "); // NOI18N
175: writer.write("permissions=\"" + // NOI18N
176: entry.getPermissions() + "\""); // NOI18N
177: }
178:
179: writer.write(">" + entry. // NOI18N
180: getName().replace("&", "&"). // NOI18N
181: replace("<", "<"). // NOI18N
182: replace(">", ">") + "</entry>\n"); // NOI18N
183: }
184: writer.write("</files-list>\n"); // NOI18N
185:
186: writer.flush();
187: writer.close();
188:
189: output.flush();
190: output.close();
191:
192: log("archived " + directoriesCount + // NOI18N
193: " directories and " + filesCount + " files"); // NOI18N
194: } catch (IOException e) {
195: throw new BuildException(e);
196: }
197: }
198:
199: // private //////////////////////////////////////////////////////////////////////
200: private void browse(final File parent,
201: final JarOutputStream output, final int offset)
202: throws IOException {
203: FileInputStream fis = null;
204: final List<File> toSkip = new LinkedList<File>();
205:
206: for (File child : parent.listFiles()) {
207: if (toSkip.contains(child)) {
208: log(" skipping " + child); // NOI18N
209: continue;
210: }
211:
212: log(" visiting " + child); // NOI18N
213:
214: final String path = child.getAbsolutePath();
215: String name = path.substring(offset + 1).replace('\\', '/'); // NOMAGI
216:
217: FileEntry entry;
218: JarEntry jarEntry;
219:
220: if (child.isDirectory()) {
221: log(" archiving directory: " + name); // NOI18N
222:
223: name = name + "/"; // NOI18N
224: entry = new FileEntry(child, name);
225:
226: output.putNextEntry(new JarEntry(name));
227:
228: directoriesCount++;
229:
230: browse(child, output, offset);
231: } else {
232: // if pack200.enabled property is set to false then disable packing
233: // otherwise do repacking
234: boolean packedProp = false;
235: final String pack200Enabled = getProject().getProperty(
236: "pack200.enabled"); //NOI18N
237: boolean usePacking = !"false".equals(pack200Enabled); //NOI18N
238:
239: // if the source file comes in already packed, we need to unpack it
240: // first and then process normally
241: if (child.getName().endsWith(".jar.pack.gz")) { // NOI18N
242: if (usePacking) {
243: log(" it is a packed jar - unpacking"); // NOI18N
244: File unpacked = new File(child.getPath()
245: .substring(0,
246: child.getPath().length() - 8));
247: File temp = null;
248:
249: if (unpacked.exists()) {
250: temp = File.createTempFile("xxx", // NOI18N
251: null, child.getParentFile());
252: temp.delete();
253: unpacked.renameTo(temp);
254: }
255:
256: if (Utils.unpack(child, unpacked)) {
257: child.delete();
258: if (temp != null) {
259: temp.delete();
260: }
261: child = unpacked.getAbsoluteFile();
262:
263: log(" successfully unpacked - processing "
264: + // NOI18N
265: "file: " + child); // NOI18N
266: } else {
267: unpacked.delete();
268: if (temp != null) {
269: temp.renameTo(unpacked);
270: }
271: }
272: } else {
273: packedProp = true;
274: }
275: }
276: entry = new FileEntry(child, name);
277: if (packedProp) {
278: entry.setJarFile(true);
279: entry.setPackedJarFile(true);
280: }
281:
282: if (usePacking && entry.isJarFile()
283: && !entry.isSignedJarFile()) {
284: File backup = new File(child.getPath() + ".bak"); // NOI18N
285: File packed = new File(child.getPath() + ".pack.gz"); // NOI18N
286:
287: // if the packed form of this jar already exists, we need to
288: // clean it up
289: if (packed.exists()) {
290: log(" packed jar already exists - " + // NOI18N
291: "deleting it"); // NOI18N
292: packed.delete();
293: toSkip.add(packed);
294: }
295:
296: Utils.copy(child, backup);
297:
298: if (Utils.pack(child, packed)
299: && Utils.unpack(packed, child)
300: && Utils.verifyJad(child)
301: && Utils.verify(child)) {
302: name = packed.getPath().substring(offset + 1)
303: .replace('\\', '/'); // NOMAGI
304:
305: entry = new FileEntry(child, name);
306: entry.setJarFile(true);
307: entry.setPackedJarFile(true);
308: entry.setSignedJarFile(false);
309:
310: child.delete();
311: backup.delete();
312:
313: child = packed;
314: } else {
315: Utils.copy(backup, child);
316:
317: packed.delete();
318: backup.delete();
319: }
320: }
321:
322: log(" archiving file: " + name); // NOI18N
323: jarEntry = new JarEntry(name);
324: jarEntry.setTime(entry.getLastModified());
325: jarEntry.setSize(entry.getSize());
326: output.putNextEntry(jarEntry);
327:
328: fis = new FileInputStream(child);
329: Utils.copy(fis, output);
330: fis.close();
331:
332: filesCount++;
333: }
334:
335: entries.add(entry);
336: }
337: }
338:
339: /////////////////////////////////////////////////////////////////////////////////
340: // Constants
341: private static final String METAINF_ENTRY = "META-INF/"; // NOI18N
342: private static final String FILES_LIST_ENTRY = "META-INF/files.list"; // NOI18N
343: private static final String MANIFEST_ENTRY = "META-INF/manifest.mf"; // NOI18N
344: }
|