/*******************************************************************************
 * Copyright (c) 2000, 2014 QNX Software Systems 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:
 *     QNX Software Systems - Initial API and implementation
 *     Freescale Semiconductor - Address watchpoints, https://bugs.eclipse.org/bugs/show_bug.cgi?id=118299
 *     Patrick Chuong (Texas Instruments) -	Update CDT ToggleBreakpointTargetFactory enablement (340177)
 *     Marc Khouzam (Ericsson) - Support for dynamic printf (400628)
 *******************************************************************************/
package org.eclipse.cdt.debug.core;

import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.model.ICProject;
import org.eclipse.cdt.debug.core.model.ICAddressBreakpoint;
import org.eclipse.cdt.debug.core.model.ICBreakpoint;
import org.eclipse.cdt.debug.core.model.ICBreakpoint2;
import org.eclipse.cdt.debug.core.model.ICBreakpointExtension;
import org.eclipse.cdt.debug.core.model.ICBreakpointType;
import org.eclipse.cdt.debug.core.model.ICDynamicPrintf;
import org.eclipse.cdt.debug.core.model.ICFunctionBreakpoint;
import org.eclipse.cdt.debug.core.model.ICLineBreakpoint;
import org.eclipse.cdt.debug.core.model.ICWatchpoint;
import org.eclipse.cdt.debug.core.model.ICWatchpoint2;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ProjectScope;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.core.variables.VariablesPlugin;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.IStatusHandler;
import org.eclipse.debug.core.model.IBreakpoint;
import org.osgi.service.prefs.BackingStoreException;
import org.w3c.dom.Document;

import com.ibm.icu.text.MessageFormat;

/**
 * Utility methods.
 */
public class CDebugUtils {

	public static boolean question(IStatus status, Object source) {
		Boolean result = Boolean.FALSE;
		IStatusHandler handler = DebugPlugin.getDefault().getStatusHandler(status);
		if (handler != null) {
			try {
				result = (Boolean) handler.handleStatus(status, source);
			} catch (CoreException e) {
			}
		}
		return result.booleanValue();
	}

	public static void info(IStatus status, Object source) {
		IStatusHandler handler = DebugPlugin.getDefault().getStatusHandler(status);
		if (handler != null) {
			try {
				handler.handleStatus(status, source);
			} catch (CoreException e) {
			}
		}
	}

	public static void error(IStatus status, Object source) {
		IStatusHandler handler = DebugPlugin.getDefault().getStatusHandler(status);
		if (handler != null) {
			try {
				handler.handleStatus(status, source);
			} catch (CoreException e) {
			}
		}
	}

	public static char[] getByteText(byte b) {
		return new char[] { charFromByte((byte) ((b >>> 4) & 0x0f)), charFromByte((byte) (b & 0x0f)) };
	}

	public static byte textToByte(char[] text) {
		byte result = 0;
		if (text.length == 2) {
			byte[] bytes = { charToByte(text[0]), charToByte(text[1]) };
			result = (byte) ((bytes[0] << 4) + bytes[1]);
		}
		return result;
	}

	public static char charFromByte(byte value) {
		if (value >= 0x0 && value <= 0x9)
			return (char) (value + '0');
		if (value >= 0xa && value <= 0xf)
			return (char) (value - 0xa + 'a');
		return '0';
	}

	public static byte charToByte(char ch) {
		if (Character.isDigit(ch)) {
			return (byte) (ch - '0');
		}
		if (ch >= 'a' && ch <= 'f') {
			return (byte) (0xa + ch - 'a');
		}
		if (ch >= 'A' && ch <= 'F') {
			return (byte) (0xa + ch - 'A');
		}
		return 0;
	}

	public static char bytesToChar(byte[] bytes) {
		try {
			return (char) Short.parseShort(new String(bytes), 16);
		} catch (RuntimeException e) {
		}
		return 0;
	}

	public static byte toByte(char[] bytes, boolean le) {
		if (bytes.length != 2)
			return 0;
		return (byte) Long.parseLong(bytesToString(bytes, le, true), 16);
	}

	public static short toUnsignedByte(char[] bytes, boolean le) {
		if (bytes.length != 2)
			return 0;
		return (short) Long.parseLong(bytesToString(bytes, le, false), 16);
	}

