/*******************************************************************************
 * Copyright (c) 2009, 2013 Andrew Gvozdev (Quoin Inc.) 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:
 *     Andrew Gvozdev (Quoin Inc.) - initial API and implementation
 *     Alex Ruiz (Google)
 *******************************************************************************/
package org.eclipse.cdt.internal.errorparsers;

import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeSet;

import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.ErrorParserManager;
import org.eclipse.cdt.core.IErrorParser;
import org.eclipse.cdt.core.IErrorParserNamed;
import org.eclipse.cdt.core.IMarkerGenerator;
import org.eclipse.cdt.core.errorparsers.ErrorParserNamedWrapper;
import org.eclipse.cdt.core.errorparsers.RegexErrorParser;
import org.eclipse.cdt.core.errorparsers.RegexErrorPattern;
import org.eclipse.cdt.internal.core.XmlUtil;
import org.eclipse.cdt.internal.core.model.Util;
import org.eclipse.core.filesystem.URIUtil;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtension;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.IExtensionRegistry;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.osgi.service.prefs.BackingStoreException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * ErrorParserExtensionManager manages error parser extensions, serialization and preferences.
 */
public class ErrorParserExtensionManager {
	private static final String STORAGE_ERRORPARSER_EXTENSIONS = "model.extensions.xml"; //$NON-NLS-1$
	private static final String PREFERENCE_ERRORPARSER_DEFAULT_IDS = "errorparser.default.ids"; //$NON-NLS-1$
	private static final String NONE = ""; //$NON-NLS-1$

	private static final String EXTENSION_POINT_ERROR_PARSER = "org.eclipse.cdt.core.ErrorParser"; //$NON-NLS-1$
	private static final String ELEM_CONTEXT = "context"; //$NON-NLS-1$
	private static final String ELEM_PLUGIN = "plugin"; //$NON-NLS-1$
	private static final String ELEM_EXTENSION = "extension"; //$NON-NLS-1$
	private static final String ELEM_ERRORPARSER = "errorparser"; //$NON-NLS-1$
	private static final String ELEM_PATTERN = "pattern"; //$NON-NLS-1$
	private static final String ATTR_CLASS = "class"; //$NON-NLS-1$
	private static final String ATTR_ID = "id"; //$NON-NLS-1$
	private static final String ATTR_NAME = "name"; //$NON-NLS-1$
	private static final String ATTR_POINT = "point"; //$NON-NLS-1$
	private static final String ATTR_TYPE = "type"; //$NON-NLS-1$

	private static final String ATTR_REGEX = "regex"; //$NON-NLS-1$
	private static final String ATTR_SEVERITY = "severity"; //$NON-NLS-1$
	private static final String ATTR_FILE = "file-expr"; //$NON-NLS-1$
	private static final String ATTR_LINE = "line-expr"; //$NON-NLS-1$
	private static final String ATTR_DESCRIPTION = "description-expr"; //$NON-NLS-1$
	private static final String ATTR_VARIABLE = "variable-expr"; //$NON-NLS-1$
	private static final String ATTR_EAT_LINE = "eat-processed-line"; //$NON-NLS-1$

	private static final String ATTR_VALUE_WARNING = "Warning"; //$NON-NLS-1$
	private static final String ATTR_VALUE_ERROR = "Error"; //$NON-NLS-1$
	private static final String ATTR_VALUE_INFO = "Info"; //$NON-NLS-1$
	private static final String ATTR_VALUE_IGNORE = "Ignore"; //$NON-NLS-1$

	private static final LinkedHashMap<String, IErrorParserNamed> fExtensionErrorParsers = new LinkedHashMap<>();
	private static final LinkedHashMap<String, IErrorParserNamed> fAvailableErrorParsers = new LinkedHashMap<>();
	private static final Map<String, Set<String>> fErrorParserContexts = new HashMap<>();
	private static LinkedHashMap<String, IErrorParserNamed> fUserDefinedErrorParsers = null;
	private static List<String> fDefaultErrorParserIds = null;

