/*******************************************************************************
 * Copyright (c) 2009, 2015 Broadcom Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     Broadcom Corporation - initial API and implementation
 *     Clare Richardson (Motorola) - Bug 281397 building specific configs
 *     Dmitry Kozlov (CodeSourcery) - Bug 309909 Headless build import fails
 *                                    silently with relative pathname
 *                                  - Bug 300554 Build status not propagated
 *                                    to exit code
 *     R. Zulliger, C. Walther (Indel AG) - Bug 355609 Disable indexer
 *     John Dallaway - Bug 513763 Save workspace on conclusion
 *     Torbjörn Svensson (STMicroelectronics) - bug #330204
 *******************************************************************************/

package org.eclipse.cdt.managedbuilder.internal.core;

import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.dom.IPDOMManager;
import org.eclipse.cdt.core.envvar.IEnvironmentVariable;
import org.eclipse.cdt.core.model.CoreModel;
import org.eclipse.cdt.core.model.ICModelMarker;
import org.eclipse.cdt.core.resources.ACBuilder;
import org.eclipse.cdt.core.settings.model.ICConfigurationDescription;
import org.eclipse.cdt.core.settings.model.ICProjectDescription;
import org.eclipse.cdt.core.settings.model.util.CDataUtil;
import org.eclipse.cdt.internal.core.envvar.EnvironmentVariableManager;
import org.eclipse.cdt.internal.core.pdom.PDOMManager;
import org.eclipse.cdt.managedbuilder.core.BuildException;
import org.eclipse.cdt.managedbuilder.core.IConfiguration;
import org.eclipse.cdt.managedbuilder.core.IManagedBuildInfo;
import org.eclipse.cdt.managedbuilder.core.IManagedProject;
import org.eclipse.cdt.managedbuilder.core.IOption;
import org.eclipse.cdt.managedbuilder.core.ITool;
import org.eclipse.cdt.managedbuilder.core.ManagedBuildManager;
import org.eclipse.cdt.managedbuilder.core.ManagedBuilderCorePlugin;
import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.filesystem.URIUtil;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceDescription;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.equinox.app.IApplication;
import org.eclipse.equinox.app.IApplicationContext;
import org.eclipse.osgi.service.datalocation.Location;

/**
 * A headless builder for CDT with additional features.
 *
 * IApplication ID: org.eclipse.cdt.managedbuilder.core.headlessbuild
 * Provides:
 *   - Import projects :                       -import     {[uri:/]/path/to/project}
 *   - Import all projects in the tree :       -importAll  {[uri:/]/path/to/projectTreeURI}
 *   - Build projects / the workspace :        -build      {project_name_reg_ex/config_name_reg_ex | all}
 *   - Clean build projects / the workspace :  -cleanBuild {project_name_reg_ex/config_name_reg_ex | all}
 *   - Add Include path to build :             -I          {include_path}
 *   - Add Include file to build :             -include    {include_file}
 *   - Add preprocessor define to build :      -D          {prepoc_define}
 *   - Replace environment variable in build : -E          {var=value}
 *   - Append environment variable to build :  -Ea         {var=value}
 *   - Prepend environment variable to build : -Ep         {var=value}
 *   - Remove environment variable in build :  -Er         {var}
 *   - Replace a tool option value:            -T          {toolid} {optionid=value}
 *   - Append to a tool option value:          -Ta         {toolid} {optionid=value}
 *   - Prepend to a tool option value:         -Tp         {toolid} {optionid=value}
 *   - Remove a tool option:                   -Tr         {toolid} {optionid=value}
 *   - Disable indexer:                        -no-indexer
 *   - Error marker types to consider:         -markerType {all | cdt | marker_id}
 *        where:
 *           all is all markers -- default
 *           cdt is C/C++ Problem markers
 *           marker_id, e.g. org.eclipse.cdt.core.problem
 *   - Display all error markers:              -printErrorMarkers
 *
 * Build output is automatically sent to stdout.
 * @since 6.0
 */
public class HeadlessBuilder implements IApplication {

	/**
	 * IProgressMonitor to provide printing of task
	 */
	public static class PrintingProgressMonitor extends NullProgressMonitor {
		@Override
		public void beginTask(String name, int totalWork) {
			if (name != null && name.length() > 0)
				System.out.println(name);
		}
	}

	/**
	 * A class representing a new tool option value
	 */
	protected static class ToolOption {
		public static final int REPLACE = 0;
		public static final int APPEND = 1;
		public static final int PREPEND = 2;
		public static final int REMOVE = 3;
		final String toolId;
		final String optionId;
		final String value;
		final int operation;

		ToolOption(String toolId, String optionId, String value, int operation) {
			this.toolId = toolId;
			this.optionId = optionId;
			this.value = value;
			this.operation = operation;
		}
	}