	public static short toShort(char[] bytes, boolean le) {
		if (bytes.length != 4)
			return 0;
		return (short) Long.parseLong(bytesToString(bytes, le, true), 16);
	}

	public static int toUnsignedShort(char[] bytes, boolean le) {
		if (bytes.length != 4)
			return 0;
		return (int) Long.parseLong(bytesToString(bytes, le, false), 16);
	}

	public static int toInt(char[] bytes, boolean le) {
		if (bytes.length != 8)
			return 0;
		return (int) Long.parseLong(bytesToString(bytes, le, true), 16);
	}

	public static long toUnsignedInt(char[] bytes, boolean le) {
		if (bytes.length != 8)
			return 0;
		return Long.parseLong(bytesToString(bytes, le, false), 16);
	}

	private static String bytesToString(char[] bytes, boolean le, boolean signed) {
		char[] copy = new char[bytes.length];
		if (le) {
			for (int i = 0; i < bytes.length / 2; ++i) {
				copy[2 * i] = bytes[bytes.length - 2 * i - 2];
				copy[2 * i + 1] = bytes[bytes.length - 2 * i - 1];
			}
		} else {
			System.arraycopy(bytes, 0, copy, 0, copy.length);
		}
		return new String(copy);
	}

	public static String prependString(String text, int length, char ch) {
		StringBuffer sb = new StringBuffer(length);
		if (text.length() > length) {
			sb.append(text.substring(0, length));
		} else {
			char[] prefix = new char[length - text.length()];
			Arrays.fill(prefix, ch);
			sb.append(prefix);
			sb.append(text);
		}
		return sb.toString();
	}

	public static boolean isReferencedProject(IProject parent, IProject project) {
		if (parent != null && parent.exists()) {
			List<IProject> projects = CDebugUtils.getReferencedProjects(project);
			for (IProject proj : projects) {
				if (proj.exists() && (proj.equals(project)))
					return true;
			}
		}
		return false;
	}

	/**
	 * Serializes a XML document into a string - encoded in UTF8 format, with platform line separators.
	 *
	 * @param doc document to serialize
	 * @param indent if the xml text should be indented.
	 *
	 * @return the document as a string
	 */
	public static String serializeDocument(Document doc, boolean indent) throws IOException, TransformerException {
		ByteArrayOutputStream s = new ByteArrayOutputStream();
		TransformerFactory factory = TransformerFactory.newInstance();
		Transformer transformer = factory.newTransformer();
		transformer.setOutputProperty(OutputKeys.METHOD, "xml"); //$NON-NLS-1$
		transformer.setOutputProperty(OutputKeys.INDENT, indent ? "yes" : "no"); //$NON-NLS-1$ //$NON-NLS-2$
		DOMSource source = new DOMSource(doc);
		StreamResult outputTarget = new StreamResult(s);
		transformer.transform(source, outputTarget);
		return s.toString("UTF8"); //$NON-NLS-1$
	}

	/**
	 * Serializes a XML document into a string - encoded in UTF8 format, with platform line separators.
	 *
	 * @param doc document to serialize
	 * @return the document as a string
	 */
	public static String serializeDocument(Document doc) throws IOException, TransformerException {
		return serializeDocument(doc, true);
	}

	public static boolean isNaN(Number value) {
		if (value instanceof Double) {
			return ((Double) value).isNaN();
		}
		if (value instanceof Float) {
			return ((Float) value).isNaN();
		}
		return false;
	}

	public static boolean isPositiveInfinity(Number value) {
		if (value instanceof Double) {
			return (((Double) value).isInfinite() && value.doubleValue() == Double.POSITIVE_INFINITY);
		}
		if (value instanceof Float) {
			return (((Float) value).isInfinite() && value.floatValue() == Float.POSITIVE_INFINITY);
		}
		return false;
	}

	public static boolean isNegativeInfinity(Number value) {
		if (value instanceof Double) {
			return (((Double) value).isInfinite() && value.doubleValue() == Double.NEGATIVE_INFINITY);
		}
		if (value instanceof Float) {
			return (((Float) value).isInfinite() && value.floatValue() == Float.NEGATIVE_INFINITY);
		}
		return false;
	}