	private static class ErrorParserComparator implements Comparator<IErrorParserNamed> {
		// For the error parsers taken from platform extensions following sorting order applies:
		// - first regular error parsers
		// - then deprecated ones
		// - then contributed by test plugin
		// inside the same category sort by parser name
		@Override
		public int compare(IErrorParserNamed errorParser1, IErrorParserNamed errorParser2) {
			final String TEST_PLUGIN_ID = "org.eclipse.cdt.core.tests"; //$NON-NLS-1$
			final String DEPRECATED = CCorePlugin.getResourceString("CCorePlugin.Deprecated"); //$NON-NLS-1$

			boolean isTestPlugin1 = errorParser1.getId().startsWith(TEST_PLUGIN_ID);
			boolean isTestPlugin2 = errorParser2.getId().startsWith(TEST_PLUGIN_ID);
			if (isTestPlugin1 == true && isTestPlugin2 == false)
				return 1;
			if (isTestPlugin1 == false && isTestPlugin2 == true)
				return -1;

			boolean isDeprecated1 = errorParser1.getName().contains(DEPRECATED);
			boolean isDeprecated2 = errorParser2.getName().contains(DEPRECATED);
			if (isDeprecated1 == true && isDeprecated2 == false)
				return 1;
			if (isDeprecated1 == false && isDeprecated2 == true)
				return -1;

			return errorParser1.getName().compareTo(errorParser2.getName());
		}
	}

	static {
		loadUserDefinedErrorParsers();
		loadDefaultErrorParserIds();
		loadErrorParserExtensions();
	}

	/**
	 * Load user defined error parsers from workspace preference storage.
	 *
	 * @noreference This method is not intended to be referenced by clients.
	 */
	synchronized public static void loadUserDefinedErrorParsers() {
		fUserDefinedErrorParsers = null;
		Document doc = null;
		try {
			doc = XmlUtil.loadXml(getStoreURI(STORAGE_ERRORPARSER_EXTENSIONS));
		} catch (Exception e) {
			CCorePlugin.log("Can't load preferences from " + STORAGE_ERRORPARSER_EXTENSIONS, e); //$NON-NLS-1$
		}

		if (doc != null) {
			Set<IErrorParserNamed> sortedErrorParsers = new TreeSet<>(new ErrorParserComparator());
			loadErrorParserExtensions(doc, sortedErrorParsers);

			if (sortedErrorParsers.size() > 0) {
				fUserDefinedErrorParsers = new LinkedHashMap<>();
				for (IErrorParserNamed errorParser : sortedErrorParsers) {
					fUserDefinedErrorParsers.put(errorParser.getId(), errorParser);
				}
			}
		}
		recalculateAvailableErrorParsers();
	}

	/**
	 * Parse error parser contributed extensions from XML document.
	 *
	 * @param doc - source XML
	 * @param errorParsers - resulting list of error parsers
	 */
	private static void loadErrorParserExtensions(Document doc, Set<IErrorParserNamed> errorParsers) {
		errorParsers.clear();
		NodeList extensionNodes = doc.getElementsByTagName(ELEM_EXTENSION);
		for (int iext = 0; iext < extensionNodes.getLength(); iext++) {
			Node extensionNode = extensionNodes.item(iext);
			if (extensionNode.getNodeType() != Node.ELEMENT_NODE)
				continue;

			NodeList errorparserNodes = extensionNode.getChildNodes();
			for (int ierp = 0; ierp < errorparserNodes.getLength(); ierp++) {
				Node errorparserNode = errorparserNodes.item(ierp);
				if (errorparserNode.getNodeType() != Node.ELEMENT_NODE
						|| !ELEM_ERRORPARSER.equals(errorparserNode.getNodeName()))
					continue;

				String className = XmlUtil.determineAttributeValue(errorparserNode, ATTR_CLASS);
				try {
					IErrorParserNamed errorParser = createErrorParserCarcass(className,
							Platform.getExtensionRegistry());
					if (errorParser != null) {
						configureErrorParser(errorParser, errorparserNode);
						errorParsers.add(errorParser);
					}
				} catch (Exception e) {
					CCorePlugin.log(
							"Can't create class [" + className + "] while trying to load error parser extension", e); //$NON-NLS-1$ //$NON-NLS-2$
				}
			}
		}
	}

