Java tutorial
/******************************************************************************* * Copyright 2011 Google Inc. All Rights Reserved. * * 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 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. *******************************************************************************/ package com.google.gwt.eclipse.oophm.model; import com.google.gdt.eclipse.core.launch.LaunchConfigurationProcessorUtilities; import com.google.gdt.eclipse.core.launch.WebAppLaunchConfiguration; import com.google.gwt.core.ext.TreeLogger; import com.google.gwt.eclipse.core.GWTPluginLog; import com.google.gwt.eclipse.oophm.Activator; import com.google.gwt.eclipse.oophm.model.BrowserTab.Info; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.debug.core.ILaunch; import org.eclipse.debug.core.ILaunchConfiguration; import java.util.ArrayList; import java.util.List; /** * A launch configuration with associated {@link BrowserTab} connections and a * {@link Server} connection. * * This class is thread-safe. */ public class LaunchConfiguration implements IModelNode { /** * Compute a name prefix the {@link ILaunch}. The caller may augment this * prefix to make the name unique within the {@link WebAppDebugModel}. * * @param clientId optional if launch is non-null */ static String computeNamePrefix(ILaunch launch, String clientId) { return launch != null ? launch.getLaunchConfiguration().getName() : clientId; } private final List<BrowserTab> browserTabs = new ArrayList<BrowserTab>(); private final int id; private boolean isServerReloading = false; private boolean isTerminated = false; private final ILaunch launch; private final String launchTypeId; private List<String> launchUrls; private WebAppDebugModel model; private final String name; private final Object privateInstanceLock = new Object(); private Server server = null; private boolean supportsRestartWebServer = false; /** * Create a new instance. * * @param name The name of the launch configuration. * @param model The model associated with this launch configuration */ LaunchConfiguration(ILaunch launch, String name, WebAppDebugModel model) { id = model.getModelNodeNextId(); this.launch = launch; this.name = name; this.model = model; // We're caching the type ID as // ModelLabelProvider.getLaunchConfigurationImage() may request it even // after the actual launch configuration has been deleted. String typeId = WebAppLaunchConfiguration.TYPE_ID; ILaunchConfiguration launchConfiguration = launch.getLaunchConfiguration(); if (launchConfiguration != null) { try { typeId = launchConfiguration.getType().getIdentifier(); } catch (CoreException e) { GWTPluginLog.logError(e, "Could not determine the launch configuration type"); } } this.launchTypeId = typeId; } /** * Associate a browser tab with this launch configuration. Also removes any * other browser tabs with the same name that have been terminated, except for * the most-recently terminated one. * * Fires an event for browser tab creation and possibly multiple events for * browser tab removals to all listeners on the {@link WebAppDebugModel}. * * If the launch configuration is already terminated, then the browser tab * will not be associated with the launch configuration. */ public BrowserTab addBrowserTab(Info info, String moduleName) { BrowserTab browserTab = null; synchronized (privateInstanceLock) { String tabName = BrowserTab.computeNamePrefix(info); if (isTerminated) { Activator.getDefault().getLog() .log(new Status(IStatus.INFO, Activator.PLUGIN_ID, "Browser tab " + tabName + " could not be added to launch configuration " + getName() + " because the launch configuration is already terminated.")); return null; } browserTab = new BrowserTab(this, info, tabName, moduleName); // Add to the LaunchConfiguration browserTabs.add(browserTab); } // Only fire events when we're not holding any locks. Otherwise, we can // cause deadlock. final WebAppDebugModelEvent<BrowserTab> createdEvent = new WebAppDebugModelEvent<BrowserTab>(browserTab); fireBrowserTabCreated(createdEvent); // Clean up terminated browser tabs with the same name removeAllAssociatedTerminatedTabsExceptMostRecent(browserTab); return browserTab; } /** * Find a browser tab that matches the given criteria. * * If the tabKey is either null or the empty string, then matching is done * only be url, userAgent, and sessionKey. * * If tabKey is set to a value, then matching is done by url, userAgent, and * tabKey. * * @param url URL of top-level window * @param tabKey stable browser tab identifier * @param sessionKey the session key * @param userAgent short-form user agent identifier * @return a browser tab instance matching the criteria, or null */ public BrowserTab findBrowserTab(String userAgent, String url, String tabKey, String sessionKey) { synchronized (privateInstanceLock) { for (BrowserTab tab : browserTabs) { Info info = tab.getInfo(); boolean curTabMatches = false; if (info.getUserAgentTag().equals(userAgent) && info.getUrl().equals(url)) { if (tabKey != null && tabKey.length() > 0) { curTabMatches = info.getTabKey().equals(tabKey); } else { curTabMatches = info.getInitialSessionKey().equals(sessionKey); } } if (curTabMatches) { return tab; } } } return null; } /** * Returns a list of the browser tabs associated with this launch * configuration. */ public final List<BrowserTab> getBrowserTabs() { synchronized (privateInstanceLock) { return new ArrayList<BrowserTab>(browserTabs); } } public List<IModelNode> getChildren() { ArrayList<IModelNode> children = new ArrayList<IModelNode>(getBrowserTabs()); Server s = getServer(); if (s != null) { children.add(s); } return children; } public int getId() { return id; } /** * Returns the most-recently created browser tab that has not been terminated * as yet, or <code>null</code> if no such tab can be found. */ public BrowserTab getLatestActiveBrowserTab() { synchronized (privateInstanceLock) { for (int i = browserTabs.size() - 1; i > -1; i--) { BrowserTab tab = browserTabs.get(i); if (!tab.isTerminated()) { return tab; } } return null; } } public ILaunch getLaunch() { return launch; } public String getLaunchTypeId() { return launchTypeId; } /** * Returns the list of launch URLs for this launch configuration, or null if * they have not been set yet. */ public List<String> getLaunchUrls() { synchronized (privateInstanceLock) { return launchUrls != null ? new ArrayList<String>(launchUrls) : null; } } /** * Returns the model that contains this launch configuration. */ public WebAppDebugModel getModel() { return model; } /** * Returns the name of this launch configuration. */ public String getName() { return name; } public String getNeedsAttentionLevel() { TreeLogger.Type maxNeedsAttentionLevel = null; synchronized (privateInstanceLock) { List<IModelNode> allChildren = new ArrayList<IModelNode>(); allChildren.addAll(browserTabs); if (server != null) { allChildren.add(server); } for (IModelNode child : allChildren) { TreeLogger.Type childAttentionLevel = null; if (child.getNeedsAttentionLevel() != null) { childAttentionLevel = LogEntry.toTreeLoggerType(child.getNeedsAttentionLevel()); } if (childAttentionLevel == null) { continue; } if (maxNeedsAttentionLevel == null || maxNeedsAttentionLevel.isLowerPriorityThan(childAttentionLevel)) { maxNeedsAttentionLevel = childAttentionLevel; } } } if (maxNeedsAttentionLevel == null) { return null; } return maxNeedsAttentionLevel.getLabel(); } public WebAppDebugModel getParent() { return getModel(); } /** * Returns the server associated with this launch configuration. */ public final Server getServer() { synchronized (privateInstanceLock) { return server; } } /** * Returns <code>true</code> if this launch configuration has a web server. */ public boolean hasWebServer() { ILaunchConfiguration config = getLaunch().getLaunchConfiguration(); if (config == null) { GWTPluginLog.logError( "Checking if the launch is running a web server, but the ILaunch does not have an ILaunchConfiguration."); // Return true for the common case return true; } try { List<String> commands = LaunchConfigurationProcessorUtilities.parseProgramArgs(config); // No web server if the -noserver flag was specified return !commands.contains("-noserver"); } catch (CoreException e) { GWTPluginLog.logError(e, "Could not check if the launch is running a web server."); return true; } } public boolean isServerReloading() { synchronized (privateInstanceLock) { return isServerReloading; } } /** * @return true if the launch is in the serving state, false if it is still * loading */ public boolean isServing() { synchronized (privateInstanceLock) { return launchUrls != null; } } /** * Returns whether or not this launch configuration has terminated. */ public boolean isTerminated() { synchronized (privateInstanceLock) { return isTerminated; } } public void setLaunchUrls(List<String> launchUrls) { synchronized (privateInstanceLock) { this.launchUrls = launchUrls; } fireLaunchConfigurationLaunchUrlsChanged(new WebAppDebugModelEvent<LaunchConfiguration>(this)); } /** * Sets the server associated with this launch configuration. Fires an event * to all listeners on the {@link WebAppDebugModel}. If the launch * configuration is already terminated, then the server will not be associated * with the launch configuration. * * The server cannot be set more than once; there can only be one server * associated with a launch configuration over it's lifetime. */ public void setServer(Server newServer) { assert (newServer != null); boolean wasServerAdded = false; synchronized (privateInstanceLock) { if (this.server == null) { if (!isTerminated) { this.server = newServer; wasServerAdded = true; } } else { throw new UnsupportedOperationException("Cannot set the server more than once."); } } // Only fire events when we're not holding any locks. Otherwise, we can // cause deadlock. if (wasServerAdded) { final WebAppDebugModelEvent<Server> createEvent = new WebAppDebugModelEvent<Server>(server); fireServerCreated(createEvent); } else { // The launch configuration must have been terminated // TODO: Create a logger for the OOPHM plugin Activator.getDefault().getLog() .log(new Status(IStatus.INFO, Activator.PLUGIN_ID, "Server " + server.getName() + " could not be added to launch configuration " + getName() + " because the launch configuration is already terminated.")); } } public void setServerReloading(boolean serverReloading) { synchronized (privateInstanceLock) { this.isServerReloading = serverReloading; } fireLaunchConfigurationRestartWebServerStatusChanged(new WebAppDebugModelEvent<LaunchConfiguration>(this)); } public void setSupportsRestartWebServer() { synchronized (privateInstanceLock) { if (supportsRestartWebServer) { return; } supportsRestartWebServer = true; } final WebAppDebugModelEvent<LaunchConfiguration> restartWebServerStatusChanged = new WebAppDebugModelEvent<LaunchConfiguration>( this); fireLaunchConfigurationRestartWebServerStatusChanged(restartWebServerStatusChanged); } /** * Flag this launch configuration as terminated. Fires an event to all * listeners on the {@link WebAppDebugModel}. * * This will also flag all browser tabs and the server as terminated as well * (which will cause events to be fired to listeners on the * {@link WebAppDebugModel}). */ public void setTerminated() { /* * While we check/set the value of isTerminated while protected by a lock, * we set the browser tabs and server to terminated outside of the block. * This has the effect of allowing the possibility that the model can be * queried where a launch configuration has been marked as terminated, * whereas all of its children are not necessarily terminated. * * In theory, we should not be violating this invariant, but in practice, it * does not really matter, because events are fired in the correct order. * That is, the browser termination and server termination events will be * fired before the launch configuration termination event. Since the view * updates are event driven, there is no real reason as to why the view * would care to query and see if the parent launch configuration is * terminated whenever it receives a server or browser tab termination * event. If there happens to be such a case, we may have to revisit this * implementation. */ synchronized (privateInstanceLock) { if (isTerminated) { return; } isTerminated = true; } for (BrowserTab tab : getBrowserTabs()) { tab.setTerminated(); } Server s = getServer(); if (s != null) { s.setTerminated(); } // Only fire events when we're not holding any locks. Otherwise, we can // cause deadlock. final WebAppDebugModelEvent<LaunchConfiguration> terminateEvent = new WebAppDebugModelEvent<LaunchConfiguration>( this); fireLaunchConfigurationTerminated(terminateEvent); } public boolean supportsRestartWebServer() { return supportsRestartWebServer; } /** * Removes all terminated browser tabs that have name matching the given * browser tab, except for the most-recently terminated related browser tab. * Fires an event to all listeners on the {@link WebAppDebugModel}. */ void removeAllAssociatedTerminatedTabsExceptMostRecent(BrowserTab browserTab) { List<BrowserTab> terminatedAssociatedBrowserTabs = new ArrayList<BrowserTab>(); synchronized (privateInstanceLock) { for (BrowserTab tab : browserTabs) { if (tab.isTerminated() && tab.getName().equals(browserTab.getName())) { terminatedAssociatedBrowserTabs.add(tab); } } } int numTerminatedTabsToRemove = terminatedAssociatedBrowserTabs.size() - 1; for (int i = 0; i < numTerminatedTabsToRemove; i++) { removeBrowserTab(terminatedAssociatedBrowserTabs.get(i)); } } private void fireBrowserTabCreated(WebAppDebugModelEvent<BrowserTab> tabCreatedEvent) { for (IWebAppDebugModelListener webAppDebugModelListener : model.getWebAppDebugModelListeners()) { webAppDebugModelListener.browserTabCreated(tabCreatedEvent); } } private void fireBrowserTabRemoved(WebAppDebugModelEvent<BrowserTab> removedEvent) { for (IWebAppDebugModelListener webAppDebugModelListener : model.getWebAppDebugModelListeners()) { webAppDebugModelListener.browserTabRemoved(removedEvent); } } private void fireLaunchConfigurationLaunchUrlsChanged( WebAppDebugModelEvent<LaunchConfiguration> launchConfigurationLaunchUrlsChangedEvent) { for (IWebAppDebugModelListener webAppDebugModelListener : model.getWebAppDebugModelListeners()) { webAppDebugModelListener .launchConfigurationLaunchUrlsChanged(launchConfigurationLaunchUrlsChangedEvent); } } private void fireLaunchConfigurationRestartWebServerStatusChanged( WebAppDebugModelEvent<LaunchConfiguration> restartWebServerStatusChangedEvent) { for (IWebAppDebugModelListener webAppDebugModelListener : model.getWebAppDebugModelListeners()) { webAppDebugModelListener .launchConfigurationRestartWebServerStatusChanged(restartWebServerStatusChangedEvent); } } private void fireLaunchConfigurationTerminated( WebAppDebugModelEvent<LaunchConfiguration> launchConfigurationTerminatedEvent) { for (IWebAppDebugModelListener webAppDebugModelListener : model.getWebAppDebugModelListeners()) { webAppDebugModelListener.launchConfigurationTerminated(launchConfigurationTerminatedEvent); } } private void fireServerCreated(WebAppDebugModelEvent<Server> serverCreatedEvent) { for (IWebAppDebugModelListener webAppDebugModelListener : model.getWebAppDebugModelListeners()) { webAppDebugModelListener.serverCreated(serverCreatedEvent); } } /** * Remove the browser tab associated with this launch configuration. Fires an * event for the browser tab that was removed. * * * @return true if the browser tab was removed successfully */ private boolean removeBrowserTab(BrowserTab browserTab) { boolean wasRemoved = false; synchronized (privateInstanceLock) { wasRemoved = browserTabs.remove(browserTab); } if (wasRemoved) { // Only fire events when we're not holding any locks. Otherwise, deadlock // may happen. WebAppDebugModelEvent<BrowserTab> removedEvent = new WebAppDebugModelEvent<BrowserTab>(browserTab); fireBrowserTabRemoved(removedEvent); } return wasRemoved; } }