/******************************************************************************* * Copyright (c) 2013 Pivotal Software, Inc. * 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: * Pivotal Software, Inc. - initial API and implementation *******************************************************************************/ package org.springsource.ide.eclipse.commons.ui.launch; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashMap; import org.eclipse.debug.core.ILaunch; import org.eclipse.debug.core.ILaunchConfiguration; import org.eclipse.debug.core.model.IProcess; /** * A variation of LiveProcessTracker that retains launch configurations even * after all the associated processes have been terminated. (Thus allowing to * relaunch. * * @author Kris De Volder */ public class LiveAndDeadProcessTracker extends LaunchList { /* * The code in this class seems more complicated than it should be. The main source of * complication is dealing with cases where tracked launch configs have been renamed * or deleted. Ideally we would update the lists based on a ILaunchConfigurationListener * any time this happens. Unfortunately there's no 'renamed' event. Instead we * get disconnected 'delete' and 'create' events. * * What we do instead is check the elements returned by accessor methods 'getLast' and * 'getLaunches' on every call for 'broken' elements. Any 'broken' elements are dealt with either * by 'repairing' them if we can determine what it was renamed to, or deleting them * if we can not. * * Further complications are due to the fact that LinkedHashMap has no 'getLast' method. * We keep the 'last' element in a separate field. Keeping this field in sync * with the actual elements in the map requires some effort. */ public class ProcessItem extends Item { private final IProcess process; //This is so we can recover the configuration // for processes after the conf has been renamed. // Rename = delete + create. public ProcessItem(ILaunchConfiguration conf, String mode, IProcess process) { super(conf, mode); this.process = process; } public ProcessItem setConf(ILaunchConfiguration newConf) { return new ProcessItem(newConf, mode, process); } } private static final boolean DEBUG = false;// (""+Platform.getLocation()).contains("kdvolder"); private static LiveAndDeadProcessTracker instance; public synchronized static LaunchList getInstance() { if (instance==null) { instance = new LiveAndDeadProcessTracker(); } return instance; } private final LinkedHashMap<String, ProcessItem> configs = new LinkedHashMap<String, ProcessItem>(); private ProcessItem last = null; public LiveAndDeadProcessTracker() { //DebugPlugin.getDefault().getLaunchManager().addLaunchConfigurationListener(this); } @Override protected void processTerminated(IProcess process) { //nothing... we keep the dead ones too! } @Override protected void processCreated(IProcess process) { synchronized (process) { ILaunch l = process.getLaunch(); if (l!=null) { ILaunchConfiguration c = l.getLaunchConfiguration(); if (c!=null) { last = new ProcessItem(c, l.getLaunchMode(), process); configs.remove(last.getName()); //so the element moves to the end being now the 'most' recent. configs.put(last.getName(), last); } } } fireChangeEvent(); } private boolean exists(ILaunchConfiguration conf) { //Assume working copyies exist as long as we have a reference to them. return conf.exists() || conf.isWorkingCopy(); } @Override public synchronized Item getLast() { if (last!=null) { if (exists(last.conf)) { return last; } //might be conf delete or renamed last = findRenamed(last); if (last==null) { //last was deleted... find the 'previous last'. // It's a shame we have to iterate the whole collection to find the last one... // but there's no other way with a LinkedHashMap for (Item newLast : getLaunches()) { last = (ProcessItem)newLast; } } } return last; } @Override public synchronized Collection<Item> getLaunches() { //debug(">>> getLaunches"); ArrayList<String> names = new ArrayList<String>(configs.keySet()); for (String name : names) { // debug("name (key) = "+name); ProcessItem item = configs.get(name); // debug("name (item) = "+item.getName()); ILaunchConfiguration conf = item.conf; if (exists(conf)) { // debug("exists: "+conf.getName()); continue; } // could be renamed or deleted ProcessItem renamedItem = findRenamed(item); if (renamedItem!=null) { // debug("renamed: "+name+" -> "+renamedItem.getName()); configs.remove(item.getName()); configs.put(renamedItem.getName(), renamedItem); continue; } //config no longer valid probably deleted (or renamed, but couldn't figure out the new name). // debug("deleted: "+name); configs.remove(item.getName()); } ArrayList<Item> items = new ArrayList<LaunchList.Item>(configs.values()); // debug("<<< getLaunches # "+items.size()); return items; } /** * Try to 'fix' a renamed element. This only works if we can recover the * new config via its association with the process. This association may * no longer exist if the launch was deregistered from the debugger already. * So this is a 'best effort' implementation. * * @return Fixed element or null if a corresponding renamed config couldn't * be found. */ private ProcessItem findRenamed(ProcessItem item) { ILaunch l = item.process.getLaunch(); if (l!=null) { ILaunchConfiguration renamedConf = l.getLaunchConfiguration(); if (renamedConf!=null && exists(renamedConf)) { return item.setConf(renamedConf); } } return null; } private void debug(String string) { if (DEBUG) { System.out.println(string); } } }