// Copyright (c) 2009 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.debug.ui.launcher;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.chromium.debug.core.ChromiumDebugPlugin;
import org.chromium.debug.core.ChromiumSourceDirector;
import org.chromium.debug.core.SourceNameMapperContainer;
import org.chromium.debug.core.model.BreakpointSynchronizer.Direction;
import org.chromium.debug.core.model.LaunchParams;
import org.chromium.debug.core.model.LaunchParams.LookupMode;
import org.chromium.debug.core.model.LaunchParams.ValueConverter;
import org.chromium.debug.ui.launcher.LaunchTabGroup.Params;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.debug.core.model.ISourceLocator;
import org.eclipse.debug.core.sourcelookup.IPersistableSourceLocator2;
import org.eclipse.debug.core.sourcelookup.ISourceContainer;
import org.eclipse.debug.core.sourcelookup.ISourceLookupDirector;
import org.eclipse.debug.internal.ui.SWTFactory;
import org.eclipse.debug.ui.DebugUITools;
import org.eclipse.debug.ui.IDebugUIConstants;
import org.eclipse.jface.preference.BooleanFieldEditor;
import org.eclipse.jface.preference.FieldEditor;
import org.eclipse.jface.preference.IntegerFieldEditor;
import org.eclipse.jface.preference.PreferenceStore;
import org.eclipse.jface.preference.StringFieldEditor;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Group;
/**
* The "Remote" tab for the Chromium JavaScript launch tab group.
* @param <ELEMENTS> type used for access to created dialog elements; it is used internally
* and allows to subclass {@link ChromiumRemoteTab} with additional dialog controls
*/
public abstract class ChromiumRemoteTab<ELEMENTS> extends TabBase<ELEMENTS, Params> {
private static final String HOST_FIELD_NAME = "host_field"; //$NON-NLS-1$
private static final String PORT_FIELD_NAME = "port_field"; //$NON-NLS-1$
private static final String ADD_NETWORK_CONSOLE_FIELD_NAME =
"add_network_console_field"; //$NON-NLS-1$
// However, recommended range is [1024, 32767].
private static final int minimumPortValue = 0;
private static final int maximumPortValue = 65535;
private final SourceContainerChecker sourceContainerChecker = new SourceContainerChecker();
/**
* Possibly checks host property in config.
*/
public static abstract class HostChecker {
public abstract String getWarning(ILaunchConfiguration config) throws CoreException;
public static HostChecker LOCAL_ONLY = new HostChecker() {
@Override
public String getWarning(ILaunchConfiguration config) throws CoreException {
String host = config.getAttribute(LaunchParams.CHROMIUM_DEBUG_HOST, ""); //$NON-NLS-1$
if (!LOCAL_HOST_NAMES.contains(host.toLowerCase())) {
return Messages.ChromiumRemoteTab_CONNECTION_FROM_LOCALHOST_WARNING;
}
return null;
}
};
private static final Collection<String> LOCAL_HOST_NAMES =
Arrays.asList("localhost", "127.0.0.1"); //$NON-NLS-1$ //$NON-NLS-2$
}
ChromiumRemoteTab(Params params) {
super(params);
}
@Override
protected ELEMENTS createElements(Composite parent, Runnable modifyListener) {
Composite composite = createDefaultComposite(parent);
setControl(composite);
PreferenceStore store = new PreferenceStore();
composite.setFont(parent.getFont());
return createDialogElements(composite, modifyListener, store);
}
protected abstract ELEMENTS createDialogElements(Composite composite,
Runnable modifyListener, PreferenceStore store);
protected static TabElements createBasicTabElements(Composite composite,
final Runnable modifyListener, PreferenceStore store, Params params) {
final StringFieldEditor debugHost;
final IntegerFieldEditor debugPort;
final BooleanFieldEditor addNetworkConsole;
{
Group connectionGroup = new Group(composite, 0);
connectionGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
connectionGroup.setText(Messages.ChromiumRemoteTab_CONNECTION_GROUP);
connectionGroup.setLayout(new GridLayout(1, false));
IPropertyChangeListener propertyModifyListener = new IPropertyChangeListener() {
public void propertyChange(PropertyChangeEvent event) {
modifyListener.run();
}
};
Composite propertiesComp = createInnerComposite(connectionGroup, 2);
// Host text field
debugHost = new StringFieldEditor(HOST_FIELD_NAME,
Messages.ChromiumRemoteTab_HostLabel, propertiesComp);
debugHost.setPropertyChangeListener(propertyModifyListener);
debugHost.setPreferenceStore(store);
// Port text field
debugPort = new IntegerFieldEditor(PORT_FIELD_NAME,
Messages.ChromiumRemoteTab_PortLabel, propertiesComp);
debugPort.setPropertyChangeListener(propertyModifyListener);
debugPort.setPreferenceStore(store);
addNetworkConsole =
new BooleanFieldEditor(ADD_NETWORK_CONSOLE_FIELD_NAME,
Messages.ChromiumRemoteTab_ShowDebuggerNetworkCommunication, propertiesComp);
addNetworkConsole.setPreferenceStore(store);
addNetworkConsole.setPropertyChangeListener(propertyModifyListener);
}
return new TabElements() {
@Override public StringFieldEditor getHost() {
return debugHost;
}
@Override public IntegerFieldEditor getPort() {
return debugPort;
}
@Override public BooleanFieldEditor getAddNetworkConsole() {
return addNetworkConsole;
}
};
}
public String getName() {
return Messages.ChromiumRemoteTab_RemoteTabName;
}
@Override
protected MessageData isValidImpl(ILaunchConfiguration config) throws CoreException {
int port = config.getAttribute(LaunchParams.CHROMIUM_DEBUG_PORT, -1);
if (port < minimumPortValue || port > maximumPortValue) {
return new MessageData(false, Messages.ChromiumRemoteTab_InvalidPortNumberError);
}
String message = getWarning(config);
return new MessageData(true, message);
}
/**
* Checks config for warnings and returns first found or null.
*/
protected String getWarning(ILaunchConfiguration config) throws CoreException {
HostChecker hostChecker = getParams().getHostChecker();
if (hostChecker != null) {
String hostWarning = hostChecker.getWarning(config);
if (hostWarning != null) {
return hostWarning;
}
}
return sourceContainerChecker.check(config);
}
@Override
public Image getImage() {
return DebugUITools.getImage(IDebugUIConstants.IMG_LCL_DISCONNECT);
}
interface TabElements {
StringFieldEditor getHost();
IntegerFieldEditor getPort();
BooleanFieldEditor getAddNetworkConsole();
}
static final TabFieldList<TabElements, Params> BASIC_TAB_FIELDS;
static {
List<TabField<?, ?, ? super TabElements, Params>> list =
new ArrayList<ChromiumRemoteTab.TabField<?, ?, ? super TabElements, Params>>(4);
list.add(new TabField<String, String, TabElements, Params>(
LaunchParams.CHROMIUM_DEBUG_HOST, TypedMethods.STRING,
new FieldEditorAccess<String, TabElements>(TypedMethods.STRING) {
@Override
FieldEditor getFieldEditor(TabElements tabElements) {
return tabElements.getHost();
}
String getEditorErrorValue() {
return ""; //$NON-NLS-1$
}
},
new DefaultsProvider<String, Params>() {
@Override String getFallbackValue() {
return PluginVariablesUtil.getValue(PluginVariablesUtil.DEFAULT_HOST);
}
@Override String getInitialConfigValue(Params context) {
return getFallbackValue();
}
},
ValueConverter.<String>getTrivial()));
list.add(new TabField<Integer, Integer, TabElements, Params>(
LaunchParams.CHROMIUM_DEBUG_PORT, TypedMethods.INT,
new FieldEditorAccess<Integer, TabElements>(TypedMethods.INT) {
@Override
FieldEditor getFieldEditor(TabElements tabElements) {
return tabElements.getPort();
}
String getEditorErrorValue() {
return "-1"; //$NON-NLS-1$
}
},
new DefaultsProvider<Integer, Params>() {
@Override Integer getFallbackValue() {
return PluginVariablesUtil.getValueAsInt(PluginVariablesUtil.DEFAULT_PORT);
}
@Override Integer getInitialConfigValue(Params context) {
return getFallbackValue();
}
},
ValueConverter.<Integer>getTrivial()));
list.add(new TabField<Boolean, Boolean, TabElements, Params>(
LaunchParams.ADD_NETWORK_CONSOLE, TypedMethods.BOOL,
new FieldEditorAccess<Boolean, TabElements>(TypedMethods.BOOL) {
FieldEditor getFieldEditor(TabElements tabElements) {
return tabElements.getAddNetworkConsole();
}
String getEditorErrorValue() {
return ""; //$NON-NLS-1$
}
},
new DefaultsProvider<Boolean, Params>() {
@Override Boolean getFallbackValue() {
return false;
}
@Override Boolean getInitialConfigValue(Params context) {
return null;
}
},
ValueConverter.<Boolean>getTrivial()));
BASIC_TAB_FIELDS = createFieldListImpl(list);
}
private static class SourceContainerChecker {
public String check(ILaunchConfiguration config) {
LookupMode lookupMode;
try {
lookupMode = ChromiumSourceDirector.readLookupMode(config);
} catch (CoreException e) {
ChromiumDebugPlugin.log(e);
return null;
}
if (lookupMode != LookupMode.AUTO_DETECT) {
return null;
}
ISourceLookupDirector director;
try {
director = read(config);
} catch (CoreException e) {
return null;
}
if (director == null) {
return null;
}
for (ISourceContainer sourceContainer : director.getSourceContainers()) {
if (sourceContainer instanceof SourceNameMapperContainer) {
return Messages.ChromiumRemoteTab_AUTO_DETECT_CONTAINER_WARNING;
}
}
return null;
}
private ISourceLookupDirector read(ILaunchConfiguration config) throws CoreException {
String memento = config.getAttribute(
ILaunchConfiguration.ATTR_SOURCE_LOCATOR_MEMENTO, (String)null);
if (memento == null) {
return null;
}
String type = config.getAttribute(ILaunchConfiguration.ATTR_SOURCE_LOCATOR_ID, (String)null);
if (type == null) {
type = config.getType().getSourceLocatorId();
}
ILaunchManager launchManager = DebugPlugin.getDefault().getLaunchManager();
ISourceLocator locator = launchManager.newSourceLocator(type);
if (locator instanceof IPersistableSourceLocator2 == false) {
return null;
}
ISourceLookupDirector director = (ISourceLookupDirector) locator;
director.initializeFromMemento(memento, config);
return director;
}
}
static abstract class DevToolsProtocolBase extends ChromiumRemoteTab<DevToolsElements> {
DevToolsProtocolBase(Params params) {
super(params);
}
@Override
protected DevToolsElements createDialogElements(Composite composite,
final Runnable modifyListener, PreferenceStore store) {
final TabElements basicElements =
createBasicTabElements(composite, modifyListener, store, getParams());
RadioButtonsLogic.Listener radioButtonsListener =
new RadioButtonsLogic.Listener() {
public void selectionChanged() {
modifyListener.run();
}
};
final RadioButtonsLogic<Integer> breakpointRadioButtons;
{
Group breakpointGroup = new Group(composite, 0);
breakpointGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
breakpointGroup.setText(Messages.ChromiumRemoteTab_BREAKPOINT_GROUP);
breakpointGroup.setLayout(new GridLayout(1, false));
Map<Integer, Button> buttonMap = new LinkedHashMap<Integer, Button>(3);
for (LaunchParams.BreakpointOption option : LaunchParams.BREAKPOINT_OPTIONS) {
Button button = new Button(breakpointGroup, SWT.RADIO);
button.setFont(composite.getFont());
button.setText(option.getLabel());
GridData gd = new GridData();
button.setLayoutData(gd);
SWTFactory.setButtonDimensionHint(button);
int index = buttonMap.size();
buttonMap.put(index, button);
}
breakpointRadioButtons =
new RadioButtonsLogic<Integer>(buttonMap, radioButtonsListener);
}
return new DevToolsElements() {
@Override
public RadioButtonsLogic<Integer> getBreakpointRadioButtons() {
return breakpointRadioButtons;
}
@Override
public TabElements getBase() {
return basicElements;
}
};
}
@Override
protected TabFieldList<? super DevToolsElements, ? super Params> getTabFields() {
return TAB_FIELD_LIST;
}
private static final TabFieldList<? super DevToolsElements, ? super Params> TAB_FIELD_LIST;
static {
TabField<String, String, DevToolsElements, Params> breakpointSyncField =
new TabField<String, String, DevToolsElements, Params>(
LaunchParams.BREAKPOINT_SYNC_DIRECTION, TypedMethods.STRING,
new FieldAccess<String, DevToolsElements>() {
@Override
void setValue(String value, DevToolsElements tabElements) {
int breakpointOptionIndex = LaunchParams.findBreakpointOption(value);
tabElements.getBreakpointRadioButtons().select(breakpointOptionIndex);
}
@Override
String getValue(DevToolsElements tabElements) {
int breakpointOption = tabElements.getBreakpointRadioButtons().getSelected();
return LaunchParams.BREAKPOINT_OPTIONS.get(breakpointOption)
.getDirectionStringValue();
}
},
new DefaultsProvider<String, Params>() {
@Override String getFallbackValue() {
return Direction.MERGE.toString();
}
@Override String getInitialConfigValue(Params context) {
return null;
}
},
ValueConverter.<String>getTrivial());
List<TabFieldList<? super DevToolsElements, ? super Params>> subLists =
new ArrayList<TabBase.TabFieldList<? super DevToolsElements,? super Params>>(2);
subLists.add(createFieldListAdapting(BASIC_TAB_FIELDS,
new Adapter<DevToolsElements, TabElements>() {
@Override
public TabElements get(DevToolsElements from) {
return from.getBase();
}
}));
subLists.add(createFieldListImpl(Collections.singletonList(breakpointSyncField)));
TAB_FIELD_LIST = createCompositeFieldList(subLists);
}
}
interface DevToolsElements {
RadioButtonsLogic<Integer> getBreakpointRadioButtons();
TabElements getBase();
}
static class DevToolsProtocol extends DevToolsProtocolBase {
public DevToolsProtocol() {
super(PARAMS);
}
private static final Params PARAMS = new Params(
ChromiumRemoteTab.HostChecker.LOCAL_ONLY,
Messages.ChromiumRemoteTab_URL, false);
}
static class Standalone extends DevToolsProtocolBase {
public Standalone() {
super(PARAMS);
}
private static final Params PARAMS = new Params(null,
Messages.ChromiumRemoteTab_FILE_PATH, true);
}
}