/**
* Copyright (c) 2012 by JP Moresmau
* This code is made available under the terms of the Eclipse Public License,
* version 1.0 (EPL). See http://www.eclipse.org/legal/epl-v10.html
*/
package net.sf.eclipsefp.haskell.debug.core.internal.launch;
import java.io.File;
import java.io.IOException;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.sf.eclipsefp.haskell.core.HaskellCorePlugin;
import net.sf.eclipsefp.haskell.core.util.ResourceUtil;
import net.sf.eclipsefp.haskell.debug.core.internal.HaskellDebugCore;
import net.sf.eclipsefp.haskell.debug.core.internal.util.CoreTexts;
import net.sf.eclipsefp.haskell.util.CommandLineUtil;
import net.sf.eclipsefp.haskell.util.NetworkUtil;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationType;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.debug.core.ILaunchesListener2;
import org.eclipse.debug.core.model.IProcess;
import org.eclipse.debug.core.model.LaunchConfigurationDelegate;
import org.eclipse.debug.ui.DebugUITools;
import org.eclipse.debug.ui.IDebugUIConstants;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.widgets.Display;
/**
* abstract base class for our launch delegates
* @author JP Moresmau
*
*/
public abstract class AbstractHaskellLaunchDelegate extends LaunchConfigurationDelegate{
public static IInteractiveLaunchOperationDelegate getDelegate(final ILaunchConfiguration configuration) throws CoreException{
String delegateClass=configuration.getAttribute( ILaunchAttributes.DELEGATE, "" ); //$NON-NLS-1$
IInteractiveLaunchOperationDelegate delegate=null;
if (delegateClass.length()>0){
try {
delegate = InteractiveDelegateManager.getContributors().get( delegateClass );
} catch (Throwable e){
HaskellDebugCore.log( e.getLocalizedMessage(), e );
}
}
return delegate;
}
@Override
public void launch( final ILaunchConfiguration configuration,
final String mode, final ILaunch launch, IProgressMonitor monitor )
throws CoreException {
if (monitor==null){
monitor=new NullProgressMonitor();
}
if( !monitor.isCanceled() ) {
try {
IInteractiveLaunchOperationDelegate delegate=getDelegate( configuration );
monitor.beginTask( configuration.getName(), 3 );
final IPath loc =delegate!=null?new Path(delegate.getExecutable()) :
getExecutableLocation( configuration );
if (loc==null){
String msg = CoreTexts.haskellLaunchDelegate_noExe;
String pluginId = HaskellDebugCore.getPluginId();
IStatus status = new Status( IStatus.ERROR, pluginId, 0, msg, null );
throw new CoreException( status );
}
checkCancellation( monitor );
String[] arguments = determineArguments( configuration,delegate,mode );
checkCancellation( monitor );
String[] cmdLine = createCmdLine( loc, arguments );
checkCancellation( monitor );
File workingDir = determineWorkingDir( configuration );
checkCancellation( monitor );
monitor.worked( 1 );
IProcess process = createProcess( configuration, mode, launch, loc,
cmdLine, workingDir );
monitor.worked( 1 );
if( process != null ) {
postProcessCreation( configuration, mode, launch, process );
}
monitor.done();
/*
* DebugPlugin.getDefault().getLaunchManager().addLaunchListener( new
* ILaunchesListener() {
*
* public void launchesRemoved( final ILaunch[] launches ) { for
* (ILaunch l:launches){ System.out.println("removed:" +l.toString()); }
*
* }
*
* public void launchesChanged( final ILaunch[] launches ) { for
* (ILaunch l:launches){ System.out.println("changed:" +l.toString()); }
*
* }
*
* public void launchesAdded( final ILaunch[] launches ) { for (ILaunch
* l:launches){ System.out.println("added:" +l.toString()); }
*
* } });
*/
if( process != null ) {
if ( isBackground( configuration ) || Display.findDisplay( Thread.currentThread())!=null) {
final IProcess theProcess = process;
Job endJob = new Job( NLS.bind( CoreTexts.running , loc.toOSString() )) {
@Override
protected IStatus run( final IProgressMonitor mon ) {
try {
while( !theProcess.isTerminated() ) {
try {
if( mon.isCanceled()) {
theProcess.terminate();
return Status.CANCEL_STATUS;
}
Thread.sleep( 50 );
} catch( InterruptedException iex ) {
// ignored
} catch ( DebugException ex ) {
// ignored
}
}
} finally {
try {
postProcessFinished(configuration);
} catch(CoreException ce){
HaskellCorePlugin.log( ce );
}
}
return Status.OK_STATUS;
}
};
endJob.schedule();
} else {
while( !process.isTerminated() ) {
try {
if(monitor.isCanceled() ) {
process.terminate();
break;
}
Thread.sleep( 50 );
} catch( InterruptedException iex ) {
// ignored
}
}
postProcessFinished(configuration);
}
}
} catch( LaunchCancelledException lcex ) {
// canceled on user request
}
}
}
protected abstract void postProcessCreation(final ILaunchConfiguration configuration,
final String mode,final ILaunch launch,IProcess process) throws CoreException;
protected abstract void preProcessCreation(final ILaunchConfiguration configuration,
final String mode,final ILaunch launch,Map<String, String> processAttribs) throws CoreException;
protected abstract void preProcessDefinitionCreation(final ILaunchConfiguration configuration,
final String mode,final ILaunch launch) throws CoreException;
protected abstract void postProcessFinished(final ILaunchConfiguration configuration) throws CoreException;
protected IProcess createProcess( final ILaunchConfiguration configuration,
final String mode, final ILaunch launch, final IPath location,
final String[] cmdLine, final File workingDir ) throws CoreException {
// Process proc = DebugPlugin.exec( cmdLine, workingDir );
ProcessBuilder pb = new ProcessBuilder( cmdLine );
pb.directory( workingDir );
if( configuration.getAttribute( ILaunchAttributes.SYNC_STREAMS, true ) ) {
pb.redirectErrorStream( true );
}
if (configuration.getAttribute( ILaunchAttributes.NEEDS_HTTP_PROXY, false ) ) {
NetworkUtil.addHTTP_PROXY_env( pb,NetworkUtil.HACKAGE_URL );
}
try {
Map<String,String> env=configuration.getAttribute( ILaunchManager.ATTR_ENVIRONMENT_VARIABLES,new HashMap<String,String>() );
if (env!=null && !env.isEmpty()){
pb.environment().putAll( env );
}
preProcessDefinitionCreation( configuration, mode, launch );
Map<String, String> processAttrs = new HashMap<>();
String programName = determineProgramName( location );
processAttrs.put( IProcess.ATTR_PROCESS_TYPE, programName );
preProcessCreation( configuration, mode, launch, processAttrs );
Process proc = pb.start();
if( proc != null ) {
//String loc = location.toOSString();
// process = new RuntimeProcess( launch, proc, configuration.getName(), processAttrs){
// @Override
// protected org.eclipse.debug.core.model.IStreamsProxy createStreamsProxy() {
//
// String encoding = getLaunch().getAttribute(DebugPlugin.ATTR_CONSOLE_ENCODING);
// return new DelayedStreamsProxy(getSystemProcess(), encoding);
// }
// };
// store start time
long startTime=System.currentTimeMillis();
processAttrs.put( ILaunchAttributes.START_TIME, String.valueOf(startTime) );
String startDate=DateFormat.getDateTimeInstance( DateFormat.LONG, DateFormat.LONG ).format( new Date(startTime) );
// see process console: label is used on its own for private config
if (DebugUITools.isPrivate( configuration )){
startDate=launch.getLaunchConfiguration().getName() + " " + startDate; //$NON-NLS-1$
}
final IProcess process=DebugPlugin.newProcess( launch, proc, startDate, processAttrs );
DebugPlugin.getDefault().getLaunchManager().addLaunchListener( new ILaunchesListener2() {
@Override
public void launchesRemoved( final ILaunch[] launches ) {
// NOOP
}
/* (non-Javadoc)
* @see org.eclipse.debug.core.ILaunchesListener2#launchesTerminated(org.eclipse.debug.core.ILaunch[])
*/
@Override
public void launchesTerminated( final ILaunch[] launches ) {
for (ILaunch l:launches){
if (launch==l){
DebugPlugin.getDefault().getLaunchManager().removeLaunchListener( this );
// specify duration
String type = null;
try {
type = configuration.getType().getName();
} catch (CoreException e) {
HaskellCorePlugin.log( e );
}
StringBuilder buffer = new StringBuilder();
buffer.append(configuration.getName());
if (type != null) {
buffer.append(" ["); //$NON-NLS-1$
buffer.append(type);
buffer.append("] "); //$NON-NLS-1$
}
String st=process.getAttribute( ILaunchAttributes.START_TIME );
if (st!=null && st.length()>0){
long start=Long.parseLong(st);
long dur=System.currentTimeMillis()-start;
buffer.append(CoreTexts.formatDuration( dur));
} else {
buffer.append(process.getLabel());
}
process.setAttribute( IProcess.ATTR_PROCESS_LABEL,buffer.toString());
}
}
}
@Override
public void launchesChanged( final ILaunch[] launches ) {
// NOOP
}
@Override
public void launchesAdded( final ILaunch[] launches ) {
// NOOP
}
});
process.setAttribute( IProcess.ATTR_CMDLINE, CommandLineUtil
.renderCommandLine( cmdLine ) );
return process;
}
return null;
} catch( IOException e ) {
Status status = new Status( IStatus.ERROR,
HaskellDebugCore.getPluginId(),
CoreTexts.haskellLaunchDelegate_noProcess, e );
throw new CoreException( status );
}
}
private String[] createCmdLine(final IPath location, final String[] arguments ) {
int cmdLineLength = 1;
if( arguments != null ) {
cmdLineLength += arguments.length;
}
String[] cmdLine = new String[ cmdLineLength ];
cmdLine[ 0 ] = location.toOSString();
if( arguments != null ) {
System.arraycopy( arguments, 0, cmdLine, 1, arguments.length );
}
return cmdLine;
}
protected File determineWorkingDir( final ILaunchConfiguration config )
throws CoreException {
String name = ILaunchAttributes.WORKING_DIRECTORY;
String attribute = config.getAttribute( name, ( String )null );
File result = null;
if( attribute != null ) {
result = new Path( attribute ).toFile();
}
return result;
}
String[] determineArguments( final ILaunchConfiguration config,final IInteractiveLaunchOperationDelegate delegate,final String mode )
throws CoreException {
String extra = config.getAttribute( ILaunchAttributes.EXTRA_ARGUMENTS,
ILaunchAttributes.EMPTY );
String args = config.getAttribute( ILaunchAttributes.ARGUMENTS,
ILaunchAttributes.EMPTY );
String[] fullArgs=CommandLineUtil.parse( extra + " " + args ); //$NON-NLS-1$
String[] delegateArgs=getDelegateArguments(config,delegate,mode);
if (delegateArgs.length>0){
String[] newArgs=new String[fullArgs.length+delegateArgs.length];
System.arraycopy( fullArgs, 0, newArgs, 0, fullArgs.length );
System.arraycopy( delegateArgs, 0, newArgs, fullArgs.length, delegateArgs.length );
fullArgs=newArgs;
}
return fullArgs;
}
public static String[] getDelegateArguments(final ILaunchConfiguration config,final IInteractiveLaunchOperationDelegate delegate,final String mode)throws CoreException{
if (delegate!=null){
IProject p=getProject( config );
List<String> fileNames=config.getAttribute( ILaunchAttributes.FILES, new ArrayList<String>() );
IFile[] files=new IFile[fileNames.size()];
for (int a=0;a<fileNames.size();a++){
files[a]=p.getFile( fileNames.get(a) );
}
return delegate.createArguments(p , files,mode );
}
return new String[0];
}
private void checkCancellation( final IProgressMonitor monitor ) {
if(monitor!=null && monitor.isCanceled() ) {
throw new LaunchCancelledException();
}
}
private String determineProgramName( final IPath location ) {
String programName = location.lastSegment();
String extension = location.getFileExtension();
if( extension != null ) {
int len = programName.length() - ( extension.length() + 1 );
programName = programName.substring( 0, len );
}
return programName.toLowerCase();
}
// helping methods
// ////////////////
public IPath getExecutableLocation( final ILaunchConfiguration config )
throws CoreException {
String location = config.getAttribute( ILaunchAttributes.EXECUTABLE,
(String)null );
if( isEmpty( location ) ) {
String stanza = config.getAttribute( ILaunchAttributes.STANZA,
(String)null );
if (isEmpty( stanza )){
String msg = CoreTexts.haskellLaunchDelegate_noExe;
String pluginId = HaskellDebugCore.getPluginId();
IStatus status = new Status( IStatus.ERROR, pluginId, 0, msg, null );
throw new CoreException( status );
}
return ResourceUtil.getExecutableLocation( getProject( config ), stanza ).getLocation();
}
return new Path( location );
}
public static IProject getProject(final ILaunchConfiguration config)throws CoreException{
String prj=config.getAttribute( ILaunchAttributes.PROJECT_NAME, (String)null );
if (prj!=null){
IProject p=ResourcesPlugin.getWorkspace().getRoot().getProject( prj );
if (p!=null){
return p;
}
}
return null;
}
protected boolean isEmpty( final String location ) {
return location == null || location.trim().length() == 0;
}
private boolean isBackground( final ILaunchConfiguration config )
throws CoreException {
return config.getAttribute( ILaunchAttributes.RUN_IN_BACKGROUND, true );
}
private class LaunchCancelledException extends RuntimeException {
private static final long serialVersionUID = 1912643423745032866L;
private LaunchCancelledException() {
super();
}
}
public static void runInConsole(final IProject prj,final List<String> commands,final File directory,final String title,final boolean needsHTTP_PROXY) throws CoreException{
runInConsole( prj, commands, directory, title, needsHTTP_PROXY,null );
}
public static void runInConsole(final IProject prj,final List<String> commands,final File directory,final String title,final boolean needsHTTP_PROXY,final Runnable after) throws CoreException{
final ILaunchManager launchManager = DebugPlugin.getDefault().getLaunchManager();
String configTypeId = ExecutableHaskellLaunchDelegate.class.getName();
ILaunchConfigurationType configType = launchManager.getLaunchConfigurationType( configTypeId );
final ILaunchConfigurationWorkingCopy wc=configType.newInstance( null,launchManager.generateLaunchConfigurationName(title) );//launchManager.generateUniqueLaunchConfigurationNameFrom( title));
// if private, we don't get the save dialog for unsaved files in project
wc.setAttribute( IDebugUIConstants.ATTR_PRIVATE, prj==null );
wc.setAttribute( IDebugUIConstants.ATTR_LAUNCH_IN_BACKGROUND, false );
wc.setAttribute( IDebugUIConstants.ATTR_CAPTURE_IN_CONSOLE, true );
if (prj!=null){
wc.setAttribute(ILaunchAttributes.PROJECT_NAME,prj.getName());
}
wc.setAttribute(ILaunchAttributes.EXECUTABLE,commands.get( 0 ));
if (directory!=null){
wc.setAttribute(ILaunchAttributes.WORKING_DIRECTORY,directory.getAbsolutePath());
}
if (commands.size()>1){
wc.setAttribute(ILaunchAttributes.EXTRA_ARGUMENTS,CommandLineUtil.renderCommandLine( commands.subList( 1, commands.size() ) ));
}
wc.setAttribute( ILaunchAttributes.NEEDS_HTTP_PROXY, needsHTTP_PROXY );
if (after!=null){
ILaunchesListener2 l=new ILaunchesListener2() {
@Override
public void launchesRemoved( final ILaunch[] paramArrayOfILaunch ) {
// NOOP
}
@Override
public void launchesChanged( final ILaunch[] paramArrayOfILaunch ) {
// NOOP
}
@Override
public void launchesAdded( final ILaunch[] paramArrayOfILaunch ) {
// NOOP
}
@Override
public void launchesTerminated( final ILaunch[] paramArrayOfILaunch ) {
for (ILaunch la:paramArrayOfILaunch){
if (la.getLaunchConfiguration()==wc){
after.run();
launchManager.removeLaunchListener( this );
}
}
}
};
launchManager.addLaunchListener( l );
}
// makes the console opens consistently
DebugUITools.launch( wc, ILaunchManager.RUN_MODE );
}
}