	/**
	 * Load workspace default error parser IDs to be used if no error parsers specified.
	 *
	 * @noreference This method is not intended to be referenced by clients.
	 */
	synchronized public static void loadDefaultErrorParserIds() {
		fDefaultErrorParserIds = null;
		IEclipsePreferences preferences = InstanceScope.INSTANCE.getNode(CCorePlugin.PLUGIN_ID);
		String ids = preferences.get(PREFERENCE_ERRORPARSER_DEFAULT_IDS, NONE);
		if (ids.equals(NONE)) {
			return;
		}

		fDefaultErrorParserIds = Arrays.asList(ids.split(String.valueOf(ErrorParserManager.ERROR_PARSER_DELIMITER)));
	}

	/**
	 * Load error parser contributed extensions.
	 *
	 * @noreference This method is not intended to be referenced by clients.
	 */
	synchronized public static void loadErrorParserExtensions() {
		Set<IErrorParserNamed> sortedErrorParsers = new TreeSet<>(new ErrorParserComparator());
		loadErrorParserExtensions(Platform.getExtensionRegistry(), sortedErrorParsers);

		fExtensionErrorParsers.clear();
		for (IErrorParserNamed errorParser : sortedErrorParsers) {
			fExtensionErrorParsers.put(errorParser.getId(), errorParser);
		}
		recalculateAvailableErrorParsers();
	}

	/**
	 * Load error parser contributed extensions from extension registry.
	 *
	 * @param registry - extension registry
	 * @param errorParsers - resulting set of error parsers
	 */
	private static void loadErrorParserExtensions(IExtensionRegistry registry, Set<IErrorParserNamed> errorParsers) {
		errorParsers.clear();
		IExtensionPoint extension = registry.getExtensionPoint(CCorePlugin.PLUGIN_ID,
				CCorePlugin.ERROR_PARSER_SIMPLE_ID);
		if (extension != null) {
			IExtension[] extensions = extension.getExtensions();
			for (IExtension ext : extensions) {
				try {
					String extensionID = ext.getUniqueIdentifier();
					String oldStyleId = extensionID;
					String oldStyleName = ext.getLabel();
					for (IConfigurationElement cfgEl : ext.getConfigurationElements()) {
						if (cfgEl.getName().equals(ELEM_ERRORPARSER)) {
							IErrorParserNamed errorParser = createErrorParserCarcass(oldStyleId, oldStyleName, cfgEl);
							if (errorParser != null) {
								configureErrorParser(errorParser, cfgEl);
								errorParsers.add(errorParser);
							}
						}
					}
				} catch (Exception e) {
					CCorePlugin.log("Cannot load ErrorParser extension " + ext.getUniqueIdentifier(), e); //$NON-NLS-1$
				}
			}
		}
	}

	/**
	 * Populate the list of available error parsers where workspace level user defined parsers
	 * overwrite contributed through error parser extension point.
	 */
	private static void recalculateAvailableErrorParsers() {
		fAvailableErrorParsers.clear();
		// put default parsers on top of the list
		List<String> ids = new ArrayList<>();
		if (fDefaultErrorParserIds != null) {
			for (String id : fDefaultErrorParserIds) {
				IErrorParserNamed errorParser = null;
				if (fUserDefinedErrorParsers != null) {
					errorParser = fUserDefinedErrorParsers.get(id);
				}
				if (errorParser == null) {
					errorParser = fExtensionErrorParsers.get(id);
				}
				if (errorParser != null) {
					fAvailableErrorParsers.put(id, errorParser);
					ids.add(id);
				}
			}
		}
		// then the rest in the order defined by comparator
		Set<IErrorParserNamed> sortedErrorParsers = new TreeSet<>(new ErrorParserComparator());

		if (fUserDefinedErrorParsers != null) {
			for (String id : fUserDefinedErrorParsers.keySet()) {
				if (!ids.contains(id)) {
					IErrorParserNamed errorParser = fUserDefinedErrorParsers.get(id);
					sortedErrorParsers.add(errorParser);
				}
			}
		}
		for (String id : fExtensionErrorParsers.keySet()) {
			if (!ids.contains(id)) {
				IErrorParserNamed errorParser = fExtensionErrorParsers.get(id);
				sortedErrorParsers.add(errorParser);
			}
		}

		for (IErrorParserNamed errorParser : sortedErrorParsers) {
			fAvailableErrorParsers.put(errorParser.getId(), errorParser);
		}
	}

