Statistics
| Revision:

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

History | View | Annotate | Download (63.1 KB)

1
/*
2
 *  $Id: Compiler.java,v 1.1 2006/06/14 07:29:07 cesar Exp $
3
 *  IzPack
4
 *  Copyright (C) 2001-2004 Julien Ponge
5
 *
6
 *  File :               Compiler.java
7
 *  Description :        The IzPack compiler.
8
 *  Author's email :     julien@izforge.com
9
 *  Author's Website :   http://www.izforge.com
10
 *
11
 *  Portions are Copyright (c) 2001 Johannes Lehtinen
12
 *  johannes.lehtinen@iki.fi
13
 *  http://www.iki.fi/jle/
14
 *
15
 *  Portions are Copyright (c) 2002 Paul Wilkinson
16
 *  paulw@wilko.com
17
 *
18
 *  Portions are Copyright (C) 2004 Gaganis Giorgos (geogka@it.teithe.gr)
19
 *
20
 *  This program is free software; you can redistribute it and/or
21
 *  modify it under the terms of the GNU General Public License
22
 *  as published by the Free Software Foundation; either version 2
23
 *  of the License, or any later version.
24
 *
25
 *  This program is distributed in the hope that it will be useful,
26
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
27
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
28
 *  GNU General Public License for more details.
29
 *
30
 *  You should have received a copy of the GNU General Public License
31
 *  along with this program; if not, write to the Free Software
32
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
33
 */
34
package com.izforge.izpack.compiler;
35

    
36
import java.io.BufferedInputStream;
37
import java.io.BufferedOutputStream;
38
import java.io.File;
39
import java.io.FileInputStream;
40
import java.io.FileNotFoundException;
41
import java.io.FileOutputStream;
42
import java.io.IOException;
43
import java.io.InputStream;
44
import java.net.MalformedURLException;
45
import java.net.URL;
46
import java.net.URLClassLoader;
47
import java.util.*;
48
import java.util.jar.JarInputStream;
49
import java.util.zip.ZipEntry;
50

    
51
import net.n3.nanoxml.NonValidator;
52
import net.n3.nanoxml.StdXMLBuilder;
53
import net.n3.nanoxml.StdXMLParser;
54
import net.n3.nanoxml.StdXMLReader;
55
import net.n3.nanoxml.XMLElement;
56

    
57
import org.apache.tools.ant.DirectoryScanner;
58

    
59
import com.izforge.izpack.CustomData;
60
import com.izforge.izpack.ExecutableFile;
61
import com.izforge.izpack.GUIPrefs;
62
import com.izforge.izpack.Info;
63
import com.izforge.izpack.PackFile;
64
import com.izforge.izpack.Panel;
65
import com.izforge.izpack.ParsableFile;
66
import com.izforge.izpack.UpdateCheck;
67
import com.izforge.izpack.event.CompilerListener;
68
import com.izforge.izpack.installer.VariableSubstitutor;
69
import com.izforge.izpack.util.Debug;
70
import com.izforge.izpack.util.OsConstraint;
71

    
72
/**
73
 *  The IzPack compiler class.
74
 *
75
 * @author     Julien Ponge
76
 * @author     Tino Schwarze
77
 * @author     Chadwick McHenry
78
 */
