/*******************************************************************************
* Copyright (c) 2000, 2016 QNX Software Systems 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:
* QNX Software Systems - Initial API and implementation
* Wind River Systems - Modified for new DSF Reference Implementation
* Ericsson - Modified for additional features in DSF Reference implementation and bug 219920
* Onur Akdemir (TUBITAK BILGEM-ITI) - Multi-process debugging (Bug 237306)
* Ingenico - Sysroot with spaces (Bug 497693)
*******************************************************************************/
package org.eclipse.cdt.dsf.mi.service.command.commands;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.eclipse.cdt.dsf.datamodel.DMContexts;
import org.eclipse.cdt.dsf.datamodel.IDMContext;
import org.eclipse.cdt.dsf.debug.service.command.ICommand;
import org.eclipse.cdt.dsf.debug.service.command.ICommandResult;
import org.eclipse.cdt.dsf.mi.service.command.MIControlDMContext;
import org.eclipse.cdt.dsf.mi.service.command.output.MIInfo;
import org.eclipse.cdt.dsf.mi.service.command.output.MIOutput;
/**
* Represents any MI command.
*/
public class MICommand<V extends MIInfo> implements ICommand<V> {
private static final String[] empty = {};
List<Adjustable> fOptions = new ArrayList<>();
List<Adjustable> fParameters = new ArrayList<>();
String fOperation = ""; //$NON-NLS-1$
Function<String, Adjustable> fParamToAdjustable;
IDMContext fCtx;
/*
* Constructors.
*/
public MICommand(IDMContext ctx, String operation) {
this(ctx, operation, empty, empty);
}
public MICommand(IDMContext ctx, String operation, String[] params) {
this(ctx, operation, empty, params);
}
public MICommand(IDMContext ctx, String operation, String[] options, String[] params) {
this(ctx, operation, options, params, null);
}
/**
* @since 5.2
*/
public MICommand(IDMContext ctx, String operation, String[] options, String[] params,
Function<String, Adjustable> paramToAdjustable) {
assert (ctx != null && DMContexts.getAncestorOfType(ctx, MIControlDMContext.class) != null);
fCtx = ctx;
fOperation = operation;
fOptions = optionsToAdjustables(options);
fParamToAdjustable = paramToAdjustable == null ? x -> new MIStandardParameterAdjustable(x) : paramToAdjustable;
fParameters = parametersToAdjustables(params);
}
private final List<Adjustable> optionsToAdjustables(String[] options) {
List<Adjustable> result = new ArrayList<>();
if (options != null) {
for (String option : options) {
result.add(new MIStandardOptionAdjustable(option));
}
}
return result;
}
private final List<Adjustable> parametersToAdjustables(String[] parameters) {
return parameters != null ? Arrays.stream(parameters).map(fParamToAdjustable).collect(Collectors.toList())
: Collections.emptyList();
}
public String getCommandControlFilter() {
MIControlDMContext controlDmc = DMContexts.getAncestorOfType(getContext(), MIControlDMContext.class);
return controlDmc.getCommandControlFilter();
}
/**
* Returns the operation of this command.
*/
public String getOperation() {
return fOperation;
}
/**
* Returns an array of command's options. An empty collection is
* returned if there are no options.
*/
public String[] getOptions() {
List<String> result = new ArrayList<>();
for (Adjustable option : fOptions) {
result.add(option.getValue());
}
return result.toArray(new String[fOptions.size()]);
}
public void setOptions(String[] options) {
fOptions = optionsToAdjustables(options);
}
/**
* Returns an array of command's parameters. An empty collection is
* returned if there are no parameters.
*/
public String[] getParameters() {
List<String> result = new ArrayList<>();
for (Adjustable parameter : fParameters) {
result.add(parameter.getValue());
}
return result.toArray(new String[fParameters.size()]);
}
public void setParameters(String[] params) {
fParameters = parametersToAdjustables(params);
}
public void setParameters(Adjustable... params) {
fParameters = Arrays.asList(params);
}
/**
* Returns the constructed command without using the --thread/--frame options.
*/
public String constructCommand() {
return constructCommand(null, -1);
}
/**
* Returns the constructed command potentially using the --thread/--frame options.
*
* @since 1.1
*/
public String constructCommand(String threadId, int frameId) {
return constructCommand(null, threadId, frameId);
}
/**
* With GDB 7.1 the --thread-group option is used to support multiple processes.
*
* @since 4.0
*/
public String constructCommand(String groupId, String threadId, int frameId) {
StringBuilder command = new StringBuilder(getOperation());
// Add the --thread option
if (supportsThreadAndFrameOptions() && threadId != null && !threadId.trim().isEmpty()) {
command.append(" --thread ").append(threadId); //$NON-NLS-1$
// Add the --frame option, but only if we are using the --thread option
if (frameId >= 0) {
command.append(" --frame ").append(frameId); //$NON-NLS-1$
}
} else if (supportsThreadGroupOption() && groupId != null && !groupId.trim().isEmpty()) {
// The --thread-group option is only allowed if we are not using the --thread option
command.append(" --thread-group ").append(groupId); //$NON-NLS-1$
}
String opt = optionsToString();
if (!opt.isEmpty()) {
command.append(' ').append(opt);
}
String p = parametersToString();
if (!p.isEmpty()) {
command.append(' ').append(p);
}
command.append('\n');
return command.toString();
}
// /*
// * Checks to see if the current command can be coalesced with the
// * supplied command.
// */
// public boolean canCoalesce(ICommand<? extends ICommandResult> command) {
// return false;
// }
@Override
public ICommand<? extends ICommandResult> coalesceWith(ICommand<? extends ICommandResult> command) {
return null;
}
@Override
public IDMContext getContext() {
return fCtx;
}
/**
* Produces the corresponding ICommandResult result for this
* command.
*
* @return result for this command
*/
public MIInfo getResult(MIOutput MIresult) {
return new MIInfo(MIresult);
}
protected String optionsToString() {
StringBuilder sb = new StringBuilder();
if (fOptions != null && !fOptions.isEmpty()) {
for (Adjustable option : fOptions) {
sb.append(option.getAdjustedValue());
}
}
return sb.toString().trim();
}
protected String parametersToString() {
String[] options = getOptions();
StringBuilder buffer = new StringBuilder();
if (fParameters != null && !fParameters.isEmpty()) {
// According to GDB/MI spec
// Add a "--" separator if any parameters start with "-"
if (options != null && options.length > 0) {
for (Adjustable parameter : fParameters) {
if (parameter.getValue().startsWith("-")) { //$NON-NLS-1$
buffer.append('-').append('-');
break;
}
}
}
for (Adjustable parameter : fParameters) {
buffer.append(' ').append(parameter.getAdjustedValue());
}
}
return buffer.toString().trim();
}
protected static boolean containsWhitespace(String s) {
for (int i = 0; i < s.length(); i++) {
if (Character.isWhitespace(s.charAt(i))) {
return true;
}
}
return false;
}
/**
* @since 1.1
*/
public boolean supportsThreadAndFrameOptions() {
return true;
}
/**
* @since 4.0
*/
public boolean supportsThreadGroupOption() {
return true;
}
/**
* Compares commands based on the MI command string that they generate,
* without the token.
*/
@Override
public boolean equals(Object obj) {
if (obj instanceof MICommand<?>) {
MICommand<?> otherCmd = (MICommand<?>) obj;
return ((fCtx == null && otherCmd.fCtx == null) || (fCtx != null && fCtx.equals(otherCmd.fCtx))) &&
constructCommand().equals(otherCmd.constructCommand());
}
return false;
}
@Override
public int hashCode() {
return constructCommand().hashCode();
}
@Override
public String toString() {
return constructCommand();
}
public static class MIStandardOptionAdjustable extends MICommandAdjustable {
public MIStandardOptionAdjustable(String option) {
super(option);
}
@Override
public String getAdjustedValue() {
StringBuilder builder = new StringBuilder();
String option = value;
// If the option argument contains " or \ it must be escaped
if (option.indexOf('"') != -1 || option.indexOf('\\') != -1) {
StringBuilder buf = new StringBuilder();
for (int j = 0; j < option.length(); j++) {
char c = option.charAt(j);
if (c == '"' || c == '\\') {
buf.append('\\');
}
buf.append(c);
}
option = buf.toString();
}
// If the option contains a space according to
// GDB/MI spec we must surround it with double quotes.
if (option.indexOf('\t') != -1 || option.indexOf(' ') != -1) {
builder.append(' ').append('"').append(option).append('"');
} else {
builder.append(' ').append(option);
}
return builder.toString();
}
}
public static class MIStandardParameterAdjustable extends
MICommandAdjustable {
public MIStandardParameterAdjustable(String parameter) {
super(parameter);
}
@Override
public String getAdjustedValue() {
StringBuilder builder = new StringBuilder();
for (int j = 0; j < value.length(); j++) {
char c = value.charAt(j);
if (c == '"' || c == '\\') {
builder.append('\\');
}
builder.append(c);
}
// If the string contains spaces instead of escaping
// surround the parameter with double quotes.
if (containsWhitespace(value)) {
builder.insert(0, '"');
builder.append('"');
}
// Although this change makes sense, it could have impacts on many
// different commands we send to GDB. The risk outweighs the benefits,
// so we comment it out. See bugs 412471 and 414959 for details.
//
// // an empty parameter can be passed with two single quotes
// if (builder.length() == 0) {
// builder.append("''"); //$NON-NLS-1$
// }
//
return builder.toString();
}
}
/**
* @since 5.2
*/
public static class MINoChangeAdjustable extends MICommandAdjustable {
public MINoChangeAdjustable(String param) {
super(param);
}
@Override
public String getAdjustedValue() {
return getValue();
}
}
public static abstract class MICommandAdjustable implements Adjustable {
protected final String value;
/**
* Creates a new instance.
*
* @param builder
* The string builder is an optimization option, if two
* commands are not processed at the same time a shared
* builder can be used to save memory.
* @param value
* The value that should be adjusted.
*/
public MICommandAdjustable(String value) {
this.value = value;
}
@Override
public String getValue() {
return value;
}
}
}