	/**
	 * Serialize error parsers in workspace level storage.
	 *
	 * @throws CoreException if something goes wrong
	 */
	public static void serializeUserDefinedErrorParsers() throws CoreException {
		try {
			Document doc = XmlUtil.newDocument();
			Element elementPlugin = XmlUtil.appendElement(doc, ELEM_PLUGIN);

			if (fUserDefinedErrorParsers != null) {
				for (Entry<String, IErrorParserNamed> entry : fUserDefinedErrorParsers.entrySet()) {
					IErrorParserNamed errorParser = entry.getValue();
					addErrorParserExtension(elementPlugin, errorParser);
				}
			}

			URI uri = getStoreURI(STORAGE_ERRORPARSER_EXTENSIONS);
			String eol = Util.getLineSeparator(uri);
			if (eol == null)
				eol = Util.getDefaultLineSeparator();
			XmlUtil.serializeXml(doc, uri, eol);

		} catch (Exception e) {
			throw new CoreException(
					CCorePlugin.createStatus("Failed serializing to file " + STORAGE_ERRORPARSER_EXTENSIONS, e)); //$NON-NLS-1$
		}
	}

	/**
	 * Utility method to convert severity to string for the purpose of serializing in XML.
	 *
	 * @param severity - severity
	 * @return string representation
	 */
	private static String severityToString(int severity) {
		switch (severity) {
		case IMarkerGenerator.SEVERITY_INFO:
			return ATTR_VALUE_INFO;
		case IMarkerGenerator.SEVERITY_WARNING:
			return ATTR_VALUE_WARNING;
		case IMarkerGenerator.SEVERITY_ERROR_BUILD:
		case IMarkerGenerator.SEVERITY_ERROR_RESOURCE:
			return ATTR_VALUE_ERROR;
		}
		return ATTR_VALUE_IGNORE;
	}

	/**
	 * Utility method to de-serialize severity from XML.
	 *
	 * @param attrSeverity - string representation of the severity
	 * @return severity
	 */
	private static int stringToSeverity(String attrSeverity) {
		if (ATTR_VALUE_ERROR.equals(attrSeverity))
			return IMarkerGenerator.SEVERITY_ERROR_RESOURCE;
		if (ATTR_VALUE_WARNING.equals(attrSeverity))
			return IMarkerGenerator.SEVERITY_WARNING;
		if (ATTR_VALUE_INFO.equals(attrSeverity))
			return IMarkerGenerator.SEVERITY_INFO;

		return RegexErrorPattern.SEVERITY_SKIP;
	}

