package org.rubypeople.rdt.internal.debug.ui.launcher;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.debug.core.DebugPlugin;
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.ui.DebugUITools;
import org.eclipse.debug.ui.ILaunchShortcut;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.rubypeople.rdt.core.IRubyElement;
import org.rubypeople.rdt.debug.ui.InstallDeveloperToolsDialog;
import org.rubypeople.rdt.debug.ui.RdtDebugUiConstants;
import org.rubypeople.rdt.internal.debug.ui.RdtDebugUiMessages;
import org.rubypeople.rdt.internal.debug.ui.RdtDebugUiPlugin;
import org.rubypeople.rdt.internal.ui.RubyPlugin;
import org.rubypeople.rdt.launching.IRubyLaunchConfigurationConstants;
import org.rubypeople.rdt.launching.RubyRuntime;
import com.aptana.rdt.AptanaRDTPlugin;
import com.aptana.rdt.core.gems.ContributedGemRegistry;
import com.aptana.rdt.core.gems.Gem;
import com.aptana.rdt.core.gems.Version;
public class RubyApplicationShortcut implements ILaunchShortcut
{
private static final String RUBY_PROFILING_GEM_NAME = "ruby-prof";
private static final String RUBY_DEBUG_IDE_GEM_NAME = "ruby-debug-ide";
private static final String MINIMUM_RUBY_DEBUG_IDE_VERSION = "0.4.5";
public void launch(ISelection selection, String mode)
{
Object firstSelection = null;
if (selection instanceof IStructuredSelection)
{
firstSelection = ((IStructuredSelection) selection).getFirstElement();
}
if (firstSelection == null)
{
log("Could not find selection.");
return;
}
IRubyElement rubyElement = null;
if (firstSelection instanceof IAdaptable)
{
rubyElement = (IRubyElement) ((IAdaptable) firstSelection).getAdapter(IRubyElement.class);
}
if (rubyElement == null)
{
log("Selection is not a ruby element.");
return;
}
doLaunchWithErrorHandling(rubyElement, mode);
}
private void doLaunchWithErrorHandling(IRubyElement rubyElement, String mode)
{
if (shouldInstallGemsFirst(mode))
return;
try
{
doLaunch(rubyElement, mode);
}
catch (CoreException e)
{
log(e);
IStatus status = e.getStatus();
String title = RdtDebugUiMessages.Dialog_launchErrorTitle;
String message = RdtDebugUiMessages.Dialog_launchErrorMessage;
if (status != null)
{
ErrorDialog.openError(RdtDebugUiPlugin.getActiveWorkbenchWindow().getShell(), title, message, status);
}
}
}
/**
* A check to run before launching. If we're trying to debug or profile, let's make sure the user has installed the
* necessary gems.
*
* @param mode
* @return
*/
private static boolean shouldInstallGemsFirst(String mode)
{
// XXX All these prompts need to have their strings externalized for i18n!
if (mode.equals(ILaunchManager.RUN_MODE))
return false;
// Install ruby-debug and ruby-prof on demand when user first tries to use those launch modes
if (mode.equals(ILaunchManager.DEBUG_MODE))
{
if (!AptanaRDTPlugin.getDefault().getGemManager().gemInstalled(RUBY_DEBUG_IDE_GEM_NAME))
{
installNecessaryDebuggingGems();
return true;
}
else if (updatingRubyDebugIDEGem())
{
return true;
}
}
else if (mode.equals(ILaunchManager.PROFILE_MODE))
{
if (RubyRuntime.currentVMIsJRuby())
{
MessageDialog
.openError(
Display.getDefault().getActiveShell(),
"Profiling not yet supported on JRuby",
"Profiling is not yet available for the JRuby interpreter. We rely on the ruby-prof gem, which requires native code, and there is not yet a Java based version of the gem.");
return true;
}
if (!AptanaRDTPlugin.getDefault().getGemManager().gemInstalled(RUBY_PROFILING_GEM_NAME))
{
installNecessaryProfilingGems();
return true;
}
}
return false;
}
/**
* Updates the ruby-debug-ide gem if necessary
*
* @return false if no update necessary, true if we scheduled an update
*/
private static boolean updatingRubyDebugIDEGem()
{
List<Version> versions = AptanaRDTPlugin.getDefault().getGemManager().getVersions(RUBY_DEBUG_IDE_GEM_NAME);
for (Version version : versions)
{
if (version.isGreaterThanOrEqualTo(MINIMUM_RUBY_DEBUG_IDE_VERSION))
return false;
}
// Prompt user to let us update ruby-debug-ide
if (!MessageDialog
.openQuestion(
Display.getDefault().getActiveShell(),
"ruby-debug-ide out of date",
"To debug, it is required that you use the ruby-debug-ide gem, with version >= "
+ MINIMUM_RUBY_DEBUG_IDE_VERSION
+ ". Would you like us to update that gem for you? (You will need to relaunch once it's been updated)."))
return true;
Job job = new Job("Updating installed ruby-debug-ide gem...")
{
@Override
protected IStatus run(IProgressMonitor monitor)
{
return AptanaRDTPlugin.getDefault().getGemManager().update(
new Gem(RUBY_DEBUG_IDE_GEM_NAME, Gem.ANY_VERSION, null), monitor);
}
};
job.setUser(true);
job.schedule();
return true;
}
private static void installNecessaryProfilingGems()
{
if (!MessageDialog.openQuestion(Display.getDefault().getActiveShell(), "ruby-prof not installed",
"To profile, it is required that you use the ruby-prof gem. Would you like us to install that gem?"))
return;
if (InstallDeveloperToolsDialog.shouldShow())
{
InstallDeveloperToolsDialog dialog = new InstallDeveloperToolsDialog(Display.getDefault().getActiveShell());
dialog.open();
return;
}
List<Gem> finalGems = new ArrayList<Gem>();
Gem gem = ContributedGemRegistry.getGem(RUBY_PROFILING_GEM_NAME);
if (gem != null)
finalGems.add(gem);
Job job = new InstallGemsJob(finalGems);
job.setSystem(true);
job.schedule();
}
private static void installNecessaryDebuggingGems()
{
if (!MessageDialog
.openQuestion(Display.getDefault().getActiveShell(), "ruby-debug not installed",
"To debug, it is recommended that you use the ruby-debug based debugger. Would you like us to install that gem?"))
return;
if (InstallDeveloperToolsDialog.shouldShow())
{
InstallDeveloperToolsDialog dialog = new InstallDeveloperToolsDialog(Display.getDefault().getActiveShell());
dialog.open();
return;
}
List<Gem> finalGems = new ArrayList<Gem>();
Gem gem = ContributedGemRegistry.getGem("ruby-debug-base");
if (gem != null)
finalGems.add(gem);
gem = ContributedGemRegistry.getGem(RUBY_DEBUG_IDE_GEM_NAME);
if (gem != null)
finalGems.add(gem);
Job job = new InstallGemsJob(finalGems);
job.setSystem(true);
job.schedule();
}
protected void doLaunch(IRubyElement rubyElement, String mode) throws CoreException
{
ILaunchConfiguration config = findOrCreateLaunchConfiguration(rubyElement, mode);
if (config != null)
{
DebugUITools.launch(config, mode);
}
}
public void launch(IEditorPart editor, String mode)
{
IEditorInput input = editor.getEditorInput();
if (input == null)
{
log("Could not retrieve input from editor: " + editor.getTitle());
return;
}
IRubyElement rubyElement = (IRubyElement) input.getAdapter(IRubyElement.class);
if (rubyElement == null)
{
log("Editor input is not a ruby file or external ruby file.");
return;
}
doLaunchWithErrorHandling(rubyElement, mode);
}
protected ILaunchConfiguration findOrCreateLaunchConfiguration(IRubyElement rubyElement, String mode)
throws CoreException
{
IFile rubyFile = (IFile) rubyElement.getUnderlyingResource();
ILaunchConfigurationType configType = getRubyLaunchConfigType();
List candidateConfigs = null;
ILaunchConfiguration[] configs = getLaunchManager().getLaunchConfigurations(configType);
candidateConfigs = new ArrayList(configs.length);
for (int i = 0; i < configs.length; i++)
{
ILaunchConfiguration config = configs[i];
boolean projectsEqual = config.getAttribute(IRubyLaunchConfigurationConstants.ATTR_PROJECT_NAME, "")
.equals(rubyFile.getProject().getName());
if (projectsEqual)
{
boolean projectRelativeFileNamesEqual = config.getAttribute(
IRubyLaunchConfigurationConstants.ATTR_FILE_NAME, "").equals(
rubyFile.getProjectRelativePath().toString());
if (projectRelativeFileNamesEqual)
{
candidateConfigs.add(config);
}
}
}
switch (candidateConfigs.size())
{
case 0:
return createConfiguration(rubyFile);
case 1:
return (ILaunchConfiguration) candidateConfigs.get(0);
default:
Status status = new Status(Status.WARNING, RdtDebugUiPlugin.PLUGIN_ID, 0,
RdtDebugUiMessages.LaunchConfigurationShortcut_Ruby_multipleConfigurationsError, null);
throw new CoreException(status);
}
}
protected ILaunchConfiguration createConfiguration(IFile rubyFile)
{
if (RubyRuntime.getDefaultVMInstall() == null)
{
this.showNoInterpreterDialog();
return null;
}
ILaunchConfiguration config = null;
try
{
ILaunchConfigurationType configType = getRubyLaunchConfigType();
ILaunchConfigurationWorkingCopy wc = configType.newInstance(null, getLaunchManager()
.generateUniqueLaunchConfigurationNameFrom(rubyFile.getName()));
wc.setAttribute(IRubyLaunchConfigurationConstants.ATTR_PROJECT_NAME, rubyFile.getProject().getName());
wc.setAttribute(IRubyLaunchConfigurationConstants.ATTR_FILE_NAME, rubyFile.getProjectRelativePath()
.toString());
wc.setAttribute(IRubyLaunchConfigurationConstants.ATTR_WORKING_DIRECTORY, RubyApplicationShortcut
.getDefaultWorkingDirectory(rubyFile.getProject()));
wc.setAttribute(IRubyLaunchConfigurationConstants.ATTR_VM_INSTALL_NAME, RubyRuntime.getDefaultVMInstall()
.getName());
wc.setAttribute(IRubyLaunchConfigurationConstants.ATTR_VM_INSTALL_TYPE, RubyRuntime.getDefaultVMInstall()
.getVMInstallType().getId());
wc.setAttribute(ILaunchConfiguration.ATTR_SOURCE_LOCATOR_ID, RdtDebugUiConstants.RUBY_SOURCE_LOCATOR);
config = wc.doSave();
}
catch (CoreException ce)
{
log(ce);
}
return config;
}
protected ILaunchConfigurationType getRubyLaunchConfigType()
{
return getLaunchManager().getLaunchConfigurationType(IRubyLaunchConfigurationConstants.ID_RUBY_APPLICATION);
}
protected ILaunchManager getLaunchManager()
{
return DebugPlugin.getDefault().getLaunchManager();
}
protected void log(String message)
{
RdtDebugUiPlugin.log(new Status(Status.INFO, RdtDebugUiPlugin.PLUGIN_ID, Status.INFO, message, null));
}
protected void log(Throwable t)
{
RdtDebugUiPlugin.log(t);
}
protected void showNoInterpreterDialog()
{
MessageDialog.openInformation(RubyPlugin.getActiveWorkbenchShell(),
RdtDebugUiMessages.Dialog_launchWithoutSelectedInterpreter_title,
RdtDebugUiMessages.Dialog_launchWithoutSelectedInterpreter);
}
protected static String getDefaultWorkingDirectory(IProject project)
{
if (project != null && project.exists())
{
return project.getLocation().toOSString();
}
// might have been deleted
return RdtDebugUiPlugin.getWorkspace().getRoot().getLocation().toOSString();
}
}