/******************************************************************************* * Copyright (c) 2005, 2017 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * *******************************************************************************/ package org.eclipse.dltk.tcl.internal.tclchecker; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Status; import org.eclipse.dltk.compiler.CharOperation; import org.eclipse.dltk.core.DLTKCore; import org.eclipse.dltk.core.ISourceModule; import org.eclipse.dltk.core.ISourceRange; import org.eclipse.dltk.core.ModelException; import org.eclipse.dltk.core.builder.ISourceLineTracker; import org.eclipse.dltk.core.environment.IDeployment; import org.eclipse.dltk.core.environment.IEnvironment; import org.eclipse.dltk.core.environment.IExecutionEnvironment; import org.eclipse.dltk.tcl.internal.tclchecker.v5.Checker5OutputProcessor; import org.eclipse.dltk.tcl.tclchecker.TclCheckerPlugin; import org.eclipse.dltk.tcl.tclchecker.model.configs.CheckerConfig; import org.eclipse.dltk.tcl.tclchecker.model.configs.CheckerEnvironmentInstance; import org.eclipse.dltk.tcl.tclchecker.model.configs.CheckerVersion; import org.eclipse.dltk.utils.DLTKLoggingOption; import org.eclipse.dltk.utils.TextUtils; import org.eclipse.dltk.validators.core.AbstractExternalValidator; import org.eclipse.dltk.validators.core.CommandLine; import org.eclipse.dltk.validators.core.ISourceModuleValidator; import org.eclipse.dltk.validators.core.IValidatorOutput; import org.eclipse.osgi.util.NLS; public class TclChecker extends AbstractExternalValidator implements ISourceModuleValidator, ITclCheckerReporter, ILineTrackerFactory { private static final String PATTERN_TXT = "pattern.txt"; //$NON-NLS-1$ protected IMarker reportErrorProblem(IResource resource, TclCheckerProblem problem, int start, int end, Map<String, Object> attributes) throws CoreException { return reportError(resource, problem.getLineNumber(), start, end, problem.getMessage(), attributes); } protected IMarker reportWarningProblem(IResource resource, TclCheckerProblem problem, int start, int end, Map<String, Object> attributes) throws CoreException { return reportWarning(resource, problem.getLineNumber(), start, end, problem.getMessage(), attributes); } private final CheckerEnvironmentInstance instance; private final CheckerConfig config; private final IEnvironment environment; public TclChecker(CheckerEnvironmentInstance instance, CheckerConfig config, IEnvironment environment) { Assert.isNotNull(instance, "CheckerInstance can't be null"); //$NON-NLS-1$ Assert.isNotNull(config, "CheckerConfig can't be null"); //$NON-NLS-1$ this.instance = instance; this.config = config; this.environment = environment; } private boolean canCheck() { return TclCheckerHelper.canExecuteTclChecker(instance, environment); } public void check(final List<ISourceModule> sourceModules, IValidatorOutput console, IProgressMonitor monitor) { if (!canCheck()) { throw new IllegalStateException(Messages.TclChecker_cannot_be_executed); } if (monitor == null) { monitor = new NullProgressMonitor(); } final IOutputProcessor processor; if (CheckerVersion.VERSION5.equals(instance.getInstance().getVersion())) { processor = new Checker5OutputProcessor(monitor, console, this, this); } else { processor = new Checker4OutputProcessor(monitor, console, this); } List<String> filenames = processor.initialize(environment, sourceModules); if (filenames.isEmpty()) { monitor.done(); return; } final CommandLine cmdLine = new CommandLine(); if (!TclCheckerHelper.buildCommandLine(instance, config, cmdLine, environment, sourceModules.get(0).getScriptProject(), console)) { console.println(Messages.TclChecker_path_not_specified); return; } monitor.beginTask(Messages.TclChecker_executing, sourceModules.size() * 2 + 1); try { final IExecutionEnvironment execEnvironment = environment.getAdapter(IExecutionEnvironment.class); final IDeployment deployment = execEnvironment.createDeployment(); if (deployment == null) { return; } try { final IPath pattern = deployFileList(deployment, filenames); if (pattern == null) { console.println(Messages.TclChecker_errorWritingFileList); return; } cmdLine.add("-@"); //$NON-NLS-1$ cmdLine.add(deployment.getFile(pattern).toOSString()); monitor.subTask(Messages.TclChecker_launching); console.setAttribute(IValidatorOutput.COMMAND_LINE, cmdLine.toString()); if (LOG_COMMAND_LINE.isEnabled()) { final String message = NLS.bind("[{0}:{1}] {2}", //$NON-NLS-1$ new Object[] { instance.getInstance().getName(), config.getName(), cmdLine.toString() }); TclCheckerPlugin.log(IStatus.INFO, message); } executeProcess(processor, execEnvironment, cmdLine.toArray()); } catch (CoreException e) { TclCheckerPlugin.log(IStatus.ERROR, Messages.TclChecker_cannot_be_executed, e); } finally { deployment.dispose(); } } finally { monitor.done(); } } private static final DLTKLoggingOption LOG_COMMAND_LINE = new DLTKLoggingOption(TclCheckerPlugin.PLUGIN_ID, "logCommandLine"); //$NON-NLS-1$ public void executeProcess(final IOutputProcessor processor, final IExecutionEnvironment execEnvironment, final String[] commandLine) throws CoreException { if (DLTKCore.DEBUG) { processor.processLine(TextUtils.join(commandLine, ' ')); } final Process process = execEnvironment.exec(commandLine, null, prepareEnvironment(execEnvironment)); try { final TclCheckerErrorReader errorReader = new TclCheckerErrorReader(process.getErrorStream(), processor); errorReader.start(); final TclCheckerOutputReader outputReader = new TclCheckerOutputReader(process.getInputStream(), processor); outputReader.start(); final IProgressMonitor monitor = processor.getProgressMonitor(); monitor.worked(1); while (outputReader.isAlive()) { try { outputReader.join(500); } catch (InterruptedException e) { // ignore } if (monitor.isCanceled()) { break; } } } finally { process.destroy(); } } private String[] prepareEnvironment(IExecutionEnvironment execEnvironment) { Map<?, ?> map = execEnvironment.getEnvironmentVariables(false); if (map == null) { return null; } String[] env = new String[map.size()]; int i = 0; for (Iterator<?> iterator = map.keySet().iterator(); iterator.hasNext();) { String key = (String) iterator.next(); String value = (String) map.get(key); env[i] = key + "=" + value; //$NON-NLS-1$ ++i; } return env; } private IPath deployFileList(IDeployment deployment, List<String> arguments) { ByteArrayOutputStream baros = new ByteArrayOutputStream(); try { for (String path : arguments) { /* * FIXME filename encoding on the remote system should be * configurable */ baros.write((path + "\n").getBytes()); //$NON-NLS-1$ } baros.close(); } catch (IOException e) { // should not happen } try { return deployment.add(new ByteArrayInputStream(baros.toByteArray()), PATTERN_TXT); } catch (IOException e) { TclCheckerPlugin.log(IStatus.ERROR, Messages.TclChecker_filelist_deploy_failed, e); if (DLTKCore.DEBUG) { e.printStackTrace(); } return null; } } @Override protected String getMarkerType() { return TclCheckerMarker.TYPE; } @Override public IStatus validate(ISourceModule[] modules, IValidatorOutput console, IProgressMonitor monitor) { final List<ISourceModule> elements = new ArrayList<>(); for (int i = 0; i < modules.length; i++) { final ISourceModule module = modules[i]; final IResource resource = module.getResource(); if (resource != null) { clean(resource); elements.add(module); } } if (elements.isEmpty()) { return Status.OK_STATUS; } check(elements, console, monitor); return Status.OK_STATUS; } private final Map<ISourceModule, ISourceLineTracker> lineTrackers = new HashMap<>(); /** * @param resource * @param problem * @param start * @param end * @throws CoreException */ @Override public void report(ISourceModule module, TclCheckerProblem problem) throws CoreException { final ISourceLineTracker lineTracker = getLineTracker(module); final int start; final int end; final CoordRange bounds = problem.getRange(); if (bounds == null) { final ISourceRange lineBounds = lineTracker.getLineInformation(problem.getLineNumber() - 1); start = lineBounds.getOffset(); end = start + lineBounds.getLength(); } else { start = calculateOffset(lineTracker, bounds.getStart()); end = calculateOffset(lineTracker, bounds.getEnd()) + 1; } final IResource resource = module.getResource(); if (resource == null) { return; } if (problem.isError()) { reportErrorProblem(resource, problem, start, end, problem.getAttributes()); } else if (problem.isWarning()) reportWarningProblem(resource, problem, start, end, problem.getAttributes()); } /** * @param lineTracker * @param start * @return */ @Override public int calculateOffset(ISourceLineTracker lineTracker, Coord coord) { final ISourceRange range = lineTracker.getLineInformation(coord.getLine() - 1); return range.getOffset() + coord.getColumn(); } @Override public ISourceLineTracker getLineTracker(ISourceModule module) { ISourceLineTracker lineTracker = lineTrackers.get(module); if (lineTracker == null) { char[] source; try { source = module.getSourceAsCharArray(); if (source == null) { source = CharOperation.NO_CHAR; } } catch (ModelException e) { if (DLTKCore.DEBUG) { e.printStackTrace(); } source = CharOperation.NO_CHAR; } lineTracker = TextUtils.createLineTracker(source); lineTrackers.put(module, lineTracker); } return lineTracker; } }