/*******************************************************************************
 * Copyright (c) 2007 - 2011 QNX Software Systems and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Ericsson - initial API and implementation this class is based on
 *     QNX Software Systems - Initial implementation for Jtag debugging
 *     Sage Electronic Engineering, LLC - bug 305943
 *              - API generalization to become transport-independent (allow
 *                connections via serial ports and pipes).
 *     John Dallaway - Wrong groupId during initialization (Bug 349736)           
 *******************************************************************************/
package org.eclipse.cdt.debug.gdbjtag.core;

/**
 * @author Andy Jin
 *
 */

import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;

import org.eclipse.cdt.debug.core.CDebugUtils;
import org.eclipse.cdt.debug.gdbjtag.core.jtagdevice.GDBJtagDeviceContribution;
import org.eclipse.cdt.debug.gdbjtag.core.jtagdevice.GDBJtagDeviceContributionFactory;
import org.eclipse.cdt.debug.gdbjtag.core.jtagdevice.IGDBJtagDevice;
import org.eclipse.cdt.dsf.concurrent.CountingRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.DsfExecutor;
import org.eclipse.cdt.dsf.concurrent.RequestMonitor;
import org.eclipse.cdt.dsf.concurrent.RequestMonitorWithProgress;
import org.eclipse.cdt.dsf.concurrent.Sequence;
import org.eclipse.cdt.dsf.datamodel.DMContexts;
import org.eclipse.cdt.dsf.datamodel.DataModelInitializedEvent;
import org.eclipse.cdt.dsf.debug.service.IBreakpoints.IBreakpointsTargetDMContext;
import org.eclipse.cdt.dsf.gdb.IGDBLaunchConfigurationConstants;
import org.eclipse.cdt.dsf.gdb.launching.GdbLaunch;
import org.eclipse.cdt.dsf.gdb.launching.LaunchMessages;
import org.eclipse.cdt.dsf.gdb.launching.LaunchUtils;
import org.eclipse.cdt.dsf.gdb.service.IGDBBackend;
import org.eclipse.cdt.dsf.gdb.service.SessionType;
import org.eclipse.cdt.dsf.gdb.service.command.IGDBControl;
import org.eclipse.cdt.dsf.mi.service.IMIContainerDMContext;
import org.eclipse.cdt.dsf.mi.service.IMIProcesses;
import org.eclipse.cdt.dsf.mi.service.MIBreakpointsManager;
import org.eclipse.cdt.dsf.mi.service.MIProcesses;
import org.eclipse.cdt.dsf.mi.service.command.CommandFactory;
import org.eclipse.cdt.dsf.mi.service.command.commands.CLICommand;
import org.eclipse.cdt.dsf.mi.service.command.output.MIInfo;
import org.eclipse.cdt.dsf.service.DsfServicesTracker;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.variables.VariablesPlugin;
import org.eclipse.debug.core.ILaunchConfiguration;

/**
 * The final launch sequence for the Jtag hardware debugging using the
 * DSF/GDB debugger framework.
 * <p>
 * This class is based on the implementation of the standard DSF/GDB debugging
 * <code>org.eclipse.cdt.dsf.gdb.launching.FinalLaunchSequence</code>
 * <p>
 * It adds Jtag hardware debugging specific steps to initialize remote target
 * and start the remote Jtag debugging.
 * <p>
 * @since 7.0
 */
public class GDBJtagDSFFinalLaunchSequence extends Sequence {

	/** utility method; cuts down on clutter */
	private void queueCommands(List<String> commands, RequestMonitor rm) {
		if (!commands.isEmpty()) { 
			fCommandControl.queueCommand(
	    		new CLICommand<MIInfo>(fCommandControl.getContext(), composeCommand(commands)),
	        	new DataRequestMonitor<MIInfo>(getExecutor(), rm));
		}
		else {
			rm.done();
		}
	}