	/**
	 * A class representing a backed-up tool option to restored at the end of the build
	 */
	protected static class SavedToolOption {
		final String toolId;
		final String optionId;
		final Object value;

		SavedToolOption(String toolId, String optionId, Object value) {
			this.toolId = toolId;
			this.optionId = optionId;
			this.value = value;
		}

		@Override
		public int hashCode() {
			return toolId.hashCode() + optionId.hashCode();
		}

		@Override
		public boolean equals(Object obj) {
			SavedToolOption option = (SavedToolOption) obj;
			return toolId.equals(option.toolId) && optionId.equals(option.optionId);
		}
	}

	/** Error return status */
	public static final Integer ERROR = 1;
	/** OK return status */
	public static final Integer OK = IApplication.EXIT_OK;

	/** Set of project URIs / paths to import */
	protected final Set<String> projectsToImport = new HashSet<>();
	/** Tree of projects to recursively import */
	protected final Set<String> projectTreeToImport = new HashSet<>();
	/** Set of project names to build */
	protected final Set<String> projectRegExToBuild = new HashSet<>();
	/** Set of project names to clean */
	protected final Set<String> projectRegExToClean = new HashSet<>();
	protected boolean buildAll = false;
	protected boolean cleanAll = false;
	protected boolean disableIndexer = false;

	/** List of Tool Option values being set */
	protected List<ToolOption> toolOptions = new ArrayList<>();
	/** Map from configuration ID -> Set of SavedToolOptions */
	protected Map<String, Set<SavedToolOption>> savedToolOptions = new HashMap<>();
	protected boolean markerTypesDefault = true;
	protected boolean markerTypesAll = false;
	protected Set<String> markerTypes = new HashSet<>();
	protected boolean printErrorMarkers = false;

	protected static final String MATCH_ALL_CONFIGS = ".*"; //$NON-NLS-1$

	/*
	 *  Find all project build configurations that match the regular expression ("project/config")
	 */
	protected Map<IProject, Set<ICConfigurationDescription>> matchConfigurations(String regularExpression,
			IProject[] projectList, Map<IProject, Set<ICConfigurationDescription>> cfgMap) {
		try {
			int separatorIndex = regularExpression.indexOf('/');

			String projectRegEx;
			String configRegEx;
			if (separatorIndex == -1 || separatorIndex == regularExpression.length() - 1) {
				// build all configurations for this project
				projectRegEx = regularExpression;
				configRegEx = MATCH_ALL_CONFIGS;
			} else {
				projectRegEx = regularExpression.substring(0, separatorIndex);
				configRegEx = regularExpression.substring(separatorIndex + 1, regularExpression.length());
			}

			Pattern projectPattern = Pattern.compile(projectRegEx);
			Pattern configPattern = Pattern.compile(configRegEx);

			// Find the projects that match the regular expression
			boolean projectMatched = false;
			boolean configMatched = false;
			for (IProject project : projectList) {
				Matcher projectMatcher = projectPattern.matcher(project.getName());

				if (projectMatcher.matches()) {
					projectMatched = true;
					// Find the configurations that match the regular expression
					ICProjectDescription desc = CoreModel.getDefault().getProjectDescription(project, false);
					if (desc == null) {
						System.err.println(HeadlessBuildMessages.HeadlessBuilder_project + project.getName()
								+ HeadlessBuildMessages.HeadlessBuilder_Not_CDT_Proj);
						continue;
					}
					ICConfigurationDescription[] cfgs = desc.getConfigurations();

					for (ICConfigurationDescription cfg : cfgs) {
						Matcher cfgMatcher = configPattern.matcher(cfg.getName());

						if (cfgMatcher.matches()) {
							configMatched = true;
							// Build this configuration for this project
							Set<ICConfigurationDescription> set = cfgMap.get(project);
							if (set == null)
								set = new HashSet<>();
							set.add(cfg);
							cfgMap.put(project, set);
						}
					}
				}
			}
			if (!projectMatched)
				System.err.println(HeadlessBuildMessages.HeadlessBuilder_NoProjectMatched + regularExpression
						+ HeadlessBuildMessages.HeadlessBuilder_Skipping2);
			else if (!configMatched)
				System.err.println(HeadlessBuildMessages.HeadlessBuilder_NoConfigMatched + regularExpression
						+ HeadlessBuildMessages.HeadlessBuilder_Skipping2);
		} catch (PatternSyntaxException e) {
			System.err.println(HeadlessBuildMessages.HeadlessBuilder_RegExSyntaxError + e.toString());
			System.err.println(HeadlessBuildMessages.HeadlessBuilder_Skipping + regularExpression
					+ HeadlessBuildMessages.HeadlessBuilder_Quote);
		}
		return cfgMap;
	}