	public static List<IProject> getReferencedProjects(IProject project) {
		ArrayList<IProject> list = new ArrayList<>(10);
		if (project != null && project.exists() && project.isOpen()) {
			IProject[] refs = new IProject[0];
			try {
				refs = project.getReferencedProjects();
			} catch (CoreException e) {
			}
			for (int i = 0; i < refs.length; ++i) {
				if (!project.equals(refs[i]) && refs[i] != null && refs[i].exists() && refs[i].isOpen()) {
					list.add(refs[i]);
					getReferencedProjects(project, refs[i], list);
				}
			}
		}
		return list;
	}

	private static void getReferencedProjects(IProject root, IProject project, List<IProject> list) {
		if (project != null && project.exists() && project.isOpen()) {
			IProject[] refs = new IProject[0];
			try {
				refs = project.getReferencedProjects();
			} catch (CoreException e) {
			}
			for (int i = 0; i < refs.length; ++i) {
				if (!list.contains(refs[i]) && refs[i] != null && !refs[i].equals(root) && refs[i].exists()
						&& refs[i].isOpen()) {
					list.add(refs[i]);
					getReferencedProjects(root, refs[i], list);
				}
			}
		}
	}

	public static String getBreakpointText(IBreakpoint breakpoint, boolean qualified) throws CoreException {
		try {
			if (breakpoint instanceof ICAddressBreakpoint) {
				return getAddressBreakpointText((ICAddressBreakpoint) breakpoint, qualified);
			}
			if (breakpoint instanceof ICFunctionBreakpoint) {
				return getFunctionBreakpointText((ICFunctionBreakpoint) breakpoint, qualified);
			}
			if (breakpoint instanceof ICLineBreakpoint) {
				return getLineBreakpointText((ICLineBreakpoint) breakpoint, qualified);
			}
			if (breakpoint instanceof ICWatchpoint) {
				return getWatchpointText((ICWatchpoint) breakpoint, qualified);
			}
			// This allows to create a new breakpoint without implementing one of the interfaces above and still see a label
			Object message = breakpoint.getMarker().getAttribute(IMarker.MESSAGE);
			if (message != null)
				return message.toString();
		} catch (CoreException e) {
			// don't log if breakpoint has been deleted
			IMarker marker = breakpoint.getMarker();
			if (marker == null || !marker.exists()) {
				return DebugCoreMessages.getString("CDebugUtils.breakpoint_deleted"); //$NON-NLS-1$
			}
			CDebugCorePlugin.log(e);
			return DebugCoreMessages.getString("CDebugUtils.exception_occurred"); //$NON-NLS-1$
		}
		return ""; //$NON-NLS-1$
	}

	protected static String getLineBreakpointText(ICLineBreakpoint breakpoint, boolean qualified) throws CoreException {
		StringBuffer label = new StringBuffer();
		appendSourceName(breakpoint, label, qualified);
		appendLineNumber(breakpoint, label);
		if (breakpoint instanceof ICDynamicPrintf) {
			appendPrintfString((ICDynamicPrintf) breakpoint, label);
		}
		appendBreakpointType(breakpoint, label);
		appendExtensionMessage(breakpoint, label);
		appendIgnoreCount(breakpoint, label);
		appendCondition(breakpoint, label);
		return label.toString();
	}

	protected static String getWatchpointText(ICWatchpoint watchpoint, boolean qualified) throws CoreException {
		StringBuffer label = new StringBuffer();
		appendSourceName(watchpoint, label, qualified);
		appendWatchExpression(watchpoint, label);
		if (watchpoint instanceof ICWatchpoint2) {
			ICWatchpoint2 wp2 = (ICWatchpoint2) watchpoint;
			appendWatchMemorySpace(wp2, label);
			appendWatchRange(wp2, label);
		}
		appendBreakpointType(watchpoint, label);
		appendExtensionMessage(watchpoint, label);
		appendIgnoreCount(watchpoint, label);
		appendCondition(watchpoint, label);
		return label.toString();
	}

	protected static String getAddressBreakpointText(ICAddressBreakpoint breakpoint, boolean qualified)
			throws CoreException {
		StringBuffer label = new StringBuffer();
		appendSourceName(breakpoint, label, qualified);
		appendAddress(breakpoint, label);
		if (breakpoint instanceof ICDynamicPrintf) {
			appendPrintfString((ICDynamicPrintf) breakpoint, label);
		}
		appendBreakpointType(breakpoint, label);
		appendExtensionMessage(breakpoint, label);
		appendIgnoreCount(breakpoint, label);
		appendCondition(breakpoint, label);
		return label.toString();
	}

