/******************************************************************************* * 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.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.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.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$ /** * 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 (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(); } /** * 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 { 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); } /** * 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) { 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(); } } } } /** * 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); } }