Statistics
| Revision:

root / import / ext3D / trunk / install-extension3d / IzPack / src / lib / com / izforge / izpack / compiler / Packager.java @ 15280

History | View | Annotate | Download (18.8 KB)

1
/*
2
 *  $Id: Packager.java,v 1.1 2006/06/14 07:29:07 cesar Exp $
3
 *  IzPack
4
 *  Copyright (C) 2001-2004 Julien Ponge
5
 *
6
 *  File :               Packager.java
7
 *  Description :        Packs files and data into an installer
8
 *  Author's email :     julien@izforge.com
9
 *
10
 *  This program is free software; you can redistribute it and/or
11
 *  modify it under the terms of the GNU General Public License
12
 *  as published by the Free Software Foundation; either version 2
13
 *  of the License, or any later version.
14
 *
15
 *  This program is distributed in the hope that it will be useful,
16
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
 *  GNU General Public License for more details.
19
 *
20
 *  You should have received a copy of the GNU General Public License
21
 *  along with this program; if not, write to the Free Software
22
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
23
 */
24
package com.izforge.izpack.compiler;
25

    
26
import java.io.File;
27
import java.io.FileInputStream;
28
import java.io.FileOutputStream;
29
import java.io.IOException;
30
import java.io.InputStream;
31
import java.io.ObjectOutputStream;
32
import java.io.OutputStream;
33
import java.net.URL;
34
import java.util.ArrayList;
35
import java.util.HashMap;
36
import java.util.HashSet;
37
import java.util.Iterator;
38
import java.util.List;
39
import java.util.Map;
40
import java.util.Properties;
41
import java.util.Set;
42
import java.util.jar.JarOutputStream;
43
import java.util.zip.Deflater;
44
import java.util.zip.ZipEntry;
45
import java.util.zip.ZipException;
46
import java.util.zip.ZipInputStream;
47
import java.util.zip.ZipOutputStream;
48

    
49
import com.izforge.izpack.CustomData;
50
import com.izforge.izpack.GUIPrefs;
51
import com.izforge.izpack.Info;
52
import com.izforge.izpack.Pack;
53
import com.izforge.izpack.PackFile;
54
import com.izforge.izpack.Panel;
55

    
56
/**
57
 * The packager class. The packager is used by the compiler to put files into an
58
 * installer, and create the actual installer files.
59
 *
60
 * @author     Julien Ponge
61
 * @author     Chadwick McHenry
62
 */