	protected static String getFunctionBreakpointText(ICFunctionBreakpoint breakpoint, boolean qualified)
			throws CoreException {
		StringBuffer label = new StringBuffer();
		appendSourceName(breakpoint, label, qualified);
		appendFunction(breakpoint, label);
		if (breakpoint instanceof ICDynamicPrintf) {
			appendPrintfString((ICDynamicPrintf) breakpoint, label);
		}
		appendBreakpointType(breakpoint, label);
		appendExtensionMessage(breakpoint, label);
		appendIgnoreCount(breakpoint, label);
		appendCondition(breakpoint, label);
		return label.toString();
	}

	/**
	 * Append the Breakpoint Extension message to the breakpoint text in buffer.
	 *
	 * @param breakpoint C Breakpoint
	 * @param buffer buffer to append message to
	 * @see {@link ICBreakpointExtension#getExtensionMessage()
	 * @since 8.2
	 */
	protected static void appendExtensionMessage(ICBreakpoint breakpoint, StringBuffer buffer) {
		if (breakpoint instanceof ICBreakpoint2) {
			ICBreakpoint2 breakpoint2 = (ICBreakpoint2) breakpoint;
			String message = breakpoint2.getExtensionMessage();
			if (message != null && !message.isEmpty()) {
				if (buffer.length() > 0) {
					buffer.append(' ');
				}
				buffer.append(message);
			}
		}
	}

	protected static StringBuffer appendSourceName(ICBreakpoint breakpoint, StringBuffer label, boolean qualified)
			throws CoreException {
		String handle = breakpoint.getSourceHandle();
		if (!isEmpty(handle)) {
			IPath path = new Path(handle);
			if (path.isValidPath(handle)) {
				label.append(qualified ? path.toOSString() : path.lastSegment());
			}
		}
		return label;
	}

	protected static StringBuffer appendLineNumber(ICLineBreakpoint breakpoint, StringBuffer label)
			throws CoreException {
		int lineNumber = breakpoint.getLineNumber();
		if (lineNumber > 0) {
			label.append(' ');
			label.append(MessageFormat.format(DebugCoreMessages.getString("CDebugUtils.0"), //$NON-NLS-1$
					(Object[]) new String[] { Integer.toString(lineNumber) }));
		}
		return label;
	}

	protected static StringBuffer appendAddress(ICAddressBreakpoint breakpoint, StringBuffer label)
			throws CoreException {
		try {
			label.append(' ');
			label.append(MessageFormat.format(DebugCoreMessages.getString("CDebugUtils.1"), //$NON-NLS-1$
					(Object[]) new String[] { breakpoint.getAddress() }));
		} catch (NumberFormatException e) {
		}
		return label;
	}

	protected static StringBuffer appendFunction(ICFunctionBreakpoint breakpoint, StringBuffer label)
			throws CoreException {
		String function = breakpoint.getFunction();
		if (function != null && function.trim().length() > 0) {
			label.append(' ');
			label.append(MessageFormat.format(DebugCoreMessages.getString("CDebugUtils.2"), //$NON-NLS-1$
					(Object[]) new String[] { function.trim() }));
		}
		return label;
	}

	/**
	 * @since 7.5
	 */
	protected static void appendPrintfString(ICDynamicPrintf dprintf, StringBuffer buffer) throws CoreException {
		String printfStr = dprintf.getPrintfString();
		if (printfStr != null && printfStr.length() > 0) {
			buffer.append(' ');
			buffer.append(MessageFormat.format(DebugCoreMessages.getString("CDebugUtils.printfString"), //$NON-NLS-1$
					(Object[]) new String[] { printfStr }));
		}
	}

	protected static StringBuffer appendIgnoreCount(ICBreakpoint breakpoint, StringBuffer label) throws CoreException {
		int ignoreCount = breakpoint.getIgnoreCount();
		if (ignoreCount > 0) {
			label.append(' ');
			label.append(MessageFormat.format(DebugCoreMessages.getString("CDebugUtils.3"), //$NON-NLS-1$
					(Object[]) new String[] { Integer.toString(ignoreCount) }));
		}
		return label;
	}

