blob: 1ecd915f28ced3cea62ba6d0365d1e2d46369293 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2019 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.debug.internal.ui.views.console;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchListener;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.debug.core.model.IProcess;
import org.eclipse.debug.internal.ui.DebugUIPlugin;
import org.eclipse.debug.ui.IDebugUIConstants;
import org.eclipse.debug.ui.console.ConsoleColorProvider;
import org.eclipse.debug.ui.console.IConsoleColorProvider;
import org.eclipse.debug.ui.console.IConsoleLineTracker;
import org.eclipse.jface.text.IDocument;
import org.eclipse.ui.console.ConsolePlugin;
import org.eclipse.ui.console.IConsole;
import org.eclipse.ui.console.IConsoleManager;
/**
* Creates documents for processes as they are registered with a launch.
* The singleton manager is accessible from the debug UI plugin.
*/
public class ProcessConsoleManager implements ILaunchListener {
/**
* Creates console for given process
*/
private final class ConsoleCreation extends Job {
private final ILaunch launch;
private final IProcess process;
ConsoleCreation(ILaunch launch, IProcess process) {
super("Creating console for " + process.getLabel()); //$NON-NLS-1$
this.launch = launch;
this.process = process;
setSystem(true);
setUser(false);
}
@Override
protected IStatus run(IProgressMonitor monitor) {
if (monitor.isCanceled() || getConsoleDocument(process) != null) {
return Status.CANCEL_STATUS;
}
IConsoleColorProvider colorProvider = getColorProvider(process.getAttribute(IProcess.ATTR_PROCESS_TYPE));
String encoding = launch.getAttribute(DebugPlugin.ATTR_CONSOLE_ENCODING);
ProcessConsole pc = new ProcessConsole(process, colorProvider, encoding);
pc.setAttribute(IDebugUIConstants.ATTR_CONSOLE_PROCESS, process);
// add new console to console manager.
ConsolePlugin.getDefault().getConsoleManager().addConsoles(new IConsole[] { pc });
// If a launch is removed the associated console is removed too. It can happen
// that the launch is removed even before the console could be created.
// see https://bugs.eclipse.org/bugs/show_bug.cgi?id=546710#c13
ILaunchManager launchManager = DebugPlugin.getDefault().getLaunchManager();
if (!launchManager.isRegistered(launch)) {
removeLaunch(launch);
}
return Status.OK_STATUS;
}
@Override
public boolean belongsTo(Object family) {
return process == family || ProcessConsoleManager.class == family;
}
@Override
public boolean shouldSchedule() {
Job[] jobs = Job.getJobManager().find(process);
for (Job job : jobs) {
if (job instanceof ConsoleCreation) {
return false;
}
}
return true;
}
}
/**
* Console document content provider extensions, keyed by extension id
*/
private Map<String, IConfigurationElement> fColorProviders;
/**
* The default color provider. Used if no color provider is contributed
* for the given process type.
*/
private IConsoleColorProvider fDefaultColorProvider;
/**
* Console line trackers; keyed by process type to list of trackers (1:N)
*/
private Map<String, List<IConfigurationElement>> fLineTrackers;
/**
* Map of processes for a launch to compute removed processes
*/
private Map<ILaunch, IProcess[]> fProcesses;
/**
* Lock for fLineTrackers
*/
private Object fLineTrackersLock = new Object();
/**
* @see ILaunchListener#launchRemoved(ILaunch)
*/
@Override
public void launchRemoved(ILaunch launch) {
removeLaunch(launch);
}
protected void removeLaunch(ILaunch launch) {
for (IProcess process : launch.getProcesses()) {
removeProcess(process);
}
if (fProcesses != null) {
fProcesses.remove(launch);
}
}
/**
* Removes the console and document associated with the given process.
*
* @param iProcess process to clean up
*/
private void removeProcess(IProcess iProcess) {
IConsole console = getConsole(iProcess);
if (console != null) {
IConsoleManager manager = ConsolePlugin.getDefault().getConsoleManager();
manager.removeConsoles(new IConsole[]{console});
}
}
/**
* Returns the console for the given process, or <code>null</code> if none.
*
* @param process
* @return the console for the given process, or <code>null</code> if none
*/
public IConsole getConsole(IProcess process) {
IConsoleManager manager = ConsolePlugin.getDefault().getConsoleManager();
for (IConsole console : manager.getConsoles()) {
if (console instanceof ProcessConsole) {
ProcessConsole pc = (ProcessConsole)console;
if (pc.getProcess().equals(process)) {
return pc;
}
}
}
return null;
}
/**
* @see ILaunchListener#launchAdded(ILaunch)
*/
@Override
public void launchAdded(ILaunch launch) {
launchChanged(launch);
}
/**
* @see ILaunchListener#launchChanged(ILaunch)
*/
@Override
public void launchChanged(final ILaunch launch) {
IProcess[] processes= launch.getProcesses();
for (IProcess process : processes) {
if (process.getStreamsProxy() == null) {
continue;
}
if (getConsoleDocument(process) == null) {
// create a new console in a separated thread, see bug 355011.
Job job = new ConsoleCreation(launch, process);
job.schedule();
}
}
List<IProcess> removed = getRemovedProcesses(launch);
if (removed != null) {
for (IProcess p : removed) {
removeProcess(p);
}
}
}
/**
* Returns the document for the process, or <code>null</code>
* if none.
*/
public IDocument getConsoleDocument(IProcess process) {
ProcessConsole console = (ProcessConsole) getConsole(process);
return (console != null ? console.getDocument() : null);
}
/**
* Called by the debug ui plug-in on startup.
* The console document manager starts listening for
* launches to be registered and initializes if any launches
* already exist.
*/
public void startup() {
ILaunchManager launchManager= DebugPlugin.getDefault().getLaunchManager();
launchManager.addLaunchListener(this);
//set up the docs for launches already registered
for (ILaunch launch : launchManager.getLaunches()) {
launchAdded(launch);
}
}
/**
* Called by the debug ui plug-in on shutdown.
* The console document manager de-registers as a
* launch listener and kills all existing console documents.
*/
public void shutdown() {
Job.getJobManager().cancel(ProcessConsoleManager.class);
ILaunchManager launchManager= DebugPlugin.getDefault().getLaunchManager();
for (ILaunch launch : launchManager.getLaunches()) {
removeLaunch(launch);
}
launchManager.removeLaunchListener(this);
if (fProcesses != null) {
fProcesses.clear();
}
}
/**
* Returns a new console document color provider extension for the given
* process type, or <code>null</code> if none.
*
* @param type corresponds to <code>IProcess.ATTR_PROCESS_TYPE</code>
* @return IConsoleColorProvider
*/
public IConsoleColorProvider getColorProvider(String type) {
if (fColorProviders == null) {
fColorProviders = new HashMap<>();
IExtensionPoint extensionPoint= Platform.getExtensionRegistry().getExtensionPoint(DebugUIPlugin.getUniqueIdentifier(), IDebugUIConstants.EXTENSION_POINT_CONSOLE_COLOR_PROVIDERS);
for (IConfigurationElement extension : extensionPoint.getConfigurationElements()) {
fColorProviders.put(extension.getAttribute("processType"), extension); //$NON-NLS-1$
}
}
IConfigurationElement extension = fColorProviders.get(type);
if (extension != null) {
try {
Object colorProvider = extension.createExecutableExtension("class"); //$NON-NLS-1$
if (colorProvider instanceof IConsoleColorProvider) {
return (IConsoleColorProvider)colorProvider;
}
DebugUIPlugin.logErrorMessage(MessageFormat.format(
"Extension {0} must specify an instanceof IConsoleColorProvider for class attribute.", //$NON-NLS-1$
new Object[] { extension.getDeclaringExtension().getUniqueIdentifier() }));
} catch (CoreException e) {
DebugUIPlugin.log(e);
}
}
//no color provider found of specified type, return default color provider.
if (fDefaultColorProvider == null) {
fDefaultColorProvider = new ConsoleColorProvider();
}
return fDefaultColorProvider;
}
/**
* Returns the Line Trackers for a given process type.
* @param process The process for which line trackers are required.
* @return An array of line trackers which match the given process type.
*/
public IConsoleLineTracker[] getLineTrackers(IProcess process) {
String type = process.getAttribute(IProcess.ATTR_PROCESS_TYPE);
if (fLineTrackers == null) {
synchronized (fLineTrackersLock) { // can't use fLineTrackers as lock as it is null here
fLineTrackers = new HashMap<>();
IExtensionPoint extensionPoint = Platform.getExtensionRegistry().getExtensionPoint(DebugUIPlugin.getUniqueIdentifier(), IDebugUIConstants.EXTENSION_POINT_CONSOLE_LINE_TRACKERS);
for (IConfigurationElement extension : extensionPoint.getConfigurationElements()) {
String processType = extension.getAttribute("processType"); //$NON-NLS-1$
List<IConfigurationElement> list = fLineTrackers.get(processType);
if (list == null) {
list = new ArrayList<>();
fLineTrackers.put(processType, list);
}
list.add(extension);
}
}
}
ArrayList<IConsoleLineTracker> trackers = new ArrayList<>();
if (type != null) {
List<IConfigurationElement> lineTrackerExtensions;
synchronized (fLineTrackers) {// need to synchronize as the update to list might be still happening
lineTrackerExtensions = fLineTrackers.get(type);
}
if(lineTrackerExtensions != null) {
for (IConfigurationElement element : lineTrackerExtensions) {
try {
trackers.add((IConsoleLineTracker) element.createExecutableExtension("class")); //$NON-NLS-1$
} catch (CoreException e) {
DebugUIPlugin.log(e);
}
}
}
}
return trackers.toArray(new IConsoleLineTracker[0]);
}
/**
* Returns the processes that have been removed from the given
* launch, or <code>null</code> if none.
*
* @param launch launch that has changed
* @return removed processes or <code>null</code>
*/
private List<IProcess> getRemovedProcesses(ILaunch launch) {
List<IProcess> removed = null;
if (fProcesses == null) {
fProcesses = new HashMap<>();
}
IProcess[] old = fProcesses.get(launch);
IProcess[] curr = launch.getProcesses();
if (old != null) {
for (IProcess process : old) {
if (!contains(curr, process)) {
if (removed == null) {
removed = new ArrayList<>();
}
removed.add(process);
}
}
}
// update cache with current processes
fProcesses.put(launch, curr);
return removed;
}
/**
* Returns whether the given object is contained in the list.
*
* @param list list to search
* @param object object to search for
* @return whether the given object is contained in the list
*/
private boolean contains(Object[] list, Object object) {
for (Object object2 : list) {
if (object2.equals(object)) {
return true;
}
}
return false;
}
}