/******************************************************************************* * Copyright (c) 2012 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.grails.ide.eclipse.core.launch; import java.util.HashSet; import java.util.IdentityHashMap; import java.util.Map; import java.util.Set; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.debug.core.ILaunchConfiguration; import org.eclipse.debug.core.ILaunchConfigurationType; import org.eclipse.debug.core.ILaunchManager; import org.grails.ide.eclipse.core.GrailsCoreActivator; /** * This class provides a mechanism to listen for the termination of a Process spawned by a Launch. To be able * to function, this infrastructure requires the cooperation of the LaunchConfigurationDelegate to check whether * a listener should be added to launch sometime between the time when the Launch is created, and when it * is actually launched. * @author Kris De Volder */ public class LaunchListenerManager { /** * This utility requires the cooperation of a LaunchConfigurationDelegate to register the listener at * the appropriate time during the launch procedure. This set contains the Ids of the launch configuration * types of LaunchConfiguration delegates that have promised they provide this support. */ private static Set<String> supportedLaunchTypes = new HashSet<String>(); /** * This map is used by launchWithListener to temporarily associate a Listener with a launch configuration. * This is not a nice way to do that, but there seems to be no other way to add an object instance as a * property of the LaunchConfiguration because it only supports simple attribute types that can be stored * into XML files. */ private static Map<ILaunchConfiguration, AbstractLaunchProcessListener> listenerMap = new IdentityHashMap<ILaunchConfiguration, AbstractLaunchProcessListener>(); private static void removeLaunchListener(ILaunchConfiguration conf, AbstractLaunchProcessListener listener) { synchronized (listenerMap) { Assert.isTrue(listenerMap.get(conf)==listener); listenerMap.remove(conf); } } /** * LaunchConfigurationDelegates that are registered as supporting the LaunchListenerManager infrastructure * must call this method during the launch sequence to retrieve any listener associated with the * launch configuration and call the Listener's init method. */ public static AbstractLaunchProcessListener getLaunchListener(ILaunchConfiguration configuration) { synchronized (listenerMap) { return listenerMap.get(configuration); } } /** * Launch the given launch configuration ensuring that the listener gets associated with * this launch configuration once it gets launched. It is more or less assumed that * launch configurations will only by launched once (or at least not concurrently with themselves). * Should this assumption get violated this will be detected and an AssertionFailedException * will be raised. */ public static AbstractLaunchProcessListener launchWithListener(ILaunchConfiguration launchConf, SynchLaunch synchLaunch, AbstractLaunchProcessListener launchListener) throws CoreException { synchronized (listenerMap) { Assert.isTrue(isSupported(launchConf.getType())); Assert.isTrue(listenerMap.get(launchConf)==null, "Concurrent reuse of a LaunchConfiguration"); // Concurrent reuse is not allowed, since this will make it impossible to // reliably associate a listener with a given Launch of the configuration. // This should not be a problem, since we are only creating "run once" configurations to // execute Grails commands. listenerMap.put(launchConf, launchListener); } try { launchConf.launch(ILaunchManager.RUN_MODE, new NullProgressMonitor(), synchLaunch.isDoBuild(), synchLaunch.isShowOutput()); } finally { removeLaunchListener(launchConf, launchListener); } return launchListener; } public static boolean isSupported(ILaunchConfigurationType type) { return supportedLaunchTypes.contains(type.getIdentifier()); } public static boolean isSupported(ILaunchConfiguration launchConf) { try { return isSupported(launchConf.getType()); } catch (CoreException e) { GrailsCoreActivator.log(e); return false; } } /** * A LaunchConfigurationDelegate should call this method to declare that it supports * the LaunchListener infrastructure. * <p> * By doing so, the LaunchConfigurationDelegate promises to call the getLaunchListener * methods to check for registered listener and initialize it at the appropriate * time (i.e. just before actually launching the Launch). */ public static void promiseSupportForType(String launchConfTypetypeID) { supportedLaunchTypes.add(launchConfTypetypeID); } /** * Method provided only for testing purposes. */ public static boolean isMemoryLeaked() { return !listenerMap.isEmpty(); } }