001/* 002 * $Id: Compiler.java,v 1.111 2005/10/27 16:44:49 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.File; 029import java.io.IOException; 030import java.net.MalformedURLException; 031import java.net.URL; 032import java.util.ArrayList; 033import java.util.Arrays; 034import java.util.HashMap; 035import java.util.Iterator; 036import java.util.List; 037import java.util.Map; 038import java.util.Properties; 039import java.util.Set; 040import java.util.jar.JarInputStream; 041import java.util.zip.ZipEntry; 042 043import com.izforge.izpack.CustomData; 044import com.izforge.izpack.GUIPrefs; 045import com.izforge.izpack.Info; 046import com.izforge.izpack.Panel; 047import com.izforge.izpack.util.Debug; 048import com.izforge.izpack.util.VariableSubstitutor; 049 050/** 051 * The IzPack compiler class. This is now a java bean style class that can be 052 * configured using the object representations of the install.xml 053 * configuration. The install.xml configuration is now handled by the 054 * CompilerConfig class. 055 * 056 * @see CompilerConfig 057 * 058 * @author Julien Ponge 059 * @author Tino Schwarze 060 * @author Chadwick McHenry 061 */ 062public class Compiler extends Thread 063{ 064 /** The IzPack version. */ 065 public final static String IZPACK_VERSION = "3.8.0"; 066 067 /** The IzPack home directory. */ 068 public static String IZPACK_HOME = "."; 069 070 /** The base directory. */ 071 protected String basedir; 072 073 /** The installer kind. */ 074 protected String kind; 075 076 /** The output jar filename. */ 077 protected String output; 078 079 /** Collects and packs files into installation jars, as told. */ 080 private Packager packager = null; 081 082 /** Error code, set to true if compilation succeeded. */ 083 private boolean compileFailed = true; 084 085 /** Key/values which are substituted at compile time in the install data */ 086 private Properties properties; 087 088 /** Replaces the properties in the install.xml file prior to compiling */ 089 private VariableSubstitutor propertySubstitutor; 090 091 /** 092 * Set the IzPack home directory 093 * @param izHome - the izpack home directory 094 */ 095 public static void setIzpackHome(String izHome) 096 { 097 IZPACK_HOME = izHome; 098 } 099 100 /** 101 * The constructor. 102 * 103 * @param basedir The base directory. 104 * @param kind The installer kind. 105 * @param output The installer filename. 106 * @throws CompilerException 107 */ 108 public Compiler(String basedir, String kind, String output) throws CompilerException 109 { 110 this(basedir,kind,output,"default"); 111 } 112 113 /** 114 * The constructor. 115 * 116 * @param basedir The base directory. 117 * @param kind The installer kind. 118 * @param output The installer filename. 119 * @param compr_format The format which should be used for the packs. 120 * @throws CompilerException 121 */ 122 public Compiler(String basedir, String kind, String output, String compr_format) throws CompilerException 123 { 124 this(basedir,kind,output, compr_format, -1); 125 } 126 127 /** 128 * The constructor. 129 * 130 * @param basedir The base directory. 131 * @param kind The installer kind. 132 * @param output The installer filename. 133 * @param compr_format The format which should be used for the packs. 134 * @param compr_level Compression level to be used if supported. 135 * @throws CompilerException 136 */ 137 public Compiler(String basedir, String kind, String output, 138 String compr_format, int compr_level) throws CompilerException 139 { 140 // Default initialisation 141 this.basedir = basedir; 142 this.kind = kind; 143 this.output = output; 144 145 // initialize backed by system properties 146 properties = new Properties(System.getProperties()); 147 propertySubstitutor = new VariableSubstitutor(properties); 148 149 // add izpack built in property 150 setProperty("izpack.version", IZPACK_VERSION); 151 setProperty("basedir", basedir); 152 153 packager = new Packager(compr_format, compr_level); 154 packager.getCompressor().setCompiler(this); 155 } 156 157 158 /** 159 * Retrieves the packager listener 160 */ 161 public PackagerListener getPackagerListener() 162 { 163 return packager.getPackagerListener(); 164 } 165 /** 166 * Sets the packager listener. 167 * 168 * @param listener The listener. 169 */ 170 public void setPackagerListener(PackagerListener listener) 171 { 172 packager.setPackagerListener(listener); 173 } 174 175 /** 176 * Access the installation kind. 177 * @return the installation kind. 178 */ 179 public String getKind() 180 { 181 return kind; 182 } 183 /** 184 * Get the packager variables. 185 * @return the packager variables 186 */ 187 public Properties getVariables() 188 { 189 return packager.getVariables(); 190 } 191 192 /** Compiles. */ 193 public void compile() 194 { 195 start(); 196 } 197 198 /** The run() method. */ 199 public void run() 200 { 201 try 202 { 203 createInstaller(); // Execute the compiler - may send info to 204 // System.out 205 } 206 catch (CompilerException ce) 207 { 208 System.out.println(ce.getMessage() + "\n"); 209 } 210 catch (Exception e) 211 { 212 if (Debug.stackTracing()) 213 { 214 e.printStackTrace(); 215 } 216 else 217 { 218 System.out.println("ERROR: " + e.getMessage()); 219 } 220 } 221 } 222 223 /** 224 * Compiles the installation. 225 * 226 * @exception Exception Description of the Exception 227 */ 228 public void createInstaller() throws Exception 229 { 230 // Add the class files from the chosen compressor. 231 if( packager.getCompressor().getContainerPaths() != null ) 232 { 233 String [] containerPaths = packager.getCompressor().getContainerPaths(); 234 String [][] decoderClassNames = packager.getCompressor().getDecoderClassNames(); 235 for( int i = 0; i < containerPaths.length; ++i) 236 { 237 URL compressorURL = null; 238 if( containerPaths[i] != null ) 239 compressorURL = findIzPackResource(containerPaths[i],"pack compression Jar file"); 240 if( decoderClassNames[i] != null && decoderClassNames[i].length > 0) 241 addJarContent(compressorURL, Arrays.asList(decoderClassNames[i])); 242 } 243 244 245 } 246 247 // We ask the packager to create the installer 248 packager.createInstaller(new File(output)); 249 this.compileFailed = false; 250 } 251 252 public boolean wasSuccessful() 253 { 254 return !this.compileFailed; 255 } 256 257 public String replaceProperties(String value) throws CompilerException 258 { 259 return propertySubstitutor.substitute(value, "at"); 260 } 261 262 public void setGUIPrefs(GUIPrefs prefs) 263 { 264 packager.setGUIPrefs(prefs); 265 } 266 public void setInfo(Info info) throws Exception 267 { 268 packager.setInfo(info); 269 } 270 271 /** 272 * Get the install packager. 273 * @return the install packager. 274 */ 275 public Packager getPackager() 276 { 277 return packager; 278 } 279 /** 280 * Get the properties currently known to the compileer. 281 */ 282 public Properties getProperties() 283 { 284 return properties; 285 } 286 287 /** 288 * Get the value of a property currerntly known to izpack. 289 * 290 * @param name the name of the property 291 * @return the value of the property, or null 292 */ 293 public String getProperty(String name) 294 { 295 return properties.getProperty(name); 296 } 297 298 /** 299 * Add a name value pair to the project property set. Overwriting any existing value. 300 * 301 * @param name the name of the property 302 * @param value the value to set 303 * @return true 304 */ 305 public boolean setProperty(String name, String value) 306 { 307 // TODO: don't allow overwriting of system properties 308 properties.put(name, value); 309 return true; 310 } 311 312 /** 313 * Add a name value pair to the project property set. It is <i>not</i> replaced it is already 314 * in the set of properties. 315 * 316 * @param name the name of the property 317 * @param value the value to set 318 * @return true if the property was not already set 319 */ 320 public boolean addProperty(String name, String value) 321 { 322 String old = properties.getProperty(name); 323 if (old == null) 324 { 325 properties.put(name, value); 326 return true; 327 } 328 return false; 329 } 330 331 /** 332 * Add jar content to the installation. 333 * @param content 334 */ 335 public void addJarContent(URL content) 336 { 337 packager.addJarContent(content); 338 } 339 /** 340 * Add jar content to the installation. 341 * @param content 342 */ 343 public void addJarContent(URL content, List files) 344 { 345 packager.addJarContent(content, files); 346 } 347 /** 348 * Add a custom jar to the installation. 349 * @param ca 350 * @param url 351 */ 352 public void addCustomJar(CustomData ca, URL url) 353 { 354 packager.addCustomJar(ca, url); 355 } 356 /** 357 * Add a lang pack to the installation. 358 * @param iso3 359 * @param iso3xmlURL 360 * @param iso3FlagURL 361 */ 362 public void addLangPack(String iso3, URL iso3xmlURL, URL iso3FlagURL) 363 { 364 packager.addLangPack(iso3, iso3xmlURL, iso3FlagURL); 365 } 366 /** 367 * Add a native library to the installation. 368 * @param name 369 * @param url 370 * @throws Exception 371 */ 372 public void addNativeLibrary(String name, URL url) throws Exception 373 { 374 packager.addNativeLibrary(name, url); 375 } 376 /** 377 * Add an unistaller library. 378 * @param data 379 */ 380 public void addNativeUninstallerLibrary(CustomData data) 381 { 382 packager.addNativeUninstallerLibrary(data); 383 } 384 /** 385 * Add a pack to the installation. 386 * @param pack 387 */ 388 public void addPack(PackInfo pack) 389 { 390 packager.addPack(pack); 391 } 392 /** 393 * Add a panel jar to the installation. 394 * @param panel 395 * @param url 396 */ 397 public void addPanelJar(Panel panel, URL url) 398 { 399 packager.addPanelJar(panel, url); 400 } 401 /** 402 * Add a resource to the installation. 403 * @param name 404 * @param url 405 */ 406 public void addResource(String name, URL url) 407 { 408 packager.addResource(name, url); 409 } 410 411 /** 412 * Checks whether the dependencies stated in the configuration file are correct. Specifically it 413 * checks that no pack point to a non existent pack and also that there are no circular 414 * dependencies in the packs. 415 */ 416 public void checkDependencies() throws CompilerException 417 { 418 checkDependencies(packager.getPacksList()); 419 } 420 /** 421 * Checks whether the dependencies among the given Packs. Specifically it 422 * checks that no pack point to a non existent pack and also that there are no circular 423 * dependencies in the packs. 424 * @param packs - List<Pack> representing the packs in the installation 425 */ 426 public void checkDependencies(List packs) throws CompilerException 427 { 428 // Because we use package names in the configuration file we assosiate 429 // the names with the objects 430 Map names = new HashMap(); 431 for (int i = 0; i < packs.size(); i++) 432 { 433 PackInfo pack = (PackInfo) packs.get(i); 434 names.put(pack.getPack().name, pack); 435 } 436 int result = dfs(packs, names); 437 // @todo More informative messages to include the source of the error 438 if (result == -2) 439 parseError("Circular dependency detected"); 440 else if (result == -1) parseError("A dependency doesn't exist"); 441 } 442 443 /** 444 * We use the dfs graph search algorithm to check whether the graph is acyclic as described in: 445 * Thomas H. Cormen, Charles Leiserson, Ronald Rivest and Clifford Stein. Introduction to 446 * algorithms 2nd Edition 540-549,MIT Press, 2001 447 * 448 * @param packs The graph 449 * @param names The name map 450 */ 451 private int dfs(List packs, Map names) 452 { 453 Map edges = new HashMap(); 454 for (int i = 0; i < packs.size(); i++) 455 { 456 PackInfo pack = (PackInfo) packs.get(i); 457 if (pack.colour == PackInfo.WHITE) 458 { 459 if (dfsVisit(pack, names, edges) != 0) return -1; 460 } 461 462 } 463 return checkBackEdges(edges); 464 } 465 466 /** 467 * This function checks for the existence of back edges. 468 */ 469 private int checkBackEdges(Map edges) 470 { 471 Set keys = edges.keySet(); 472 for (Iterator iterator = keys.iterator(); iterator.hasNext();) 473 { 474 final Object key = iterator.next(); 475 int color = ((Integer) edges.get(key)).intValue(); 476 if (color == PackInfo.GREY) { return -2; } 477 } 478 return 0; 479 480 } 481 482 /** 483 * This class is used for the classification of the edges 484 */ 485 private class Edge 486 { 487 488 PackInfo u; 489 490 PackInfo v; 491 492 Edge(PackInfo u, PackInfo v) 493 { 494 this.u = u; 495 this.v = v; 496 } 497 } 498 499 private int dfsVisit(PackInfo u, Map names, Map edges) 500 { 501 u.colour = PackInfo.GREY; 502 List deps = u.getDependencies(); 503 if (deps != null) 504 { 505 for (int i = 0; i < deps.size(); i++) 506 { 507 String name = (String) deps.get(i); 508 PackInfo v = (PackInfo) names.get(name); 509 if (v == null) 510 { 511 System.out.println("Failed to find dependency: "+name); 512 return -1; 513 } 514 Edge edge = new Edge(u, v); 515 if (edges.get(edge) == null) edges.put(edge, new Integer(v.colour)); 516 517 if (v.colour == PackInfo.WHITE) 518 { 519 520 final int result = dfsVisit(v, names, edges); 521 if (result != 0) return result; 522 } 523 } 524 } 525 u.colour = PackInfo.BLACK; 526 return 0; 527 } 528 529 /** 530 * Recursive method to add files in a pack. 531 * 532 * @param file The file to add. 533 * @param targetdir The relative path to the parent. 534 * @param osList The target OS constraints. 535 * @param override Overriding behaviour. 536 * @param pack Pack to be packed into 537 * @param additionals Map which contains additional data 538 * @exception FileNotFoundException if the file does not exist 539 */ 540 protected void addRecursively(File file, String targetdir, List osList, int override, 541 PackInfo pack, Map additionals) throws IOException 542 { 543 String targetfile = targetdir + "/" + file.getName(); 544 if (!file.isDirectory()) 545 pack.addFile(file, targetfile, osList, override, additionals); 546 else 547 { 548 File[] files = file.listFiles(); 549 if (files.length == 0) // The directory is empty so must be added 550 pack.addFile(file, targetfile, osList, override, additionals); 551 else 552 { 553 // new targetdir = targetfile; 554 for (int i = 0; i < files.length; i++) 555 addRecursively(files[i], targetfile, osList, override, pack, additionals); 556 } 557 } 558 } 559 560 /** 561 * Look for an IzPack resource either in the compiler jar, or within IZPACK_HOME. The path must 562 * not be absolute. The path must use '/' as the fileSeparator (it's used to access the jar 563 * file). If the resource is not found, a CompilerException is thrown indicating fault in the 564 * parent element. 565 * 566 * @param path the relative path (using '/' as separator) to the resource. 567 * @param desc the description of the resource used to report errors 568 * @return a URL to the resource. 569 */ 570 public URL findIzPackResource(String path, String desc) 571 throws CompilerException 572 { 573 URL url = getClass().getResource("/" + path); 574 if (url == null) 575 { 576 File resource = new File(path); 577 if (!resource.isAbsolute()) resource = new File(IZPACK_HOME, path); 578 579 if (!resource.exists()) // fatal 580 parseError(desc + " not found: " + resource); 581 582 try 583 { 584 url = resource.toURL(); 585 } 586 catch (MalformedURLException how) 587 { 588 parseError(desc + "(" + resource + ")", how); 589 } 590 } 591 592 return url; 593 } 594 595 /** 596 * Create parse error with consistent messages. Includes file name. For use When parent is 597 * unknown. 598 * 599 * @param message Brief message explaining error 600 */ 601 public void parseError(String message) throws CompilerException 602 { 603 this.compileFailed = true; 604 throw new CompilerException(message); 605 } 606 public void parseError(String message, Throwable how) throws CompilerException 607 { 608 this.compileFailed = true; 609 throw new CompilerException(message, how); 610 } 611 612 /** 613 * The main method if the compiler is invoked by a command-line call. 614 * This simply calls the CompilerConfig.main method. 615 * 616 * @param args The arguments passed on the command-line. 617 */ 618 public static void main(String[] args) 619 { 620 CompilerConfig.main(args); 621 } 622 623 // ------------------------------------------------------------------------- 624 // ------------- Listener stuff ------------------------- START ------------ 625 626 /** 627 * This method parses install.xml for defined listeners and put them in the right position. If 628 * posible, the listeners will be validated. Listener declaration is a fragmention in 629 * install.xml like : <listeners> <listener compiler="PermissionCompilerListener" 630 * installer="PermissionInstallerListener"/1gt; </listeners> 631 * 632 * @param type The listener type. 633 * @param className The class name. 634 * @param jarPath The jar path. 635 * @param constraints The list of constraints. 636 * @throws Exception Thrown in case an error occurs. 637 */ 638 public void addCustomListener(int type, String className, String jarPath, List constraints) throws Exception 639 { 640 jarPath = replaceProperties(jarPath); 641 URL url = findIzPackResource(jarPath, "CustomAction jar file"); 642 List filePaths = getContainedFilePaths(url); 643 String fullClassName = getFullClassName(url, className); 644 CustomData ca = new CustomData(fullClassName, filePaths, constraints, type); 645 packager.addCustomJar(ca, url); 646 } 647 648 /** 649 * Returns a list which contains the pathes of all files which are included in the given url. 650 * This method expects as the url param a jar. 651 * 652 * @param url url of the jar file 653 * @return full qualified paths of the contained files 654 * @throws Exception 655 */ 656 private List getContainedFilePaths(URL url) throws Exception 657 { 658 JarInputStream jis = new JarInputStream(url.openStream()); 659 ZipEntry zentry = null; 660 ArrayList fullNames = new ArrayList(); 661 while ((zentry = jis.getNextEntry()) != null) 662 { 663 String name = zentry.getName(); 664 // Add only files, no directory entries. 665 if (!zentry.isDirectory()) fullNames.add(name); 666 } 667 jis.close(); 668 return (fullNames); 669 } 670 671 /** 672 * Returns the qualified class name for the given class. This method expects as the url param a 673 * jar file which contains the given class. It scans the zip entries of the jar file. 674 * 675 * @param url url of the jar file which contains the class 676 * @param className short name of the class for which the full name should be resolved 677 * @return full qualified class name 678 * @throws Exception 679 */ 680 private String getFullClassName(URL url, String className) throws Exception 681 { 682 JarInputStream jis = new JarInputStream(url.openStream()); 683 ZipEntry zentry = null; 684 while ((zentry = jis.getNextEntry()) != null) 685 { 686 String name = zentry.getName(); 687 int lastPos = name.lastIndexOf(".class"); 688 if (lastPos < 0) 689 { 690 continue; // No class file. 691 } 692 name = name.replace('/', '.'); 693 int pos = -1; 694 if (className != null) 695 { 696 pos = name.indexOf(className); 697 } 698 if (name.length() == pos + className.length() + 6) // "Main" class 699 // found 700 { 701 jis.close(); 702 return (name.substring(0, lastPos)); 703 } 704 } 705 jis.close(); 706 return (null); 707 } 708 709 // ------------------------------------------------------------------------- 710 // ------------- Listener stuff ------------------------- END ------------ 711 712 /** 713 * Used to handle the packager messages in the command-line mode. 714 * 715 * @author julien created October 26, 2002 716 */ 717 static class CmdlinePackagerListener implements PackagerListener 718 { 719 720 /** 721 * Print a message to the console at default priority (MSG_INFO). 722 * 723 * @param info The information. 724 */ 725 public void packagerMsg(String info) 726 { 727 packagerMsg(info, MSG_INFO); 728 } 729 730 /** 731 * Print a message to the console at the specified priority. 732 * 733 * @param info The information. 734 */ 735 public void packagerMsg(String info, int priority) 736 { 737 final String prefix; 738 switch (priority) 739 { 740 case MSG_DEBUG: 741 prefix = "[ DEBUG ] "; 742 break; 743 case MSG_ERR: 744 prefix = "[ ERROR ] "; 745 break; 746 case MSG_WARN: 747 prefix = "[ WARNING ] "; 748 break; 749 case MSG_INFO: 750 case MSG_VERBOSE: 751 default: // don't die, but don't prepend anything 752 prefix = ""; 753 } 754 755 System.out.println(prefix + info); 756 } 757 758 /** Called when the packager starts. */ 759 public void packagerStart() 760 { 761 System.out.println("[ Begin ]"); 762 System.out.println(); 763 } 764 765 /** Called when the packager stops. */ 766 public void packagerStop() 767 { 768 System.out.println(); 769 System.out.println("[ End ]"); 770 } 771 } 772 773}