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
/******************************************************************************* * 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.
/** * 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 LAUNCH_TEMP_FILE_PREFIX and will be deleted once the process is terminated
/** * 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
/** * prefix for temp files */
public static final String LAUNCH_TEMP_FILE_PREFIX = ".temp-"; //$NON-NLS-1$
The DebugTrace object to print to OSGi tracing
Since:3.8
/** * 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 "org.eclipse.jdt.launching").
/** * 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
/** * Identifier for 'vmConnectors' extension point */
public static final String ID_EXTENSION_POINT_VM_CONNECTORS = "vmConnectors"; //$NON-NLS-1$
Identifier for 'runtimeClasspathEntries' extension point
/** * 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
/** * 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.
/** * 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.

Mapping: Map<String,Long>
Since:3.7
/** * Mapping of the last time the directory of a given SDK was modified. * <br><br> * Mapping: <code>Map&lt;String,Long&gt;</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
/** * 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
/** * 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.
/** * 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
/** * Shared XML parser */
private static DocumentBuilder fgXMLParser = null;
Stores VM changes resulting from a JRE preference change.
/** * 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 null if none.
Params:
Returns:container id or null
/** * 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 Also:
  • defaultVMInstallChanged.defaultVMInstallChanged(IVMInstall, IVMInstall)
/** * @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 Also:
  • vmAdded.vmAdded(IVMInstall)
/** * @see org.eclipse.jdt.launching.IVMInstallChangedListener#vmAdded(org.eclipse.jdt.launching.IVMInstall) */
@Override public void vmAdded(IVMInstall vm) { }
See Also:
  • vmChanged.vmChanged(PropertyChangeEvent)
/** * @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 Also:
  • vmRemoved.vmRemoved(IVMInstall)
/** * @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.
/** * 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.
Params:
  • monitor – a progress monitor or null
  • projects – the list of IJavaProjects to re-bind the VM to
Throws:
/** * 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
/** * Constructor */
public LaunchingPlugin() { super(); fgLaunchingPlugin = this; }
Returns the library info that corresponds to the specified JRE install path, or null if none.
Params:
  • javaInstallPath – the absolute path to the java executable
Returns:the library info that corresponds to the specified JRE install path, or null if none
/** * 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.
Params:
  • javaInstallPath – home location for a JRE
  • info – the library information, or null to remove
/** * 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 java.io.File object that corresponds to the specified IPath in the plug-in directory.
Params:
  • path – the path to look for in the launching bundle
Returns:the File from the bundle or null
/** * 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.
Returns:the id of the LaunchingPlugin
/** * 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 LaunchingPlugin
Returns:the singleton instance of LaunchingPlugin
/** * 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
Params:
  • status – the status to log
/** * 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 Status
Params:
  • message – the message to log as an error 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 Status
Params:
/** * 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 Also:
  • stop.stop(BundleContext)
/** * 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 null if none.
Params:
  • id – connector identifier
Returns:VM connector
/** * 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.
Returns:VM connectors
/** * 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
/** * 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.
Params:
  • id – extension type id
Throws:
Returns:new uninitialized runtime classpath entry
/** * 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
/** * 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.
Params:
  • oldValue – the old preference value
  • newValue – the new preference value
/** * 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.
Params:
  • xml – the XML to parse for VM descriptions
Returns:VMDefinitionsContainer
/** * 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
Params:
  • ignore – if we should ignore VM property changed events or not
/** * 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
Returns:if VM property changed event should be ignored or not
/** * 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.

The resulting XML is compatible with the static method parseXMLIntoContainer.

Throws:
  • CoreException – if this method fails. Reasons include:
    • serialization of the XML document failed
Returns:String the results of flattening this object into XML
/** * 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.
Params:
Returns:Element
/** * 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.
Params:
  • doc – the backing Document
  • elementType – the kind of Element to create
  • libraryElement – the Element describing a given LibraryInfo object
  • paths – the paths to add
/** * 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
/** * 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
/** * 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
Params:
  • location – the location of the SDK we want to check the time stamp for
Returns:true if the time stamp has changed compared to the cached one or if there is no recorded time stamp, false otherwise.
Since:3.7
/** * 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 fgInstallTimeMap. See https://bugs.eclipse.org/bugs/show_bug.cgi?id=266651 for more information
Since:3.7
/** * 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
/** * 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
Params:
  • lib – the library path in Element form
  • pathType – the type of the path
Returns:paths stored in XML
/** * 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.
Throws:
Returns:an XML parser
Since:3.0
/** * 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.
Params:
  • message – error message
  • exception – underlying exception or null if none
Throws:
/** * 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
Params:
  • url1 – a given URL
  • url2 – another given URL to compare to url1
Since:3.5
Returns:true if the URLs are equal, false otherwise
/** * 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.
Params:
  • url – the URL to get the String value of
Returns:the lower-case String form of the given URL
/** * 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)
Params:
  • option – the option or null
  • message – the message to print or null
  • throwable – the Throwable or null
Since:3.8
/** * 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)
Params:
  • message – the message or null
Since:3.8
/** * 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); } }