	/*
	 *  Build the given configurations using the specified build type (FULL, CLEAN, INCREMENTAL)
	 */
	protected void buildConfigurations(Map<IProject, Set<ICConfigurationDescription>> projConfigs,
			final IProgressMonitor monitor, final int buildType) throws CoreException {
		for (Map.Entry<IProject, Set<ICConfigurationDescription>> entry : projConfigs.entrySet()) {
			Set<ICConfigurationDescription> cfgDescs = entry.getValue();

			IConfiguration[] configs = new IConfiguration[cfgDescs.size()];
			int i = 0;
			for (ICConfigurationDescription cfgDesc : cfgDescs)
				configs[i++] = ManagedBuildManager.getConfigurationForDescription(cfgDesc);

			ManagedBuildManager.buildConfigurations(configs, null, new SubProgressMonitor(monitor, 1), true, buildType);
		}
	}

	/**
	 * Import a project into the workspace
	 * @param projURIStr base URI string
	 * @param recurse should we recurse down the URI importing all projects?
	 * @return int OK / ERROR
	 */
	protected int importProject(String projURIStr, boolean recurse) throws CoreException {
		IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
		IProgressMonitor monitor = new PrintingProgressMonitor();
		InputStream in = null;
		try {
			URI project_uri = null;
			try {
				project_uri = URI.create(projURIStr);
			} catch (Exception e) {
				// Will be treated as straightforward path in the case below
			}

			// Handle local paths as well
			if (project_uri == null || project_uri.getScheme() == null) {
				IPath p = new Path(projURIStr).addTrailingSeparator();
				project_uri = URIUtil.toURI(p);

				// Handle relative paths as relative to cwd
				if (project_uri.getScheme() == null) {
					String cwd = System.getProperty("user.dir"); //$NON-NLS-1$
					p = new Path(cwd).addTrailingSeparator();
					p = p.append(projURIStr);
					project_uri = URIUtil.toURI(p);
				}
				if (project_uri.getScheme() == null) {
					System.err.println(HeadlessBuildMessages.HeadlessBuilder_invalid_uri + project_uri);
					return ERROR;
				}
			}

			if (recurse) {
				if (!EFS.getStore(project_uri).fetchInfo().exists()) {
					System.err.println(HeadlessBuildMessages.HeadlessBuilder_Directory + project_uri
							+ HeadlessBuildMessages.HeadlessBuilder_cant_be_found);
					return ERROR;
				}
				for (IFileStore info : EFS.getStore(project_uri).childStores(EFS.NONE, monitor)) {
					if (!info.fetchInfo().isDirectory())
						continue;
					int status = importProject(info.toURI().toString(), recurse);
					if (status != OK)
						return status;
				}
			}

			// Load the project description
			IFileStore fstore = EFS.getStore(project_uri).getChild(".project"); //$NON-NLS-1$
			if (!fstore.fetchInfo().exists()) {
				if (!recurse) {
					System.err.println(HeadlessBuildMessages.HeadlessBuilder_project + project_uri
							+ HeadlessBuildMessages.HeadlessBuilder_cant_be_found);
					return ERROR;
				}
				// .project not found; OK if we're not recursing
				return OK;
			}

			in = fstore.openInputStream(EFS.NONE, monitor);
			IProjectDescription desc = root.getWorkspace().loadProjectDescription(in);

			// Check that a project with the same name doesn't already exist in the workspace
			IProject project = root.getProject(desc.getName());
			if (project.exists()) {
				// It's ok if the project we're importing is the same as one already in the workspace
				if (URIUtil.equals(project.getLocationURI(), project_uri)) {
					project.open(monitor);
					return OK;
				}
				System.err.println(HeadlessBuildMessages.HeadlessBuilder_project + desc.getName()
						+ HeadlessBuildMessages.HeadlessBuilder_already_exists_in_workspace);
				return ERROR;
			}
			// Create and open the project
			// Note that if the project exists directly under the workspace root, we can't #setLocationURI(...)
			if (!URIUtil.equals(
					org.eclipse.core.runtime.URIUtil.append(ResourcesPlugin.getWorkspace().getRoot().getLocationURI(),
							org.eclipse.core.runtime.URIUtil.lastSegment(project_uri)),
					project_uri))
				desc.setLocationURI(project_uri);
			else
				project_uri = null;
			// Check the URI is valid for a project in this workspace
			if (!root.getWorkspace().validateProjectLocationURI(project, project_uri).equals(Status.OK_STATUS)) {
				System.err.println(HeadlessBuildMessages.HeadlessBuilder_URI + project_uri
						+ HeadlessBuildMessages.HeadlessBuilder_is_not_valid_in_workspace);
				return ERROR;
			}

			project.create(desc, monitor);
			project.open(monitor);
		} finally {
			try {
				if (in != null)
					in.close();
			} catch (IOException e2) {
				/* don't care */ }
		}
		return OK;
	}

