001: /*
002: * $Id: Packager.java 2060 2008-02-25 20:02:53Z jponge $
003: * IzPack - Copyright 2001-2008 Julien Ponge, All Rights Reserved.
004: *
005: * http://izpack.org/
006: * http://izpack.codehaus.org/
007: *
008: * Licensed under the Apache License, Version 2.0 (the "License");
009: * you may not use this file except in compliance with the License.
010: * You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing, software
015: * distributed under the License is distributed on an "AS IS" BASIS,
016: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: * See the License for the specific language governing permissions and
018: * limitations under the License.
019: */
020:
021: package com.izforge.izpack.compiler;
022:
023: import java.io.File;
024: import java.io.FileInputStream;
025: import java.io.FileWriter;
026: import java.io.IOException;
027: import java.io.InputStream;
028: import java.io.ObjectOutputStream;
029: import java.io.OutputStream;
030: import java.net.URL;
031: import java.util.HashMap;
032: import java.util.HashSet;
033: import java.util.Iterator;
034: import java.util.List;
035: import java.util.Map;
036: import java.util.zip.Deflater;
037: import java.util.zip.ZipEntry;
038: import java.util.zip.ZipException;
039: import java.util.zip.ZipInputStream;
040:
041: import net.n3.nanoxml.XMLElement;
042: import net.n3.nanoxml.XMLWriter;
043:
044: import com.izforge.izpack.Pack;
045: import com.izforge.izpack.PackFile;
046: import com.izforge.izpack.util.FileUtil;
047:
048: /**
049: * The packager class. The packager is used by the compiler to put files into an installer, and
050: * create the actual installer files.
051: *
052: * @author Julien Ponge
053: * @author Chadwick McHenry
054: */
055: public class Packager extends PackagerBase {
056:
057: /** Executable zipped output stream. First to open, last to close.
058: * Attention! This is our own JarOutputStream, not the java standard! */
059: private com.izforge.izpack.util.JarOutputStream primaryJarStream;
060:
061: /** The constructor.
062: * @throws CompilerException*/
063: public Packager() throws CompilerException {
064: this ("default");
065: }
066:
067: /**
068: * Extended constructor.
069: * @param compr_format Compression format to be used for packs
070: * compression format (if supported)
071: * @throws CompilerException
072: */
073: public Packager(String compr_format) throws CompilerException {
074: this (compr_format, -1);
075: }
076:
077: /**
078: * Extended constructor.
079: * @param compr_format Compression format to be used for packs
080: * @param compr_level Compression level to be used with the chosen
081: * compression format (if supported)
082: * @throws CompilerException
083: */
084: public Packager(String compr_format, int compr_level)
085: throws CompilerException {
086: initPackCompressor(compr_format, compr_level);
087: }
088:
089: /* (non-Javadoc)
090: * @see com.izforge.izpack.compiler.IPackager#createInstaller(java.io.File)
091: */
092: public void createInstaller(File primaryFile) throws Exception {
093: // preliminary work
094: String baseName = primaryFile.getName();
095: if (baseName.endsWith(".jar")) {
096: baseName = baseName.substring(0, baseName.length() - 4);
097: baseFile = new File(primaryFile.getParentFile(), baseName);
098: } else
099: baseFile = primaryFile;
100:
101: info.setInstallerBase(baseFile.getName());
102: packJarsSeparate = (info.getWebDirURL() != null);
103:
104: // primary (possibly only) jar. -1 indicates primary
105: primaryJarStream = getJarOutputStream(baseFile.getName()
106: + ".jar");
107:
108: sendStart();
109:
110: writeInstaller();
111:
112: // Finish up. closeAlways is a hack for pack compressions other than
113: // default. Some of it (e.g. BZip2) closes the slave of it also.
114: // But this should not be because the jar stream should be open
115: // for the next pack. Therefore an own JarOutputStream will be used
116: // which close method will be blocked.
117: primaryJarStream.closeAlways();
118:
119: sendStop();
120: }
121:
122: /***********************************************************************************************
123: * Private methods used when writing out the installer to jar files.
124: **********************************************************************************************/
125:
126: /**
127: * Write skeleton installer to primary jar. It is just an included jar, except that we copy the
128: * META-INF as well.
129: */
130: protected void writeSkeletonInstaller() throws IOException {
131: sendMsg("Copying the skeleton installer",
132: PackagerListener.MSG_VERBOSE);
133:
134: InputStream is = Packager.class.getResourceAsStream("/"
135: + SKELETON_SUBPATH);
136: if (is == null) {
137: File skeleton = new File(Compiler.IZPACK_HOME,
138: SKELETON_SUBPATH);
139: is = new FileInputStream(skeleton);
140: }
141: ZipInputStream inJarStream = new ZipInputStream(is);
142: copyZip(inJarStream, primaryJarStream);
143: }
144:
145: /**
146: * Write an arbitrary object to primary jar.
147: */
148: protected void writeInstallerObject(String entryName, Object object)
149: throws IOException {
150: primaryJarStream
151: .putNextEntry(new org.apache.tools.zip.ZipEntry(
152: entryName));
153: ObjectOutputStream out = new ObjectOutputStream(
154: primaryJarStream);
155: out.writeObject(object);
156: out.flush();
157: primaryJarStream.closeEntry();
158: }
159:
160: /** Write the data referenced by URL to primary jar. */
161: protected void writeInstallerResources() throws IOException {
162: sendMsg("Copying " + installerResourceURLMap.size()
163: + " files into installer");
164:
165: Iterator<String> i = installerResourceURLMap.keySet()
166: .iterator();
167: while (i.hasNext()) {
168: String name = i.next();
169: InputStream in = (installerResourceURLMap.get(name))
170: .openStream();
171:
172: org.apache.tools.zip.ZipEntry newEntry = new org.apache.tools.zip.ZipEntry(
173: name);
174: long dateTime = FileUtil
175: .getFileDateTime(installerResourceURLMap.get(name));
176: if (dateTime != -1)
177: newEntry.setTime(dateTime);
178: primaryJarStream.putNextEntry(newEntry);
179:
180: PackagerHelper.copyStream(in, primaryJarStream);
181: primaryJarStream.closeEntry();
182: in.close();
183: }
184: }
185:
186: /** Copy included jars to primary jar. */
187: protected void writeIncludedJars() throws IOException {
188: sendMsg("Merging " + includedJarURLs.size()
189: + " jars into installer");
190:
191: Iterator<Object[]> i = includedJarURLs.iterator();
192: while (i.hasNext()) {
193: Object[] current = i.next();
194: InputStream is = ((URL) current[0]).openStream();
195: ZipInputStream inJarStream = new ZipInputStream(is);
196: copyZip(inJarStream, primaryJarStream,
197: (List<String>) current[1]);
198: }
199: }
200:
201: /**
202: * Write Packs to primary jar or each to a separate jar.
203: */
204: protected void writePacks() throws Exception {
205: final int num = packsList.size();
206: sendMsg("Writing " + num + " Pack" + (num > 1 ? "s" : "")
207: + " into installer");
208:
209: // Map to remember pack number and bytes offsets of back references
210: Map<File, Object[]> storedFiles = new HashMap<File, Object[]>();
211:
212: // First write the serialized files and file metadata data for each pack
213: // while counting bytes.
214:
215: int packNumber = 0;
216: Iterator<PackInfo> packIter = packsList.iterator();
217:
218: XMLElement root = new XMLElement("packs");
219:
220: while (packIter.hasNext()) {
221: PackInfo packInfo = packIter.next();
222: Pack pack = packInfo.getPack();
223: pack.nbytes = 0;
224: if ((pack.id == null) || (pack.id.length() == 0)) {
225: pack.id = pack.name;
226: }
227:
228: // create a pack specific jar if required
229: com.izforge.izpack.util.JarOutputStream packStream = primaryJarStream;
230: if (packJarsSeparate) {
231: // See installer.Unpacker#getPackAsStream for the counterpart
232: String name = baseFile.getName() + ".pack-" + pack.id
233: + ".jar";
234: packStream = getJarOutputStream(name);
235: }
236: OutputStream comprStream = packStream;
237:
238: sendMsg("Writing Pack " + packNumber + ": " + pack.name,
239: PackagerListener.MSG_VERBOSE);
240:
241: // Retrieve the correct output stream
242: org.apache.tools.zip.ZipEntry entry = new org.apache.tools.zip.ZipEntry(
243: "packs/pack-" + pack.id);
244: if (!compressor.useStandardCompression()) {
245: entry.setMethod(ZipEntry.STORED);
246: entry.setComment(compressor
247: .getCompressionFormatSymbols()[0]);
248: // We must set the entry before we get the compressed stream
249: // because some writes initialize data (e.g. bzip2).
250: packStream.putNextEntry(entry);
251: packStream.flush(); // flush before we start counting
252: comprStream = compressor.getOutputStream(packStream);
253: } else {
254: int level = compressor.getCompressionLevel();
255: if (level >= 0 && level < 10)
256: packStream.setLevel(level);
257: packStream.putNextEntry(entry);
258: packStream.flush(); // flush before we start counting
259: }
260:
261: ByteCountingOutputStream dos = new ByteCountingOutputStream(
262: comprStream);
263: ObjectOutputStream objOut = new ObjectOutputStream(dos);
264:
265: // We write the actual pack files
266: objOut.writeInt(packInfo.getPackFiles().size());
267:
268: Iterator iter = packInfo.getPackFiles().iterator();
269: while (iter.hasNext()) {
270: boolean addFile = !pack.loose;
271: PackFile pf = (PackFile) iter.next();
272: File file = packInfo.getFile(pf);
273:
274: // use a back reference if file was in previous pack, and in
275: // same jar
276: Object[] info = storedFiles.get(file);
277: if (info != null && !packJarsSeparate) {
278: pf.setPreviousPackFileRef((String) info[0],
279: (Long) info[1]);
280: addFile = false;
281: }
282:
283: objOut.writeObject(pf); // base info
284: objOut.flush(); // make sure it is written
285:
286: if (addFile && !pf.isDirectory()) {
287: long pos = dos.getByteCount(); // get the position
288:
289: FileInputStream inStream = new FileInputStream(file);
290: long bytesWritten = PackagerHelper.copyStream(
291: inStream, objOut);
292:
293: if (bytesWritten != pf.length())
294: throw new IOException(
295: "File size mismatch when reading "
296: + file);
297:
298: inStream.close();
299: storedFiles
300: .put(file, new Object[] { pack.id, pos });
301: }
302:
303: // even if not written, it counts towards pack size
304: pack.nbytes += pf.length();
305: }
306:
307: // Write out information about parsable files
308: objOut.writeInt(packInfo.getParsables().size());
309: iter = packInfo.getParsables().iterator();
310: while (iter.hasNext())
311: objOut.writeObject(iter.next());
312:
313: // Write out information about executable files
314: objOut.writeInt(packInfo.getExecutables().size());
315: iter = packInfo.getExecutables().iterator();
316: while (iter.hasNext())
317: objOut.writeObject(iter.next());
318:
319: // Write out information about updatecheck files
320: objOut.writeInt(packInfo.getUpdateChecks().size());
321: iter = packInfo.getUpdateChecks().iterator();
322: while (iter.hasNext())
323: objOut.writeObject(iter.next());
324:
325: // Cleanup
326: objOut.flush();
327: if (!compressor.useStandardCompression()) {
328: comprStream.close();
329: }
330:
331: packStream.closeEntry();
332:
333: // close pack specific jar if required
334: if (packJarsSeparate)
335: packStream.closeAlways();
336:
337: XMLElement child = new XMLElement("pack");
338: child.setAttribute("nbytes", Long.toString(pack.nbytes));
339: child.setAttribute("name", pack.name);
340: if (pack.id != null)
341: child.setAttribute("id", pack.id);
342: root.addChild(child);
343:
344: packNumber++;
345: }
346:
347: // Write packsinfo for web installers
348: if (packJarsSeparate) {
349: FileWriter writer = new FileWriter(baseFile.getParent()
350: + File.separator + "packsinfo.xml");
351: XMLWriter xmlwriter = new XMLWriter(writer);
352: xmlwriter.write(root);
353: }
354:
355: // Now that we know sizes, write pack metadata to primary jar.
356: primaryJarStream
357: .putNextEntry(new org.apache.tools.zip.ZipEntry(
358: "packs.info"));
359: ObjectOutputStream out = new ObjectOutputStream(
360: primaryJarStream);
361: out.writeInt(packsList.size());
362:
363: Iterator<PackInfo> i = packsList.iterator();
364: while (i.hasNext()) {
365: PackInfo pack = i.next();
366: out.writeObject(pack.getPack());
367: }
368: out.flush();
369: primaryJarStream.closeEntry();
370: }
371:
372: /***********************************************************************************************
373: * Stream utilites for creation of the installer.
374: **********************************************************************************************/
375:
376: /** Return a stream for the next jar. */
377: private com.izforge.izpack.util.JarOutputStream getJarOutputStream(
378: String name) throws IOException {
379: File file = new File(baseFile.getParentFile(), name);
380: sendMsg("Building installer jar: " + file.getAbsolutePath());
381:
382: com.izforge.izpack.util.JarOutputStream jar = new com.izforge.izpack.util.JarOutputStream(
383: file);
384: jar.setLevel(Deflater.BEST_COMPRESSION);
385: jar.setPreventClose(true); // Needed at using FilterOutputStreams which calls close
386: // of the slave at finalizing.
387:
388: return jar;
389: }
390:
391: /**
392: * Copies contents of one jar to another.
393: *
394: * <p>
395: * TODO: it would be useful to be able to keep signature information from signed jar files, can
396: * we combine manifests and still have their content signed?
397: *
398: */
399: private void copyZip(ZipInputStream zin,
400: org.apache.tools.zip.ZipOutputStream out)
401: throws IOException {
402: copyZip(zin, out, null);
403: }
404:
405: /**
406: * Copies specified contents of one jar to another.
407: *
408: * <p>
409: * TODO: it would be useful to be able to keep signature information from signed jar files, can
410: * we combine manifests and still have their content signed?
411: *
412: */
413: private void copyZip(ZipInputStream zin,
414: org.apache.tools.zip.ZipOutputStream out, List<String> files)
415: throws IOException {
416: java.util.zip.ZipEntry zentry;
417: if (!alreadyWrittenFiles.containsKey(out))
418: alreadyWrittenFiles.put(out, new HashSet<String>());
419: HashSet<String> currentSet = alreadyWrittenFiles.get(out);
420: while ((zentry = zin.getNextEntry()) != null) {
421: String currentName = zentry.getName();
422: String testName = currentName.replace('/', '.');
423: testName = testName.replace('\\', '.');
424: if (files != null) {
425: Iterator<String> i = files.iterator();
426: boolean founded = false;
427: while (i.hasNext()) { // Make "includes" self to support regex.
428: String doInclude = i.next();
429: if (testName.matches(doInclude)) {
430: founded = true;
431: break;
432: }
433: }
434: if (!founded)
435: continue;
436: }
437: if (currentSet.contains(currentName))
438: continue;
439: try {
440: // Create new entry for zip file.
441: org.apache.tools.zip.ZipEntry newEntry = new org.apache.tools.zip.ZipEntry(
442: currentName);
443: // Get input file date and time.
444: long fileTime = zentry.getTime();
445: // Make sure there is date and time set.
446: if (fileTime != -1)
447: newEntry.setTime(fileTime); // If found set it into output file.
448: out.putNextEntry(newEntry);
449:
450: PackagerHelper.copyStream(zin, out);
451: out.closeEntry();
452: zin.closeEntry();
453: currentSet.add(currentName);
454: } catch (ZipException x) {
455: // This avoids any problem that can occur with duplicate
456: // directories. for instance all META-INF data in jars
457: // unfortunately this do not work with the apache ZipOutputStream...
458: }
459: }
460: }
461:
462: /* (non-Javadoc)
463: * @see com.izforge.izpack.compiler.IPackager#addConfigurationInformation(net.n3.nanoxml.XMLElement)
464: */
465: public void addConfigurationInformation(XMLElement data) {
466: // TODO Auto-generated method stub
467:
468: }
469: }
|