/******************************************************************************* * Copyright © 2011, 2013 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.edt.ide.testserver; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceChangeEvent; import org.eclipse.core.resources.IResourceChangeListener; import org.eclipse.core.resources.IResourceDelta; import org.eclipse.core.resources.IResourceDeltaVisitor; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.debug.core.DebugException; import org.eclipse.edt.ide.internal.testserver.UpdateErrorDialog; import org.eclipse.edt.ide.testserver.TestServerConfiguration.TerminationListener; import org.eclipse.jface.dialogs.ErrorDialog; import org.eclipse.osgi.util.NLS; import org.eclipse.swt.widgets.Display; /** * Keeps track of the test server instances, created new ones when needed. Also handles notifying the user when * the classpath for one or more servers has changed. This class should be used instead of directly instantiating * {@link TestServerConfiguration}. */ public class TestServerManager implements TerminationListener, IResourceChangeListener { private static TestServerManager INSTANCE; private final Object syncObj; private final HashMap<IProject, TestServerConfiguration> serverConfigMap; public static synchronized TestServerManager getInstance() { if (INSTANCE == null) { INSTANCE = new TestServerManager(); } return INSTANCE; } private TestServerManager() { syncObj = new Object(); serverConfigMap = new HashMap<IProject, TestServerConfiguration>(); ResourcesPlugin.getWorkspace().addResourceChangeListener(this, IResourceChangeEvent.PRE_CLOSE | IResourceChangeEvent.PRE_DELETE | IResourceChangeEvent.POST_CHANGE); } /** * Resets this manager. All running servers will be terminated. */ public void reset() { synchronized(syncObj) { for (TestServerConfiguration config : serverConfigMap.values()) { try { config.terminate(); } catch (DebugException e) { } } serverConfigMap.clear(); } } /** * @return a test server configuration for the given project. If the configuration didn't yet exist, it will be created. */ public TestServerConfiguration getServerConfiguration(IProject project, boolean debugMode) { synchronized(syncObj) { TestServerConfiguration config = serverConfigMap.get(project); if (config == null) { config = new TestServerConfiguration(project, debugMode); config.addTerminationListener(this); serverConfigMap.put(project, config); } return config; } } /** * @return the corresponding test server configuration for the given project, or null if one does not exist. */ public TestServerConfiguration lookupConfiguration(IProject project) { synchronized(syncObj) { return serverConfigMap.get(project); } } /** * Removes the configuration for the given project from this manager, if it exists. */ public void removeServerConfiguration(IProject project) { synchronized(syncObj) { serverConfigMap.remove(project); } } @Override public void terminated(TestServerConfiguration config) { removeServerConfiguration(config.getProject()); } @Override public void resourceChanged(IResourceChangeEvent event) { switch (event.getType()) { case IResourceChangeEvent.POST_CHANGE: if (event.getDelta() != null) { int mapSize; synchronized (syncObj) { mapSize = serverConfigMap.size(); } if (mapSize > 0) { class CheckClasspath extends RuntimeException {private static final long serialVersionUID = 1L;}; // For fast exit of delta visitor try { event.getDelta().accept(new IResourceDeltaVisitor() { @Override public boolean visit(IResourceDelta delta) throws CoreException { if (delta == null) { return false; } switch (delta.getKind()) { case IResourceDelta.CHANGED: if ((delta.getFlags() & IResourceDelta.CONTENT) == 0 && (delta.getFlags() & IResourceDelta.ENCODING) == 0) { // No actual change, skip it. return true; } // Fall through. case IResourceDelta.ADDED: case IResourceDelta.REMOVED: if (ClasspathUtil.canAffectClasspath(delta.getFullPath().lastSegment())) { throw new CheckClasspath(); } break; } return true; } }); } catch (CoreException ce) { TestServerPlugin.getDefault().log(ce.getMessage(), ce); } catch (CheckClasspath cc) { handleClasspathChanged(); } } } break; case IResourceChangeEvent.PRE_DELETE: case IResourceChangeEvent.PRE_CLOSE: IResource resource = event.getResource(); if (resource != null && resource.getType() == IResource.PROJECT) { TestServerConfiguration config = null; synchronized(syncObj) { config = serverConfigMap.remove(resource); } if (config != null) { try { config.terminate(); } catch (DebugException e) { } config.dispose(); } } break; } } /** * We check for classpath changes on all test servers at once so that you don't get several dialogs * when a classpath change affects multiple servers. Instead, you get one that lists all the affected * test servers. */ private void handleClasspathChanged() { final List<TestServerConfiguration> configsWithChanges = new ArrayList<TestServerConfiguration>(); HashMap<IProject, TestServerConfiguration> mapClone; synchronized(syncObj) { mapClone = (HashMap)serverConfigMap.clone(); } if (mapClone != null) { for (TestServerConfiguration config : mapClone.values()) { if (config.hasClasspathChanged()) { configsWithChanges.add(config); } } } if (configsWithChanges.size() > 0) { switch (TestServerPlugin.getDefault().getPreferenceStore().getInt(ITestServerPreferenceConstants.PREFERENCE_TESTSERVER_CLASSPATH_CHANGED)) { case ITestServerPreferenceConstants.TESTSERVER_IGNORE: break; case ITestServerPreferenceConstants.TESTSERVER_TERMINATE: for (final TestServerConfiguration config : configsWithChanges) { try { config.terminate(); } catch (final DebugException e) { final Display display = TestServerPlugin.getDisplay(); display.asyncExec(new Runnable() { @Override public void run() { ErrorDialog.openError(TestServerPlugin.getShell(), TestServerMessages.TerminateFailedTitle, NLS.bind(TestServerMessages.TerminateFailedMsg, config.getProject().getName()), e.getStatus()); } }); } } break; case ITestServerPreferenceConstants.TESTSERVER_PROMPT: default: final Display display = TestServerPlugin.getDisplay(); display.asyncExec(new Runnable() { @Override public void run() { if (display.isDisposed()) { return; } StringBuilder projectsInsert = new StringBuilder(20 * configsWithChanges.size()); for (TestServerConfiguration config : configsWithChanges) { if (projectsInsert.length() != 0) { projectsInsert.append('\n'); } projectsInsert.append(config.getProject().getName()); } UpdateErrorDialog dialog = new UpdateErrorDialog( TestServerPlugin.getShell(), TestServerMessages.ClasspathChangedTitle, null, new Status(IStatus.WARNING, TestServerPlugin.PLUGIN_ID, NLS.bind(TestServerMessages.ClasspathChangedMsg, projectsInsert) + "\n\n" //$NON-NLS-1$ + (configsWithChanges.size() > 1 ? TestServerMessages.ErrorDialogTerminatePluralMsg : TestServerMessages.ErrorDialogTerminateMsg)), TestServerPlugin.getDefault().getPreferenceStore(), ITestServerPreferenceConstants.PREFERENCE_TESTSERVER_CLASSPATH_CHANGED, configsWithChanges.toArray(new TestServerConfiguration[configsWithChanges.size()])); dialog.open(); } }); break; } } } }