	protected static void appendCondition(ICBreakpoint breakpoint, StringBuffer buffer) throws CoreException {
		String condition = breakpoint.getCondition();
		if (condition != null && condition.length() > 0) {
			buffer.append(' ');
			buffer.append(MessageFormat.format(DebugCoreMessages.getString("CDebugUtils.4"), //$NON-NLS-1$
					(Object[]) new String[] { condition }));
		}
	}

	private static void appendWatchExpression(ICWatchpoint watchpoint, StringBuffer label) throws CoreException {
		String expression = watchpoint.getExpression();
		if (expression != null && expression.length() > 0) {
			label.append(' ');
			label.append(MessageFormat.format(DebugCoreMessages.getString("CDebugUtils.5"), //$NON-NLS-1$
					(Object[]) new String[] { expression }));
		}
	}

	private static void appendWatchMemorySpace(ICWatchpoint2 watchpoint, StringBuffer label) throws CoreException {
		String memorySpace = watchpoint.getMemorySpace();
		if (memorySpace != null && memorySpace.length() > 0) {
			label.append(' ');
			label.append(MessageFormat.format(DebugCoreMessages.getString("CDebugUtils.6"), //$NON-NLS-1$
					(Object[]) new String[] { memorySpace }));
		}
	}

	private static void appendWatchRange(ICWatchpoint2 watchpoint, StringBuffer label) throws CoreException {
		String range = watchpoint.getRange().toString();
		if (range.length() > 0 && !range.equals("0")) { //$NON-NLS-1$
			label.append(' ');
			label.append(MessageFormat.format(DebugCoreMessages.getString("CDebugUtils.7"), //$NON-NLS-1$
					(Object[]) new String[] { range }));
		}
	}

	protected static StringBuffer appendBreakpointType(ICBreakpoint breakpoint, StringBuffer label)
			throws CoreException {
		if (breakpoint instanceof ICBreakpointType) {
			String typeString = ""; //$NON-NLS-1$
			int type = ((ICBreakpointType) breakpoint).getType();

			// Need to filter out the TEMPORARY bit-flag to get the real type
			// The REGULAR type is implicit so we don't report that.
			switch (type & ~ICBreakpointType.TEMPORARY) {
			case ICBreakpointType.HARDWARE:
				typeString = DebugCoreMessages.getString("CDebugUtils.Hardware"); //$NON-NLS-1$
				break;
			case ICBreakpointType.SOFTWARE:
				typeString = DebugCoreMessages.getString("CDebugUtils.Software"); //$NON-NLS-1$
				break;
			}

			// Now factor in the TEMPORARY qualifier to form, .e.,g "Hardware/Temporary"
			// Thing is, a temporary breakpoint should never show in the GUI, so this is
			// here as a just-in-case.
			if ((type & ICBreakpointType.TEMPORARY) != 0) {
				if (typeString.length() > 0) {
					typeString += "/"; //$NON-NLS-1$
				}
				typeString += DebugCoreMessages.getString("CDebugUtils.Temporary"); //$NON-NLS-1$
			}

			if (typeString.length() > 0) {
				label.append(' ');
				label.append(MessageFormat.format(DebugCoreMessages.getString("CDebugUtils.8"), //$NON-NLS-1$
						(Object[]) new String[] { typeString }));
			}
		}
		return label;
	}

	private static boolean isEmpty(String string) {
		return (string == null || string.trim().length() == 0);
	}

	private static CharsetDecoder fDecoder;

	public static CharsetDecoder getCharsetDecoder() {
		String charsetName = CDebugCorePlugin.getDefault().getPluginPreferences()
				.getString(ICDebugConstants.PREF_DEBUG_WIDE_CHARSET);
		if (fDecoder == null || !fDecoder.charset().name().equals(charsetName)) {
			Charset charset = Charset.forName(charsetName);
			fDecoder = charset.newDecoder();
		}
		return fDecoder;
	}

