0001: /*
0002: * $Id: CompilerConfig.java 2061 2008-02-25 20:05:31Z jponge $
0003: * IzPack - Copyright 2001-2008 Julien Ponge, All Rights Reserved.
0004: *
0005: * http://izpack.org/
0006: * http://izpack.codehaus.org/
0007: *
0008: * Copyright 2001 Johannes Lehtinen
0009: * Copyright 2002 Paul Wilkinson
0010: * Copyright 2004 Gaganis Giorgos
0011: * Copyright 2007 Syed Khadeer / Hans Aikema
0012: *
0013: *
0014: * Licensed under the Apache License, Version 2.0 (the "License");
0015: * you may not use this file except in compliance with the License.
0016: * You may obtain a copy of the License at
0017: *
0018: * http://www.apache.org/licenses/LICENSE-2.0
0019: *
0020: * Unless required by applicable law or agreed to in writing, software
0021: * distributed under the License is distributed on an "AS IS" BASIS,
0022: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0023: * See the License for the specific language governing permissions and
0024: * limitations under the License.
0025: */
0026:
0027: package com.izforge.izpack.compiler;
0028:
0029: import com.izforge.izpack.CustomData;
0030: import com.izforge.izpack.ExecutableFile;
0031: import com.izforge.izpack.GUIPrefs;
0032: import com.izforge.izpack.Info;
0033: import com.izforge.izpack.PackFile;
0034: import com.izforge.izpack.Panel;
0035: import com.izforge.izpack.ParsableFile;
0036: import com.izforge.izpack.UpdateCheck;
0037: import com.izforge.izpack.compiler.Compiler.CmdlinePackagerListener;
0038: import com.izforge.izpack.event.CompilerListener;
0039: import com.izforge.izpack.rules.Condition;
0040: import com.izforge.izpack.rules.RulesEngine;
0041: import com.izforge.izpack.util.Debug;
0042: import com.izforge.izpack.util.Log;
0043: import com.izforge.izpack.util.OsConstraint;
0044: import com.izforge.izpack.util.VariableSubstitutor;
0045: import net.n3.nanoxml.IXMLParser;
0046: import net.n3.nanoxml.IXMLReader;
0047: import net.n3.nanoxml.NonValidator;
0048: import net.n3.nanoxml.StdXMLParser;
0049: import net.n3.nanoxml.StdXMLReader;
0050: import net.n3.nanoxml.XMLBuilderFactory;
0051: import net.n3.nanoxml.XMLElement;
0052: import net.n3.nanoxml.XMLException;
0053: import net.n3.nanoxml.XMLParserFactory;
0054: import net.n3.nanoxml.XMLWriter;
0055: import org.apache.tools.ant.DirectoryScanner;
0056:
0057: import java.io.BufferedInputStream;
0058: import java.io.BufferedOutputStream;
0059: import java.io.ByteArrayInputStream;
0060: import java.io.ByteArrayOutputStream;
0061: import java.io.File;
0062: import java.io.FileInputStream;
0063: import java.io.FileNotFoundException;
0064: import java.io.FileOutputStream;
0065: import java.io.IOException;
0066: import java.io.InputStream;
0067: import java.io.OutputStream;
0068: import java.net.MalformedURLException;
0069: import java.net.URI;
0070: import java.net.URL;
0071: import java.net.URLClassLoader;
0072: import java.util.ArrayList;
0073: import java.util.Date;
0074: import java.util.Enumeration;
0075: import java.util.HashMap;
0076: import java.util.Iterator;
0077: import java.util.List;
0078: import java.util.Map;
0079: import java.util.Properties;
0080: import java.util.Set;
0081: import java.util.StringTokenizer;
0082: import java.util.TreeMap;
0083: import java.util.Vector;
0084: import java.util.jar.JarInputStream;
0085: import java.util.zip.ZipEntry;
0086: import java.util.zip.ZipFile;
0087: import java.util.zip.ZipInputStream;
0088:
0089: /**
0090: * A parser for the installer xml configuration. This parses a document
0091: * conforming to the installation.dtd and populates a Compiler instance to
0092: * perform the install compilation.
0093: *
0094: * @author Scott Stark
0095: * @version $Revision: 2061 $
0096: */
0097: public class CompilerConfig extends Thread {
0098: /** The compiler version. */
0099: public final static String VERSION = "1.0";
0100:
0101: /** Standard installer. */
0102: public final static String STANDARD = "standard";
0103:
0104: /** Web installer. */
0105: public final static String WEB = "web";
0106:
0107: /** Constant for checking attributes. */
0108: private static boolean YES = true;
0109:
0110: /** Constant for checking attributes. */
0111: private static boolean NO = false;
0112:
0113: private final static String IZ_TEST_FILE = "ShellLink.dll";
0114:
0115: private final static String IZ_TEST_SUBDIR = "bin" + File.separator
0116: + "native" + File.separator + "izpack";
0117:
0118: /** The xml install file */
0119: private String filename;
0120: /** The xml install configuration text */
0121: private String installText;
0122: /** The base directory. */
0123: protected String basedir;
0124:
0125: /** The installer packager compiler */
0126: private Compiler compiler;
0127:
0128: /**
0129: * List of CompilerListeners which should be called at packaging
0130: */
0131: protected List<CompilerListener> compilerListeners = new ArrayList<CompilerListener>();
0132:
0133: /**
0134: * Set the IzPack home directory
0135: * @param izHome - the izpack home directory
0136: */
0137: public static void setIzpackHome(String izHome) {
0138: Compiler.setIzpackHome(izHome);
0139: }
0140:
0141: /**
0142: * The constructor.
0143: *
0144: * @param filename The XML filename.
0145: * @param basedir The base directory.
0146: * @param kind The installer kind.
0147: * @param output The installer filename.
0148: * @throws CompilerException
0149: */
0150: public CompilerConfig(String filename, String basedir, String kind,
0151: String output) throws CompilerException {
0152: this (filename, basedir, kind, output, null);
0153: }
0154:
0155: /**
0156: * The constructor.
0157: *
0158: * @param filename The XML filename.
0159: * @param basedir The base directory.
0160: * @param kind The installer kind.
0161: * @param output The installer filename.
0162: * @param listener The PackagerListener.
0163: * @throws CompilerException
0164: */
0165: public CompilerConfig(String filename, String basedir, String kind,
0166: String output, PackagerListener listener)
0167: throws CompilerException {
0168: this (filename, basedir, kind, output, "default", listener);
0169: }
0170:
0171: /**
0172: * @param filename The XML filename.
0173: * @param kind The installer kind.
0174: * @param output The installer filename.
0175: * @param compr_format The compression format to be used for packs.
0176: * @param listener The PackagerListener.
0177: * @throws CompilerException
0178: */
0179: public CompilerConfig(String filename, String base, String kind,
0180: String output, String compr_format,
0181: PackagerListener listener) throws CompilerException {
0182: this (filename, base, kind, output, compr_format, listener, null);
0183: }
0184:
0185: /**
0186: *
0187: * @param basedir The base directory.
0188: * @param kind The installer kind.
0189: * @param output The installer filename.
0190: * @param listener The PackagerListener.
0191: * @param installText The install xml configuration text
0192: * @throws CompilerException
0193: */
0194: public CompilerConfig(String basedir, String kind, String output,
0195: PackagerListener listener, String installText)
0196: throws CompilerException {
0197: this (null, basedir, kind, output, "default", listener,
0198: installText);
0199: }
0200:
0201: /**
0202: *
0203: * @param filename The XML filename.
0204: * @param basedir The base directory.
0205: * @param kind The installer kind.
0206: * @param output The installer filename.
0207: * @param compr_format The compression format to be used for packs.
0208: * @param listener The PackagerListener.
0209: * @param installText The install xml configuration text
0210: * @throws CompilerException
0211: */
0212: public CompilerConfig(String filename, String basedir, String kind,
0213: String output, String compr_format,
0214: PackagerListener listener, String installText)
0215: throws CompilerException {
0216: this (filename, basedir, kind, output, compr_format, -1,
0217: listener, installText);
0218: }
0219:
0220: /**
0221: * @param filename The XML filename.
0222: * @param basedir The base directory.
0223: * @param kind The installer kind.
0224: * @param output The installer filename.
0225: * @param compr_format The compression format to be used for packs.
0226: * @param compr_level Compression level to be used if supported.
0227: * @param listener The PackagerListener.
0228: * @param installText The install xml configuration text
0229: * @throws CompilerException
0230: */
0231: public CompilerConfig(String filename, String basedir, String kind,
0232: String output, String compr_format, int compr_level,
0233: PackagerListener listener, String installText)
0234: throws CompilerException {
0235: this .filename = filename;
0236: this .installText = installText;
0237: this .basedir = basedir;
0238: this .compiler = new Compiler(basedir, kind, output,
0239: compr_format, compr_level);
0240: compiler.setPackagerListener(listener);
0241: }
0242:
0243: /**
0244: * Add a name value pair to the project property set. It is <i>not</i>
0245: * replaced it is already in the set of properties.
0246: *
0247: * @param name the name of the property
0248: * @param value the value to set
0249: * @return true if the property was not already set
0250: */
0251: public boolean addProperty(String name, String value) {
0252: return compiler.addProperty(name, value);
0253: }
0254:
0255: /**
0256: * Access the install compiler
0257: * @return the install compiler
0258: */
0259: public Compiler getCompiler() {
0260: return compiler;
0261: }
0262:
0263: /**
0264: * Retrieves the packager listener
0265: */
0266: public PackagerListener getPackagerListener() {
0267: return compiler.getPackagerListener();
0268: }
0269:
0270: /** Compile the installation */
0271: public void compile() {
0272: start();
0273: }
0274:
0275: /** The run() method. */
0276: public void run() {
0277: try {
0278: executeCompiler();
0279: } catch (CompilerException ce) {
0280: System.out.println(ce.getMessage() + "\n");
0281: } catch (Exception e) {
0282: if (Debug.stackTracing()) {
0283: e.printStackTrace();
0284: } else {
0285: System.out.println("ERROR: " + e.getMessage());
0286: }
0287: }
0288: }
0289:
0290: /**
0291: * Compiles the installation.
0292: *
0293: * @exception Exception Description of the Exception
0294: */
0295: public void executeCompiler() throws Exception {
0296: // normalize and test: TODO: may allow failure if we require write
0297: // access
0298: File base = new File(basedir).getAbsoluteFile();
0299: if (!base.canRead() || !base.isDirectory())
0300: throw new CompilerException("Invalid base directory: "
0301: + base);
0302:
0303: // add izpack built in property
0304: compiler.setProperty("basedir", base.toString());
0305:
0306: // We get the XML data tree
0307: XMLElement data = getXMLTree();
0308: // loads the specified packager
0309: loadPackagingInformation(data);
0310:
0311: // Listeners to various events
0312: addCustomListeners(data);
0313:
0314: // Read the properties and perform replacement on the rest of the tree
0315: substituteProperties(data);
0316:
0317: // We add all the information
0318: addVariables(data);
0319: addDynamicVariables(data);
0320: addConditions(data);
0321: addInfo(data);
0322: addGUIPrefs(data);
0323: addLangpacks(data);
0324: addResources(data);
0325: addNativeLibraries(data);
0326: addJars(data);
0327: addPanels(data);
0328: addPacks(data);
0329:
0330: // We ask the packager to create the installer
0331: compiler.createInstaller();
0332: }
0333:
0334: private void loadPackagingInformation(XMLElement data)
0335: throws CompilerException {
0336: notifyCompilerListener("loadPackager", CompilerListener.BEGIN,
0337: data);
0338: // Initialisation
0339: XMLElement root = data.getFirstChildNamed("packaging");
0340: String packagerclassname = "com.izforge.izpack.compiler.Packager";
0341: String unpackerclassname = "com.izforge.izpack.installer.Unpacker";
0342: XMLElement packager = null;
0343: if (root != null) {
0344: packager = root.getFirstChildNamed("packager");
0345:
0346: if (packager != null) {
0347: packagerclassname = requireAttribute(packager, "class");
0348: }
0349:
0350: XMLElement unpacker = root.getFirstChildNamed("unpacker");
0351:
0352: if (unpacker != null) {
0353: unpackerclassname = requireAttribute(unpacker, "class");
0354: }
0355: }
0356: compiler.initPackager(packagerclassname);
0357: if (packager != null) {
0358: XMLElement options = packager.getFirstChildNamed("options");
0359: if (options != null) {
0360: compiler.getPackager().addConfigurationInformation(
0361: options);
0362: }
0363: }
0364: compiler.addProperty("UNPACKER_CLASS", unpackerclassname);
0365: notifyCompilerListener("loadPackager", CompilerListener.END,
0366: data);
0367: }
0368:
0369: public boolean wasSuccessful() {
0370: return compiler.wasSuccessful();
0371: }
0372:
0373: /**
0374: * Returns the GUIPrefs.
0375: *
0376: * @param data The XML data.
0377: * @exception CompilerException Description of the Exception
0378: */
0379: protected void addGUIPrefs(XMLElement data)
0380: throws CompilerException {
0381: notifyCompilerListener("addGUIPrefs", CompilerListener.BEGIN,
0382: data);
0383: // We get the XMLElement & the attributes
0384: XMLElement gp = data.getFirstChildNamed("guiprefs");
0385: GUIPrefs prefs = new GUIPrefs();
0386: if (gp != null) {
0387: prefs.resizable = requireYesNoAttribute(gp, "resizable");
0388: prefs.width = requireIntAttribute(gp, "width");
0389: prefs.height = requireIntAttribute(gp, "height");
0390:
0391: // Look and feel mappings
0392: Iterator<XMLElement> it = gp.getChildrenNamed("laf")
0393: .iterator();
0394: while (it.hasNext()) {
0395: XMLElement laf = it.next();
0396: String lafName = requireAttribute(laf, "name");
0397: requireChildNamed(laf, "os");
0398:
0399: Iterator<XMLElement> oit = laf.getChildrenNamed("os")
0400: .iterator();
0401: while (oit.hasNext()) {
0402: XMLElement os = oit.next();
0403: String osName = requireAttribute(os, "family");
0404: prefs.lookAndFeelMapping.put(osName, lafName);
0405: }
0406:
0407: Iterator<XMLElement> pit = laf
0408: .getChildrenNamed("param").iterator();
0409: Map<String, String> params = new TreeMap<String, String>();
0410: while (pit.hasNext()) {
0411: XMLElement param = pit.next();
0412: String name = requireAttribute(param, "name");
0413: String value = requireAttribute(param, "value");
0414: params.put(name, value);
0415: }
0416: prefs.lookAndFeelParams.put(lafName, params);
0417: }
0418: // Load modifier
0419: it = gp.getChildrenNamed("modifier").iterator();
0420: while (it.hasNext()) {
0421: XMLElement curentModifier = it.next();
0422: String key = requireAttribute(curentModifier, "key");
0423: String value = requireAttribute(curentModifier, "value");
0424: prefs.modifier.put(key, value);
0425:
0426: }
0427: // make sure jar contents of each are available in installer
0428: // map is easier to read/modify than if tree
0429: HashMap<String, String> lafMap = new HashMap<String, String>();
0430: lafMap.put("liquid", "liquidlnf.jar");
0431: lafMap.put("kunststoff", "kunststoff.jar");
0432: lafMap.put("metouia", "metouia.jar");
0433: lafMap.put("looks", "looks.jar");
0434: lafMap.put("substance", "substance.jar");
0435: lafMap.put("nimbus", "nimbus.jar");
0436:
0437: // is this really what we want? a double loop? needed, since above,
0438: // it's
0439: // the /last/ lnf for an os which is used, so can't add during
0440: // initial
0441: // loop
0442: Iterator<String> kit = prefs.lookAndFeelMapping.keySet()
0443: .iterator();
0444: while (kit.hasNext()) {
0445: String lafName = prefs.lookAndFeelMapping.get(kit
0446: .next());
0447: String lafJarName = lafMap.get(lafName);
0448: if (lafJarName == null)
0449: parseError(gp, "Unrecognized Look and Feel: "
0450: + lafName);
0451:
0452: URL lafJarURL = findIzPackResource("lib/" + lafJarName,
0453: "Look and Feel Jar file", gp);
0454: compiler.addJarContent(lafJarURL);
0455: }
0456: }
0457: compiler.setGUIPrefs(prefs);
0458: notifyCompilerListener("addGUIPrefs", CompilerListener.END,
0459: data);
0460: }
0461:
0462: /**
0463: * Add project specific external jar files to the installer.
0464: *
0465: * @param data The XML data.
0466: */
0467: protected void addJars(XMLElement data) throws Exception {
0468: notifyCompilerListener("addJars", CompilerListener.BEGIN, data);
0469: Iterator<XMLElement> iter = data.getChildrenNamed("jar")
0470: .iterator();
0471: while (iter.hasNext()) {
0472: XMLElement el = iter.next();
0473: String src = requireAttribute(el, "src");
0474: URL url = findProjectResource(src, "Jar file", el);
0475: compiler.addJarContent(url);
0476: // Additionals for mark a jar file also used in the uninstaller.
0477: // The contained files will be copied from the installer into the
0478: // uninstaller if needed.
0479: // Therefore the contained files of the jar should be in the
0480: // installer also
0481: // they are used only from the uninstaller. This is the reason why
0482: // the stage
0483: // wiil be only observed for the uninstaller.
0484: String stage = el.getAttribute("stage");
0485: if (stage != null
0486: && ("both".equalsIgnoreCase(stage) || "uninstall"
0487: .equalsIgnoreCase(stage))) {
0488: CustomData ca = new CustomData(null,
0489: getContainedFilePaths(url), null,
0490: CustomData.UNINSTALLER_JAR);
0491: compiler.addCustomJar(ca, url);
0492: }
0493: }
0494: notifyCompilerListener("addJars", CompilerListener.END, data);
0495: }
0496:
0497: /**
0498: * Add native libraries to the installer.
0499: *
0500: * @param data The XML data.
0501: */
0502: protected void addNativeLibraries(XMLElement data) throws Exception {
0503: boolean needAddOns = false;
0504: notifyCompilerListener("addNativeLibraries",
0505: CompilerListener.BEGIN, data);
0506: Iterator<XMLElement> iter = data.getChildrenNamed("native")
0507: .iterator();
0508: while (iter.hasNext()) {
0509: XMLElement el = iter.next();
0510: String type = requireAttribute(el, "type");
0511: String name = requireAttribute(el, "name");
0512: String path = "bin/native/" + type + "/" + name;
0513: URL url = findIzPackResource(path, "Native Library", el);
0514: compiler.addNativeLibrary(name, url);
0515: // Additionals for mark a native lib also used in the uninstaller
0516: // The lib will be copied from the installer into the uninstaller if
0517: // needed.
0518: // Therefore the lib should be in the installer also it is used only
0519: // from
0520: // the uninstaller. This is the reason why the stage wiil be only
0521: // observed
0522: // for the uninstaller.
0523: String stage = el.getAttribute("stage");
0524: List<OsConstraint> constraints = OsConstraint.getOsList(el);
0525: if (stage != null
0526: && ("both".equalsIgnoreCase(stage) || "uninstall"
0527: .equalsIgnoreCase(stage))) {
0528: ArrayList<String> al = new ArrayList<String>();
0529: al.add(name);
0530: CustomData cad = new CustomData(null, al, constraints,
0531: CustomData.UNINSTALLER_LIB);
0532: compiler.addNativeUninstallerLibrary(cad);
0533: needAddOns = true;
0534: }
0535:
0536: }
0537: if (needAddOns) {
0538: // Add the uninstaller extensions as a resource if specified
0539: XMLElement root = requireChildNamed(data, "info");
0540: XMLElement uninstallInfo = root
0541: .getFirstChildNamed("uninstaller");
0542: if (validateYesNoAttribute(uninstallInfo, "write", YES)) {
0543: URL url = findIzPackResource("lib/uninstaller-ext.jar",
0544: "Uninstaller extensions", root);
0545: compiler.addResource("IzPack.uninstaller-ext", url);
0546: }
0547:
0548: }
0549: notifyCompilerListener("addNativeLibraries",
0550: CompilerListener.END, data);
0551: }
0552:
0553: /**
0554: * Add packs and their contents to the installer.
0555: *
0556: * @param data The XML data.
0557: */
0558: protected void addPacks(XMLElement data) throws CompilerException {
0559: notifyCompilerListener("addPacks", CompilerListener.BEGIN, data);
0560:
0561: // the actual adding is delegated to addPacksSingle to enable recursive
0562: // parsing of refpack package definitions
0563: addPacksSingle(data);
0564:
0565: compiler.checkDependencies();
0566: compiler.checkExcludes();
0567:
0568: notifyCompilerListener("addPacks", CompilerListener.END, data);
0569: }
0570:
0571: /**
0572: * Add packs and their contents to the installer without checking
0573: * the dependencies and includes.
0574: *
0575: * Helper method to recursively add more packs from refpack XML packs definitions
0576: *
0577: * @param data The XML data
0578: * @throws CompilerException
0579: */
0580: private void addPacksSingle(XMLElement data)
0581: throws CompilerException {
0582: notifyCompilerListener("addPacksSingle",
0583: CompilerListener.BEGIN, data);
0584: // Initialisation
0585: XMLElement root = requireChildNamed(data, "packs");
0586:
0587: // at least one pack is required
0588: Vector<XMLElement> packElements = root.getChildrenNamed("pack");
0589: Vector<XMLElement> refPackElements = root
0590: .getChildrenNamed("refpack");
0591: if (packElements.isEmpty() && refPackElements.isEmpty())
0592: parseError(root, "<packs> requires a <pack> or <refpack>");
0593:
0594: File baseDir = new File(basedir);
0595:
0596: Iterator<XMLElement> packIter = packElements.iterator();
0597: while (packIter.hasNext()) {
0598: XMLElement el = packIter.next();
0599:
0600: // Trivial initialisations
0601: String name = requireAttribute(el, "name");
0602: String id = el.getAttribute("id");
0603: String packImgId = el.getAttribute("packImgId");
0604:
0605: boolean loose = "true".equalsIgnoreCase(el.getAttribute(
0606: "loose", "false"));
0607: String description = requireChildNamed(el, "description")
0608: .getContent();
0609: boolean required = requireYesNoAttribute(el, "required");
0610: String group = el.getAttribute("group");
0611: String installGroups = el.getAttribute("installGroups");
0612: String excludeGroup = el.getAttribute("excludeGroup");
0613: boolean uninstall = "yes".equalsIgnoreCase(el.getAttribute(
0614: "uninstall", "yes"));
0615: String parent = el.getAttribute("parent");
0616:
0617: String conditionid = el.getAttribute("condition");
0618:
0619: if (required && excludeGroup != null) {
0620: parseError(
0621: el,
0622: "Pack, which has excludeGroup can not be required.",
0623: new Exception(
0624: "Pack, which has excludeGroup can not be required."));
0625: }
0626:
0627: PackInfo pack = new PackInfo(name, id, description,
0628: required, loose, excludeGroup, uninstall);
0629: pack.setOsConstraints(OsConstraint.getOsList(el)); // TODO:
0630: pack.setParent(parent);
0631: pack.setCondition(conditionid);
0632:
0633: // unverified
0634: // if the pack belongs to an excludeGroup it's not preselected by default
0635: if (excludeGroup == null)
0636: pack.setPreselected(validateYesNoAttribute(el,
0637: "preselected", YES));
0638: else
0639: pack.setPreselected(validateYesNoAttribute(el,
0640: "preselected", NO));
0641:
0642: // Set the pack group if specified
0643: if (group != null)
0644: pack.setGroup(group);
0645: // Set the pack install groups if specified
0646: if (installGroups != null) {
0647: StringTokenizer st = new StringTokenizer(installGroups,
0648: ",");
0649: while (st.hasMoreTokens()) {
0650: String igroup = st.nextToken();
0651: pack.addInstallGroup(igroup);
0652: }
0653: }
0654:
0655: // Set the packImgId if specified
0656: if (packImgId != null) {
0657: pack.setPackImgId(packImgId);
0658: }
0659:
0660: // We get the parsables list
0661: Iterator<XMLElement> iter = el.getChildrenNamed("parsable")
0662: .iterator();
0663: while (iter.hasNext()) {
0664: XMLElement p = iter.next();
0665: String target = requireAttribute(p, "targetfile");
0666: String type = p.getAttribute("type", "plain");
0667: String encoding = p.getAttribute("encoding", null);
0668: List<OsConstraint> osList = OsConstraint.getOsList(p); // TODO: unverified
0669: String condition = p.getAttribute("condition");
0670: ParsableFile parsable = new ParsableFile(target, type,
0671: encoding, osList);
0672: parsable.setCondition(condition);
0673: pack.addParsable(parsable);
0674: }
0675:
0676: // We get the executables list
0677: iter = el.getChildrenNamed("executable").iterator();
0678: while (iter.hasNext()) {
0679: XMLElement e = iter.next();
0680: ExecutableFile executable = new ExecutableFile();
0681: String val; // temp value
0682: String condition = e.getAttribute("condition");
0683: executable.setCondition(condition);
0684: executable.path = requireAttribute(e, "targetfile");
0685:
0686: // when to execute this executable
0687: val = e.getAttribute("stage", "never");
0688: if ("postinstall".equalsIgnoreCase(val))
0689: executable.executionStage = ExecutableFile.POSTINSTALL;
0690: else if ("uninstall".equalsIgnoreCase(val))
0691: executable.executionStage = ExecutableFile.UNINSTALL;
0692:
0693: // type of this executable
0694: val = e.getAttribute("type", "bin");
0695: if ("jar".equalsIgnoreCase(val)) {
0696: executable.type = ExecutableFile.JAR;
0697: executable.mainClass = e.getAttribute("class"); // executable
0698: // class
0699: }
0700:
0701: // what to do if execution fails
0702: val = e.getAttribute("failure", "ask");
0703: if ("abort".equalsIgnoreCase(val))
0704: executable.onFailure = ExecutableFile.ABORT;
0705: else if ("warn".equalsIgnoreCase(val))
0706: executable.onFailure = ExecutableFile.WARN;
0707: else if ("ignore".equalsIgnoreCase(val))
0708: executable.onFailure = ExecutableFile.IGNORE;
0709:
0710: // whether to keep the executable after executing it
0711: val = e.getAttribute("keep");
0712: executable.keepFile = "true".equalsIgnoreCase(val);
0713:
0714: // get arguments for this executable
0715: XMLElement args = e.getFirstChildNamed("args");
0716: if (null != args) {
0717: Iterator<XMLElement> argIterator = args
0718: .getChildrenNamed("arg").iterator();
0719: while (argIterator.hasNext()) {
0720: XMLElement arg = argIterator.next();
0721: executable.argList.add(requireAttribute(arg,
0722: "value"));
0723: }
0724: }
0725:
0726: executable.osList = OsConstraint.getOsList(e); // TODO:
0727: // unverified
0728:
0729: pack.addExecutable(executable);
0730: }
0731:
0732: // We get the files list
0733: iter = el.getChildrenNamed("file").iterator();
0734: while (iter.hasNext()) {
0735: XMLElement f = iter.next();
0736: String src = requireAttribute(f, "src");
0737: String targetdir = requireAttribute(f, "targetdir");
0738: List<OsConstraint> osList = OsConstraint.getOsList(f); // TODO: unverified
0739: int override = getOverrideValue(f);
0740: Map additionals = getAdditionals(f);
0741: boolean unpack = src.endsWith(".zip")
0742: && "true".equalsIgnoreCase(f
0743: .getAttribute("unpack"));
0744: String condition = f.getAttribute("condition");
0745:
0746: File file = new File(src);
0747: if (!file.isAbsolute())
0748: file = new File(basedir, src);
0749:
0750: try {
0751: if (unpack)
0752: addArchiveContent(baseDir, file, targetdir,
0753: osList, override, pack, additionals,
0754: condition);
0755: else
0756: addRecursively(baseDir, file, targetdir,
0757: osList, override, pack, additionals,
0758: condition);
0759: } catch (Exception x) {
0760: parseError(f, x.getMessage(), x);
0761: }
0762: }
0763:
0764: // We get the singlefiles list
0765: iter = el.getChildrenNamed("singlefile").iterator();
0766: while (iter.hasNext()) {
0767: XMLElement f = iter.next();
0768: String src = requireAttribute(f, "src");
0769: String target = requireAttribute(f, "target");
0770: List<OsConstraint> osList = OsConstraint.getOsList(f); // TODO: unverified
0771: int override = getOverrideValue(f);
0772: Map additionals = getAdditionals(f);
0773: String condition = f.getAttribute("condition");
0774: File file = new File(src);
0775: if (!file.isAbsolute())
0776: file = new File(basedir, src);
0777:
0778: try {
0779: pack.addFile(baseDir, file, target, osList,
0780: override, additionals, condition);
0781: } catch (FileNotFoundException x) {
0782: parseError(f, x.getMessage(), x);
0783: }
0784: }
0785:
0786: // We get the fileset list
0787: iter = el.getChildrenNamed("fileset").iterator();
0788: while (iter.hasNext()) {
0789: XMLElement f = iter.next();
0790: String dir_attr = requireAttribute(f, "dir");
0791:
0792: File dir = new File(dir_attr);
0793: if (!dir.isAbsolute())
0794: dir = new File(basedir, dir_attr);
0795: if (!dir.isDirectory()) // also tests '.exists()'
0796: parseError(f, "Invalid directory 'dir': "
0797: + dir_attr);
0798:
0799: boolean casesensitive = validateYesNoAttribute(f,
0800: "casesensitive", YES);
0801: boolean defexcludes = validateYesNoAttribute(f,
0802: "defaultexcludes", YES);
0803: String targetdir = requireAttribute(f, "targetdir");
0804: List<OsConstraint> osList = OsConstraint.getOsList(f); // TODO: unverified
0805: int override = getOverrideValue(f);
0806: Map additionals = getAdditionals(f);
0807: String condition = f.getAttribute("condition");
0808:
0809: // get includes and excludes
0810: Vector<XMLElement> xcludesList = null;
0811: String[] includes = null;
0812: xcludesList = f.getChildrenNamed("include");
0813: if (!xcludesList.isEmpty()) {
0814: includes = new String[xcludesList.size()];
0815: for (int j = 0; j < xcludesList.size(); j++) {
0816: XMLElement xclude = xcludesList.get(j);
0817: includes[j] = requireAttribute(xclude, "name");
0818: }
0819: }
0820: String[] excludes = null;
0821: xcludesList = f.getChildrenNamed("exclude");
0822: if (!xcludesList.isEmpty()) {
0823: excludes = new String[xcludesList.size()];
0824: for (int j = 0; j < xcludesList.size(); j++) {
0825: XMLElement xclude = xcludesList.get(j);
0826: excludes[j] = requireAttribute(xclude, "name");
0827: }
0828: }
0829:
0830: // parse additional fileset attributes "includes" and "excludes"
0831: String[] toDo = new String[] { "includes", "excludes" };
0832: // use the existing containers filled from include and exclude
0833: // and add the includes and excludes to it
0834: String[][] containers = new String[][] { includes,
0835: excludes };
0836: for (int j = 0; j < toDo.length; ++j) {
0837: String inex = f.getAttribute(toDo[j]);
0838: if (inex != null && inex.length() > 0) { // This is the same "splitting" as ant PatternSet do ...
0839: StringTokenizer tok = new StringTokenizer(inex,
0840: ", ", false);
0841: int newSize = tok.countTokens();
0842: int k = 0;
0843: String[] nCont = null;
0844: if (containers[j] != null
0845: && containers[j].length > 0) { // old container exist; create a new which can hold
0846: // all values
0847: // and copy the old stuff to the front
0848: newSize += containers[j].length;
0849: nCont = new String[newSize];
0850: for (; k < containers[j].length; ++k)
0851: nCont[k] = containers[j][k];
0852: }
0853: if (nCont == null) // No container for old values
0854: // created,
0855: // create a new one.
0856: nCont = new String[newSize];
0857: for (; k < newSize; ++k)
0858: // Fill the new one or expand the existent container
0859: nCont[k] = tok.nextToken();
0860: containers[j] = nCont;
0861: }
0862: }
0863: includes = containers[0]; // push the new includes to the
0864: // local var
0865: excludes = containers[1]; // push the new excludes to the
0866: // local var
0867:
0868: // scan and add fileset
0869: DirectoryScanner ds = new DirectoryScanner();
0870: ds.setIncludes(includes);
0871: ds.setExcludes(excludes);
0872: if (defexcludes)
0873: ds.addDefaultExcludes();
0874: ds.setBasedir(dir);
0875: ds.setCaseSensitive(casesensitive);
0876: ds.scan();
0877:
0878: String[] files = ds.getIncludedFiles();
0879: String[] dirs = ds.getIncludedDirectories();
0880:
0881: // Directory scanner has done recursion, add files and
0882: // directories
0883: for (String file : files) {
0884: try {
0885: String target = new File(targetdir, file)
0886: .getPath();
0887: pack.addFile(baseDir, new File(dir, file),
0888: target, osList, override, additionals,
0889: condition);
0890: } catch (FileNotFoundException x) {
0891: parseError(f, x.getMessage(), x);
0892: }
0893: }
0894: for (String dir1 : dirs) {
0895: try {
0896: String target = new File(targetdir, dir1)
0897: .getPath();
0898: pack.addFile(baseDir, new File(dir, dir1),
0899: target, osList, override, additionals,
0900: condition);
0901: } catch (FileNotFoundException x) {
0902: parseError(f, x.getMessage(), x);
0903: }
0904: }
0905: }
0906:
0907: // get the updatechecks list
0908: iter = el.getChildrenNamed("updatecheck").iterator();
0909: while (iter.hasNext()) {
0910: XMLElement f = iter.next();
0911:
0912: String casesensitive = f.getAttribute("casesensitive");
0913:
0914: // get includes and excludes
0915: ArrayList<String> includesList = new ArrayList<String>();
0916: ArrayList<String> excludesList = new ArrayList<String>();
0917:
0918: // get includes and excludes
0919: Iterator<XMLElement> include_it = f.getChildrenNamed(
0920: "include").iterator();
0921: while (include_it.hasNext()) {
0922: XMLElement inc_el = include_it.next();
0923: includesList.add(requireAttribute(inc_el, "name"));
0924: }
0925:
0926: Iterator<XMLElement> exclude_it = f.getChildrenNamed(
0927: "exclude").iterator();
0928: while (exclude_it.hasNext()) {
0929: XMLElement excl_el = exclude_it.next();
0930: excludesList.add(requireAttribute(excl_el, "name"));
0931: }
0932:
0933: pack.addUpdateCheck(new UpdateCheck(includesList,
0934: excludesList, casesensitive));
0935: }
0936: // We get the dependencies
0937: iter = el.getChildrenNamed("depends").iterator();
0938: while (iter.hasNext()) {
0939: XMLElement dep = iter.next();
0940: String depName = requireAttribute(dep, "packname");
0941: pack.addDependency(depName);
0942:
0943: }
0944: // We add the pack
0945: compiler.addPack(pack);
0946: }
0947:
0948: Iterator<XMLElement> refPackIter = refPackElements.iterator();
0949: while (refPackIter.hasNext()) {
0950: XMLElement el = refPackIter.next();
0951:
0952: // get the name of reference xml file
0953: String refFileName = requireAttribute(el, "file");
0954: String selfcontained = el.getAttribute("selfcontained");
0955: boolean isselfcontained = Boolean.valueOf(selfcontained);
0956:
0957: File refXMLFile = new File(refFileName);
0958: if (!refXMLFile.isAbsolute())
0959: refXMLFile = new File(basedir, refFileName);
0960: if (!refXMLFile.canRead()) {
0961: throw new CompilerException("Invalid file: "
0962: + refXMLFile);
0963: }
0964:
0965: InputStream specin = null;
0966:
0967: if (isselfcontained) {
0968: if (!refXMLFile.getAbsolutePath().endsWith(".zip")) {
0969: throw new CompilerException(
0970: "Invalid file: "
0971: + refXMLFile
0972: + ". Selfcontained files can only be of type zip.");
0973: }
0974: ZipFile zip;
0975: try {
0976: zip = new ZipFile(refXMLFile, ZipFile.OPEN_READ);
0977: ZipEntry specentry = zip
0978: .getEntry("META-INF/izpack.xml");
0979: specin = zip.getInputStream(specentry);
0980: } catch (IOException e) {
0981: throw new CompilerException(
0982: "Error reading META-INF/izpack.xml in "
0983: + refXMLFile);
0984: }
0985: } else {
0986: try {
0987: specin = new FileInputStream(refXMLFile
0988: .getAbsolutePath());
0989: } catch (FileNotFoundException e) {
0990: throw new CompilerException(
0991: "FileNotFoundException exception while reading refXMLFile");
0992: }
0993: }
0994:
0995: // Initialises the parser
0996: IXMLReader refXMLReader = null;
0997:
0998: // Load the reference XML file
0999: try {
1000: refXMLReader = new StdXMLReader(specin);
1001: } catch (CompilerException c) {
1002: throw new CompilerException(
1003: "Compiler exception while reading refXMLFile");
1004: } catch (IOException io) {
1005: throw new CompilerException(
1006: "IOException exception while reading refXMLFile");
1007: }
1008:
1009: StdXMLParser refXMLParser = new StdXMLParser();
1010: refXMLParser.setBuilder(XMLBuilderFactory
1011: .createXMLBuilder());
1012: refXMLParser.setReader(refXMLReader);
1013: refXMLParser.setValidator(new NonValidator());
1014:
1015: // We get it
1016: XMLElement refXMLData = null;
1017: try {
1018: refXMLData = (XMLElement) refXMLParser.parse();
1019:
1020: } catch (XMLException x) {
1021: throw new CompilerException(
1022: "Error parsing installation file", x);
1023: }
1024:
1025: // Now checked the loaded XML file for basic syntax
1026: // We check it
1027: if (!"installation".equalsIgnoreCase(refXMLData.getName())) {
1028: parseError(refXMLData,
1029: "this is not an IzPack XML installation file");
1030: }
1031: if (!VERSION.equalsIgnoreCase(requireAttribute(refXMLData,
1032: "version"))) {
1033: parseError(refXMLData,
1034: "the file version is different from the compiler version");
1035: }
1036:
1037: // Read the properties and perform replacement on the rest of the tree
1038: substituteProperties(refXMLData);
1039:
1040: // call addResources to add the referenced XML resources to this installation
1041: addResources(refXMLData);
1042:
1043: try {
1044: specin.close();
1045: } catch (IOException e) {
1046: // TODO Auto-generated catch block
1047: e.printStackTrace();
1048: }
1049: // Recursively call myself to add all packs and refpacks from the reference XML
1050: addPacksSingle(refXMLData);
1051: }
1052: notifyCompilerListener("addPacksSingle", CompilerListener.END,
1053: data);
1054: }
1055:
1056: /**
1057: * Checks whether the dependencies stated in the configuration file are correct. Specifically it
1058: * checks that no pack point to a non existent pack and also that there are no circular
1059: * dependencies in the packs.
1060: */
1061: public void checkDependencies(List<PackInfo> packs)
1062: throws CompilerException {
1063: // Because we use package names in the configuration file we assosiate
1064: // the names with the objects
1065: Map<String, PackInfo> names = new HashMap<String, PackInfo>();
1066: for (PackInfo pack : packs) {
1067: names.put(pack.getPack().name, pack);
1068: }
1069: int result = dfs(packs, names);
1070: // @todo More informative messages to include the source of the error
1071: if (result == -2)
1072: parseError("Circular dependency detected");
1073: else if (result == -1)
1074: parseError("A dependency doesn't exist");
1075: }
1076:
1077: /**
1078: * We use the dfs graph search algorithm to check whether the graph is acyclic as described in:
1079: * Thomas H. Cormen, Charles Leiserson, Ronald Rivest and Clifford Stein. Introduction to
1080: * algorithms 2nd Edition 540-549,MIT Press, 2001
1081: *
1082: * @param packs The graph
1083: * @param names The name map
1084: */
1085: private int dfs(List<PackInfo> packs, Map<String, PackInfo> names) {
1086: Map<Edge, Integer> edges = new HashMap<Edge, Integer>();
1087: for (PackInfo pack : packs) {
1088: if (pack.colour == PackInfo.WHITE) {
1089: if (dfsVisit(pack, names, edges) != 0) {
1090: return -1;
1091: }
1092: }
1093:
1094: }
1095: return checkBackEdges(edges);
1096: }
1097:
1098: /**
1099: * This function checks for the existence of back edges.
1100: */
1101: private int checkBackEdges(Map<Edge, Integer> edges) {
1102: Set<Edge> keys = edges.keySet();
1103: for (final Edge key : keys) {
1104: int color = edges.get(key);
1105: if (color == PackInfo.GREY) {
1106: return -2;
1107: }
1108: }
1109: return 0;
1110:
1111: }
1112:
1113: /**
1114: * This class is used for the classification of the edges
1115: */
1116: private class Edge {
1117:
1118: PackInfo u;
1119:
1120: PackInfo v;
1121:
1122: Edge(PackInfo u, PackInfo v) {
1123: this .u = u;
1124: this .v = v;
1125: }
1126: }
1127:
1128: private int dfsVisit(PackInfo u, Map<String, PackInfo> names,
1129: Map<Edge, Integer> edges) {
1130: u.colour = PackInfo.GREY;
1131: List<String> deps = u.getDependencies();
1132: if (deps != null) {
1133: for (String name : deps) {
1134: PackInfo v = names.get(name);
1135: if (v == null) {
1136: System.out.println("Failed to find dependency: "
1137: + name);
1138: return -1;
1139: }
1140: Edge edge = new Edge(u, v);
1141: if (edges.get(edge) == null) {
1142: edges.put(edge, v.colour);
1143: }
1144:
1145: if (v.colour == PackInfo.WHITE) {
1146:
1147: final int result = dfsVisit(v, names, edges);
1148: if (result != 0) {
1149: return result;
1150: }
1151: }
1152: }
1153: }
1154: u.colour = PackInfo.BLACK;
1155: return 0;
1156: }
1157:
1158: /**
1159: * Add files in an archive to a pack
1160: * @param archive the archive file to unpack
1161: * @param targetdir the target directory where the content of the archive will be installed
1162: * @param osList The target OS constraints.
1163: * @param override Overriding behaviour.
1164: * @param pack Pack to be packed into
1165: * @param additionals Map which contains additional data
1166: * @param condition
1167: */
1168: protected void addArchiveContent(File baseDir, File archive,
1169: String targetdir, List<OsConstraint> osList, int override,
1170: PackInfo pack, Map additionals, String condition)
1171: throws IOException {
1172:
1173: FileInputStream fin = new FileInputStream(archive);
1174: ZipInputStream zin = new ZipInputStream(fin);
1175: while (true) {
1176: ZipEntry zentry = zin.getNextEntry();
1177: if (zentry == null)
1178: break;
1179: if (zentry.isDirectory())
1180: continue;
1181:
1182: try {
1183: File temp = File.createTempFile("izpack", null);
1184: temp.deleteOnExit();
1185:
1186: FileOutputStream out = new FileOutputStream(temp);
1187: PackagerHelper.copyStream(zin, out);
1188: out.close();
1189:
1190: pack.addFile(baseDir, temp, targetdir + "/"
1191: + zentry.getName(), osList, override,
1192: additionals, condition);
1193: } catch (IOException e) {
1194: throw new IOException(
1195: "Couldn't create temporary file for "
1196: + zentry.getName() + " in archive "
1197: + archive + " (" + e.getMessage() + ")");
1198: }
1199:
1200: }
1201: fin.close();
1202: }
1203:
1204: /**
1205: * Recursive method to add files in a pack.
1206: *
1207: * @param file The file to add.
1208: * @param targetdir The relative path to the parent.
1209: * @param osList The target OS constraints.
1210: * @param override Overriding behaviour.
1211: * @param pack Pack to be packed into
1212: * @param additionals Map which contains additional data
1213: * @param condition
1214: * @exception FileNotFoundException if the file does not exist
1215: */
1216: protected void addRecursively(File baseDir, File file,
1217: String targetdir, List<OsConstraint> osList, int override,
1218: PackInfo pack, Map additionals, String condition)
1219: throws IOException {
1220: String targetfile = targetdir + "/" + file.getName();
1221: if (!file.isDirectory())
1222: pack.addFile(baseDir, file, targetfile, osList, override,
1223: additionals, condition);
1224: else {
1225: File[] files = file.listFiles();
1226: if (files.length == 0) // The directory is empty so must be added
1227: pack.addFile(baseDir, file, targetfile, osList,
1228: override, additionals, condition);
1229: else {
1230: // new targetdir = targetfile;
1231: for (File file1 : files) {
1232: addRecursively(baseDir, file1, targetfile, osList,
1233: override, pack, additionals, condition);
1234: }
1235: }
1236: }
1237: }
1238:
1239: /**
1240: * Parse panels and their paramters, locate the panels resources and add to the Packager.
1241: *
1242: * @param data The XML data.
1243: * @exception CompilerException Description of the Exception
1244: */
1245: protected void addPanels(XMLElement data) throws CompilerException {
1246: notifyCompilerListener("addPanels", CompilerListener.BEGIN,
1247: data);
1248: XMLElement root = requireChildNamed(data, "panels");
1249:
1250: // at least one panel is required
1251: Vector<XMLElement> panels = root.getChildrenNamed("panel");
1252: if (panels.isEmpty())
1253: parseError(root, "<panels> requires a <panel>");
1254:
1255: // We process each panel markup
1256: Iterator<XMLElement> iter = panels.iterator();
1257: while (iter.hasNext()) {
1258: XMLElement xmlPanel = iter.next();
1259:
1260: // create the serialized Panel data
1261: Panel panel = new Panel();
1262: panel.osConstraints = OsConstraint.getOsList(xmlPanel);
1263: String className = xmlPanel.getAttribute("classname");
1264: // add an id
1265: String panelid = xmlPanel.getAttribute("id");
1266: panel.setPanelid(panelid);
1267: String condition = xmlPanel.getAttribute("condition");
1268: panel.setCondition(condition);
1269:
1270: // Panel files come in jars packaged w/ IzPack
1271: String jarPath = "bin/panels/" + className + ".jar";
1272: URL url = findIzPackResource(jarPath, "Panel jar file",
1273: xmlPanel);
1274: String fullClassName = null;
1275: try {
1276: fullClassName = getFullClassName(url, className);
1277: } catch (IOException e) {
1278: }
1279: if (fullClassName != null)
1280: panel.className = fullClassName;
1281: else
1282: panel.className = className;
1283: // insert into the packager
1284: compiler.addPanelJar(panel, url);
1285: }
1286: notifyCompilerListener("addPanels", CompilerListener.END, data);
1287: }
1288:
1289: /**
1290: * Adds the resources.
1291: *
1292: * @param data The XML data.
1293: * @exception CompilerException Description of the Exception
1294: */
1295: protected void addResources(XMLElement data)
1296: throws CompilerException {
1297: notifyCompilerListener("addResources", CompilerListener.BEGIN,
1298: data);
1299: XMLElement root = data.getFirstChildNamed("resources");
1300: if (root == null)
1301: return;
1302:
1303: // We process each res markup
1304: Iterator<XMLElement> iter = root.getChildrenNamed("res")
1305: .iterator();
1306: while (iter.hasNext()) {
1307: XMLElement res = iter.next();
1308: String id = requireAttribute(res, "id");
1309: String src = requireAttribute(res, "src");
1310: // the parse attribute causes substitution to occur
1311: boolean substitute = validateYesNoAttribute(res, "parse",
1312: NO);
1313: // the parsexml attribute causes the xml document to be parsed
1314: boolean parsexml = validateYesNoAttribute(res, "parsexml",
1315: NO);
1316:
1317: // basedir is not prepended if src is already an absolute path
1318: URL originalUrl = findProjectResource(src, "Resource", res);
1319: URL url = originalUrl;
1320:
1321: InputStream is = null;
1322: OutputStream os = null;
1323: try {
1324: if (parsexml
1325: || (substitute && !compiler.getVariables()
1326: .isEmpty())) {
1327: // make the substitutions into a temp file
1328: File parsedFile = File.createTempFile("izpp", null);
1329: parsedFile.deleteOnExit();
1330: FileOutputStream outFile = new FileOutputStream(
1331: parsedFile);
1332: os = new BufferedOutputStream(outFile);
1333: // and specify the substituted file to be added to the
1334: // packager
1335: url = parsedFile.toURL();
1336: }
1337:
1338: if (parsexml) {
1339: IXMLParser parser = XMLParserFactory
1340: .createDefaultXMLParser();
1341: // this constructor will open the specified url (this is
1342: // why the InputStream is not handled in a similar manner
1343: // to the OutputStream)
1344: IXMLReader reader = new StdXMLReader(null,
1345: originalUrl.toExternalForm());
1346: parser.setReader(reader);
1347: XMLElement xml = (XMLElement) parser.parse();
1348:
1349: if (substitute
1350: && !compiler.getVariables().isEmpty()) {
1351: // if we are also performing substitutions on the file
1352: // then create an in-memory copy to pass to the
1353: // substitutor
1354: ByteArrayOutputStream baos = new ByteArrayOutputStream();
1355: XMLWriter xmlWriter = new XMLWriter(baos);
1356: xmlWriter.write(xml);
1357: is = new ByteArrayInputStream(baos
1358: .toByteArray());
1359: } else {
1360: // otherwise write direct to the temp file
1361: XMLWriter xmlWriter = new XMLWriter(os);
1362: xmlWriter.write(xml);
1363: }
1364: }
1365:
1366: // substitute variable values in the resource if parsed
1367: if (substitute) {
1368: if (compiler.getVariables().isEmpty()) {
1369: // reset url to original.
1370: url = originalUrl;
1371: parseWarn(res, "No variables defined. "
1372: + url.getPath() + " not parsed.");
1373: } else {
1374: String type = res.getAttribute("type");
1375: String encoding = res.getAttribute("encoding");
1376:
1377: // if the xml parser did not open the url
1378: // ('parsexml' was not enabled)
1379: if (null == is) {
1380: is = new BufferedInputStream(url
1381: .openStream());
1382: }
1383: VariableSubstitutor vs = new VariableSubstitutor(
1384: compiler.getVariables());
1385: vs.substitute(is, os, type, encoding);
1386: }
1387: }
1388:
1389: } catch (Exception e) {
1390: parseError(res, e.getMessage(), e);
1391: } finally {
1392: if (null != os) {
1393: try {
1394: os.close();
1395: } catch (IOException e) {
1396: // ignore as there is nothing we can realistically do
1397: // so lets at least try to close the input stream
1398: }
1399: }
1400: if (null != is) {
1401: try {
1402: is.close();
1403: } catch (IOException e) {
1404: // ignore as there is nothing we can realistically do
1405: }
1406: }
1407: }
1408:
1409: compiler.addResource(id, url);
1410: }
1411: notifyCompilerListener("addResources", CompilerListener.END,
1412: data);
1413: }
1414:
1415: /**
1416: * Adds the ISO3 codes of the langpacks and associated resources.
1417: *
1418: * @param data The XML data.
1419: * @exception CompilerException Description of the Exception
1420: */
1421: protected void addLangpacks(XMLElement data)
1422: throws CompilerException {
1423: notifyCompilerListener("addLangpacks", CompilerListener.BEGIN,
1424: data);
1425: XMLElement root = requireChildNamed(data, "locale");
1426:
1427: // at least one langpack is required
1428: Vector<XMLElement> locals = root.getChildrenNamed("langpack");
1429: if (locals.isEmpty())
1430: parseError(root, "<locale> requires a <langpack>");
1431:
1432: // We process each langpack markup
1433: Iterator<XMLElement> iter = locals.iterator();
1434: while (iter.hasNext()) {
1435: XMLElement el = iter.next();
1436: String iso3 = requireAttribute(el, "iso3");
1437: String path;
1438:
1439: path = "bin/langpacks/installer/" + iso3 + ".xml";
1440: URL iso3xmlURL = findIzPackResource(path, "ISO3 file", el);
1441:
1442: path = "bin/langpacks/flags/" + iso3 + ".gif";
1443: URL iso3FlagURL = findIzPackResource(path,
1444: "ISO3 flag image", el);
1445:
1446: compiler.addLangPack(iso3, iso3xmlURL, iso3FlagURL);
1447: }
1448: notifyCompilerListener("addLangpacks", CompilerListener.END,
1449: data);
1450: }
1451:
1452: /**
1453: * Builds the Info class from the XML tree.
1454: *
1455: * @param data The XML data. return The Info.
1456: * @exception Exception Description of the Exception
1457: */
1458: protected void addInfo(XMLElement data) throws Exception {
1459: notifyCompilerListener("addInfo", CompilerListener.BEGIN, data);
1460: // Initialisation
1461: XMLElement root = requireChildNamed(data, "info");
1462:
1463: Info info = new Info();
1464: info.setAppName(requireContent(requireChildNamed(root,
1465: "appname")));
1466: info.setAppVersion(requireContent(requireChildNamed(root,
1467: "appversion")));
1468: // We get the installation subpath
1469: XMLElement subpath = root.getFirstChildNamed("appsubpath");
1470: if (subpath != null) {
1471: info.setInstallationSubPath(requireContent(subpath));
1472: }
1473:
1474: // validate and insert app URL
1475: final XMLElement URLElem = root.getFirstChildNamed("url");
1476: if (URLElem != null) {
1477: URL appURL = requireURLContent(URLElem);
1478: info.setAppURL(appURL.toString());
1479: }
1480:
1481: // We get the authors list
1482: XMLElement authors = root.getFirstChildNamed("authors");
1483: if (authors != null) {
1484: Iterator<XMLElement> iter = authors.getChildrenNamed(
1485: "author").iterator();
1486: while (iter.hasNext()) {
1487: XMLElement author = iter.next();
1488: String name = requireAttribute(author, "name");
1489: String email = requireAttribute(author, "email");
1490: info.addAuthor(new Info.Author(name, email));
1491: }
1492: }
1493:
1494: // We get the java version required
1495: XMLElement javaVersion = root.getFirstChildNamed("javaversion");
1496: if (javaVersion != null)
1497: info.setJavaVersion(requireContent(javaVersion));
1498:
1499: // Is a JDK required?
1500: XMLElement jdkRequired = root.getFirstChildNamed("requiresjdk");
1501: if (jdkRequired != null)
1502: info.setJdkRequired("yes".equals(jdkRequired.getContent()));
1503:
1504: // validate and insert (and require if -web kind) web dir
1505: XMLElement webDirURL = root.getFirstChildNamed("webdir");
1506: if (webDirURL != null)
1507: info.setWebDirURL(requireURLContent(webDirURL).toString());
1508: String kind = compiler.getKind();
1509: if (kind != null) {
1510: if (kind.equalsIgnoreCase(WEB) && webDirURL == null) {
1511: parseError(root,
1512: "<webdir> required when \"WEB\" installer requested");
1513: } else if (kind.equalsIgnoreCase(STANDARD)
1514: && webDirURL != null) {
1515: // Need a Warning? parseWarn(webDirURL, "Not creating web
1516: // installer.");
1517: info.setWebDirURL(null);
1518: }
1519: }
1520:
1521: // Add the uninstaller as a resource if specified
1522: XMLElement uninstallInfo = root
1523: .getFirstChildNamed("uninstaller");
1524: if (validateYesNoAttribute(uninstallInfo, "write", YES)) {
1525: URL url = findIzPackResource("lib/uninstaller.jar",
1526: "Uninstaller", root);
1527: compiler.addResource("IzPack.uninstaller", url);
1528:
1529: if (uninstallInfo != null) {
1530: String uninstallerName = uninstallInfo
1531: .getAttribute("name");
1532: if (uninstallerName != null
1533: && uninstallerName.length() > ".jar".length())
1534: info.setUninstallerName(uninstallerName);
1535: }
1536: }
1537: // Add the path for the summary log file if specified
1538: XMLElement slfPath = root
1539: .getFirstChildNamed("summarylogfilepath");
1540: if (slfPath != null)
1541: info.setSummaryLogFilePath(requireContent(slfPath));
1542:
1543: XMLElement writeInstallInfo = root
1544: .getFirstChildNamed("writeinstallationinformation");
1545: if (writeInstallInfo != null) {
1546: String writeInstallInfoString = requireContent(writeInstallInfo);
1547: info
1548: .setWriteInstallationInformation(validateYesNo(writeInstallInfoString));
1549: }
1550:
1551: // look for an unpacker class
1552: String unpackerclass = compiler.getProperty("UNPACKER_CLASS");
1553: info.setUnpackerClassName(unpackerclass);
1554: compiler.setInfo(info);
1555: notifyCompilerListener("addInfo", CompilerListener.END, data);
1556: }
1557:
1558: /**
1559: * Variable declaration is a fragment of the xml file. For example:
1560: *
1561: * <pre>
1562: *
1563: *
1564: *
1565: *
1566: * <variables>
1567: * <variable name="nom" value="value"/>
1568: * <variable name="foo" value="pippo"/>
1569: * </variables>
1570: *
1571: *
1572: *
1573: *
1574: * </pre>
1575: *
1576: * variable declared in this can be referred to in parsable files.
1577: *
1578: * @param data The XML data.
1579: * @exception CompilerException Description of the Exception
1580: */
1581: protected void addVariables(XMLElement data)
1582: throws CompilerException {
1583: notifyCompilerListener("addVariables", CompilerListener.BEGIN,
1584: data);
1585: // We get the varible list
1586: XMLElement root = data.getFirstChildNamed("variables");
1587: if (root == null)
1588: return;
1589:
1590: Properties variables = compiler.getVariables();
1591:
1592: Iterator<XMLElement> iter = root.getChildrenNamed("variable")
1593: .iterator();
1594: while (iter.hasNext()) {
1595: XMLElement var = iter.next();
1596: String name = requireAttribute(var, "name");
1597: String value = requireAttribute(var, "value");
1598: if (variables.contains(name))
1599: parseWarn(var, "Variable '" + name
1600: + "' being overwritten");
1601: variables.setProperty(name, value);
1602: }
1603: notifyCompilerListener("addVariables", CompilerListener.END,
1604: data);
1605: }
1606:
1607: protected void addDynamicVariables(XMLElement data)
1608: throws CompilerException {
1609: notifyCompilerListener("addDynamicVariables",
1610: CompilerListener.BEGIN, data);
1611: // We get the dynamic variable list
1612: XMLElement root = data.getFirstChildNamed("dynamicvariables");
1613: if (root == null)
1614: return;
1615:
1616: Map<String, DynamicVariable> dynamicvariables = compiler
1617: .getDynamicVariables();
1618:
1619: Iterator<XMLElement> iter = root.getChildrenNamed("variable")
1620: .iterator();
1621: while (iter.hasNext()) {
1622: XMLElement var = iter.next();
1623: String name = requireAttribute(var, "name");
1624: String value = requireAttribute(var, "value");
1625: String conditionid = var.getAttribute("condition");
1626: if (dynamicvariables.containsKey(name))
1627: parseWarn(var, "DynamicVariable '" + name
1628: + "' being overwritten");
1629: DynamicVariable dynvar = new DynamicVariable();
1630: dynvar.setName(name);
1631: dynvar.setValue(value);
1632: dynvar.setConditionid(conditionid);
1633: dynamicvariables.put(name, dynvar);
1634: }
1635: notifyCompilerListener("addDynamicVariables",
1636: CompilerListener.END, data);
1637: }
1638:
1639: /**
1640: * Parse conditions and add them to the compiler.
1641: * @param data
1642: * @throws CompilerException
1643: */
1644: protected void addConditions(XMLElement data)
1645: throws CompilerException {
1646: notifyCompilerListener("addConditions", CompilerListener.BEGIN,
1647: data);
1648: // We get the condition list
1649: XMLElement root = data.getFirstChildNamed("conditions");
1650: Map<String, Condition> conditions = compiler.getConditions();
1651: if (root != null) {
1652: Iterator<XMLElement> iter = root.getChildrenNamed(
1653: "condition").iterator();
1654: while (iter.hasNext()) {
1655: XMLElement conditionel = iter.next();
1656: Condition condition = RulesEngine
1657: .analyzeCondition(conditionel);
1658: if (condition != null) {
1659: String conditionid = condition.getId();
1660: if (conditions.containsKey(conditionid)) {
1661: parseWarn(conditionel, "Condition with id '"
1662: + conditionid + "' will be overwritten");
1663: }
1664: conditions.put(conditionid, condition);
1665:
1666: } else {
1667: parseWarn(conditionel,
1668: "Condition couldn't be instantiated.");
1669: }
1670: }
1671: }
1672: notifyCompilerListener("addConditions", CompilerListener.END,
1673: data);
1674: }
1675:
1676: /**
1677: * Properties declaration is a fragment of the xml file. For example:
1678: *
1679: * <pre>
1680: *
1681: *
1682: *
1683: *
1684: * <properties>
1685: * <property name="app.name" value="Property Laden Installer"/>
1686: * <!-- Ant styles 'location' and 'refid' are not yet supported -->
1687: * <property file="filename-relative-to-install?"/>
1688: * <property file="filename-relative-to-install?" prefix="prefix"/>
1689: * <!-- Ant style 'url' and 'resource' are not yet supported -->
1690: * <property environment="prefix"/>
1691: * </properties>
1692: *
1693: *
1694: *
1695: *
1696: * </pre>
1697: *
1698: * variable declared in this can be referred to in parsable files.
1699: *
1700: * @param data The XML data.
1701: * @exception CompilerException Description of the Exception
1702: */
1703: protected void substituteProperties(XMLElement data)
1704: throws CompilerException {
1705: notifyCompilerListener("substituteProperties",
1706: CompilerListener.BEGIN, data);
1707:
1708: XMLElement root = data.getFirstChildNamed("properties");
1709: if (root != null) {
1710: // add individual properties
1711: Iterator<XMLElement> iter = root.getChildrenNamed(
1712: "property").iterator();
1713: while (iter.hasNext()) {
1714: XMLElement prop = iter.next();
1715: Property property = new Property(prop, this );
1716: property.execute();
1717: }
1718: }
1719:
1720: // temporarily remove the 'properties' branch, replace all properties in
1721: // the remaining DOM, and replace properties branch.
1722: // TODO: enhance XMLElement with an "indexOf(XMLElement)" method
1723: // and addChild(XMLElement, int) so returns to the same place.
1724: if (root != null)
1725: data.removeChild(root);
1726:
1727: substituteAllProperties(data);
1728: if (root != null)
1729: data.addChild(root);
1730:
1731: notifyCompilerListener("substituteProperties",
1732: CompilerListener.END, data);
1733: }
1734:
1735: /**
1736: * Perform recursive substitution on all properties
1737: */
1738: protected void substituteAllProperties(XMLElement element)
1739: throws CompilerException {
1740: Enumeration attributes = element.enumerateAttributeNames();
1741: while (attributes.hasMoreElements()) {
1742: String name = (String) attributes.nextElement();
1743: String value = compiler.replaceProperties(element
1744: .getAttribute(name));
1745: element.setAttribute(name, value);
1746: }
1747:
1748: String content = element.getContent();
1749: if (content != null) {
1750: element.setContent(compiler.replaceProperties(content));
1751: }
1752:
1753: Enumeration children = element.enumerateChildren();
1754: while (children.hasMoreElements()) {
1755: XMLElement child = (XMLElement) children.nextElement();
1756: substituteAllProperties(child);
1757: }
1758: }
1759:
1760: /**
1761: * Returns the XMLElement representing the installation XML file.
1762: *
1763: * @return The XML tree.
1764: * @exception CompilerException For problems with the installation file
1765: * @exception IOException for errors reading the installation file
1766: */
1767: protected XMLElement getXMLTree() throws CompilerException,
1768: IOException {
1769: // Initialises the parser
1770: IXMLReader reader = null;
1771: if (filename != null) {
1772: File file = new File(filename).getAbsoluteFile();
1773: if (!file.canRead())
1774: throw new CompilerException("Invalid file: " + file);
1775: reader = new StdXMLReader(new FileInputStream(filename));
1776: reader.setSystemID(file.toURL().toExternalForm());
1777: // add izpack built in property
1778: compiler.setProperty("izpack.file", file.toString());
1779: } else if (installText != null) {
1780: reader = StdXMLReader.stringReader(installText);
1781: } else {
1782: throw new CompilerException(
1783: "Neither install file nor text specified");
1784: }
1785:
1786: StdXMLParser parser = new StdXMLParser();
1787: parser.setBuilder(XMLBuilderFactory.createXMLBuilder());
1788: parser.setReader(reader);
1789: parser.setValidator(new NonValidator());
1790:
1791: // We get it
1792: XMLElement data = null;
1793: try {
1794: data = (XMLElement) parser.parse();
1795: } catch (Exception x) {
1796: throw new CompilerException(
1797: "Error parsing installation file", x);
1798: }
1799:
1800: // We check it
1801: if (!"installation".equalsIgnoreCase(data.getName()))
1802: parseError(data,
1803: "this is not an IzPack XML installation file");
1804: if (!VERSION
1805: .equalsIgnoreCase(requireAttribute(data, "version")))
1806: parseError(data,
1807: "the file version is different from the compiler version");
1808:
1809: // We finally return the tree
1810: return data;
1811: }
1812:
1813: protected int getOverrideValue(XMLElement f)
1814: throws CompilerException {
1815: int override = PackFile.OVERRIDE_UPDATE;
1816:
1817: String override_val = f.getAttribute("override");
1818: if (override_val != null) {
1819: if ("true".equalsIgnoreCase(override_val)) {
1820: override = PackFile.OVERRIDE_TRUE;
1821: } else if ("false".equalsIgnoreCase(override_val)) {
1822: override = PackFile.OVERRIDE_FALSE;
1823: } else if ("asktrue".equalsIgnoreCase(override_val)) {
1824: override = PackFile.OVERRIDE_ASK_TRUE;
1825: } else if ("askfalse".equalsIgnoreCase(override_val)) {
1826: override = PackFile.OVERRIDE_ASK_FALSE;
1827: } else if ("update".equalsIgnoreCase(override_val)) {
1828: override = PackFile.OVERRIDE_UPDATE;
1829: } else
1830: parseError(f,
1831: "invalid value for attribute \"override\"");
1832: }
1833:
1834: return override;
1835: }
1836:
1837: /**
1838: * Look for a project specified resources, which, if not absolute, are sought relative to the
1839: * projects basedir. The path should use '/' as the fileSeparator. If the resource is not found,
1840: * a CompilerException is thrown indicating fault in the parent element.
1841: *
1842: * @param path the relative path (using '/' as separator) to the resource.
1843: * @param desc the description of the resource used to report errors
1844: * @param parent the XMLElement the resource is specified in, used to report errors
1845: * @return a URL to the resource.
1846: */
1847: private URL findProjectResource(String path, String desc,
1848: XMLElement parent) throws CompilerException {
1849: URL url = null;
1850: File resource = new File(path);
1851: if (!resource.isAbsolute())
1852: resource = new File(basedir, path);
1853:
1854: if (!resource.exists()) // fatal
1855: parseError(parent, desc + " not found: " + resource);
1856:
1857: try {
1858: url = resource.toURL();
1859: } catch (MalformedURLException how) {
1860: parseError(parent, desc + "(" + resource + ")", how);
1861: }
1862:
1863: return url;
1864: }
1865:
1866: /**
1867: * Look for an IzPack resource either in the compiler jar, or within IZPACK_HOME. The path must
1868: * not be absolute. The path must use '/' as the fileSeparator (it's used to access the jar
1869: * file). If the resource is not found, a CompilerException is thrown indicating fault in the
1870: * parent element.
1871: *
1872: * @param path the relative path (using '/' as separator) to the resource.
1873: * @param desc the description of the resource used to report errors
1874: * @param parent the XMLElement the resource is specified in, used to report errors
1875: * @return a URL to the resource.
1876: */
1877: private URL findIzPackResource(String path, String desc,
1878: XMLElement parent) throws CompilerException {
1879: URL url = getClass().getResource("/" + path);
1880: if (url == null) {
1881: File resource = new File(path);
1882:
1883: if (!resource.isAbsolute())
1884: resource = new File(Compiler.IZPACK_HOME, path);
1885:
1886: if (!resource.exists()) // fatal
1887: parseError(parent, desc + " not found: " + resource);
1888:
1889: try {
1890: url = resource.toURL();
1891: } catch (MalformedURLException how) {
1892: parseError(parent, desc + "(" + resource + ")", how);
1893: }
1894: }
1895:
1896: return url;
1897: }
1898:
1899: /**
1900: * Create parse error with consistent messages. Includes file name. For use When parent is
1901: * unknown.
1902: *
1903: * @param message Brief message explaining error
1904: */
1905: protected void parseError(String message) throws CompilerException {
1906: throw new CompilerException(filename + ":" + message);
1907: }
1908:
1909: /**
1910: * Create parse error with consistent messages. Includes file name and line # of parent. It is
1911: * an error for 'parent' to be null.
1912: *
1913: * @param parent The element in which the error occured
1914: * @param message Brief message explaining error
1915: */
1916: protected void parseError(XMLElement parent, String message)
1917: throws CompilerException {
1918: throw new CompilerException(filename + ":" + parent.getLineNr()
1919: + ": " + message);
1920: }
1921:
1922: /**
1923: * Create a chained parse error with consistent messages. Includes file name and line # of
1924: * parent. It is an error for 'parent' to be null.
1925: *
1926: * @param parent The element in which the error occured
1927: * @param message Brief message explaining error
1928: */
1929: protected void parseError(XMLElement parent, String message,
1930: Throwable cause) throws CompilerException {
1931: throw new CompilerException(filename + ":" + parent.getLineNr()
1932: + ": " + message, cause);
1933: }
1934:
1935: /**
1936: * Create a parse warning with consistent messages. Includes file name and line # of parent. It
1937: * is an error for 'parent' to be null.
1938: *
1939: * @param parent The element in which the warning occured
1940: * @param message Warning message
1941: */
1942: protected void parseWarn(XMLElement parent, String message) {
1943: System.out.println(filename + ":" + parent.getLineNr() + ": "
1944: + message);
1945: }
1946:
1947: /**
1948: * Call getFirstChildNamed on the parent, producing a meaningful error message on failure. It is
1949: * an error for 'parent' to be null.
1950: *
1951: * @param parent The element to search for a child
1952: * @param name Name of the child element to get
1953: */
1954: protected XMLElement requireChildNamed(XMLElement parent,
1955: String name) throws CompilerException {
1956: XMLElement child = parent.getFirstChildNamed(name);
1957: if (child == null)
1958: parseError(parent, "<" + parent.getName()
1959: + "> requires child <" + name + ">");
1960: return child;
1961: }
1962:
1963: /**
1964: * Call getContent on an element, producing a meaningful error message if not present, or empty,
1965: * or a valid URL. It is an error for 'element' to be null.
1966: *
1967: * @param element The element to get content of
1968: */
1969: protected URL requireURLContent(XMLElement element)
1970: throws CompilerException {
1971: URL url = null;
1972: try {
1973: url = new URL(requireContent(element));
1974: } catch (MalformedURLException x) {
1975: parseError(element, "<" + element.getName()
1976: + "> requires valid URL", x);
1977: }
1978: return url;
1979: }
1980:
1981: /**
1982: * Call getContent on an element, producing a meaningful error message if not present, or empty.
1983: * It is an error for 'element' to be null.
1984: *
1985: * @param element The element to get content of
1986: */
1987: protected String requireContent(XMLElement element)
1988: throws CompilerException {
1989: String content = element.getContent();
1990: if (content == null || content.length() == 0)
1991: parseError(element, "<" + element.getName()
1992: + "> requires content");
1993: return content;
1994: }
1995:
1996: protected boolean validateYesNo(String value) {
1997: boolean result = false;
1998: if ("yes".equalsIgnoreCase(value)) {
1999: result = true;
2000: } else if ("no".equalsIgnoreCase(value)) {
2001: result = false;
2002: } else {
2003: Debug.trace("yes/no not found. trying true/false");
2004: result = Boolean.valueOf(value);
2005: }
2006: return result;
2007: }
2008:
2009: /**
2010: * Call getAttribute on an element, producing a meaningful error message if not present, or
2011: * empty. It is an error for 'element' or 'attribute' to be null.
2012: *
2013: * @param element The element to get the attribute value of
2014: * @param attribute The name of the attribute to get
2015: */
2016: protected String requireAttribute(XMLElement element,
2017: String attribute) throws CompilerException {
2018: String value = element.getAttribute(attribute);
2019: if (value == null)
2020: parseError(element, "<" + element.getName()
2021: + "> requires attribute '" + attribute + "'");
2022: return value;
2023: }
2024:
2025: /**
2026: * Get a required attribute of an element, ensuring it is an integer. A meaningful error message
2027: * is generated as a CompilerException if not present or parseable as an int. It is an error for
2028: * 'element' or 'attribute' to be null.
2029: *
2030: * @param element The element to get the attribute value of
2031: * @param attribute The name of the attribute to get
2032: */
2033: protected int requireIntAttribute(XMLElement element,
2034: String attribute) throws CompilerException {
2035: String value = element.getAttribute(attribute);
2036: if (value == null || value.length() == 0)
2037: parseError(element, "<" + element.getName()
2038: + "> requires attribute '" + attribute + "'");
2039: try {
2040: return Integer.parseInt(value);
2041: } catch (NumberFormatException x) {
2042: parseError(element, "'" + attribute
2043: + "' must be an integer");
2044: }
2045: return 0; // never happens
2046: }
2047:
2048: /**
2049: * Call getAttribute on an element, producing a meaningful error message if not present, or one
2050: * of "yes" or "no". It is an error for 'element' or 'attribute' to be null.
2051: *
2052: * @param element The element to get the attribute value of
2053: * @param attribute The name of the attribute to get
2054: */
2055: protected boolean requireYesNoAttribute(XMLElement element,
2056: String attribute) throws CompilerException {
2057: String value = requireAttribute(element, attribute);
2058: if ("yes".equalsIgnoreCase(value))
2059: return true;
2060: if ("no".equalsIgnoreCase(value))
2061: return false;
2062:
2063: parseError(element, "<" + element.getName()
2064: + "> invalid attribute '" + attribute
2065: + "': Expected (yes|no)");
2066:
2067: return false; // never happens
2068: }
2069:
2070: /**
2071: * Call getAttribute on an element, producing a meaningful warning if not "yes" or "no". If the
2072: * 'element' or 'attribute' are null, the default value is returned.
2073: *
2074: * @param element The element to get the attribute value of
2075: * @param attribute The name of the attribute to get
2076: * @param defaultValue Value returned if attribute not present or invalid
2077: */
2078: protected boolean validateYesNoAttribute(XMLElement element,
2079: String attribute, boolean defaultValue) {
2080: if (element == null)
2081: return defaultValue;
2082:
2083: String value = element.getAttribute(attribute,
2084: (defaultValue ? "yes" : "no"));
2085: if ("yes".equalsIgnoreCase(value))
2086: return true;
2087: if ("no".equalsIgnoreCase(value))
2088: return false;
2089:
2090: // TODO: should this be an error if it's present but "none of the
2091: // above"?
2092: parseWarn(element, "<" + element.getName()
2093: + "> invalid attribute '" + attribute
2094: + "': Expected (yes|no) if present");
2095:
2096: return defaultValue;
2097: }
2098:
2099: /**
2100: * The main method if the compiler is invoked by a command-line call.
2101: *
2102: * @param args The arguments passed on the command-line.
2103: */
2104: public static void main(String[] args) {
2105: // Outputs some informations
2106: System.out.println("");
2107: System.out.println(".:: IzPack - Version "
2108: + Compiler.IZPACK_VERSION + " ::.");
2109: System.out.println("");
2110: System.out.println("< compiler specifications version: "
2111: + VERSION + " >");
2112: System.out.println("");
2113: System.out.println("- Copyright (c) 2001-2008 Julien Ponge");
2114: System.out
2115: .println("- Visit http://izpack.org/ for the latest releases");
2116: System.out
2117: .println("- Released under the terms of the Apache Software License version 2.0.");
2118: System.out.println("");
2119:
2120: // exit code 1 means: error
2121: int exitCode = 1;
2122: String home = ".";
2123: // We get the IzPack home directory
2124: String izHome = System.getProperty("IZPACK_HOME");
2125: if (izHome != null)
2126: home = izHome;
2127:
2128: // We analyse the command line parameters
2129: try {
2130: // Our arguments
2131: String filename;
2132: String base = ".";
2133: String kind = "standard";
2134: String output;
2135: String compr_format = "default";
2136: int compr_level = -1;
2137:
2138: // First check
2139: int nArgs = args.length;
2140: if (nArgs < 1)
2141: throw new Exception("no arguments given");
2142:
2143: // The users wants to know the command line parameters
2144: if ("-?".equalsIgnoreCase(args[0])) {
2145: System.out
2146: .println("-> Command line parameters are : (xml file) [args]");
2147: System.out
2148: .println(" (xml file): the xml file describing the installation");
2149: System.out
2150: .println(" -h (IzPack home) : the root path of IzPack. This will be needed");
2151: System.out
2152: .println(" if the compiler is not called in the root directory of IzPack.");
2153: System.out
2154: .println(" Do not forget quotations if there are blanks in the path.");
2155: System.out
2156: .println(" -b (base) : indicates the base path that the compiler will use for filenames");
2157: System.out
2158: .println(" of sources. Default is the current path. Attend to -h.");
2159: System.out
2160: .println(" -k (kind) : indicates the kind of installer to generate");
2161: System.out
2162: .println(" default is standard");
2163: System.out
2164: .println(" -o (out) : indicates the output file name");
2165: System.out
2166: .println(" default is the xml file name\n");
2167: System.out
2168: .println(" -c (compression) : indicates the compression format to be used for packs");
2169: System.out
2170: .println(" default is the internal deflate compression\n");
2171: System.out
2172: .println(" -l (compression-level) : indicates the level for the used compression format");
2173: System.out
2174: .println(" if supported. Only integer are valid\n");
2175:
2176: System.out
2177: .println(" When using vm option -DSTACKTRACE=true there is all kind of debug info ");
2178: System.out.println("");
2179: exitCode = 0;
2180: } else {
2181: // We can parse the other parameters & try to compile the
2182: // installation
2183:
2184: // We get the input file name and we initialize the output file
2185: // name
2186: filename = args[0];
2187: // default jar files names are based on input file name
2188: output = filename.substring(0, filename.length() - 3)
2189: + "jar";
2190:
2191: // We parse the other ones
2192: int pos = 1;
2193: while (pos < nArgs)
2194: if ((args[pos].startsWith("-"))
2195: && (args[pos].length() == 2)) {
2196: switch (args[pos].toLowerCase().charAt(1)) {
2197: case 'b':
2198: if ((pos + 1) < nArgs) {
2199: pos++;
2200: base = args[pos];
2201: } else
2202: throw new Exception(
2203: "base argument missing");
2204: break;
2205: case 'k':
2206: if ((pos + 1) < nArgs) {
2207: pos++;
2208: kind = args[pos];
2209: } else
2210: throw new Exception(
2211: "kind argument missing");
2212: break;
2213: case 'o':
2214: if ((pos + 1) < nArgs) {
2215: pos++;
2216: output = args[pos];
2217: } else
2218: throw new Exception(
2219: "output argument missing");
2220: break;
2221: case 'c':
2222: if ((pos + 1) < nArgs) {
2223: pos++;
2224: compr_format = args[pos];
2225: } else
2226: throw new Exception(
2227: "compression format argument missing");
2228: break;
2229: case 'l':
2230: if ((pos + 1) < nArgs) {
2231: pos++;
2232: compr_level = Integer
2233: .parseInt(args[pos]);
2234: } else
2235: throw new Exception(
2236: "compression level argument missing");
2237: break;
2238: case 'h':
2239: if ((pos + 1) < nArgs) {
2240: pos++;
2241: home = args[pos];
2242: } else
2243: throw new Exception(
2244: "IzPack home path argument missing");
2245: break;
2246: default:
2247: throw new Exception("unknown argument");
2248: }
2249: pos++;
2250: } else
2251: throw new Exception("bad argument");
2252:
2253: home = resolveIzPackHome(home);
2254: // Outputs what we are going to do
2255: System.out.println("-> Processing : " + filename);
2256: System.out.println("-> Output : " + output);
2257: System.out.println("-> Base path : " + base);
2258: System.out.println("-> Kind : " + kind);
2259: System.out.println("-> Compression : " + compr_format);
2260: System.out.println("-> Compr. level: " + compr_level);
2261: System.out.println("-> IzPack home : " + home);
2262: System.out.println("");
2263:
2264: Compiler.setIzpackHome(home);
2265:
2266: // Calls the compiler
2267: CmdlinePackagerListener listener = new CmdlinePackagerListener();
2268: CompilerConfig compiler = new CompilerConfig(filename,
2269: base, kind, output, compr_format, compr_level,
2270: listener, null);
2271: compiler.executeCompiler();
2272:
2273: // Waits
2274: while (compiler.isAlive())
2275: Thread.sleep(100);
2276:
2277: if (compiler.wasSuccessful())
2278: exitCode = 0;
2279:
2280: System.out.println("Build time: " + new Date());
2281: }
2282: } catch (Exception err) {
2283: // Something bad has happened
2284: System.err.println("-> Fatal error :");
2285: System.err.println(" " + err.getMessage());
2286: err.printStackTrace();
2287: System.err.println("");
2288: System.err
2289: .println("(tip : use -? to get the commmand line parameters)");
2290: }
2291:
2292: // Closes the JVM
2293: System.exit(exitCode);
2294: }
2295:
2296: private static String resolveIzPackHome(String home) {
2297: File test = new File(home, IZ_TEST_SUBDIR + File.separator
2298: + IZ_TEST_FILE);
2299: if (test.exists())
2300: return (home);
2301: // Try to resolve the path using compiler.jar which also should be under
2302: // IZPACK_HOME.
2303: String self = Compiler.class.getName();
2304: self = self.replace('.', '/');
2305: self = "/" + self + ".class";
2306: URL url = Compiler.class.getResource(self);
2307: String np = url.getFile();
2308: int start = np.indexOf(self);
2309: np = np.substring(0, start);
2310: if (np.endsWith("!")) { // Where shut IZPACK_HOME at the standalone-compiler be??
2311: // No idea.
2312: if (np.endsWith("standalone-compiler.jar!"))
2313: return (".");
2314: np = np.substring(0, np.length() - 1);
2315: }
2316: File root = null;
2317: if (URI.create(np).isAbsolute())
2318: root = new File(URI.create(np));
2319: else
2320: root = new File(np);
2321: while (true) {
2322: if (root == null)
2323: throw new IllegalArgumentException(
2324: "No valid IzPack home directory found");
2325: test = new File(root, IZ_TEST_SUBDIR + File.separator
2326: + IZ_TEST_FILE);
2327: if (test.exists())
2328: return (root.getAbsolutePath());
2329: root = root.getParentFile();
2330: }
2331: }
2332:
2333: // -------------------------------------------------------------------------
2334: // ------------- Listener stuff ------------------------- START ------------
2335:
2336: /**
2337: * This method parses install.xml for defined listeners and put them in the right position. If
2338: * posible, the listeners will be validated. Listener declaration is a fragmention in
2339: * install.xml like : <listeners> <listener compiler="PermissionCompilerListener"
2340: * installer="PermissionInstallerListener"/> </<listeners>
2341: *
2342: * @param data the XML data
2343: * @exception Exception Description of the Exception
2344: */
2345: private void addCustomListeners(XMLElement data) throws Exception {
2346: // We get the listeners
2347: XMLElement root = data.getFirstChildNamed("listeners");
2348: if (root == null)
2349: return;
2350: Iterator<XMLElement> iter = root.getChildrenNamed("listener")
2351: .iterator();
2352: while (iter.hasNext()) {
2353: XMLElement xmlAction = iter.next();
2354: Object[] listener = getCompilerListenerInstance(xmlAction);
2355: if (listener != null)
2356: addCompilerListener((CompilerListener) listener[0]);
2357: String[] typeNames = new String[] { "installer",
2358: "uninstaller" };
2359: int[] types = new int[] { CustomData.INSTALLER_LISTENER,
2360: CustomData.UNINSTALLER_LISTENER };
2361: for (int i = 0; i < typeNames.length; ++i) {
2362: String className = xmlAction.getAttribute(typeNames[i]);
2363: if (className != null) {
2364: // Check for a jar attribute on the listener
2365: String jarPath = xmlAction.getAttribute("jar");
2366: jarPath = compiler.replaceProperties(jarPath);
2367: if (jarPath == null)
2368: jarPath = "bin/customActions/" + className
2369: + ".jar";
2370: List<OsConstraint> constraints = OsConstraint
2371: .getOsList(xmlAction);
2372: compiler.addCustomListener(types[i], className,
2373: jarPath, constraints);
2374: }
2375: }
2376: }
2377:
2378: }
2379:
2380: /**
2381: * Returns a list which contains the pathes of all files which are included in the given url.
2382: * This method expects as the url param a jar.
2383: *
2384: * @param url url of the jar file
2385: * @return full qualified paths of the contained files
2386: * @throws Exception
2387: */
2388: private List<String> getContainedFilePaths(URL url)
2389: throws Exception {
2390: JarInputStream jis = new JarInputStream(url.openStream());
2391: ZipEntry zentry = null;
2392: ArrayList<String> fullNames = new ArrayList<String>();
2393: while ((zentry = jis.getNextEntry()) != null) {
2394: String name = zentry.getName();
2395: // Add only files, no directory entries.
2396: if (!zentry.isDirectory())
2397: fullNames.add(name);
2398: }
2399: jis.close();
2400: return (fullNames);
2401: }
2402:
2403: /**
2404: * Returns the qualified class name for the given class. This method expects as the url param a
2405: * jar file which contains the given class. It scans the zip entries of the jar file.
2406: *
2407: * @param url url of the jar file which contains the class
2408: * @param className short name of the class for which the full name should be resolved
2409: * @return full qualified class name
2410: * @throws IOException
2411: */
2412: private String getFullClassName(URL url, String className)
2413: throws IOException //throws Exception
2414: {
2415: JarInputStream jis = new JarInputStream(url.openStream());
2416: ZipEntry zentry = null;
2417: while ((zentry = jis.getNextEntry()) != null) {
2418: String name = zentry.getName();
2419: int lastPos = name.lastIndexOf(".class");
2420: if (lastPos < 0) {
2421: continue; // No class file.
2422: }
2423: name = name.replace('/', '.');
2424: int pos = -1;
2425: int nonCasePos = -1;
2426: if (className != null) {
2427: pos = name.indexOf(className);
2428: nonCasePos = name.toLowerCase().indexOf(
2429: className.toLowerCase());
2430: }
2431: if (pos != -1
2432: && name.length() == pos + className.length() + 6) // "Main" class found
2433: {
2434: jis.close();
2435: return (name.substring(0, lastPos));
2436: }
2437:
2438: if (nonCasePos != -1
2439: && name.length() == nonCasePos + className.length()
2440: + 6)
2441: // "Main" class with different case found
2442: throw new IllegalArgumentException(
2443: "Fatal error! The declared panel name in the xml file ("
2444: + className
2445: + ") differs in case to the founded class file ("
2446: + name + ").");
2447: }
2448: jis.close();
2449: return (null);
2450: }
2451:
2452: /**
2453: * Returns the compiler listener which is defined in the xml element. As
2454: * xml element a "listner" node will be expected. Additional it is expected,
2455: * that either "findIzPackResource" returns an url based on
2456: * "bin/customActions/[className].jar", or that the listener element has
2457: * a jar attribute specifying the listener jar path. The class will be
2458: * loaded via an URLClassLoader.
2459: *
2460: * @param var the xml element of the "listener" node
2461: * @return instance of the defined compiler listener
2462: * @throws Exception
2463: */
2464: private Object[] getCompilerListenerInstance(XMLElement var)
2465: throws Exception {
2466: String className = var.getAttribute("compiler");
2467: Class listener = null;
2468: Object instance = null;
2469: if (className == null)
2470: return (null);
2471:
2472: // CustomAction files come in jars packaged IzPack, or they can be
2473: // specified via a jar attribute on the listener
2474: String jarPath = var.getAttribute("jar");
2475: jarPath = compiler.replaceProperties(jarPath);
2476: if (jarPath == null)
2477: jarPath = "bin/customActions/" + className + ".jar";
2478: URL url = findIzPackResource(jarPath, "CustomAction jar file",
2479: var);
2480: String fullName = getFullClassName(url, className);
2481: if (fullName == null) {
2482: // class not found
2483: return null;
2484: }
2485: if (url != null) {
2486: if (getClass().getResource("/" + jarPath) != null) { // Oops, standalone, URLClassLoader will not work ...
2487: // Write the jar to a temp file.
2488: InputStream in = null;
2489: FileOutputStream outFile = null;
2490: byte[] buffer = new byte[5120];
2491: File tf = null;
2492: try {
2493: tf = File.createTempFile("izpj", ".jar");
2494: tf.deleteOnExit();
2495: outFile = new FileOutputStream(tf);
2496: in = getClass().getResourceAsStream("/" + jarPath);
2497: long bytesCopied = 0;
2498: int bytesInBuffer;
2499: while ((bytesInBuffer = in.read(buffer)) != -1) {
2500: outFile.write(buffer, 0, bytesInBuffer);
2501: bytesCopied += bytesInBuffer;
2502: }
2503: } finally {
2504: if (in != null)
2505: in.close();
2506: if (outFile != null)
2507: outFile.close();
2508: }
2509: url = tf.toURL();
2510:
2511: }
2512: // Use the class loader of the interface as parent, else
2513: // compile will fail at using it via an Ant task.
2514: URLClassLoader ucl = new URLClassLoader(new URL[] { url },
2515: CompilerListener.class.getClassLoader());
2516: listener = ucl.loadClass(fullName);
2517: }
2518: if (listener != null)
2519: instance = listener.newInstance();
2520: else
2521: parseError(var, "Cannot find defined compiler listener "
2522: + className);
2523: if (!CompilerListener.class.isInstance(instance))
2524: parseError(var, "'" + className + "' must be implemented "
2525: + CompilerListener.class.toString());
2526: List<OsConstraint> constraints = OsConstraint.getOsList(var);
2527: return (new Object[] { instance, className, constraints });
2528: }
2529:
2530: /**
2531: * Add a CompilerListener. A registered CompilerListener will be called at every enhancmend
2532: * point of compiling.
2533: *
2534: * @param pe CompilerListener which should be added
2535: */
2536: private void addCompilerListener(CompilerListener pe) {
2537: compilerListeners.add(pe);
2538: }
2539:
2540: /**
2541: * Calls all defined compile listeners notify method with the given data
2542: *
2543: * @param callerName name of the calling method as string
2544: * @param state CompileListener.BEGIN or END
2545: * @param data current install data
2546: * @throws CompilerException
2547: */
2548: private void notifyCompilerListener(String callerName, int state,
2549: XMLElement data) throws CompilerException {
2550: Iterator<CompilerListener> i = compilerListeners.iterator();
2551: IPackager packager = compiler.getPackager();
2552: while (i != null && i.hasNext()) {
2553: CompilerListener listener = i.next();
2554: listener.notify(callerName, state, data, packager);
2555: }
2556:
2557: }
2558:
2559: /**
2560: * Calls the reviseAdditionalDataMap method of all registered CompilerListener's.
2561: *
2562: * @param f file releated XML node
2563: * @return a map with the additional attributes
2564: */
2565: private Map getAdditionals(XMLElement f) throws CompilerException {
2566: Iterator<CompilerListener> i = compilerListeners.iterator();
2567: Map retval = null;
2568: try {
2569: while (i != null && i.hasNext()) {
2570: retval = (i.next()).reviseAdditionalDataMap(retval, f);
2571: }
2572: } catch (CompilerException ce) {
2573: parseError(f, ce.getMessage());
2574: }
2575: return (retval);
2576: }
2577: }
|