79
public class Compiler extends Thread
80
{
81
  /**  The compiler version. */
82
  public final static String VERSION = "1.0";
83

    
84
  /**  The IzPack version. */
85
  public final static String IZPACK_VERSION = "3.7.2 (build 2005.04.22)";
86

    
87
  /**  Standard installer. */
88
  public final static String STANDARD = "standard";
89

    
90
  /**  Web installer. */
91
  public final static String WEB = "web";
92

    
93
  /**  The IzPack home directory. */
94
  public static String IZPACK_HOME = ".";
95
  
96
  /** Constant for checking attributes. */
97
  private static boolean YES = true;
98

    
99
  /** Constant for checking attributes. */
100
  private static boolean NO = false;
101

    
102
  /**  The IzPack home directory specified or found on startup. */
103
  private static File home = new File (IZPACK_HOME);
104

    
105
  /**  The XML filename. */
106
  protected String filename;
107

    
108
  /**  The base directory. */
109
  protected String basedir;
110

    
111
  /**  The installer kind. */
112
  protected String kind;
113

    
114
  /**  The output jar filename. */
115
  protected String output;
116

    
117
  /**  The packager listener. */
118
  protected PackagerListener packagerListener;
119
  
120
  /** List of CompilerListeners which should be called 
121
   * at packaging */
122
  protected List compilerListeners;
123

    
124
  /** Collects and packs files into installation jars, as told. */
125
  private Packager packager = null;
126
  
127
  /** Error code, set to true if compilation succeeded. */
128
  private boolean compileFailed = true;
129

    
130
  /**
131
   *  The constructor.
132
   *
133
   * @param  filename  The XML filename.
134
   * @param  basedir   The base directory.
135
   * @param  kind      The installer kind.
136
   * @param  output    The installer filename.
137
   */
138
  public Compiler(String filename, String basedir, String kind, String output)
139
  {
140
    // Default initialisation
141
    this.filename = filename;
142
    this.basedir = basedir;
143
    this.kind = kind;
144
    this.output = output;
145
  }
146

    
147
  /**
148
   *  Sets the packager listener.
149
   *
150
   * @param  listener  The listener.
151
   */
152
  public void setPackagerListener(PackagerListener listener)
153
  {
154
    packagerListener = listener;
155
  }
156

    
157
  /**  Compiles. */
158
  public void compile()
159
  {
160
    start();
161
  }
162

    
163
  /**  The run() method. */
164
  public void run()
165
  {
166
    try
167
    {
168
      executeCompiler(); // Execute the compiler - may send info to System.out
169
    } catch (CompilerException ce)
170
    {
171
      System.out.println(ce.getMessage() + "\n");
172
    } catch (Exception e)
173
    {
174
      if (Debug.stackTracing())
175
      {
176
        e.printStackTrace();
177
      } else
178
      {
179
        System.out.println("ERROR: " + e.getMessage());
180
      }
181
    }
182
  }
183

    
184
  /**
185
   *  Compiles the installation.
186
   *
187
   * @exception  Exception  Description of the Exception
188
   */
189
  public void executeCompiler() throws Exception
190
  {
191
    // normalize and test: TODO: may allow failure if we require write access
192
    File base = new File(basedir).getAbsoluteFile();
193
    if (!base.canRead() || !base.isDirectory())
194
      throw new CompilerException("Invalid base directory: " + base);
195

    
196
    // We get the XML data tree
197
    XMLElement data = getXMLTree();
198

    
199
    // We create the Packager
200
    packager = new Packager();
201
    packager.setPackagerListener(packagerListener);
202

    
203
    // We add all the information
204
    addCustomListeners(data);
205
    addVariables(data);
206
    addInfo(data);
207
    addGUIPrefs(data);
208
    addLangpacks(data);
209
    addResources(data);
210
    addNativeLibraries(data);
211
    addJars(data);
212
    addPanels(data);
213
    addPacks(data);
214
    
215
    // We ask the packager to create the installer
216
    packager.createInstaller(new File(output));
217
    this.compileFailed = false;
218
  }
219

    
220
  public boolean wasSuccessful()
221
  {
222
    return !this.compileFailed;
223
  }
224

    
225

    
226
  /**
227
   *  Returns the GUIPrefs.
228
   *
229
   * @param  data           The XML data.
230
   * return                The GUIPrefs.
231
   * @exception  CompilerException  Description of the Exception
232
   */
233
  protected void addGUIPrefs(XMLElement data) throws CompilerException
234
  {
235
    notifyCompilerListener("addGUIPrefs", CompilerListener.BEGIN, data);
236
    // We get the XMLElement & the attributes
237
    XMLElement gp = data.getFirstChildNamed("guiprefs");
238
    GUIPrefs prefs = new GUIPrefs();
239
    if (gp != null)
240
    {
241
      prefs.resizable = requireYesNoAttribute(gp, "resizable");
242
      prefs.width = requireIntAttribute(gp, "width");
243
      prefs.height = requireIntAttribute(gp, "height");
244

    
245
      // Look and feel mappings
246
      Iterator it = gp.getChildrenNamed("laf").iterator();
247
      while (it.hasNext())
248
      {
249
        XMLElement laf = (XMLElement)it.next();
250
        String lafName = requireAttribute(laf, "name");
251
        requireChildNamed(laf, "os");
252

    
253
        Iterator oit = laf.getChildrenNamed("os").iterator();
254
        while (oit.hasNext())
255
        {
256
          XMLElement os = (XMLElement)oit.next(); 
257
          String osName = requireAttribute(os, "family");
258
          prefs.lookAndFeelMapping.put(osName, lafName);
259
        }
260

    
261
        Iterator pit = laf.getChildrenNamed("param").iterator();
262
        Map params = new TreeMap();
263
        while (pit.hasNext())
264
        {
265
          XMLElement param = (XMLElement)pit.next();
266
          String name  = requireAttribute(param, "name");
267
          String value = requireAttribute(param, "value");
268
          params.put(name, value);
269
        }
270
        prefs.lookAndFeelParams.put(lafName, params);
271
      }
272
      // Load modifier
273
      it = gp.getChildrenNamed("modifier").iterator();
274
      while (it.hasNext())
275
      {
276
        XMLElement curentModifier = (XMLElement)it.next();
277
        String key  = requireAttribute(curentModifier, "key");
278
        String value = requireAttribute(curentModifier, "value");
279
        prefs.modifier.put(key, value);
280
       
281
      }
282
      // make sure jar contents of each are available in installer
283
      // map is easier to read/modify than if tree
284
      HashMap lafMap = new HashMap();
285
      lafMap.put("liquid",    "liquidlnf.jar");
286
      lafMap.put("kunststoff","kunststoff.jar");
287
      lafMap.put("metouia",   "metouia.jar");
288
      lafMap.put("looks",     "looks.jar");
289
      
290
      // is this really what we want? a double loop? needed, since above, it's
291
      // the /last/ lnf for an os which is used, so can't add during initial
292
      // loop
293
      Iterator kit = prefs.lookAndFeelMapping.keySet().iterator();
294
      while (kit.hasNext())
295
      {
296
        String lafName = (String)prefs.lookAndFeelMapping.get(kit.next());
297
        String lafJarName = (String) lafMap.get(lafName);
298
        if (lafJarName == null)
299
          parseError(gp, "Unrecognized Look and Feel: " + lafName);
300

    
301
        URL lafJarURL = findIzPackResource("lib/" + lafJarName,
302
                                            "Look and Feel Jar file", gp);
303
        packager.addJarContent(lafJarURL);
304
      }
305
    }
306
    packager.setGUIPrefs(prefs);
307
    notifyCompilerListener("addGUIPrefs", CompilerListener.END, data);
308
  }
309

    
310
  /**
311
   * Add project specific external jar files to the installer.
312
   *
313
   * @param  data           The XML data.
314
   */
315
  protected void addJars(XMLElement data) throws Exception
316
  {
317
    notifyCompilerListener("addJars", CompilerListener.BEGIN, data);
318
    Iterator iter = data.getChildrenNamed("jar").iterator();
319
    while (iter.hasNext())
320
    {
321
      XMLElement el = (XMLElement) iter.next();
322
      String src = requireAttribute(el, "src");
323
      URL url = findProjectResource(src, "Jar file", el);
324
      packager.addJarContent(url);
325
      // Additionals for mark a jar file also used in the uninstaller.
326
      // The contained files will be copied from the installer into the 
327
      // uninstaller if needed.
328
      // Therefore the contained files of the jar should be in the installer also 
329
      // they are used only from the uninstaller. This is the reason why the stage 
330
      // wiil be only observed for the uninstaller.
331
      String stage = el.getAttribute("stage");
332
      if( stage != null && 
333
        ( stage.equalsIgnoreCase("both") || stage.equalsIgnoreCase("uninstall")))
334
      {
335
        CustomData ca = new CustomData( null, 
336
          getContainedFilePaths(url), null, CustomData.UNINSTALLER_JAR );
337
        packager.addCustomJar( ca, url);  
338
      }
339
     }
340
    notifyCompilerListener("addJars", CompilerListener.END, data);
341
  }
342

    
343
  /**
344
   * Add native libraries to the installer.
345
   *
346
   * @param  data           The XML data.
347
   */
348
  protected void addNativeLibraries(XMLElement data) throws Exception
349
  {
350
    boolean needAddOns = false;
351
    notifyCompilerListener("addNativeLibraries", CompilerListener.BEGIN, data);
352
    Iterator iter = data.getChildrenNamed("native").iterator();
353
    while (iter.hasNext())
354
    {
355
      XMLElement el = (XMLElement) iter.next();
356
      String type = requireAttribute(el, "type");
357
      String name = requireAttribute(el, "name");
358
      String path = "bin/native/" + type + "/" + name;
359
      URL url = findIzPackResource(path, "Native Library", el);
360
      packager.addNativeLibrary(name, url);
361
      // Additionals for mark a native lib also used in the uninstaller
362
      // The lib will be copied from the installer into the uninstaller if needed.
363
      // Therefore the lib should be in the installer also it is used only from
364
      // the uninstaller. This is the reason why the stage wiil be only observed
365
      // for the uninstaller.
366
      String stage = el.getAttribute("stage");
367
      List constraints = OsConstraint.getOsList(el);
368
      if( stage != null && 
369
        ( stage.equalsIgnoreCase("both") || stage.equalsIgnoreCase("uninstall")))
370
      {
371
        ArrayList al = new ArrayList();
372
        al.add(name);
373
        CustomData cad = new CustomData(null,al, constraints, CustomData.UNINSTALLER_LIB);
374
        packager.addNativeUninstallerLibrary(cad);
375
        needAddOns = true;
376
      }
377
      
378
    }
379
    if( needAddOns )
380
    {
381
      // Add the uninstaller extensions as a resource if specified
382
      XMLElement root = requireChildNamed(data, "info");
383
      XMLElement uninstallInfo = root.getFirstChildNamed("uninstaller");
384
      if (validateYesNoAttribute(uninstallInfo, "write", YES))
385
      {
386
        URL url = findIzPackResource("lib/uninstaller-ext.jar", "Uninstaller extensions", root);
387
        packager.addResource("IzPack.uninstaller-ext", url);
388
      }
389
      
390
    }
391
    notifyCompilerListener("addNativeLibraries", CompilerListener.END, data);
392
  }
393

    
394
  /**
395
   *  Add packs and their contents to the installer.
396
   *
397
   * @param  data           The XML data.
398
   */
399
  protected void addPacks(XMLElement data) throws CompilerException
400
  {
401
    notifyCompilerListener("addPacks", CompilerListener.BEGIN, data);
402
    // Initialisation
403
    XMLElement root = requireChildNamed(data, "packs");
404

    
405
    // at least one pack is required
406
    Vector packElements = root.getChildrenNamed("pack");
407
    if (packElements.isEmpty())
408
      parseError(root, "<packs> requires a <pack>");
409
    
410
    Iterator packIter = packElements.iterator();
411
    while (packIter.hasNext())
412
    {
413
      XMLElement el = (XMLElement) packIter.next();
414

    
415
      // Trivial initialisations
416
      String name = requireAttribute(el, "name");
417
      String id = el.getAttribute("id");
418
      boolean loose = "true".equalsIgnoreCase(el.getAttribute("loose", "false"));
419
      String description = requireChildNamed(el, "description").getContent();
420
      boolean required = requireYesNoAttribute(el, "required");
421

    
422
      PackInfo pack = new PackInfo(name, id, description, required, loose);
423
      pack.setOsConstraints(OsConstraint.getOsList(el)); // TODO: unverified
424
      pack.setPreselected(validateYesNoAttribute(el, "preselected", YES));
425

    
426
      // We get the parsables list
427
      Iterator iter = el.getChildrenNamed("parsable").iterator();
428
      while (iter.hasNext())
429
      {
430
        XMLElement p = (XMLElement) iter.next();
431
        String target = requireAttribute(p, "targetfile");
432
        String type = p.getAttribute("type", "plain");
433
        String encoding = p.getAttribute("encoding", null);
434
        List osList = OsConstraint.getOsList(p); // TODO: unverified
435

    
436
        pack.addParsable(new ParsableFile(target, type, encoding, osList));
437
      }
438

    
439
      // We get the executables list
440
      iter = el.getChildrenNamed("executable").iterator();
441
      while (iter.hasNext())
442
      {
443
        XMLElement e = (XMLElement) iter.next();
444
        ExecutableFile executable = new ExecutableFile();
445
        String val; // temp value
446

    
447
        executable.path = requireAttribute(e, "targetfile");
448

    
449
        // when to execute this executable
450
        val = e.getAttribute("stage", "never");
451
        if ("postinstall".equalsIgnoreCase(val))
452
          executable.executionStage = ExecutableFile.POSTINSTALL;
453
        else if ("uninstall".equalsIgnoreCase(val))
454
          executable.executionStage = ExecutableFile.UNINSTALL;
455

    
456
        // type of this executable
457
        val = e.getAttribute("type", "bin");
458
        if ("jar".equalsIgnoreCase(val))
459
        {
460
          executable.type = ExecutableFile.JAR;
461
          executable.mainClass = e.getAttribute("class"); // executable class
462
        }
463

    
464
        // what to do if execution fails
465
        val = e.getAttribute("failure", "ask");
466
        if ("abort".equalsIgnoreCase(val))
467
          executable.onFailure = ExecutableFile.ABORT;
468
        else if ("warn".equalsIgnoreCase(val))
469
          executable.onFailure = ExecutableFile.WARN;
470

    
471
        // whether to keep the executable after executing it
472
        val = e.getAttribute("keep");
473
        executable.keepFile = "true".equalsIgnoreCase(val);
474

    
475
        // get arguments for this executable
476
        XMLElement args = e.getFirstChildNamed("args");
477
        if (null != args)
478
        {
479
          Iterator argIterator = args.getChildrenNamed("arg").iterator();
480
          while (argIterator.hasNext())
481
          {
482
            XMLElement arg = (XMLElement) argIterator.next();
483
            executable.argList.add(requireAttribute(arg, "value"));
484
          }
485
        }
486

    
487
        executable.osList = OsConstraint.getOsList(e); // TODO: unverified
488

    
489
        pack.addExecutable(executable);
490
      }
491

    
492
      // We get the files list
493
      iter = el.getChildrenNamed("file").iterator();
494
      while (iter.hasNext())
495
      {
496
        XMLElement f = (XMLElement) iter.next();
497
        String src = requireAttribute(f, "src");
498
        String targetdir = requireAttribute(f, "targetdir");
499
        List osList = OsConstraint.getOsList(f); // TODO: unverified
500
        int override = getOverrideValue(f);
501
        Map additionals = getAdditionals(f);
502

    
503

    
504
        File file = new File(src);
505
        if (! file.isAbsolute())
506
          file = new File(basedir, src);
507
        
508
        try
509
        {
510
          addRecursively(file, targetdir, osList, override, pack, additionals);
511
        } catch (Exception x)
512
        {
513
          parseError(f, x.getMessage(), x);
514
        }
515
      }
516

    
517
      // We get the singlefiles list
518
      iter = el.getChildrenNamed("singlefile").iterator();
519
      while (iter.hasNext())
520
      {
521
        XMLElement f = (XMLElement) iter.next();
522
        String src = requireAttribute(f, "src");
523
        String target = requireAttribute(f, "target");
524
        List osList = OsConstraint.getOsList(f); // TODO: unverified
525
        int override = getOverrideValue(f);
526
        Map additionals = getAdditionals(f);
527

    
528
        File file = new File(src);
529
        if (! file.isAbsolute())
530
          file = new File(basedir, src);
531

    
532
        try
533
        {
534
          pack.addFile(file, target, osList, override, additionals);
535
        } catch (FileNotFoundException x)
536
        {
537
          parseError(f, x.getMessage(), x);
538
        }
539
      }
540

    
541
      // We get the fileset list
542
      iter = el.getChildrenNamed("fileset").iterator();
543
      while (iter.hasNext())
544
      {
545
        XMLElement f = (XMLElement) iter.next();
546
        String dir_attr = requireAttribute(f, "dir");
547

    
548
        File dir = new File(dir_attr);
549
        if (! dir.isAbsolute())
550
          dir = new File(basedir, dir_attr);
551
        if (! dir.isDirectory()) // also tests '.exists()'
552
          parseError(f, "Invalid directory 'dir': " + dir_attr);
553

    
554
        boolean casesensitive = validateYesNoAttribute(f, "casesensitive", YES);
555
        boolean defexcludes = validateYesNoAttribute(f, "defaultexcludes", YES);
556
        String targetdir = requireAttribute(f, "targetdir");
557
        List osList = OsConstraint.getOsList(f); // TODO: unverified
558
        int override = getOverrideValue(f);
559
        Map additionals = getAdditionals(f);
560

    
561
        //  get includes and excludes
562
        Vector xcludesList = null;
563
        String[] includes = null;
564
        xcludesList = f.getChildrenNamed("include");
565
        if (! xcludesList.isEmpty())
566
        {
567
          includes = new String[xcludesList.size()];
568
          for (int j = 0; j < xcludesList.size(); j++)
569
          {
570
            XMLElement xclude = (XMLElement) xcludesList.get(j);
571
            includes[j] = requireAttribute(xclude, "name");
572
          }
573
        }
574
        String[] excludes = null;
575
        xcludesList = f.getChildrenNamed("exclude");
576
        if (! xcludesList.isEmpty())
577
        {
578
          excludes = new String[xcludesList.size()];
579
          for (int j = 0; j < xcludesList.size(); j++)
580
          {
581
            XMLElement xclude = (XMLElement) xcludesList.get(j);
582
            excludes[j] = requireAttribute(xclude, "name");
583
          }
584
        }
585

    
586
        // parse additional fileset attributes "includes" and "excludes" 
587
        String [] toDo = new String[] {"includes", "excludes" };
588
        // use the existing containers filled from include and exclude 
589
        // and add the includes and excludes to it
590
        String [][] containers =  new String[][] { includes, excludes };
591
        for( int j = 0; j < toDo.length; ++j )
592
        {
593
          String inex = f.getAttribute(toDo[j]);
594
          if( inex != null && inex.length() > 0  )
595
          { // This is the same "splitting" as ant PatternSet do ...
596
            StringTokenizer tok = new StringTokenizer(inex, ", ", false);
597
            int newSize = tok.countTokens();
598
            int k = 0;
599
            String [] nCont = null;
600
            if(containers[j] != null && containers[j].length > 0 )
601
            { // old container exist; create a new which can hold all values 
602
              // and copy the old stuff to the front
603
              newSize += containers[j].length;
604
              nCont = new String[newSize];
605
              for(; k < containers[j].length; ++k)
606
                nCont[k] = containers[j][k];
607
            }
608
            if( nCont == null ) // No container for old values created,
609
              // create a new one.
610
              nCont = new String[newSize];
611
            for( ; k < newSize; ++k) 
612
              // Fill the new one or expand the existent container
613
              nCont[k] = tok.nextToken();
614
            containers[j] = nCont;
615
          }
616
        }
617
        includes = containers[0]; // push the new includes to the local var
618
        excludes = containers[1]; // push the new excludes to the local var
619

    
620
        // scan and add fileset
621
        DirectoryScanner ds = new DirectoryScanner();
622
        ds.setIncludes(includes);
623
        ds.setExcludes(excludes);
624
        if (defexcludes)
625
          ds.addDefaultExcludes();
626
        ds.setBasedir(dir);
627
        ds.setCaseSensitive(casesensitive);
628
        ds.scan();
629

    
630
        String[] files = ds.getIncludedFiles();
631
        String[] dirs = ds.getIncludedDirectories();
632

    
633
        // Directory scanner has done recursion, add files and directories
634
        for (int i = 0; i < files.length; ++i)
635
        {
636
          try
637
          {
638
            String target = new File(targetdir, files[i]).getPath();
639
            pack.addFile(new File(dir, files[i]), target, osList, 
640
              override, additionals);
641
          } catch (FileNotFoundException x)
642
          {
643
            parseError(f, x.getMessage(), x);
644
          }
645
        }
646
        for (int i = 0; i < dirs.length; ++i)
647
        {
648
          try
649
          {
650
            String target = new File(targetdir, dirs[i]).getPath();
651
            pack.addFile(new File(dir, dirs[i]), target, osList,
652
              override, additionals);
653
          } catch (FileNotFoundException x)
654
          {
655
            parseError(f, x.getMessage(), x);
656
          }
657
        }
658
      }
659

    
660
      // get the updatechecks list
661
      iter = el.getChildrenNamed("updatecheck").iterator();
662
      while (iter.hasNext())
663
      {
664
        XMLElement f = (XMLElement) iter.next();
665

    
666
        String casesensitive = f.getAttribute("casesensitive");
667

    
668
        //  get includes and excludes
669
        ArrayList includesList = new ArrayList();
670
        ArrayList excludesList = new ArrayList();
671

    
672
        //  get includes and excludes
673
        Iterator include_it = f.getChildrenNamed("include").iterator();
674
        while (include_it.hasNext())
675
        {
676
          XMLElement inc_el = (XMLElement) include_it.next();
677
          includesList.add(requireAttribute(inc_el, "name"));
678
        }
679

    
680
        Iterator exclude_it = f.getChildrenNamed("exclude").iterator();
681
        while (exclude_it.hasNext())
682
        {
683
          XMLElement excl_el = (XMLElement) exclude_it.next();
684
          excludesList.add(requireAttribute(excl_el, "name"));
685
        }
686

    
687
        pack.addUpdateCheck(
688
          new UpdateCheck(includesList, excludesList, casesensitive));
689
      }
690
      //We get the dependencies
691
      iter = el.getChildrenNamed("depends").iterator();
692
      while (iter.hasNext())
693
      {
694
        XMLElement dep = (XMLElement) iter.next();
695
        String depName = requireAttribute(dep,"packname");
696
        pack.addDependency(depName);
697

    
698
      }
699

    
700
      // We add the pack
701
      packager.addPack(pack);
702
    }
703
    checkDependencies(packager.getPacksList());
704

    
705
    notifyCompilerListener("addPacks", CompilerListener.END, data);
706
  }
707
  /**
708
   * Checks whether the dependencies stated in the configuration file are
709
   * correct. Specifically it checks that no pack point to a non existent
710
   * pack and also that there are no circular dependencies in the packs.
711
   */
712
  public void checkDependencies(List packs) throws CompilerException
713
  {
714
    // Because we use package names in the configuration file we assosiate
715
    // the names with the objects
716
    Map names = new HashMap();
717
    for (int i = 0; i < packs.size(); i++)
718
    {
719
      PackInfo pack = (PackInfo) packs.get(i);
720
      names.put(pack.getPack().name,pack);
721
    }
722
    int result = dfs(packs,names);
723
    //@todo More informative messages to include the source of the error
724
    if(result == -2)
725
      parseError("Circular dependency detected");
726
    else if(result == -1)
727
      parseError("A dependency doesn't exist");
728
  }
729
  /** We use the dfs graph search algorithm to check whether the graph
730
   * is acyclic as described in:
731
   * Thomas H. Cormen, Charles Leiserson, Ronald Rivest and Clifford Stein. Introduction
732
   * to algorithms 2nd Edition 540-549,MIT Press, 2001
733
   * @param packs The graph
734
   * @param names The name map
735
   * @return
736
   */
737
  private int dfs(List packs,Map names)
738
  {
739
    Map edges = new HashMap();
740
    for (int i = 0; i < packs.size(); i++)
741
    {
742
      PackInfo pack = (PackInfo) packs.get(i);
743
      if(pack.colour == PackInfo.WHITE)
744
      {
745
        if(dfsVisit(pack,names,edges)!=0)
746
          return -1;
747
      }
748

    
749
    }
750
    return checkBackEdges(edges);
751
  }
752
  /**
753
   * This function checks for the existence of back edges.
754
   */
755
  private int checkBackEdges(Map edges)
756
  {
757
    Set keys = edges.keySet();
758
    for (Iterator iterator = keys.iterator(); iterator.hasNext();)
759
    {
760
      final Object key = iterator.next();
761
      int color = ((Integer) edges.get(key)).intValue();
762
      if(color == PackInfo.GREY)
763
      {
764
        return -2;
765
      }
766
    }
767
    return 0;
768

    
769
  }
770
  /**
771
   * This class is used for the classification of the edges
772
   */
773
  private class Edge
774
  {
775
    PackInfo u;
776
    PackInfo v;
777
    Edge(PackInfo u,PackInfo v)
778
    {
779
      this.u = u;
780
      this.v = v;
781
    }
782
  }
783
  private int dfsVisit(PackInfo u,Map names,Map edges)
784
  {
785
    u.colour = PackInfo.GREY;
786
    List deps = u.getDependencies();
787
    if (deps != null)
788
    {
789
      for (int i = 0; i < deps.size(); i++)
790
      {
791
        String name = (String) deps.get(i);
792
        PackInfo v = (PackInfo)names.get(name);
793
        if(v == null)
794
          return -1;
795
        Edge edge = new Edge(u,v);
796
        if(edges.get(edge) == null)
797
          edges.put(edge,new Integer(v.colour));
798

    
799
        if(v.colour == PackInfo.WHITE)
800
        {
801

    
802
          final int result = dfsVisit(v,names,edges);
803
          if(result != 0)
804
            return result;
805
        }
806
      }
807
    }
808
    u.colour = PackInfo.BLACK;
809
    return 0;
810
  }
811

    
812

    
813

    
814

    
815
  /**
816
   *  Recursive method to add files in a pack.
817
   *
818
   * @param  file           The file to add.
819
   * @param  targetdir      The relative path to the parent.
820
   * @param  osList         The target OS constraints.
821
   * @param  override       Overriding behaviour.
822
   * @param  pack           Pack to be packed into
823
   * @param  additionals    Map which contains additional data
824
   * @exception FileNotFoundException if the file does not exist
825
   */
826
  protected void addRecursively(File file, String targetdir,
827
                                List osList, int override, 
828
                                PackInfo pack, Map additionals)
829
    throws IOException
830
  {
831
    String targetfile = targetdir + "/" + file.getName();
832
    if (! file.isDirectory())
833
      pack.addFile(file, targetfile, osList, override, additionals);
834
    else
835
    {
836
      File[] files = file.listFiles();
837
      if (files.length == 0) // The directory is empty so must be added
838
        pack.addFile(file, targetfile, osList, override, additionals);
839
      else
840
      {
841
        // new targetdir = targetfile;
842
        for (int i = 0; i < files.length; i++)
843
          addRecursively(files[i], targetfile, osList, override, 
844
            pack, additionals);
845
      }
846
    }
847
  }
848

    
849
  /**
850
   * Parse panels and their paramters, locate the panels resources and add to
851
   * the Packager.
852
   *
853
   * @param  data           The XML data.
854
   * @exception  CompilerException  Description of the Exception
855
   */
856
  protected void addPanels(XMLElement data) throws CompilerException
857
  {
858
    notifyCompilerListener("addPanels", CompilerListener.BEGIN, data);
859
    XMLElement root = requireChildNamed(data, "panels");
860

    
861
    // at least one panel is required
862
    Vector panels = root.getChildrenNamed("panel");
863
    if (panels.isEmpty())
864
      parseError(root, "<panels> requires a <panel>");
865
      
866
    // We process each panel markup
867
    Iterator iter = panels.iterator();
868
    while (iter.hasNext())
869
    {
870
      XMLElement xmlPanel = (XMLElement) iter.next();
871
      
872
      // create the serialized Panel data
873
      Panel panel = new Panel();
874
      panel.osConstraints = OsConstraint.getOsList(xmlPanel);
875
      String className = xmlPanel.getAttribute("classname");
876

    
877
      // Panel files come in jars packaged w/ IzPack
878
      String jarPath = "bin/panels/" + className + ".jar";
879
      URL url = findIzPackResource(jarPath, "Panel jar file", xmlPanel);
880
      String fullClassName = null;
881
      try
882
      {
883
        fullClassName = getFullClassName(url, className);
884
      }
885
      catch (Exception e)
886
      {
887
        ;
888
      }
889
      if( fullClassName != null)
890
        panel.className = fullClassName;
891
      else
892
        panel.className = className;
893
      // insert into the packager
894
      packager.addPanelJar(panel, url);
895
    }
896
    notifyCompilerListener("addPanels", CompilerListener.END, data);
897
  }
898

    
899
  /**
900
   *  Adds the resources.
901
   *
902
   * @param  data           The XML data.
903
   * @exception  CompilerException  Description of the Exception
904
   */
905
  protected void addResources(XMLElement data) throws CompilerException
906
  {
907
    notifyCompilerListener("addResources", CompilerListener.BEGIN, data);
908
    XMLElement root = data.getFirstChildNamed("resources");
909
    if (root == null)
910
      return;
911

    
912
    // We process each res markup
913
    Iterator iter = root.getChildrenNamed("res").iterator();
914
    while (iter.hasNext())
915
    {
916
      XMLElement res = (XMLElement) iter.next();
917
      String id = requireAttribute(res, "id");
918
      String src = requireAttribute(res, "src");
919
      boolean parse = validateYesNoAttribute(res, "parse", NO);
920

    
921
      // basedir is not prepended if src is already an absolute path
922
      URL url = findProjectResource(src, "Resource", res);
923

    
924
      // substitute variable values in the resource if parsed
925
      if (parse)
926
      {
927
        if (packager.getVariables().isEmpty())
928
        {
929
          parseWarn(res, "No variables defined. " +
930
                    url.getPath() + " not parsed.");
931
        } else
932
        {
933
          String type = res.getAttribute("type");
934
          String encoding = res.getAttribute("encoding");
935
          File parsedFile = null;
936

    
937
          try
938
          {
939
            // make the substitutions into a temp file
940
            InputStream bin = new BufferedInputStream(url.openStream());
941

    
942
            parsedFile = File.createTempFile("izpp", null);
943
            parsedFile.deleteOnExit();
944
            FileOutputStream outFile = new FileOutputStream(parsedFile);
945
            BufferedOutputStream bout = new BufferedOutputStream(outFile);
946

    
947
            VariableSubstitutor vs = new VariableSubstitutor(packager.getVariables());
948
            vs.substitute(bin, bout, type, encoding);
949
            bin.close();
950
            bout.close();
951

    
952
            // and specify the substituted file to be added to the packager
953
            url = parsedFile.toURL();
954
          } catch (IOException x)
955
          {
956
            parseError(res, x.getMessage(), x);
957
          }
958
        }
959
      }
960

    
961
      packager.addResource(id, url);
962
    }
963
    notifyCompilerListener("addResources", CompilerListener.END, data);
964
  }
965

    
966
  /**
967
   *  Adds the ISO3 codes of the langpacks and associated resources.
968
   *
969
   * @param  data           The XML data.
970
   * @exception  CompilerException  Description of the Exception
971
   */
972
  protected void addLangpacks(XMLElement data)
973
    throws CompilerException
974
  {
975
    notifyCompilerListener("addLangpacks", CompilerListener.BEGIN, data);
976
    XMLElement root = requireChildNamed(data, "locale");
977

    
978
    // at least one langpack is required
979
    Vector locals = root.getChildrenNamed("langpack");
980
    if (locals.isEmpty())
981
      parseError(root, "<locale> requires a <langpack>");
982
      
983
    // We process each langpack markup
984
    Iterator iter = locals.iterator();
985
    while (iter.hasNext())
986
    {
987
      XMLElement el = (XMLElement) iter.next();
988
      String iso3 = requireAttribute(el, "iso3");
989
      String path;
990

    
991
      path = "bin/langpacks/installer/" + iso3 + ".xml";
992
      URL iso3xmlURL = findIzPackResource(path, "ISO3 file", el);
993

    
994
      path = "bin/langpacks/flags/" + iso3 + ".gif";
995
      URL iso3FlagURL = findIzPackResource(path, "ISO3 flag image", el);
996
      
997
      packager.addLangPack(iso3, iso3xmlURL, iso3FlagURL);
998
    }
999
    notifyCompilerListener("addLangpacks", CompilerListener.END, data);
1000
  }
1001

    
1002
  /**
1003
   *  Builds the Info class from the XML tree.
1004
   *
1005
   * @param  data           The XML data.
1006
   * return                The Info.
1007
   * @exception  Exception  Description of the Exception
1008
   */
1009
  protected void addInfo(XMLElement data) throws Exception
1010
  {
1011
    notifyCompilerListener("addInfo", CompilerListener.BEGIN, data);
1012
    // Initialisation
1013
    XMLElement root = requireChildNamed(data, "info");
1014

    
1015
    Info info = new Info();
1016
    String temp = null;
1017
    info.setAppName(requireContent(requireChildNamed(root, "appname")));
1018
    info.setAppVersion(requireContent(requireChildNamed(root, "appversion")));
1019
    // We get the installation subpath
1020
    XMLElement subpath = root.getFirstChildNamed("appsubpath");
1021
    if(subpath != null)
1022
    {
1023
      info.setInstallationSubPath(requireContent(subpath));
1024
    }
1025

    
1026
    // validate and insert app URL
1027
    final XMLElement URLElem = root.getFirstChildNamed("url");
1028
    if(URLElem != null)
1029
    {
1030
      URL appURL = requireURLContent(URLElem);
1031
      info.setAppURL(appURL.toString());
1032
    }
1033

    
1034
    // We get the authors list
1035
    XMLElement authors = root.getFirstChildNamed("authors");
1036
    if (authors != null)
1037
    {
1038
      Iterator iter = authors.getChildrenNamed("author").iterator();
1039
      while (iter.hasNext())
1040
      {
1041
        XMLElement author = (XMLElement) iter.next();
1042
        String name = requireAttribute(author, "name");
1043
        String email = requireAttribute(author, "email");
1044
        info.addAuthor(new Info.Author(name, email));
1045
      }
1046
    }
1047

    
1048
    // We get the java version required
1049
    XMLElement javaVersion = root.getFirstChildNamed("javaversion");
1050
    if (javaVersion != null)
1051
      info.setJavaVersion(requireContent(javaVersion));
1052

    
1053
    // validate and insert (and require if -web kind) web dir
1054
    XMLElement webDirURL = root.getFirstChildNamed("webdir");
1055
    if (webDirURL != null)
1056
      info.setWebDirURL(requireURLContent(webDirURL).toString());
1057
    if (kind != null)
1058
    {
1059
      if (kind.equalsIgnoreCase(WEB) && webDirURL == null)
1060
      {
1061
        parseError(root, "<webdir> required when \"WEB\" installer requested");
1062
      }
1063
      else if (kind.equalsIgnoreCase(STANDARD) && webDirURL != null)
1064
      {
1065
        // Need a Warning? parseWarn(webDirURL, "Not creating web installer.");
1066
        info.setWebDirURL(null);
1067
      }
1068
    }
1069

    
1070
    // Add the uninstaller as a resource if specified
1071
    XMLElement uninstallInfo = root.getFirstChildNamed("uninstaller");
1072
    if (validateYesNoAttribute(uninstallInfo, "write", YES))
1073
    {
1074
      URL url = findIzPackResource("lib/uninstaller.jar", "Uninstaller", root);
1075
      packager.addResource("IzPack.uninstaller", url);
1076
    }
1077

    
1078
    packager.setInfo(info);
1079
    notifyCompilerListener("addInfo", CompilerListener.END, data);
1080
  }
1081

    
1082
  /**
1083
   *  Variable declaration is a fragment of the xml file.  For example:
1084
   *  <pre>
1085
   *    &lt;variables&gt;
1086
   *      &lt;variable name="nom" value="value"/&gt;
1087
   *      &lt;variable name="foo" value="pippo"/&gt;
1088
   *    &lt;/variables&gt;
1089
   *  </pre>
1090
   *  variable declared in this can be referred to in parsable files.
1091
   *
1092
   * @param  data           The XML data.
1093
   * @exception  CompilerException  Description of the Exception
1094
   */
1095
  protected void addVariables(XMLElement data) throws CompilerException
1096
  {
1097
    notifyCompilerListener("addVariables", CompilerListener.BEGIN, data);
1098
    // We get the varible list
1099
    XMLElement root = data.getFirstChildNamed("variables");
1100
    if (root == null)
1101
      return;
1102

    
1103
    Properties variables = packager.getVariables();
1104

    
1105
    Iterator iter = root.getChildrenNamed("variable").iterator();
1106
    while (iter.hasNext())
1107
    {
1108
      XMLElement var = (XMLElement) iter.next();
1109
      String name = requireAttribute(var, "name");
1110
      String value = requireAttribute(var, "value");
1111
      if (variables.contains(name))
1112
        parseWarn(var, "Variable '" + name + "' being overwritten");
1113
      variables.setProperty(name, value);
1114
    }
1115
    notifyCompilerListener("addVariables", CompilerListener.END, data);
1116
  }
1117

    
1118
  /**
1119
   *  Returns the XMLElement representing the installation XML file.
1120
   *
1121
   * @return                The XML tree.
1122
   * @exception  CompilerException  For problems with the installation file
1123
   * @exception  IOException  for errors reading the installation file
1124
   */
1125
  protected XMLElement getXMLTree() throws CompilerException, IOException
1126
  {
1127
    // Initialises the parser
1128
    StdXMLParser parser = new StdXMLParser();
1129
    parser.setBuilder(new StdXMLBuilder());
1130
    parser.setReader(new StdXMLReader(new FileInputStream(filename)));
1131
    parser.setValidator(new NonValidator());
1132

    
1133
    // We get it
1134
    XMLElement data = null;
1135
    try
1136
    {
1137
      data = (XMLElement) parser.parse();
1138
    } catch (Exception x)
1139
    {
1140
      throw new CompilerException("Error parsing installation file", x);
1141
    }
1142

    
1143
    // We check it
1144
    if (!"installation".equalsIgnoreCase(data.getName()))
1145
      parseError(data, "this is not an IzPack XML installation file");
1146
    if (!requireAttribute(data, "version").equalsIgnoreCase(VERSION))
1147
      parseError(
1148
        data,
1149
        "the file version is different from the compiler version");
1150

    
1151
    // We finally return the tree
1152
    return data;
1153
  }
1154

    
1155
  protected int getOverrideValue(XMLElement f)
1156
    throws CompilerException
1157
  {
1158
    int override = PackFile.OVERRIDE_UPDATE;
1159

    
1160
    String override_val = f.getAttribute("override");
1161
    if (override_val != null)
1162
    {
1163
      if (override_val.equalsIgnoreCase("true"))
1164
      {
1165
        override = PackFile.OVERRIDE_TRUE;
1166
      } else if (override_val.equalsIgnoreCase("false"))
1167
      {
1168
        override = PackFile.OVERRIDE_FALSE;
1169
      } else if (override_val.equalsIgnoreCase("asktrue"))
1170
      {
1171
        override = PackFile.OVERRIDE_ASK_TRUE;
1172
      } else if (override_val.equalsIgnoreCase("askfalse"))
1173
      {
1174
        override = PackFile.OVERRIDE_ASK_FALSE;
1175
      } else if (override_val.equalsIgnoreCase("update"))
1176
      {
1177
        override = PackFile.OVERRIDE_UPDATE;
1178
      }
1179
      else
1180
        parseError(f, "invalid value for attribute \"override\"");
1181
    }
1182

    
1183
    return override;
1184
  }
1185

    
1186
  /**
1187
   * Look for a project specified resources, which, if not absolute, are sought
1188
   * relative to the projects basedir. The path should use '/' as the
1189
   * fileSeparator. If the resource is not found, a CompilerException is thrown
1190
   * indicating fault in the parent element.
1191
   *
1192
   * @param path the relative path (using '/' as separator) to the resource.
1193
   * @param desc the description of the resource used to report errors
1194
   * @param parent the XMLElement the resource is specified in, used to
1195
   *               report errors
1196
   * @return a URL to the resource.
1197
   */
1198
  private URL findProjectResource(String path, String desc, XMLElement parent)
1199
    throws CompilerException
1200
  {
1201
    URL url = null;
1202
    File resource = new File(path);
1203
    if (! resource.isAbsolute())
1204
      resource = new File(basedir, path);
1205

    
1206
    if (! resource.exists()) // fatal
1207
      parseError(parent, desc + " not found: " + resource);
1208

    
1209
    try
1210
    {
1211
      url = resource.toURL();
1212
    } catch(MalformedURLException how)
1213
    {
1214
      parseError(parent, desc + "(" + resource + ")", how);
1215
    }
1216

    
1217
    return url;
1218
  }
1219

    
1220
  /**
1221
   * Look for an IzPack resource either in the compiler jar, or within
1222
   * IZPACK_HOME.  The path must not be absolute. The path must use '/' as the
1223
   * fileSeparator (it's used to access the jar file). If the resource is not
1224
   * found, a CompilerException is thrown indicating fault in the parent
1225
   * element.
1226
   *
1227
   * @param path the relative path (using '/' as separator) to the resource.
1228
   * @param desc the description of the resource used to report errors
1229
   * @param parent the XMLElement the resource is specified in, used to
1230
   *               report errors
1231
   * @return a URL to the resource.
1232
   */
1233
  private URL findIzPackResource(String path, String desc, XMLElement parent)
1234
    throws CompilerException
1235
  {
1236
    URL url = getClass().getResource("/" + path);
1237
    if (url == null)
1238
    {
1239
      File resource = new File(path);
1240
      if (! resource.isAbsolute())
1241
        resource = new File(IZPACK_HOME, path);
1242

    
1243
      if (! resource.exists()) // fatal
1244
        parseError(parent, desc + " not found: " + resource);
1245

    
1246
      try
1247
      {
1248
        url = resource.toURL();
1249
      } catch(MalformedURLException how)
1250
      {
1251
        parseError(parent, desc + "(" + resource + ")", how);
1252
      }
1253
    }
1254

    
1255
    return url;
1256
  }
1257
  /**
1258
   * Create parse error with consistent messages. Includes file name. For use
1259
   * When parent is unknown.
1260
   *
1261
   * @param message Brief message explaining error
1262
   */
1263
  protected void parseError(String message)
1264
    throws CompilerException
1265
  {
1266
    this.compileFailed = true;
1267
    throw new CompilerException(
1268
      filename + ":" + message);
1269
  }
1270
  /**
1271
   * Create parse error with consistent messages. Includes file name and line #
1272
   * of parent. It is an error for 'parent' to be null.
1273
   *
1274
   * @param parent  The element in which the error occured
1275
   * @param message Brief message explaining error
1276
   */
1277
  protected void parseError(XMLElement parent, String message)
1278
    throws CompilerException
1279
  {
1280
    this.compileFailed = true;
1281
    throw new CompilerException(
1282
      filename + ":" + parent.getLineNr() + ": " + message);
1283
  }
1284

    
1285
  /**
1286
   * Create a chained parse error with consistent messages. Includes file name
1287
   * and line # of parent. It is an error for 'parent' to be null.
1288
   *
1289
   * @param parent  The element in which the error occured
1290
   * @param message Brief message explaining error
1291
   */
1292
  protected void parseError(XMLElement parent, String message, Throwable cause)
1293
    throws CompilerException
1294
  {
1295
    this.compileFailed = true;
1296
    throw new CompilerException(
1297
      filename + ":" + parent.getLineNr() + ": " + message,
1298
      cause);
1299
  }
1300

    
1301
  /**
1302
   * Create a parse warning with consistent messages. Includes file name
1303
   * and line # of parent. It is an error for 'parent' to be null.
1304
   *
1305
   * @param parent  The element in which the warning occured
1306
   * @param message Warning message
1307
   */
1308
  protected void parseWarn(XMLElement parent, String message)
1309
  {
1310
    System.out.println(filename + ":" + parent.getLineNr() + ": " + message);
1311
  }
1312

    
1313
  /**
1314
   * Call getFirstChildNamed on the parent, producing a meaningful error
1315
   * message on failure. It is an error for 'parent' to be null.
1316
   *
1317
   * @param parent  The element to search for a child
1318
   * @param name    Name of the child element to get
1319
   */
1320
  protected XMLElement requireChildNamed(XMLElement parent, String name)
1321
    throws CompilerException
1322
  {
1323
    XMLElement child = parent.getFirstChildNamed(name);
1324
    if (child == null)
1325
      parseError(
1326
        parent,
1327
        "<" + parent.getName() + "> requires child <" + name + ">");
1328
    return child;
1329
  }
1330

    
1331
  /**
1332
   * Call getContent on an element, producing a meaningful error message if not
1333
   * present, or empty, or a valid URL. It is an error for 'element' to be
1334
   * null.
1335
   *
1336
   * @param element   The element to get content of
1337
   */
1338
  protected URL requireURLContent(XMLElement element)
1339
    throws CompilerException
1340
  {
1341
    URL url = null;
1342
    try
1343
    {
1344
      url = new URL(requireContent(element));
1345
    }
1346
    catch (MalformedURLException x)
1347
    {
1348
      parseError(element, "<" + element.getName() + "> requires valid URL", x);
1349
    }
1350
    return url;
1351
  }
1352

    
1353
  /**
1354
   * Call getContent on an element, producing a meaningful error message if not
1355
   * present, or empty. It is an error for 'element' to be null.
1356
   *
1357
   * @param element   The element to get content of
1358
   */
1359
  protected String requireContent(XMLElement element) throws CompilerException
1360
  {
1361
    String content = element.getContent();
1362
    if (content == null || content.length() == 0)
1363
      parseError(element, "<" + element.getName() + "> requires content");
1364
    return content;
1365
  }
1366

    
1367
  /**
1368
   * Call getAttribute on an element, producing a meaningful error message if
1369
   * not present, or empty. It is an error for 'element' or 'attribute' to be null.
1370
   *
1371
   * @param element   The element to get the attribute value of
1372
   * @param attribute The name of the attribute to get
1373
   */
1374
  protected String requireAttribute(XMLElement element, String attribute)
1375
    throws CompilerException
1376
  {
1377
    String value = element.getAttribute(attribute);
1378
    if (value == null)
1379
      parseError(
1380
        element,
1381
        "<" + element.getName() + "> requires attribute '" + attribute + "'");
1382
    return value;
1383
  }
1384

    
1385
  /**
1386
   * Get a required attribute of an element, ensuring it is an integer.  A
1387
   * meaningful error message is generated as a CompilerException if not
1388
   * present or parseable as an int. It is an error for 'element' or
1389
   * 'attribute' to be null.
1390
   *
1391
   * @param element   The element to get the attribute value of
1392
   * @param attribute The name of the attribute to get
1393
   */
1394
  protected int requireIntAttribute(XMLElement element, String attribute)
1395
    throws CompilerException
1396
  {
1397
    String value = element.getAttribute(attribute);
1398
    if (value == null || value.length() == 0)
1399
      parseError(
1400
        element,
1401
        "<" + element.getName() + "> requires attribute '" + attribute + "'");
1402
    try
1403
    {
1404
      return Integer.parseInt(value);
1405
    } catch (NumberFormatException x)
1406
    {
1407
      parseError(element, "'" + attribute + "' must be an integer");
1408
    }
1409
    return 0; // never happens
1410
  }
1411

    
1412
  /**
1413
   * Call getAttribute on an element, producing a meaningful error message if
1414
   * not present, or one of "yes" or "no". It is an error for 'element' or
1415
   * 'attribute' to be null.
1416
   *
1417
   * @param element        The element to get the attribute value of
1418
   * @param attribute      The name of the attribute to get
1419
   */
1420
  protected boolean requireYesNoAttribute(XMLElement element, String attribute)
1421
    throws CompilerException
1422
  {
1423
    String value = requireAttribute(element, attribute);
1424
    if (value.equalsIgnoreCase("yes"))
1425
      return true;
1426
    if (value.equalsIgnoreCase("no"))
1427
      return false;
1428

    
1429
    parseError(
1430
      element,
1431
      "<" + element.getName() + "> invalid attribute '"
1432
      + attribute + "': Expected (yes|no)");
1433

    
1434
    return false; // never happens
1435
  }
1436

    
1437
  /**
1438
   * Call getAttribute on an element, producing a meaningful warning if not
1439
   * "yes" or "no". If the 'element' or 'attribute' are null, the default value
1440
   * is returned.
1441
   *
1442
   * @param element        The element to get the attribute value of
1443
   * @param attribute      The name of the attribute to get
1444
   * @param defaultValue   Value returned if attribute not present or invalid
1445
   */
1446
  protected boolean validateYesNoAttribute(
1447
    XMLElement element,
1448
    String attribute,
1449
    boolean defaultValue)
1450
  {
1451
    if (element == null)
1452
      return defaultValue;
1453

    
1454
    String value =
1455
      element.getAttribute(attribute, (defaultValue ? "yes" : "no"));
1456
    if (value.equalsIgnoreCase("yes"))
1457
      return true;
1458
    if (value.equalsIgnoreCase("no"))
1459
      return false;
1460

    
1461
    // TODO: should this be an error if it's present but "none of the above"?
1462
    parseWarn(
1463
      element,
1464
      "<" + element.getName() + "> invalid attribute '"
1465
      + attribute + "': Expected (yes|no) if present");
1466

    
1467
    return defaultValue;
1468
  }
1469

    
1470
  /**
1471
   *  The main method if the compiler is invoked by a command-line call.
1472
   *
1473
   * @param  args  The arguments passed on the command-line.
1474
   */
1475
  public static void main(String[] args)
1476
  {
1477
    // Outputs some informations
1478
    System.out.println("");
1479
    System.out.println(".::  IzPack - Version " + IZPACK_VERSION + " ::.");
1480
    System.out.println("");
1481
    System.out.println("< compiler specifications version : " + VERSION + " >");
1482
    System.out.println("");
1483
    System.out.println("- Copyright (C) 2001-2004 Julien Ponge");
1484
    System.out.println(
1485
      "- Visit http://www.izforge.com/ for the latests releases");
1486
    System.out.println(
1487
      "- Released under the terms of the GNU GPL either version 2");
1488
    System.out.println("  of the licence, or any later version.");
1489
    System.out.println("");
1490

    
1491
    // exit code 1 means: error
1492
    int exitCode = 1;
1493

    
1494
    // We analyse the command line parameters
1495
    try
1496
    {
1497
      // Our arguments
1498
      String filename;
1499
      String base = ".";
1500
      String kind = "standard";
1501
      String output;
1502

    
1503
      // First check
1504
      int nArgs = args.length;
1505
      if (nArgs < 3)
1506
        throw new Exception("no arguments given");
1507

    
1508
      // We get the IzPack home directory
1509
      int stdArgsIndex;
1510
      if (args[0].equalsIgnoreCase("-HOME"))
1511
      {
1512
        stdArgsIndex = 2;
1513
        IZPACK_HOME = args[1];
1514
      } else
1515
      {
1516
        stdArgsIndex = 0;
1517
        String izHome = System.getProperty("IZPACK_HOME");
1518
        if (izHome != null)
1519
          IZPACK_HOME = izHome;
1520
      }
1521
      
1522
      home = new File(IZPACK_HOME);
1523
      if (! home.exists() && home.isDirectory())
1524
      {
1525
        System.err.println("IZPACK_HOME (" + IZPACK_HOME + ") doesn't exist");
1526
        System.exit(-1);
1527
      }
1528

    
1529
      // The users wants to know the command line parameters
1530
      if (args[stdArgsIndex].equalsIgnoreCase("-?"))
1531
      {
1532
        System.out.println(
1533
          "-> Command line parameters are : (xml file) [args]");
1534
        System.out.println(
1535
          "   (xml file): the xml file describing the installation");
1536
        System.out.println(
1537
          "   -b (base) : indicates the base path that the compiler will use for filenames");
1538
        System.out.println("               default is the current path");
1539
        System.out.println(
1540
          "   -k (kind) : indicates the kind of installer to generate");
1541
        System.out.println("               default is standard");
1542
        System.out.println("   -o (out)  : indicates the output file name");
1543
        System.out.println("               default is the xml file name\n");
1544

    
1545
        System.out.println(
1546
          "   When using vm option -DSTACKTRACE=true there is all kind of debug info ");
1547
        System.out.println("");
1548
      } else
1549
      {
1550
        // We can parse the other parameters & try to compile the installation
1551

    
1552
        // We get the input file name and we initialize the output file name
1553
        filename = args[stdArgsIndex];
1554
        // default jar files names are based on input file name
1555
        output = filename.substring(0, filename.length() - 3) + "jar";
1556

    
1557
        // We parse the other ones
1558
        int pos = stdArgsIndex + 1;
1559
        while (pos < nArgs)
1560
          if ((args[pos].startsWith("-")) && (args[pos].length() == 2))
1561
          {
1562
            switch (args[pos].toLowerCase().charAt(1))
1563
            {
1564
              case 'b' :
1565
                if ((pos + 1) < nArgs)
1566
                {
1567
                  pos++;
1568
                  base = args[pos];
1569
                } else
1570
                  throw new Exception("base argument missing");
1571
                break;
1572
              case 'k' :
1573
                if ((pos + 1) < nArgs)
1574
                {
1575
                  pos++;
1576
                  kind = args[pos];
1577
                } else
1578
                  throw new Exception("kind argument missing");
1579
                break;
1580
              case 'o' :
1581
                if ((pos + 1) < nArgs)
1582
                {
1583
                  pos++;
1584
                  output = args[pos];
1585
                } else
1586
                  throw new Exception("output argument missing");
1587
                break;
1588
              default :
1589
                throw new Exception("unknown argument");
1590
            }
1591
            pos++;
1592
          } else
1593
            throw new Exception("bad argument");
1594

    
1595
        // Outputs what we are going to do
1596
        System.out.println("-> Processing : " + filename);
1597
        System.out.println("-> Output     : " + output);
1598
        System.out.println("-> Base path  : " + base);
1599
        System.out.println("-> Kind       : " + kind);
1600
        System.out.println("");
1601

    
1602
        // Calls the compiler
1603
        Compiler compiler = new Compiler(filename, base, kind, output);
1604
        CmdlinePackagerListener listener = new CmdlinePackagerListener();
1605
        compiler.setPackagerListener(listener);
1606
        compiler.compile();
1607

    
1608
        // Waits
1609
        while (compiler.isAlive())
1610
          Thread.sleep(100);
1611

    
1612
        if (compiler.wasSuccessful())
1613
          exitCode = 0;
1614

    
1615
        System.out.println("Build time: " + new Date());
1616
      }
1617
    } catch (Exception err)
1618
    {
1619
      // Something bad has happened
1620
      System.err.println("-> Fatal error :");
1621
      System.err.println("   " + err.getMessage());
1622
      err.printStackTrace();
1623
      System.err.println("");
1624
      System.err.println("(tip : use -? to get the commmand line parameters)");
1625
    }
1626

    
1627
    // Closes the JVM
1628
    System.exit(exitCode);
1629
  }
1630

    
1631
  //-------------------------------------------------------------------------
1632
  //------------- Listener stuff ------------------------- START ------------
1633

    
1634
  /**
1635
   * This method parses install.xml for defined listeners and put they
1636
   * to the right position. If posible, the listeners will be validated.
1637
   * Listener declaration is a fragmention in install.xml like : <listeners>
1638
   * <listener compiler="PermissionCompilerListener" installer="PermissionInstallerListener"/> 
1639
   * </<listeners>
1640
   *
1641
   * @param  data  the XML data
1642
   * @exception  Exception  Description of the Exception
1643
   */
1644
  private void addCustomListeners(XMLElement data) throws Exception
1645
  {
1646
    compilerListeners = new ArrayList();
1647
    // We get the listeners
1648
    XMLElement root = data.getFirstChildNamed("listeners");
1649
    if (root == null)
1650
      return;
1651
    Iterator iter = root.getChildrenNamed("listener").iterator();
1652
    while (iter.hasNext())
1653
    {
1654
      
1655
      XMLElement xmlAction = (XMLElement) iter.next();
1656
      Object [] listener = getCompilerListenerInstance( xmlAction);
1657
      if( listener != null)
1658
        addCompilerListener((CompilerListener) listener[0] );
1659
      String [] typeNames = new String[] {"installer", "uninstaller"};
1660
      int [] types = new int[] {CustomData.INSTALLER_LISTENER, CustomData.UNINSTALLER_LISTENER};
1661
      for( int i = 0; i < typeNames.length; ++i )
1662
      {
1663
        String className = xmlAction.getAttribute(typeNames[i]);
1664
        if( className != null )
1665
        {
1666
          String jarPath = "bin/customActions/" + className + ".jar";
1667
          URL url = findIzPackResource(jarPath, "CustomAction jar file", xmlAction);
1668
          CustomData ca = new CustomData( getFullClassName(url, className), 
1669
            getContainedFilePaths(url), OsConstraint.getOsList(xmlAction), types[i] );
1670
          packager.addCustomJar( ca, url);  
1671
        }
1672
      }
1673
    }
1674
    
1675
  }
1676

    
1677
  /**
1678
   * Returns a list which contains the pathes of all
1679
   * files which are included in the given url.
1680
   * This method expects as the url param a jar. 
1681
   * @param url url of the jar file
1682
   * @return full qualified paths of the contained files
1683
   * @throws Exception
1684
   */
1685
  private List getContainedFilePaths(URL url) 
1686
    throws Exception
1687
  {
1688
    JarInputStream jis = new JarInputStream(url.openStream());
1689
    ZipEntry zentry = null;
1690
    String fullName = null;
1691
    ArrayList fullNames = new ArrayList();
1692
    while ( (zentry = jis.getNextEntry()) != null)
1693
    {
1694
      String name = zentry.getName();
1695
      // Add only files, no directory entries.
1696
      if( ! zentry.isDirectory())
1697
        fullNames.add(name);
1698
    }
1699
    jis.close();
1700
    return( fullNames );
1701
  }
1702

    
1703
  /**
1704
   * Returns the qualified class name for the given class.
1705
   * This method expects as the url param a jar file which contains
1706
   * the given class. It scans the zip entries of the jar file.
1707
   * @param url url of the jar file which contains the class
1708
   * @param className short name of the class for which the full name 
1709
   * should be resolved
1710
   * @return full qualified class name
1711
   * @throws Exception
1712
   */
1713
  private String getFullClassName(URL url, String className) 
1714
    throws Exception
1715
  {
1716
    JarInputStream jis = new JarInputStream(url.openStream());
1717
    ZipEntry zentry = null;
1718
    String fullName = null;
1719
    while ( (zentry = jis.getNextEntry()) != null)
1720
    {
1721
      String name = zentry.getName();
1722
      int lastPos = name.lastIndexOf(".class");
1723
      if( lastPos < 0 )
1724
      {
1725
        continue; // No class file.
1726
      }
1727
      name = name.replace('/', '.');
1728
      int pos = -1;
1729
      if( className != null  )
1730
      {
1731
        pos = name.indexOf(className);
1732
      }
1733
      if( name.length()  == pos + className.length() + 6 ) // "Main" class found
1734
      {
1735
        jis.close();
1736
        return(name.substring(0, lastPos));
1737
      }
1738
    }
1739
    jis.close();
1740
    return( null );
1741
  }
1742

    
1743
  /**
1744
   * Returns the compiler listener which is defined in the xml
1745
   * element. As xml element a "listner" node will be expected.
1746
   * Additional it is expected, that "findIzPackResource" returns
1747
   * an url based on "bin/customActions/[className].jar". The 
1748
   * class will be loaded via an URLClassLoader.
1749
   * @param var the xml element of the "listener" node
1750
   * @return instance of the defined compiler listener
1751
   * @throws Exception
1752
   */
1753
  private Object [] getCompilerListenerInstance( XMLElement var ) throws Exception
1754
  {
1755
    final String defaultRootPath = "com.izforge.izpack.";
1756
    final String defaultCompilerPath = "compiler.";
1757
    String className = var.getAttribute("compiler");
1758
    Class listener = null;
1759
    Object instance = null;
1760
    if( className == null )
1761
      return(null);
1762
      
1763
    // CustomAction files come in jars packaged  IzPack
1764
    String jarPath = "bin/customActions/" + className + ".jar";
1765
    URL url = findIzPackResource(jarPath, "CustomAction jar file", var);
1766
    String fullName = getFullClassName(url, className);
1767
    if( url != null )
1768
    {
1769
      if(getClass().getResource("/" + jarPath) != null )
1770
      { // Oops, standalone, URLClassLoader will not work ...
1771
        // Write the jar to a temp file.
1772
        InputStream in = null;
1773
        FileOutputStream outFile = null;
1774
        byte[] buffer = new byte[5120];
1775
        File tf = null;
1776
        try
1777
        {
1778
          tf = File. createTempFile("izpj", ".jar");
1779
          tf.deleteOnExit();
1780
          outFile = new FileOutputStream(tf);
1781
          in = getClass().getResourceAsStream("/" + jarPath);
1782
          long bytesCopied = 0;
1783
          int bytesInBuffer;
1784
          while ((bytesInBuffer = in.read(buffer)) != -1)
1785
          {
1786
            outFile.write(buffer, 0, bytesInBuffer);
1787
            bytesCopied += bytesInBuffer;
1788
          }
1789
        }
1790
        finally
1791
        {
1792
          if( in != null )
1793
            in.close();
1794
          if( outFile != null )
1795
          outFile.close();
1796
       }
1797
       url = tf.toURL();
1798
       
1799
      }
1800
      // Use the class loader of the interface as parent, else
1801
      // compile will fail at using it via an Ant task.
1802
      URLClassLoader ucl = new URLClassLoader( new URL[] {url}, 
1803
        CompilerListener.class.getClassLoader() );
1804
      listener = ucl.loadClass(fullName);
1805
    }
1806
    if( listener != null )
1807
      instance = listener.newInstance();
1808
    else
1809
      parseError(var, "Cannot find defined compiler listener " + className);
1810
    if( !  CompilerListener.class.isInstance(instance) )
1811
      parseError(var, "'" + className + "' must be implemented " + CompilerListener.class.toString());
1812
    List constraints = OsConstraint.getOsList(var);
1813
    return( new Object[] {instance, className, constraints} );
1814
  }
1815

    
1816
  /**
1817
   * Add a CompilerListener.
1818
   * A registered CompilerListener will be called at every
1819
   * enhancmend point of compiling.
1820
   * @param pe CompilerListener which should be added
1821
   */
1822
  private void addCompilerListener(CompilerListener pe)
1823
  {
1824
      compilerListeners.add(pe);
1825
  }
1826
  
1827
  /**
1828
   * Calls all defined compile listeners notify method with the given data
1829
   * @param callerName name of the calling method as string
1830
   * @param state CompileListener.BEGIN or END
1831
   * @param data current install data
1832
   * @throws CompilerException
1833
   */
1834
  private void notifyCompilerListener( String callerName, 
1835
    int state, XMLElement data ) throws CompilerException 
1836
  {
1837
    Iterator i = compilerListeners.iterator();
1838
    while( i != null && i.hasNext())
1839
    {
1840
      ((CompilerListener)i.next()).notify(callerName, state, data, packager);
1841
    }
1842

    
1843
  }
1844
  /**
1845
   * Calls the reviseAdditionalDataMap method of all registered
1846
   * CompilerListener's. 
1847
   * @param f file releated  XML node
1848
   * @return a map with the additional attributes
1849
   */
1850
  private Map getAdditionals(XMLElement f) throws CompilerException
1851
  {
1852
    Iterator i = compilerListeners.iterator();
1853
    Map retval = null;
1854
    try
1855
    {
1856
      while( i != null && i.hasNext())
1857
      {
1858
        retval = ((CompilerListener)i.next()).reviseAdditionalDataMap(retval, f);
1859
      }
1860
    }
1861
    catch(CompilerException ce)
1862
    {
1863
      parseError(f, ce.getMessage());
1864
    }
1865
    return( retval);
1866
  }
1867
  //-------------------------------------------------------------------------
1868
  //------------- Listener stuff ------------------------- END ------------
1869

    
1870
  /**
1871
   *  Used to handle the packager messages in the command-line mode.
1872
   *
1873
   * @author     julien
1874
   * created    October 26, 2002
1875
   */
1876
  static class CmdlinePackagerListener implements PackagerListener
1877
  {
1878
    /**
1879
     *  Called as the packager sends messages.
1880
     *
1881
     * @param  info  The information.
1882
     */
1883
    public void packagerMsg(String info)
1884
    {
1885
      System.out.println(info);
1886
    }
1887

    
1888
    /**  Called when the packager starts. */
1889
    public void packagerStart()
1890
    {
1891
      System.out.println("[ Begin ]");
1892
      System.out.println("");
1893
    }
1894

    
1895
    /**  Called when the packager stops. */
1896
    public void packagerStop()
1897
    {
1898
      System.out.println("");
1899
      System.out.println("[ End ]");
1900
    }
1901
  }
1902
}