001/* 002 * IzPack - Copyright 2001-2005 Julien Ponge, All Rights Reserved. 003 * 004 * http://www.izforge.com/izpack/ 005 * http://developer.berlios.de/projects/izpack/ 006 * 007 * Copyright 2003 Tino Schwarze 008 * 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 */ 021 022package com.izforge.izpack.installer; 023 024import java.io.File; 025import java.io.IOException; 026import java.io.InputStream; 027import java.util.ArrayList; 028import java.util.Enumeration; 029import java.util.Iterator; 030import java.util.LinkedList; 031import java.util.List; 032import java.util.StringTokenizer; 033import java.util.Vector; 034 035import net.n3.nanoxml.NonValidator; 036import net.n3.nanoxml.StdXMLBuilder; 037import net.n3.nanoxml.StdXMLParser; 038import net.n3.nanoxml.StdXMLReader; 039import net.n3.nanoxml.XMLElement; 040 041import com.izforge.izpack.LocaleDatabase; 042import com.izforge.izpack.util.Debug; 043import com.izforge.izpack.util.FileExecutor; 044import com.izforge.izpack.util.OsConstraint; 045import com.izforge.izpack.util.VariableSubstitutor; 046 047/** 048 * This class does alle the work for compiling sources. 049 * 050 * It responsible for 051 * <ul> 052 * <li>parsing the compilation spec XML file 053 * <li>collecting and creating all jobs 054 * <li>doing the actual compilation 055 * </ul> 056 * 057 * @author Tino Schwarze 058 */ 059public class CompileWorker implements Runnable 060{ 061 062 /** Compilation jobs */ 063 private ArrayList jobs; 064 065 /** Name of resource for specifying compilation parameters. */ 066 private static final String SPEC_RESOURCE_NAME = "CompilePanel.Spec.xml"; 067 068 private VariableSubstitutor vs; 069 070 /** We spawn a thread to perform compilation. */ 071 private Thread compilationThread; 072 073 private XMLElement spec; 074 075 private AutomatedInstallData idata; 076 077 private CompileHandler handler; 078 079 private XMLElement compilerSpec; 080 081 private ArrayList compilerList; 082 083 private String compilerToUse; 084 085 private XMLElement compilerArgumentsSpec; 086 087 private ArrayList compilerArgumentsList; 088 089 private String compilerArgumentsToUse; 090 091 private CompileResult result = null; 092 093 /** 094 * The constructor. 095 * 096 * @param idata The installation data. 097 * @param handler The handler to notify of progress. 098 */ 099 public CompileWorker(AutomatedInstallData idata, CompileHandler handler) throws IOException 100 { 101 this.idata = idata; 102 this.handler = handler; 103 this.vs = new VariableSubstitutor(idata.getVariables()); 104 105 this.compilationThread = null; 106 107 if (!readSpec()) throw new IOException("Error reading compilation specification"); 108 } 109 110 /** 111 * Return list of compilers to choose from. 112 * 113 * @return ArrayList of String 114 */ 115 public ArrayList getAvailableCompilers() 116 { 117 readChoices(this.compilerSpec, this.compilerList); 118 return this.compilerList; 119 } 120 121 /** 122 * Set the compiler to use. 123 * 124 * The compiler is checked before compilation starts. 125 * 126 * @param compiler compiler to use (not checked) 127 */ 128 public void setCompiler(String compiler) 129 { 130 this.compilerToUse = compiler; 131 } 132 133 /** Get the compiler used. */ 134 public String getCompiler() 135 { 136 return this.compilerToUse; 137 } 138 139 /** 140 * Return list of compiler arguments to choose from. 141 * 142 * @return ArrayList of String 143 */ 144 public ArrayList getAvailableArguments() 145 { 146 readChoices(this.compilerArgumentsSpec, this.compilerArgumentsList); 147 return this.compilerArgumentsList; 148 } 149 150 /** Set the compiler arguments to use. */ 151 public void setCompilerArguments(String arguments) 152 { 153 this.compilerArgumentsToUse = arguments; 154 } 155 156 /** Get the compiler arguments used. */ 157 public String getCompilerArguments() 158 { 159 return this.compilerArgumentsToUse; 160 } 161 162 /** Get the result of the compilation. */ 163 public CompileResult getResult() 164 { 165 return this.result; 166 } 167 168 /** Start the compilation in a separate thread. */ 169 public void startThread() 170 { 171 this.compilationThread = new Thread(this, "compilation thread"); 172 // will call this.run() 173 this.compilationThread.start(); 174 } 175 176 /** 177 * This is called when the compilation thread is activated. 178 * 179 * Can also be called directly if asynchronous processing is not desired. 180 */ 181 public void run() 182 { 183 try 184 { 185 if (!collectJobs()) 186 { 187 String[] dummy_command = { "no command"}; 188 189 this.result = new CompileResult(this.idata.langpack 190 .getString("CompilePanel.worker.nofiles"), dummy_command, "", ""); 191 } 192 else 193 { 194 this.result = compileJobs(); 195 } 196 } 197 catch (Exception e) 198 { 199 this.result = new CompileResult(); 200 this.result.setStatus(CompileResult.FAILED); 201 this.result.setAction(CompileResult.ACTION_ABORT); 202 } 203 204 this.handler.stopAction(); 205 } 206 207 private boolean readSpec() 208 { 209 InputStream input; 210 try 211 { 212 input = ResourceManager.getInstance().getInputStream(SPEC_RESOURCE_NAME); 213 } 214 catch (Exception e) 215 { 216 e.printStackTrace(); 217 return false; 218 } 219 220 StdXMLParser parser = new StdXMLParser(); 221 parser.setBuilder(new StdXMLBuilder()); 222 parser.setValidator(new NonValidator()); 223 224 try 225 { 226 parser.setReader(new StdXMLReader(input)); 227 228 this.spec = (XMLElement) parser.parse(); 229 } 230 catch (Exception e) 231 { 232 System.out.println("Error parsing XML specification for compilation."); 233 e.printStackTrace(); 234 return false; 235 } 236 237 if (!this.spec.hasChildren()) return false; 238 239 this.compilerArgumentsList = new ArrayList(); 240 this.compilerList = new ArrayList(); 241 242 // read <global> information 243 XMLElement global = this.spec.getFirstChildNamed("global"); 244 245 // use some default values if no <global> section found 246 if (global != null) 247 { 248 249 // get list of compilers 250 this.compilerSpec = global.getFirstChildNamed("compiler"); 251 252 if (this.compilerSpec != null) 253 { 254 readChoices(this.compilerSpec, this.compilerList); 255 } 256 257 this.compilerArgumentsSpec = global.getFirstChildNamed("arguments"); 258 259 if (this.compilerArgumentsSpec != null) 260 { 261 // basicly perform sanity check 262 readChoices(this.compilerArgumentsSpec, this.compilerArgumentsList); 263 } 264 265 } 266 267 // supply default values if no useful ones where found 268 if (this.compilerList.size() == 0) 269 { 270 this.compilerList.add("javac"); 271 this.compilerList.add("jikes"); 272 } 273 274 if (this.compilerArgumentsList.size() == 0) 275 { 276 this.compilerArgumentsList.add("-O -g:none"); 277 this.compilerArgumentsList.add("-O"); 278 this.compilerArgumentsList.add("-g"); 279 this.compilerArgumentsList.add(""); 280 } 281 282 return true; 283 } 284 285 // helper function 286 private void readChoices(XMLElement element, ArrayList result) 287 { 288 Vector choices = element.getChildrenNamed("choice"); 289 290 if (choices == null) return; 291 292 result.clear(); 293 294 Iterator choice_it = choices.iterator(); 295 296 while (choice_it.hasNext()) 297 { 298 XMLElement choice = (XMLElement) choice_it.next(); 299 300 String value = choice.getAttribute("value"); 301 302 if (value != null) 303 { 304 List osconstraints = OsConstraint.getOsList(choice); 305 306 if (OsConstraint.oneMatchesCurrentSystem(osconstraints)) 307 { 308 result.add(this.vs.substitute(value, "plain")); 309 } 310 } 311 312 } 313 314 } 315 316 /** 317 * Parse the compilation specification file and create jobs. 318 */ 319 private boolean collectJobs() throws Exception 320 { 321 XMLElement data = this.spec.getFirstChildNamed("jobs"); 322 323 if (data == null) return false; 324 325 // list of classpath entries 326 ArrayList classpath = new ArrayList(); 327 328 this.jobs = new ArrayList(); 329 330 // we throw away the toplevel compilation job 331 // (all jobs are collected in this.jobs) 332 collectJobsRecursive(data, classpath); 333 334 return true; 335 } 336 337 /** perform the actual compilation */ 338 private CompileResult compileJobs() 339 { 340 ArrayList args = new ArrayList(); 341 StringTokenizer tokenizer = new StringTokenizer(this.compilerArgumentsToUse); 342 343 while (tokenizer.hasMoreTokens()) 344 { 345 args.add(tokenizer.nextToken()); 346 } 347 348 Iterator job_it = this.jobs.iterator(); 349 350 this.handler.startAction("Compilation", this.jobs.size()); 351 352 // check whether compiler is valid (but only if there are jobs) 353 if (job_it.hasNext()) 354 { 355 CompilationJob first_job = (CompilationJob) this.jobs.get(0); 356 357 CompileResult check_result = first_job.checkCompiler(this.compilerToUse, args); 358 if (!check_result.isContinue()) { return check_result; } 359 360 } 361 362 int job_no = 0; 363 364 while (job_it.hasNext()) 365 { 366 CompilationJob job = (CompilationJob) job_it.next(); 367 368 this.handler.nextStep(job.getName(), job.getSize(), job_no++); 369 370 CompileResult result = job.perform(this.compilerToUse, args); 371 372 if (!result.isContinue()) return result; 373 } 374 375 Debug.trace("compilation finished."); 376 return new CompileResult(); 377 } 378 379 private CompilationJob collectJobsRecursive(XMLElement node, ArrayList classpath) 380 throws Exception 381 { 382 Enumeration toplevel_tags = node.enumerateChildren(); 383 ArrayList ourclasspath = (ArrayList) classpath.clone(); 384 ArrayList files = new ArrayList(); 385 386 while (toplevel_tags.hasMoreElements()) 387 { 388 XMLElement child = (XMLElement) toplevel_tags.nextElement(); 389 390 if (child.getName().equals("classpath")) 391 { 392 changeClassPath(ourclasspath, child); 393 } 394 else if (child.getName().equals("job")) 395 { 396 CompilationJob subjob = collectJobsRecursive(child, ourclasspath); 397 if (subjob != null) this.jobs.add(subjob); 398 } 399 else if (child.getName().equals("directory")) 400 { 401 String name = child.getAttribute("name"); 402 403 if (name != null) 404 { 405 // substitute variables 406 String finalname = this.vs.substitute(name, "plain"); 407 408 files.addAll(scanDirectory(new File(finalname))); 409 } 410 411 } 412 else if (child.getName().equals("file")) 413 { 414 String name = child.getAttribute("name"); 415 416 if (name != null) 417 { 418 // substitute variables 419 String finalname = this.vs.substitute(name, "plain"); 420 421 files.add(new File(finalname)); 422 } 423 424 } 425 else if (child.getName().equals("packdepency")) 426 { 427 String name = child.getAttribute("name"); 428 429 if (name == null) 430 { 431 System.out 432 .println("invalid compilation spec: <packdepency> without name attribute"); 433 return null; 434 } 435 436 // check whether the wanted pack was selected for installation 437 Iterator pack_it = this.idata.selectedPacks.iterator(); 438 boolean found = false; 439 440 while (pack_it.hasNext()) 441 { 442 com.izforge.izpack.Pack pack = (com.izforge.izpack.Pack) pack_it.next(); 443 444 if (pack.name.equals(name)) 445 { 446 found = true; 447 break; 448 } 449 } 450 451 if (!found) 452 { 453 Debug.trace("skipping job because pack " + name + " was not selected."); 454 return null; 455 } 456 457 } 458 459 } 460 461 if (files.size() > 0) 462 return new CompilationJob(this.handler, this.idata.langpack, (String) node 463 .getAttribute("name"), files, ourclasspath); 464 465 return null; 466 } 467 468 /** helper: process a <code><classpath></code> tag. */ 469 private void changeClassPath(ArrayList classpath, XMLElement child) throws Exception 470 { 471 String add = child.getAttribute("add"); 472 if (add != null) 473 { 474 add = this.vs.substitute(add, "plain"); 475 if (!new File(add).exists()) 476 { 477 if (!this.handler.emitWarning("Invalid classpath", "The path " + add 478 + " could not be found.\nCompilation may fail.")) 479 throw new Exception("Classpath " + add + " does not exist."); 480 } 481 else 482 { 483 classpath.add(this.vs.substitute(add, "plain")); 484 } 485 486 } 487 488 String sub = child.getAttribute("sub"); 489 if (sub != null) 490 { 491 int cpidx = -1; 492 sub = this.vs.substitute(sub, "plain"); 493 494 do 495 { 496 cpidx = classpath.indexOf(sub); 497 classpath.remove(cpidx); 498 } 499 while (cpidx >= 0); 500 501 } 502 503 } 504 505 /** 506 * helper: recursively scan given directory. 507 * 508 * @return list of files found (might be empty) 509 */ 510 private ArrayList scanDirectory(File path) 511 { 512 Debug.trace("scanning directory " + path.getAbsolutePath()); 513 514 ArrayList result = new ArrayList(); 515 516 if (!path.isDirectory()) return result; 517 518 File[] entries = path.listFiles(); 519 520 for (int i = 0; i < entries.length; i++) 521 { 522 File f = entries[i]; 523 524 if (f == null) continue; 525 526 if (f.isDirectory()) 527 { 528 result.addAll(scanDirectory(f)); 529 } 530 else if ((f.isFile()) && (f.getName().toLowerCase().endsWith(".java"))) 531 { 532 result.add(f); 533 } 534 535 } 536 537 return result; 538 } 539 540 /** a compilation job */ 541 private static class CompilationJob 542 { 543 544 private CompileHandler listener; 545 546 private String name; 547 548 private ArrayList files; 549 550 private ArrayList classpath; 551 552 private LocaleDatabase langpack; 553 554 // XXX: figure that out (on runtime?) 555 private static final int MAX_CMDLINE_SIZE = 4096; 556 557 public CompilationJob(CompileHandler listener, LocaleDatabase langpack, ArrayList files, 558 ArrayList classpath) 559 { 560 this.listener = listener; 561 this.langpack = langpack; 562 this.name = null; 563 this.files = files; 564 this.classpath = classpath; 565 } 566 567 public CompilationJob(CompileHandler listener, LocaleDatabase langpack, String name, 568 ArrayList files, ArrayList classpath) 569 { 570 this.listener = listener; 571 this.langpack = langpack; 572 this.name = name; 573 this.files = files; 574 this.classpath = classpath; 575 } 576 577 public String getName() 578 { 579 if (this.name != null) return this.name; 580 581 return ""; 582 } 583 584 public int getSize() 585 { 586 return this.files.size(); 587 } 588 589 public CompileResult perform(String compiler, ArrayList arguments) 590 { 591 Debug.trace("starting job " + this.name); 592 // we have some maximum command line length - need to count 593 int cmdline_len = 0; 594 595 // used to collect the arguments for executing the compiler 596 LinkedList args = new LinkedList(arguments); 597 598 { 599 Iterator arg_it = args.iterator(); 600 while (arg_it.hasNext()) 601 cmdline_len += ((String) arg_it.next()).length() + 1; 602 } 603 604 // add compiler in front of arguments 605 args.add(0, compiler); 606 cmdline_len += compiler.length() + 1; 607 608 // construct classpath argument for compiler 609 // - collect all classpaths 610 StringBuffer classpath_sb = new StringBuffer(); 611 Iterator cp_it = this.classpath.iterator(); 612 while (cp_it.hasNext()) 613 { 614 String cp = (String) cp_it.next(); 615 if (classpath_sb.length() > 0) classpath_sb.append(File.pathSeparatorChar); 616 classpath_sb.append(new File(cp).getAbsolutePath()); 617 } 618 619 String classpath_str = classpath_sb.toString(); 620 621 // - add classpath argument to command line 622 if (classpath_str.length() > 0) 623 { 624 args.add("-classpath"); 625 cmdline_len = cmdline_len + 11; 626 args.add(classpath_str); 627 cmdline_len += classpath_str.length() + 1; 628 } 629 630 // remember how many arguments we have which don't change for the 631 // job 632 int common_args_no = args.size(); 633 // remember how long the common command line is 634 int common_args_len = cmdline_len; 635 636 // used for execution 637 FileExecutor executor = new FileExecutor(); 638 String output[] = new String[2]; 639 640 // used for displaying the progress bar 641 String jobfiles = ""; 642 int fileno = 0; 643 int last_fileno = 0; 644 645 // now iterate over all files of this job 646 Iterator file_it = this.files.iterator(); 647 648 while (file_it.hasNext()) 649 { 650 File f = (File) file_it.next(); 651 652 String fpath = f.getAbsolutePath(); 653 654 Debug.trace("processing " + fpath); 655 656 // we add the file _first_ to the arguments to have a better 657 // chance to get something done if the command line is almost 658 // MAX_CMDLINE_SIZE or even above 659 fileno++; 660 jobfiles += f.getName() + " "; 661 args.add(fpath); 662 cmdline_len += fpath.length(); 663 664 // start compilation if maximum command line length reached 665 if (cmdline_len >= MAX_CMDLINE_SIZE) 666 { 667 Debug.trace("compiling " + jobfiles); 668 669 // display useful progress bar (avoid showing 100% while 670 // still 671 // compiling a lot) 672 this.listener.progress(last_fileno, jobfiles); 673 last_fileno = fileno; 674 675 String[] full_cmdline = (String[]) args.toArray(output); 676 677 int retval = executor.executeCommand(full_cmdline, output); 678 679 // update progress bar: compilation of fileno files done 680 this.listener.progress(fileno, jobfiles); 681 682 if (retval != 0) 683 { 684 CompileResult result = new CompileResult(this.langpack 685 .getString("CompilePanel.error"), full_cmdline, output[0], 686 output[1]); 687 this.listener.handleCompileError(result); 688 if (!result.isContinue()) return result; 689 } 690 else 691 { 692 // verify that all files have been compiled successfully 693 // I found that sometimes, no error code is returned 694 // although 695 // compilation failed. 696 Iterator arg_it = args.listIterator(common_args_no); 697 while (arg_it.hasNext()) 698 { 699 File java_file = new File((String) arg_it.next()); 700 701 String basename = java_file.getName(); 702 int dotpos = basename.lastIndexOf('.'); 703 basename = basename.substring(0, dotpos) + ".class"; 704 File class_file = new File(java_file.getParentFile(), basename); 705 706 if (!class_file.exists()) 707 { 708 CompileResult result = new CompileResult(this.langpack 709 .getString("CompilePanel.error.noclassfile") 710 + java_file.getAbsolutePath(), full_cmdline, output[0], 711 output[1]); 712 this.listener.handleCompileError(result); 713 if (!result.isContinue()) return result; 714 // don't continue any further 715 break; 716 } 717 718 } 719 720 } 721 722 // clean command line: remove files we just compiled 723 for (int i = args.size() - 1; i >= common_args_no; i--) 724 { 725 args.removeLast(); 726 } 727 728 cmdline_len = common_args_len; 729 jobfiles = ""; 730 } 731 732 } 733 734 if (cmdline_len > common_args_len) 735 { 736 this.listener.progress(last_fileno, jobfiles); 737 738 String[] full_cmdline = (String[]) args.toArray(output); 739 740 int retval = executor.executeCommand(full_cmdline, output); 741 742 this.listener.progress(fileno, jobfiles); 743 744 if (retval != 0) 745 { 746 CompileResult result = new CompileResult(this.langpack 747 .getString("CompilePanel.error"), full_cmdline, output[0], output[1]); 748 this.listener.handleCompileError(result); 749 if (!result.isContinue()) return result; 750 } 751 752 } 753 754 Debug.trace("job " + this.name + " done (" + fileno + " files compiled)"); 755 756 return new CompileResult(); 757 } 758 759 /** 760 * Check whether the given compiler works. 761 * 762 * This performs two steps: 763 * <ol> 764 * <li>check whether we can successfully call "compiler -help"</li> 765 * <li>check whether we can successfully call "compiler -help arguments" (not all compilers 766 * return an error here)</li> 767 * </ol> 768 * 769 * On failure, the method CompileHandler#errorCompile is called with a descriptive error 770 * message. 771 * 772 * @param compiler the compiler to use 773 * @param arguments additional arguments to pass to the compiler 774 * @return false on error 775 */ 776 public CompileResult checkCompiler(String compiler, ArrayList arguments) 777 { 778 int retval = 0; 779 FileExecutor executor = new FileExecutor(); 780 String[] output = new String[2]; 781 782 Debug.trace("checking whether \"" + compiler + " -help\" works"); 783 784 { 785 String[] args = { compiler, "-help"}; 786 787 retval = executor.executeCommand(args, output); 788 789 if (retval != 0) 790 { 791 CompileResult result = new CompileResult(this.langpack 792 .getString("CompilePanel.error.compilernotfound"), args, output[0], 793 output[1]); 794 this.listener.handleCompileError(result); 795 if (!result.isContinue()) return result; 796 } 797 } 798 799 Debug.trace("checking whether \"" + compiler + " -help +arguments\" works"); 800 801 // used to collect the arguments for executing the compiler 802 LinkedList args = new LinkedList(arguments); 803 804 // add -help argument to prevent the compiler from doing anything 805 args.add(0, "-help"); 806 807 // add compiler in front of arguments 808 args.add(0, compiler); 809 810 // construct classpath argument for compiler 811 // - collect all classpaths 812 StringBuffer classpath_sb = new StringBuffer(); 813 Iterator cp_it = this.classpath.iterator(); 814 while (cp_it.hasNext()) 815 { 816 String cp = (String) cp_it.next(); 817 if (classpath_sb.length() > 0) classpath_sb.append(File.pathSeparatorChar); 818 classpath_sb.append(new File(cp).getAbsolutePath()); 819 } 820 821 String classpath_str = classpath_sb.toString(); 822 823 // - add classpath argument to command line 824 if (classpath_str.length() > 0) 825 { 826 args.add("-classpath"); 827 args.add(classpath_str); 828 } 829 830 String[] args_arr = (String[]) args.toArray(output); 831 832 retval = executor.executeCommand(args_arr, output); 833 834 if (retval != 0) 835 { 836 CompileResult result = new CompileResult(this.langpack 837 .getString("CompilePanel.error.invalidarguments"), args_arr, output[0], 838 output[1]); 839 this.listener.handleCompileError(result); 840 if (!result.isContinue()) return result; 841 } 842 843 return new CompileResult(); 844 } 845 846 } 847 848}