63
public class Packager
64
{
65
  /** Path to the skeleton installer. */
66
  public static final String SKELETON_SUBPATH = "lib/installer.jar";
67

    
68
  /** Base file name of all jar files. This has no ".jar" suffix. */
69
  private File baseFile = null;
70

    
71
  /** Executable zipped output stream. First to open, last to close. */
72
  private JarOutputStream primaryJarStream;
73

    
74
  /** Basic installer info. */
75
  private Info info = null;
76

    
77
  /** Gui preferences of instatller. */
78
  private GUIPrefs guiPrefs = null;
79

    
80
  /** The variables used in the project */
81
  private Properties variables = new Properties();
82

    
83
  /** The ordered panels informations. */
84
  private List panelList = new ArrayList();
85

    
86
  /** The ordered packs informations (as PackInfo objects). */
87
  private List packsList = new ArrayList();
88

    
89
  /** The ordered langpack ISO3 names. */
90
  private List langpackNameList = new ArrayList();
91

    
92
  /** The ordered custom actions informations. */
93
  private List customDataList = new ArrayList();
94

    
95
  /** The langpack URLs keyed by ISO3 name. */
96
  private Map installerResourceURLMap = new HashMap();
97

    
98
  /** Jar file URLs who's contents will be copied into the installer. */
99
  private Set includedJarURLs = new HashSet();
100

    
101
  /** Each pack is created in a separte jar if webDirURL is non-null. */
102
  private boolean packJarsSeparate = false;
103
  
104
  /** The listeners. */
105
  private PackagerListener listener;
106

    
107
  /** The constructor. */
108
  public Packager() {}
109

    
110
  /**
111
   * Create the installer, beginning with the specified jar. If the name
112
   * specified does not end in ".jar", it is appended. If secondary jars are
113
   * created for packs (if the Info object added has a webDirURL set), they are
114
   * created in the same directory, named sequentially by inserting ".pack#"
115
   * (where '#' is the pack number) ".jar" suffix: e.g. "foo.pack1.jar". If any
116
   * file exists, it is overwritten.
117
   */
118
  public void createInstaller(File primaryFile) throws IOException
119
  {
120
    // preliminary work
121
    String baseName = primaryFile.getName();
122
    if (baseName.endsWith(".jar"))
123
    {
124
      baseName = baseName.substring(0, baseName.length()-4);
125
      baseFile = new File(primaryFile.getParentFile(), baseName);
126
    }
127
    else
128
      baseFile = primaryFile;
129
    
130
    info.setInstallerBase(baseFile.getName());
131
    packJarsSeparate = (info.getWebDirURL() != null);
132
    
133
    // primary (possibly only) jar. -1 indicates primary
134
    primaryJarStream = getJarOutputStream(baseFile.getName() + ".jar");
135

    
136
    sendStart();
137

    
138
    // write the primary jar. MUST be first so manifest is not overwritten by
139
    // an included jar
140
    writeSkeletonInstaller();
141

    
142
    writeInstallerObject("info", info);
143
    writeInstallerObject("vars", variables);
144
    writeInstallerObject("GUIPrefs", guiPrefs);
145
    writeInstallerObject("panelsOrder", panelList);
146
    writeInstallerObject("customData", customDataList);
147
    writeInstallerObject("langpacks.info", langpackNameList);
148
    writeInstallerResources();
149
    writeIncludedJars();
150

    
151
    // Pack File Data  may be written to separate jars
152
    writePacks();
153
    
154
    // finish up
155
    primaryJarStream.close();
156

    
157
    sendStop();
158
  }
159

    
160
  /* **********************************************************************
161
   * Listener assistance
162
   * **********************************************************************/
163

    
164
  /**
165
   * Adds a listener.
166
   *
167
   * @param  listener  The listener.
168
   */
169
  public void setPackagerListener(PackagerListener listener)
170
  {
171
    this.listener = listener;
172
  }
173

    
174
  /**
175
   * Dispatches a message to the listeners.
176
   *
177
   * @param  job  The job description.
178
   */
179
  private void sendMsg(String job)
180
  {
181
    if (listener != null)
182
      listener.packagerMsg(job);
183
  }
184

    
185
  /** Dispatches a start event to the listeners.  */
186
  private void sendStart()
187
  {
188
    if (listener != null)
189
      listener.packagerStart();
190
  }
191

    
192
  /** Dispatches a stop event to the listeners.  */
193
  private void sendStop()
194
  {
195
    if (listener != null)
196
      listener.packagerStop();
197
  }
198

    
199

    
200
  /* **********************************************************************
201
   * Public methods to add data to the Installer being packed
202
   * **********************************************************************/
203

    
204
  /**
205
   * Sets the informations related to this installation.
206
   *
207
   * @param  info           The info section.
208
   * @exception  Exception  Description of the Exception
209
   */
210
  public void setInfo(Info info) throws Exception
211
  {
212
    sendMsg("Setting the installer informations ...");
213
    this.info = info;
214
  }
215

    
216
  /**
217
   * Sets the GUI preferences.
218
   *
219
   * @param  prefs          The new gUIPrefs value
220
   * @exception  Exception  Description of the Exception
221
   */
222
  public void setGUIPrefs(GUIPrefs prefs)
223
  {
224
    sendMsg("Setting the GUI preferences ...");
225
    guiPrefs = prefs;
226
  }
227

    
228
  /**
229
   * Allows access to add, remove and update the variables for the project,
230
   * which are maintained in the packager.
231
   *
232
   * @return map of variable names to values
233
   */
234
  public Properties getVariables()
235
  {
236
    return variables;
237
  }
238

    
239
  /**
240
   * Add a panel, where order is important. Only one copy of the class files
241
   * neeed are inserted in the installer.
242
   */
243
  public void addPanelJar(Panel panel, URL jarURL )
244
  {
245
    panelList.add(panel);  // serialized to keep order/variables correct
246
    addJarContent(jarURL); // each included once, no matter how many times added
247
  }
248

    
249

    
250
  /**
251
   * Add a custom data like custom actions, 
252
   * where order is important. Only one copy of the class files
253
   * neeed are inserted in the installer.
254
   * @param ca custom action object
255
   * @param url the URL to include once
256
   */
257
  public void addCustomJar(CustomData ca, URL url)
258
  {
259
    customDataList.add(ca);  // serialized to keep order/variables correct
260
    addJarContent(url); // each included once, no matter how many times added
261
  }
262
  /**
263
   * Adds a pack, order is mostly irrelevant.
264
   *
265
   * @param  pack contains all the files and items that go with a pack
266
   */
267
  public void addPack(PackInfo pack)
268
  {
269
    packsList.add(pack);
270
  }
271
  /**
272
   * Gets the packages list
273
   */
274
  public List getPacksList()
275
  {
276
    return packsList;
277
  }
278

    
279
  /**
280
   * Adds a language pack.
281
   *
282
   * @param  iso3           The ISO3 code.
283
   * @param  xmlURL         The location of the xml local info
284
   * @param  flagURL        The location of the flag image resource
285
   * @exception  Exception  Description of the Exception
286
   */
287
  public void addLangPack(String iso3, URL xmlURL, URL flagURL)
288
  {
289
    sendMsg("Adding langpack : " + iso3 + " ...");
290
    // put data & flag as entries in installer, and keep array of iso3's names
291
    langpackNameList.add(iso3);
292
    addResource("flag." + iso3, flagURL);
293
    installerResourceURLMap.put("langpacks/" + iso3 + ".xml", xmlURL);
294
  }
295

    
296
  /**
297
   * Adds a resource.
298
   *
299
   * @param  resId          The resource Id.
300
   * @param  url            The location of the data
301
   * @exception  Exception  Description of the Exception
302
   */
303
  public void addResource(String resId, URL url)
304
  {
305
    sendMsg("Adding resource : " + resId + " ...");
306
    installerResourceURLMap.put("res/" + resId, url);
307
  }
308

    
309
  /**
310
   * Adds a native library.
311
   *
312
   * @param  name           The native library name.
313
   * @param  url            The url to get the data from.
314
   * @exception  Exception  Description of the Exception
315
   */
316
  public void addNativeLibrary(String name, URL url) throws Exception
317
  {
318
    sendMsg("Adding native library : " + name + " ...");
319
    installerResourceURLMap.put("native/" + name, url);
320
  }
321

    
322
  /**
323
   * Adds a jar file content to the installer. Package structure is
324
   * maintained. Need mechanism to copy over signed entry information.
325
   *
326
   * @param jarURL          The url of the jar to add to the installer. We use
327
   *                        a URL so the jar may be nested within another.
328
   */
329
  public void addJarContent(URL jarURL)
330
  {
331
    sendMsg("Adding content of jar : " + jarURL.getFile() + " ...");
332
    includedJarURLs.add(jarURL);
333
  }
334
  
335
  /**
336
   * Marks a native library to be added to the uninstaller.
337
   * @param data the describing custom action data object
338
   */
339
  public void addNativeUninstallerLibrary(CustomData data)
340
  {
341
    customDataList.add(data);  // serialized to keep order/variables correct
342

    
343
  }
344

    
345

    
346
  /* **********************************************************************
347
   * Private methods used when writing out the installer to jar files.
348
   * **********************************************************************/
349

    
350
  /**
351
   * Write skeleton installer to primary jar. It is just an included jar,
352
   * except that we copy the META-INF as well.
353
   */
354
  private void writeSkeletonInstaller() throws IOException
355
  {
356
    sendMsg("Copying the skeleton installer ...");
357

    
358
    InputStream is = Packager.class.getResourceAsStream("/" + SKELETON_SUBPATH);
359
    if (is == null)
360
    {
361
      File skeleton = new File(Compiler.IZPACK_HOME, SKELETON_SUBPATH);
362
      is = new FileInputStream(skeleton);
363
    }
364
    ZipInputStream inJarStream = new ZipInputStream(is);
365
    copyZip(inJarStream, primaryJarStream);
366
  }
367

    
368
  /**
369
   * Write an arbitrary object to primary jar.
370
   */
371
  private void writeInstallerObject(String entryName, Object object)
372
    throws IOException
373
  {
374
    primaryJarStream.putNextEntry(new ZipEntry(entryName));
375
    ObjectOutputStream out = new ObjectOutputStream(primaryJarStream);
376
    out.writeObject(object);
377
    out.flush();
378
    primaryJarStream.closeEntry();
379
  }
380

    
381
  /** Write the data referenced by URL to primary jar. */
382
  private void writeInstallerResources() throws IOException
383
  {
384
    sendMsg("Copying " + installerResourceURLMap.size() +
385
            " files into installer ...");
386
    
387
    Iterator i = installerResourceURLMap.keySet().iterator();
388
    while (i.hasNext())
389
    {
390
      String name = (String) i.next();
391
      InputStream in = ((URL)installerResourceURLMap.get(name)).openStream();
392
      primaryJarStream.putNextEntry(new ZipEntry(name));
393
      copyStream(in, primaryJarStream);
394
      primaryJarStream.closeEntry();
395
      in.close();
396
    }
397
  }
398

    
399
  /** Copy included jars to primary jar. */
400
  private void writeIncludedJars() throws IOException
401
  {
402
    sendMsg("Copying contents of " + includedJarURLs.size() +
403
            " jars into installer ...");
404
    
405
    Iterator i = includedJarURLs.iterator();
406
    while (i.hasNext())
407
    {
408
      InputStream is = ((URL)i.next()).openStream();
409
      ZipInputStream inJarStream = new ZipInputStream(is);
410
      copyZip(inJarStream, primaryJarStream);
411
    }
412
  }
413

    
414
  /**
415
   * Write Packs to primary jar or each to a separate jar.
416
   */
417
  private void writePacks() throws IOException
418
  {
419
    sendMsg("Writing Packs ...");
420
    
421
    // Map to remember pack number and bytes offsets of back references
422
    Map storedFiles = new HashMap();
423
    
424
    // First write the serialized files and file metadata data for each pack
425
    // while counting bytes.
426
    
427
    int packNumber = 0;
428
    Iterator packIter = packsList.iterator();
429
    while (packIter.hasNext())
430
    {
431
      PackInfo packInfo = (PackInfo) packIter.next();
432
      Pack pack = packInfo.getPack();
433
      pack.nbytes = 0;
434

    
435
      // create a pack specific jar if required
436
      JarOutputStream packStream = primaryJarStream;
437
      if (packJarsSeparate)
438
      {
439
        // See installer.Unpacker#getPackAsStream for the counterpart
440
        String name = baseFile.getName() + ".pack" + packNumber + ".jar";
441
        packStream = getJarOutputStream(name);
442
      }
443
      
444
      sendMsg("Writing Pack #" + packNumber + " : " + pack.name);
445

    
446
      // Retrieve the correct output stream
447
      ZipEntry entry = new ZipEntry("packs/pack" + packNumber);
448
      packStream.putNextEntry(entry);
449
      packStream.flush(); // flush before we start counting
450
      
451
      ByteCountingOutputStream dos = new ByteCountingOutputStream(packStream);
452
      ObjectOutputStream objOut = new ObjectOutputStream(dos);
453

    
454
      // We write the actual pack files
455
      long packageBytes = 0;
456
      objOut.writeInt(packInfo.getPackFiles().size());
457
      
458
      Iterator iter = packInfo.getPackFiles().iterator();
459
      while (iter.hasNext())
460
      {
461
        boolean addFile = !pack.loose;
462
        PackFile pf = (PackFile) iter.next();
463
        File file = packInfo.getFile(pf);
464

    
465
        // use a back reference if file was in previous pack, and in same jar
466
        long[] info = (long[]) storedFiles.get(file);
467
        if (info != null && ! packJarsSeparate)
468
        {
469
          pf.setPreviousPackFileRef((int) info[0], info[1]);
470
          addFile = false;
471
        }
472
        
473
        objOut.writeObject(pf); // base info
474
        objOut.flush(); //make sure it is written
475

    
476
        if (addFile && ! pf.isDirectory())
477
        {
478
          long pos = dos.getByteCount(); //get the position
479

    
480
          FileInputStream inStream = new FileInputStream(file);
481
          long bytesWritten = copyStream(inStream, objOut);
482

    
483
          if (bytesWritten != pf.length())
484
            throw new IOException("File size mismatch when reading " + file);
485

    
486
          inStream.close();
487
          storedFiles.put(file, new long[] { packNumber, pos });
488
        }
489
        
490
        // even if not written, it counts towards pack size
491
        pack.nbytes += pf.length();
492
      }
493

    
494
      // Write out information about parsable files
495
      objOut.writeInt(packInfo.getParsables().size());
496
      iter = packInfo.getParsables().iterator();
497
      while (iter.hasNext())
498
        objOut.writeObject(iter.next());
499

    
500
      // Write out information about executable files
501
      objOut.writeInt(packInfo.getExecutables().size());
502
      iter = packInfo.getExecutables().iterator();
503
      while (iter.hasNext())
504
        objOut.writeObject(iter.next());
505

    
506
      // Write out information about updatecheck files
507
      objOut.writeInt(packInfo.getUpdateChecks().size());
508
      iter = packInfo.getUpdateChecks().iterator();
509
      while (iter.hasNext())
510
        objOut.writeObject(iter.next());
511

    
512
      // Cleanup
513
      objOut.flush();
514
      packStream.closeEntry();
515

    
516
      // close pack specific jar if required
517
      if (packJarsSeparate)
518
        packStream.close();
519
      
520
      packNumber++;
521
    }
522

    
523
    // Now that we know sizes, write pack metadata to primary jar.
524
    primaryJarStream.putNextEntry(new ZipEntry("packs.info"));
525
    ObjectOutputStream out = new ObjectOutputStream(primaryJarStream);
526
    out.writeInt(packsList.size());
527

    
528
    Iterator i = packsList.iterator();
529
    while (i.hasNext())
530
    {
531
      PackInfo pack = (PackInfo) i.next();
532
      out.writeObject(pack.getPack());
533
    }
534
    out.flush();
535
    primaryJarStream.closeEntry();
536
  }
537
  
538

    
539
  /* **********************************************************************
540
   * Stream utilites for creation of the installer.
541
   * **********************************************************************/
542

    
543
  /** Return a stream for the next jar. */
544
  private JarOutputStream getJarOutputStream(String name) throws IOException
545
  {
546
    File file = new File(baseFile.getParentFile(), name);
547
    sendMsg("Building installer jar: " + file.getAbsolutePath());
548

    
549
    JarOutputStream jar = new JarOutputStream(new FileOutputStream(file));
550
    jar.setLevel(Deflater.BEST_COMPRESSION);
551
    
552
    return jar;
553
  }
554

    
555
  /**
556
   * Copies contents of one jar to another.
557
   *
558
   * <p>TODO: it would be useful to be able to keep signature information from
559
   * signed jar files, can we combine manifests and still have their content
560
   * signed?
561
   *
562
   * @see #copyStream(InputStream, OutputStream)
563
   */
564
  private void copyZip(ZipInputStream zin, ZipOutputStream out)
565
    throws IOException
566
  {
567
    ZipEntry zentry;
568
    while ( (zentry = zin.getNextEntry()) != null)
569
    {
570
      try
571
      {
572
        out.putNextEntry(new ZipEntry(zentry.getName()));
573
        copyStream(zin, out);
574
        out.closeEntry();
575
        zin.closeEntry();
576
      } catch (ZipException x)
577
      {
578
        // This avoids any problem that can occur with duplicate
579
        // directories. for instance all META-INF data in jars
580
      }
581
    }
582
  }
583

    
584
  /**
585
   *  Copies all the data from the specified input stream to the specified
586
   *  output stream.
587
   *
588
   * @param  in               the input stream to read
589
   * @param  out              the output stream to write
590
   * @return                  the total number of bytes copied
591
   * @exception  IOException  if an I/O error occurs
592
   */
593
  private long copyStream(InputStream in, OutputStream out)
594
    throws IOException
595
  {
596
    byte[] buffer = new byte[5120];
597
    long bytesCopied = 0;
598
    int bytesInBuffer;
599
    while ((bytesInBuffer = in.read(buffer)) != -1)
600
    {
601
      out.write(buffer, 0, bytesInBuffer);
602
      bytesCopied += bytesInBuffer;
603
    }
604
    return bytesCopied;
605
  }
606
}