0001: /*
0002: * IzPack - Copyright 2001-2008 Julien Ponge, All Rights Reserved.
0003: *
0004: * http://izpack.org/
0005: * http://izpack.codehaus.org/
0006: *
0007: * Copyright 2003 Tino Schwarze
0008: *
0009: * Licensed under the Apache License, Version 2.0 (the "License");
0010: * you may not use this file except in compliance with the License.
0011: * You may obtain a copy of the License at
0012: *
0013: * http://www.apache.org/licenses/LICENSE-2.0
0014: *
0015: * Unless required by applicable law or agreed to in writing, software
0016: * distributed under the License is distributed on an "AS IS" BASIS,
0017: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0018: * See the License for the specific language governing permissions and
0019: * limitations under the License.
0020: */
0021:
0022: package com.izforge.izpack.installer;
0023:
0024: import java.io.ByteArrayOutputStream;
0025: import java.io.File;
0026: import java.io.FileNotFoundException;
0027: import java.io.FileOutputStream;
0028: import java.io.IOException;
0029: import java.io.InputStream;
0030: import java.io.OutputStream;
0031: import java.io.PrintStream;
0032: import java.lang.reflect.InvocationTargetException;
0033: import java.lang.reflect.Method;
0034: import java.util.ArrayList;
0035: import java.util.Enumeration;
0036: import java.util.Iterator;
0037: import java.util.LinkedList;
0038: import java.util.List;
0039: import java.util.StringTokenizer;
0040: import java.util.Vector;
0041:
0042: import net.n3.nanoxml.NonValidator;
0043: import net.n3.nanoxml.StdXMLParser;
0044: import net.n3.nanoxml.StdXMLReader;
0045: import net.n3.nanoxml.XMLElement;
0046: import net.n3.nanoxml.XMLBuilderFactory;
0047:
0048: import com.izforge.izpack.LocaleDatabase;
0049: import com.izforge.izpack.util.Debug;
0050: import com.izforge.izpack.util.FileExecutor;
0051: import com.izforge.izpack.util.OsConstraint;
0052: import com.izforge.izpack.util.VariableSubstitutor;
0053:
0054: /**
0055: * This class does alle the work for compiling sources.
0056: *
0057: * It responsible for
0058: * <ul>
0059: * <li>parsing the compilation spec XML file
0060: * <li>collecting and creating all jobs
0061: * <li>doing the actual compilation
0062: * </ul>
0063: *
0064: * @author Tino Schwarze
0065: */
0066: public class CompileWorker implements Runnable {
0067:
0068: /** Compilation jobs */
0069: private ArrayList<CompilationJob> jobs;
0070:
0071: /** Name of resource for specifying compilation parameters. */
0072: private static final String SPEC_RESOURCE_NAME = "CompilePanel.Spec.xml";
0073:
0074: private static final String ECLIPSE_COMPILER_NAME = "Integrated Eclipse JDT Compiler";
0075:
0076: private static final String ECLIPSE_COMPILER_CLASS = "org.eclipse.jdt.internal.compiler.batch.Main";
0077:
0078: private VariableSubstitutor vs;
0079:
0080: private XMLElement spec;
0081:
0082: private AutomatedInstallData idata;
0083:
0084: private CompileHandler handler;
0085:
0086: private XMLElement compilerSpec;
0087:
0088: private ArrayList<String> compilerList;
0089:
0090: private String compilerToUse;
0091:
0092: private XMLElement compilerArgumentsSpec;
0093:
0094: private ArrayList<String> compilerArgumentsList;
0095:
0096: private String compilerArgumentsToUse;
0097:
0098: private CompileResult result = null;
0099:
0100: /**
0101: * The constructor.
0102: *
0103: * @param idata The installation data.
0104: * @param handler The handler to notify of progress.
0105: *
0106: * @throws IOException
0107: */
0108: public CompileWorker(AutomatedInstallData idata,
0109: CompileHandler handler) throws IOException {
0110: this .idata = idata;
0111: this .handler = handler;
0112: this .vs = new VariableSubstitutor(idata.getVariables());
0113: if (!readSpec())
0114: throw new IOException(
0115: "Error reading compilation specification");
0116: }
0117:
0118: /**
0119: * Return list of compilers to choose from.
0120: *
0121: * @return ArrayList of String
0122: */
0123: public ArrayList<String> getAvailableCompilers() {
0124: readChoices(this .compilerSpec, this .compilerList);
0125: return this .compilerList;
0126: }
0127:
0128: /**
0129: * Set the compiler to use.
0130: *
0131: * The compiler is checked before compilation starts.
0132: *
0133: * @param compiler compiler to use (not checked)
0134: */
0135: public void setCompiler(String compiler) {
0136: this .compilerToUse = compiler;
0137: }
0138:
0139: /**
0140: * Get the compiler used.
0141: *
0142: * @return the compiler.
0143: */
0144: public String getCompiler() {
0145: return this .compilerToUse;
0146: }
0147:
0148: /**
0149: * Return list of compiler arguments to choose from.
0150: *
0151: * @return ArrayList of String
0152: */
0153: public ArrayList<String> getAvailableArguments() {
0154: readChoices(this .compilerArgumentsSpec,
0155: this .compilerArgumentsList);
0156: return this .compilerArgumentsList;
0157: }
0158:
0159: /**
0160: * Set the compiler arguments to use.
0161: *
0162: * @param arguments The argument to use.
0163: */
0164: public void setCompilerArguments(String arguments) {
0165: this .compilerArgumentsToUse = arguments;
0166: }
0167:
0168: /**
0169: * Get the compiler arguments used.
0170: *
0171: * @return The arguments used for compiling.
0172: */
0173: public String getCompilerArguments() {
0174: return this .compilerArgumentsToUse;
0175: }
0176:
0177: /**
0178: * Get the result of the compilation.
0179: *
0180: * @return The result.
0181: */
0182: public CompileResult getResult() {
0183: return this .result;
0184: }
0185:
0186: /** Start the compilation in a separate thread. */
0187: public void startThread() {
0188: Thread compilationThread = new Thread(this ,
0189: "compilation thread");
0190: // will call this.run()
0191: compilationThread.start();
0192: }
0193:
0194: /**
0195: * This is called when the compilation thread is activated.
0196: *
0197: * Can also be called directly if asynchronous processing is not desired.
0198: */
0199: public void run() {
0200: try {
0201: if (!collectJobs()) {
0202: List<String> args = new ArrayList<String>();
0203: args.add("nothing to do");
0204:
0205: this .result = new CompileResult(this .idata.langpack
0206: .getString("CompilePanel.worker.nofiles"),
0207: args, "", "");
0208: } else {
0209: this .result = compileJobs();
0210: }
0211: } catch (Exception e) {
0212: this .result = new CompileResult(e);
0213: }
0214:
0215: this .handler.stopAction();
0216: }
0217:
0218: private boolean readSpec() {
0219: InputStream input;
0220: try {
0221: input = ResourceManager.getInstance().getInputStream(
0222: SPEC_RESOURCE_NAME);
0223: } catch (Exception e) {
0224: e.printStackTrace();
0225: return false;
0226: }
0227:
0228: StdXMLParser parser = new StdXMLParser();
0229: parser.setBuilder(XMLBuilderFactory.createXMLBuilder());
0230: parser.setValidator(new NonValidator());
0231:
0232: try {
0233: parser.setReader(new StdXMLReader(input));
0234:
0235: this .spec = (XMLElement) parser.parse();
0236: } catch (Exception e) {
0237: System.out
0238: .println("Error parsing XML specification for compilation.");
0239: e.printStackTrace();
0240: return false;
0241: }
0242:
0243: if (!this .spec.hasChildren())
0244: return false;
0245:
0246: this .compilerArgumentsList = new ArrayList<String>();
0247: this .compilerList = new ArrayList<String>();
0248:
0249: // read <global> information
0250: XMLElement global = this .spec.getFirstChildNamed("global");
0251:
0252: // use some default values if no <global> section found
0253: if (global != null) {
0254:
0255: // get list of compilers
0256: this .compilerSpec = global.getFirstChildNamed("compiler");
0257:
0258: if (this .compilerSpec != null) {
0259: readChoices(this .compilerSpec, this .compilerList);
0260: }
0261:
0262: this .compilerArgumentsSpec = global
0263: .getFirstChildNamed("arguments");
0264:
0265: if (this .compilerArgumentsSpec != null) {
0266: // basicly perform sanity check
0267: readChoices(this .compilerArgumentsSpec,
0268: this .compilerArgumentsList);
0269: }
0270:
0271: }
0272:
0273: // supply default values if no useful ones where found
0274: if (this .compilerList.size() == 0) {
0275: this .compilerList.add("javac");
0276: this .compilerList.add("jikes");
0277: }
0278:
0279: if (this .compilerArgumentsList.size() == 0) {
0280: this .compilerArgumentsList.add("-O -g:none");
0281: this .compilerArgumentsList.add("-O");
0282: this .compilerArgumentsList.add("-g");
0283: this .compilerArgumentsList.add("");
0284: }
0285:
0286: return true;
0287: }
0288:
0289: // helper function
0290: private void readChoices(XMLElement element,
0291: ArrayList<String> choiceList) {
0292: Vector<XMLElement> choices = element.getChildrenNamed("choice");
0293:
0294: if (choices == null)
0295: return;
0296:
0297: choiceList.clear();
0298:
0299: Iterator<XMLElement> choice_it = choices.iterator();
0300:
0301: while (choice_it.hasNext()) {
0302: XMLElement choice = choice_it.next();
0303:
0304: String value = choice.getAttribute("value");
0305:
0306: if (value != null) {
0307: List<OsConstraint> osconstraints = OsConstraint
0308: .getOsList(choice);
0309:
0310: if (OsConstraint.oneMatchesCurrentSystem(osconstraints)) {
0311: if (value.equalsIgnoreCase(ECLIPSE_COMPILER_NAME)) {
0312: // check for availability of eclipse compiler
0313: try {
0314: Class.forName(ECLIPSE_COMPILER_CLASS);
0315: choiceList.add(value);
0316: } catch (ExceptionInInitializerError eiie) {
0317: // ignore, just don't add it as a choice
0318: } catch (ClassNotFoundException cnfe) {
0319: // ignore, just don't add it as a choice
0320: }
0321: } else
0322: choiceList.add(this .vs.substitute(value,
0323: "plain"));
0324: }
0325: }
0326:
0327: }
0328:
0329: }
0330:
0331: /**
0332: * Parse the compilation specification file and create jobs.
0333: */
0334: private boolean collectJobs() throws Exception {
0335: XMLElement data = this .spec.getFirstChildNamed("jobs");
0336:
0337: if (data == null)
0338: return false;
0339:
0340: // list of classpath entries
0341: ArrayList classpath = new ArrayList();
0342:
0343: this .jobs = new ArrayList<CompilationJob>();
0344:
0345: // we throw away the toplevel compilation job
0346: // (all jobs are collected in this.jobs)
0347: collectJobsRecursive(data, classpath);
0348:
0349: return true;
0350: }
0351:
0352: /** perform the actual compilation */
0353: private CompileResult compileJobs() {
0354: ArrayList<String> args = new ArrayList<String>();
0355: StringTokenizer tokenizer = new StringTokenizer(
0356: this .compilerArgumentsToUse);
0357:
0358: while (tokenizer.hasMoreTokens()) {
0359: args.add(tokenizer.nextToken());
0360: }
0361:
0362: Iterator<CompilationJob> job_it = this .jobs.iterator();
0363:
0364: this .handler.startAction("Compilation", this .jobs.size());
0365:
0366: // check whether compiler is valid (but only if there are jobs)
0367: if (job_it.hasNext()) {
0368: CompilationJob first_job = this .jobs.get(0);
0369:
0370: CompileResult check_result = first_job.checkCompiler(
0371: this .compilerToUse, args);
0372: if (!check_result.isContinue()) {
0373: return check_result;
0374: }
0375:
0376: }
0377:
0378: int job_no = 0;
0379:
0380: while (job_it.hasNext()) {
0381: CompilationJob job = job_it.next();
0382:
0383: this .handler.nextStep(job.getName(), job.getSize(),
0384: job_no++);
0385:
0386: CompileResult job_result = job.perform(this .compilerToUse,
0387: args);
0388:
0389: if (!job_result.isContinue())
0390: return job_result;
0391: }
0392:
0393: Debug.trace("compilation finished.");
0394: return new CompileResult();
0395: }
0396:
0397: private CompilationJob collectJobsRecursive(XMLElement node,
0398: ArrayList classpath) throws Exception {
0399: Enumeration toplevel_tags = node.enumerateChildren();
0400: ArrayList ourclasspath = (ArrayList) classpath.clone();
0401: ArrayList<File> files = new ArrayList<File>();
0402:
0403: while (toplevel_tags.hasMoreElements()) {
0404: XMLElement child = (XMLElement) toplevel_tags.nextElement();
0405:
0406: if ("classpath".equals(child.getName())) {
0407: changeClassPath(ourclasspath, child);
0408: } else if ("job".equals(child.getName())) {
0409: CompilationJob subjob = collectJobsRecursive(child,
0410: ourclasspath);
0411: if (subjob != null)
0412: this .jobs.add(subjob);
0413: } else if ("directory".equals(child.getName())) {
0414: String name = child.getAttribute("name");
0415:
0416: if (name != null) {
0417: // substitute variables
0418: String finalname = this .vs
0419: .substitute(name, "plain");
0420:
0421: files.addAll(scanDirectory(new File(finalname)));
0422: }
0423:
0424: } else if ("file".equals(child.getName())) {
0425: String name = child.getAttribute("name");
0426:
0427: if (name != null) {
0428: // substitute variables
0429: String finalname = this .vs
0430: .substitute(name, "plain");
0431:
0432: files.add(new File(finalname));
0433: }
0434:
0435: } else if ("packdepency".equals(child.getName())) {
0436: String name = child.getAttribute("name");
0437:
0438: if (name == null) {
0439: System.out
0440: .println("invalid compilation spec: <packdepency> without name attribute");
0441: return null;
0442: }
0443:
0444: // check whether the wanted pack was selected for installation
0445: Iterator pack_it = this .idata.selectedPacks.iterator();
0446: boolean found = false;
0447:
0448: while (pack_it.hasNext()) {
0449: com.izforge.izpack.Pack pack = (com.izforge.izpack.Pack) pack_it
0450: .next();
0451:
0452: if (pack.name.equals(name)) {
0453: found = true;
0454: break;
0455: }
0456: }
0457:
0458: if (!found) {
0459: Debug.trace("skipping job because pack " + name
0460: + " was not selected.");
0461: return null;
0462: }
0463:
0464: }
0465:
0466: }
0467:
0468: if (files.size() > 0)
0469: return new CompilationJob(this .handler, this .idata, node
0470: .getAttribute("name"), files, ourclasspath);
0471:
0472: return null;
0473: }
0474:
0475: /** helper: process a <code><classpath></code> tag. */
0476: private void changeClassPath(ArrayList classpath, XMLElement child)
0477: throws Exception {
0478: String add = child.getAttribute("add");
0479: if (add != null) {
0480: add = this .vs.substitute(add, "plain");
0481: if (!new File(add).exists()) {
0482: if (!this .handler
0483: .emitWarning(
0484: "Invalid classpath",
0485: "The path "
0486: + add
0487: + " could not be found.\nCompilation may fail."))
0488: throw new Exception("Classpath " + add
0489: + " does not exist.");
0490: } else {
0491: classpath.add(this .vs.substitute(add, "plain"));
0492: }
0493:
0494: }
0495:
0496: String sub = child.getAttribute("sub");
0497: if (sub != null) {
0498: int cpidx = -1;
0499: sub = this .vs.substitute(sub, "plain");
0500:
0501: do {
0502: cpidx = classpath.indexOf(sub);
0503: classpath.remove(cpidx);
0504: } while (cpidx >= 0);
0505:
0506: }
0507:
0508: }
0509:
0510: /**
0511: * helper: recursively scan given directory.
0512: *
0513: * @return list of files found (might be empty)
0514: */
0515: private ArrayList<File> scanDirectory(File path) {
0516: Debug.trace("scanning directory " + path.getAbsolutePath());
0517:
0518: ArrayList<File> scan_result = new ArrayList<File>();
0519:
0520: if (!path.isDirectory())
0521: return scan_result;
0522:
0523: File[] entries = path.listFiles();
0524:
0525: for (File f : entries) {
0526: if (f == null) {
0527: continue;
0528: }
0529:
0530: if (f.isDirectory()) {
0531: scan_result.addAll(scanDirectory(f));
0532: } else if ((f.isFile())
0533: && (f.getName().toLowerCase().endsWith(".java"))) {
0534: scan_result.add(f);
0535: }
0536:
0537: }
0538:
0539: return scan_result;
0540: }
0541:
0542: /** a compilation job */
0543: private static class CompilationJob {
0544:
0545: private CompileHandler listener;
0546:
0547: private String name;
0548:
0549: private ArrayList<File> files;
0550:
0551: private ArrayList classpath;
0552:
0553: private LocaleDatabase langpack;
0554:
0555: private AutomatedInstallData idata;
0556:
0557: // XXX: figure that out (on runtime?)
0558: private static final int MAX_CMDLINE_SIZE = 4096;
0559:
0560: /**
0561: * Construct new compilation job.
0562: *
0563: * @param listener The listener to report progress to.
0564: * @param idata The installation data.
0565: * @param name The name of the job.
0566: * @param files The files to compile.
0567: * @param classpath The class path to use.
0568: */
0569: public CompilationJob(CompileHandler listener,
0570: AutomatedInstallData idata, String name,
0571: ArrayList<File> files, ArrayList classpath) {
0572: this .listener = listener;
0573: this .idata = idata;
0574: this .langpack = idata.langpack;
0575: this .name = name;
0576: this .files = files;
0577: this .classpath = classpath;
0578: }
0579:
0580: /**
0581: * Get the name of the job.
0582: *
0583: * @return The name or an empty string if there is no name.
0584: */
0585: public String getName() {
0586: if (this .name != null)
0587: return this .name;
0588:
0589: return "";
0590: }
0591:
0592: /**
0593: * Get the number of files in this job.
0594: *
0595: * @return The number of files to compile.
0596: */
0597: public int getSize() {
0598: return this .files.size();
0599: }
0600:
0601: /**
0602: * Perform this job - start compilation.
0603: *
0604: * @param compiler The compiler to use.
0605: * @param arguments The compiler arguments to use.
0606: * @return The result.
0607: */
0608: public CompileResult perform(String compiler,
0609: ArrayList<String> arguments) {
0610: Debug.trace("starting job " + this .name);
0611: // we have some maximum command line length - need to count
0612: int cmdline_len = 0;
0613:
0614: // used to collect the arguments for executing the compiler
0615: LinkedList<String> args = new LinkedList<String>(arguments);
0616:
0617: {
0618: Iterator<String> arg_it = args.iterator();
0619: while (arg_it.hasNext())
0620: cmdline_len += (arg_it.next()).length() + 1;
0621: }
0622:
0623: boolean isEclipseCompiler = compiler
0624: .equalsIgnoreCase(ECLIPSE_COMPILER_NAME);
0625:
0626: // add compiler in front of arguments
0627: args.add(0, compiler);
0628: cmdline_len += compiler.length() + 1;
0629:
0630: // construct classpath argument for compiler
0631: // - collect all classpaths
0632: StringBuffer classpath_sb = new StringBuffer();
0633: Iterator cp_it = this .classpath.iterator();
0634: while (cp_it.hasNext()) {
0635: String cp = (String) cp_it.next();
0636: if (classpath_sb.length() > 0)
0637: classpath_sb.append(File.pathSeparatorChar);
0638: classpath_sb.append(new File(cp).getAbsolutePath());
0639: }
0640:
0641: String classpath_str = classpath_sb.toString();
0642:
0643: // - add classpath argument to command line
0644: if (classpath_str.length() > 0) {
0645: args.add("-classpath");
0646: cmdline_len += 11;
0647: args.add(classpath_str);
0648: cmdline_len += classpath_str.length() + 1;
0649: }
0650:
0651: // remember how many arguments we have which don't change for the
0652: // job
0653: int common_args_no = args.size();
0654: // remember how long the common command line is
0655: int common_args_len = cmdline_len;
0656:
0657: // used for execution
0658: FileExecutor executor = new FileExecutor();
0659: String output[] = new String[2];
0660:
0661: // used for displaying the progress bar
0662: String jobfiles = "";
0663: int fileno = 0;
0664: int last_fileno = 0;
0665:
0666: // now iterate over all files of this job
0667: Iterator<File> file_it = this .files.iterator();
0668:
0669: while (file_it.hasNext()) {
0670: File f = file_it.next();
0671:
0672: String fpath = f.getAbsolutePath();
0673:
0674: Debug.trace("processing " + fpath);
0675:
0676: // we add the file _first_ to the arguments to have a better
0677: // chance to get something done if the command line is almost
0678: // MAX_CMDLINE_SIZE or even above
0679: fileno++;
0680: jobfiles += f.getName() + " ";
0681: args.add(fpath);
0682: cmdline_len += fpath.length();
0683:
0684: // start compilation if maximum command line length reached
0685: if (!isEclipseCompiler
0686: && cmdline_len >= MAX_CMDLINE_SIZE) {
0687: Debug.trace("compiling " + jobfiles);
0688:
0689: // display useful progress bar (avoid showing 100% while
0690: // still compiling a lot)
0691: this .listener.progress(last_fileno, jobfiles);
0692: last_fileno = fileno;
0693:
0694: int retval = runCompiler(executor, output, args);
0695:
0696: // update progress bar: compilation of fileno files done
0697: this .listener.progress(fileno, jobfiles);
0698:
0699: if (retval != 0) {
0700: CompileResult result = new CompileResult(
0701: this .langpack
0702: .getString("CompilePanel.error"),
0703: args, output[0], output[1]);
0704: this .listener.handleCompileError(result);
0705: if (!result.isContinue())
0706: return result;
0707: } else {
0708: // verify that all files have been compiled successfully
0709: // I found that sometimes, no error code is returned
0710: // although compilation failed.
0711: Iterator<String> arg_it = args
0712: .listIterator(common_args_no);
0713: while (arg_it.hasNext()) {
0714: File java_file = new File(arg_it.next());
0715:
0716: String basename = java_file.getName();
0717: int dotpos = basename.lastIndexOf('.');
0718: basename = basename.substring(0, dotpos)
0719: + ".class";
0720: File class_file = new File(java_file
0721: .getParentFile(), basename);
0722:
0723: if (!class_file.exists()) {
0724: CompileResult result = new CompileResult(
0725: this .langpack
0726: .getString("CompilePanel.error.noclassfile")
0727: + java_file
0728: .getAbsolutePath(),
0729: args, output[0], output[1]);
0730: this .listener
0731: .handleCompileError(result);
0732: if (!result.isContinue())
0733: return result;
0734: // don't continue any further
0735: break;
0736: }
0737:
0738: }
0739:
0740: }
0741:
0742: // clean command line: remove files we just compiled
0743: for (int i = args.size() - 1; i >= common_args_no; i--) {
0744: args.removeLast();
0745: }
0746:
0747: cmdline_len = common_args_len;
0748: jobfiles = "";
0749: }
0750:
0751: }
0752:
0753: if (cmdline_len > common_args_len) {
0754: this .listener.progress(last_fileno, jobfiles);
0755:
0756: int retval = runCompiler(executor, output, args);
0757:
0758: if (!isEclipseCompiler)
0759: this .listener.progress(fileno, jobfiles);
0760:
0761: if (retval != 0) {
0762: CompileResult result = new CompileResult(
0763: this .langpack
0764: .getString("CompilePanel.error"),
0765: args, output[0], output[1]);
0766: this .listener.handleCompileError(result);
0767: if (!result.isContinue())
0768: return result;
0769: } else {
0770: // verify that all files have been compiled successfully
0771: // I found that sometimes, no error code is returned
0772: // although compilation failed.
0773: Iterator<String> arg_it = args
0774: .listIterator(common_args_no);
0775: while (arg_it.hasNext()) {
0776: File java_file = new File(arg_it.next());
0777:
0778: String basename = java_file.getName();
0779: int dotpos = basename.lastIndexOf('.');
0780: basename = basename.substring(0, dotpos)
0781: + ".class";
0782: File class_file = new File(java_file
0783: .getParentFile(), basename);
0784:
0785: if (!class_file.exists()) {
0786: CompileResult result = new CompileResult(
0787: this .langpack
0788: .getString("CompilePanel.error.noclassfile")
0789: + java_file
0790: .getAbsolutePath(),
0791: args, output[0], output[1]);
0792: this .listener.handleCompileError(result);
0793: if (!result.isContinue())
0794: return result;
0795: // don't continue any further
0796: break;
0797: }
0798:
0799: }
0800:
0801: }
0802:
0803: }
0804:
0805: Debug.trace("job " + this .name + " done (" + fileno
0806: + " files compiled)");
0807:
0808: return new CompileResult();
0809: }
0810:
0811: /**
0812: * Internal helper method.
0813: *
0814: * @param executor The executor, only used when using external compiler.
0815: * @param output The output from the compiler ([0] = stdout, [1] = stderr)
0816: * @return The result of the compilation.
0817: */
0818: private int runCompiler(FileExecutor executor, String[] output,
0819: List<String> cmdline) {
0820: if (cmdline.get(0).equals(ECLIPSE_COMPILER_NAME))
0821: return runEclipseCompiler(output, cmdline);
0822:
0823: return executor.executeCommand((String[]) cmdline
0824: .toArray(new String[cmdline.size()]), output);
0825: }
0826:
0827: private int runEclipseCompiler(String[] output,
0828: List<String> cmdline) {
0829: try {
0830: List<String> final_cmdline = new LinkedList<String>(
0831: cmdline);
0832:
0833: // remove compiler name from argument list
0834: final_cmdline.remove(0);
0835:
0836: Class eclipseCompiler = Class
0837: .forName(ECLIPSE_COMPILER_CLASS);
0838:
0839: Method compileMethod = eclipseCompiler.getMethod(
0840: "main", new Class[] { String[].class });
0841:
0842: final_cmdline.add(0, "-noExit");
0843: final_cmdline.add(0, "-progress");
0844: final_cmdline.add(0, "-verbose");
0845:
0846: File _logfile = new File(this .idata.getInstallPath(),
0847: "compile-" + getName() + ".log");
0848:
0849: if (Debug.isTRACE()) {
0850: final_cmdline.add(0, _logfile.getPath());
0851: final_cmdline.add(0, "-log");
0852: }
0853:
0854: // get log files / determine results...
0855: try {
0856: // capture stdout and stderr
0857: PrintStream _orgStdout = System.out;
0858: PrintStream _orgStderr = System.err;
0859: int error_count = 0;
0860:
0861: try {
0862: ByteArrayOutputStream outStream = new ByteArrayOutputStream();
0863: EclipseStdOutHandler ownStdout = new EclipseStdOutHandler(
0864: outStream, this .listener);
0865: System.setOut(ownStdout);
0866: ByteArrayOutputStream errStream = new ByteArrayOutputStream();
0867: EclipseStdErrHandler ownStderr = new EclipseStdErrHandler(
0868: errStream, this .listener);
0869: System.setErr(ownStderr);
0870:
0871: compileMethod
0872: .invoke(
0873: null,
0874: new Object[] { final_cmdline
0875: .toArray(new String[final_cmdline
0876: .size()]) });
0877:
0878: // TODO: launch thread which updates the progress
0879: output[0] = outStream.toString();
0880: output[1] = errStream.toString();
0881: error_count = ownStderr.getErrorCount();
0882: // for debugging: write output to log files
0883: if (error_count > 0 || Debug.isTRACE()) {
0884: File _out = new File(_logfile.getPath()
0885: + ".stdout");
0886: FileOutputStream _fout = new FileOutputStream(
0887: _out);
0888: _fout.write(outStream.toByteArray());
0889: _fout.close();
0890: _out = new File(_logfile.getPath()
0891: + ".stderr");
0892: _fout = new FileOutputStream(_out);
0893: _fout.write(errStream.toByteArray());
0894: _fout.close();
0895: }
0896:
0897: } finally {
0898: System.setOut(_orgStdout);
0899: System.setErr(_orgStderr);
0900: }
0901:
0902: if (error_count == 0)
0903: return 0;
0904:
0905: // TODO: construct human readable error message from log
0906: this .listener.emitNotification("Compiler reported "
0907: + error_count + " errors");
0908:
0909: return 1;
0910: } catch (FileNotFoundException fnfe) {
0911: this .listener.emitError("error compiling", fnfe
0912: .getMessage());
0913: return -1;
0914: } catch (IOException ioe) {
0915: this .listener.emitError("error compiling", ioe
0916: .getMessage());
0917: return -1;
0918: }
0919:
0920: } catch (ClassNotFoundException cnfe) {
0921: output[0] = "error getting eclipse compiler";
0922: output[1] = cnfe.getMessage();
0923: return -1;
0924: } catch (NoSuchMethodException nsme) {
0925: output[0] = "error getting eclipse compiler method";
0926: output[1] = nsme.getMessage();
0927: return -1;
0928: } catch (IllegalAccessException iae) {
0929: output[0] = "error calling eclipse compiler";
0930: output[1] = iae.getMessage();
0931: return -1;
0932: } catch (InvocationTargetException ite) {
0933: output[0] = "error calling eclipse compiler";
0934: output[1] = ite.getMessage();
0935: return -1;
0936: }
0937:
0938: }
0939:
0940: /**
0941: * Check whether the given compiler works.
0942: *
0943: * This performs two steps:
0944: * <ol>
0945: * <li>check whether we can successfully call "compiler -help"</li>
0946: * <li>check whether we can successfully call "compiler -help arguments" (not all compilers
0947: * return an error here)</li>
0948: * </ol>
0949: *
0950: * On failure, the method CompileHandler#errorCompile is called with a descriptive error
0951: * message.
0952: *
0953: * @param compiler the compiler to use
0954: * @param arguments additional arguments to pass to the compiler
0955: * @return false on error
0956: */
0957: public CompileResult checkCompiler(String compiler,
0958: ArrayList<String> arguments) {
0959: // don't do further checks for eclipse compiler - it would exit
0960: if (compiler.equalsIgnoreCase(ECLIPSE_COMPILER_NAME))
0961: return new CompileResult();
0962:
0963: int retval = 0;
0964: FileExecutor executor = new FileExecutor();
0965: String[] output = new String[2];
0966:
0967: Debug.trace("checking whether \"" + compiler
0968: + " -help\" works");
0969:
0970: {
0971: List<String> args = new ArrayList<String>();
0972: args.add(compiler);
0973: args.add("-help");
0974:
0975: retval = runCompiler(executor, output, args);
0976:
0977: if (retval != 0) {
0978: CompileResult result = new CompileResult(
0979: this .langpack
0980: .getString("CompilePanel.error.compilernotfound"),
0981: args, output[0], output[1]);
0982: this .listener.handleCompileError(result);
0983: if (!result.isContinue())
0984: return result;
0985: }
0986: }
0987:
0988: Debug.trace("checking whether \"" + compiler
0989: + " -help +arguments\" works");
0990:
0991: // used to collect the arguments for executing the compiler
0992: LinkedList<String> args = new LinkedList<String>(arguments);
0993:
0994: // add -help argument to prevent the compiler from doing anything
0995: args.add(0, "-help");
0996:
0997: // add compiler in front of arguments
0998: args.add(0, compiler);
0999:
1000: // construct classpath argument for compiler
1001: // - collect all classpaths
1002: StringBuffer classpath_sb = new StringBuffer();
1003: Iterator cp_it = this .classpath.iterator();
1004: while (cp_it.hasNext()) {
1005: String cp = (String) cp_it.next();
1006: if (classpath_sb.length() > 0)
1007: classpath_sb.append(File.pathSeparatorChar);
1008: classpath_sb.append(new File(cp).getAbsolutePath());
1009: }
1010:
1011: String classpath_str = classpath_sb.toString();
1012:
1013: // - add classpath argument to command line
1014: if (classpath_str.length() > 0) {
1015: args.add("-classpath");
1016: args.add(classpath_str);
1017: }
1018:
1019: retval = runCompiler(executor, output, args);
1020:
1021: if (retval != 0) {
1022: CompileResult result = new CompileResult(
1023: this .langpack
1024: .getString("CompilePanel.error.invalidarguments"),
1025: args, output[0], output[1]);
1026: this .listener.handleCompileError(result);
1027: if (!result.isContinue())
1028: return result;
1029: }
1030:
1031: return new CompileResult();
1032: }
1033:
1034: }
1035:
1036: /**
1037: * This PrintStream is used to track the Eclipse compiler output.
1038: *
1039: * It will pass on all println requests and report progress to the listener.
1040: */
1041: private static class EclipseStdOutHandler extends PrintStream {
1042: private CompileHandler listener;
1043: private StdOutParser parser;
1044:
1045: /**
1046: * Default constructor.
1047: *
1048: * @param anOutputStream The stream to wrap.
1049: * @param aHandler the handler to use.
1050: */
1051: public EclipseStdOutHandler(final OutputStream anOutputStream,
1052: final CompileHandler aHandler) {
1053: // initialize with dummy stream (PrintStream needs it)
1054: super (anOutputStream);
1055: this .listener = aHandler;
1056: this .parser = new StdOutParser();
1057: }
1058:
1059: /**
1060: * Eclipse compiler hopefully only uses println(String).
1061: *
1062: * {@inheritDoc}
1063: */
1064: public void println(String x) {
1065: if (x.startsWith("[completed ")) {
1066: int pos = x.lastIndexOf("#");
1067: int endpos = x.lastIndexOf("/");
1068: String fileno_str = x.substring(pos + 1, endpos - pos
1069: - 1);
1070: try {
1071: int fileno = Integer.parseInt(fileno_str);
1072: this .listener.progress(fileno, x);
1073: } catch (NumberFormatException _nfe) {
1074: Debug
1075: .log("could not parse eclipse compiler output: '"
1076: + x + "': " + _nfe.getMessage());
1077: }
1078: }
1079:
1080: super .println(x);
1081: }
1082:
1083: /**
1084: * Unfortunately, the Eclipse compiler wraps System.out into a BufferedWriter.
1085: *
1086: * So we get whole buffers here and cannot do anything about it.
1087: *
1088: * {@inheritDoc}
1089: */
1090: public void write(byte[] buf, int off, int len) {
1091: super .write(buf, off, len);
1092: // we cannot convert back to string because the buffer might start
1093: // _inside_ a multibyte character
1094: // so we build a simple parser.
1095: int _fileno = this .parser.parse(buf, off, len);
1096: if (_fileno > -1) {
1097: this .listener.setSubStepNo(this .parser.getJobSize());
1098: this .listener.progress(_fileno, this .parser
1099: .getLastFilename());
1100: }
1101: }
1102:
1103: }
1104:
1105: /**
1106: * This PrintStream is used to track the Eclipse compiler error output.
1107: *
1108: * It will pass on all println requests and report progress to the listener.
1109: */
1110: private static class EclipseStdErrHandler extends PrintStream {
1111: // private CompileHandler listener; // Unused
1112: private int errorCount = 0;
1113: private StdErrParser parser;
1114:
1115: /**
1116: * Default constructor.
1117: *
1118: * @param anOutputStream The stream to wrap.
1119: * @param aHandler the handler to use.
1120: */
1121: public EclipseStdErrHandler(final OutputStream anOutputStream,
1122: final CompileHandler aHandler) {
1123: // initialize with dummy stream (PrintStream needs it)
1124: super (anOutputStream);
1125: // this.listener = aHandler; // TODO : reactivate this when we want to do something with it
1126: this .parser = new StdErrParser();
1127: }
1128:
1129: /**
1130: * Eclipse compiler hopefully only uses println(String).
1131: *
1132: * {@inheritDoc}
1133: */
1134: public void println(String x) {
1135: if (x.indexOf(". ERROR in ") > 0) {
1136: this .errorCount++;
1137: }
1138:
1139: super .println(x);
1140: }
1141:
1142: /**
1143: * Unfortunately, the Eclipse compiler wraps System.out into a BufferedWriter.
1144: *
1145: * So we get whole buffers here and cannot do anything about it.
1146: *
1147: * {@inheritDoc}
1148: */
1149: public void write(byte[] buf, int off, int len) {
1150: super .write(buf, off, len);
1151: // we cannot convert back to string because the buffer might start
1152: // _inside_ a multibyte character
1153: // so we build a simple parser.
1154: int _errno = this .parser.parse(buf, off, len);
1155: if (_errno > 0) {
1156: // TODO: emit error message instantly, but it may be incomplete yet
1157: // and we'd need to throw an exception to abort compilation
1158: this .errorCount += _errno;
1159: }
1160: }
1161:
1162: /**
1163: * Get the error state.
1164: *
1165: * @return true if there was an error detected.
1166: */
1167: public int getErrorCount() {
1168: return this .errorCount;
1169: }
1170: }
1171:
1172: /**
1173: * Common class for parsing Eclipse compiler output.
1174: */
1175: private static abstract class StreamParser {
1176: int idx;
1177: byte[] buffer;
1178: int offset;
1179: int length;
1180: byte[] lastIdentifier;
1181: int lastDigit;
1182:
1183: abstract int parse(byte[] buf, int off, int len);
1184:
1185: void init(byte[] buf, int off, int len) {
1186: this .buffer = buf;
1187: this .offset = off;
1188: this .length = len;
1189: this .idx = 0;
1190: this .lastIdentifier = null;
1191: this .lastDigit = -1;
1192: }
1193:
1194: int getNext() {
1195: if (this .offset + this .idx == this .length)
1196: return Integer.MIN_VALUE;
1197:
1198: return this .buffer[this .offset + this .idx++];
1199: }
1200:
1201: boolean findString(final String aString) {
1202: byte[] _search_bytes = aString.getBytes();
1203: int _search_idx = 0;
1204:
1205: do {
1206: int _c = getNext();
1207: if (_c == Integer.MIN_VALUE)
1208: return false;
1209:
1210: if (_c == _search_bytes[_search_idx])
1211: _search_idx++;
1212: else {
1213: _search_idx = 0;
1214: if (_c == _search_bytes[_search_idx])
1215: _search_idx++;
1216: }
1217: } while (_search_idx < _search_bytes.length);
1218:
1219: return true;
1220: }
1221:
1222: boolean readIdentifier() {
1223: int _c;
1224: int _start_idx = this .idx;
1225:
1226: do {
1227: _c = getNext();
1228: // abort on incomplete string
1229: if (_c == Integer.MIN_VALUE)
1230: return false;
1231: } while (!Character.isWhitespace((char) _c));
1232:
1233: this .idx--;
1234: this .lastIdentifier = new byte[this .idx - _start_idx];
1235: System.arraycopy(this .buffer, _start_idx,
1236: this .lastIdentifier, 0, this .idx - _start_idx);
1237:
1238: return true;
1239: }
1240:
1241: boolean readNumber() {
1242: int _c;
1243: int _start_idx = this .idx;
1244:
1245: do {
1246: _c = getNext();
1247: // abort on incomplete string
1248: if (_c == Integer.MIN_VALUE)
1249: return false;
1250: } while (Character.isDigit((char) _c));
1251:
1252: this .idx--;
1253: String _digit_str = new String(this .buffer, _start_idx,
1254: this .idx - _start_idx);
1255: try {
1256: this .lastDigit = Integer.parseInt(_digit_str);
1257: } catch (NumberFormatException _nfe) {
1258: // should not happen - ignore
1259: }
1260:
1261: return true;
1262: }
1263:
1264: boolean skipSpaces() {
1265: int _c;
1266:
1267: do {
1268: _c = getNext();
1269: if (_c == Integer.MIN_VALUE)
1270: return false;
1271: } while (Character.isWhitespace((char) _c));
1272:
1273: this .idx--;
1274:
1275: return true;
1276: }
1277:
1278: }
1279:
1280: private static class StdOutParser extends StreamParser {
1281: int fileno;
1282: int jobSize;
1283: String lastFilename;
1284:
1285: int parse(byte[] buf, int off, int len) {
1286: super .init(buf, off, len);
1287: this .fileno = -1;
1288: this .jobSize = -1;
1289: this .lastFilename = null;
1290:
1291: // a line looks like this:
1292: // [completed /path/to/file.java - #1/2025]
1293: do {
1294: if (findString("[completed ") && skipSpaces()
1295: && readIdentifier()) {
1296: // remember file name
1297: String filename = new String(this .lastIdentifier);
1298:
1299: if (!skipSpaces())
1300: continue;
1301:
1302: int _c = getNext();
1303: if (_c == Integer.MIN_VALUE)
1304: return this .fileno;
1305: if (_c != '-')
1306: continue;
1307:
1308: if (!skipSpaces())
1309: continue;
1310:
1311: _c = getNext();
1312: if (_c == Integer.MIN_VALUE)
1313: return this .fileno;
1314: if (_c != '#')
1315: continue;
1316:
1317: if (!readNumber())
1318: return this .fileno;
1319:
1320: int _fileno = this .lastDigit;
1321:
1322: _c = getNext();
1323: if (_c == Integer.MIN_VALUE)
1324: return this .fileno;
1325: if (_c != '/')
1326: continue;
1327:
1328: if (!readNumber())
1329: return this .fileno;
1330:
1331: _c = getNext();
1332: if (_c == Integer.MIN_VALUE)
1333: return this .fileno;
1334: if (_c != ']')
1335: continue;
1336:
1337: this .lastFilename = filename;
1338: this .fileno = _fileno;
1339: this .jobSize = this .lastDigit;
1340: // continue parsing (figure out last occurence)
1341: } else
1342: return this .fileno;
1343:
1344: } while (true);
1345: }
1346:
1347: String getLastFilename() {
1348: return this .lastFilename;
1349: }
1350:
1351: int getJobSize() {
1352: return this .jobSize;
1353: }
1354: }
1355:
1356: private static class StdErrParser extends StreamParser {
1357: int errorCount;
1358:
1359: int parse(byte[] buf, int off, int len) {
1360: super .init(buf, off, len);
1361: this .errorCount = 0;
1362:
1363: // a line looks like this:
1364: // [completed /path/to/file.java - #1/2025]
1365: do {
1366: if (findString(". ERROR in "))
1367: this .errorCount++;
1368: else
1369: return this .errorCount;
1370: } while (true);
1371: }
1372:
1373: int getErrorCount() {
1374: return this.errorCount;
1375: }
1376: }
1377: }
|