	private Step[] fSteps = new Step[] {
		/*
		 * Create the service tracker for later use
		 */
		new Step() { 
        	@Override
            public void execute(RequestMonitor requestMonitor) {
                fTracker = new DsfServicesTracker(Activator.getBundleContext(), fLaunch.getSession().getId());
                requestMonitor.done();
            }
            @Override
            public void rollBack(RequestMonitor requestMonitor) {
                if (fTracker != null) fTracker.dispose();
                fTracker = null;
                requestMonitor.done();
            }},
        /*
         * Fetch the GDBBackend service for later use
         */
        new Step() { 
            @Override
            public void execute(RequestMonitor requestMonitor) {
                fGDBBackend = fTracker.getService(IGDBBackend.class);
                if (fGDBBackend == null) {
            		requestMonitor.setStatus(new Status(IStatus.ERROR, Activator.PLUGIN_ID, -1, "Cannot obtain GDBBackend service", null)); //$NON-NLS-1$
                }

                requestMonitor.done();
            }},
        /*
         * Fetch the control service for later use
         */
        new Step() {
            @Override
            public void execute(RequestMonitor requestMonitor) {
                fCommandControl = fTracker.getService(IGDBControl.class);
                if (fCommandControl == null) {
            		requestMonitor.setStatus(new Status(IStatus.ERROR, Activator.PLUGIN_ID, -1, "Cannot obtain control service", null)); //$NON-NLS-1$
                }
                else {
                	fCommandFactory = fCommandControl.getCommandFactory();
                }
                
                requestMonitor.done();
            }},
        /*
         * Fetch the process service for later use
         */
        new Step() { 
            @Override
            public void execute(RequestMonitor requestMonitor) {
                fProcService = fTracker.getService(IMIProcesses.class);
                if (fProcService == null) {
            		requestMonitor.setStatus(new Status(IStatus.ERROR, Activator.PLUGIN_ID, -1, "Cannot obtain process service", null)); //$NON-NLS-1$
                }

                requestMonitor.done();
            }},
        /*
         * Specify GDB's working directory
         */
        new Step() { 
        	@Override
        	public void execute(final RequestMonitor requestMonitor) {
        		IPath dir = null;
        		try {
        			dir = fGDBBackend.getGDBWorkingDirectory();
        		} catch (CoreException e) {
        			requestMonitor.setStatus(new Status(IStatus.ERROR, Activator.PLUGIN_ID, -1, "Cannot get working directory", e)); //$NON-NLS-1$
        			requestMonitor.done();
        			return;
        		}

        		if (dir != null) {
        			fCommandControl.queueCommand(
        					fCommandFactory.createMIEnvironmentCD(fCommandControl.getContext(), dir.toPortableString()), 
        					new DataRequestMonitor<MIInfo>(getExecutor(), requestMonitor));
        		} else {
        			requestMonitor.done();
        		}
        	}},
	/*
    	 * Source the gdbinit file specified in the launch
    	 */
        new Step() { 
            @Override
            public void execute(final RequestMonitor requestMonitor) {
            	try {
            		final String gdbinitFile = fGDBBackend.getGDBInitFile();
            		
            		if (gdbinitFile != null && gdbinitFile.length() > 0) {
            			fCommandControl.queueCommand(
            					fCommandFactory.createCLISource(fCommandControl.getContext(), gdbinitFile), 
            					new DataRequestMonitor<MIInfo>(getExecutor(), requestMonitor) {
            						@Override
            						protected void handleCompleted() {
            							// If the gdbinitFile is the default, then it may not exist and we
            							// should not consider this an error.
            							// If it is not the default, then the user must have specified it and
            							// we want to warn the user if we can't find it.
            							if (!gdbinitFile.equals(IGDBLaunchConfigurationConstants.DEBUGGER_GDB_INIT_DEFAULT )) {
            								requestMonitor.setStatus(getStatus());
            							}
            							requestMonitor.done();
            						}
            					});
            		} else {
            			requestMonitor.done();
            		}
            	} catch (CoreException e) {
            		requestMonitor.setStatus(new Status(IStatus.ERROR, Activator.PLUGIN_ID, -1, "Cannot get gdbinit option", e)); //$NON-NLS-1$
            		requestMonitor.done();
            	}
            }},
    	/*
    	 * Specify the arguments to the executable file
    	 */
        new Step() { 
            @Override
            public void execute(final RequestMonitor requestMonitor) {
        		try {
        			String args = fGDBBackend.getProgramArguments();
        			
            		if (args != null) {
        				String[] argArray = args.replaceAll("\n", " ").split(" ");  //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
        				IMIContainerDMContext containerDmc = fProcService.createContainerContextFromGroupId(fCommandControl.getContext(), MIProcesses.UNIQUE_GROUP_ID);
            			fCommandControl.queueCommand(
            					fCommandFactory.createMIGDBSetArgs(containerDmc, argArray), 
            					new DataRequestMonitor<MIInfo>(getExecutor(), requestMonitor));
            		} else {
            			requestMonitor.done();
            		}
        		} catch (CoreException e) {
        			requestMonitor.setStatus(new Status(IStatus.ERROR, Activator.PLUGIN_ID, -1, "Cannot get inferior arguments", e)); //$NON-NLS-1$
        			requestMonitor.done();
        		}    		
            }},
    	/*
    	 * Specify environment variables if needed
    	 */
        new Step() { 
            @Override
            public void execute(final RequestMonitor requestMonitor) {
            	boolean clear = false;
       			Properties properties = new Properties();
           		try {
           			clear = fGDBBackend.getClearEnvironment();
           			properties = fGDBBackend.getEnvironmentVariables();
           		} catch (CoreException e) {
           			requestMonitor.setStatus(new Status(IStatus.ERROR, Activator.PLUGIN_ID, -1, "Cannot get environment information", e)); //$NON-NLS-1$
           			requestMonitor.done();
           			return;
          		}

            	if (clear == true || properties.size() > 0) {
            		fCommandControl.setEnvironment(properties, clear, requestMonitor);
            	} else {
            		requestMonitor.done();
            	}
            }},
    	/*
    	 * Enable non-stop mode if necessary
    	 */
        new Step() { 
            @Override
            public void execute(final RequestMonitor requestMonitor) {
            	boolean isNonStop = false;
        		try {
        			isNonStop = fLaunch.getLaunchConfiguration().getAttribute(IGDBLaunchConfigurationConstants.ATTR_DEBUGGER_NON_STOP,
                                                                              LaunchUtils.getIsNonStopModeDefault());
            	} catch (CoreException e) {    		
            	}

            	// GDBs that don't support non-stop don't allow you to set it to false.
            	// We really should set it to false when GDB supports it though.
            	// Something to fix later.
            	if (isNonStop) {
            		// The raw commands should not be necessary in the official GDB release
            		fCommandControl.queueCommand(
            			fCommandFactory.createMIGDBSetTargetAsync(fCommandControl.getContext(), true),
           				new DataRequestMonitor<MIInfo>(getExecutor(), requestMonitor) {
           					@Override
           					protected void handleSuccess() {
           						fCommandControl.queueCommand(
           							fCommandFactory.createMIGDBSetPagination(fCommandControl.getContext(), false), 
       								new DataRequestMonitor<MIInfo>(getExecutor(), requestMonitor) {
       									@Override
       									protected void handleSuccess() {
       										fCommandControl.queueCommand(
       											fCommandFactory.createMIGDBSetNonStop(fCommandControl.getContext(), true), 
    											new DataRequestMonitor<MIInfo>(getExecutor(), requestMonitor));
          									}
          								});
           					}
    					});
            	} else {
            		requestMonitor.done();
            	}
            }},
        /*
         * Tell GDB to automatically load or not the shared library symbols
         */
        new Step() { 
            @Override
			public void execute(RequestMonitor requestMonitor) {
        		try {
        			boolean autolib = fLaunch.getLaunchConfiguration().getAttribute(IGDBLaunchConfigurationConstants.ATTR_DEBUGGER_AUTO_SOLIB,
        					                                                        IGDBLaunchConfigurationConstants.DEBUGGER_AUTO_SOLIB_DEFAULT);
                    fCommandControl.queueCommand(
                    	fCommandFactory.createMIGDBSetAutoSolib(fCommandControl.getContext(), autolib), 
                    	new DataRequestMonitor<MIInfo>(getExecutor(), requestMonitor));
        		} catch (CoreException e) {
        			requestMonitor.setStatus(new Status(IStatus.ERROR, Activator.PLUGIN_ID, -1, "Cannot set shared library option", e)); //$NON-NLS-1$
        			requestMonitor.done();
        		}
            }},
        /*
         * Set the shared library paths
         */
        new Step() { 
            @Override
            public void execute(final RequestMonitor requestMonitor) {
          		try {
        			List<String> p = fGDBBackend.getSharedLibraryPaths();
          		    
       				if (p.size() > 0) {
       					String[] paths = p.toArray(new String[p.size()]);
       	                fCommandControl.queueCommand(
       	                	fCommandFactory.createMIGDBSetSolibSearchPath(fCommandControl.getContext(), paths), 
       	                	new DataRequestMonitor<MIInfo>(getExecutor(), requestMonitor) {
       	                		@Override
       	                		protected void handleSuccess() {
    // Sysroot is not available in GDB6.6 and will make the launch fail in that case.
    // Let's remove it for now
       	                			requestMonitor.done();
//	       	                			// If we are able to set the solib-search-path,
//	       	                			// we should disable the sysroot variable, as indicated
//	       	                			// in the GDB documentation.  This is to avoid the sysroot
//	       	                			// variable finding libraries that were not meant to be found.
//	       	        	                fCommandControl.queueCommand(
//	       	        	   	                	new MIGDBSetSysroot(fCommandControl.getContext()), 
//	       	        	   	                	new DataRequestMonitor<MIInfo>(getExecutor(), requestMonitor));
       	                		};
       	                	});
       				} else {
       	                requestMonitor.done();
       				}
        		} catch (CoreException e) {
                    requestMonitor.setStatus(new Status(IStatus.ERROR, Activator.PLUGIN_ID, -1, "Cannot set share library paths", e)); //$NON-NLS-1$
                    requestMonitor.done();
        		}
        	}},

// -environment-directory with a lot of paths could
// make setting breakpoint incredibly slow, which makes
// the debug session un-workable.  We simply stop
// using it because it's usefulness is unclear.
// Bug 225805
//    	/*
//    	 * Setup the source paths
//    	 */
//        new Step() { 
//    		@Override
//            public void execute(RequestMonitor requestMonitor) {
//                CSourceLookup sourceLookup = fTracker.getService(CSourceLookup.class);
//                CSourceLookupDirector locator = (CSourceLookupDirector)fLaunch.getSourceLocator();
//        		ISourceLookupDMContext sourceLookupDmc = (ISourceLookupDMContext)fCommandControl.getContext();
//
//                sourceLookup.setSourceLookupPath(sourceLookupDmc, locator.getSourceContainers(), requestMonitor);
//            }},
  
            // Below steps are specific to JTag hardware debugging
            
        /*
         * Retrieve the IGDBJtagDevice instance
         */
        new Step() {
			@Override
        	public void execute(RequestMonitor rm) {
				Exception exception = null;
            	try {
					fGdbJtagDevice = getGDBJtagDevice(fLaunch.getLaunchConfiguration());
				} catch (NullPointerException e) {
					exception = e;
				} catch (CoreException e) {
					exception = e;
				}
				if (fGdbJtagDevice == null) {
					// Abort the launch
					rm.setStatus(new Status(IStatus.ERROR, Activator.PLUGIN_ID, -1, "Cannot get Jtag device", exception)); //$NON-NLS-1$
				}
				rm.done();
            }},
        /*
         * Execute symbol loading
         */
        new Step() {
			@Override
			public void execute(RequestMonitor rm) {
				ILaunchConfiguration config = fLaunch.getLaunchConfiguration();
				try {
					if (config.getAttribute(IGDBJtagConstants.ATTR_LOAD_SYMBOLS, IGDBJtagConstants.DEFAULT_LOAD_SYMBOLS)) {
						String symbolsFileName = null;

						// New setting in Helios. Default is true. Check for existence
						// in order to support older launch configs
						if (config.hasAttribute(IGDBJtagConstants.ATTR_USE_PROJ_BINARY_FOR_SYMBOLS) &&
								config.getAttribute(IGDBJtagConstants.ATTR_USE_PROJ_BINARY_FOR_SYMBOLS, IGDBJtagConstants.DEFAULT_USE_PROJ_BINARY_FOR_SYMBOLS)) {
							IPath programFile = CDebugUtils.verifyProgramPath(config);
							if (programFile != null) {
								symbolsFileName = programFile.toOSString();
							}
						}
						else {
							symbolsFileName = config.getAttribute(IGDBJtagConstants.ATTR_SYMBOLS_FILE_NAME, IGDBJtagConstants.DEFAULT_SYMBOLS_FILE_NAME);
							if (symbolsFileName.length() > 0) {
								symbolsFileName = VariablesPlugin.getDefault().getStringVariableManager().performStringSubstitution(symbolsFileName);
							} else {
								symbolsFileName = null;
							}
						}
						
						if (symbolsFileName == null) {
		        			rm.setStatus(new Status(IStatus.ERROR, Activator.PLUGIN_ID, -1, Messages.getString("GDBJtagDebugger.err_no_img_file"), null)); //$NON-NLS-1$
		        			rm.done();
		        			return;
						}

						// Escape windows path separator characters TWICE, once for Java and once for GDB.						
						symbolsFileName = symbolsFileName.replace("\\", "\\\\"); //$NON-NLS-1$ //$NON-NLS-2$

						String symbolsOffset = config.getAttribute(IGDBJtagConstants.ATTR_SYMBOLS_OFFSET, IGDBJtagConstants.DEFAULT_SYMBOLS_OFFSET);
						if (symbolsOffset.length() > 0) {
							symbolsOffset = "0x" + symbolsOffset;					
						}
						List<String> commands = new ArrayList<String>();
						fGdbJtagDevice.doLoadSymbol(symbolsFileName, symbolsOffset, commands);
						queueCommands(commands, rm);									
						
					} else {
						rm.done();
					}
				} catch (CoreException e) {
        			rm.setStatus(new Status(IStatus.ERROR, Activator.PLUGIN_ID, -1, "Cannot load symbol", e)); //$NON-NLS-1$
        			rm.done();
				}
			}},

        /*
         * Hook up to remote target
         */
        new Step() {
			@SuppressWarnings("deprecation")
			@Override
			public void execute(RequestMonitor rm) {
				ILaunchConfiguration config = fLaunch.getLaunchConfiguration();
				try {
					if (config.getAttribute(IGDBJtagConstants.ATTR_USE_REMOTE_TARGET, IGDBJtagConstants.DEFAULT_USE_REMOTE_TARGET)) {
						List<String> commands = new ArrayList<String>();
						if (fGdbJtagDevice instanceof IGDBJtagConnection) {
							URI	uri = new URI(config.getAttribute(IGDBJtagConstants.ATTR_CONNECTION, IGDBJtagConstants.DEFAULT_CONNECTION));
							IGDBJtagConnection device = (IGDBJtagConnection)fGdbJtagDevice;
							device.doRemote(uri.getSchemeSpecificPart(), commands);
						} else {
							// Handle legacy network device contributions that don't understand URIs
							String ipAddress = config.getAttribute(IGDBJtagConstants.ATTR_IP_ADDRESS, IGDBJtagConstants.DEFAULT_IP_ADDRESS);
							int portNumber = config.getAttribute(IGDBJtagConstants.ATTR_PORT_NUMBER, IGDBJtagConstants.DEFAULT_PORT_NUMBER);
							fGdbJtagDevice.doRemote(ipAddress, portNumber, commands);
						}
						queueCommands(commands, rm);
					} else {
						rm.done();
					}
				} catch (CoreException e) {
        			rm.setStatus(new Status(IStatus.ERROR, Activator.PLUGIN_ID, -1, "Cannot connect to remote target", e)); //$NON-NLS-1$
        			rm.done();
				} catch (URISyntaxException e) {
        			rm.setStatus(new Status(IStatus.ERROR, Activator.PLUGIN_ID, -1, "Invalid remote target connection syntax", e)); //$NON-NLS-1$
        			rm.done();
				}
			}},
        /*
         * Run device-specific code to reset the board
         */
        new Step() {
			@Override
			public void execute(RequestMonitor rm) {
				ILaunchConfiguration config = fLaunch.getLaunchConfiguration();
				try {
					if (config.getAttribute(IGDBJtagConstants.ATTR_DO_RESET, IGDBJtagConstants.DEFAULT_DO_RESET)) {
						List<String> commands = new ArrayList<String>();
						fGdbJtagDevice.doReset(commands);
						queueCommands(commands, rm);
					} else {
						rm.done();
					}
				} catch (CoreException e) {
        			rm.setStatus(new Status(IStatus.ERROR, Activator.PLUGIN_ID, -1, "Cannot reset the remote target", e)); //$NON-NLS-1$
        			rm.done();
				}
			}},
        /*
         * Run device-specific code to delay the startup
         */
        new Step() {
			@Override
			public void execute(RequestMonitor rm) {
				ILaunchConfiguration config = fLaunch.getLaunchConfiguration();
				int defaultDelay = fGdbJtagDevice.getDefaultDelay();
				try {
					List<String> commands = new ArrayList<String>();
					fGdbJtagDevice.doDelay(config.getAttribute(IGDBJtagConstants.ATTR_DELAY, defaultDelay), commands);
					queueCommands(commands, rm);								
				} catch (CoreException e) {
        			rm.setStatus(new Status(IStatus.ERROR, Activator.PLUGIN_ID, -1, "Cannot delay the remote target", e)); //$NON-NLS-1$
        			rm.done();
				}
			}},
        /*
         * Run device-specific code to halt the board
         */
        new Step() {
			@Override
			public void execute(RequestMonitor rm) {
				ILaunchConfiguration config = fLaunch.getLaunchConfiguration();
				try {
					if (config.getAttribute(IGDBJtagConstants.ATTR_DO_HALT, IGDBJtagConstants.DEFAULT_DO_HALT)) {
						List<String> commands = new ArrayList<String>();
						fGdbJtagDevice.doHalt(commands);
						queueCommands(commands, rm);								
					} else {
						rm.done();
					}
				} catch (CoreException e) {
        			rm.setStatus(new Status(IStatus.ERROR, Activator.PLUGIN_ID, -1, "Cannot halt the remote target", e)); //$NON-NLS-1$
        			rm.done();
				}
			}},
        /*
         * Execute any user defined init commands
         */
        new Step() {
			@Override
			public void execute(RequestMonitor rm) {
				ILaunchConfiguration config = fLaunch.getLaunchConfiguration();
				try {
					String userCmd = config.getAttribute(IGDBJtagConstants.ATTR_INIT_COMMANDS, IGDBJtagConstants.DEFAULT_INIT_COMMANDS);
					userCmd = VariablesPlugin.getDefault().getStringVariableManager().performStringSubstitution(userCmd);
					if (userCmd.length() > 0) {
						String[] commands = userCmd.split("\\r?\\n"); //$NON-NLS-1$
						
						CountingRequestMonitor crm = new CountingRequestMonitor(getExecutor(), rm);
						crm.setDoneCount(commands.length);
						for (int i = 0; i < commands.length; ++i) {
							fCommandControl.queueCommand(
							    new CLICommand<MIInfo>(fCommandControl.getContext(), commands[i]),
							    new DataRequestMonitor<MIInfo>(getExecutor(), crm));
						}
					}
					else {
						rm.done();
					}
				} catch (CoreException e) {
        			rm.setStatus(new Status(IStatus.ERROR, Activator.PLUGIN_ID, -1, "Cannot run user defined init commands", e)); //$NON-NLS-1$
        			rm.done();
				}
			}},
        /*
         * Execute image loading
         */
        new Step() {
			@Override
			public void execute(RequestMonitor rm) {
				ILaunchConfiguration config = fLaunch.getLaunchConfiguration();
				try {
					String imageFileName = null;
					if (config.getAttribute(IGDBJtagConstants.ATTR_LOAD_IMAGE, IGDBJtagConstants.DEFAULT_LOAD_IMAGE)) {
						// New setting in Helios. Default is true. Check for existence
						// in order to support older launch configs
						if (config.hasAttribute(IGDBJtagConstants.ATTR_USE_PROJ_BINARY_FOR_IMAGE) &&
								config.getAttribute(IGDBJtagConstants.ATTR_USE_PROJ_BINARY_FOR_IMAGE, IGDBJtagConstants.DEFAULT_USE_PROJ_BINARY_FOR_IMAGE)) {
							IPath programFile = CDebugUtils.verifyProgramPath(config);
							if (programFile != null) {
								imageFileName = programFile.toOSString();
							}
						}
						else {
							imageFileName = config.getAttribute(IGDBJtagConstants.ATTR_IMAGE_FILE_NAME, IGDBJtagConstants.DEFAULT_IMAGE_FILE_NAME); 
							if (imageFileName.length() > 0) {
								imageFileName = VariablesPlugin.getDefault().getStringVariableManager().performStringSubstitution(imageFileName);
							} else {
								imageFileName = null;
							}
						}
						
						if (imageFileName == null) {
		        			rm.setStatus(new Status(IStatus.ERROR, Activator.PLUGIN_ID, -1, Messages.getString("GDBJtagDebugger.err_no_img_file"), null)); //$NON-NLS-1$
		        			rm.done();
		        			return;
						}

						// Escape windows path separator characters TWICE, once for Java and once for GDB.						
						imageFileName = imageFileName.replace("\\", "\\\\"); //$NON-NLS-1$ //$NON-NLS-2$

						String imageOffset = config.getAttribute(IGDBJtagConstants.ATTR_IMAGE_OFFSET, IGDBJtagConstants.DEFAULT_IMAGE_OFFSET);
						if (imageOffset.length() > 0) {
							imageOffset = (imageFileName.endsWith(".elf")) ? "" : "0x" + config.getAttribute(IGDBJtagConstants.ATTR_IMAGE_OFFSET, IGDBJtagConstants.DEFAULT_IMAGE_OFFSET); //$NON-NLS-2$ 
						}
						List<String> commands = new ArrayList<String>();
						fGdbJtagDevice.doLoadImage(imageFileName, imageOffset, commands);
						queueCommands(commands, rm);									
					} 
					else {
						rm.done();
					}
				} catch (CoreException e) {
        			rm.setStatus(new Status(IStatus.ERROR, Activator.PLUGIN_ID, -1, "Cannot load image", e)); //$NON-NLS-1$
        			rm.done();
				}
			}},
        /* 
         * Start tracking the breakpoints once we know we are connected to the target (necessary for remote debugging) 
         */
        new Step() { 
        	@Override
	        public void execute(final RequestMonitor requestMonitor) {
	           	if (fSessionType != SessionType.CORE) {
	           		MIBreakpointsManager bpmService = fTracker.getService(MIBreakpointsManager.class);
	    			IMIContainerDMContext containerDmc = fProcService.createContainerContextFromGroupId(fCommandControl.getContext(), MIProcesses.UNIQUE_GROUP_ID);
	    			IBreakpointsTargetDMContext bpTargetDmc = DMContexts.getAncestorOfType(containerDmc, IBreakpointsTargetDMContext.class);
	
	           		bpmService.startTrackingBreakpoints(bpTargetDmc, requestMonitor);
	           	} else {
	           		requestMonitor.done();
	           	}
	        }},
        /*
         * Set the program counter
         */
        new Step() {
			@Override
			public void execute(RequestMonitor rm) {
				ILaunchConfiguration config = fLaunch.getLaunchConfiguration();
				try {
					if (config.getAttribute(IGDBJtagConstants.ATTR_SET_PC_REGISTER, IGDBJtagConstants.DEFAULT_SET_PC_REGISTER)) {
						String pcRegister = config.getAttribute(IGDBJtagConstants.ATTR_PC_REGISTER, config.getAttribute(IGDBJtagConstants.ATTR_IMAGE_OFFSET, IGDBJtagConstants.DEFAULT_PC_REGISTER)); 
						List<String> commands = new ArrayList<String>();
						fGdbJtagDevice.doSetPC(pcRegister, commands);
						queueCommands(commands, rm);								
					} else {
						rm.done();
					}
				} catch (CoreException e) {
        			rm.setStatus(new Status(IStatus.ERROR, Activator.PLUGIN_ID, -1, "Cannot set program counter", e)); //$NON-NLS-1$
        			rm.done();
				}
			}},
        /*
         * Execute the stop script
         */
        new Step() {
			@Override
			public void execute(RequestMonitor rm) {
				ILaunchConfiguration config = fLaunch.getLaunchConfiguration();
				try {
					if (config.getAttribute(IGDBJtagConstants.ATTR_SET_STOP_AT, IGDBJtagConstants.DEFAULT_SET_STOP_AT)) {
						String stopAt = config.getAttribute(IGDBJtagConstants.ATTR_STOP_AT, IGDBJtagConstants.DEFAULT_STOP_AT); 
						List<String> commands = new ArrayList<String>();
						fGdbJtagDevice.doStopAt(stopAt, commands);
						queueCommands(commands, rm);								
					} else {
						rm.done();
					}
				} catch (CoreException e) {
        			rm.setStatus(new Status(IStatus.ERROR, Activator.PLUGIN_ID, -1, "Cannot run the stop script", e)); //$NON-NLS-1$
        			rm.done();
				}
			}
        },
        /*
         * Execute the resume script
         */
        new Step() {
			@Override
			public void execute(RequestMonitor rm) {
				ILaunchConfiguration config = fLaunch.getLaunchConfiguration();
				try {
					if (config.getAttribute(IGDBJtagConstants.ATTR_SET_RESUME, IGDBJtagConstants.DEFAULT_SET_RESUME)) {
						List<String> commands = new ArrayList<String>();
						fGdbJtagDevice.doContinue(commands);
						queueCommands(commands, rm);									
					} else {
						rm.done();
					}
				} catch (CoreException e) {
        			rm.setStatus(new Status(IStatus.ERROR, Activator.PLUGIN_ID, -1, "Cannot resume the remote target", e)); //$NON-NLS-1$
        			rm.done();
				}
			}},
        /*
         * Run any user defined commands to start debugging
         */
        new Step() {
			@Override
			public void execute(RequestMonitor rm) {
				ILaunchConfiguration config = fLaunch.getLaunchConfiguration();
				try {
					String userCmd = config.getAttribute(IGDBJtagConstants.ATTR_RUN_COMMANDS, IGDBJtagConstants.DEFAULT_RUN_COMMANDS); 
					if (userCmd.length() > 0) {
						userCmd = VariablesPlugin.getDefault().getStringVariableManager().performStringSubstitution(userCmd);
						String[] commands = userCmd.split("\\r?\\n"); //$NON-NLS-1$
						
						CountingRequestMonitor crm = new CountingRequestMonitor(getExecutor(), rm);
						crm.setDoneCount(commands.length);
						for (int i = 0; i < commands.length; ++i) {
							fCommandControl.queueCommand(
							    new CLICommand<MIInfo>(fCommandControl.getContext(), commands[i]),
							    new DataRequestMonitor<MIInfo>(getExecutor(), crm));
						}
					}
					else {
						rm.done();
					}
				} catch (CoreException e) {
        			rm.setStatus(new Status(IStatus.ERROR, Activator.PLUGIN_ID, -1, "Cannot run user defined run commands", e)); //$NON-NLS-1$
        			rm.done();
				}
			}},

        /*
         * Indicate that the Data Model has been filled.  This will trigger the Debug view to expand.
         */
        new Step() {
            @Override
            public void execute(final RequestMonitor requestMonitor) {
            	fLaunch.getSession().dispatchEvent(new DataModelInitializedEvent(fCommandControl.getContext()),
            			                           fCommandControl.getProperties());
            	requestMonitor.done();
            }},
        /*
         * Cleanup
         */
        new Step() {
            @Override
            public void execute(final RequestMonitor requestMonitor) {
            	fTracker.dispose();
                fTracker = null;
                requestMonitor.done();
            }},
	};
	     
