All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.izforge.izpack.panels.compile.CompileWorker Maven / Gradle / Ivy

There is a newer version: 5.2.3
Show newest version
/*
 * IzPack - Copyright 2001-2008 Julien Ponge, All Rights Reserved.
 *
 * http://izpack.org/
 * http://izpack.codehaus.org/
 *
 * Copyright 2003 Tino Schwarze
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.izforge.izpack.panels.compile;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;

import com.izforge.izpack.api.adaptator.IXMLElement;
import com.izforge.izpack.api.adaptator.IXMLParser;
import com.izforge.izpack.api.adaptator.impl.XMLParser;
import com.izforge.izpack.api.data.InstallData;
import com.izforge.izpack.api.data.Pack;
import com.izforge.izpack.api.data.binding.OsModel;
import com.izforge.izpack.api.resource.Messages;
import com.izforge.izpack.api.resource.Resources;
import com.izforge.izpack.api.substitutor.SubstitutionType;
import com.izforge.izpack.api.substitutor.VariableSubstitutor;
import com.izforge.izpack.util.FileExecutor;
import com.izforge.izpack.util.OsConstraintHelper;
import com.izforge.izpack.util.PlatformModelMatcher;

/**
 * This class does alle the work for compiling sources.
 * 

* It responsible for *

    *
  • parsing the compilation spec XML file *
  • collecting and creating all jobs *
  • doing the actual compilation *
* * @author Tino Schwarze */ public class CompileWorker implements Runnable { private static final Logger logger = Logger.getLogger(CompileWorker.class.getName()); /** * Compilation jobs */ private ArrayList jobs; /** * Name of resource for specifying compilation parameters. */ private static final String SPEC_RESOURCE_NAME = "CompilePanel.Spec.xml"; private static final String ECLIPSE_COMPILER_NAME = "Integrated Eclipse JDT Compiler"; private static final String ECLIPSE_COMPILER_CLASS = "org.eclipse.jdt.internal.compiler.batch.Main"; private VariableSubstitutor vs; private IXMLElement spec; private InstallData idata; private CompileHandler handler; private IXMLElement compilerSpec; private ArrayList compilerList; private String compilerToUse; private IXMLElement compilerArgumentsSpec; private ArrayList compilerArgumentsList; private String compilerArgumentsToUse; private CompileResult result = null; private final Resources resources; /** * The platform-model matcher. */ private final PlatformModelMatcher matcher; /** * The constructor. * * @param installData the installation data * @param handler the handler to notify of progress * @param variableSubstitutor the variable substituter * @param resources the resources * @param matcher The platform-model matcher * @throws IOException for any I/O error */ public CompileWorker(InstallData installData, CompileHandler handler, VariableSubstitutor variableSubstitutor, Resources resources, PlatformModelMatcher matcher) throws IOException { this.idata = installData; this.handler = handler; this.vs = variableSubstitutor; this.resources = resources; this.matcher = matcher; if (!readSpec()) { throw new IOException("Error reading compilation specification"); } } /** * Return list of compilers to choose from. * * @return ArrayList of String */ public ArrayList getAvailableCompilers() { readChoices(this.compilerSpec, this.compilerList); return this.compilerList; } /** * Set the compiler to use. *

* The compiler is checked before compilation starts. * * @param compiler compiler to use (not checked) */ public void setCompiler(String compiler) { this.compilerToUse = compiler; } /** * Get the compiler used. * * @return the compiler. */ public String getCompiler() { return this.compilerToUse; } /** * Return list of compiler arguments to choose from. * * @return ArrayList of String */ public ArrayList getAvailableArguments() { readChoices(this.compilerArgumentsSpec, this.compilerArgumentsList); return this.compilerArgumentsList; } /** * Set the compiler arguments to use. * * @param arguments The argument to use. */ public void setCompilerArguments(String arguments) { this.compilerArgumentsToUse = arguments; } /** * Get the compiler arguments used. * * @return The arguments used for compiling. */ public String getCompilerArguments() { return this.compilerArgumentsToUse; } /** * Get the result of the compilation. * * @return The result. */ public CompileResult getResult() { return this.result; } /** * Start the compilation in a separate thread. */ public void startThread() { Thread compilationThread = new Thread(this, "compilation thread"); // will call this.run() compilationThread.start(); } /** * This is called when the compilation thread is activated. *

* Can also be called directly if asynchronous processing is not desired. */ @Override public void run() { try { if (!collectJobs()) { List args = new ArrayList(); args.add("nothing to do"); this.result = new CompileResult(idata.getMessages().get("CompilePanel.worker.nofiles"), args, "", ""); } else { this.result = compileJobs(); } } catch (Exception e) { this.result = new CompileResult(e); } this.handler.stopAction(); } private boolean readSpec() { InputStream input; try { input = resources.getInputStream(SPEC_RESOURCE_NAME); } catch (Exception e) { e.printStackTrace(); return false; } IXMLParser parser = new XMLParser(); try { this.spec = parser.parse(input); } catch (Exception e) { System.out.println("Error parsing XML specification for compilation."); e.printStackTrace(); return false; } if (!this.spec.hasChildren()) { return false; } this.compilerArgumentsList = new ArrayList(); this.compilerList = new ArrayList(); // read information IXMLElement global = this.spec.getFirstChildNamed("global"); // use some default values if no section found if (global != null) { // get list of compilers this.compilerSpec = global.getFirstChildNamed("compiler"); if (this.compilerSpec != null) { readChoices(this.compilerSpec, this.compilerList); } this.compilerArgumentsSpec = global.getFirstChildNamed("arguments"); if (this.compilerArgumentsSpec != null) { // basicly perform sanity check readChoices(this.compilerArgumentsSpec, this.compilerArgumentsList); } } // supply default values if no useful ones where found if (this.compilerList.size() == 0) { this.compilerList.add("javac"); this.compilerList.add("jikes"); } if (this.compilerArgumentsList.size() == 0) { this.compilerArgumentsList.add("-O -g:none"); this.compilerArgumentsList.add("-O"); this.compilerArgumentsList.add("-g"); this.compilerArgumentsList.add(""); } return true; } // helper function private void readChoices(IXMLElement element, ArrayList choiceList) { List choices = element.getChildrenNamed("choice"); if (choices == null) { return; } choiceList.clear(); for (IXMLElement choice : choices) { String value = choice.getAttribute("value"); if (value != null) { List osconstraints = OsConstraintHelper.getOsList(choice); if (matcher.matchesCurrentPlatform(osconstraints)) { if (value.equalsIgnoreCase(ECLIPSE_COMPILER_NAME)) { // check for availability of eclipse compiler try { Class.forName(ECLIPSE_COMPILER_CLASS); choiceList.add(value); } catch (ExceptionInInitializerError eiie) { // ignore, just don't add it as a choice } catch (ClassNotFoundException cnfe) { // ignore, just don't add it as a choice } } else { try { choiceList.add(this.vs.substitute(value, SubstitutionType.TYPE_PLAIN)); } catch (Exception e) { // ignore, just don't add it as a choice } } } } } } /** * Parse the compilation specification file and create jobs. */ private boolean collectJobs() throws Exception { IXMLElement data = this.spec.getFirstChildNamed("jobs"); if (data == null) { return false; } // list of classpath entries List classpath = new ArrayList(); this.jobs = new ArrayList(); // we throw away the toplevel compilation job // (all jobs are collected in this.jobs) collectJobsRecursive(data, classpath); return true; } /** * perform the actual compilation */ private CompileResult compileJobs() { ArrayList args = new ArrayList(); StringTokenizer tokenizer = new StringTokenizer(this.compilerArgumentsToUse); while (tokenizer.hasMoreTokens()) { args.add(tokenizer.nextToken()); } Iterator job_it = this.jobs.iterator(); this.handler.startAction("Compilation", this.jobs.size()); // check whether compiler is valid (but only if there are jobs) if (job_it.hasNext()) { CompilationJob first_job = this.jobs.get(0); CompileResult check_result = first_job.checkCompiler(this.compilerToUse, args); if (!check_result.isContinue()) { return check_result; } } int job_no = 0; while (job_it.hasNext()) { CompilationJob job = job_it.next(); this.handler.nextStep(job.getName(), job.getSize(), job_no++); CompileResult job_result = job.perform(this.compilerToUse, args); if (!job_result.isContinue()) { return job_result; } } logger.fine("Compilation finished"); return new CompileResult(); } private CompilationJob collectJobsRecursive(IXMLElement node, List classpath) throws Exception { List toplevel_tags = node.getChildren(); List ourclasspath = new ArrayList(classpath); ArrayList files = new ArrayList(); for (IXMLElement child : toplevel_tags) { if ("classpath".equals(child.getName())) { changeClassPath(ourclasspath, child); } else if ("job".equals(child.getName())) { CompilationJob subjob = collectJobsRecursive(child, ourclasspath); if (subjob != null) { this.jobs.add(subjob); } } else if ("directory".equals(child.getName())) { String name = child.getAttribute("name"); if (name != null) { // substitute variables String finalname = this.vs.substitute(name); files.addAll(scanDirectory(new File(finalname))); } } else if ("file".equals(child.getName())) { String name = child.getAttribute("name"); if (name != null) { // substitute variables String finalname = this.vs.substitute(name); files.add(new File(finalname)); } } else if ("packdependency".equals(child.getName())) { String name = child.getAttribute("name"); if (name == null) { System.out .println("invalid compilation spec: without name attribute"); return null; } // check whether the wanted pack was selected for installation boolean found = false; for (Pack pack : this.idata.getSelectedPacks()) { if (pack.getName().equals(name)) { found = true; break; } } if (!found) { logger.fine("Skipping job because pack " + name + " was not selected."); return null; } } } if (files.size() > 0) { return new CompilationJob(this.handler, this.idata, node.getAttribute("name"), files, ourclasspath); } return null; } /** * helper: process a <classpath> tag. */ private void changeClassPath(List classpath, IXMLElement child) throws Exception { String add = child.getAttribute("add"); if (add != null) { add = this.vs.substitute(add, SubstitutionType.TYPE_PLAIN); if (!new File(add).exists()) { if (!this.handler.emitWarning("Invalid classpath", "The path " + add + " could not be found.\nCompilation may fail.")) { throw new Exception("Classpath " + add + " does not exist."); } } else { classpath.add(this.vs.substitute(add, SubstitutionType.TYPE_PLAIN)); } } String sub = child.getAttribute("sub"); if (sub != null) { int cpidx = -1; sub = this.vs.substitute(sub, SubstitutionType.TYPE_PLAIN); do { cpidx = classpath.indexOf(sub); classpath.remove(cpidx); } while (cpidx >= 0); } } /** * helper: recursively scan given directory. * * @return list of files found (might be empty) */ private ArrayList scanDirectory(File path) { logger.fine("scanning directory " + path.getAbsolutePath()); ArrayList scan_result = new ArrayList(); if (!path.isDirectory()) { return scan_result; } File[] entries = path.listFiles(); for (File file : entries) { if (file == null) { continue; } if (file.isDirectory()) { scan_result.addAll(scanDirectory(file)); } else if ((file.isFile()) && (file.getName().toLowerCase().endsWith(".java"))) { scan_result.add(file); } } return scan_result; } /** * a compilation job */ private static class CompilationJob { private CompileHandler listener; private String name; private ArrayList files; private List classpath; private Messages messages; private InstallData idata; // XXX: figure that out (on runtime?) private static final int MAX_CMDLINE_SIZE = 4096; /** * Construct new compilation job. * * @param listener The listener to report progress to. * @param idata The installation data. * @param name The name of the job. * @param files The files to compile. * @param classpath The class path to use. */ public CompilationJob(CompileHandler listener, InstallData idata, String name, ArrayList files, List classpath) { this.listener = listener; this.idata = idata; this.messages = idata.getMessages(); this.name = name; this.files = files; this.classpath = classpath; } /** * Get the name of the job. * * @return The name or an empty string if there is no name. */ public String getName() { if (this.name != null) { return this.name; } return ""; } /** * Get the number of files in this job. * * @return The number of files to compile. */ public int getSize() { return this.files.size(); } /** * Perform this job - start compilation. * * @param compiler The compiler to use. * @param arguments The compiler arguments to use. * @return The result. */ public CompileResult perform(String compiler, ArrayList arguments) { logger.fine("starting job " + this.name); // we have some maximum command line length - need to count int cmdline_len = 0; // used to collect the arguments for executing the compiler LinkedList args = new LinkedList(arguments); { for (String arg : args) { cmdline_len += (arg).length() + 1; } } boolean isEclipseCompiler = compiler.equalsIgnoreCase(ECLIPSE_COMPILER_NAME); // add compiler in front of arguments args.add(0, compiler); cmdline_len += compiler.length() + 1; // construct classpath argument for compiler // - collect all classpaths StringBuffer classpath_sb = new StringBuffer(); for (String cp : this.classpath) { if (classpath_sb.length() > 0) { classpath_sb.append(File.pathSeparatorChar); } classpath_sb.append(new File(cp).getAbsolutePath()); } String classpath_str = classpath_sb.toString(); // - add classpath argument to command line if (classpath_str.length() > 0) { args.add("-classpath"); cmdline_len += 11; args.add(classpath_str); cmdline_len += classpath_str.length() + 1; } // remember how many arguments we have which don't change for the // job int common_args_no = args.size(); // remember how long the common command line is int common_args_len = cmdline_len; // used for execution FileExecutor executor = new FileExecutor(); String output[] = new String[2]; // used for displaying the progress bar String jobfiles = ""; int fileno = 0; int last_fileno = 0; // now iterate over all files of this job for (File file : this.files) { String fpath = file.getAbsolutePath(); logger.fine("processing " + fpath); // we add the file _first_ to the arguments to have a better // chance to get something done if the command line is almost // MAX_CMDLINE_SIZE or even above fileno++; jobfiles += file.getName() + " "; args.add(fpath); cmdline_len += fpath.length(); // start compilation if maximum command line length reached if (!isEclipseCompiler && cmdline_len >= MAX_CMDLINE_SIZE) { logger.fine("compiling " + jobfiles); // display useful progress bar (avoid showing 100% while // still compiling a lot) this.listener.progress(last_fileno, jobfiles); last_fileno = fileno; int retval = runCompiler(executor, output, args); // update progress bar: compilation of fileno files done this.listener.progress(fileno, jobfiles); if (retval != 0) { CompileResult result = new CompileResult(messages.get("CompilePanel.error"), args, output[0], output[1]); this.listener.handleCompileError(result); if (!result.isContinue()) { return result; } } else { // verify that all files have been compiled successfully // I found that sometimes, no error code is returned // although compilation failed. Iterator arg_it = args.listIterator(common_args_no); while (arg_it.hasNext()) { File java_file = new File(arg_it.next()); String basename = java_file.getName(); int dotpos = basename.lastIndexOf('.'); basename = basename.substring(0, dotpos) + ".class"; File class_file = new File(java_file.getParentFile(), basename); if (!class_file.exists()) { CompileResult result = new CompileResult(messages.get("CompilePanel.error.noclassfile") + java_file.getAbsolutePath(), args, output[0], output[1]); this.listener.handleCompileError(result); if (!result.isContinue()) { return result; } // don't continue any further break; } } } // clean command line: remove files we just compiled for (int i = args.size() - 1; i >= common_args_no; i--) { args.removeLast(); } cmdline_len = common_args_len; jobfiles = ""; } } if (cmdline_len > common_args_len) { this.listener.progress(last_fileno, jobfiles); int retval = runCompiler(executor, output, args); if (!isEclipseCompiler) { this.listener.progress(fileno, jobfiles); } if (retval != 0) { CompileResult result = new CompileResult(messages.get("CompilePanel.error"), args, output[0], output[1]); this.listener.handleCompileError(result); if (!result.isContinue()) { return result; } } else { // verify that all files have been compiled successfully // I found that sometimes, no error code is returned // although compilation failed. Iterator arg_it = args.listIterator(common_args_no); while (arg_it.hasNext()) { File java_file = new File(arg_it.next()); String basename = java_file.getName(); int dotpos = basename.lastIndexOf('.'); basename = basename.substring(0, dotpos) + ".class"; File class_file = new File(java_file.getParentFile(), basename); if (!class_file.exists()) { CompileResult result = new CompileResult(messages.get("CompilePanel.error.noclassfile") + java_file.getAbsolutePath(), args, output[0], output[1]); this.listener.handleCompileError(result); if (!result.isContinue()) { return result; } // don't continue any further break; } } } } logger.fine("Job " + this.name + " done (" + fileno + " files compiled)"); return new CompileResult(); } /** * Internal helper method. * * @param executor The executor, only used when using external compiler. * @param output The output from the compiler ([0] = stdout, [1] = stderr) * @return The result of the compilation. */ private int runCompiler(FileExecutor executor, String[] output, List cmdline) { if (cmdline.get(0).equals(ECLIPSE_COMPILER_NAME)) { return runEclipseCompiler(output, cmdline); } return executor.executeCommand(cmdline.toArray(new String[cmdline.size()]), output); } private int runEclipseCompiler(String[] output, List cmdline) { try { List final_cmdline = new LinkedList(cmdline); // remove compiler name from argument list final_cmdline.remove(0); Class eclipseCompiler = Class.forName(ECLIPSE_COMPILER_CLASS); Method compileMethod = eclipseCompiler.getMethod("main", new Class[]{String[].class}); final_cmdline.add(0, "-noExit"); final_cmdline.add(0, "-progress"); final_cmdline.add(0, "-verbose"); File _logfile = new File(this.idata.getInstallPath(), "compile-" + getName() + ".log"); final boolean isTrace = LogManager.getLogManager().getLogger("").isLoggable(Level.FINE); if (isTrace) { final_cmdline.add(0, _logfile.getPath()); final_cmdline.add(0, "-log"); } // get log files / determine results... try { // capture stdout and stderr PrintStream _orgStdout = System.out; PrintStream _orgStderr = System.err; int error_count = 0; try { ByteArrayOutputStream outStream = new ByteArrayOutputStream(); EclipseStdOutHandler ownStdout = new EclipseStdOutHandler(outStream, this.listener); System.setOut(ownStdout); ByteArrayOutputStream errStream = new ByteArrayOutputStream(); EclipseStdErrHandler ownStderr = new EclipseStdErrHandler(errStream, this.listener); System.setErr(ownStderr); compileMethod.invoke(null, new Object[]{final_cmdline.toArray(new String[final_cmdline.size()])}); // TODO: launch thread which updates the progress output[0] = outStream.toString(); output[1] = errStream.toString(); error_count = ownStderr.getErrorCount(); // for debugging: write output to log files if (error_count > 0 || isTrace) { File _out = new File(_logfile.getPath() + ".stdout"); FileOutputStream _fout = new FileOutputStream(_out); _fout.write(outStream.toByteArray()); _fout.close(); _out = new File(_logfile.getPath() + ".stderr"); _fout = new FileOutputStream(_out); _fout.write(errStream.toByteArray()); _fout.close(); } } finally { System.setOut(_orgStdout); System.setErr(_orgStderr); } if (error_count == 0) { return 0; } // TODO: construct human readable error message from log this.listener.emitNotification("Compiler reported " + error_count + " errors"); return 1; } catch (FileNotFoundException fnfe) { this.listener.emitError("error compiling", fnfe.getMessage()); return -1; } catch (IOException ioe) { this.listener.emitError("error compiling", ioe.getMessage()); return -1; } } catch (ClassNotFoundException cnfe) { output[0] = "error getting eclipse compiler"; output[1] = cnfe.getMessage(); return -1; } catch (NoSuchMethodException nsme) { output[0] = "error getting eclipse compiler method"; output[1] = nsme.getMessage(); return -1; } catch (IllegalAccessException iae) { output[0] = "error calling eclipse compiler"; output[1] = iae.getMessage(); return -1; } catch (InvocationTargetException ite) { output[0] = "error calling eclipse compiler"; output[1] = ite.getMessage(); return -1; } } /** * Check whether the given compiler works. *

* This performs two steps: *

    *
  1. check whether we can successfully call "compiler -help"
  2. *
  3. check whether we can successfully call "compiler -help arguments" (not all compilers * return an error here)
  4. *
*

* On failure, the method CompileHandler#errorCompile is called with a descriptive error * message. * * @param compiler the compiler to use * @param arguments additional arguments to pass to the compiler * @return false on error */ public CompileResult checkCompiler(String compiler, ArrayList arguments) { // don't do further checks for eclipse compiler - it would exit if (compiler.equalsIgnoreCase(ECLIPSE_COMPILER_NAME)) { return new CompileResult(); } int retval = 0; FileExecutor executor = new FileExecutor(); String[] output = new String[2]; logger.fine("checking whether \"" + compiler + " -help\" works"); { List args = new ArrayList(); args.add(compiler); args.add("-help"); retval = runCompiler(executor, output, args); if (retval != 0) { CompileResult result = new CompileResult(messages.get("CompilePanel.error.compilernotfound"), args, output[0], output[1]); this.listener.handleCompileError(result); if (!result.isContinue()) { return result; } } } logger.fine("Checking whether \"" + compiler + " -help +arguments\" works"); // used to collect the arguments for executing the compiler LinkedList args = new LinkedList(arguments); // add -help argument to prevent the compiler from doing anything args.add(0, "-help"); // add compiler in front of arguments args.add(0, compiler); // construct classpath argument for compiler // - collect all classpaths StringBuffer classpath_sb = new StringBuffer(); for (String cp : this.classpath) { if (classpath_sb.length() > 0) { classpath_sb.append(File.pathSeparatorChar); } classpath_sb.append(new File(cp).getAbsolutePath()); } String classpath_str = classpath_sb.toString(); // - add classpath argument to command line if (classpath_str.length() > 0) { args.add("-classpath"); args.add(classpath_str); } retval = runCompiler(executor, output, args); if (retval != 0) { CompileResult result = new CompileResult(messages.get("CompilePanel.error.invalidarguments"), args, output[0], output[1]); this.listener.handleCompileError(result); if (!result.isContinue()) { return result; } } return new CompileResult(); } } /** * This PrintStream is used to track the Eclipse compiler output. *

* It will pass on all println requests and report progress to the listener. */ private static class EclipseStdOutHandler extends PrintStream { private CompileHandler listener; private StdOutParser parser; /** * Default constructor. * * @param anOutputStream The stream to wrap. * @param aHandler the handler to use. */ public EclipseStdOutHandler(final OutputStream anOutputStream, final CompileHandler aHandler) { // initialize with dummy stream (PrintStream needs it) super(anOutputStream); this.listener = aHandler; this.parser = new StdOutParser(); } /** * Eclipse compiler hopefully only uses println(String). *

* {@inheritDoc} */ @Override public void println(String x) { if (x.startsWith("[completed ")) { int pos = x.lastIndexOf("#"); int endpos = x.lastIndexOf("/"); String fileno_str = x.substring(pos + 1, endpos - pos - 1); try { int fileno = Integer.parseInt(fileno_str); this.listener.progress(fileno, x); } catch (NumberFormatException e) { logger.log(Level.WARNING, "Could not parse eclipse compiler output: '" + x + "': " + e.getMessage(), e); } } super.println(x); } /** * Unfortunately, the Eclipse compiler wraps System.out into a BufferedWriter. *

* So we get whole buffers here and cannot do anything about it. *

* {@inheritDoc} */ @Override public void write(byte[] buf, int off, int len) { super.write(buf, off, len); // we cannot convert back to string because the buffer might start // _inside_ a multibyte character // so we build a simple parser. int _fileno = this.parser.parse(buf, off, len); if (_fileno > -1) { this.listener.setSubStepNo(this.parser.getJobSize()); this.listener.progress(_fileno, this.parser.getLastFilename()); } } } /** * This PrintStream is used to track the Eclipse compiler error output. *

* It will pass on all println requests and report progress to the listener. */ private static class EclipseStdErrHandler extends PrintStream { // private CompileHandler listener; // Unused private int errorCount = 0; private StdErrParser parser; /** * Default constructor. * * @param anOutputStream The stream to wrap. * @param aHandler the handler to use. */ public EclipseStdErrHandler(final OutputStream anOutputStream, final CompileHandler aHandler) { // initialize with dummy stream (PrintStream needs it) super(anOutputStream); // this.listener = aHandler; // TODO : reactivate this when we want to do something with it this.parser = new StdErrParser(); } /** * Eclipse compiler hopefully only uses println(String). *

* {@inheritDoc} */ @Override public void println(String x) { if (x.indexOf(". ERROR in ") > 0) { this.errorCount++; } super.println(x); } /** * Unfortunately, the Eclipse compiler wraps System.out into a BufferedWriter. *

* So we get whole buffers here and cannot do anything about it. *

* {@inheritDoc} */ @Override public void write(byte[] buf, int off, int len) { super.write(buf, off, len); // we cannot convert back to string because the buffer might start // _inside_ a multibyte character // so we build a simple parser. int _errno = this.parser.parse(buf, off, len); if (_errno > 0) { // TODO: emit error message instantly, but it may be incomplete yet // and we'd need to throw an exception to abort compilation this.errorCount += _errno; } } /** * Get the error state. * * @return true if there was an error detected. */ public int getErrorCount() { return this.errorCount; } } /** * Common class for parsing Eclipse compiler output. */ private static abstract class StreamParser { int idx; byte[] buffer; int offset; int length; byte[] lastIdentifier; int lastDigit; abstract int parse(byte[] buf, int off, int len); void init(byte[] buf, int off, int len) { this.buffer = buf; this.offset = off; this.length = len; this.idx = 0; this.lastIdentifier = null; this.lastDigit = -1; } int getNext() { if (this.offset + this.idx == this.length) { return Integer.MIN_VALUE; } return this.buffer[this.offset + this.idx++]; } boolean findString(final String aString) { byte[] _search_bytes = aString.getBytes(); int _search_idx = 0; do { int _c = getNext(); if (_c == Integer.MIN_VALUE) { return false; } if (_c == _search_bytes[_search_idx]) { _search_idx++; } else { _search_idx = 0; if (_c == _search_bytes[_search_idx]) { _search_idx++; } } } while (_search_idx < _search_bytes.length); return true; } boolean readIdentifier() { int _c; int _start_idx = this.idx; do { _c = getNext(); // abort on incomplete string if (_c == Integer.MIN_VALUE) { return false; } } while (!Character.isWhitespace((char) _c)); this.idx--; this.lastIdentifier = new byte[this.idx - _start_idx]; System.arraycopy(this.buffer, _start_idx, this.lastIdentifier, 0, this.idx - _start_idx); return true; } boolean readNumber() { int _c; int _start_idx = this.idx; do { _c = getNext(); // abort on incomplete string if (_c == Integer.MIN_VALUE) { return false; } } while (Character.isDigit((char) _c)); this.idx--; String _digit_str = new String(this.buffer, _start_idx, this.idx - _start_idx); try { this.lastDigit = Integer.parseInt(_digit_str); } catch (NumberFormatException _nfe) { // should not happen - ignore } return true; } boolean skipSpaces() { int _c; do { _c = getNext(); if (_c == Integer.MIN_VALUE) { return false; } } while (Character.isWhitespace((char) _c)); this.idx--; return true; } } private static class StdOutParser extends StreamParser { int fileno; int jobSize; String lastFilename; @Override int parse(byte[] buf, int off, int len) { super.init(buf, off, len); this.fileno = -1; this.jobSize = -1; this.lastFilename = null; // a line looks like this: // [completed /path/to/file.java - #1/2025] do { if (findString("[completed ") && skipSpaces() && readIdentifier()) { // remember file name String filename = new String(this.lastIdentifier); if (!skipSpaces()) { continue; } int _c = getNext(); if (_c == Integer.MIN_VALUE) { return this.fileno; } if (_c != '-') { continue; } if (!skipSpaces()) { continue; } _c = getNext(); if (_c == Integer.MIN_VALUE) { return this.fileno; } if (_c != '#') { continue; } if (!readNumber()) { return this.fileno; } int _fileno = this.lastDigit; _c = getNext(); if (_c == Integer.MIN_VALUE) { return this.fileno; } if (_c != '/') { continue; } if (!readNumber()) { return this.fileno; } _c = getNext(); if (_c == Integer.MIN_VALUE) { return this.fileno; } if (_c != ']') { continue; } this.lastFilename = filename; this.fileno = _fileno; this.jobSize = this.lastDigit; // continue parsing (figure out last occurence) } else { return this.fileno; } } while (true); } String getLastFilename() { return this.lastFilename; } int getJobSize() { return this.jobSize; } } private static class StdErrParser extends StreamParser { int errorCount; @Override int parse(byte[] buf, int off, int len) { super.init(buf, off, len); this.errorCount = 0; // a line looks like this: // [completed /path/to/file.java - #1/2025] do { if (findString(". ERROR in ")) { this.errorCount++; } else { return this.errorCount; } } while (true); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy