/******************************************************************************* * Copyright (c) 2000, 2015 IBM Corporation 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: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.jdt.internal.debug.ui.snippeteditor; import java.io.File; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; import java.net.URL; import java.net.URLEncoder; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.StringTokenizer; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.FileLocator; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.QualifiedName; import org.eclipse.core.runtime.Status; import org.eclipse.debug.core.DebugEvent; import org.eclipse.debug.core.DebugPlugin; import org.eclipse.debug.core.IDebugEventSetListener; 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.model.IBreakpoint; import org.eclipse.debug.core.model.IDebugTarget; import org.eclipse.debug.ui.IDebugUIConstants; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.ToolFactory; import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.core.util.IClassFileReader; import org.eclipse.jdt.core.util.ICodeAttribute; import org.eclipse.jdt.core.util.ILineNumberAttribute; import org.eclipse.jdt.core.util.IMethodInfo; import org.eclipse.jdt.debug.core.IJavaLineBreakpoint; import org.eclipse.jdt.debug.core.JDIDebugModel; import org.eclipse.jdt.debug.ui.IJavaDebugUIConstants; import org.eclipse.jdt.internal.debug.ui.JDIDebugUIPlugin; import org.eclipse.jdt.internal.launching.JavaMigrationDelegate; import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants; import org.eclipse.jdt.launching.IRuntimeClasspathEntry; import org.eclipse.jdt.launching.IVMInstall; import org.eclipse.jdt.launching.JavaRuntime; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.osgi.util.NLS; /** * Support for launching scrapbook using launch configurations. */ public class ScrapbookLauncher implements IDebugEventSetListener { public static final String SCRAPBOOK_LAUNCH = IJavaDebugUIConstants.PLUGIN_ID + ".scrapbook_launch"; //$NON-NLS-1$ public static final String SCRAPBOOK_FILE_PATH = IJavaDebugUIConstants.PLUGIN_ID + ".scrapbook_file_path"; //$NON-NLS-1$ /** * Persistent property associated with snippet files specifying working directory. * Same format as the associated launch configuration attribute * <code>ATTR_WORKING_DIR</code>. */ public static final QualifiedName SNIPPET_EDITOR_LAUNCH_CONFIG_HANDLE_MEMENTO = new QualifiedName(IJavaDebugUIConstants.PLUGIN_ID, "snippet_editor_launch_config"); //$NON-NLS-1$ private IJavaLineBreakpoint fMagicBreakpoint; private HashMap<IFile, IDebugTarget> fScrapbookToVMs = new HashMap<>(10); private HashMap<IDebugTarget, IBreakpoint> fVMsToBreakpoints = new HashMap<>(10); private HashMap<IDebugTarget, IFile> fVMsToScrapbooks = new HashMap<>(10); private static ScrapbookLauncher fgDefault = null; private ScrapbookLauncher() { //see getDefault() } public static ScrapbookLauncher getDefault() { if (fgDefault == null) { fgDefault = new ScrapbookLauncher(); } return fgDefault; } /** * Launches a VM for the given scrapbook page, in debug mode. * Returns an existing launch if the page is already running. * @param page the scrapbook page file * * @return resulting launch, or <code>null</code> on failure */ protected ILaunch launch(IFile page) { // clean up orphaned launch configurations cleanupLaunchConfigurations(); if (!page.getFileExtension().equals("jpage")) { //$NON-NLS-1$ showNoPageDialog(); return null; } IDebugTarget vm = getDebugTarget(page); if (vm != null) { //already launched return vm.getLaunch(); } IJavaProject javaProject= JavaCore.create(page.getProject()); URL jarURL = null; try { jarURL = JDIDebugUIPlugin.getDefault().getBundle().getEntry("snippetsupport.jar"); //$NON-NLS-1$ jarURL = FileLocator.toFileURL(jarURL); } catch (MalformedURLException e) { JDIDebugUIPlugin.errorDialog("Unable to launch scrapbook VM", e); //$NON-NLS-1$ return null; } catch (IOException e) { JDIDebugUIPlugin.errorDialog("Unable to launch scrapbook VM", e); //$NON-NLS-1$ return null; } List<IRuntimeClasspathEntry> cp = new ArrayList<>(3); String jarFile = jarURL.getFile(); IRuntimeClasspathEntry supportEntry = JavaRuntime.newArchiveRuntimeClasspathEntry(new Path(jarFile)); cp.add(supportEntry); // get bootpath entries try { IRuntimeClasspathEntry[] entries = JavaRuntime.computeUnresolvedRuntimeClasspath(javaProject); for (int i = 0; i < entries.length; i++) { if (entries[i].getClasspathProperty() != IRuntimeClasspathEntry.USER_CLASSES) { cp.add(entries[i]); } } IRuntimeClasspathEntry[] classPath = cp.toArray(new IRuntimeClasspathEntry[cp.size()]); return doLaunch(javaProject, page, classPath, jarFile); } catch (CoreException e) { JDIDebugUIPlugin.errorDialog("Unable to launch scrapbook VM", e); //$NON-NLS-1$ } return null; } private ILaunch doLaunch(IJavaProject p, IFile page, IRuntimeClasspathEntry[] classPath, String jarFile) { try { if (fVMsToScrapbooks.isEmpty()) { // register for debug events if a scrapbook is not currently running DebugPlugin.getDefault().addDebugEventListener(this); } ILaunchConfiguration config = null; ILaunchConfigurationWorkingCopy wc = null; try { config = getLaunchConfigurationTemplate(page); if (config != null) { wc = config.getWorkingCopy(); } } catch (CoreException e) { config = null; JDIDebugUIPlugin.errorDialog("Unable to retrieve scrapbook settings", e); //$NON-NLS-1$ } if (config == null) { config = createLaunchConfigurationTemplate(page); wc = config.getWorkingCopy(); } IPath outputLocation = p.getProject().getWorkingLocation(JDIDebugUIPlugin.getUniqueIdentifier()); File f = outputLocation.toFile(); URL u = null; try { u = getEncodedURL(f); } catch (MalformedURLException e) { JDIDebugUIPlugin.errorDialog("Unable to launch scrapbook VM", e); //$NON-NLS-1$ return null; } catch(UnsupportedEncodingException usee) { JDIDebugUIPlugin.errorDialog("Unable to launch scrapbook VM", usee); //$NON-NLS-1$ return null; } String[] defaultClasspath = JavaRuntime.computeDefaultRuntimeClassPath(p); String[] urls = new String[defaultClasspath.length + 1]; urls[0] = u.toExternalForm(); for (int i = 0; i < defaultClasspath.length; i++) { f = new File(defaultClasspath[i]); try { urls[i + 1] = getEncodedURL(f).toExternalForm(); } catch (MalformedURLException e) { JDIDebugUIPlugin.errorDialog("Unable to launch scrapbook VM", e); //$NON-NLS-1$ return null; } catch(UnsupportedEncodingException usee) { JDIDebugUIPlugin.errorDialog("Unable to launch scrapbook VM", usee); //$NON-NLS-1$ return null; } } // convert to mementos List<String> classpathList= new ArrayList<>(classPath.length); for (int i = 0; i < classPath.length; i++) { classpathList.add(classPath[i].getMemento()); } if(wc == null) { wc = config.getWorkingCopy(); } wc.setAttribute(IJavaLaunchConfigurationConstants.ATTR_DEFAULT_CLASSPATH, false); wc.setAttribute(IJavaLaunchConfigurationConstants.ATTR_CLASSPATH, classpathList); wc.setAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, p.getElementName()); if (wc.getAttribute(IJavaLaunchConfigurationConstants.ATTR_SOURCE_PATH_PROVIDER, (String)null) == null) { wc.setAttribute(IJavaLaunchConfigurationConstants.ATTR_SOURCE_PATH_PROVIDER, "org.eclipse.jdt.debug.ui.scrapbookSourcepathProvider"); //$NON-NLS-1$ } StringBuffer urlsString = new StringBuffer(); for (int i = 0; i < urls.length; i++) { urlsString.append(' '); urlsString.append(urls[i]); } wc.setAttribute(IJavaLaunchConfigurationConstants.ATTR_PROGRAM_ARGUMENTS, urlsString.toString()); wc.setAttribute(SCRAPBOOK_LAUNCH, SCRAPBOOK_LAUNCH); config = wc.doSave(); ILaunch launch = config.launch(ILaunchManager.DEBUG_MODE, null); if (launch != null) { IDebugTarget dt = launch.getDebugTarget(); IBreakpoint magicBreakpoint = createMagicBreakpoint(jarFile); fScrapbookToVMs.put(page, dt); fVMsToScrapbooks.put(dt, page); fVMsToBreakpoints.put(dt, magicBreakpoint); dt.breakpointAdded(magicBreakpoint); launch.setAttribute(SCRAPBOOK_LAUNCH, SCRAPBOOK_LAUNCH); return launch; } } catch (CoreException e) { JDIDebugUIPlugin.errorDialog("Unable to launch scrapbook VM", e); //$NON-NLS-1$ } return null; } /** * Creates an "invisible" line breakpoint. * * @param jarFile * path to the snippetsupport.jar file * @return the new 'magic' breakpoint * @throws CoreException * if an exception occurs */ IBreakpoint createMagicBreakpoint(String jarFile) throws CoreException { // set a breakpoint on the "Thread.sleep(100);" line of the "ScrapbookMainnop()" method String typeName = "org.eclipse.jdt.internal.debug.ui.snippeteditor.ScrapbookMain"; //$NON-NLS-1$ IClassFileReader reader = ToolFactory.createDefaultClassFileReader(jarFile, typeName.replace('.', '/') + ".class", IClassFileReader.METHOD_INFOS | IClassFileReader.METHOD_BODIES); //$NON-NLS-1$ IMethodInfo[] methodInfos = reader.getMethodInfos(); for (IMethodInfo methodInfo : methodInfos) { if (!CharOperation.equals("nop".toCharArray(), methodInfo.getName())) {//$NON-NLS-1$ continue; } ICodeAttribute codeAttribute = methodInfo.getCodeAttribute(); ILineNumberAttribute lineNumberAttribute = codeAttribute.getLineNumberAttribute(); int[][] lineNumberTable = lineNumberAttribute.getLineNumberTable(); int lineNumber = lineNumberTable[0][1]; fMagicBreakpoint = JDIDebugModel.createLineBreakpoint(ResourcesPlugin.getWorkspace().getRoot(), typeName, lineNumber, -1, -1, 0, false, null); fMagicBreakpoint.setPersisted(false); return fMagicBreakpoint; } throw new CoreException(new Status(IStatus.ERROR, JDIDebugUIPlugin.getUniqueIdentifier(), IJavaDebugUIConstants.INTERNAL_ERROR, "An error occurred creating the evaluation breakpoint location.", null)); //$NON-NLS-1$ } /** * @see IDebugEventSetListener#handleDebugEvents(DebugEvent[]) */ @Override public void handleDebugEvents(DebugEvent[] events) { for (int i = 0; i < events.length; i++) { DebugEvent event = events[i]; if (event.getSource() instanceof IDebugTarget && event.getKind() == DebugEvent.TERMINATE) { cleanup((IDebugTarget)event.getSource()); } } } /** * Returns the debug target associated with the given * scrapbook page, or <code>null</code> if none. * * @param page file representing scrapbook page * @return associated debug target or <code>null</code> */ public IDebugTarget getDebugTarget(IFile page) { return fScrapbookToVMs.get(page); } /** * Returns the magic breakpoint associated with the given * scrapbook VM. The magic breakpoint is the location at * which an evaluation begins. * * @param target a scrapbook debug target * @return the breakpoint at which an evaluation begins * or <code>null</code> if none */ public IBreakpoint getMagicBreakpoint(IDebugTarget target) { return fVMsToBreakpoints.get(target); } protected void showNoPageDialog() { String title= SnippetMessages.getString("ScrapbookLauncher.error.title"); //$NON-NLS-1$ String msg= SnippetMessages.getString("ScrapbookLauncher.error.pagenotfound"); //$NON-NLS-1$ MessageDialog.openError(JDIDebugUIPlugin.getActiveWorkbenchShell(),title, msg); } protected void cleanup(IDebugTarget target) { Object page = fVMsToScrapbooks.get(target); if (page != null) { fVMsToScrapbooks.remove(target); fScrapbookToVMs.remove(page); fVMsToBreakpoints.remove(target); ILaunch launch = target.getLaunch(); if (launch != null) { getLaunchManager().removeLaunch(launch); } if (fVMsToScrapbooks.isEmpty()) { // no need to listen to events if no scrap books running DebugPlugin.getDefault().removeDebugEventListener(this); } } } protected URL getEncodedURL(File file) throws MalformedURLException, UnsupportedEncodingException { //looking at File.toURL the delimiter is always '/' // NOT File.separatorChar String urlDelimiter= "/"; //$NON-NLS-1$ String unencoded= file.toURL().toExternalForm(); StringBuffer encoded= new StringBuffer(); StringTokenizer tokenizer= new StringTokenizer(unencoded, urlDelimiter); encoded.append(tokenizer.nextToken()); //file: encoded.append(urlDelimiter); encoded.append(tokenizer.nextToken()); //drive letter and ':' while (tokenizer.hasMoreElements()) { encoded.append(urlDelimiter); String token= tokenizer.nextToken(); try { encoded.append(URLEncoder.encode(token, ResourcesPlugin.getEncoding())); } catch (UnsupportedEncodingException e) { encoded.append(URLEncoder.encode(token, "UTF-8")); //$NON-NLS-1$ } } if (file.isDirectory()) { encoded.append(urlDelimiter); } return new URL(encoded.toString()); } /** * Returns the launch configuration used as a template for launching the * given scrapbook file, or <code>null</code> if none. The template contains * working directory and JRE settings to use when launching the scrapbook. * @param file the backing config file * @return the launch configuration template * @throws CoreException if an exception occurs */ public static ILaunchConfiguration getLaunchConfigurationTemplate(IFile file) throws CoreException { String memento = getLaunchConfigMemento(file); if (memento != null) { return getLaunchManager().getLaunchConfiguration(memento); } return null; } /** * Creates and saves template launch configuration for the given scrapbook file. * @param page the backing page * @return the new {@link ILaunchConfiguration} template * @throws CoreException if an exception occurs */ public static ILaunchConfiguration createLaunchConfigurationTemplate(IFile page) throws CoreException { ILaunchConfigurationType lcType = getLaunchManager().getLaunchConfigurationType(IJavaLaunchConfigurationConstants.ID_JAVA_APPLICATION); String name = NLS.bind(SnippetMessages.getString("ScrapbookLauncher.17"), new String[]{page.getName()}); //$NON-NLS-1$ ILaunchConfigurationWorkingCopy wc = lcType.newInstance(null, name); wc.setAttribute(IDebugUIConstants.ATTR_PRIVATE, true); wc.setAttribute(IJavaLaunchConfigurationConstants.ATTR_MAIN_TYPE_NAME, "org.eclipse.jdt.internal.debug.ui.snippeteditor.ScrapbookMain"); //$NON-NLS-1$ wc.setAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, page.getProject().getName()); wc.setAttribute(SCRAPBOOK_LAUNCH, SCRAPBOOK_LAUNCH); wc.setAttribute(SCRAPBOOK_FILE_PATH, page.getFullPath().toString()); wc.setAttribute(IJavaLaunchConfigurationConstants.ATTR_SOURCE_PATH_PROVIDER, "org.eclipse.jdt.debug.ui.scrapbookSourcepathProvider"); //$NON-NLS-1$ JavaMigrationDelegate.updateResourceMapping(wc); ILaunchConfiguration config = wc.doSave(); setLaunchConfigMemento(page, config.getMemento()); return config; } /** * Returns the handle memento for the given scrapbook's launch configuration * template, or <code>null</code> if none. * @param file the launch configuration template * @return the {@link String} memento */ private static String getLaunchConfigMemento(IFile file) { try { return file.getPersistentProperty(SNIPPET_EDITOR_LAUNCH_CONFIG_HANDLE_MEMENTO); } catch (CoreException e) { JDIDebugUIPlugin.log(e); } return null; } /** * Sets the handle memento for the given scrapbook's launch configuration * template. * @param file the backing file * @param memento the {@link String} memento */ protected static void setLaunchConfigMemento(IFile file, String memento) { try { file.setPersistentProperty(SNIPPET_EDITOR_LAUNCH_CONFIG_HANDLE_MEMENTO, memento); } catch (CoreException e) { JDIDebugUIPlugin.log(e); } } /** * Returns the launch manager. * @return the launch manager instance */ protected static ILaunchManager getLaunchManager() { return DebugPlugin.getDefault().getLaunchManager(); } /** * Returns the working directory attribute for the given snippet file, * possibly <code>null</code>. * @param file the backing file * @return the working directory * * @exception CoreException if unable to retrieve the attribute */ public static String getWorkingDirectoryAttribute(IFile file) throws CoreException { ILaunchConfiguration config = getLaunchConfigurationTemplate(file); if (config != null) { return config.getAttribute(IJavaLaunchConfigurationConstants.ATTR_WORKING_DIRECTORY, (String)null); } return null; } /** * Returns the VM arguments attribute for the given snippet file, * possibly <code>null</code>. * @param file the backing file * @return the VM arguments * * @exception CoreException if unable to retrieve the attribute */ public static String getVMArgsAttribute(IFile file) throws CoreException { ILaunchConfiguration config = getLaunchConfigurationTemplate(file); if (config != null) { return config.getAttribute(IJavaLaunchConfigurationConstants.ATTR_VM_ARGUMENTS, (String)null); } return null; } /** * Returns the VM install used to launch the given snippet file. * @param file the backing file * @return the VM install * * @exception CoreException if unable to retrieve the attribute */ public static IVMInstall getVMInstall(IFile file) throws CoreException { ILaunchConfiguration config = getLaunchConfigurationTemplate(file); if (config == null) { IJavaProject pro = JavaCore.create(file.getProject()); return JavaRuntime.getVMInstall(pro); } return JavaRuntime.computeVMInstall(config); } /** * Deletes any scrapbook launch configurations for scrap books that * have been deleted. Rather than listening to all resource deltas, * configurations are deleted each time a scrapbook is launched - which is * infrequent. */ public void cleanupLaunchConfigurations() { try { ILaunchConfigurationType lcType = getLaunchManager().getLaunchConfigurationType(IJavaLaunchConfigurationConstants.ID_JAVA_APPLICATION); ILaunchConfiguration[] configs = getLaunchManager().getLaunchConfigurations(lcType); IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); for (int i = 0; i < configs.length; i++) { String path = configs[i].getAttribute(SCRAPBOOK_FILE_PATH, (String)null); if (path != null) { IPath pagePath = new Path(path); IResource res = root.findMember(pagePath); if (res == null) { // config without a page - delete it configs[i].delete(); } } } } catch (CoreException e) { // log quietly JDIDebugUIPlugin.log(e); } } }