	GdbLaunch fLaunch;
    SessionType fSessionType;
    boolean fAttach;

    private IGDBControl fCommandControl;
    private IGDBBackend	fGDBBackend;
    private IMIProcesses fProcService;
    private CommandFactory fCommandFactory;
    private IGDBJtagDevice fGdbJtagDevice;

    DsfServicesTracker fTracker;
        
    /**
	 * @since 8.0
	 */
    public GDBJtagDSFFinalLaunchSequence(DsfExecutor executor, GdbLaunch launch, SessionType sessionType, boolean attach, RequestMonitorWithProgress rm) {
        super(executor, rm, LaunchMessages.getString("FinalLaunchSequence.0"), LaunchMessages.getString("FinalLaunchSequence.1"));     //$NON-NLS-1$ //$NON-NLS-2$
        fLaunch = launch;
        fSessionType = sessionType;
        fAttach = attach;
    }
	
	/* (non-Javadoc)
	 * @see org.eclipse.cdt.dsf.gdb.launching.FinalLaunchSequence#getSteps()
	 */
	@Override
	public Step[] getSteps() {
		return fSteps;
	}
	
	/**
	 * @param config
	 * @return IGDBJtagDevice the selected Jtag device
	 * @throws CoreException
	 * @throws NullPointerException
	 */
	private IGDBJtagDevice getGDBJtagDevice (ILaunchConfiguration config) 
	throws CoreException, NullPointerException {
		IGDBJtagDevice gdbJtagDevice = null;
		String jtagDeviceName = config.getAttribute(IGDBJtagConstants.ATTR_JTAG_DEVICE, IGDBJtagConstants.DEFAULT_JTAG_DEVICE); 
		GDBJtagDeviceContribution[] availableDevices = GDBJtagDeviceContributionFactory.getInstance().getGDBJtagDeviceContribution();
		for (GDBJtagDeviceContribution availableDevice : availableDevices) {
			if (jtagDeviceName.equals(availableDevice.getDeviceName())) {
				gdbJtagDevice = availableDevice.getDevice();
				break;
			}
		}
		return gdbJtagDevice;
	}
	
	/**
	 * @param commands
	 * @return String commands in String format
	 */
	private String composeCommand(Collection<String> commands) {
		if (commands.isEmpty())
			return null;
		StringBuffer sb = new StringBuffer();
		Iterator<String> it = commands.iterator();
		while (it.hasNext()) {
			sb.append(it.next());
		}
		return sb.toString();
	}
}