	protected boolean isProjectSuccesfullyBuild(IProject project) {
		try {
			for (String markerType : markerTypes) {
				int findMaxProblemSeverity = project.findMaxProblemSeverity(markerType, true, IResource.DEPTH_INFINITE);
				if (findMaxProblemSeverity >= IMarker.SEVERITY_ERROR) {
					return false;
				}
			}
		} catch (CoreException e) {
			ManagedBuilderCorePlugin.log(e);
			return false;
		}
		return true;
	}

	protected void accumulateErrorMarkers(IProject project, List<String> allBuildErrors) {
		try {
			for (String markerType : markerTypes) {
				IMarker[] findMarkers = project.findMarkers(markerType, true, IResource.DEPTH_INFINITE);
				for (IMarker marker : findMarkers) {
					int severity = marker.getAttribute(IMarker.SEVERITY, IMarker.SEVERITY_INFO);
					if (severity >= IMarker.SEVERITY_ERROR) {
						// the string format of Markers is not API and recommended only for debugging, but
						// it suits our purpose here.
						allBuildErrors.add(marker.toString());
					}
				}
			}
		} catch (CoreException e) {
			ManagedBuilderCorePlugin.log(e);
		}
	}

	@Override
	public Object start(IApplicationContext context) throws Exception {
		// Build result: whether projects were built successfully
		boolean buildSuccessful = true;
		List<String> allBuildErrors = new ArrayList<>();

		// Check its OK to use this workspace as IDEApplication does
		if (!checkInstanceLocation())
			return ERROR;

		IProgressMonitor monitor = new PrintingProgressMonitor();
		IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();

		final boolean isAutoBuilding = root.getWorkspace().isAutoBuilding();
		try {
			{
				// Turn off workspace auto-build
				IWorkspaceDescription desc = root.getWorkspace().getDescription();
				desc.setAutoBuilding(false);
				root.getWorkspace().setDescription(desc);
			}

			if (!root.isAccessible()) {
				System.err.println(HeadlessBuildMessages.HeadlessBuilder_Workspace + root.getLocationURI().toString()
						+ HeadlessBuildMessages.HeadlessBuilder_is_not_accessible);
				return ERROR;
			}

			// Handle user provided arguments
			if (!getArguments((String[]) context.getArguments().get(IApplicationContext.APPLICATION_ARGS)))
				return ERROR;

			if (markerTypesDefault || markerTypesAll) {
				markerTypes.clear();
				markerTypes.add(IMarker.PROBLEM);
			}

			if (disableIndexer) {
				CCorePlugin.getIndexManager().setDefaultIndexerId(IPDOMManager.ID_NO_INDEXER);
				// yes, this is ugly - but I haven't found a way to "officially"
				// stop the indexer.
				// The problem is that the "workspace job" for the
				// "index manager" (PDOMIndexerJob) is created and scheduled
				// during plugin activation (CCorePlugin.start(BundleContext))
				// and therefore, it may already be running when we first reach
				// this line of code. Therefore, we do not just (temporarily)
				// disable the indexer but also stop it.
				if (CCorePlugin.getIndexManager() instanceof PDOMManager) {
					PDOMManager man = (PDOMManager) CCorePlugin.getIndexManager();
					man.shutdown();
				}
			}

			// Set the console environment so build output is echo'd to stdout
			if (System.getProperty("org.eclipse.cdt.core.console") == null) //$NON-NLS-1$
				System.setProperty("org.eclipse.cdt.core.console", "org.eclipse.cdt.core.systemConsole"); //$NON-NLS-1$ //$NON-NLS-2$

			/*
			 * Perform the project import
			 */
			// Import any projects that need importing
			for (String projURIStr : projectsToImport) {
				int status = importProject(projURIStr, false);
				if (status != OK)
					return status;
			}
			for (String projURIStr : projectTreeToImport) {
				int status = importProject(projURIStr, true);
				if (status != OK)
					return status;
			}

			// Hook in our external settings to the build
			HeadlessBuilderExternalSettingsProvider.hookExternalSettingsProvider();

			IProject[] allProjects = root.getProjects();
			// Map from Project -> Configurations to build. We also Build all projects which are clean'd
			Map<IProject, Set<ICConfigurationDescription>> configsToBuild = new HashMap<>();

			/*
			 * Perform the Clean / Build
			 */
			final boolean buildAllConfigs = ACBuilder.needAllConfigBuild();
			try {
				// Set the tool options for all project configurations
				// (This can't be done just for the projects being built, as they
				// may cause other projects to be built via references)
				if (!toolOptions.isEmpty())
					for (IProject project : allProjects) {
						IManagedBuildInfo info = ManagedBuildManager.getBuildInfo(project);
						if (info == null)
							continue;
						IManagedProject mProj = info.getManagedProject();
						IConfiguration[] cfgs = mProj.getConfigurations();
						for (IConfiguration cfg : cfgs)
							setToolOptions(cfg);
						ManagedBuildManager.saveBuildInfo(project, true);
					}

				// Clean the projects
				if (cleanAll) {
					// Ensure we clean all the configurations
					ACBuilder.setAllConfigBuild(true);

					System.out.println(HeadlessBuildMessages.HeadlessBuilder_cleaning_all_projects);
					root.getWorkspace().build(IncrementalProjectBuilder.CLEAN_BUILD, monitor);

					// Reset the build_all_configs preference value to its previous state
					ACBuilder.setAllConfigBuild(buildAllConfigs);
				} else {
					// Resolve the regular expression project names to build configurations
					for (String regEx : projectRegExToClean)
						matchConfigurations(regEx, allProjects, configsToBuild);
					// Clean the list of configurations
					buildConfigurations(configsToBuild, monitor, IncrementalProjectBuilder.CLEAN_BUILD);
				}

				// Build the projects the user wants building
				if (buildAll) {
					// Ensure we build all the configurations
					ACBuilder.setAllConfigBuild(true);

					System.out.println(HeadlessBuildMessages.HeadlessBuilder_building_all);
					root.getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, monitor);
					for (IProject p : root.getProjects()) {
						buildSuccessful = buildSuccessful && isProjectSuccesfullyBuild(p);
						if (printErrorMarkers) {
							accumulateErrorMarkers(p, allBuildErrors);
						}
					}
				} else {
					// Resolve the regular expression project names to build configurations
					for (String regEx : projectRegExToBuild)
						matchConfigurations(regEx, allProjects, configsToBuild);
					// Build the list of configurations
					buildConfigurations(configsToBuild, monitor, IncrementalProjectBuilder.FULL_BUILD);
					for (IProject p : configsToBuild.keySet()) {
						buildSuccessful = buildSuccessful && isProjectSuccesfullyBuild(p);
						if (printErrorMarkers) {
							accumulateErrorMarkers(p, allBuildErrors);
						}
					}
				}
			} finally {
				// Reset the tool options
				if (!savedToolOptions.isEmpty())
					for (IProject project : allProjects) {
						IManagedBuildInfo info = ManagedBuildManager.getBuildInfo(project);
						if (info == null)
							continue;
						IManagedProject mProj = info.getManagedProject();
						IConfiguration[] cfgs = mProj.getConfigurations();
						for (IConfiguration cfg : cfgs)
							resetToolOptions(cfg);
						ManagedBuildManager.saveBuildInfo(project, true);
					}
				// Reset the build_all_configs preference value to its previous state
				ACBuilder.setAllConfigBuild(buildAllConfigs);
				// Unhook the external settings provider
				HeadlessBuilderExternalSettingsProvider.unhookExternalSettingsProvider();
			}
		} finally {
			// Wait for any outstanding jobs to finish
			while (!Job.getJobManager().isIdle())
				Thread.sleep(10);

			// Reset workspace auto-build preference
			IWorkspaceDescription desc = root.getWorkspace().getDescription();
			desc.setAutoBuilding(isAutoBuilding);
			root.getWorkspace().setDescription(desc);

			// Save modified workspace (bug 513763)
			root.getWorkspace().save(true, monitor);
		}
		if (printErrorMarkers) {
			if (buildSuccessful) {
				System.out.println(HeadlessBuildMessages.HeadlessBuilder_completed_successfully);
			} else {
				System.out.println(HeadlessBuildMessages.HeadlessBuilder_encountered_errors);
				for (String buildError : allBuildErrors) {
					System.err.println(buildError);
				}
			}
		}
		return buildSuccessful ? OK : ERROR;
	}

	/**
	 * Verify that it's safe to use the specified workspace. i.e. that we can write to it and that it's not
	 * already locked / in-use.
	 *
	 * Return true if a valid workspace path has been set and false otherwise.
	 *
	 * @return true if a valid instance location has been set and false otherwise
	 */
	protected boolean checkInstanceLocation() {
		// -data @none was specified but an ide requires workspace
		Location instanceLoc = Platform.getInstanceLocation();
		if (instanceLoc == null || !instanceLoc.isSet()) {
			System.err.println(HeadlessBuildMessages.HeadlessBuilder_MustSpecifyWorkspace);
			return false;
		}

		// -data "/valid/path", workspace already set
		try {
			// at this point its valid, so try to lock it to prevent concurrent use
			if (!instanceLoc.lock()) {
				System.err.println(HeadlessBuildMessages.HeadlessBuilder_WorkspaceInUse);
				return false;
			}
			return true;
		} catch (IOException e) {
			System.err.println(HeadlessBuildMessages.HeadlessBuilder_CouldntLockWorkspace);
		}
		return false;
	}

	/**
	 * Helper method to process expected arguments
	 *
	 * Arguments
	 *   -import     {[uri:/]/path/to/project}
	 *   -importAll  {[uri:/]/path/to/projectTreeURI} Import all projects in the tree
	 *   -build      {project_name_reg_ex/config_name_reg_ex | all}
	 *   -cleanBuild {project_name_reg_ex/config_name_reg_ex | all}
	 *   -I          {include_path} additional include_path to add to tools
	 *   -include    {include_file} additional include_file to pass to tools
	 *   -D          {prepoc_define} addition preprocessor defines to pass to the tools
	 *   -E			 {var=value} replace/add value to environment variable when running all tools
	 *   -Ea		 {var=value} append value to environment variable when running all tools
	 *   -Ep		 {var=value} prepend value to environment variable when running all tools
	 *   -Er         {var} remove/unset the given environment variable
	 *   -T          {toolid} {optionid=value} replace a tool option value
	 *   -Ta         {toolid} {optionid=value} append to a tool option value
	 *   -Tp         {toolid} {optionid=value} prepend to a tool option value
	 *   -Tr         {toolid} {optionid=value} remove a tool option value
	 *   -no-indexer Disable indexer
	 *   -markerType Which markers to consider
	 *   -printErrorMarkers Print all error markers that caused build to fail
	 *
	 * Each argument may be specified more than once
	 * @param args String[] of arguments to parse
	 * @return boolean indicating success
	 */
	public boolean getArguments(String[] args) {
		try {
			if (args == null || args.length == 0)
				throw new Exception(HeadlessBuildMessages.HeadlessBuilder_no_arguments);
			for (int i = 0; i < args.length; i++) {
				if ("-help".equals(args[i])) { //$NON-NLS-1$
					throw new Exception(HeadlessBuildMessages.HeadlessBuilder_help_requested);
				} else if ("-import".equals(args[i])) { //$NON-NLS-1$
					projectsToImport.add(args[++i]);
				} else if ("-importAll".equals(args[i])) { //$NON-NLS-1$
					projectTreeToImport.add(args[++i]);
				} else if ("-build".equals(args[i])) { //$NON-NLS-1$
					projectRegExToBuild.add(args[++i]);
				} else if ("-cleanBuild".equals(args[i])) { //$NON-NLS-1$
					projectRegExToClean.add(args[++i]);
				} else if ("-D".equals(args[i])) { //$NON-NLS-1$
					String macro = args[++i];
					String macroVal = ""; //$NON-NLS-1$
					if (macro.indexOf('=') != -1) {
						macroVal = macro.substring(macro.indexOf('=') + 1);
						macro = macro.substring(0, macro.indexOf('='));
					}
					HeadlessBuilderExternalSettingsProvider.additionalSettings
							.add(CDataUtil.createCMacroEntry(macro, macroVal, 0));
				} else if ("-I".equals(args[i])) { //$NON-NLS-1$
					HeadlessBuilderExternalSettingsProvider.additionalSettings
							.add(CDataUtil.createCIncludePathEntry(args[++i], 0));
				} else if ("-include".equals(args[i])) { //$NON-NLS-1$
					HeadlessBuilderExternalSettingsProvider.additionalSettings
							.add(CDataUtil.createCIncludeFileEntry(args[++i], 0));
				} else if ("-E".equals(args[i])) { //$NON-NLS-1$
					addEnvironmentVariable(args[++i], IEnvironmentVariable.ENVVAR_REPLACE);
				} else if ("-Ea".equals(args[i])) { //$NON-NLS-1$
					addEnvironmentVariable(args[++i], IEnvironmentVariable.ENVVAR_APPEND);
				} else if ("-Ep".equals(args[i])) { //$NON-NLS-1$
					addEnvironmentVariable(args[++i], IEnvironmentVariable.ENVVAR_PREPEND);
				} else if ("-Er".equals(args[i])) { //$NON-NLS-1$
					addEnvironmentVariable(args[++i], IEnvironmentVariable.ENVVAR_REMOVE);
				} else if ("-T".equals(args[i])) { //$NON-NLS-1$
					String toolId = args[++i];
					String option = args[++i];
					addToolOption(toolId, option, ToolOption.REPLACE);
				} else if ("-Ta".equals(args[i])) { //$NON-NLS-1$
					String toolId = args[++i];
					String option = args[++i];
					addToolOption(toolId, option, ToolOption.APPEND);
				} else if ("-Tp".equals(args[i])) { //$NON-NLS-1$
					String toolId = args[++i];
					String option = args[++i];
					addToolOption(toolId, option, ToolOption.PREPEND);
				} else if ("-Tr".equals(args[i])) { //$NON-NLS-1$
					String toolId = args[++i];
					String option = args[++i];
					addToolOption(toolId, option, ToolOption.REMOVE);
				} else if ("-no-indexer".equals(args[i])) { //$NON-NLS-1$
					disableIndexer = true;
				} else if ("-markerType".equals(args[i])) { //$NON-NLS-1$
					addMarkerType(args[++i]);
				} else if ("-printErrorMarkers".equals(args[i])) { //$NON-NLS-1$
					printErrorMarkers = true;
				} else {
					throw new Exception(HeadlessBuildMessages.HeadlessBuilder_unknown_argument + args[i]);
				}
			}
		} catch (Exception e) {
			// Print usage
			System.err.println(
					HeadlessBuildMessages.HeadlessBuilder_invalid_argument + args != null ? Arrays.toString(args) : ""); //$NON-NLS-1$
			System.err.println(HeadlessBuildMessages.HeadlessBuilder_Error + e.getMessage());
			System.err.println(HeadlessBuildMessages.HeadlessBuilder_usage);
			System.err.println(HeadlessBuildMessages.HeadlessBuilder_usage_import);
			System.err.println(HeadlessBuildMessages.HeadlessBuilder_importAll);
			System.err.println(HeadlessBuildMessages.HeadlessBuilder_usage_build);
			System.err.println(HeadlessBuildMessages.HeadlessBuilder_usage_clean_build);
			System.err.println(HeadlessBuildMessages.HeadlessBuilder_usage_no_indexer);
			System.err.println(HeadlessBuildMessages.HeadlessBuilder_usage_marker_type);
			System.err.println(HeadlessBuildMessages.HeadlessBuilder_usage_print_all_error_markers);
			System.err.println(HeadlessBuildMessages.HeadlessBuilder_InlucdePath);
			System.err.println(HeadlessBuildMessages.HeadlessBuilder_IncludeFile);
			System.err.println(HeadlessBuildMessages.HeadlessBuilder_PreprocessorDefine);
			System.err.println(HeadlessBuildMessages.HeadlessBuilder_EnvVar_Replace);
			System.err.println(HeadlessBuildMessages.HeadlessBuilder_EnvVar_Append);
			System.err.println(HeadlessBuildMessages.HeadlessBuilder_EnvVar_Prepend);
			System.err.println(HeadlessBuildMessages.HeadlessBuilder_EnvVar_Remove);
			System.err.println(HeadlessBuildMessages.HeadlessBuilder_ToolOption_Replace);
			System.err.println(HeadlessBuildMessages.HeadlessBuilder_ToolOption_Append);
			System.err.println(HeadlessBuildMessages.HeadlessBuilder_ToolOption_Prepend);
			System.err.println(HeadlessBuildMessages.HeadlessBuilder_ToolOption_Remove);
			System.err.println(HeadlessBuildMessages.HeadlessBuilder_ToolOption_Types);
			return false;
		}

		if (projectRegExToClean.contains("all") || projectRegExToClean.contains("*")) { //$NON-NLS-1$ //$NON-NLS-2$
			cleanAll = true;
			buildAll = true;
			projectRegExToClean.remove("all"); //$NON-NLS-1$
			projectRegExToClean.remove("*"); //$NON-NLS-1$
		}
		if (projectRegExToBuild.contains("all") || projectRegExToBuild.contains("*")) { //$NON-NLS-1$ //$NON-NLS-2$
			buildAll = true;
			projectRegExToBuild.remove("all"); //$NON-NLS-1$
			projectRegExToBuild.remove("*"); //$NON-NLS-1$
		}

		return true;
	}

	protected void addEnvironmentVariable(String string, int op) throws Exception {
		String[] parts = string.split("=", 2); //$NON-NLS-1$
		String name = parts[0];
		String value = ""; //$NON-NLS-1$
		if (parts.length > 1)
			value = parts[1];
		EnvironmentVariableManager.fUserSupplier.createOverrideVariable(name, value, op, null);
	}

	protected void addToolOption(String toolId, String option, int operation) {
		String optionId = option;
		String value = ""; //$NON-NLS-1$
		if (option.indexOf('=') != -1) {
			value = option.substring(option.indexOf('=') + 1);
			optionId = option.substring(0, option.indexOf('='));
		}
		toolOptions.add(new ToolOption(toolId, optionId, value, operation));
	}

	protected void addMarkerType(String markerType) {
		markerTypesDefault = false;
		if ("all".equals(markerType)) { //$NON-NLS-1$
			markerTypesAll = true;
		} else if ("cdt".equals(markerType)) { //$NON-NLS-1$
			markerTypes.add(ICModelMarker.C_MODEL_PROBLEM_MARKER);
		} else {
			markerTypes.add(markerType);
		}
	}

	/**
	 * Set the tool options in a configuration, and saves the current values so that
	 * they can be restored at the end of the build. These are reset after the build
	 * by calls to {@link #resetToolOptions(IConfiguration)}.
	 */
	@SuppressWarnings("unchecked")
	protected void setToolOptions(IConfiguration configuration) throws BuildException {
		if (!savedToolOptions.containsKey(configuration.getId()))
			savedToolOptions.put(configuration.getId(), new HashSet<SavedToolOption>());
		Set<SavedToolOption> savedToolOptionsSet = savedToolOptions.get(configuration.getId());
		for (ToolOption toolOption : toolOptions) {
			ITool[] tools = configuration.getToolsBySuperClassId(toolOption.toolId);
			for (ITool tool : tools) {
				IOption option = tool.getOptionBySuperClassId(toolOption.optionId);
				if (option != null) {
					// Save the tool option so that it can be reset later (does not overwrite existing
					// saved options, so if an option is specified multiple times it will be reset to the
					// correct value)
					savedToolOptionsSet.add(new SavedToolOption(tool.getId(), option.getId(), option.getValue()));
					// Update the value of the tool option in a type-dependent manner
					switch (option.getValueType()) {
					case IOption.BOOLEAN:
						boolean booleanValue = (Boolean) option.getDefaultValue();
						if (toolOption.operation != ToolOption.REMOVE)
							booleanValue = Boolean.parseBoolean(toolOption.value);
						ManagedBuildManager.setOption(configuration, tool, option, booleanValue);
						break;
					case IOption.STRING_LIST:
					case IOption.INCLUDE_PATH:
					case IOption.PREPROCESSOR_SYMBOLS:
					case IOption.LIBRARIES:
					case IOption.OBJECTS:
					case IOption.INCLUDE_FILES:
					case IOption.LIBRARY_PATHS:
					case IOption.LIBRARY_FILES:
					case IOption.MACRO_FILES:
					case IOption.UNDEF_INCLUDE_PATH:
					case IOption.UNDEF_PREPROCESSOR_SYMBOLS:
					case IOption.UNDEF_INCLUDE_FILES:
					case IOption.UNDEF_LIBRARY_PATHS:
					case IOption.UNDEF_LIBRARY_FILES:
					case IOption.UNDEF_MACRO_FILES:
						List<String> listValue = new ArrayList<>();
						switch (toolOption.operation) {
						case ToolOption.APPEND:
							listValue.addAll((List<String>) option.getValue());
							listValue.addAll(Arrays.asList(toolOption.value.split(","))); //$NON-NLS-1$
							break;
						case ToolOption.PREPEND:
							listValue.addAll(Arrays.asList(toolOption.value.split(","))); //$NON-NLS-1$
							listValue.addAll((List<String>) option.getValue());
							break;
						case ToolOption.REMOVE:
							listValue = (List<String>) option.getDefaultValue();
							break;
						default:
							listValue = Arrays.asList(toolOption.value.split(",")); //$NON-NLS-1$
							break;
						}
						ManagedBuildManager.setOption(configuration, tool, option,
								listValue == null ? new String[0] : listValue.toArray(new String[listValue.size()]));
						break;
					default: // IOption.ENUMERATED, IOption.STRING
						String stringValue = toolOption.value;
						switch (toolOption.operation) {
						case ToolOption.APPEND:
							stringValue = option.getValue() + stringValue;
							break;
						case ToolOption.PREPEND:
							stringValue = stringValue + option.getValue();
							break;
						case ToolOption.REMOVE:
							stringValue = (String) option.getDefaultValue();
							break;
						}
						ManagedBuildManager.setOption(configuration, tool, option, stringValue);
						break;
					}
				}
			}
		}
	}

	/**
	 * Reset the tool options that were set using {@link #setToolOptions(IConfiguration)}
	 */
	protected void resetToolOptions(IConfiguration configuration) throws BuildException {
		for (SavedToolOption toolOption : savedToolOptions.get(configuration.getId())) {
			IOption option = configuration.getTool(toolOption.toolId).getOptionById(toolOption.optionId);
			option.setValue(toolOption.value);
		}
	}

	@Override
	public void stop() {
	}
}
