com.google.gwt.eclipse.oophm.model.LaunchConfiguration.java Source code

Java tutorial

Introduction

Here is the source code for com.google.gwt.eclipse.oophm.model.LaunchConfiguration.java

Source

/*******************************************************************************
 * 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;
    }
}