	/**
	 * Add error parser extension to XML fragment, normally under <plugin/> element.
	 *
	 * @param elementPlugin - element where to add error parser extension
	 * @param errorParserNamed - error parser to add
	 */
	private static void addErrorParserExtension(Element elementPlugin, IErrorParserNamed errorParserNamed) {
		String id = errorParserNamed.getId();
		String name = errorParserNamed.getName();
		String simpleId = getSimpleId(id);

		IErrorParser errorParser = errorParserNamed;
		if (errorParser instanceof ErrorParserNamedWrapper)
			errorParser = ((ErrorParserNamedWrapper) errorParser).getErrorParser();

		// <extension/>
		Element elementExtension = XmlUtil.appendElement(elementPlugin, ELEM_EXTENSION,
				new String[] { ATTR_ID, simpleId, ATTR_NAME, name, ATTR_POINT, EXTENSION_POINT_ERROR_PARSER, });

		// <errorparser/>
		Element elementErrorParser = XmlUtil.appendElement(elementExtension, ELEM_ERRORPARSER,
				new String[] { ATTR_ID, id, ATTR_NAME, name, ATTR_CLASS, errorParser.getClass().getCanonicalName(), });

		if (errorParserNamed instanceof RegexErrorParser) {
			RegexErrorParser regexErrorParser = (RegexErrorParser) errorParserNamed;
			RegexErrorPattern[] patterns = regexErrorParser.getPatterns();

			for (RegexErrorPattern pattern : patterns) {
				// <pattern/>
				@SuppressWarnings("unused")
				Element elementPattern = XmlUtil.appendElement(elementErrorParser, ELEM_PATTERN,
						new String[] { ATTR_SEVERITY, severityToString(pattern.getSeverity()), ATTR_REGEX,
								pattern.getPattern(), ATTR_FILE, pattern.getFileExpression(), ATTR_LINE,
								pattern.getLineExpression(), ATTR_DESCRIPTION, pattern.getDescriptionExpression(),
								ATTR_EAT_LINE, String.valueOf(pattern.isEatProcessedLine()), });
			}

		}
	}

	/**
	 * Determine simple ID of error parser as last segment of full or unique ID.
	 *
	 * @param uniqueId - full ID of error parser
	 * @return simple ID of error parser
	 */
	private static String getSimpleId(String uniqueId) {
		String simpleId = uniqueId;
		int dot = uniqueId.lastIndexOf('.');
		if (dot >= 0) {
			simpleId = uniqueId.substring(dot + 1);
		}
		return simpleId;
	}

	/**
	 * Save the list of default error parsers in preferences.
	 *
	 * @throws BackingStoreException in case of problem storing
	 */
	public static void serializeDefaultErrorParserIds() throws BackingStoreException {
		IEclipsePreferences preferences = InstanceScope.INSTANCE.getNode(CCorePlugin.PLUGIN_ID);
		String ids = NONE;
		if (fDefaultErrorParserIds != null) {
			ids = ErrorParserManager.toDelimitedString(fDefaultErrorParserIds.toArray(new String[0]));
		}

		preferences.put(PREFERENCE_ERRORPARSER_DEFAULT_IDS, ids);
		preferences.flush();
	}

	/**
	 * @param store - name of the store
	 * @return URI of the store in the plug-in state area to keep plug-in specific data.
	 */
	private static URI getStoreURI(String store) {
		IPath location = CCorePlugin.getDefault().getStateLocation().append(store);
		URI uri = URIUtil.toURI(location);
		return uri;
	}

	/**
	 * Creates empty non-configured error parser from extension point definition looking at "class" attribute.
	 * ID and name of error parser are assigned from first extension point encountered.
	 *
	 * @param className - full qualified class name of error parser.
	 * @param registry - extension registry
	 * @return new non-configured error parser
	 */
	private static IErrorParserNamed createErrorParserCarcass(String className, IExtensionRegistry registry) {
		if (className == null || className.length() == 0 || className.equals(RegexErrorParser.class.getName()))
			return new RegexErrorParser();

		try {
			IExtensionPoint extension = registry.getExtensionPoint(CCorePlugin.PLUGIN_ID,
					CCorePlugin.ERROR_PARSER_SIMPLE_ID);
			if (extension != null) {
				IExtension[] extensions = extension.getExtensions();
				for (IExtension ext : extensions) {
					String extensionID = ext.getUniqueIdentifier();
					String oldStyleId = extensionID;
					String oldStyleName = ext.getLabel();
					for (IConfigurationElement cfgEl : ext.getConfigurationElements()) {
						if (cfgEl.getName().equals(ELEM_ERRORPARSER)
								&& className.equals(cfgEl.getAttribute(ATTR_CLASS))) {
							return createErrorParserCarcass(oldStyleId, oldStyleName, cfgEl);
						}
					}
				}
			}
		} catch (Exception e) {
			CCorePlugin.log("Error creating error parser", e); //$NON-NLS-1$
		}
		return null;
	}

