/******************************************************************************* * Copyright (c) 2012 Google, Inc 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 * * Contributors: * Alex Ruiz (Google) - initial API and implementation *******************************************************************************/ package org.eclipse.cdt.codan.core.cxx.externaltool; import java.net.URI; import org.eclipse.cdt.codan.core.CodanRuntime; import org.eclipse.cdt.codan.core.cxx.Activator; import org.eclipse.cdt.codan.core.cxx.internal.externaltool.ExternalToolInvoker; import org.eclipse.cdt.codan.core.model.AbstractCheckerWithProblemPreferences; import org.eclipse.cdt.codan.core.model.CheckerLaunchMode; import org.eclipse.cdt.codan.core.model.IProblem; import org.eclipse.cdt.codan.core.model.IProblemLocation; import org.eclipse.cdt.codan.core.model.IProblemLocationFactory; import org.eclipse.cdt.codan.core.model.IProblemWorkingCopy; import org.eclipse.cdt.codan.core.param.IProblemPreference; import org.eclipse.cdt.codan.core.param.MapProblemPreference; import org.eclipse.cdt.codan.core.param.RootProblemPreference; import org.eclipse.cdt.codan.core.param.SharedRootProblemPreference; import org.eclipse.cdt.core.ErrorParserManager; import org.eclipse.cdt.core.IConsoleParser; import org.eclipse.cdt.core.IMarkerGenerator; import org.eclipse.cdt.core.ProblemMarkerInfo; import org.eclipse.core.filesystem.URIUtil; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; /** * Base class for checkers that invoke external command-line tools to perform code checking. * <p> * A file, to be processed by this type of checker, must: * <ol> * <li>be in the current active editor</li> * <li>not have any unsaved changes</li> * </ol> * </p> * By default, implementations of this checker are not allowed to run while the user types, since * external tools cannot see unsaved changes. * * @since 2.1 */ public abstract class AbstractExternalToolBasedChecker extends AbstractCheckerWithProblemPreferences implements IMarkerGenerator { private final IInvocationParametersProvider parametersProvider; private final ArgsSeparator argsSeparator; private final ConfigurationSettings settings; private final ExternalToolInvoker externalToolInvoker; private final RootProblemPreference preferences; /** * Constructor. * @param settings user-configurable external tool configuration settings. */ public AbstractExternalToolBasedChecker(ConfigurationSettings settings) { this(new InvocationParametersProvider(), new ArgsSeparator(), settings); } /** * Constructor. * @param parametersProvider provides the parameters to pass when invoking the external tool. * @param argsSeparator separates the arguments to pass to the external tool executable. These * arguments are stored in a single {@code String}. * @param settings user-configurable external tool configuration settings. */ public AbstractExternalToolBasedChecker(IInvocationParametersProvider parametersProvider, ArgsSeparator argsSeparator, ConfigurationSettings settings) { this.parametersProvider = parametersProvider; this.argsSeparator = argsSeparator; this.settings = settings; externalToolInvoker = new ExternalToolInvoker(); preferences = new SharedRootProblemPreference(); } /** * Returns {@code false} because this checker cannot run "as you type" by default. * @return {@code false}. */ @Override public boolean runInEditor() { return false; } @Override public boolean processResource(IResource resource) { process(resource); return false; } private void process(IResource resource) { try { InvocationParameters parameters = parametersProvider.createParameters(resource); if (parameters != null) { invokeExternalTool(parameters); } } catch (Throwable error) { logResourceProcessingFailure(error, resource); } } private void invokeExternalTool(InvocationParameters parameters) throws Throwable { updateConfigurationSettingsFromPreferences(parameters.getActualFile()); IConsoleParser[] parsers = new IConsoleParser[] { createErrorParserManager(parameters) }; try { externalToolInvoker.invoke(parameters, settings, argsSeparator, parsers); } catch (InvocationFailure error) { handleInvocationFailure(error, parameters); } } private void updateConfigurationSettingsFromPreferences(IResource fileToProcess) { IProblem problem = getProblemById(getReferenceProblemId(), fileToProcess); MapProblemPreference preferences = (MapProblemPreference) problem.getPreference(); settings.updateValuesFrom(preferences); } private ErrorParserManager createErrorParserManager(InvocationParameters parameters) { IProject project = parameters.getActualFile().getProject(); URI workingDirectory = URIUtil.toURI(parameters.getWorkingDirectory()); return new ErrorParserManager(project, workingDirectory, this, getParserIDs()); } /** * @return the IDs of the parsers to use to parse the output of the external tool. */ protected abstract String[] getParserIDs(); /** * Handles a failure reported when invoking the external tool. This implementation simply * logs the failure. * @param error the reported failure. * @param parameters the parameters passed to the external tool executable. */ protected void handleInvocationFailure(InvocationFailure error, InvocationParameters parameters) { logResourceProcessingFailure(error, parameters.getActualFile()); } private void logResourceProcessingFailure(Throwable error, IResource resource) { String location = resource.getLocation().toOSString(); String msg = String.format("Unable to process resource %s", location); //$NON-NLS-1$ Activator.log(msg, error); } /** * Returns the id of the problem used as reference to obtain this checker's preferences. All * preferences in a external-tool-based checker are shared among its defined problems. * @return the id of the problem used as reference to obtain this checker's preferences. */ protected abstract String getReferenceProblemId(); @Override public void initPreferences(IProblemWorkingCopy problem) { super.initPreferences(problem); getLaunchModePreference(problem).enableInLaunchModes( CheckerLaunchMode.RUN_ON_DEMAND, CheckerLaunchMode.RUN_ON_FILE_OPEN, CheckerLaunchMode.RUN_ON_FILE_SAVE); addPreference(problem, settings.getPath()); addPreference(problem, settings.getArgs()); } private void addPreference(IProblemWorkingCopy problem, SingleConfigurationSetting<?> setting) { IProblemPreference descriptor = (IProblemPreference) setting.getDescriptor(); addPreference(problem, descriptor, setting.getDefaultValue()); } @Override protected void setDefaultPreferenceValue(IProblemWorkingCopy problem, String key, Object defaultValue) { MapProblemPreference map = getTopLevelPreference(problem); map.setChildValue(key, defaultValue); } @Override public RootProblemPreference getTopLevelPreference(IProblem problem) { RootProblemPreference map = (RootProblemPreference) problem.getPreference(); if (map == null) { map = preferences; if (problem instanceof IProblemWorkingCopy) { ((IProblemWorkingCopy) problem).setPreference(map); } } return map; } @Deprecated @Override public void addMarker(IResource file, int lineNumber, String description, int severity, String variableName) { addMarker(new ProblemMarkerInfo(file, lineNumber, description, severity, variableName)); } @Override public void addMarker(ProblemMarkerInfo info) { reportProblem(getReferenceProblemId(), createProblemLocation(info), info.description); } protected IProblemLocation createProblemLocation(ProblemMarkerInfo info) { IProblemLocationFactory factory = CodanRuntime.getInstance().getProblemLocationFactory(); return factory.createProblemLocation( (IFile) info.file, info.startChar, info.endChar, info.lineNumber); } }