	/**
	 * Note: Moved from AbstractCLaunchDelegate
	 * @since 6.0
	 */
	public static ICProject getCProject(ILaunchConfiguration configuration) throws CoreException {
		String projectName = getProjectName(configuration);
		if (projectName != null) {
			projectName = projectName.trim();
			if (projectName.length() > 0) {
				IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(projectName);
				ICProject cProject = CCorePlugin.getDefault().getCoreModel().create(project);
				if (cProject != null && cProject.exists()) {
					return cProject;
				}
			}
		}
		return null;
	}

	/**
	 * Note: Moved from AbstractCLaunchDelegate
	 * @since 6.0
	 */
	public static String getProjectName(ILaunchConfiguration configuration) throws CoreException {
		return configuration.getAttribute(ICDTLaunchConfigurationConstants.ATTR_PROJECT_NAME, (String) null);
	}

	/**
	 * Note: Moved from AbstractCLaunchDelegate
	 * @since 6.0
	 */
	public static String getProgramName(ILaunchConfiguration configuration) throws CoreException {
		String programName = configuration.getAttribute(ICDTLaunchConfigurationConstants.ATTR_PROGRAM_NAME,
				(String) null);
		if (programName != null) {
			programName = VariablesPlugin.getDefault().getStringVariableManager()
					.performStringSubstitution(programName);
		}
		return programName;
	}

	/**
	 * Note: Moved from AbstractCLaunchDelegate
	 * @since 6.0
	 */
	public static IPath getProgramPath(ILaunchConfiguration configuration) throws CoreException {
		String path = getProgramName(configuration);
		if (path == null || path.trim().length() == 0) {
			return null;
		}
		return new Path(path);
	}

	/**
	 * Returns the ICProject associated with the project setting in the Main tab
	 * of a CDT launch configuration, or throws a CoreException providing a
	 * reason (e.g., the setting is empty, the project no longer exists, the
	 * isn't a CDT one, etc).
	 *
	 * @param config
	 *            the launch configuration
	 * @return an ICProject; never null.
	 * @throws CoreException
	 * @since 7.0
	 */
	public static ICProject verifyCProject(ILaunchConfiguration config) throws CoreException {
		String name = CDebugUtils.getProjectName(config);
		if (name == null) {
			throwCoreException(DebugCoreMessages.getString("CDebugUtils.C_Project_not_specified"), //$NON-NLS-1$
					ICDTLaunchConfigurationConstants.ERR_UNSPECIFIED_PROJECT);
		}
		ICProject cproject = CDebugUtils.getCProject(config);
		if (cproject == null) {
			IProject proj = ResourcesPlugin.getWorkspace().getRoot().getProject(name);
			if (!proj.exists()) {
				throwCoreException(
						DebugCoreMessages.getFormattedString("CDebugUtils.Project_NAME_does_not_exist", name), //$NON-NLS-1$
						ICDTLaunchConfigurationConstants.ERR_NOT_A_C_PROJECT);
			} else if (!proj.isOpen()) {
				throwCoreException(DebugCoreMessages.getFormattedString("CDebugUtils.Project_NAME_is_closed", name), //$NON-NLS-1$
						ICDTLaunchConfigurationConstants.ERR_NOT_A_C_PROJECT);
			}
			throwCoreException(DebugCoreMessages.getString("CDebugUtils.Not_a_C_CPP_project"), //$NON-NLS-1$
					ICDTLaunchConfigurationConstants.ERR_NOT_A_C_PROJECT);
		}
		return cproject;
	}