	/**
	 * Creates empty non-configured error parser as executable extension from extension point definition.
	 * If "class" attribute is empty RegexErrorParser is created.
	 *
	 * @param initialId - nominal ID of error parser
	 * @param initialName - nominal name of error parser
	 * @param ce - configuration element with error parser definition
	 * @return new non-configured error parser
	 * @throws CoreException in case of failure
	 */
	private static IErrorParserNamed createErrorParserCarcass(String initialId, String initialName,
			IConfigurationElement ce) throws CoreException {
		IErrorParserNamed errorParser = null;
		if (ce.getAttribute(ATTR_CLASS) != null) {
			IErrorParser ep = (IErrorParser) ce.createExecutableExtension(ATTR_CLASS);
			if (ep instanceof IErrorParserNamed) {
				errorParser = (IErrorParserNamed) ep;
				errorParser.setId(initialId);
				errorParser.setName(initialName);
			} else if (ep != null) {
				errorParser = new ErrorParserNamedWrapper(initialId, initialName, ep);
			}
		}
		if (errorParser == null) {
			errorParser = new RegexErrorParser(initialId, initialName);
		}
		return errorParser;
	}

	/**
	 * Configure error parser from XML error parser node.
	 *
	 * @param errorParser - error parser to configure
	 * @param errorparserNode - XML error parser node
	 */
	private static void configureErrorParser(IErrorParserNamed errorParser, Node errorparserNode) {
		String id = XmlUtil.determineAttributeValue(errorparserNode, ATTR_ID);
		String name = XmlUtil.determineAttributeValue(errorparserNode, ATTR_NAME);
		errorParser.setId(id);
		errorParser.setName(name);
		if (errorParser instanceof RegexErrorParser) {
			RegexErrorParser regexErrorParser = (RegexErrorParser) errorParser;

			NodeList patternNodes = errorparserNode.getChildNodes();
			for (int ipat = 0; ipat < patternNodes.getLength(); ipat++) {
				Node patternNode = patternNodes.item(ipat);
				if (patternNode.getNodeType() != Node.ELEMENT_NODE || !ELEM_PATTERN.equals(patternNode.getNodeName()))
					continue;

				String attrSeverity = XmlUtil.determineAttributeValue(patternNode, ATTR_SEVERITY);
				String regex = XmlUtil.determineAttributeValue(patternNode, ATTR_REGEX);
				String fileExpr = XmlUtil.determineAttributeValue(patternNode, ATTR_FILE);
				String lineExpr = XmlUtil.determineAttributeValue(patternNode, ATTR_LINE);
				String DescExpr = XmlUtil.determineAttributeValue(patternNode, ATTR_DESCRIPTION);
				String attrEatLine = XmlUtil.determineAttributeValue(patternNode, ATTR_EAT_LINE);

				int severity = stringToSeverity(attrSeverity);

				boolean eatLine = !Boolean.FALSE.toString().equals(attrEatLine); // if null default to true
				regexErrorParser.addPattern(
						new RegexErrorPattern(regex, fileExpr, lineExpr, DescExpr, null, severity, eatLine));
			}
		}
	}

	/**
	 * Configure error parser from extension configuration element.
	 *
	 * @param errorParser - error parser to configure
	 * @param cfgEl - extension configuration element
	 * @throws CoreException
	 */
	private static void configureErrorParser(IErrorParserNamed errorParser, IConfigurationElement cfgEl)
			throws CoreException {
		String id = cfgEl.getAttribute(ATTR_ID);
		if (id != null && id.length() > 0)
			errorParser.setId(id);
		String name = cfgEl.getAttribute(ATTR_NAME);
		if (name != null && name.length() > 0)
			errorParser.setName(name);

		if (errorParser instanceof RegexErrorParser) {
			RegexErrorParser regexErrorParser = (RegexErrorParser) errorParser;

			for (IConfigurationElement cepat : cfgEl.getChildren()) {
				if (cepat.getName().equals(ELEM_PATTERN)) {

					boolean eat = !Boolean.FALSE.toString().equals(cepat.getAttribute(ATTR_EAT_LINE));
					regexErrorParser.addPattern(new RegexErrorPattern(cepat.getAttribute(ATTR_REGEX),
							cepat.getAttribute(ATTR_FILE), cepat.getAttribute(ATTR_LINE),
							cepat.getAttribute(ATTR_DESCRIPTION), cepat.getAttribute(ATTR_VARIABLE),
							stringToSeverity(cepat.getAttribute(ATTR_SEVERITY)), eat));
				}
			}
		}
		findContexts(id, cfgEl);
	}

