| /******************************************************************************* |
| * Copyright (c) 2000, 2015 IBM Corporation 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: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.jdt.internal.launching; |
| |
| |
| import java.io.BufferedInputStream; |
| import java.io.BufferedOutputStream; |
| import java.io.ByteArrayInputStream; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.net.URL; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Hashtable; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| |
| import javax.xml.parsers.DocumentBuilder; |
| import javax.xml.parsers.DocumentBuilderFactory; |
| import javax.xml.parsers.FactoryConfigurationError; |
| import javax.xml.parsers.ParserConfigurationException; |
| |
| import org.eclipse.core.resources.IResourceChangeEvent; |
| import org.eclipse.core.resources.IResourceChangeListener; |
| import org.eclipse.core.resources.ISaveContext; |
| import org.eclipse.core.resources.ISaveParticipant; |
| import org.eclipse.core.resources.IWorkspaceRunnable; |
| import org.eclipse.core.resources.ResourcesPlugin; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.FileLocator; |
| import org.eclipse.core.runtime.IConfigurationElement; |
| import org.eclipse.core.runtime.IExtensionPoint; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.MultiStatus; |
| import org.eclipse.core.runtime.Path; |
| import org.eclipse.core.runtime.Platform; |
| import org.eclipse.core.runtime.Plugin; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.core.runtime.jobs.Job; |
| import org.eclipse.core.runtime.preferences.IEclipsePreferences; |
| import org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent; |
| import org.eclipse.core.runtime.preferences.InstanceScope; |
| 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.ILaunchConfigurationWorkingCopy; |
| import org.eclipse.debug.core.ILaunchesListener; |
| import org.eclipse.debug.core.model.IDebugTarget; |
| import org.eclipse.debug.core.model.IProcess; |
| import org.eclipse.jdt.core.IClasspathEntry; |
| import org.eclipse.jdt.core.IJavaProject; |
| import org.eclipse.jdt.core.JavaCore; |
| import org.eclipse.jdt.internal.launching.sourcelookup.advanced.AdvancedSourceLookupSupport; |
| import org.eclipse.jdt.launching.IRuntimeClasspathEntry2; |
| import org.eclipse.jdt.launching.IVMConnector; |
| import org.eclipse.jdt.launching.IVMInstall; |
| import org.eclipse.jdt.launching.IVMInstallChangedListener; |
| import org.eclipse.jdt.launching.JavaRuntime; |
| import org.eclipse.jdt.launching.VMStandin; |
| import org.eclipse.jdt.launching.sourcelookup.ArchiveSourceLocation; |
| import org.eclipse.osgi.service.debug.DebugOptions; |
| import org.eclipse.osgi.service.debug.DebugOptionsListener; |
| import org.eclipse.osgi.service.debug.DebugTrace; |
| import org.eclipse.osgi.util.NLS; |
| import org.osgi.framework.BundleContext; |
| import org.osgi.service.prefs.BackingStoreException; |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.Node; |
| import org.w3c.dom.NodeList; |
| import org.xml.sax.InputSource; |
| import org.xml.sax.SAXException; |
| import org.xml.sax.helpers.DefaultHandler; |
| |
| @SuppressWarnings("deprecation") |
| public class LaunchingPlugin extends Plugin implements DebugOptionsListener, IEclipsePreferences.IPreferenceChangeListener, IVMInstallChangedListener, IResourceChangeListener, ILaunchesListener, IDebugEventSetListener { |
| |
| /** |
| * Whether debug options are turned on for this plug-in. |
| */ |
| public static boolean DEBUG = false; |
| public static boolean DEBUG_JRE_CONTAINER = false; |
| |
| public static final String DEBUG_JRE_CONTAINER_FLAG = "org.eclipse.jdt.launching/debug/classpath/jreContainer"; //$NON-NLS-1$ |
| public static final String DEBUG_FLAG = "org.eclipse.jdt.launching/debug"; //$NON-NLS-1$ |
| |
| /** |
| * list of temp files for the launch (separated by the path separator char). Files must start with {@link #LAUNCH_TEMP_FILE_PREFIX} and will be |
| * deleted once the process is terminated |
| */ |
| public static final String ATTR_LAUNCH_TEMP_FILES = "tempFiles"; //$NON-NLS-1$ |
| |
| /** |
| * prefix for temp files |
| */ |
| public static final String LAUNCH_TEMP_FILE_PREFIX = ".temp-"; //$NON-NLS-1$ |
| |
| /** |
| * The {@link DebugTrace} object to print to OSGi tracing |
| * @since 3.8 |
| */ |
| private static DebugTrace fgDebugTrace; |
| |
| /** |
| * The id of the JDT launching plug-in (value <code>"org.eclipse.jdt.launching"</code>). |
| */ |
| public static final String ID_PLUGIN= "org.eclipse.jdt.launching"; //$NON-NLS-1$ |
| |
| /** |
| * Identifier for 'vmConnectors' extension point |
| */ |
| public static final String ID_EXTENSION_POINT_VM_CONNECTORS = "vmConnectors"; //$NON-NLS-1$ |
| |
| /** |
| * Identifier for 'runtimeClasspathEntries' extension point |
| */ |
| public static final String ID_EXTENSION_POINT_RUNTIME_CLASSPATH_ENTRIES = "runtimeClasspathEntries"; //$NON-NLS-1$ |
| |
| private static LaunchingPlugin fgLaunchingPlugin; |
| |
| private HashMap<String, IVMConnector> fVMConnectors = null; |
| |
| /** |
| * Runtime classpath extensions |
| */ |
| private HashMap<String, IConfigurationElement> fClasspathEntryExtensions = null; |
| |
| private String fOldVMPrefString = EMPTY_STRING; |
| |
| private boolean fIgnoreVMDefPropertyChangeEvents = false; |
| |
| private static final String EMPTY_STRING = ""; //$NON-NLS-1$ |
| |
| /** |
| * Mapping of top-level VM installation directories to library info for that |
| * VM. |
| */ |
| private static Map<String, LibraryInfo> fgLibraryInfoMap = null; |
| |
| /** |
| * Mapping of the last time the directory of a given SDK was modified. |
| * <br><br> |
| * Mapping: <code>Map<String,Long></code> |
| * @since 3.7 |
| */ |
| private static Map<String, Long> fgInstallTimeMap = null; |
| /** |
| * List of install locations that have been detected to have changed |
| * |
| * @since 3.7 |
| */ |
| private static HashSet<String> fgHasChanged = new HashSet<>(); |
| /** |
| * Mutex for checking the time stamp of an install location |
| * |
| * @since 3.7 |
| */ |
| private static Object installLock = new Object(); |
| |
| /** |
| * Whether changes in VM preferences are being batched. When being batched |
| * the plug-in can ignore processing and changes. |
| */ |
| private boolean fBatchingChanges = false; |
| |
| /** |
| * Shared XML parser |
| */ |
| private static DocumentBuilder fgXMLParser = null; |
| |
| /** |
| * Stores VM changes resulting from a JRE preference change. |
| */ |
| class VMChanges implements IVMInstallChangedListener { |
| |
| // true if the default VM changes |
| private boolean fDefaultChanged = false; |
| |
| // old container ids to new |
| private HashMap<IPath, IPath> fRenamedContainerIds = new HashMap<>(); |
| |
| /** |
| * Returns the JRE container id that the given VM would map to, or |
| * <code>null</code> if none. |
| * |
| * @param vm the new path id of the {@link IVMInstall} |
| * @return container id or <code>null</code> |
| */ |
| private IPath getContainerId(IVMInstall vm) { |
| if (vm != null) { |
| String name = vm.getName(); |
| if (name != null) { |
| IPath path = new Path(JavaRuntime.JRE_CONTAINER); |
| path = path.append(new Path(vm.getVMInstallType().getId())); |
| path = path.append(new Path(name)); |
| return path; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * @see org.eclipse.jdt.launching.IVMInstallChangedListener#defaultVMInstallChanged(org.eclipse.jdt.launching.IVMInstall, org.eclipse.jdt.launching.IVMInstall) |
| */ |
| @Override |
| public void defaultVMInstallChanged(IVMInstall previous, IVMInstall current) { |
| fDefaultChanged = true; |
| } |
| |
| /** |
| * @see org.eclipse.jdt.launching.IVMInstallChangedListener#vmAdded(org.eclipse.jdt.launching.IVMInstall) |
| */ |
| @Override |
| public void vmAdded(IVMInstall vm) { |
| } |
| |
| /** |
| * @see org.eclipse.jdt.launching.IVMInstallChangedListener#vmChanged(org.eclipse.jdt.launching.PropertyChangeEvent) |
| */ |
| @Override |
| public void vmChanged(org.eclipse.jdt.launching.PropertyChangeEvent event) { |
| String property = event.getProperty(); |
| IVMInstall vm = (IVMInstall)event.getSource(); |
| if (property.equals(IVMInstallChangedListener.PROPERTY_NAME)) { |
| IPath newId = getContainerId(vm); |
| IPath oldId = new Path(JavaRuntime.JRE_CONTAINER); |
| oldId = oldId.append(vm.getVMInstallType().getId()); |
| String oldName = (String)event.getOldValue(); |
| // bug 33746 - if there is no old name, then this is not a re-name. |
| if (oldName != null) { |
| oldId = oldId.append(oldName); |
| fRenamedContainerIds.put(oldId, newId); |
| //bug 39222 update launch configurations that ref old name |
| try { |
| ILaunchConfiguration[] configs = DebugPlugin.getDefault().getLaunchManager().getLaunchConfigurations(); |
| String container = null; |
| ILaunchConfigurationWorkingCopy wc = null; |
| IPath cpath = null; |
| for(int i = 0; i < configs.length; i++) { |
| container = configs[i].getAttribute(JavaRuntime.JRE_CONTAINER, (String)null); |
| if(container != null) { |
| cpath = new Path(container); |
| if(cpath.lastSegment().equals(oldName)) { |
| cpath = cpath.removeLastSegments(1).append(newId.lastSegment()).addTrailingSeparator(); |
| wc = configs[i].getWorkingCopy(); |
| wc.setAttribute(JavaRuntime.JRE_CONTAINER, cpath.toString()); |
| wc.doSave(); |
| } |
| } |
| } |
| } catch (CoreException e) {} |
| } |
| } |
| } |
| |
| /** |
| * @see org.eclipse.jdt.launching.IVMInstallChangedListener#vmRemoved(org.eclipse.jdt.launching.IVMInstall) |
| */ |
| @Override |
| public void vmRemoved(IVMInstall vm) { |
| } |
| |
| /** |
| * Re-bind classpath variables and containers affected by the JRE |
| * changes. |
| */ |
| public void process() { |
| JREUpdateJob job = new JREUpdateJob(this); |
| job.schedule(); |
| } |
| |
| protected void doit(IProgressMonitor monitor) throws CoreException { |
| IWorkspaceRunnable runnable = new IWorkspaceRunnable() { |
| @Override |
| public void run(IProgressMonitor monitor1) throws CoreException { |
| IJavaProject[] projects = JavaCore.create(ResourcesPlugin.getWorkspace().getRoot()).getJavaProjects(); |
| monitor1.beginTask(LaunchingMessages.LaunchingPlugin_0, projects.length + 1); |
| rebind(monitor1, projects); |
| monitor1.done(); |
| } |
| }; |
| JavaCore.run(runnable, null, monitor); |
| } |
| |
| /** |
| * Re-bind classpath variables and containers affected by the JRE |
| * changes. |
| * @param monitor a progress monitor or <code>null</code> |
| * @param projects the list of {@link IJavaProject}s to re-bind the VM to |
| * @throws CoreException if an exception is thrown |
| */ |
| private void rebind(IProgressMonitor monitor, IJavaProject[] projects) throws CoreException { |
| if (fDefaultChanged) { |
| // re-bind JRELIB if the default VM changed |
| JavaClasspathVariablesInitializer initializer = new JavaClasspathVariablesInitializer(); |
| initializer.initialize(JavaRuntime.JRELIB_VARIABLE); |
| initializer.initialize(JavaRuntime.JRESRC_VARIABLE); |
| initializer.initialize(JavaRuntime.JRESRCROOT_VARIABLE); |
| } |
| monitor.worked(1); |
| |
| // re-bind all container entries |
| int length = projects.length; |
| Map<IPath, List<IJavaProject>> projectsMap = new HashMap<>(); |
| for (int i = 0; i < length; i++) { |
| IJavaProject project = projects[i]; |
| IClasspathEntry[] entries = project.getRawClasspath(); |
| boolean replace = false; |
| for (int j = 0; j < entries.length; j++) { |
| IClasspathEntry entry = entries[j]; |
| switch (entry.getEntryKind()) { |
| case IClasspathEntry.CPE_CONTAINER: |
| IPath reference = entry.getPath(); |
| IPath newBinding = null; |
| String firstSegment = reference.segment(0); |
| if (JavaRuntime.JRE_CONTAINER.equals(firstSegment)) { |
| if (reference.segmentCount() > 1) { |
| IPath renamed = fRenamedContainerIds.get(reference); |
| if (renamed != null) { |
| // The JRE was re-named. This changes the identifier of |
| // the container entry. |
| newBinding = renamed; |
| } |
| } |
| if (newBinding == null){ |
| // re-bind old path |
| // @see bug 310789 - batch updates by common container paths |
| List<IJavaProject> projectsList = projectsMap.get(reference); |
| if (projectsList == null) { |
| projectsMap.put(reference, projectsList = new ArrayList<>(length)); |
| } |
| projectsList.add(project); |
| } else { |
| // replace old class path entry with a new one |
| IClasspathEntry newEntry = JavaCore.newContainerEntry(newBinding, entry.isExported()); |
| entries[j] = newEntry; |
| replace = true; |
| } |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| if (replace) { |
| project.setRawClasspath(entries, null); |
| } |
| monitor.worked(1); |
| } |
| Iterator<IPath> references = projectsMap.keySet().iterator(); |
| while (references.hasNext()) { |
| IPath reference = references.next(); |
| List<IJavaProject> projectsList = projectsMap.get(reference); |
| IJavaProject[] referenceProjects = new IJavaProject[projectsList.size()]; |
| projectsList.toArray(referenceProjects); |
| // re-bind old path |
| JREContainerInitializer initializer = new JREContainerInitializer(); |
| initializer.initialize(reference, referenceProjects); |
| } |
| } |
| |
| } |
| |
| class JREUpdateJob extends Job { |
| private VMChanges fChanges; |
| |
| public JREUpdateJob(VMChanges changes) { |
| super(LaunchingMessages.LaunchingPlugin_1); |
| fChanges = changes; |
| setSystem(true); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor) |
| */ |
| @Override |
| protected IStatus run(IProgressMonitor monitor) { |
| try { |
| fChanges.doit(monitor); |
| } catch (CoreException e) { |
| return e.getStatus(); |
| } |
| return Status.OK_STATUS; |
| } |
| |
| } |
| |
| /** |
| * Constructor |
| */ |
| public LaunchingPlugin() { |
| super(); |
| fgLaunchingPlugin = this; |
| } |
| |
| /** |
| * Returns the library info that corresponds to the specified JRE install |
| * path, or <code>null</code> if none. |
| * |
| * @param javaInstallPath the absolute path to the java executable |
| * @return the library info that corresponds to the specified JRE install |
| * path, or <code>null</code> if none |
| */ |
| public static LibraryInfo getLibraryInfo(String javaInstallPath) { |
| if (fgLibraryInfoMap == null) { |
| restoreLibraryInfo(); |
| } |
| return fgLibraryInfoMap.get(javaInstallPath); |
| } |
| |
| /** |
| * Sets the library info that corresponds to the specified JRE install |
| * path. |
| * |
| * @param javaInstallPath home location for a JRE |
| * @param info the library information, or <code>null</code> to remove |
| */ |
| public static void setLibraryInfo(String javaInstallPath, LibraryInfo info) { |
| if (isVMLogging()) { |
| LaunchingPlugin.log(LaunchingMessages.VMLogging_2 + javaInstallPath); |
| } |
| if (fgLibraryInfoMap == null) { |
| restoreLibraryInfo(); |
| } |
| if (info == null) { |
| fgLibraryInfoMap.remove(javaInstallPath); |
| if(fgInstallTimeMap != null) { |
| fgInstallTimeMap.remove(javaInstallPath); |
| writeInstallInfo(); |
| } |
| |
| } else { |
| fgLibraryInfoMap.put(javaInstallPath, info); |
| } |
| //once the library info has been set we can forget it has changed |
| fgHasChanged.remove(javaInstallPath); |
| saveLibraryInfo(); |
| } |
| |
| public static boolean isVMLogging() { |
| String vmLogging = System.getProperty("jdt.debug.launching.vmLogging"); //$NON-NLS-1$ |
| return "true".equalsIgnoreCase(vmLogging); //$NON-NLS-1$ |
| } |
| |
| /** |
| * Return a <code>java.io.File</code> object that corresponds to the specified |
| * <code>IPath</code> in the plug-in directory. |
| * |
| * @param path the path to look for in the launching bundle |
| * @return the {@link File} from the bundle or <code>null</code> |
| */ |
| public static File getFileInPlugin(IPath path) { |
| try { |
| URL installURL = |
| new URL(getDefault().getBundle().getEntry("/"), path.toString()); //$NON-NLS-1$ |
| URL localURL = FileLocator.toFileURL(installURL); |
| return new File(localURL.getFile()); |
| } catch (IOException ioe) { |
| return null; |
| } |
| } |
| |
| /** |
| * Convenience method which returns the unique identifier of this plug-in. |
| * |
| * @return the id of the {@link LaunchingPlugin} |
| */ |
| public static String getUniqueIdentifier() { |
| return ID_PLUGIN; |
| } |
| |
| /** |
| * Returns the singleton instance of <code>LaunchingPlugin</code> |
| * @return the singleton instance of <code>LaunchingPlugin</code> |
| */ |
| public static LaunchingPlugin getDefault() { |
| return fgLaunchingPlugin; |
| } |
| |
| /** |
| * Logs the specified status |
| * @param status the status to log |
| */ |
| public static void log(IStatus status) { |
| getDefault().getLog().log(status); |
| } |
| |
| /** |
| * Logs the specified message, by creating a new <code>Status</code> |
| * @param message the message to log as an error status |
| */ |
| public static void log(String message) { |
| log(new Status(IStatus.ERROR, getUniqueIdentifier(), IStatus.ERROR, message, null)); |
| } |
| |
| /** |
| * Logs the specified exception by creating a new <code>Status</code> |
| * @param e the {@link Throwable} to log as an error |
| */ |
| public static void log(Throwable e) { |
| log(new Status(IStatus.ERROR, getUniqueIdentifier(), IStatus.ERROR, e.getMessage(), e)); |
| } |
| |
| /** |
| * Clears zip file cache. |
| * Shutdown the launch configuration helper. |
| * |
| * @see Plugin#stop(BundleContext) |
| */ |
| @Override |
| public void stop(BundleContext context) throws Exception { |
| try { |
| AdvancedSourceLookupSupport.stop(); |
| |
| DebugPlugin.getDefault().getLaunchManager().removeLaunchListener(this); |
| DebugPlugin.getDefault().removeDebugEventListener(this); |
| ResourcesPlugin.getWorkspace().removeResourceChangeListener(this); |
| ArchiveSourceLocation.closeArchives(); |
| InstanceScope.INSTANCE.getNode(ID_PLUGIN).removePreferenceChangeListener(this); |
| JavaRuntime.removeVMInstallChangedListener(this); |
| JavaRuntime.saveVMConfiguration(); |
| fgXMLParser = null; |
| ResourcesPlugin.getWorkspace().removeSaveParticipant(ID_PLUGIN); |
| } finally { |
| super.stop(context); |
| } |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.core.runtime.Plugin#start(org.osgi.framework.BundleContext) |
| */ |
| @Override |
| public void start(BundleContext context) throws Exception { |
| super.start(context); |
| Hashtable<String, String> props = new Hashtable<>(2); |
| props.put(org.eclipse.osgi.service.debug.DebugOptions.LISTENER_SYMBOLICNAME, getUniqueIdentifier()); |
| context.registerService(DebugOptionsListener.class.getName(), this, props); |
| ResourcesPlugin.getWorkspace().addSaveParticipant(ID_PLUGIN, new ISaveParticipant() { |
| @Override |
| public void doneSaving(ISaveContext context1) {} |
| @Override |
| public void prepareToSave(ISaveContext context1) throws CoreException {} |
| @Override |
| public void rollback(ISaveContext context1) {} |
| @Override |
| public void saving(ISaveContext context1) throws CoreException { |
| try { |
| InstanceScope.INSTANCE.getNode(ID_PLUGIN).flush(); |
| } catch (BackingStoreException e) { |
| log(e); |
| } |
| //catch in case any install times are still cached for removed JREs |
| writeInstallInfo(); |
| } |
| }); |
| |
| InstanceScope.INSTANCE.getNode(ID_PLUGIN).addPreferenceChangeListener(this); |
| JavaRuntime.addVMInstallChangedListener(this); |
| ResourcesPlugin.getWorkspace().addResourceChangeListener(this, IResourceChangeEvent.PRE_DELETE | IResourceChangeEvent.PRE_CLOSE); |
| DebugPlugin.getDefault().getLaunchManager().addLaunchListener(this); |
| DebugPlugin.getDefault().addDebugEventListener(this); |
| |
| AdvancedSourceLookupSupport.start(); |
| } |
| |
| /** |
| * Returns the VM connector with the specified id, or <code>null</code> |
| * if none. |
| * |
| * @param id connector identifier |
| * @return VM connector |
| */ |
| public IVMConnector getVMConnector(String id) { |
| if (fVMConnectors == null) { |
| initializeVMConnectors(); |
| } |
| return fVMConnectors.get(id); |
| } |
| |
| /** |
| * Returns all VM connector extensions. |
| * |
| * @return VM connectors |
| */ |
| public IVMConnector[] getVMConnectors() { |
| if (fVMConnectors == null) { |
| initializeVMConnectors(); |
| } |
| return fVMConnectors.values().toArray(new IVMConnector[fVMConnectors.size()]); |
| } |
| |
| /** |
| * Loads VM connector extensions |
| */ |
| private void initializeVMConnectors() { |
| IExtensionPoint extensionPoint= Platform.getExtensionRegistry().getExtensionPoint(ID_PLUGIN, ID_EXTENSION_POINT_VM_CONNECTORS); |
| IConfigurationElement[] configs= extensionPoint.getConfigurationElements(); |
| MultiStatus status= new MultiStatus(getUniqueIdentifier(), IStatus.OK, "Exception occurred reading vmConnectors extensions.", null); //$NON-NLS-1$ |
| fVMConnectors = new HashMap<>(configs.length); |
| for (int i= 0; i < configs.length; i++) { |
| try { |
| IVMConnector vmConnector= (IVMConnector)configs[i].createExecutableExtension("class"); //$NON-NLS-1$ |
| fVMConnectors.put(vmConnector.getIdentifier(), vmConnector); |
| } catch (CoreException e) { |
| status.add(e.getStatus()); |
| } |
| } |
| if (!status.isOK()) { |
| LaunchingPlugin.log(status); |
| } |
| } |
| |
| /** |
| * Returns a new runtime classpath entry of the specified type. |
| * |
| * @param id extension type id |
| * @return new uninitialized runtime classpath entry |
| * @throws CoreException if unable to create an entry |
| */ |
| public IRuntimeClasspathEntry2 newRuntimeClasspathEntry(String id) throws CoreException { |
| if (fClasspathEntryExtensions == null) { |
| initializeRuntimeClasspathExtensions(); |
| } |
| IConfigurationElement config = fClasspathEntryExtensions.get(id); |
| if (config != null) { |
| return (IRuntimeClasspathEntry2) config.createExecutableExtension("class"); //$NON-NLS-1$ |
| } |
| abort(NLS.bind(LaunchingMessages.LaunchingPlugin_32, new String[]{id}), null); |
| return null; |
| } |
| |
| /** |
| * Loads runtime classpath extensions |
| */ |
| private void initializeRuntimeClasspathExtensions() { |
| IExtensionPoint extensionPoint= Platform.getExtensionRegistry().getExtensionPoint(LaunchingPlugin.ID_PLUGIN, ID_EXTENSION_POINT_RUNTIME_CLASSPATH_ENTRIES); |
| IConfigurationElement[] configs= extensionPoint.getConfigurationElements(); |
| fClasspathEntryExtensions = new HashMap<>(configs.length); |
| for (int i= 0; i < configs.length; i++) { |
| fClasspathEntryExtensions.put(configs[i].getAttribute("id"), configs[i]); //$NON-NLS-1$ |
| } |
| } |
| |
| /** |
| * Check for differences between the old & new sets of installed JREs. |
| * Differences may include additions, deletions and changes. Take |
| * appropriate action for each type of difference. |
| * |
| * When importing preferences, TWO propertyChange events are fired. The first |
| * has an old value but an empty new value. The second has a new value, but an empty |
| * old value. Normal user changes to the preferences result in a single propertyChange |
| * event, with both old and new values populated. This method handles both types |
| * of notification. |
| * |
| * @param oldValue the old preference value |
| * @param newValue the new preference value |
| */ |
| protected void processVMPrefsChanged(String oldValue, String newValue) { |
| |
| // batch changes |
| fBatchingChanges = true; |
| VMChanges vmChanges = null; |
| try { |
| |
| String oldPrefString; |
| String newPrefString; |
| |
| // If empty new value, save the old value and wait for 2nd propertyChange notification |
| if (newValue == null || newValue.equals(EMPTY_STRING)) { |
| fOldVMPrefString = oldValue; |
| return; |
| } |
| // An empty old value signals the second notification in the import preferences |
| // sequence. Now that we have both old & new preferences, we can parse and compare them. |
| else if (oldValue == null || oldValue.equals(EMPTY_STRING)) { |
| oldPrefString = fOldVMPrefString; |
| newPrefString = newValue; |
| } |
| // If both old & new values are present, this is a normal user change |
| else { |
| oldPrefString = oldValue; |
| newPrefString = newValue; |
| } |
| |
| vmChanges = new VMChanges(); |
| JavaRuntime.addVMInstallChangedListener(vmChanges); |
| |
| // Generate the previous VMs |
| VMDefinitionsContainer oldResults = getVMDefinitions(oldPrefString); |
| |
| // Generate the current |
| VMDefinitionsContainer newResults = getVMDefinitions(newPrefString); |
| |
| // Determine the deleted VMs |
| List<IVMInstall> deleted = oldResults.getVMList(); |
| List<IVMInstall> current = newResults.getValidVMList(); |
| deleted.removeAll(current); |
| |
| // Dispose deleted VMs. The 'disposeVMInstall' method fires notification of the |
| // deletion. |
| Iterator<IVMInstall> deletedIterator = deleted.iterator(); |
| while (deletedIterator.hasNext()) { |
| VMStandin deletedVMStandin = (VMStandin) deletedIterator.next(); |
| deletedVMStandin.getVMInstallType().disposeVMInstall(deletedVMStandin.getId()); |
| } |
| |
| // Fire change notification for added and changed VMs. The 'convertToRealVM' |
| // fires the appropriate notification. |
| Iterator<IVMInstall> iter = current.iterator(); |
| while (iter.hasNext()) { |
| VMStandin standin = (VMStandin)iter.next(); |
| standin.convertToRealVM(); |
| } |
| |
| // set the new default VM install. This will fire a 'defaultVMChanged', |
| // if it in fact changed |
| String newDefaultId = newResults.getDefaultVMInstallCompositeID(); |
| if (newDefaultId != null) { |
| IVMInstall newDefaultVM = JavaRuntime.getVMFromCompositeId(newDefaultId); |
| if (newDefaultVM != null) { |
| try { |
| JavaRuntime.setDefaultVMInstall(newDefaultVM, null, false); |
| } catch (CoreException ce) { |
| log(ce); |
| } |
| } |
| } |
| |
| } finally { |
| // stop batch changes |
| fBatchingChanges = false; |
| if (vmChanges != null) { |
| JavaRuntime.removeVMInstallChangedListener(vmChanges); |
| vmChanges.process(); |
| } |
| } |
| |
| } |
| |
| /** |
| * Parse the given XML into a VM definitions container, returning an empty |
| * container if an exception occurs. |
| * |
| * @param xml the XML to parse for VM descriptions |
| * @return VMDefinitionsContainer |
| */ |
| private VMDefinitionsContainer getVMDefinitions(String xml) { |
| if (xml.length() > 0) { |
| try { |
| ByteArrayInputStream stream = new ByteArrayInputStream(xml.getBytes("UTF8")); //$NON-NLS-1$ |
| return VMDefinitionsContainer.parseXMLIntoContainer(stream); |
| } catch (IOException e) { |
| LaunchingPlugin.log(e); |
| } |
| } |
| return new VMDefinitionsContainer(); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.jdt.launching.IVMInstallChangedListener#defaultVMInstallChanged(org.eclipse.jdt.launching.IVMInstall, org.eclipse.jdt.launching.IVMInstall) |
| */ |
| @Override |
| public void defaultVMInstallChanged(IVMInstall previous, IVMInstall current) { |
| if (!fBatchingChanges) { |
| VMChanges changes = new VMChanges(); |
| changes.defaultVMInstallChanged(previous, current); |
| changes.process(); |
| } |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.jdt.launching.IVMInstallChangedListener#vmAdded(org.eclipse.jdt.launching.IVMInstall) |
| */ |
| @Override |
| public void vmAdded(IVMInstall vm) { |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.jdt.launching.IVMInstallChangedListener#vmChanged(org.eclipse.jdt.launching.PropertyChangeEvent) |
| */ |
| @Override |
| public void vmChanged(org.eclipse.jdt.launching.PropertyChangeEvent event) { |
| if (!fBatchingChanges) { |
| VMChanges changes = new VMChanges(); |
| changes.vmChanged(event); |
| changes.process(); |
| } |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.jdt.launching.IVMInstallChangedListener#vmRemoved(org.eclipse.jdt.launching.IVMInstall) |
| */ |
| @Override |
| public void vmRemoved(IVMInstall vm) { |
| if (!fBatchingChanges) { |
| VMChanges changes = new VMChanges(); |
| changes.vmRemoved(vm); |
| changes.process(); |
| } |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.core.resources.IResourceChangeListener#resourceChanged(org.eclipse.core.resources.IResourceChangeEvent) |
| */ |
| @Override |
| public void resourceChanged(IResourceChangeEvent event) { |
| ArchiveSourceLocation.closeArchives(); |
| } |
| |
| /** |
| * Allows VM property change events to be ignored |
| * @param ignore if we should ignore VM property changed events or not |
| */ |
| public void setIgnoreVMDefPropertyChangeEvents(boolean ignore) { |
| fIgnoreVMDefPropertyChangeEvents = ignore; |
| } |
| |
| /** |
| * Returns if VM property changed event should be ignored or not |
| * @return if VM property changed event should be ignored or not |
| */ |
| public boolean isIgnoreVMDefPropertyChangeEvents() { |
| return fIgnoreVMDefPropertyChangeEvents; |
| } |
| |
| /** |
| * Return the VM definitions contained in this object as a String of XML. The String |
| * is suitable for storing in the workbench preferences. |
| * <p> |
| * The resulting XML is compatible with the static method <code>parseXMLIntoContainer</code>. |
| * </p> |
| * @return String the results of flattening this object into XML |
| * @throws CoreException if this method fails. Reasons include:<ul> |
| * <li>serialization of the XML document failed</li> |
| * </ul> |
| */ |
| private static String getLibraryInfoAsXML() throws CoreException { |
| |
| Document doc = DebugPlugin.newDocument(); |
| Element config = doc.createElement("libraryInfos"); //$NON-NLS-1$ |
| doc.appendChild(config); |
| |
| // Create a node for each info in the table |
| Iterator<String> locations = fgLibraryInfoMap.keySet().iterator(); |
| while (locations.hasNext()) { |
| String home = locations.next(); |
| LibraryInfo info = fgLibraryInfoMap.get(home); |
| Element locationElemnet = infoAsElement(doc, info); |
| locationElemnet.setAttribute("home", home); //$NON-NLS-1$ |
| config.appendChild(locationElemnet); |
| } |
| |
| // Serialize the Document and return the resulting String |
| return DebugPlugin.serializeDocument(doc); |
| } |
| |
| /** |
| * Creates an XML element for the given info. |
| * |
| * @param doc the backing {@link Document} |
| * @param info the {@link LibraryInfo} to add to the {@link Document} |
| * @return Element |
| */ |
| private static Element infoAsElement(Document doc, LibraryInfo info) { |
| Element libraryElement = doc.createElement("libraryInfo"); //$NON-NLS-1$ |
| libraryElement.setAttribute("version", info.getVersion()); //$NON-NLS-1$ |
| appendPathElements(doc, "bootpath", libraryElement, info.getBootpath()); //$NON-NLS-1$ |
| appendPathElements(doc, "extensionDirs", libraryElement, info.getExtensionDirs()); //$NON-NLS-1$ |
| appendPathElements(doc, "endorsedDirs", libraryElement, info.getEndorsedDirs()); //$NON-NLS-1$ |
| return libraryElement; |
| } |
| |
| /** |
| * Appends path elements to the given library element, rooted by an |
| * element of the given type. |
| * |
| * @param doc the backing {@link Document} |
| * @param elementType the kind of {@link Element} to create |
| * @param libraryElement the {@link Element} describing a given {@link LibraryInfo} object |
| * @param paths the paths to add |
| */ |
| private static void appendPathElements(Document doc, String elementType, Element libraryElement, String[] paths) { |
| if (paths.length > 0) { |
| Element child = doc.createElement(elementType); |
| libraryElement.appendChild(child); |
| for (int i = 0; i < paths.length; i++) { |
| String path = paths[i]; |
| Element entry = doc.createElement("entry"); //$NON-NLS-1$ |
| child.appendChild(entry); |
| entry.setAttribute("path", path); //$NON-NLS-1$ |
| } |
| } |
| } |
| |
| /** |
| * Saves the library info in a local workspace state location |
| */ |
| private static void saveLibraryInfo() { |
| try { |
| String xml = getLibraryInfoAsXML(); |
| IPath libPath = getDefault().getStateLocation(); |
| libPath = libPath.append("libraryInfos.xml"); //$NON-NLS-1$ |
| File file = libPath.toFile(); |
| if (!file.exists()) { |
| file.createNewFile(); |
| } |
| try (OutputStream stream = new BufferedOutputStream(new FileOutputStream(file))) { |
| stream.write(xml.getBytes("UTF8")); //$NON-NLS-1$ |
| } |
| } catch (IOException e) { |
| log(e); |
| } catch (CoreException e) { |
| log(e); |
| } |
| } |
| |
| /** |
| * Restores library information for VMs |
| */ |
| private static void restoreLibraryInfo() { |
| fgLibraryInfoMap = new HashMap<>(10); |
| IPath libPath = getDefault().getStateLocation(); |
| libPath = libPath.append("libraryInfos.xml"); //$NON-NLS-1$ |
| File file = libPath.toFile(); |
| if (file.exists()) { |
| try { |
| InputStream stream = new BufferedInputStream(new FileInputStream(file)); |
| DocumentBuilder parser = DocumentBuilderFactory.newInstance().newDocumentBuilder(); |
| parser.setErrorHandler(new DefaultHandler()); |
| Element root = parser.parse(new InputSource(stream)).getDocumentElement(); |
| if(!root.getNodeName().equals("libraryInfos")) { //$NON-NLS-1$ |
| return; |
| } |
| |
| NodeList list = root.getChildNodes(); |
| int length = list.getLength(); |
| for (int i = 0; i < length; ++i) { |
| Node node = list.item(i); |
| short type = node.getNodeType(); |
| if (type == Node.ELEMENT_NODE) { |
| Element element = (Element) node; |
| String nodeName = element.getNodeName(); |
| if (nodeName.equalsIgnoreCase("libraryInfo")) { //$NON-NLS-1$ |
| String version = element.getAttribute("version"); //$NON-NLS-1$ |
| String location = element.getAttribute("home"); //$NON-NLS-1$ |
| String[] bootpath = getPathsFromXML(element, "bootpath"); //$NON-NLS-1$ |
| String[] extDirs = getPathsFromXML(element, "extensionDirs"); //$NON-NLS-1$ |
| String[] endDirs = getPathsFromXML(element, "endorsedDirs"); //$NON-NLS-1$ |
| if (location != null) { |
| if (isVMLogging()) { |
| LaunchingPlugin.log(LaunchingMessages.VMLogging_1 + location); |
| } |
| LibraryInfo info = new LibraryInfo(version, bootpath, extDirs, endDirs); |
| fgLibraryInfoMap.put(location, info); |
| } |
| } |
| } |
| } |
| } catch (IOException e) { |
| log(e); |
| } catch (ParserConfigurationException e) { |
| log(e); |
| } catch (SAXException e) { |
| log(e); |
| } |
| } |
| } |
| |
| /** |
| * Checks to see if the time stamp of the file describe by the given location string |
| * has been modified since the last recorded time stamp. If there is no last recorded |
| * time stamp we assume it has changed. See https://bugs.eclipse.org/bugs/show_bug.cgi?id=266651 for more information |
| * |
| * @param location the location of the SDK we want to check the time stamp for |
| * @return <code>true</code> if the time stamp has changed compared to the cached one or if there is |
| * no recorded time stamp, <code>false</code> otherwise. |
| * |
| * @since 3.7 |
| */ |
| public static boolean timeStampChanged(String location) { |
| synchronized (installLock) { |
| if(fgHasChanged.contains(location)) { |
| return true; |
| } |
| File file = new File(location); |
| if(file.exists()) { |
| if(fgInstallTimeMap == null) { |
| readInstallInfo(); |
| } |
| Long stamp = fgInstallTimeMap.get(location); |
| long fstamp = file.lastModified(); |
| if(stamp != null) { |
| if(stamp.longValue() == fstamp) { |
| return false; |
| } |
| } |
| //if there is no recorded stamp we have to assume it is new |
| stamp = new Long(fstamp); |
| fgInstallTimeMap.put(location, stamp); |
| writeInstallInfo(); |
| fgHasChanged.add(location); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Reads the file of saved time stamps and populates the {@link #fgInstallTimeMap}. |
| * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=266651 for more information |
| * |
| * @since 3.7 |
| */ |
| private static void readInstallInfo() { |
| fgInstallTimeMap = new HashMap<>(); |
| IPath libPath = getDefault().getStateLocation(); |
| libPath = libPath.append(".install.xml"); //$NON-NLS-1$ |
| File file = libPath.toFile(); |
| if (file.exists()) { |
| try { |
| InputStream stream = new BufferedInputStream(new FileInputStream(file)); |
| DocumentBuilder parser = DocumentBuilderFactory.newInstance().newDocumentBuilder(); |
| parser.setErrorHandler(new DefaultHandler()); |
| Element root = parser.parse(new InputSource(stream)).getDocumentElement(); |
| if(root.getNodeName().equalsIgnoreCase("dirs")) { //$NON-NLS-1$ |
| NodeList nodes = root.getChildNodes(); |
| Node node = null; |
| Element element = null; |
| for (int i = 0; i < nodes.getLength(); i++) { |
| node = nodes.item(i); |
| if(node.getNodeType() == Node.ELEMENT_NODE) { |
| element = (Element) node; |
| if(element.getNodeName().equalsIgnoreCase("entry")) { //$NON-NLS-1$ |
| String loc = element.getAttribute("loc"); //$NON-NLS-1$ |
| String stamp = element.getAttribute("stamp"); //$NON-NLS-1$ |
| try { |
| Long l = new Long(stamp); |
| fgInstallTimeMap.put(loc, l); |
| } |
| catch(NumberFormatException nfe) { |
| //do nothing |
| } |
| } |
| } |
| } |
| } |
| } catch (IOException e) { |
| log(e); |
| } catch (ParserConfigurationException e) { |
| log(e); |
| } catch (SAXException e) { |
| log(e); |
| } |
| } |
| } |
| |
| /** |
| * Writes out the mappings of SDK install time stamps to disk. See |
| * https://bugs.eclipse.org/bugs/show_bug.cgi?id=266651 for more information. |
| * |
| * @since 3.7 |
| */ |
| private static void writeInstallInfo() { |
| if(fgInstallTimeMap != null) { |
| try { |
| Document doc = DebugPlugin.newDocument(); |
| Element root = doc.createElement("dirs"); //$NON-NLS-1$ |
| doc.appendChild(root); |
| Entry<String, Long> entry = null; |
| Element e = null; |
| String key = null; |
| for(Iterator<Entry<String, Long>> i = fgInstallTimeMap.entrySet().iterator(); i.hasNext();) { |
| entry = i.next(); |
| key = entry.getKey(); |
| if(fgLibraryInfoMap == null || fgLibraryInfoMap.containsKey(key)) { |
| //only persist the info if the library map also has info OR is null - prevent persisting deleted JRE information |
| e = doc.createElement("entry"); //$NON-NLS-1$ |
| root.appendChild(e); |
| e.setAttribute("loc", key); //$NON-NLS-1$ |
| e.setAttribute("stamp", entry.getValue().toString()); //$NON-NLS-1$ |
| } |
| } |
| String xml = DebugPlugin.serializeDocument(doc); |
| IPath libPath = getDefault().getStateLocation(); |
| libPath = libPath.append(".install.xml"); //$NON-NLS-1$ |
| File file = libPath.toFile(); |
| if (!file.exists()) { |
| file.createNewFile(); |
| } |
| try (OutputStream stream = new BufferedOutputStream(new FileOutputStream(file))) { |
| stream.write(xml.getBytes("UTF8")); //$NON-NLS-1$ |
| } |
| } catch (IOException e) { |
| log(e); |
| } catch (CoreException e) { |
| log(e); |
| } |
| } |
| } |
| |
| /** |
| * Returns paths stored in XML |
| * @param lib the library path in {@link Element} form |
| * @param pathType the type of the path |
| * @return paths stored in XML |
| */ |
| private static String[] getPathsFromXML(Element lib, String pathType) { |
| List<String> paths = new ArrayList<>(); |
| NodeList list = lib.getChildNodes(); |
| int length = list.getLength(); |
| for (int i = 0; i < length; ++i) { |
| Node node = list.item(i); |
| short type = node.getNodeType(); |
| if (type == Node.ELEMENT_NODE) { |
| Element element = (Element) node; |
| String nodeName = element.getNodeName(); |
| if (nodeName.equalsIgnoreCase(pathType)) { |
| NodeList entries = element.getChildNodes(); |
| int numEntries = entries.getLength(); |
| for (int j = 0; j < numEntries; j++) { |
| Node n = entries.item(j); |
| short t = n.getNodeType(); |
| if (t == Node.ELEMENT_NODE) { |
| Element entryElement = (Element)n; |
| String name = entryElement.getNodeName(); |
| if (name.equals("entry")) { //$NON-NLS-1$ |
| String path = entryElement.getAttribute("path"); //$NON-NLS-1$ |
| if (path != null && path.length() > 0) { |
| paths.add(path); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| return paths.toArray(new String[paths.size()]); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.debug.core.ILaunchesListener#launchesRemoved(org.eclipse.debug.core.ILaunch[]) |
| */ |
| @Override |
| public void launchesRemoved(ILaunch[] launches) { |
| ArchiveSourceLocation.closeArchives(); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.debug.core.ILaunchesListener#launchesAdded(org.eclipse.debug.core.ILaunch[]) |
| */ |
| @Override |
| public void launchesAdded(ILaunch[] launches) { |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.debug.core.ILaunchesListener#launchesChanged(org.eclipse.debug.core.ILaunch[]) |
| */ |
| @Override |
| public void launchesChanged(ILaunch[] launches) { |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.debug.core.IDebugEventSetListener#handleDebugEvents(org.eclipse.debug.core.DebugEvent[]) |
| */ |
| @Override |
| public void handleDebugEvents(DebugEvent[] events) { |
| for (int i = 0; i < events.length; i++) { |
| DebugEvent event = events[i]; |
| if (event.getKind() == DebugEvent.TERMINATE) { |
| Object source = event.getSource(); |
| if (source instanceof IDebugTarget || source instanceof IProcess) { |
| ArchiveSourceLocation.closeArchives(); |
| IProcess process; |
| if (source instanceof IProcess) { |
| process = (IProcess) source; |
| } else { |
| process = ((IDebugTarget) source).getProcess(); |
| } |
| if (process != null) { |
| deleteProcessTempFiles(process); |
| } |
| } |
| } |
| } |
| } |
| |
| private void deleteProcessTempFiles(IProcess process) { |
| String tempFiles = process.getAttribute(ATTR_LAUNCH_TEMP_FILES); |
| if (tempFiles == null) { |
| return; |
| } |
| // we only delete files starting with LAUNCH_TEMP_FILE_PREFIX² |
| Arrays.stream(tempFiles.split(File.pathSeparator)).map(path -> new File(path)).filter(file -> isValidProcessTempFile(file)).forEach(file -> file.delete()); |
| } |
| |
| private boolean isValidProcessTempFile(File file) { |
| return file.getName().startsWith(LAUNCH_TEMP_FILE_PREFIX); |
| } |
| |
| /** |
| * Returns a shared XML parser. |
| * |
| * @return an XML parser |
| * @throws CoreException if unable to create a parser |
| * @since 3.0 |
| */ |
| public static DocumentBuilder getParser() throws CoreException { |
| if (fgXMLParser == null) { |
| try { |
| fgXMLParser = DocumentBuilderFactory.newInstance().newDocumentBuilder(); |
| fgXMLParser.setErrorHandler(new DefaultHandler()); |
| } catch (ParserConfigurationException e) { |
| abort(LaunchingMessages.LaunchingPlugin_34, e); |
| } catch (FactoryConfigurationError e) { |
| abort(LaunchingMessages.LaunchingPlugin_34, e); |
| } |
| } |
| return fgXMLParser; |
| } |
| |
| /** |
| * Throws an exception with the given message and underlying exception. |
| * |
| * @param message error message |
| * @param exception underlying exception or <code>null</code> if none |
| * @throws CoreException if an exception occurs |
| */ |
| protected static void abort(String message, Throwable exception) throws CoreException { |
| IStatus status = new Status(IStatus.ERROR, LaunchingPlugin.getUniqueIdentifier(), 0, message, exception); |
| throw new CoreException(status); |
| } |
| |
| /** |
| * Compares two URL for equality, but do not connect to do DNS resolution |
| * |
| * @param url1 a given URL |
| * @param url2 another given URL to compare to url1 |
| * |
| * @since 3.5 |
| * @return <code>true</code> if the URLs are equal, <code>false</code> otherwise |
| */ |
| public static boolean sameURL(URL url1, URL url2) { |
| if (url1 == url2) { |
| return true; |
| } |
| if (url1 == null ^ url2 == null) { |
| return false; |
| } |
| // check if URL are file: URL as we may have two URL pointing to the same doc location |
| // but with different representation - (i.e. file:/C;/ and file:C:/) |
| final boolean isFile1 = "file".equalsIgnoreCase(url1.getProtocol());//$NON-NLS-1$ |
| final boolean isFile2 = "file".equalsIgnoreCase(url2.getProtocol());//$NON-NLS-1$ |
| if (isFile1 && isFile2) { |
| File file1 = new File(url1.getFile()); |
| File file2 = new File(url2.getFile()); |
| return file1.equals(file2); |
| } |
| // URL1 XOR URL2 is a file, return false. (They either both need to be files, or neither) |
| if (isFile1 ^ isFile2) { |
| return false; |
| } |
| return getExternalForm(url1).equals(getExternalForm(url2)); |
| } |
| |
| /** |
| * Gets the external form of this URL. In particular, it trims any white space, |
| * removes a trailing slash and creates a lower case string. |
| * @param url the URL to get the {@link String} value of |
| * @return the lower-case {@link String} form of the given URL |
| */ |
| private static String getExternalForm(URL url) { |
| String externalForm = url.toExternalForm(); |
| if (externalForm == null) |
| { |
| return ""; //$NON-NLS-1$ |
| } |
| externalForm = externalForm.trim(); |
| if (externalForm.endsWith("/")) { //$NON-NLS-1$ |
| // Remove the trailing slash |
| externalForm = externalForm.substring(0, externalForm.length() - 1); |
| } |
| return externalForm.toLowerCase(); |
| |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener#preferenceChange(org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent) |
| */ |
| @Override |
| public void preferenceChange(PreferenceChangeEvent event) { |
| String property = event.getKey(); |
| if (property.equals(JavaRuntime.PREF_VM_XML)) { |
| if (!isIgnoreVMDefPropertyChangeEvents()) { |
| processVMPrefsChanged((String)event.getOldValue(), (String)event.getNewValue()); |
| } |
| } |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.osgi.service.debug.DebugOptionsListener#optionsChanged(org.eclipse.osgi.service.debug.DebugOptions) |
| */ |
| @Override |
| public void optionsChanged(DebugOptions options) { |
| DEBUG = options.getBooleanOption(DEBUG_FLAG, false); |
| DEBUG_JRE_CONTAINER = DEBUG && options.getBooleanOption(DEBUG_JRE_CONTAINER_FLAG, false); |
| } |
| |
| /** |
| * Prints the given message to System.out and to the OSGi tracing (if started) |
| * @param option the option or <code>null</code> |
| * @param message the message to print or <code>null</code> |
| * @param throwable the {@link Throwable} or <code>null</code> |
| * @since 3.8 |
| */ |
| public static void trace(String option, String message, Throwable throwable) { |
| System.out.println(message); |
| if(fgDebugTrace != null) { |
| fgDebugTrace.trace(option, message, throwable); |
| } |
| } |
| |
| /** |
| * Prints the given message to System.out and to the OSGi tracing (if enabled) |
| * |
| * @param message the message or <code>null</code> |
| * @since 3.8 |
| */ |
| public static void trace(String message) { |
| trace(null, message, null); |
| } |
| } |