	/**
	 * Returns an IPath for the file referenced in the <i>C/C++ Application</i>
	 * setting of a CDT launch configuration. Typically, the file is obtained by
	 * combining the <i>C/C++ Application</i> setting with the <i>Project</i>
	 * setting. If unable to combine and resolve these settings to a valid file,
	 * a CoreException is thrown that provides the reason why. There are many
	 * such possible reasons (a problem with the <i>Project</i> setting, an
	 * empty <i>C/C++ Application</i> setting, the combined settings doesn't
	 * resolve to an existing file, etc).
	 *
	 * @param config
	 *            the launch configuration
	 * @param ignoreProjectSetting
	 *            if true, resolve the file using only the <i>C/C++
	 *            Application</i> setting. Do not take the <i>Project</i>
	 *            setting into account.
	 * @return an IPath; never null
	 * @throws CoreException
	 * @since 7.0
	 */
	public static IPath verifyProgramPath(ILaunchConfiguration config, boolean ignoreProjectSetting)
			throws CoreException {
		ICProject cproject = null;
		if (!ignoreProjectSetting) {
			cproject = verifyCProject(config); // will throw exception if project setting not valid
		}
		IPath programPath = CDebugUtils.getProgramPath(config);
		if (programPath == null || programPath.isEmpty()) {
			throwCoreException(DebugCoreMessages.getString("CDebugUtils.Program_file_not_specified"), //$NON-NLS-1$
					ICDTLaunchConfigurationConstants.ERR_UNSPECIFIED_PROGRAM);
		}

		if (programPath != null) { // this check is here only to avoid warning; compiler can't tell we'll throw an exception above
			if (!programPath.isAbsolute() && (cproject != null)) {
				// See if we can brute-force append the program path to the
				// project location. This allows us to support the program file
				// being outside the project, even outside the workspace, without
				// requiring a linked resource (e.g., the setting could be
				// "..\..\some\dir\myprogram.exe")
				IPath location = cproject.getProject().getLocation();
				if (location != null) {
					programPath = location.append(programPath);
					if (!programPath.toFile().exists()) {
						// Try looking in the project for the file. This
						// supports linked resources.
						IFile projFile = null;
						try {
							projFile = cproject.getProject().getFile(CDebugUtils.getProgramPath(config));
						} catch (IllegalArgumentException exc) {
							// thrown if relative path that resolves to a root file (e.g., "..\somefile")
						}
						if (projFile != null && projFile.exists()) {
							programPath = projFile.getLocation();
						}
					}
				}
			}
			if (!programPath.toFile().exists()) {
				throwCoreException(DebugCoreMessages.getString("CDebugUtils.Program_file_does_not_exist"), //$NON-NLS-1$
						new FileNotFoundException(DebugCoreMessages
								.getFormattedString("CDebugUtils.PROGRAM_PATH_not_found", programPath.toOSString())), //$NON-NLS-1$
						ICDTLaunchConfigurationConstants.ERR_PROGRAM_NOT_EXIST);
			}
		}
		return programPath;
	}

	/**
	 * Variant that expects (requires) the launch configuration to have a valid
	 * <i>Project</i> setting. See
	 * {@link #verifyProgramPath(ILaunchConfiguration, boolean)}
	 *
	 * @since 7.0
	 */
	public static IPath verifyProgramPath(ILaunchConfiguration config) throws CoreException {
		return verifyProgramPath(config, false);
	}

	/** Throws a CoreException. Clutter-reducing utility method. */
	private static void throwCoreException(String msg, int code) throws CoreException {
		throwCoreException(msg, null, code);
	}

	/** Throws a CoreException. Clutter-reducing utility method. */
	private static void throwCoreException(String msg, Exception innerException, int code) throws CoreException {
		throw new CoreException(
				new Status(IStatus.ERROR, CDebugCorePlugin.getUniqueIdentifier(), code, msg, innerException));
	}

	/**
	 * Generic method to fetch an attribute from a Map that has keys of type String.  The defaultValue
	 * parameter will be returned if the map does not contain the key, or if the matching value is not
	 * of the correct type.
	 *
	 * @param <V> The type of the value we are looking for.  Specified by the type of defaultValue.
	 * @param attributes The map with keys of type String, and values of any type.  Cannot be null.
	 * @param key They key for which we want the value.
	 * @param defaultValue The default value to return if the key is not found in the map, or if the value found
	 *                     is not of the same type as defaultValue. Cannot be null.
	 * @return The value, if found and of the same type as defaultValue.  Else, returns defaultValue.
	 * @since 7.1
	 */
	@SuppressWarnings("unchecked")
	public static <V> V getAttribute(Map<String, ?> attributes, String key, V defaultValue) {
		Object value = attributes.get(key);
		if (defaultValue.getClass().isInstance(value)) {
			return (V) value;
		}
		return defaultValue;
	}