	/**
	 * Finds the contexts where an error parser can be used. Clients determine whether the found
	 * context values are valid.
	 *
	 * @param errorParserId the error parser id.
	 * @param errorParserElement represents an "errorparser" element in the extension point
	 *         "org.eclipse.cdt.core.ErrorParser".
	 */
	private static void findContexts(String errorParserId, IConfigurationElement errorParserElement) {
		Set<String> contexts = fErrorParserContexts.get(errorParserId);
		IConfigurationElement[] contextElements = errorParserElement.getChildren(ELEM_CONTEXT);
		if (contextElements.length == 0) {
			return;
		}
		boolean newContextCreated = false;
		if (contexts == null) {
			contexts = new HashSet<>();
			newContextCreated = true;
		}
		for (IConfigurationElement contextElement : contextElements) {
			String context = contextElement.getAttribute(ATTR_TYPE);
			if (context != null) {
				contexts.add(context);
			}
		}
		if (newContextCreated && !contexts.isEmpty()) {
			fErrorParserContexts.put(errorParserId, contexts);
		}
	}

	/**
	 * Return error parser as stored in internal list.
	 *
	 * @noreference This method is not intended to be referenced by clients.
	 * Use {@link #getErrorParserCopy(String, boolean)} instead.
	 *
	 * @param id - ID of error parser
	 * @return internal instance of error parser
	 */
	public static IErrorParser getErrorParserInternal(String id) {
		IErrorParserNamed errorParser = fAvailableErrorParsers.get(id);
		if (errorParser instanceof ErrorParserNamedWrapper)
			return ((ErrorParserNamedWrapper) errorParser).getErrorParser();
		return errorParser;
	}

	/**
	 * Set and store in workspace area user defined error parsers.
	 *
	 * @param errorParsers - array of user defined error parsers
	 * @throws CoreException in case of problems
	 */
	public static void setUserDefinedErrorParsers(IErrorParserNamed[] errorParsers) throws CoreException {
		setUserDefinedErrorParsersInternal(errorParsers);
		serializeUserDefinedErrorParsers();
	}

	/**
	 * Internal method to set user defined error parsers in memory.
	 *
	 * @noreference This method is not intended to be referenced by clients.
	 * Use {@link #setUserDefinedErrorParsers(IErrorParserNamed[])}.
	 *
	 * @param errorParsers - array of user defined error parsers
	 */
	public static void setUserDefinedErrorParsersInternal(IErrorParserNamed[] errorParsers) {
		if (errorParsers == null) {
			fUserDefinedErrorParsers = null;
		} else {
			Set<IErrorParserNamed> sortedErrorParsers = new TreeSet<>(new ErrorParserComparator());
			sortedErrorParsers.addAll(Arrays.asList(errorParsers));
			fUserDefinedErrorParsers = new LinkedHashMap<>();
			// set customized list
			for (IErrorParserNamed errorParser : sortedErrorParsers) {
				fUserDefinedErrorParsers.put(errorParser.getId(), errorParser);
			}
		}
		recalculateAvailableErrorParsers();
	}

	/**
	 * @return available error parsers IDs which include contributed through extension + user defined ones
	 * from workspace
	 */
	public static String[] getErrorParserAvailableIds() {
		return fAvailableErrorParsers.keySet().toArray(new String[fAvailableErrorParsers.size()]);
	}

