/*******************************************************************************
* 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.views.hierarchical;
import com.google.gdt.eclipse.core.StatusUtilities;
import com.google.gwt.eclipse.core.preferences.GWTPreferences;
import com.google.gwt.eclipse.oophm.Activator;
import com.google.gwt.eclipse.oophm.DevModeImages;
import com.google.gwt.eclipse.oophm.devmode.DevModeServiceClient;
import com.google.gwt.eclipse.oophm.devmode.DevModeServiceClientManager;
import com.google.gwt.eclipse.oophm.model.BrowserTab;
import com.google.gwt.eclipse.oophm.model.LaunchConfiguration;
import com.google.gwt.eclipse.oophm.model.Log;
import com.google.gwt.eclipse.oophm.model.WebAppDebugModel;
import com.google.gwt.eclipse.oophm.model.WebAppDebugModelEvent;
import com.google.gwt.eclipse.oophm.model.WebAppDebugModelListenerAdapter;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.DebugException;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.part.PageBook;
import org.eclipse.ui.part.ViewPart;
import java.util.List;
/**
* A Web Application debug view that allows inspections of launch configurations, browsers and
* servers.
*/
public class WebAppLaunchView extends ViewPart {
/**
* Enumeration used for persisting and restoring the layout state of this view.
*/
enum LayoutType {
BREADCRUMB, TREE;
public static LayoutType toLayoutType(String layoutName) {
try {
if (layoutName != null) {
return valueOf(layoutName);
}
} catch (IllegalArgumentException e) {
// ignored;
}
// Return BREADCRUMB as the default
return BREADCRUMB;
}
}
/**
* Action for clearing a log viewer in the current selection.
*/
private final class ClearLogViewerAction extends Action {
private ClearLogViewerAction() {
super("Clear Log Viewer", Activator.getDefault().getImageDescriptor(DevModeImages.CLEAR_LOG));
}
@Override
public void run() {
ISelection selection = currentLayout.getSelection();
if (selection.isEmpty()) {
return;
}
if (selection instanceof IStructuredSelection) {
IStructuredSelection structuredSelection = (IStructuredSelection) selection;
Object firstElement = structuredSelection.getFirstElement();
if (firstElement instanceof BrowserTab) {
BrowserTab browserTab = (BrowserTab) firstElement;
browserTab.clearLog();
}
// TODO: When we have a server entry, we'll have to clear its logs as
// well
clearLogAction.setEnabled(false);
}
}
}
/**
* Controls when the ClearLogViewerAction should be enabled/disabled based on the current
* selection.
*/
private final class ClearLogViewerEnablementController implements ISelectionChangedListener {
@Override
public void selectionChanged(SelectionChangedEvent event) {
ISelection selection = event.getSelection();
if (selection.isEmpty()) {
// Empty selection disables the action
clearLogAction.setEnabled(false);
return;
}
if (selection instanceof IStructuredSelection) {
IStructuredSelection structuredSelection = (IStructuredSelection) selection;
Object firstElement = structuredSelection.getFirstElement();
clearLogAction.setEnabled(shouldEnableClearLogAction(firstElement));
}
}
private boolean shouldEnableClearLogAction(Object firstElement) {
if (firstElement instanceof BrowserTab) {
Log<?> log = ((BrowserTab) firstElement).getLog();
return log != null && log.hasDisclosedLogEntries();
}
return false;
}
}
/**
* Action for clearing terminated launches.
*/
private final class ClearTerminatedLaunchesAction extends Action {
private ClearTerminatedLaunchesAction() {
super("Remove all terminated launches", Activator.getDefault().getImageDescriptor(
DevModeImages.CLEAR_TERMINATED_LAUNCHES));
}
@Override
public void run() {
WebAppDebugModel.getInstance().removeTerminatedLaunchesFromModel();
/*
* At this point, we know that the view has been notified of the removals of the terminated
* launches from the model. It is safe to disable the "Clear Terminated Launches" button,
* because there is no way that the viewer could have been notified about a newly-terminated
* launch while this method is executing. Notifications occur on the UI thread, and we're
* currently executing on the UI thread.
*/
clearTerminatedLaunches.setEnabled(false);
}
}
/**
* Controls when the {@link ClearTerminatedLaunchesAction} should be enabled/disabled based on the
* current selection.
*/
private final class ClearTerminatedLaunchesEnablementController extends
WebAppDebugModelListenerAdapter implements ISelectionChangedListener {
public ClearTerminatedLaunchesEnablementController() {
WebAppDebugModel.getInstance().addWebAppDebugModelListener(this);
}
@Override
public void launchConfigurationTerminated(WebAppDebugModelEvent<LaunchConfiguration> e) {
Display.getDefault().syncExec(new Runnable() {
@Override
public void run() {
/*
* We execute the runnable synchronously here, because we want to make sure it's safe to
* enable the "Clear Terminated Launches" button. If we were to execute this runnable
* asynchronously, the enabling may happen after the user actually clicks the
* "Clear Terminated Launches" action. (the likelihood of this is very low).
*/
clearTerminatedLaunches.setEnabled(true);
}
});
}
@Override
public void selectionChanged(SelectionChangedEvent event) {
ISelection selection = event.getSelection();
if (selection.isEmpty()) {
clearTerminatedLaunches.setEnabled(false);
return;
}
if (selection instanceof IStructuredSelection) {
IStructuredSelection structuredSelection = (IStructuredSelection) selection;
Object firstElement = structuredSelection.getFirstElement();
clearTerminatedLaunches.setEnabled(shouldEnableAction(firstElement));
}
}
private boolean shouldEnableAction(Object firstElement) {
WebAppDebugModel model = WebAppDebugModel.getInstance();
List<LaunchConfiguration> terminatedLaunchConfigurations =
model.getTerminatedLaunchConfigurations();
return terminatedLaunchConfigurations.size() > 0;
}
}
/**
* Action for restarting a server.
*/
private final class ReloadServerAction extends Action {
private ReloadServerAction() {
super("Reload web server", Activator.getDefault().getImageDescriptor(
DevModeImages.RELOAD_WEB_SERVER));
}
@Override
public void run() {
ISelection selection = currentLayout.getSelection();
if (selection.isEmpty()) {
return;
}
if (selection instanceof IStructuredSelection) {
IStructuredSelection structuredSelection = (IStructuredSelection) selection;
Object firstElement = structuredSelection.getFirstElement();
final LaunchConfiguration lc;
if (firstElement instanceof BrowserTab) {
lc = ((BrowserTab) firstElement).getLaunchConfiguration();
} else if (firstElement instanceof LaunchConfiguration) {
lc = (LaunchConfiguration) firstElement;
} else {
lc = null;
}
if (lc == null || lc.isTerminated()) {
return;
}
final DevModeServiceClient client = DevModeServiceClientManager.getInstance().getClient(lc);
if (client == null) {
return;
}
reloadServer.setEnabled(false); // ok because we're on the UI thread
lc.setServerReloading(true);
Thread t = new Thread(new Runnable() {
@Override
public void run() {
try {
client.restartWebServer();
} catch (Exception e) {
Activator
.getDefault()
.getLog()
.log(
new Status(IStatus.ERROR, Activator.PLUGIN_ID,
"Unable to reload the web server.", e));
Display.getDefault().asyncExec(new Runnable() {
@Override
public void run() {
MessageDialog.openError(getSite().getShell(), "Development Mode View",
"Unable to reload the web server. See the Error Log for detals.");
}
});
}
lc.setServerReloading(false);
}
});
t.setName("Restart Web Server Executor Thread");
t.setDaemon(true);
t.start();
}
}
}
/**
* Controls when the {@link ReloadServerAction} should be enabled/disabled based on the current
* selection.
*/
private final class ReloadServerEnablementController extends WebAppDebugModelListenerAdapter
implements ISelectionChangedListener {
public ReloadServerEnablementController() {
WebAppDebugModel.getInstance().addWebAppDebugModelListener(this);
}
@Override
public void launchConfigurationRestartWebServerStatusChanged(
WebAppDebugModelEvent<LaunchConfiguration> e) {
Display.getDefault().asyncExec(new Runnable() {
@Override
public void run() {
shouldEnableActionBasedOnSelection(currentLayout.getSelection());
}
});
}
@Override
public void launchConfigurationTerminated(WebAppDebugModelEvent<LaunchConfiguration> e) {
Display.getDefault().asyncExec(new Runnable() {
@Override
public void run() {
shouldEnableActionBasedOnSelection(currentLayout.getSelection());
}
});
}
@Override
public void selectionChanged(SelectionChangedEvent event) {
shouldEnableActionBasedOnSelection(event.getSelection());
}
private boolean shouldEnableAction(Object firstElement) {
LaunchConfiguration launchConfiguration = null;
if (firstElement instanceof BrowserTab) {
BrowserTab browserTab = (BrowserTab) firstElement;
launchConfiguration = browserTab.getLaunchConfiguration();
} else if (firstElement instanceof LaunchConfiguration) {
launchConfiguration = (LaunchConfiguration) firstElement;
} else {
return false;
}
return launchConfiguration.hasWebServer() && launchConfiguration.supportsRestartWebServer()
&& !launchConfiguration.isTerminated() && !launchConfiguration.isServerReloading();
}
private void shouldEnableActionBasedOnSelection(ISelection selection) {
if (selection.isEmpty()) {
reloadServer.setEnabled(false);
return;
}
if (selection instanceof IStructuredSelection) {
IStructuredSelection structuredSelection = (IStructuredSelection) selection;
Object firstElement = structuredSelection.getFirstElement();
reloadServer.setEnabled(shouldEnableAction(firstElement));
}
}
}
/**
* Listens for a new launch, and if the preference is set, remove terminated launches.
*/
private final class RemoveTerminatedLaunchesOnLaunchListener extends
WebAppDebugModelListenerAdapter {
@Override
public void launchConfigurationLaunched(WebAppDebugModelEvent<LaunchConfiguration> e) {
Display.getDefault().syncExec(new Runnable() {
@Override
@SuppressWarnings("restriction")
public void run() {
if (GWTPreferences.getRemoveTerminatedLaunches()) {
clearTerminatedLaunches.run();
}
}
});
}
}
/**
* Action for terminating a launch configuration.
*/
private final class TerminateLaunchAction extends Action {
public TerminateLaunchAction() {
super("Terminate Selected Launch", Activator.getDefault().getImageDescriptor(
DevModeImages.TERMINATE_LAUNCH));
}
@Override
public void run() {
ISelection selection = currentLayout.getSelection();
if (selection.isEmpty()) {
return;
}
if (selection instanceof IStructuredSelection) {
IStructuredSelection structuredSelection = (IStructuredSelection) selection;
Object firstElement = structuredSelection.getFirstElement();
LaunchConfiguration launchConfiguration = null;
if (firstElement instanceof BrowserTab) {
BrowserTab browserTab = (BrowserTab) firstElement;
launchConfiguration = browserTab.getLaunchConfiguration();
} else if (firstElement instanceof LaunchConfiguration) {
launchConfiguration = (LaunchConfiguration) firstElement;
} else {
return;
}
try {
launchConfiguration.getLaunch().terminate();
terminateLaunchAction.setEnabled(false);
} catch (DebugException e) {
Activator.getDefault().getLog()
.log(StatusUtilities.newErrorStatus(e, Activator.PLUGIN_ID));
}
}
}
}
/**
* Controls when the TerminateLaunchAction should be enabled/disabled based on the current
* selection.
*/
private final class TerminateLaunchEnablementController implements ISelectionChangedListener {
@Override
public void selectionChanged(SelectionChangedEvent event) {
ISelection selection = event.getSelection();
if (selection.isEmpty()) {
// Empty selection disables the action
terminateLaunchAction.setEnabled(false);
return;
}
if (selection instanceof IStructuredSelection) {
IStructuredSelection structuredSelection = (IStructuredSelection) selection;
Object firstElement = structuredSelection.getFirstElement();
terminateLaunchAction.setEnabled(shouldEnableAction(firstElement));
}
}
private boolean shouldEnableAction(Object firstElement) {
LaunchConfiguration launchConfiguration = null;
if (firstElement instanceof BrowserTab) {
BrowserTab browserTab = (BrowserTab) firstElement;
launchConfiguration = browserTab.getLaunchConfiguration();
} else if (firstElement instanceof LaunchConfiguration) {
launchConfiguration = (LaunchConfiguration) firstElement;
} else {
return false;
}
return !launchConfiguration.isTerminated();
}
}
/**
* The ID of the view as specified by the extension. Note this is hard-coded since the ID differs
* from the plugin package name.
*/
public static final String ID = "com.gwtplugins.gwt.eclipse.DevModeView";
protected static final Object[] NO_ELEMENTS = new Object[0];
/**
* Key used to store this view's layout in this plugin's {@link IDialogSettings}.
*/
private static final String DIALOG_PROPERTIES_LAYOUT_KEY = WebAppLaunchView.ID + ".layout";
private BreadcrumbNavigationView breadcrumbLayout;
private Action clearLogAction;
private Action clearTerminatedLaunches;
private ISelectionProvider currentLayout;
private PageBook pageBook;
private Action reloadServer;
private Action switchToBreadcrumbLayoutAction;
private Action switchToTreeLayoutAction;
private Action terminateLaunchAction;
private TreeNavigationView treeLayout;
/**
* This is a callback that will allow us to create the viewer and initialize it.
*/
@Override
public void createPartControl(Composite parent) {
createActions();
addTerminatedLaunchListener();
createLayouts(parent);
contributeToActionBars();
initControls();
}
public IToolBarManager getToolbarManager() {
return getViewSite().getActionBars().getToolBarManager();
}
/**
* Passing the focus request to the viewer's control.
*/
@Override
public void setFocus() {
// TODO: Fill me in
}
private void addTerminatedLaunchListener() {
WebAppDebugModel.getInstance().addWebAppDebugModelListener(
new RemoveTerminatedLaunchesOnLaunchListener());
}
private void contributeToActionBars() {
IActionBars bars = getViewSite().getActionBars();
fillLocalPullDown(bars.getMenuManager());
fillLocalToolBar(bars.getToolBarManager());
}
private void createActions() {
switchToTreeLayoutAction = new Action("Tree", IAction.AS_RADIO_BUTTON) {
@Override
public void run() {
switchToTreeLayout();
}
};
switchToBreadcrumbLayoutAction = new Action("Breadcrumb", IAction.AS_RADIO_BUTTON) {
@Override
public void run() {
switchToBreadcrumbLayout();
}
};
clearLogAction = new ClearLogViewerAction();
clearTerminatedLaunches = new ClearTerminatedLaunchesAction();
reloadServer = new ReloadServerAction();
terminateLaunchAction = new TerminateLaunchAction();
}
private void createLayouts(Composite parent) {
pageBook = new PageBook(parent, SWT.NONE);
WebAppDebugModel model = WebAppDebugModel.getInstance();
treeLayout = new TreeNavigationView(pageBook, SWT.NONE);
treeLayout.addSelectionChangedListener(new ClearLogViewerEnablementController());
treeLayout.addSelectionChangedListener(new ClearTerminatedLaunchesEnablementController());
treeLayout.addSelectionChangedListener(new ReloadServerEnablementController());
treeLayout.addSelectionChangedListener(new TerminateLaunchEnablementController());
treeLayout.setInput(model);
breadcrumbLayout = new BreadcrumbNavigationView(pageBook, SWT.NONE);
breadcrumbLayout.addSelectionChangedListener(new ClearLogViewerEnablementController());
breadcrumbLayout.addSelectionChangedListener(new ClearTerminatedLaunchesEnablementController());
breadcrumbLayout.addSelectionChangedListener(new ReloadServerEnablementController());
breadcrumbLayout.addSelectionChangedListener(new TerminateLaunchEnablementController());
breadcrumbLayout.setInput(model);
}
private void fillLocalPullDown(IMenuManager manager) {
MenuManager layoutMenu = new MenuManager("Layout");
layoutMenu.add(switchToTreeLayoutAction);
layoutMenu.add(switchToBreadcrumbLayoutAction);
manager.add(layoutMenu);
}
private void fillLocalToolBar(IToolBarManager manager) {
manager.add(terminateLaunchAction);
manager.add(clearTerminatedLaunches);
manager.add(new Separator());
manager.add(reloadServer);
manager.add(new Separator());
manager.add(clearLogAction);
}
private IDialogSettings getDialogSettings() {
return Activator.getDefault().getDialogSettings();
}
private void initControls() {
IDialogSettings dialogSettings = getDialogSettings();
String layoutName = dialogSettings.get(DIALOG_PROPERTIES_LAYOUT_KEY);
LayoutType layoutType = LayoutType.toLayoutType(layoutName);
if (layoutType == LayoutType.BREADCRUMB) {
switchToBreadcrumbLayout();
} else {
switchToTreeLayout();
}
}
private StructuredSelection maybeSelectFirstLaunchConfig() {
if (WebAppDebugModel.getInstance().getLaunchConfigurations().size() > 0) {
return new StructuredSelection(WebAppDebugModel.getInstance().getLaunchConfigurations()
.get(0));
}
return null;
}
private void switchToBreadcrumbLayout() {
if (currentLayout == breadcrumbLayout) {
return;
}
ISelection selection = null;
if (currentLayout == null) {
// Initializing
selection = maybeSelectFirstLaunchConfig();
} else {
assert (currentLayout == treeLayout);
selection = treeLayout.getSelection();
}
switchToBreadcrumbLayoutAction.setChecked(true);
switchToTreeLayoutAction.setChecked(false);
currentLayout = breadcrumbLayout;
if (selection != null && !selection.isEmpty()) {
currentLayout.setSelection(selection);
}
pageBook.showPage(breadcrumbLayout);
getDialogSettings().put(DIALOG_PROPERTIES_LAYOUT_KEY, LayoutType.BREADCRUMB.name());
}
private void switchToTreeLayout() {
if (currentLayout == treeLayout) {
return;
}
switchToTreeLayoutAction.setChecked(true);
switchToBreadcrumbLayoutAction.setChecked(false);
ISelection selection = null;
if (currentLayout == null) {
// Initializing
selection = maybeSelectFirstLaunchConfig();
} else {
assert (currentLayout == breadcrumbLayout);
selection = breadcrumbLayout.getSelection();
}
currentLayout = treeLayout;
if (selection != null && !selection.isEmpty()) {
currentLayout.setSelection(selection);
}
pageBook.showPage(treeLayout);
getDialogSettings().put(DIALOG_PROPERTIES_LAYOUT_KEY, LayoutType.TREE.name());
}
}