	/**
	 * Overrides the standard project ICBreakpoint toggle breakpoint factory with
	 * a custom toggle breakpoint factory. The ICBreakpoint toggle breakpoint factory
	 * will be disabled and it is up to the client to contribute it's own toggle
	 * breakpoint factory.
	 *
	 * @param project a project
	 * @param factoryId a breakpoint toggle factory identifier
	 * @since 7.1
	 */
	public static void setToggleBreakpointFactory(IProject project, String factoryId) {
		try {
			IEclipsePreferences pref = new ProjectScope(project).getNode(CDebugCorePlugin.PLUGIN_ID);
			pref.put(ICDebugConstants.PREF_TOGGLE_BREAKPOINT_MODEL_IDENTIFIER, factoryId);
			pref.flush();
		} catch (BackingStoreException e) {
			CDebugCorePlugin.log(e);
		}
	}

	/**
	 * Returns the toggle breakpoint factory identifier for the project
	 *
	 * @param project the project
	 * @return the toggle breakpoint factory identifier, can be {@code null}
	 * @since 7.1
	 */
	public static String getToggleBreakpointFactory(IProject project) {
		IEclipsePreferences pref = new ProjectScope(project.getProject()).getNode(CDebugCorePlugin.PLUGIN_ID);
		return pref.get(ICDebugConstants.PREF_TOGGLE_BREAKPOINT_MODEL_IDENTIFIER, null);
	}

	/**
	 * Returns whether the project uses the standard ICBreakpoint toggle breakpoint factory.
	 *
	 * @param project the project
	 * @return {@code true} if the project uses the standard ICBreakpoint breakpoint toggle factory
	 * @since 7.1
	 */
	public static boolean isStandardCBreakpointFactory(IProject project) {
		return getToggleBreakpointFactory(project) == null;
	}

	/**
	 * Returns whether the custom toggle breakpoint factory should be consider when evaluating the
	 * enablement of the standard ICBreakpoint toggle breakpoint factory.
	 *
	 * @return true if the custom model breakpoint system property is set
	 * @since 7.1
	 * @see ICDebugConstants#PREF_TOGGLE_BREAKPOINT_MODEL_IDENTIFIER
	 */
	public static boolean isCustomToggleBreakpointFactory() {
		String customModel = System.getProperty(ICDebugConstants.PREF_TOGGLE_BREAKPOINT_MODEL_IDENTIFIER, null);
		return customModel != null && Boolean.valueOf(customModel);
	}

	/**
	 * Return value type for {@link CDebugUtils#getFileParts(String)}
	 * @since 8.0
	 */
	public static class FileParts {
		private String folder;
		private String fileName;
		private String extension;

		/**
		 * Returns the containing folder of the input file. Can be empty if the
		 * input file was a filename.
		 *
		 * @return containing folder
		 */
		public String getFolder() {
			return folder;
		}

		/**
		 * Returns the filename (after last slash) of the input file. Can be
		 * empty if input ended with a slash.
		 *
		 * @return file name
		 */
		public String getFileName() {
			return fileName;
		}

		/**
		 * Returns the extension of {@link #getFileName()}
		 *
		 * @return the extension
		 */
		public String getExtension() {
			return extension;
		}

		/**
		 * CDebugUtils.getFileParts(String) should be called to create FileParts.
		 */
		private FileParts() {
		}
	}

	/**
	 * Split a Windows or Unix style path into its constituent parts.
	 *
	 * The split does not modify or canonicalize the individual parts of the
	 * file name. Nor does it convert between platforms.
	 *
	 * This method is useful for dealing with Windows paths when running on
	 * Linux and vice versa, it is also useful for non-canonical paths (ones
	 * with .. in them).
	 *
	 * @param file
	 *            file name
	 * @return parts of a file
	 * @since 8.0
	 */
	public static FileParts getFileParts(String file) {
		FileParts parts = new FileParts();
		int lastSlash = Math.max(file.lastIndexOf("/"), file.lastIndexOf("\\")); //$NON-NLS-1$ //$NON-NLS-2$
		// lastSlash may be -1 if no separator found, that means the
		// whole path is just a fileName and folder ends up as ""
		parts.fileName = file.substring(lastSlash + 1);
		parts.folder = file.substring(0, lastSlash + 1);
		int lastDot = parts.fileName.lastIndexOf("."); //$NON-NLS-1$
		if (lastDot < 0) {
			parts.extension = ""; //$NON-NLS-1$
		} else {
			parts.extension = parts.fileName.substring(lastDot + 1);
		}
		return parts;
	}
}