	/**
	 * @return IDs of error parsers contributed through error parser extension point.
	 */
	public static String[] getErrorParserExtensionIds() {
		return fExtensionErrorParsers.keySet().toArray(new String[fExtensionErrorParsers.size()]);
	}

	/**
	 * @return default error parsers IDs to be used if error parser list is empty.
	 */
	public static String[] getUserDefinedErrorParserIds() {
		if (fUserDefinedErrorParsers != null)
			return fUserDefinedErrorParsers.keySet().toArray(new String[0]);
		return null;
	}

	/**
	 * Set and store default error parsers IDs to be used if error parser list is empty.
	 *
	 * @param ids - default error parsers IDs
	 * @throws BackingStoreException in case of problem with storing
	 */
	public static void setDefaultErrorParserIds(String[] ids) throws BackingStoreException {
		setDefaultErrorParserIdsInternal(ids);
		serializeDefaultErrorParserIds();
	}

	/**
	 * Set default error parsers IDs in internal list.
	 *
	 * @noreference This method is not intended to be referenced by clients.
	 * Use {@link #setDefaultErrorParserIds(String[])}.
	 *
	 * @param ids - default error parsers IDs
	 */
	public static void setDefaultErrorParserIdsInternal(String[] ids) {
		if (ids == null) {
			fDefaultErrorParserIds = null;
		} else {
			fDefaultErrorParserIds = new ArrayList<>(Arrays.asList(ids));
		}
		recalculateAvailableErrorParsers();
	}

	/**
	 * @return default error parsers IDs to be used if error parser list is empty.
	 */
	public static String[] getDefaultErrorParserIds() {
		if (fDefaultErrorParserIds == null) {
			return fAvailableErrorParsers.keySet().toArray(new String[fAvailableErrorParsers.size()]);
		}
		return fDefaultErrorParserIds.toArray(new String[fDefaultErrorParserIds.size()]);
	}

	/**
	 * @param id - ID of error parser
	 * @param isExtension - if {@code true} get unmodified copy of error parser defined as extension
	 * @return cloned copy of error parser. Note that {@link ErrorParserNamedWrapper} returns
	 * shallow copy with the same instance of underlying error parser.
	 */
	public static IErrorParserNamed getErrorParserCopy(String id, boolean isExtension) {
		IErrorParserNamed errorParser = isExtension ? fExtensionErrorParsers.get(id) : fAvailableErrorParsers.get(id);

		try {
			if (errorParser instanceof RegexErrorParser) {
				return (RegexErrorParser) ((RegexErrorParser) errorParser).clone();
			} else if (errorParser instanceof ErrorParserNamedWrapper) {
				return (ErrorParserNamedWrapper) ((ErrorParserNamedWrapper) errorParser).clone();
			}
		} catch (CloneNotSupportedException e) {
			CCorePlugin.log(e);
		}
		return errorParser;
	}

	/**
	 * @param context - indicates the context in which an error parser can be used.
	 * @return available error parsers ID, which include contributed through extension and user-
	 *         defined ones from workspace, that can be used in the given context.
	 */
	public static String[] getErrorParserAvailableIdsInContext(String context) {
		List<String> ids = new ArrayList<>();
		for (String id : fAvailableErrorParsers.keySet()) {
			if (getErrorParserContexts(id).contains(context)) {
				ids.add(id);
			}
		}
		return ids.toArray(new String[ids.size()]);
	}

	/**
	 * Returns all the contexts of the error parser with the given ID. Contexts are {@code String}
	 * values belonging to an error parser, defined as part the "errorparser" extension point
	 * element.
	 * <p>
	 * If an error parser does not have an explicit context, it is assumed that the error parser
	 * can be used in the "build" context.
	 * </p>
	 * @param id the id of the error parser.
	 * @return the contexts of the error parser with the given ID.
	 */
	private static Collection<String> getErrorParserContexts(String id) {
		Set<String> contexts = fErrorParserContexts.get(id);
		if (contexts == null) {
			return Collections.singletonList(ErrorParserManager.BUILD_CONTEXT);
		}
		return contexts;
	}
}
