001/* 002 * $Id: CompilerConfig.java,v 1.8 2005/09/09 03:28:22 jponge Exp $ 003 * IzPack - Copyright 2001-2005 Julien Ponge, All Rights Reserved. 004 * 005 * http://www.izforge.com/izpack/ 006 * http://developer.berlios.de/projects/izpack/ 007 * 008 * Copyright 2001 Johannes Lehtinen 009 * Copyright 2002 Paul Wilkinson 010 * Copyright 2004 Gaganis Giorgos 011 * 012 * 013 * Licensed under the Apache License, Version 2.0 (the "License"); 014 * you may not use this file except in compliance with the License. 015 * You may obtain a copy of the License at 016 * 017 * http://www.apache.org/licenses/LICENSE-2.0 018 * 019 * Unless required by applicable law or agreed to in writing, software 020 * distributed under the License is distributed on an "AS IS" BASIS, 021 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 022 * See the License for the specific language governing permissions and 023 * limitations under the License. 024 */ 025 026package com.izforge.izpack.compiler; 027 028import java.io.BufferedInputStream; 029import java.io.BufferedOutputStream; 030import java.io.File; 031import java.io.FileNotFoundException; 032import java.io.FileInputStream; 033import java.io.FileOutputStream; 034import java.io.IOException; 035import java.io.InputStream; 036import java.net.MalformedURLException; 037import java.net.URL; 038import java.net.URLClassLoader; 039import java.util.ArrayList; 040import java.util.Date; 041import java.util.Enumeration; 042import java.util.HashMap; 043import java.util.Iterator; 044import java.util.List; 045import java.util.Map; 046import java.util.Properties; 047import java.util.Set; 048import java.util.StringTokenizer; 049import java.util.TreeMap; 050import java.util.Vector; 051import java.util.jar.JarInputStream; 052import java.util.zip.ZipEntry; 053 054import org.apache.tools.ant.DirectoryScanner; 055 056import com.izforge.izpack.CustomData; 057import com.izforge.izpack.ExecutableFile; 058import com.izforge.izpack.GUIPrefs; 059import com.izforge.izpack.Info; 060import com.izforge.izpack.PackFile; 061import com.izforge.izpack.Panel; 062import com.izforge.izpack.ParsableFile; 063import com.izforge.izpack.UpdateCheck; 064import com.izforge.izpack.compiler.Compiler.CmdlinePackagerListener; 065import com.izforge.izpack.event.CompilerListener; 066import com.izforge.izpack.util.Debug; 067import com.izforge.izpack.util.OsConstraint; 068import com.izforge.izpack.util.VariableSubstitutor; 069 070import net.n3.nanoxml.IXMLReader; 071import net.n3.nanoxml.NonValidator; 072import net.n3.nanoxml.StdXMLBuilder; 073import net.n3.nanoxml.StdXMLParser; 074import net.n3.nanoxml.StdXMLReader; 075import net.n3.nanoxml.XMLElement; 076 077/** 078 * A parser for the installer xml configuration. This parses a document 079 * conforming to the installation.dtd and populates a Compiler instance to 080 * perform the install compilation. 081 * 082 * @author Scott Stark 083 * @version $Revision: 1.8 $ 084 */ 085public class CompilerConfig extends Thread 086{ 087 /** The compiler version. */ 088 public final static String VERSION = "1.0"; 089 090 /** Standard installer. */ 091 public final static String STANDARD = "standard"; 092 093 /** Web installer. */ 094 public final static String WEB = "web"; 095 096 /** Constant for checking attributes. */ 097 private static boolean YES = true; 098 099 /** Constant for checking attributes. */ 100 private static boolean NO = false; 101 102 /** The xml install file */ 103 private String filename; 104 /** The xml install configuration text */ 105 private String installText; 106 /** The base directory. */ 107 protected String basedir; 108 109 /** The installer packager compiler */ 110 private Compiler compiler; 111 112 /** 113 * List of CompilerListeners which should be called at packaging 114 */ 115 protected List compilerListeners = new ArrayList(); 116 117 /** 118 * Set the IzPack home directory 119 * @param izHome - the izpack home directory 120 */ 121 public static void setIzpackHome(String izHome) 122 { 123 Compiler.setIzpackHome(izHome); 124 } 125 126 /** 127 * The constructor. 128 * 129 * @param filename The XML filename. 130 * @param basedir The base directory. 131 * @param kind The installer kind. 132 * @param output The installer filename. 133 * @throws CompilerException 134 */ 135 public CompilerConfig(String filename, String basedir, String kind, String output) throws CompilerException 136 { 137 this(filename, basedir, kind, output, (PackagerListener) null); 138 } 139 /** 140 * The constructor. 141 * 142 * @param filename The XML filename. 143 * @param basedir The base directory. 144 * @param kind The installer kind. 145 * @param output The installer filename. 146 * @param listener The PackagerListener. 147 * @throws CompilerException 148 */ 149 public CompilerConfig(String filename, String basedir, String kind, String output, PackagerListener listener) 150 throws CompilerException 151 { 152 this(filename,basedir,kind,output, "default", listener); 153 } 154 155 /** 156 * @param filename The XML filename. 157 * @param kind The installer kind. 158 * @param output The installer filename. 159 * @param compr_format The compression format to be used for packs. 160 * @param listener The PackagerListener. 161 * @throws CompilerException 162 */ 163 public CompilerConfig(String filename, String base, String kind, String output, String compr_format, 164 PackagerListener listener) throws CompilerException 165 { 166 this(filename,base,kind,output, compr_format,listener, (String) null); 167 } 168 169 /** 170 * 171 * @param basedir The base directory. 172 * @param kind The installer kind. 173 * @param output The installer filename. 174 * @param listener The PackagerListener. 175 * @param installText The install xml configuration text 176 * @throws CompilerException 177 */ 178 public CompilerConfig(String basedir, String kind, String output, PackagerListener listener, 179 String installText) throws CompilerException 180 { 181 this((String) null, basedir, kind, output, "default", listener, installText); 182 } 183 /** 184 * 185 * @param filename The XML filename. 186 * @param basedir The base directory. 187 * @param kind The installer kind. 188 * @param output The installer filename. 189 * @param compr_format The compression format to be used for packs. 190 * @param listener The PackagerListener. 191 * @param installText The install xml configuration text 192 * @throws CompilerException 193 */ 194 public CompilerConfig(String filename, String basedir, String kind, String output, String compr_format, 195 PackagerListener listener, String installText) throws CompilerException 196 { 197 this( filename, basedir, kind, output, compr_format, -1, listener, installText); 198 } 199 200 /** 201 * @param filename The XML filename. 202 * @param basedir The base directory. 203 * @param kind The installer kind. 204 * @param output The installer filename. 205 * @param compr_format The compression format to be used for packs. 206 * @param compr_level Compression level to be used if supported. 207 * @param listener The PackagerListener. 208 * @param installText The install xml configuration text 209 * @throws CompilerException 210 */ 211 public CompilerConfig(String filename, String basedir, String kind, String output, String compr_format, 212 int compr_level, PackagerListener listener, String installText) throws CompilerException 213 { 214 this.filename = filename; 215 this.installText = installText; 216 this.basedir = basedir; 217 this.compiler = new Compiler(basedir, kind, output, compr_format, compr_level); 218 compiler.setPackagerListener(listener); 219 } 220 221 222 223 /** 224 * Add a name value pair to the project property set. It is <i>not</i> 225 * replaced it is already in the set of properties. 226 * 227 * @param name the name of the property 228 * @param value the value to set 229 * @return true if the property was not already set 230 */ 231 public boolean addProperty(String name, String value) 232 { 233 return compiler.addProperty(name, value); 234 } 235 236 /** 237 * Access the install compiler 238 * @return the install compiler 239 */ 240 public Compiler getCompiler() 241 { 242 return compiler; 243 } 244 245 /** 246 * Retrieves the packager listener 247 */ 248 public PackagerListener getPackagerListener() 249 { 250 return compiler.getPackagerListener(); 251 } 252 253 /** Compile the installation */ 254 public void compile() 255 { 256 start(); 257 } 258 259 /** The run() method. */ 260 public void run() 261 { 262 try 263 { 264 executeCompiler(); 265 } 266 catch (CompilerException ce) 267 { 268 System.out.println(ce.getMessage() + "\n"); 269 } 270 catch (Exception e) 271 { 272 if (Debug.stackTracing()) 273 { 274 e.printStackTrace(); 275 } 276 else 277 { 278 System.out.println("ERROR: " + e.getMessage()); 279 } 280 } 281 } 282 283 /** 284 * Compiles the installation. 285 * 286 * @exception Exception Description of the Exception 287 */ 288 public void executeCompiler() throws Exception 289 { 290 // normalize and test: TODO: may allow failure if we require write 291 // access 292 File base = new File(basedir).getAbsoluteFile(); 293 if (!base.canRead() || !base.isDirectory()) 294 throw new CompilerException("Invalid base directory: " + base); 295 296 // add izpack built in property 297 compiler.setProperty("basedir", base.toString()); 298 299 // We get the XML data tree 300 XMLElement data = getXMLTree(); 301 302 // Listeners to various events 303 addCustomListeners(data); 304 305 // Read the properties and perform replacement on the rest of the tree 306 substituteProperties(data); 307 308 // We add all the information 309 addVariables(data); 310 addInfo(data); 311 addGUIPrefs(data); 312 addLangpacks(data); 313 addResources(data); 314 addNativeLibraries(data); 315 addJars(data); 316 addPanels(data); 317 addPacks(data); 318 319 // We ask the packager to create the installer 320 compiler.createInstaller(); 321 } 322 323 public boolean wasSuccessful() 324 { 325 return compiler.wasSuccessful(); 326 } 327 328 /** 329 * Returns the GUIPrefs. 330 * 331 * @param data The XML data. 332 * @exception CompilerException Description of the Exception 333 */ 334 protected void addGUIPrefs(XMLElement data) throws CompilerException 335 { 336 notifyCompilerListener("addGUIPrefs", CompilerListener.BEGIN, data); 337 // We get the XMLElement & the attributes 338 XMLElement gp = data.getFirstChildNamed("guiprefs"); 339 GUIPrefs prefs = new GUIPrefs(); 340 if (gp != null) 341 { 342 prefs.resizable = requireYesNoAttribute(gp, "resizable"); 343 prefs.width = requireIntAttribute(gp, "width"); 344 prefs.height = requireIntAttribute(gp, "height"); 345 346 // Look and feel mappings 347 Iterator it = gp.getChildrenNamed("laf").iterator(); 348 while (it.hasNext()) 349 { 350 XMLElement laf = (XMLElement) it.next(); 351 String lafName = requireAttribute(laf, "name"); 352 requireChildNamed(laf, "os"); 353 354 Iterator oit = laf.getChildrenNamed("os").iterator(); 355 while (oit.hasNext()) 356 { 357 XMLElement os = (XMLElement) oit.next(); 358 String osName = requireAttribute(os, "family"); 359 prefs.lookAndFeelMapping.put(osName, lafName); 360 } 361 362 Iterator pit = laf.getChildrenNamed("param").iterator(); 363 Map params = new TreeMap(); 364 while (pit.hasNext()) 365 { 366 XMLElement param = (XMLElement) pit.next(); 367 String name = requireAttribute(param, "name"); 368 String value = requireAttribute(param, "value"); 369 params.put(name, value); 370 } 371 prefs.lookAndFeelParams.put(lafName, params); 372 } 373 // Load modifier 374 it = gp.getChildrenNamed("modifier").iterator(); 375 while (it.hasNext()) 376 { 377 XMLElement curentModifier = (XMLElement) it.next(); 378 String key = requireAttribute(curentModifier, "key"); 379 String value = requireAttribute(curentModifier, "value"); 380 prefs.modifier.put(key, value); 381 382 } 383 // make sure jar contents of each are available in installer 384 // map is easier to read/modify than if tree 385 HashMap lafMap = new HashMap(); 386 lafMap.put("liquid", "liquidlnf.jar"); 387 lafMap.put("kunststoff", "kunststoff.jar"); 388 lafMap.put("metouia", "metouia.jar"); 389 lafMap.put("looks", "looks.jar"); 390 391 // is this really what we want? a double loop? needed, since above, 392 // it's 393 // the /last/ lnf for an os which is used, so can't add during 394 // initial 395 // loop 396 Iterator kit = prefs.lookAndFeelMapping.keySet().iterator(); 397 while (kit.hasNext()) 398 { 399 String lafName = (String) prefs.lookAndFeelMapping.get(kit.next()); 400 String lafJarName = (String) lafMap.get(lafName); 401 if (lafJarName == null) parseError(gp, "Unrecognized Look and Feel: " + lafName); 402 403 URL lafJarURL = findIzPackResource("lib/" + lafJarName, "Look and Feel Jar file", 404 gp); 405 compiler.addJarContent(lafJarURL); 406 } 407 } 408 compiler.setGUIPrefs(prefs); 409 notifyCompilerListener("addGUIPrefs", CompilerListener.END, data); 410 } 411 412 /** 413 * Add project specific external jar files to the installer. 414 * 415 * @param data The XML data. 416 */ 417 protected void addJars(XMLElement data) throws Exception 418 { 419 notifyCompilerListener("addJars", CompilerListener.BEGIN, data); 420 Iterator iter = data.getChildrenNamed("jar").iterator(); 421 while (iter.hasNext()) 422 { 423 XMLElement el = (XMLElement) iter.next(); 424 String src = requireAttribute(el, "src"); 425 URL url = findProjectResource(src, "Jar file", el); 426 compiler.addJarContent(url); 427 // Additionals for mark a jar file also used in the uninstaller. 428 // The contained files will be copied from the installer into the 429 // uninstaller if needed. 430 // Therefore the contained files of the jar should be in the 431 // installer also 432 // they are used only from the uninstaller. This is the reason why 433 // the stage 434 // wiil be only observed for the uninstaller. 435 String stage = el.getAttribute("stage"); 436 if (stage != null 437 && (stage.equalsIgnoreCase("both") || stage.equalsIgnoreCase("uninstall"))) 438 { 439 CustomData ca = new CustomData(null, getContainedFilePaths(url), null, 440 CustomData.UNINSTALLER_JAR); 441 compiler.addCustomJar(ca, url); 442 } 443 } 444 notifyCompilerListener("addJars", CompilerListener.END, data); 445 } 446 447 /** 448 * Add native libraries to the installer. 449 * 450 * @param data The XML data. 451 */ 452 protected void addNativeLibraries(XMLElement data) throws Exception 453 { 454 boolean needAddOns = false; 455 notifyCompilerListener("addNativeLibraries", CompilerListener.BEGIN, data); 456 Iterator iter = data.getChildrenNamed("native").iterator(); 457 while (iter.hasNext()) 458 { 459 XMLElement el = (XMLElement) iter.next(); 460 String type = requireAttribute(el, "type"); 461 String name = requireAttribute(el, "name"); 462 String path = "bin/native/" + type + "/" + name; 463 URL url = findIzPackResource(path, "Native Library", el); 464 compiler.addNativeLibrary(name, url); 465 // Additionals for mark a native lib also used in the uninstaller 466 // The lib will be copied from the installer into the uninstaller if 467 // needed. 468 // Therefore the lib should be in the installer also it is used only 469 // from 470 // the uninstaller. This is the reason why the stage wiil be only 471 // observed 472 // for the uninstaller. 473 String stage = el.getAttribute("stage"); 474 List constraints = OsConstraint.getOsList(el); 475 if (stage != null 476 && (stage.equalsIgnoreCase("both") || stage.equalsIgnoreCase("uninstall"))) 477 { 478 ArrayList al = new ArrayList(); 479 al.add(name); 480 CustomData cad = new CustomData(null, al, constraints, CustomData.UNINSTALLER_LIB); 481 compiler.addNativeUninstallerLibrary(cad); 482 needAddOns = true; 483 } 484 485 } 486 if (needAddOns) 487 { 488 // Add the uninstaller extensions as a resource if specified 489 XMLElement root = requireChildNamed(data, "info"); 490 XMLElement uninstallInfo = root.getFirstChildNamed("uninstaller"); 491 if (validateYesNoAttribute(uninstallInfo, "write", YES)) 492 { 493 URL url = findIzPackResource("lib/uninstaller-ext.jar", "Uninstaller extensions", 494 root); 495 compiler.addResource("IzPack.uninstaller-ext", url); 496 } 497 498 } 499 notifyCompilerListener("addNativeLibraries", CompilerListener.END, data); 500 } 501 502 /** 503 * Add packs and their contents to the installer. 504 * 505 * @param data The XML data. 506 */ 507 protected void addPacks(XMLElement data) throws CompilerException 508 { 509 notifyCompilerListener("addPacks", CompilerListener.BEGIN, data); 510 // Initialisation 511 XMLElement root = requireChildNamed(data, "packs"); 512 513 // at least one pack is required 514 Vector packElements = root.getChildrenNamed("pack"); 515 if (packElements.isEmpty()) parseError(root, "<packs> requires a <pack>"); 516 517 Iterator packIter = packElements.iterator(); 518 while (packIter.hasNext()) 519 { 520 XMLElement el = (XMLElement) packIter.next(); 521 522 // Trivial initialisations 523 String name = requireAttribute(el, "name"); 524 String id = el.getAttribute("id"); 525 boolean loose = "true".equalsIgnoreCase(el.getAttribute("loose", "false")); 526 String description = requireChildNamed(el, "description").getContent(); 527 boolean required = requireYesNoAttribute(el, "required"); 528 String group = el.getAttribute("group"); 529 String installGroups = el.getAttribute("installGroups"); 530 531 PackInfo pack = new PackInfo(name, id, description, required, loose); 532 pack.setOsConstraints(OsConstraint.getOsList(el)); // TODO: 533 // unverified 534 pack.setPreselected(validateYesNoAttribute(el, "preselected", YES)); 535 // Set the pack group if specified 536 if (group != null) 537 pack.setGroup(group); 538 // Set the pack install groups if specified 539 if (installGroups != null) 540 { 541 StringTokenizer st = new StringTokenizer(installGroups, ","); 542 while (st.hasMoreTokens()) 543 { 544 String igroup = st.nextToken(); 545 pack.addInstallGroup(igroup); 546 } 547 } 548 549 // We get the parsables list 550 Iterator iter = el.getChildrenNamed("parsable").iterator(); 551 while (iter.hasNext()) 552 { 553 XMLElement p = (XMLElement) iter.next(); 554 String target = requireAttribute(p, "targetfile"); 555 String type = p.getAttribute("type", "plain"); 556 String encoding = p.getAttribute("encoding", null); 557 List osList = OsConstraint.getOsList(p); // TODO: unverified 558 559 pack.addParsable(new ParsableFile(target, type, encoding, osList)); 560 } 561 562 // We get the executables list 563 iter = el.getChildrenNamed("executable").iterator(); 564 while (iter.hasNext()) 565 { 566 XMLElement e = (XMLElement) iter.next(); 567 ExecutableFile executable = new ExecutableFile(); 568 String val; // temp value 569 570 executable.path = requireAttribute(e, "targetfile"); 571 572 // when to execute this executable 573 val = e.getAttribute("stage", "never"); 574 if ("postinstall".equalsIgnoreCase(val)) 575 executable.executionStage = ExecutableFile.POSTINSTALL; 576 else if ("uninstall".equalsIgnoreCase(val)) 577 executable.executionStage = ExecutableFile.UNINSTALL; 578 579 // type of this executable 580 val = e.getAttribute("type", "bin"); 581 if ("jar".equalsIgnoreCase(val)) 582 { 583 executable.type = ExecutableFile.JAR; 584 executable.mainClass = e.getAttribute("class"); // executable 585 // class 586 } 587 588 // what to do if execution fails 589 val = e.getAttribute("failure", "ask"); 590 if ("abort".equalsIgnoreCase(val)) 591 executable.onFailure = ExecutableFile.ABORT; 592 else if ("warn".equalsIgnoreCase(val)) executable.onFailure = ExecutableFile.WARN; 593 594 // whether to keep the executable after executing it 595 val = e.getAttribute("keep"); 596 executable.keepFile = "true".equalsIgnoreCase(val); 597 598 // get arguments for this executable 599 XMLElement args = e.getFirstChildNamed("args"); 600 if (null != args) 601 { 602 Iterator argIterator = args.getChildrenNamed("arg").iterator(); 603 while (argIterator.hasNext()) 604 { 605 XMLElement arg = (XMLElement) argIterator.next(); 606 executable.argList.add(requireAttribute(arg, "value")); 607 } 608 } 609 610 executable.osList = OsConstraint.getOsList(e); // TODO: 611 // unverified 612 613 pack.addExecutable(executable); 614 } 615 616 // We get the files list 617 iter = el.getChildrenNamed("file").iterator(); 618 while (iter.hasNext()) 619 { 620 XMLElement f = (XMLElement) iter.next(); 621 String src = requireAttribute(f, "src"); 622 String targetdir = requireAttribute(f, "targetdir"); 623 List osList = OsConstraint.getOsList(f); // TODO: unverified 624 int override = getOverrideValue(f); 625 Map additionals = getAdditionals(f); 626 627 File file = new File(src); 628 if (!file.isAbsolute()) file = new File(basedir, src); 629 630 try 631 { 632 addRecursively(file, targetdir, osList, override, pack, additionals); 633 } 634 catch (Exception x) 635 { 636 parseError(f, x.getMessage(), x); 637 } 638 } 639 640 // We get the singlefiles list 641 iter = el.getChildrenNamed("singlefile").iterator(); 642 while (iter.hasNext()) 643 { 644 XMLElement f = (XMLElement) iter.next(); 645 String src = requireAttribute(f, "src"); 646 String target = requireAttribute(f, "target"); 647 List osList = OsConstraint.getOsList(f); // TODO: unverified 648 int override = getOverrideValue(f); 649 Map additionals = getAdditionals(f); 650 651 File file = new File(src); 652 if (!file.isAbsolute()) file = new File(basedir, src); 653 654 try 655 { 656 pack.addFile(file, target, osList, override, additionals); 657 } 658 catch (FileNotFoundException x) 659 { 660 parseError(f, x.getMessage(), x); 661 } 662 } 663 664 // We get the fileset list 665 iter = el.getChildrenNamed("fileset").iterator(); 666 while (iter.hasNext()) 667 { 668 XMLElement f = (XMLElement) iter.next(); 669 String dir_attr = requireAttribute(f, "dir"); 670 671 File dir = new File(dir_attr); 672 if (!dir.isAbsolute()) dir = new File(basedir, dir_attr); 673 if (!dir.isDirectory()) // also tests '.exists()' 674 parseError(f, "Invalid directory 'dir': " + dir_attr); 675 676 boolean casesensitive = validateYesNoAttribute(f, "casesensitive", YES); 677 boolean defexcludes = validateYesNoAttribute(f, "defaultexcludes", YES); 678 String targetdir = requireAttribute(f, "targetdir"); 679 List osList = OsConstraint.getOsList(f); // TODO: unverified 680 int override = getOverrideValue(f); 681 Map additionals = getAdditionals(f); 682 683 // get includes and excludes 684 Vector xcludesList = null; 685 String[] includes = null; 686 xcludesList = f.getChildrenNamed("include"); 687 if (!xcludesList.isEmpty()) 688 { 689 includes = new String[xcludesList.size()]; 690 for (int j = 0; j < xcludesList.size(); j++) 691 { 692 XMLElement xclude = (XMLElement) xcludesList.get(j); 693 includes[j] = requireAttribute(xclude, "name"); 694 } 695 } 696 String[] excludes = null; 697 xcludesList = f.getChildrenNamed("exclude"); 698 if (!xcludesList.isEmpty()) 699 { 700 excludes = new String[xcludesList.size()]; 701 for (int j = 0; j < xcludesList.size(); j++) 702 { 703 XMLElement xclude = (XMLElement) xcludesList.get(j); 704 excludes[j] = requireAttribute(xclude, "name"); 705 } 706 } 707 708 // parse additional fileset attributes "includes" and "excludes" 709 String[] toDo = new String[] { "includes", "excludes"}; 710 // use the existing containers filled from include and exclude 711 // and add the includes and excludes to it 712 String[][] containers = new String[][] { includes, excludes}; 713 for (int j = 0; j < toDo.length; ++j) 714 { 715 String inex = f.getAttribute(toDo[j]); 716 if (inex != null && inex.length() > 0) 717 { // This is the same "splitting" as ant PatternSet do ... 718 StringTokenizer tok = new StringTokenizer(inex, ", ", false); 719 int newSize = tok.countTokens(); 720 int k = 0; 721 String[] nCont = null; 722 if (containers[j] != null && containers[j].length > 0) 723 { // old container exist; create a new which can hold 724 // all values 725 // and copy the old stuff to the front 726 newSize += containers[j].length; 727 nCont = new String[newSize]; 728 for (; k < containers[j].length; ++k) 729 nCont[k] = containers[j][k]; 730 } 731 if (nCont == null) // No container for old values 732 // created, 733 // create a new one. 734 nCont = new String[newSize]; 735 for (; k < newSize; ++k) 736 // Fill the new one or expand the existent container 737 nCont[k] = tok.nextToken(); 738 containers[j] = nCont; 739 } 740 } 741 includes = containers[0]; // push the new includes to the 742 // local var 743 excludes = containers[1]; // push the new excludes to the 744 // local var 745 746 // scan and add fileset 747 DirectoryScanner ds = new DirectoryScanner(); 748 ds.setIncludes(includes); 749 ds.setExcludes(excludes); 750 if (defexcludes) ds.addDefaultExcludes(); 751 ds.setBasedir(dir); 752 ds.setCaseSensitive(casesensitive); 753 ds.scan(); 754 755 String[] files = ds.getIncludedFiles(); 756 String[] dirs = ds.getIncludedDirectories(); 757 758 // Directory scanner has done recursion, add files and 759 // directories 760 for (int i = 0; i < files.length; ++i) 761 { 762 try 763 { 764 String target = new File(targetdir, files[i]).getPath(); 765 pack 766 .addFile(new File(dir, files[i]), target, osList, override, 767 additionals); 768 } 769 catch (FileNotFoundException x) 770 { 771 parseError(f, x.getMessage(), x); 772 } 773 } 774 for (int i = 0; i < dirs.length; ++i) 775 { 776 try 777 { 778 String target = new File(targetdir, dirs[i]).getPath(); 779 pack.addFile(new File(dir, dirs[i]), target, osList, override, additionals); 780 } 781 catch (FileNotFoundException x) 782 { 783 parseError(f, x.getMessage(), x); 784 } 785 } 786 } 787 788 // get the updatechecks list 789 iter = el.getChildrenNamed("updatecheck").iterator(); 790 while (iter.hasNext()) 791 { 792 XMLElement f = (XMLElement) iter.next(); 793 794 String casesensitive = f.getAttribute("casesensitive"); 795 796 // get includes and excludes 797 ArrayList includesList = new ArrayList(); 798 ArrayList excludesList = new ArrayList(); 799 800 // get includes and excludes 801 Iterator include_it = f.getChildrenNamed("include").iterator(); 802 while (include_it.hasNext()) 803 { 804 XMLElement inc_el = (XMLElement) include_it.next(); 805 includesList.add(requireAttribute(inc_el, "name")); 806 } 807 808 Iterator exclude_it = f.getChildrenNamed("exclude").iterator(); 809 while (exclude_it.hasNext()) 810 { 811 XMLElement excl_el = (XMLElement) exclude_it.next(); 812 excludesList.add(requireAttribute(excl_el, "name")); 813 } 814 815 pack.addUpdateCheck(new UpdateCheck(includesList, excludesList, casesensitive)); 816 } 817 // We get the dependencies 818 iter = el.getChildrenNamed("depends").iterator(); 819 while (iter.hasNext()) 820 { 821 XMLElement dep = (XMLElement) iter.next(); 822 String depName = requireAttribute(dep, "packname"); 823 pack.addDependency(depName); 824 825 } 826 827 // We add the pack 828 compiler.addPack(pack); 829 } 830 compiler.checkDependencies(); 831 832 notifyCompilerListener("addPacks", CompilerListener.END, data); 833 } 834 835 /** 836 * Checks whether the dependencies stated in the configuration file are correct. Specifically it 837 * checks that no pack point to a non existent pack and also that there are no circular 838 * dependencies in the packs. 839 */ 840 public void checkDependencies(List packs) throws CompilerException 841 { 842 // Because we use package names in the configuration file we assosiate 843 // the names with the objects 844 Map names = new HashMap(); 845 for (int i = 0; i < packs.size(); i++) 846 { 847 PackInfo pack = (PackInfo) packs.get(i); 848 names.put(pack.getPack().name, pack); 849 } 850 int result = dfs(packs, names); 851 // @todo More informative messages to include the source of the error 852 if (result == -2) 853 parseError("Circular dependency detected"); 854 else if (result == -1) parseError("A dependency doesn't exist"); 855 } 856 857 /** 858 * We use the dfs graph search algorithm to check whether the graph is acyclic as described in: 859 * Thomas H. Cormen, Charles Leiserson, Ronald Rivest and Clifford Stein. Introduction to 860 * algorithms 2nd Edition 540-549,MIT Press, 2001 861 * 862 * @param packs The graph 863 * @param names The name map 864 */ 865 private int dfs(List packs, Map names) 866 { 867 Map edges = new HashMap(); 868 for (int i = 0; i < packs.size(); i++) 869 { 870 PackInfo pack = (PackInfo) packs.get(i); 871 if (pack.colour == PackInfo.WHITE) 872 { 873 if (dfsVisit(pack, names, edges) != 0) return -1; 874 } 875 876 } 877 return checkBackEdges(edges); 878 } 879 880 /** 881 * This function checks for the existence of back edges. 882 */ 883 private int checkBackEdges(Map edges) 884 { 885 Set keys = edges.keySet(); 886 for (Iterator iterator = keys.iterator(); iterator.hasNext();) 887 { 888 final Object key = iterator.next(); 889 int color = ((Integer) edges.get(key)).intValue(); 890 if (color == PackInfo.GREY) { return -2; } 891 } 892 return 0; 893 894 } 895 896 /** 897 * This class is used for the classification of the edges 898 */ 899 private class Edge 900 { 901 902 PackInfo u; 903 904 PackInfo v; 905 906 Edge(PackInfo u, PackInfo v) 907 { 908 this.u = u; 909 this.v = v; 910 } 911 } 912 913 private int dfsVisit(PackInfo u, Map names, Map edges) 914 { 915 u.colour = PackInfo.GREY; 916 List deps = u.getDependencies(); 917 if (deps != null) 918 { 919 for (int i = 0; i < deps.size(); i++) 920 { 921 String name = (String) deps.get(i); 922 PackInfo v = (PackInfo) names.get(name); 923 if (v == null) 924 { 925 System.out.println("Failed to find dependency: "+name); 926 return -1; 927 } 928 Edge edge = new Edge(u, v); 929 if (edges.get(edge) == null) edges.put(edge, new Integer(v.colour)); 930 931 if (v.colour == PackInfo.WHITE) 932 { 933 934 final int result = dfsVisit(v, names, edges); 935 if (result != 0) return result; 936 } 937 } 938 } 939 u.colour = PackInfo.BLACK; 940 return 0; 941 } 942 943 /** 944 * Recursive method to add files in a pack. 945 * 946 * @param file The file to add. 947 * @param targetdir The relative path to the parent. 948 * @param osList The target OS constraints. 949 * @param override Overriding behaviour. 950 * @param pack Pack to be packed into 951 * @param additionals Map which contains additional data 952 * @exception FileNotFoundException if the file does not exist 953 */ 954 protected void addRecursively(File file, String targetdir, List osList, int override, 955 PackInfo pack, Map additionals) throws IOException 956 { 957 String targetfile = targetdir + "/" + file.getName(); 958 if (!file.isDirectory()) 959 pack.addFile(file, targetfile, osList, override, additionals); 960 else 961 { 962 File[] files = file.listFiles(); 963 if (files.length == 0) // The directory is empty so must be added 964 pack.addFile(file, targetfile, osList, override, additionals); 965 else 966 { 967 // new targetdir = targetfile; 968 for (int i = 0; i < files.length; i++) 969 addRecursively(files[i], targetfile, osList, override, pack, additionals); 970 } 971 } 972 } 973 974 /** 975 * Parse panels and their paramters, locate the panels resources and add to the Packager. 976 * 977 * @param data The XML data. 978 * @exception CompilerException Description of the Exception 979 */ 980 protected void addPanels(XMLElement data) throws CompilerException 981 { 982 notifyCompilerListener("addPanels", CompilerListener.BEGIN, data); 983 XMLElement root = requireChildNamed(data, "panels"); 984 985 // at least one panel is required 986 Vector panels = root.getChildrenNamed("panel"); 987 if (panels.isEmpty()) parseError(root, "<panels> requires a <panel>"); 988 989 // We process each panel markup 990 Iterator iter = panels.iterator(); 991 while (iter.hasNext()) 992 { 993 XMLElement xmlPanel = (XMLElement) iter.next(); 994 995 // create the serialized Panel data 996 Panel panel = new Panel(); 997 panel.osConstraints = OsConstraint.getOsList(xmlPanel); 998 String className = xmlPanel.getAttribute("classname"); 999 1000 // Panel files come in jars packaged w/ IzPack 1001 String jarPath = "bin/panels/" + className + ".jar"; 1002 URL url = findIzPackResource(jarPath, "Panel jar file", xmlPanel); 1003 String fullClassName = null; 1004 try 1005 { 1006 fullClassName = getFullClassName(url, className); 1007 } 1008 catch (Exception e) 1009 { 1010 ; 1011 } 1012 if (fullClassName != null) 1013 panel.className = fullClassName; 1014 else 1015 panel.className = className; 1016 // insert into the packager 1017 compiler.addPanelJar(panel, url); 1018 } 1019 notifyCompilerListener("addPanels", CompilerListener.END, data); 1020 } 1021 1022 /** 1023 * Adds the resources. 1024 * 1025 * @param data The XML data. 1026 * @exception CompilerException Description of the Exception 1027 */ 1028 protected void addResources(XMLElement data) throws CompilerException 1029 { 1030 notifyCompilerListener("addResources", CompilerListener.BEGIN, data); 1031 XMLElement root = data.getFirstChildNamed("resources"); 1032 if (root == null) return; 1033 1034 // We process each res markup 1035 Iterator iter = root.getChildrenNamed("res").iterator(); 1036 while (iter.hasNext()) 1037 { 1038 XMLElement res = (XMLElement) iter.next(); 1039 String id = requireAttribute(res, "id"); 1040 String src = requireAttribute(res, "src"); 1041 boolean parse = validateYesNoAttribute(res, "parse", NO); 1042 1043 // basedir is not prepended if src is already an absolute path 1044 URL url = findProjectResource(src, "Resource", res); 1045 1046 // substitute variable values in the resource if parsed 1047 if (parse) 1048 { 1049 if (compiler.getVariables().isEmpty()) 1050 { 1051 parseWarn(res, "No variables defined. " + url.getPath() + " not parsed."); 1052 } 1053 else 1054 { 1055 String type = res.getAttribute("type"); 1056 String encoding = res.getAttribute("encoding"); 1057 File parsedFile = null; 1058 1059 try 1060 { 1061 // make the substitutions into a temp file 1062 InputStream bin = new BufferedInputStream(url.openStream()); 1063 1064 parsedFile = File.createTempFile("izpp", null); 1065 parsedFile.deleteOnExit(); 1066 FileOutputStream outFile = new FileOutputStream(parsedFile); 1067 BufferedOutputStream bout = new BufferedOutputStream(outFile); 1068 1069 VariableSubstitutor vs = new VariableSubstitutor(compiler.getVariables()); 1070 vs.substitute(bin, bout, type, encoding); 1071 bin.close(); 1072 bout.close(); 1073 1074 // and specify the substituted file to be added to the 1075 // packager 1076 url = parsedFile.toURL(); 1077 } 1078 catch (IOException x) 1079 { 1080 parseError(res, x.getMessage(), x); 1081 } 1082 } 1083 } 1084 1085 compiler.addResource(id, url); 1086 } 1087 notifyCompilerListener("addResources", CompilerListener.END, data); 1088 } 1089 1090 /** 1091 * Adds the ISO3 codes of the langpacks and associated resources. 1092 * 1093 * @param data The XML data. 1094 * @exception CompilerException Description of the Exception 1095 */ 1096 protected void addLangpacks(XMLElement data) throws CompilerException 1097 { 1098 notifyCompilerListener("addLangpacks", CompilerListener.BEGIN, data); 1099 XMLElement root = requireChildNamed(data, "locale"); 1100 1101 // at least one langpack is required 1102 Vector locals = root.getChildrenNamed("langpack"); 1103 if (locals.isEmpty()) parseError(root, "<locale> requires a <langpack>"); 1104 1105 // We process each langpack markup 1106 Iterator iter = locals.iterator(); 1107 while (iter.hasNext()) 1108 { 1109 XMLElement el = (XMLElement) iter.next(); 1110 String iso3 = requireAttribute(el, "iso3"); 1111 String path; 1112 1113 path = "bin/langpacks/installer/" + iso3 + ".xml"; 1114 URL iso3xmlURL = findIzPackResource(path, "ISO3 file", el); 1115 1116 path = "bin/langpacks/flags/" + iso3 + ".gif"; 1117 URL iso3FlagURL = findIzPackResource(path, "ISO3 flag image", el); 1118 1119 compiler.addLangPack(iso3, iso3xmlURL, iso3FlagURL); 1120 } 1121 notifyCompilerListener("addLangpacks", CompilerListener.END, data); 1122 } 1123 1124 /** 1125 * Builds the Info class from the XML tree. 1126 * 1127 * @param data The XML data. return The Info. 1128 * @exception Exception Description of the Exception 1129 */ 1130 protected void addInfo(XMLElement data) throws Exception 1131 { 1132 notifyCompilerListener("addInfo", CompilerListener.BEGIN, data); 1133 // Initialisation 1134 XMLElement root = requireChildNamed(data, "info"); 1135 1136 Info info = new Info(); 1137 info.setAppName(requireContent(requireChildNamed(root, "appname"))); 1138 info.setAppVersion(requireContent(requireChildNamed(root, "appversion"))); 1139 // We get the installation subpath 1140 XMLElement subpath = root.getFirstChildNamed("appsubpath"); 1141 if (subpath != null) 1142 { 1143 info.setInstallationSubPath(requireContent(subpath)); 1144 } 1145 1146 // validate and insert app URL 1147 final XMLElement URLElem = root.getFirstChildNamed("url"); 1148 if (URLElem != null) 1149 { 1150 URL appURL = requireURLContent(URLElem); 1151 info.setAppURL(appURL.toString()); 1152 } 1153 1154 // We get the authors list 1155 XMLElement authors = root.getFirstChildNamed("authors"); 1156 if (authors != null) 1157 { 1158 Iterator iter = authors.getChildrenNamed("author").iterator(); 1159 while (iter.hasNext()) 1160 { 1161 XMLElement author = (XMLElement) iter.next(); 1162 String name = requireAttribute(author, "name"); 1163 String email = requireAttribute(author, "email"); 1164 info.addAuthor(new Info.Author(name, email)); 1165 } 1166 } 1167 1168 // We get the java version required 1169 XMLElement javaVersion = root.getFirstChildNamed("javaversion"); 1170 if (javaVersion != null) info.setJavaVersion(requireContent(javaVersion)); 1171 1172 // validate and insert (and require if -web kind) web dir 1173 XMLElement webDirURL = root.getFirstChildNamed("webdir"); 1174 if (webDirURL != null) info.setWebDirURL(requireURLContent(webDirURL).toString()); 1175 String kind = compiler.getKind(); 1176 if (kind != null) 1177 { 1178 if (kind.equalsIgnoreCase(WEB) && webDirURL == null) 1179 { 1180 parseError(root, "<webdir> required when \"WEB\" installer requested"); 1181 } 1182 else if (kind.equalsIgnoreCase(STANDARD) && webDirURL != null) 1183 { 1184 // Need a Warning? parseWarn(webDirURL, "Not creating web 1185 // installer."); 1186 info.setWebDirURL(null); 1187 } 1188 } 1189 1190 // Add the uninstaller as a resource if specified 1191 XMLElement uninstallInfo = root.getFirstChildNamed("uninstaller"); 1192 if (validateYesNoAttribute(uninstallInfo, "write", YES)) 1193 { 1194 URL url = findIzPackResource("lib/uninstaller.jar", "Uninstaller", root); 1195 compiler.addResource("IzPack.uninstaller", url); 1196 1197 if (uninstallInfo != null) 1198 { 1199 String uninstallerName = uninstallInfo.getAttribute("name"); 1200 if (uninstallerName != null && uninstallerName.endsWith(".jar") 1201 && uninstallerName.length() > ".jar".length()) 1202 info.setUninstallerName(uninstallerName); 1203 } 1204 } 1205 // Add the path for the summary log file if specified 1206 XMLElement slfPath = root.getFirstChildNamed("summarylogfilepath"); 1207 if (slfPath != null) info.setSummaryLogFilePath(requireContent(slfPath)); 1208 1209 compiler.setInfo(info); 1210 notifyCompilerListener("addInfo", CompilerListener.END, data); 1211 } 1212 1213 /** 1214 * Variable declaration is a fragment of the xml file. For example: 1215 * 1216 * <pre> 1217 * 1218 * 1219 * 1220 * 1221 * <variables> 1222 * <variable name="nom" value="value"/> 1223 * <variable name="foo" value="pippo"/> 1224 * </variables> 1225 * 1226 * 1227 * 1228 * 1229 * </pre> 1230 * 1231 * variable declared in this can be referred to in parsable files. 1232 * 1233 * @param data The XML data. 1234 * @exception CompilerException Description of the Exception 1235 */ 1236 protected void addVariables(XMLElement data) throws CompilerException 1237 { 1238 notifyCompilerListener("addVariables", CompilerListener.BEGIN, data); 1239 // We get the varible list 1240 XMLElement root = data.getFirstChildNamed("variables"); 1241 if (root == null) return; 1242 1243 Properties variables = compiler.getVariables(); 1244 1245 Iterator iter = root.getChildrenNamed("variable").iterator(); 1246 while (iter.hasNext()) 1247 { 1248 XMLElement var = (XMLElement) iter.next(); 1249 String name = requireAttribute(var, "name"); 1250 String value = requireAttribute(var, "value"); 1251 if (variables.contains(name)) 1252 parseWarn(var, "Variable '" + name + "' being overwritten"); 1253 variables.setProperty(name, value); 1254 } 1255 notifyCompilerListener("addVariables", CompilerListener.END, data); 1256 } 1257 1258 /** 1259 * Properties declaration is a fragment of the xml file. For example: 1260 * 1261 * <pre> 1262 * 1263 * 1264 * 1265 * 1266 * <properties> 1267 * <property name="app.name" value="Property Laden Installer"/> 1268 * <!-- Ant styles 'location' and 'refid' are not yet supported --> 1269 * <property file="filename-relative-to-install?"/> 1270 * <property file="filename-relative-to-install?" prefix="prefix"/> 1271 * <!-- Ant style 'url' and 'resource' are not yet supported --> 1272 * <property environment="prefix"/> 1273 * </properties> 1274 * 1275 * 1276 * 1277 * 1278 * </pre> 1279 * 1280 * variable declared in this can be referred to in parsable files. 1281 * 1282 * @param data The XML data. 1283 * @exception CompilerException Description of the Exception 1284 */ 1285 protected void substituteProperties(XMLElement data) throws CompilerException 1286 { 1287 notifyCompilerListener("substituteProperties", CompilerListener.BEGIN, data); 1288 1289 XMLElement root = data.getFirstChildNamed("properties"); 1290 if (root != null) 1291 { 1292 // add individual properties 1293 Iterator iter = root.getChildrenNamed("property").iterator(); 1294 while (iter.hasNext()) 1295 { 1296 XMLElement prop = (XMLElement) iter.next(); 1297 Property property = new Property(prop, this); 1298 property.execute(); 1299 } 1300 } 1301 1302 // temporarily remove the 'properties' branch, replace all properties in 1303 // the remaining DOM, and replace properties branch. 1304 // TODO: enhance XMLElement with an "indexOf(XMLElement)" method 1305 // and addChild(XMLElement, int) so returns to the same place. 1306 if (root != null) data.removeChild(root); 1307 1308 substituteAllProperties(data); 1309 if (root != null) data.addChild(root); 1310 1311 notifyCompilerListener("substituteProperties", CompilerListener.END, data); 1312 } 1313 1314 /** 1315 * Perform recursive substitution on all properties 1316 */ 1317 protected void substituteAllProperties(XMLElement element) throws CompilerException 1318 { 1319 Enumeration attributes = element.enumerateAttributeNames(); 1320 while (attributes.hasMoreElements()) 1321 { 1322 String name = (String) attributes.nextElement(); 1323 String value = compiler.replaceProperties(element.getAttribute(name)); 1324 element.setAttribute(name, value); 1325 } 1326 1327 String content = element.getContent(); 1328 if (content != null) 1329 { 1330 element.setContent(compiler.replaceProperties(content)); 1331 } 1332 1333 Enumeration children = element.enumerateChildren(); 1334 while (children.hasMoreElements()) 1335 { 1336 XMLElement child = (XMLElement) children.nextElement(); 1337 substituteAllProperties(child); 1338 } 1339 } 1340 1341 /** 1342 * Returns the XMLElement representing the installation XML file. 1343 * 1344 * @return The XML tree. 1345 * @exception CompilerException For problems with the installation file 1346 * @exception IOException for errors reading the installation file 1347 */ 1348 protected XMLElement getXMLTree() throws CompilerException, IOException 1349 { 1350 // Initialises the parser 1351 IXMLReader reader = null; 1352 if( filename != null ) 1353 { 1354 File file = new File(filename).getAbsoluteFile(); 1355 if (!file.canRead()) throw new CompilerException("Invalid file: " + file); 1356 reader = new StdXMLReader(new FileInputStream(filename)); 1357 // add izpack built in property 1358 compiler.setProperty("izpack.file", file.toString()); 1359 } 1360 else if( installText != null ) 1361 { 1362 reader = StdXMLReader.stringReader(installText); 1363 } 1364 else 1365 { 1366 throw new CompilerException("Neither install file or text specified"); 1367 } 1368 1369 StdXMLParser parser = new StdXMLParser(); 1370 parser.setBuilder(new StdXMLBuilder()); 1371 parser.setReader(reader); 1372 parser.setValidator(new NonValidator()); 1373 1374 // We get it 1375 XMLElement data = null; 1376 try 1377 { 1378 data = (XMLElement) parser.parse(); 1379 } 1380 catch (Exception x) 1381 { 1382 throw new CompilerException("Error parsing installation file", x); 1383 } 1384 1385 // We check it 1386 if (!"installation".equalsIgnoreCase(data.getName())) 1387 parseError(data, "this is not an IzPack XML installation file"); 1388 if (!requireAttribute(data, "version").equalsIgnoreCase(VERSION)) 1389 parseError(data, "the file version is different from the compiler version"); 1390 1391 // We finally return the tree 1392 return data; 1393 } 1394 1395 protected int getOverrideValue(XMLElement f) throws CompilerException 1396 { 1397 int override = PackFile.OVERRIDE_UPDATE; 1398 1399 String override_val = f.getAttribute("override"); 1400 if (override_val != null) 1401 { 1402 if (override_val.equalsIgnoreCase("true")) 1403 { 1404 override = PackFile.OVERRIDE_TRUE; 1405 } 1406 else if (override_val.equalsIgnoreCase("false")) 1407 { 1408 override = PackFile.OVERRIDE_FALSE; 1409 } 1410 else if (override_val.equalsIgnoreCase("asktrue")) 1411 { 1412 override = PackFile.OVERRIDE_ASK_TRUE; 1413 } 1414 else if (override_val.equalsIgnoreCase("askfalse")) 1415 { 1416 override = PackFile.OVERRIDE_ASK_FALSE; 1417 } 1418 else if (override_val.equalsIgnoreCase("update")) 1419 { 1420 override = PackFile.OVERRIDE_UPDATE; 1421 } 1422 else 1423 parseError(f, "invalid value for attribute \"override\""); 1424 } 1425 1426 return override; 1427 } 1428 1429 /** 1430 * Look for a project specified resources, which, if not absolute, are sought relative to the 1431 * projects basedir. The path should use '/' as the fileSeparator. If the resource is not found, 1432 * a CompilerException is thrown indicating fault in the parent element. 1433 * 1434 * @param path the relative path (using '/' as separator) to the resource. 1435 * @param desc the description of the resource used to report errors 1436 * @param parent the XMLElement the resource is specified in, used to report errors 1437 * @return a URL to the resource. 1438 */ 1439 private URL findProjectResource(String path, String desc, XMLElement parent) 1440 throws CompilerException 1441 { 1442 URL url = null; 1443 File resource = new File(path); 1444 if (!resource.isAbsolute()) resource = new File(basedir, path); 1445 1446 if (!resource.exists()) // fatal 1447 parseError(parent, desc + " not found: " + resource); 1448 1449 try 1450 { 1451 url = resource.toURL(); 1452 } 1453 catch (MalformedURLException how) 1454 { 1455 parseError(parent, desc + "(" + resource + ")", how); 1456 } 1457 1458 return url; 1459 } 1460 1461 /** 1462 * Look for an IzPack resource either in the compiler jar, or within IZPACK_HOME. The path must 1463 * not be absolute. The path must use '/' as the fileSeparator (it's used to access the jar 1464 * file). If the resource is not found, a CompilerException is thrown indicating fault in the 1465 * parent element. 1466 * 1467 * @param path the relative path (using '/' as separator) to the resource. 1468 * @param desc the description of the resource used to report errors 1469 * @param parent the XMLElement the resource is specified in, used to report errors 1470 * @return a URL to the resource. 1471 */ 1472 private URL findIzPackResource(String path, String desc, XMLElement parent) 1473 throws CompilerException 1474 { 1475 URL url = getClass().getResource("/" + path); 1476 if (url == null) 1477 { 1478 File resource = new File(path); 1479 1480 if (!resource.isAbsolute()) resource = new File(Compiler.IZPACK_HOME, path); 1481 1482 if (!resource.exists()) // fatal 1483 parseError(parent, desc + " not found: " + resource); 1484 1485 try 1486 { 1487 url = resource.toURL(); 1488 } 1489 catch (MalformedURLException how) 1490 { 1491 parseError(parent, desc + "(" + resource + ")", how); 1492 } 1493 } 1494 1495 return url; 1496 } 1497 1498 /** 1499 * Create parse error with consistent messages. Includes file name. For use When parent is 1500 * unknown. 1501 * 1502 * @param message Brief message explaining error 1503 */ 1504 protected void parseError(String message) throws CompilerException 1505 { 1506 throw new CompilerException(filename + ":" + message); 1507 } 1508 1509 /** 1510 * Create parse error with consistent messages. Includes file name and line # of parent. It is 1511 * an error for 'parent' to be null. 1512 * 1513 * @param parent The element in which the error occured 1514 * @param message Brief message explaining error 1515 */ 1516 protected void parseError(XMLElement parent, String message) throws CompilerException 1517 { 1518 throw new CompilerException(filename + ":" + parent.getLineNr() + ": " + message); 1519 } 1520 1521 /** 1522 * Create a chained parse error with consistent messages. Includes file name and line # of 1523 * parent. It is an error for 'parent' to be null. 1524 * 1525 * @param parent The element in which the error occured 1526 * @param message Brief message explaining error 1527 */ 1528 protected void parseError(XMLElement parent, String message, Throwable cause) 1529 throws CompilerException 1530 { 1531 throw new CompilerException(filename + ":" + parent.getLineNr() + ": " + message, cause); 1532 } 1533 1534 /** 1535 * Create a parse warning with consistent messages. Includes file name and line # of parent. It 1536 * is an error for 'parent' to be null. 1537 * 1538 * @param parent The element in which the warning occured 1539 * @param message Warning message 1540 */ 1541 protected void parseWarn(XMLElement parent, String message) 1542 { 1543 System.out.println(filename + ":" + parent.getLineNr() + ": " + message); 1544 } 1545 1546 /** 1547 * Call getFirstChildNamed on the parent, producing a meaningful error message on failure. It is 1548 * an error for 'parent' to be null. 1549 * 1550 * @param parent The element to search for a child 1551 * @param name Name of the child element to get 1552 */ 1553 protected XMLElement requireChildNamed(XMLElement parent, String name) throws CompilerException 1554 { 1555 XMLElement child = parent.getFirstChildNamed(name); 1556 if (child == null) 1557 parseError(parent, "<" + parent.getName() + "> requires child <" + name + ">"); 1558 return child; 1559 } 1560 1561 /** 1562 * Call getContent on an element, producing a meaningful error message if not present, or empty, 1563 * or a valid URL. It is an error for 'element' to be null. 1564 * 1565 * @param element The element to get content of 1566 */ 1567 protected URL requireURLContent(XMLElement element) throws CompilerException 1568 { 1569 URL url = null; 1570 try 1571 { 1572 url = new URL(requireContent(element)); 1573 } 1574 catch (MalformedURLException x) 1575 { 1576 parseError(element, "<" + element.getName() + "> requires valid URL", x); 1577 } 1578 return url; 1579 } 1580 1581 /** 1582 * Call getContent on an element, producing a meaningful error message if not present, or empty. 1583 * It is an error for 'element' to be null. 1584 * 1585 * @param element The element to get content of 1586 */ 1587 protected String requireContent(XMLElement element) throws CompilerException 1588 { 1589 String content = element.getContent(); 1590 if (content == null || content.length() == 0) 1591 parseError(element, "<" + element.getName() + "> requires content"); 1592 return content; 1593 } 1594 1595 /** 1596 * Call getAttribute on an element, producing a meaningful error message if not present, or 1597 * empty. It is an error for 'element' or 'attribute' to be null. 1598 * 1599 * @param element The element to get the attribute value of 1600 * @param attribute The name of the attribute to get 1601 */ 1602 protected String requireAttribute(XMLElement element, String attribute) 1603 throws CompilerException 1604 { 1605 String value = element.getAttribute(attribute); 1606 if (value == null) 1607 parseError(element, "<" + element.getName() + "> requires attribute '" + attribute 1608 + "'"); 1609 return value; 1610 } 1611 1612 /** 1613 * Get a required attribute of an element, ensuring it is an integer. A meaningful error message 1614 * is generated as a CompilerException if not present or parseable as an int. It is an error for 1615 * 'element' or 'attribute' to be null. 1616 * 1617 * @param element The element to get the attribute value of 1618 * @param attribute The name of the attribute to get 1619 */ 1620 protected int requireIntAttribute(XMLElement element, String attribute) 1621 throws CompilerException 1622 { 1623 String value = element.getAttribute(attribute); 1624 if (value == null || value.length() == 0) 1625 parseError(element, "<" + element.getName() + "> requires attribute '" + attribute 1626 + "'"); 1627 try 1628 { 1629 return Integer.parseInt(value); 1630 } 1631 catch (NumberFormatException x) 1632 { 1633 parseError(element, "'" + attribute + "' must be an integer"); 1634 } 1635 return 0; // never happens 1636 } 1637 1638 /** 1639 * Call getAttribute on an element, producing a meaningful error message if not present, or one 1640 * of "yes" or "no". It is an error for 'element' or 'attribute' to be null. 1641 * 1642 * @param element The element to get the attribute value of 1643 * @param attribute The name of the attribute to get 1644 */ 1645 protected boolean requireYesNoAttribute(XMLElement element, String attribute) 1646 throws CompilerException 1647 { 1648 String value = requireAttribute(element, attribute); 1649 if (value.equalsIgnoreCase("yes")) return true; 1650 if (value.equalsIgnoreCase("no")) return false; 1651 1652 parseError(element, "<" + element.getName() + "> invalid attribute '" + attribute 1653 + "': Expected (yes|no)"); 1654 1655 return false; // never happens 1656 } 1657 1658 /** 1659 * Call getAttribute on an element, producing a meaningful warning if not "yes" or "no". If the 1660 * 'element' or 'attribute' are null, the default value is returned. 1661 * 1662 * @param element The element to get the attribute value of 1663 * @param attribute The name of the attribute to get 1664 * @param defaultValue Value returned if attribute not present or invalid 1665 */ 1666 protected boolean validateYesNoAttribute(XMLElement element, String attribute, 1667 boolean defaultValue) 1668 { 1669 if (element == null) return defaultValue; 1670 1671 String value = element.getAttribute(attribute, (defaultValue ? "yes" : "no")); 1672 if (value.equalsIgnoreCase("yes")) return true; 1673 if (value.equalsIgnoreCase("no")) return false; 1674 1675 // TODO: should this be an error if it's present but "none of the 1676 // above"? 1677 parseWarn(element, "<" + element.getName() + "> invalid attribute '" + attribute 1678 + "': Expected (yes|no) if present"); 1679 1680 return defaultValue; 1681 } 1682 1683 /** 1684 * The main method if the compiler is invoked by a command-line call. 1685 * 1686 * @param args The arguments passed on the command-line. 1687 */ 1688 public static void main(String[] args) 1689 { 1690 // Outputs some informations 1691 System.out.println(""); 1692 System.out.println(".:: IzPack - Version " + Compiler.IZPACK_VERSION + " ::."); 1693 System.out.println(""); 1694 System.out.println("< compiler specifications version : " + VERSION + " >"); 1695 System.out.println(""); 1696 System.out.println("- Copyright (C) 2001-2005 Julien Ponge"); 1697 System.out.println("- Visit http://www.izforge.com/ for the latests releases"); 1698 System.out.println("- Released under the terms of the Apache Software License version 2.0."); 1699 System.out.println(""); 1700 1701 // exit code 1 means: error 1702 int exitCode = 1; 1703 1704 // We analyse the command line parameters 1705 try 1706 { 1707 // Our arguments 1708 String filename; 1709 String base = "."; 1710 String kind = "standard"; 1711 String output; 1712 String compr_format = "default"; 1713 int compr_level = -1; 1714 1715 // First check 1716 int nArgs = args.length; 1717 if (nArgs < 3) throw new Exception("no arguments given"); 1718 1719 // We get the IzPack home directory 1720 int stdArgsIndex; 1721 String home = "."; 1722 if (args[0].equalsIgnoreCase("-HOME")) 1723 { 1724 stdArgsIndex = 2; 1725 home = args[1]; 1726 } 1727 else 1728 { 1729 stdArgsIndex = 0; 1730 String izHome = System.getProperty("IZPACK_HOME"); 1731 if (izHome != null) home = izHome; 1732 } 1733 1734 File homeFile = new File(home); 1735 if (!homeFile.exists() && homeFile.isDirectory()) 1736 { 1737 System.err.println("IZPACK_HOME (" + home + ") doesn't exist"); 1738 System.exit(-1); 1739 } 1740 Compiler.setIzpackHome(home); 1741 1742 // The users wants to know the command line parameters 1743 if (args[stdArgsIndex].equalsIgnoreCase("-?")) 1744 { 1745 System.out.println("-> Command line parameters are : (xml file) [args]"); 1746 System.out.println(" (xml file): the xml file describing the installation"); 1747 System.out 1748 .println(" -b (base) : indicates the base path that the compiler will use for filenames"); 1749 System.out.println(" default is the current path"); 1750 System.out.println(" -k (kind) : indicates the kind of installer to generate"); 1751 System.out.println(" default is standard"); 1752 System.out.println(" -o (out) : indicates the output file name"); 1753 System.out.println(" default is the xml file name\n"); 1754 System.out.println(" -c (compression) : indicates the compression format to be used for packs"); 1755 System.out.println(" default is the internal deflate compression\n"); 1756 System.out.println(" -l (compression-level) : indicates the level for the used compression format"); 1757 System.out.println(" if supported. Only integer are valid\n"); 1758 1759 System.out 1760 .println(" When using vm option -DSTACKTRACE=true there is all kind of debug info "); 1761 System.out.println(""); 1762 } 1763 else 1764 { 1765 // We can parse the other parameters & try to compile the 1766 // installation 1767 1768 // We get the input file name and we initialize the output file 1769 // name 1770 filename = args[stdArgsIndex]; 1771 // default jar files names are based on input file name 1772 output = filename.substring(0, filename.length() - 3) + "jar"; 1773 1774 // We parse the other ones 1775 int pos = stdArgsIndex + 1; 1776 while (pos < nArgs) 1777 if ((args[pos].startsWith("-")) && (args[pos].length() == 2)) 1778 { 1779 switch (args[pos].toLowerCase().charAt(1)) 1780 { 1781 case 'b': 1782 if ((pos + 1) < nArgs) 1783 { 1784 pos++; 1785 base = args[pos]; 1786 } 1787 else 1788 throw new Exception("base argument missing"); 1789 break; 1790 case 'k': 1791 if ((pos + 1) < nArgs) 1792 { 1793 pos++; 1794 kind = args[pos]; 1795 } 1796 else 1797 throw new Exception("kind argument missing"); 1798 break; 1799 case 'o': 1800 if ((pos + 1) < nArgs) 1801 { 1802 pos++; 1803 output = args[pos]; 1804 } 1805 else 1806 throw new Exception("output argument missing"); 1807 break; 1808 case 'c': 1809 if ((pos + 1) < nArgs) 1810 { 1811 pos++; 1812 compr_format = args[pos]; 1813 } 1814 else 1815 throw new Exception("compression format argument missing"); 1816 break; 1817 case 'l': 1818 if ((pos + 1) < nArgs) 1819 { 1820 pos++; 1821 compr_level = Integer.parseInt(args[pos]); 1822 } 1823 else 1824 throw new Exception("compression level argument missing"); 1825 break; 1826 default: 1827 throw new Exception("unknown argument"); 1828 } 1829 pos++; 1830 } 1831 else 1832 throw new Exception("bad argument"); 1833 1834 // Outputs what we are going to do 1835 System.out.println("-> Processing : " + filename); 1836 System.out.println("-> Output : " + output); 1837 System.out.println("-> Base path : " + base); 1838 System.out.println("-> Kind : " + kind); 1839 System.out.println("-> Compression : " + compr_format); 1840 System.out.println("-> Compr. level: " + compr_level); 1841 System.out.println(""); 1842 1843 // Calls the compiler 1844 CmdlinePackagerListener listener = new CmdlinePackagerListener(); 1845 CompilerConfig compiler = new CompilerConfig(filename, base, kind, output, 1846 compr_format, compr_level, listener, (String) null); 1847 compiler.executeCompiler(); 1848 1849 // Waits 1850 while (compiler.isAlive()) 1851 Thread.sleep(100); 1852 1853 if (compiler.wasSuccessful()) exitCode = 0; 1854 1855 System.out.println("Build time: " + new Date()); 1856 } 1857 } 1858 catch (Exception err) 1859 { 1860 // Something bad has happened 1861 System.err.println("-> Fatal error :"); 1862 System.err.println(" " + err.getMessage()); 1863 err.printStackTrace(); 1864 System.err.println(""); 1865 System.err.println("(tip : use -? to get the commmand line parameters)"); 1866 } 1867 1868 // Closes the JVM 1869 System.exit(exitCode); 1870 } 1871 1872 // ------------------------------------------------------------------------- 1873 // ------------- Listener stuff ------------------------- START ------------ 1874 1875 /** 1876 * This method parses install.xml for defined listeners and put them in the right position. If 1877 * posible, the listeners will be validated. Listener declaration is a fragmention in 1878 * install.xml like : <listeners> <listener compiler="PermissionCompilerListener" 1879 * installer="PermissionInstallerListener"/> </<listeners> 1880 * 1881 * @param data the XML data 1882 * @exception Exception Description of the Exception 1883 */ 1884 private void addCustomListeners(XMLElement data) throws Exception 1885 { 1886 // We get the listeners 1887 XMLElement root = data.getFirstChildNamed("listeners"); 1888 if (root == null) return; 1889 1890 Iterator iter = root.getChildrenNamed("listener").iterator(); 1891 while (iter.hasNext()) 1892 { 1893 XMLElement xmlAction = (XMLElement) iter.next(); 1894 Object[] listener = getCompilerListenerInstance(xmlAction); 1895 if (listener != null) 1896 addCompilerListener((CompilerListener) listener[0]); 1897 String[] typeNames = new String[] { "installer", "uninstaller"}; 1898 int[] types = new int[] { CustomData.INSTALLER_LISTENER, 1899 CustomData.UNINSTALLER_LISTENER}; 1900 for (int i = 0; i < typeNames.length; ++i) 1901 { 1902 String className = xmlAction.getAttribute(typeNames[i]); 1903 if (className != null) 1904 { 1905 // Check for a jar attribute on the listener 1906 String jarPath = xmlAction.getAttribute("jar"); 1907 jarPath = compiler.replaceProperties(jarPath); 1908 if( jarPath == null ) 1909 jarPath = "bin/customActions/" + className + ".jar"; 1910 List constraints = OsConstraint.getOsList(xmlAction); 1911 compiler.addCustomListener(types[i], className, jarPath, constraints); 1912 } 1913 } 1914 } 1915 1916 } 1917 1918 /** 1919 * Returns a list which contains the pathes of all files which are included in the given url. 1920 * This method expects as the url param a jar. 1921 * 1922 * @param url url of the jar file 1923 * @return full qualified paths of the contained files 1924 * @throws Exception 1925 */ 1926 private List getContainedFilePaths(URL url) throws Exception 1927 { 1928 JarInputStream jis = new JarInputStream(url.openStream()); 1929 ZipEntry zentry = null; 1930 ArrayList fullNames = new ArrayList(); 1931 while ((zentry = jis.getNextEntry()) != null) 1932 { 1933 String name = zentry.getName(); 1934 // Add only files, no directory entries. 1935 if (!zentry.isDirectory()) fullNames.add(name); 1936 } 1937 jis.close(); 1938 return (fullNames); 1939 } 1940 1941 /** 1942 * Returns the qualified class name for the given class. This method expects as the url param a 1943 * jar file which contains the given class. It scans the zip entries of the jar file. 1944 * 1945 * @param url url of the jar file which contains the class 1946 * @param className short name of the class for which the full name should be resolved 1947 * @return full qualified class name 1948 * @throws Exception 1949 */ 1950 private String getFullClassName(URL url, String className) throws Exception 1951 { 1952 JarInputStream jis = new JarInputStream(url.openStream()); 1953 ZipEntry zentry = null; 1954 while ((zentry = jis.getNextEntry()) != null) 1955 { 1956 String name = zentry.getName(); 1957 int lastPos = name.lastIndexOf(".class"); 1958 if (lastPos < 0) 1959 { 1960 continue; // No class file. 1961 } 1962 name = name.replace('/', '.'); 1963 int pos = -1; 1964 if (className != null) 1965 { 1966 pos = name.indexOf(className); 1967 } 1968 if (name.length() == pos + className.length() + 6) // "Main" class 1969 // found 1970 { 1971 jis.close(); 1972 return (name.substring(0, lastPos)); 1973 } 1974 } 1975 jis.close(); 1976 return (null); 1977 } 1978 1979 /** 1980 * Returns the compiler listener which is defined in the xml element. As 1981 * xml element a "listner" node will be expected. Additional it is expected, 1982 * that either "findIzPackResource" returns an url based on 1983 * "bin/customActions/[className].jar", or that the listener element has 1984 * a jar attribute specifying the listener jar path. The class will be 1985 * loaded via an URLClassLoader. 1986 * 1987 * @param var the xml element of the "listener" node 1988 * @return instance of the defined compiler listener 1989 * @throws Exception 1990 */ 1991 private Object[] getCompilerListenerInstance(XMLElement var) throws Exception 1992 { 1993 String className = var.getAttribute("compiler"); 1994 Class listener = null; 1995 Object instance = null; 1996 if (className == null) return (null); 1997 1998 // CustomAction files come in jars packaged IzPack, or they can be 1999 // specified via a jar attribute on the listener 2000 String jarPath = var.getAttribute("jar"); 2001 jarPath = compiler.replaceProperties(jarPath); 2002 if( jarPath == null ) 2003 jarPath = "bin/customActions/" + className + ".jar"; 2004 URL url = findIzPackResource(jarPath, "CustomAction jar file", var); 2005 String fullName = getFullClassName(url, className); 2006 if (url != null) 2007 { 2008 if (getClass().getResource("/" + jarPath) != null) 2009 { // Oops, standalone, URLClassLoader will not work ... 2010 // Write the jar to a temp file. 2011 InputStream in = null; 2012 FileOutputStream outFile = null; 2013 byte[] buffer = new byte[5120]; 2014 File tf = null; 2015 try 2016 { 2017 tf = File.createTempFile("izpj", ".jar"); 2018 tf.deleteOnExit(); 2019 outFile = new FileOutputStream(tf); 2020 in = getClass().getResourceAsStream("/" + jarPath); 2021 long bytesCopied = 0; 2022 int bytesInBuffer; 2023 while ((bytesInBuffer = in.read(buffer)) != -1) 2024 { 2025 outFile.write(buffer, 0, bytesInBuffer); 2026 bytesCopied += bytesInBuffer; 2027 } 2028 } 2029 finally 2030 { 2031 if (in != null) in.close(); 2032 if (outFile != null) outFile.close(); 2033 } 2034 url = tf.toURL(); 2035 2036 } 2037 // Use the class loader of the interface as parent, else 2038 // compile will fail at using it via an Ant task. 2039 URLClassLoader ucl = new URLClassLoader(new URL[] { url}, CompilerListener.class 2040 .getClassLoader()); 2041 listener = ucl.loadClass(fullName); 2042 } 2043 if (listener != null) 2044 instance = listener.newInstance(); 2045 else 2046 parseError(var, "Cannot find defined compiler listener " + className); 2047 if (!CompilerListener.class.isInstance(instance)) 2048 parseError(var, "'" + className + "' must be implemented " 2049 + CompilerListener.class.toString()); 2050 List constraints = OsConstraint.getOsList(var); 2051 return (new Object[] { instance, className, constraints}); 2052 } 2053 2054 /** 2055 * Add a CompilerListener. A registered CompilerListener will be called at every enhancmend 2056 * point of compiling. 2057 * 2058 * @param pe CompilerListener which should be added 2059 */ 2060 private void addCompilerListener(CompilerListener pe) 2061 { 2062 compilerListeners.add(pe); 2063 } 2064 2065 /** 2066 * Calls all defined compile listeners notify method with the given data 2067 * 2068 * @param callerName name of the calling method as string 2069 * @param state CompileListener.BEGIN or END 2070 * @param data current install data 2071 * @throws CompilerException 2072 */ 2073 private void notifyCompilerListener(String callerName, int state, XMLElement data) 2074 throws CompilerException 2075 { 2076 Iterator i = compilerListeners.iterator(); 2077 Packager packager = compiler.getPackager(); 2078 while (i != null && i.hasNext()) 2079 { 2080 CompilerListener listener = (CompilerListener) i.next(); 2081 listener.notify(callerName, state, data, packager); 2082 } 2083 2084 } 2085 2086 /** 2087 * Calls the reviseAdditionalDataMap method of all registered CompilerListener's. 2088 * 2089 * @param f file releated XML node 2090 * @return a map with the additional attributes 2091 */ 2092 private Map getAdditionals(XMLElement f) throws CompilerException 2093 { 2094 Iterator i = compilerListeners.iterator(); 2095 Map retval = null; 2096 try 2097 { 2098 while (i != null && i.hasNext()) 2099 { 2100 retval = ((CompilerListener) i.next()).reviseAdditionalDataMap(retval, f); 2101 } 2102 } 2103 catch (CompilerException ce) 2104 { 2105 parseError(f, ce.getMessage()); 2106 } 2107 return (retval); 2108 } 2109}