blob: ebbbb0f906b6de74fe8768727b351a421f42d4a3 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2016 Wind River Systems, Inc. and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Ted R Williams (Wind River Systems, Inc.) - initial implementation
*******************************************************************************/
package org.eclipse.cdt.debug.ui.memory.search;
import java.lang.reflect.Method;
import java.math.BigInteger;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.Vector;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.model.IMemoryBlockExtension;
import org.eclipse.debug.core.model.MemoryByte;
import org.eclipse.debug.ui.memory.IMemoryRendering;
import org.eclipse.debug.ui.memory.IMemoryRenderingContainer;
import org.eclipse.debug.ui.memory.IMemoryRenderingSite;
import org.eclipse.debug.ui.memory.IRepositionableMemoryRendering;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.search.ui.ISearchQuery;
import org.eclipse.search.ui.ISearchResult;
import org.eclipse.search.ui.NewSearchUI;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.dialogs.SelectionDialog;
public class FindReplaceDialog extends SelectionDialog {
private IMemoryBlockExtension fMemoryBlock;
final static int preFetchSize = 20 * 1024;
private Text fFindText;
private Text fReplaceText;
private Combo fStartText;
private Combo fEndText;
private Button fFindButton;
private Button fFindAllButton;
private Button fReplaceButton;
private Button fReplaceFindButton;
private Button fReplaceAllButton;
private IMemoryRenderingSite fMemoryView;
private Button fFormatAsciiButton;
private Button fFormatHexButton;
private Button fFormatOctalButton;
private Button fFormatBinaryButton;
private Button fFormatDecimalButton;
private Button fFormatByteSequenceButton;
private Button fCaseInSensitiveCheckbox;
private Button fWrapCheckbox;
private Button fForwardButton;
private Properties fProperties;
protected final static String SEARCH_FIND = "SEARCH_FIND"; //$NON-NLS-1$
protected final static String SEARCH_REPLACE = "SEARCH_REPLACE"; //$NON-NLS-1$
protected final static String SEARCH_START = "SEARCH_START"; //$NON-NLS-1$
protected final static String SEARCH_END = "SEARCH_END"; //$NON-NLS-1$
protected final static String SEARCH_LAST_START = "SEARCH_LAST_START"; //$NON-NLS-1$
protected final static String SEARCH_LAST_END = "SEARCH_LAST_END"; //$NON-NLS-1$
protected final static String SEARCH_FORMAT = "SEARCH_FORMAT"; //$NON-NLS-1$
protected final static String SEARCH_FORMAT_ASCII = "SEARCH_FORMAT_ASCII"; //$NON-NLS-1$
protected final static String SEARCH_FORMAT_HEX = "SEARCH_FORMAT_HEX"; //$NON-NLS-1$
protected final static String SEARCH_FORMAT_OCTAL = "SEARCH_FORMAT_OCTAL"; //$NON-NLS-1$
protected final static String SEARCH_FORMAT_BINARY = "SEARCH_FORMAT_BINARY"; //$NON-NLS-1$
protected final static String SEARCH_FORMAT_DECIMAL = "SEARCH_FORMAT_DECIMAL"; //$NON-NLS-1$
protected final static String SEARCH_FORMAT_BYTESEQUENCE = "SEARCH_FORMAT_BYTESEQUENCE"; //$NON-NLS-1$
protected final static String SEARCH_FORMAT_CASEINSENSTIVE = "SEARCH_FORMAT_CASEINSENSTIVE"; //$NON-NLS-1$
protected final static String SEARCH_FORMAT_FORWARD = "SEARCH_FORMAT_FORWARD"; //$NON-NLS-1$
protected final static String SEARCH_FORMAT_WRAP = "SEARCH_FORMAT_WRAP"; //$NON-NLS-1$
protected final static String SEARCH_ENABLE_FIND_NEXT = "SEARCH_ENABLE_FIND_NEXT"; //$NON-NLS-1$
//the width of text fields of Find and Replace, increase it to 400 to fix the tvt defect 356901
protected final static int FIND_REPLACE_TEXT_WIDTH = 400;
private IAction fFindAction = null;
public FindReplaceDialog(Shell parent, IMemoryBlockExtension memoryBlock, IMemoryRenderingSite memoryView,
Properties properties, IAction findAction) {
super(parent);
super.setTitle(Messages.getString("FindReplaceDialog.Title")); //$NON-NLS-1$
setShellStyle(getShellStyle() | SWT.RESIZE);
fMemoryBlock = memoryBlock;
fMemoryView = memoryView;
fProperties = properties;
this.setBlockOnOpen(false);
fFindAction = findAction;
}
private BigInteger getUserStart() {
String start = fStartText.getText();
if (start.toUpperCase().startsWith("0X")) //$NON-NLS-1$
start = start.substring(2);
return new BigInteger(start, 16);
}
private BigInteger getUserEnd() {
String end = fEndText.getText();
if (end.toUpperCase().startsWith("0X")) //$NON-NLS-1$
end = end.substring(2);
return new BigInteger(end, 16);
}
private boolean getIsDirectionForward() {
return fForwardButton.getSelection();
}
private SearchPhrase getSearchPhrase() {
SearchPhrase phrase = null;
if (fFormatAsciiButton.getSelection()) {
phrase = new AsciiSearchPhrase(fFindText.getText(), fCaseInSensitiveCheckbox.getSelection());
} else if (fFormatHexButton.getSelection()) {
phrase = new BigIntegerSearchPhrase(new BigInteger(fFindText.getText().toUpperCase().startsWith("0X") //$NON-NLS-1$
? fFindText.getText().substring(2)
: fFindText.getText(), 16), 16);
} else if (fFormatOctalButton.getSelection()) {
phrase = new BigIntegerSearchPhrase(new BigInteger(fFindText.getText().startsWith("0") //$NON-NLS-1$
? fFindText.getText().substring(1)
: fFindText.getText(), 8), 8);
} else if (fFormatBinaryButton.getSelection()) {
phrase = new BigIntegerSearchPhrase(new BigInteger(fFindText.getText().toUpperCase().startsWith("0B") //$NON-NLS-1$
? fFindText.getText().substring(2)
: fFindText.getText(), 2), 2);
} else if (fFormatDecimalButton.getSelection()) {
phrase = new BigIntegerSearchPhrase(new BigInteger(fFindText.getText(), 10), 10);
} else if (fFormatByteSequenceButton.getSelection()) {
phrase = new ByteSequenceSearchPhrase(fFindText.getText());
}
return phrase;
}
protected byte[] parseByteSequence(String s) {
Vector<Byte> sequence = new Vector<>();
StringTokenizer st = new StringTokenizer(s, " "); //$NON-NLS-1$
while (st.hasMoreElements()) {
String element = ((String) st.nextElement()).trim();
if (element.length() > 0) {
BigInteger value;
if (element.toUpperCase().startsWith("0X")) //$NON-NLS-1$
value = new BigInteger(element.substring(2), 16);
else if (element.toUpperCase().startsWith("0B")) //$NON-NLS-1$
value = new BigInteger(element.substring(2), 2);
else if (element.toUpperCase().startsWith("0")) //$NON-NLS-1$
value = new BigInteger(element.substring(1), 8);
else
value = new BigInteger(element, 10);
Byte b = value.byteValue();
if (value.compareTo(BigInteger.valueOf(255)) > 0)
return null;
sequence.addElement(b);
}
}
Byte seq[] = sequence.toArray(new Byte[sequence.size()]);
byte[] bytes = new byte[seq.length];
for (int i = 0; i < seq.length; i++)
bytes[i] = seq[i].byteValue();
return bytes;
}
private byte[] getReplaceData() {
if (fFormatAsciiButton.getSelection())
return fReplaceText.getText().getBytes();
else if (fFormatHexButton.getSelection())
return removeZeroPrefixByte(new BigInteger(
fReplaceText.getText().toUpperCase().startsWith("0X") ? fReplaceText.getText().substring(2) //$NON-NLS-1$
: fReplaceText.getText(),
16).toByteArray());
else if (fFormatOctalButton.getSelection())
return removeZeroPrefixByte(
new BigInteger(fReplaceText.getText().startsWith("0") ? fReplaceText.getText().substring(1) //$NON-NLS-1$
: fReplaceText.getText(), 8).toByteArray());
else if (fFormatBinaryButton.getSelection())
return removeZeroPrefixByte(new BigInteger(
fReplaceText.getText().toUpperCase().startsWith("0B") ? fReplaceText.getText().substring(2) //$NON-NLS-1$
: fReplaceText.getText(),
2).toByteArray());
else if (fFormatDecimalButton.getSelection())
return removeZeroPrefixByte(new BigInteger(fReplaceText.getText(), 10).toByteArray());
else if (fFormatByteSequenceButton.getSelection())
return parseByteSequence(fReplaceText.getText());
return new byte[0];
}
/* (non-Javadoc)
* @see org.eclipse.jface.dialogs.Dialog#createButtonsForButtonBar(org.eclipse.swt.widgets.Composite)
*/
@Override
protected void createButtonsForButtonBar(Composite parent) {
fFindButton = createButton(parent, 10, Messages.getString("FindReplaceDialog.ButtonFind"), true); //$NON-NLS-1$
fFindButton.addSelectionListener(new SelectionListener() {
@Override
public void widgetDefaultSelected(SelectionEvent e) {
}
@Override
public void widgetSelected(SelectionEvent e) {
performFind(getUserStart(), getUserEnd(), getSearchPhrase(), getIsDirectionForward(), null, false,
false);
cancelPressed();
}
});
fFindAllButton = createButton(parent, 10, Messages.getString("FindReplaceDialog.ButtonFindAll"), true); //$NON-NLS-1$
fFindAllButton.addSelectionListener(new SelectionListener() {
@Override
public void widgetDefaultSelected(SelectionEvent e) {
}
@Override
public void widgetSelected(SelectionEvent e) {
performFind(getUserStart(), getUserEnd(), getSearchPhrase(), getIsDirectionForward(), null, true,
false);
cancelPressed();
}
});
fReplaceFindButton = createButton(parent, 11, Messages.getString("FindReplaceDialog.ButtonReplaceFind"), false); //$NON-NLS-1$
fReplaceFindButton.addSelectionListener(new SelectionListener() {
@Override
public void widgetDefaultSelected(SelectionEvent e) {
}
@Override
public void widgetSelected(SelectionEvent e) {
performFind(getUserStart(), getUserEnd(), getSearchPhrase(), getIsDirectionForward(), getReplaceData(),
false, true);
cancelPressed();
}
});
fReplaceButton = createButton(parent, 12, Messages.getString("FindReplaceDialog.ButtonReplace"), false); //$NON-NLS-1$
fReplaceButton.addSelectionListener(new SelectionListener() {
@Override
public void widgetDefaultSelected(SelectionEvent e) {
}
@Override
public void widgetSelected(SelectionEvent e) {
performFind(getUserStart(), getUserEnd(), getSearchPhrase(), getIsDirectionForward(), getReplaceData(),
false, false);
cancelPressed();
}
});
fReplaceAllButton = createButton(parent, 13, Messages.getString("FindReplaceDialog.ButtonReplaceAll"), false); //$NON-NLS-1$
fReplaceAllButton.addSelectionListener(new SelectionListener() {
@Override
public void widgetDefaultSelected(SelectionEvent e) {
}
@Override
public void widgetSelected(SelectionEvent e) {
performFind(getUserStart(), getUserEnd(), getSearchPhrase(), getIsDirectionForward(), getReplaceData(),
true, false);
cancelPressed();
}
});
createButton(parent, IDialogConstants.CANCEL_ID, Messages.getString("FindReplaceDialog.Close"), false); //$NON-NLS-1$
((GridLayout) parent.getLayout()).numColumns = 2;
validate();
}
/* (non-Javadoc)
* @see org.eclipse.ui.dialogs.SelectionDialog#getResult()
*/
@Override
public Object[] getResult() {
Object[] results = super.getResult();
if (results != null) {
return results;
}
return new Object[0];
}
/* (non-Javadoc)
* @see org.eclipse.jface.dialogs.Dialog#cancelPressed()
*/
@Override
protected void cancelPressed() {
fProperties.setProperty(SEARCH_FIND, fFindText.getText());
fProperties.setProperty(SEARCH_REPLACE, fReplaceText.getText());
fProperties.setProperty(SEARCH_START, fStartText.getText());
fProperties.setProperty(SEARCH_END, fEndText.getText());
if (fFormatAsciiButton.getSelection())
fProperties.setProperty(SEARCH_FORMAT, SEARCH_FORMAT_ASCII);
else if (fFormatBinaryButton.getSelection())
fProperties.setProperty(SEARCH_FORMAT, SEARCH_FORMAT_BINARY);
else if (fFormatByteSequenceButton.getSelection())
fProperties.setProperty(SEARCH_FORMAT, SEARCH_FORMAT_BYTESEQUENCE);
else if (fFormatDecimalButton.getSelection())
fProperties.setProperty(SEARCH_FORMAT, SEARCH_FORMAT_DECIMAL);
else if (fFormatHexButton.getSelection())
fProperties.setProperty(SEARCH_FORMAT, SEARCH_FORMAT_HEX);
else if (fFormatOctalButton.getSelection())
fProperties.setProperty(SEARCH_FORMAT, SEARCH_FORMAT_OCTAL);
fProperties.setProperty(SEARCH_FORMAT_FORWARD, Boolean.toString(fForwardButton.getSelection()));
fProperties.setProperty(SEARCH_FORMAT_CASEINSENSTIVE,
Boolean.toString(fCaseInSensitiveCheckbox.getSelection()));
fProperties.setProperty(SEARCH_FORMAT_WRAP, Boolean.toString(fWrapCheckbox.getSelection()));
fProperties.setProperty(SEARCH_ENABLE_FIND_NEXT, Boolean.FALSE.toString());
setResult(null);
super.cancelPressed();
}
/* (non-Javadoc)
* @see org.eclipse.jface.dialogs.Dialog#okPressed()
*/
@Override
protected void okPressed() {
setSelectionResult(new Object[] { fProperties });
super.okPressed();
}
public BigInteger getEndAddress() {
String text = fEndText.getText();
boolean hex = text.startsWith("0x"); //$NON-NLS-1$
BigInteger endAddress = new BigInteger(hex ? text.substring(2) : text, hex ? 16 : 10);
return endAddress;
}
public BigInteger getStartAddress() {
String text = fStartText.getText();
boolean hex = text.startsWith("0x"); //$NON-NLS-1$
BigInteger startAddress = new BigInteger(hex ? text.substring(2) : text, hex ? 16 : 10);
return startAddress;
}
private void validate() {
boolean valid = false;
boolean replaceValid = false;
try {
BigInteger endAddress = getEndAddress();
BigInteger startAddress = getStartAddress();
/*
* The end-address must be larger that the start-address.
*/
if (startAddress.compareTo(endAddress) == -1) {
/*
* Validate the search phrase.
*/
if (getSearchPhrase() != null && getSearchPhrase().getByteLength() > 0) {
valid = true;
}
/*
* Validate the replacement phrase.
*/
if (getReplaceData() != null && getReplaceData().length > 0) {
replaceValid = true;
}
}
} catch (Throwable ex) {
// do nothing
}
fFindButton.setEnabled(valid);
fFindAllButton.setEnabled(valid);
fReplaceButton.setEnabled(replaceValid);
fReplaceFindButton.setEnabled(replaceValid);
fReplaceAllButton.setEnabled(replaceValid);
}
private String pad(int characterCount, String value) {
StringBuilder sb = new StringBuilder(value);
for (int i = 0; i < characterCount - value.length(); i++)
sb.insert(0, "0"); //$NON-NLS-1$
return sb.toString();
}
private String[] removeNullElements(String strings[]) {
Vector<String> nonNullStrings = new Vector<>();
for (String string : strings)
if (string != null)
nonNullStrings.addElement(string);
return nonNullStrings.toArray(new String[0]);
}
private String getMemoryBlockBaseAddress() {
BigInteger base = null;
try {
base = fMemoryBlock.getBigBaseAddress();
} catch (DebugException de) {
// do nothing
}
if (base == null)
base = BigInteger.ZERO;
return "0x" + pad(getAddressSize() * 2, base.toString(16).toUpperCase()); //$NON-NLS-1$
}
private String getViewportStart() {
ISelection selection = fMemoryView.getMemoryRenderingContainers()[0].getMemoryRenderingSite().getSite()
.getSelectionProvider().getSelection();
if (selection instanceof StructuredSelection) {
if (((StructuredSelection) selection).getFirstElement() instanceof IRepositionableMemoryRendering) {
((IRepositionableMemoryRendering) ((StructuredSelection) selection).getFirstElement())
.getSelectedAddress();
}
}
return null;
}
private String getStart() {
BigInteger start = null;
try {
start = fMemoryBlock.getMemoryBlockStartAddress();
} catch (DebugException de) {
// do nothing
}
if (start == null)
start = BigInteger.ZERO;
return "0x" + pad(getAddressSize() * 2, start.toString(16).toUpperCase()); //$NON-NLS-1$
}
private String getEnd() {
BigInteger end = null;
try {
end = fMemoryBlock.getMemoryBlockEndAddress();
} catch (DebugException de) {
// do nothing
}
if (end == null) {
end = BigInteger.ZERO;
for (int i = getAddressSize(); i > 0; i--) {
end = end.shiftLeft(8);
end = end.or(BigInteger.valueOf(255));
}
}
return "0x" + pad(getAddressSize() * 2, end.toString(16).toUpperCase()); //$NON-NLS-1$
}
private int getAddressSize() {
int addressSize;
try {
addressSize = fMemoryBlock.getAddressSize();
} catch (DebugException de) {
addressSize = 4; // default to 32bit?
}
return addressSize;
}
/* (non-Javadoc)
* @see org.eclipse.jface.dialogs.Dialog#createDialogArea(org.eclipse.swt.widgets.Composite)
*/
@Override
protected Control createDialogArea(Composite parent) {
PlatformUI.getWorkbench().getHelpSystem().setHelp(parent,
MemorySearchPlugin.getUniqueIdentifier() + ".FindReplaceDialog_context"); //$NON-NLS-1$
Composite composite = new Composite(parent, SWT.NONE);
FormLayout formLayout = new FormLayout();
formLayout.spacing = 5;
formLayout.marginWidth = formLayout.marginHeight = 9;
composite.setLayout(formLayout);
// find
Label findLabel = new Label(composite, SWT.NONE);
Label replaceLabel = new Label(composite, SWT.NONE);
fReplaceText = new Text(composite, SWT.BORDER);
findLabel.setText(Messages.getString("FindReplaceDialog.LabelFind")); //$NON-NLS-1$
fFindText = new Text(composite, SWT.BORDER);
FormData data = new FormData();
data.left = new FormAttachment(fReplaceText, 0, SWT.LEFT);
data.width = FIND_REPLACE_TEXT_WIDTH;
fFindText.setLayoutData(data);
fFindText.setText(fProperties.getProperty(SEARCH_FIND, "")); //$NON-NLS-1$
data = new FormData();
data.top = new FormAttachment(fFindText, 0, SWT.CENTER);
findLabel.setLayoutData(data);
// replace
replaceLabel.setText(Messages.getString("FindReplaceDialog.LabelReplaceWith")); //$NON-NLS-1$
data = new FormData();
data.top = new FormAttachment(fFindText);
replaceLabel.setLayoutData(data);
data = new FormData();
data.top = new FormAttachment(replaceLabel, 0, SWT.CENTER);
data.left = new FormAttachment(replaceLabel);
data.width = FIND_REPLACE_TEXT_WIDTH;
fReplaceText.setLayoutData(data);
fReplaceText.setText(fProperties.getProperty(SEARCH_REPLACE, "")); //$NON-NLS-1$
// group direction
Group directionGroup = new Group(composite, SWT.NONE);
Group formatGroup = new Group(composite, SWT.NONE);
Group rangeGroup = new Group(composite, SWT.NONE);
directionGroup.setText(Messages.getString("FindReplaceDialog.LabelDirection")); //$NON-NLS-1$
GridLayout layout = new GridLayout();
layout.numColumns = 1;
directionGroup.setLayout(layout);
fForwardButton = new Button(directionGroup, SWT.RADIO);
fForwardButton.setText(Messages.getString("FindReplaceDialog.ButtonForward")); //$NON-NLS-1$
final Button backwardButton = new Button(directionGroup, SWT.RADIO);
backwardButton.setText(Messages.getString("FindReplaceDialog.ButtonBackward")); //$NON-NLS-1$
final boolean isForward = Boolean
.parseBoolean(fProperties.getProperty(SEARCH_FORMAT_FORWARD, Boolean.TRUE.toString()));
fForwardButton.setSelection(isForward);
backwardButton.setSelection(!isForward);
data = new FormData();
data.top = new FormAttachment(fReplaceText);
data.right = new FormAttachment(formatGroup, 0, SWT.RIGHT);
data.left = new FormAttachment(formatGroup, 0, SWT.LEFT);
data.bottom = new FormAttachment(rangeGroup, 0, SWT.BOTTOM);
directionGroup.setLayoutData(data);
// group range
rangeGroup.setText(Messages.getString("FindReplaceDialog.LabelRange")); //$NON-NLS-1$
layout = new GridLayout();
layout.numColumns = 2;
layout.makeColumnsEqualWidth = false;
rangeGroup.setLayout(layout);
// group range - start address
Label startLabel = new Label(rangeGroup, SWT.NONE);
startLabel.setText(Messages.getString("FindReplaceDialog.LabelStartAddress")); //$NON-NLS-1$
fStartText = new Combo(rangeGroup, SWT.BORDER);
GridData gridData = new GridData();
gridData.widthHint = 200;
gridData.grabExcessHorizontalSpace = true;
fStartText.setLayoutData(gridData);
// group range - end address
Label endLabel = new Label(rangeGroup, SWT.NONE);
endLabel.setText(Messages.getString("FindReplaceDialog.LabelEndAddress")); //$NON-NLS-1$
fEndText = new Combo(rangeGroup, SWT.BORDER);
gridData = new GridData();
gridData.widthHint = 200;
gridData.grabExcessHorizontalSpace = true;
fEndText.setLayoutData(gridData);
data = new FormData();
data.left = new FormAttachment(directionGroup);
data.top = new FormAttachment(directionGroup, 0, SWT.TOP);
data.right = new FormAttachment(fFindText, 0, SWT.RIGHT);
rangeGroup.setLayoutData(data);
fStartText.setItems(removeNullElements(
new String[] { getViewportStart(), getStart(), getEnd(), getMemoryBlockBaseAddress() }));
fEndText.setItems(removeNullElements(
new String[] { getEnd(), getStart(), getMemoryBlockBaseAddress(), getViewportStart() }));
if (fProperties.getProperty(SEARCH_START) != null)
fStartText.add(fProperties.getProperty(SEARCH_START), 0);
if (fProperties.getProperty(SEARCH_END) != null)
fEndText.add(fProperties.getProperty(SEARCH_END), 0);
fStartText.select(0);
fEndText.select(0);
// format group
formatGroup.setText(Messages.getString("FindReplaceDialog.LabelFormat")); //$NON-NLS-1$
layout = new GridLayout();
layout.numColumns = 1;
formatGroup.setLayout(layout);
fFormatAsciiButton = new Button(formatGroup, SWT.RADIO);
fFormatAsciiButton.setText(Messages.getString("FindReplaceDialog.ButtonASCII")); //$NON-NLS-1$
fFormatHexButton = new Button(formatGroup, SWT.RADIO);
fFormatHexButton.setText(Messages.getString("FindReplaceDialog.ButtonHexadecimal")); //$NON-NLS-1$
fFormatOctalButton = new Button(formatGroup, SWT.RADIO);
fFormatOctalButton.setText(Messages.getString("FindReplaceDialog.ButtonOctal")); //$NON-NLS-1$
fFormatBinaryButton = new Button(formatGroup, SWT.RADIO);
fFormatBinaryButton.setText(Messages.getString("FindReplaceDialog.ButtonBinary")); //$NON-NLS-1$
fFormatDecimalButton = new Button(formatGroup, SWT.RADIO);
fFormatDecimalButton.setText(Messages.getString("FindReplaceDialog.ButtonDecimal")); //$NON-NLS-1$
fFormatByteSequenceButton = new Button(formatGroup, SWT.RADIO);
fFormatByteSequenceButton.setText(Messages.getString("FindReplaceDialog.ButtonByteSequence")); //$NON-NLS-1$
final String format = fProperties.getProperty(SEARCH_FORMAT, FindReplaceDialog.SEARCH_FORMAT_ASCII);
fFormatAsciiButton.setSelection(format.equals(SEARCH_FORMAT_ASCII));
fFormatOctalButton.setSelection(format.equals(SEARCH_FORMAT_OCTAL));
fFormatBinaryButton.setSelection(format.equals(SEARCH_FORMAT_BINARY));
fFormatDecimalButton.setSelection(format.equals(SEARCH_FORMAT_DECIMAL));
fFormatHexButton.setSelection(format.equals(SEARCH_FORMAT_HEX));
fFormatByteSequenceButton.setSelection(format.equals(SEARCH_FORMAT_BYTESEQUENCE));
data = new FormData();
data.top = new FormAttachment(rangeGroup);
formatGroup.setLayoutData(data);
// options group
Group optionsGroup = new Group(composite, SWT.NONE);
optionsGroup.setText(Messages.getString("FindReplaceDialog.LabelOptions")); //$NON-NLS-1$
data = new FormData();
data.left = new FormAttachment(formatGroup);
data.top = new FormAttachment(rangeGroup);
data.bottom = new FormAttachment(formatGroup, 0, SWT.BOTTOM);
data.right = new FormAttachment(rangeGroup, 0, SWT.RIGHT);
optionsGroup.setLayoutData(data);
layout = new GridLayout();
layout.numColumns = 1;
optionsGroup.setLayout(layout);
// wrap
fWrapCheckbox = new Button(optionsGroup, SWT.CHECK);
fWrapCheckbox.setText(Messages.getString("FindReplaceDialog.ButtonWrapSearch")); //$NON-NLS-1$
fWrapCheckbox.setEnabled(false); // TODO implement wrap
fCaseInSensitiveCheckbox = new Button(optionsGroup, SWT.CHECK);
fCaseInSensitiveCheckbox.setText(Messages.getString("FindReplaceDialog.ButtonCaseInsensitive")); //$NON-NLS-1$
fCaseInSensitiveCheckbox.setEnabled(format.equals(SEARCH_FORMAT_ASCII));
fCaseInSensitiveCheckbox.setSelection(
Boolean.parseBoolean(fProperties.getProperty(SEARCH_FORMAT_CASEINSENSTIVE, Boolean.FALSE.toString())));
fFormatAsciiButton.addSelectionListener(new SelectionListener() {
@Override
public void widgetDefaultSelected(SelectionEvent e) {
}
@Override
public void widgetSelected(SelectionEvent e) {
fCaseInSensitiveCheckbox.setEnabled(true);
}
});
SelectionListener nonAsciiListener = new SelectionListener() {
@Override
public void widgetDefaultSelected(SelectionEvent e) {
}
@Override
public void widgetSelected(SelectionEvent e) {
fCaseInSensitiveCheckbox.setEnabled(false);
validate();
}
};
fFormatHexButton.addSelectionListener(nonAsciiListener);
fFormatOctalButton.addSelectionListener(nonAsciiListener);
fFormatBinaryButton.addSelectionListener(nonAsciiListener);
fFormatDecimalButton.addSelectionListener(nonAsciiListener);
fFormatByteSequenceButton.addSelectionListener(nonAsciiListener);
fStartText.addModifyListener(e -> {
boolean valid = true;
try {
getStartAddress();
} catch (Exception ex) {
valid = false;
}
fStartText.setForeground(valid ? Display.getDefault().getSystemColor(SWT.COLOR_BLACK)
: Display.getDefault().getSystemColor(SWT.COLOR_RED));
validate();
});
fEndText.addModifyListener(e -> {
try {
getEndAddress();
fEndText.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
} catch (Exception ex) {
fEndText.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_RED));
}
validate();
});
fFindText.addModifyListener(e -> validate());
fReplaceText.addModifyListener(e -> validate());
composite.setTabList(
new Control[] { fFindText, fReplaceText, directionGroup, rangeGroup, formatGroup, optionsGroup, });
fFindText.setFocus();
return composite;
}
class FindReplaceMemoryCache {
BigInteger memoryCacheStartAddress = BigInteger.ZERO;
MemoryByte memoryCacheData[] = new MemoryByte[0];
}
/**
* Function : getSearchableBytes
*
* This function returns to the user an array of memory
* @param start Address ( inclusive ) of the beginning byte of the memory region to be searched
* @param end Address ( inclusive ) of the ending
* @param forwardSearch direction of the search ( true == searching forward , false = searching backwards
* @param address Address ( inclusive ) of the byte set being requested/returned
* @param length Number of bytes of data to be returned
* @param cache Cached memory byte data ( this routine fetches additional bytes of memory to try and reduce interaction with the debug engine )
* @return MemoryByte[] array which contains the requested bytes
* @throws DebugException
*/
private MemoryByte[] getSearchableBytes(BigInteger start, BigInteger end, boolean forwardSearch, BigInteger address,
int length, FindReplaceMemoryCache cache) throws DebugException {
BigInteger endCacheAddress = cache.memoryCacheStartAddress
.add(BigInteger.valueOf(cache.memoryCacheData.length));
/*
* Determine if the requested data is already within the cache.
*/
if (!((address.compareTo(cache.memoryCacheStartAddress) >= 0)
&& (address.add(BigInteger.valueOf(length)).compareTo(endCacheAddress) < 0))) {
BigInteger prefetchSize = BigInteger.valueOf(preFetchSize);
BigInteger len = BigInteger.valueOf(length);
BigInteger fetchAddress = address;
BigInteger fetchSize;
/*
* Determine which way we are searching. Whichever way we are searching we need to make sure
* we capture the minimum requested amount of data in the forward direction.
*/
if (forwardSearch) {
/*
* Legend : "#" == minimum requested data , "*" == additional data we want to prefetch/cache
*
* This is the best case where everything cleanly fits within the starting/ending ranges
* to be searched. What we cannot do is to fetch data outside of these ranges. The user
* has specified them, if they are in error that is OK, but we must respect the boundaries
* they specified.
*
* +-- address
* |
* +--length--+--prefetch--+------------------------------------+
* |##########|************| |
* |##########|************| |
* |##########|************| |
* +----------+------------+------------------------------------+
* | |
* +-- start end --+
*
* This is the worst case scenario. We cannot even get the requested minimum ( no matter
* the desired prefetch ) before we run out of the specified range.
*
* +-- address
* |
* +----------------------------------------------------+--length--+--prefetch--+
* | |##########|************|
* | |##########|************|
* | |##########|************|
* +----------------------------------------------------+-------+--+------------+
* | |
* +-- start end --+
*
* See if the desired size ( minimum length + desired prefetch ) fits in to the current range.
* If so there is nothing to adjust.
*/
if (prefetchSize.compareTo(len) >= 0) {
fetchSize = prefetchSize;
} else {
fetchSize = len;
}
if (address.add(fetchSize).compareTo(end) > 0) {
/*
* It does not all fit. Get as much as we can ( end - current ) + 1.
*/
fetchSize = end.subtract(address).add(BigInteger.ONE);
/*
* If the amount of data we can get does not even meet the minimum request. In this case
* we have to readjust how much we copy to match what we can actually read. If we do not
* do this then we will run past the actual data fetched and generate an exception.
*/
if (fetchSize.compareTo(len) < 0) {
length = fetchSize.intValue();
}
}
/*
* The fetch address just starts at the current requested location since we are searching in
* the forward direction and thus prefetching in the forward direction.
*/
fetchAddress = address;
} else {
/*
* Legend : "#" == minimum requested data , "*" == additional data we want to prefetch/cache
*
* This is the best case where everything cleanly fits within the starting/ending ranges
* to be searched. What we cannot do is to fetch data outside of these ranges. The user
* has specified them, if they are in error that is OK, but we must respect the boundaries
* they specified.
*
* +-- address
* |
* +--prefetch--+--length--+------------------------------------+
* |************|##########| |
* |************|##########| |
* |************|##########| |
* +------------+----------+------------------------------------+
* | |
* +-- start end --+
*
* This is the second worst case scenario. We cannot even get the requested minimum ( no matter
* the desired prefetch ) before we run out of the specified range.
*
* +-- address
* |
* +--------------------------------------------+--prefetch--+--length--+
* | |************|##########|
* | |************|##########|
* | |************|##########|
* +--------------------------------------------+------------+--+-------+
* | |
* +-- start end --+
*
* This is the worst case scenario. The minimum length moves us off the end of the high range
* end and the prefetch before this minimum data request ( remember we are fetching backwards
* since we are searching backwards ) runs us off the start of the data.
*
* +-- address
* |
* +---+-----------------------------------------------prefetch--+--length--+
* |*************************************************************|##########|
* |*************************************************************|##########|
* |*************************************************************|##########|
* +---+---------------------------------------------------------+--+-------+
* | |
* +-- start end --+
*
* See if the desired size ( minimum length + desired prefetch ) fits in to the current range.
* Without running off the end.
*/
if (address.add(len).compareTo(end) > 0) {
/*
* We need to reduce the amount we can ask for to whats left. Also make sure to reduce the
* amount to copy, otherwise we will overrun the buffer and generate an exception.
*/
len = end.subtract(address).add(BigInteger.ONE);
length = len.intValue();
}
/*
* Now determine if the prefetch is going to run backwards past the "start" of where we are allowed
* to access the memory. We will normalize the prefetch size so it takes in to account the amount of
* data being gathered as part of the length requested portion. This should insure that in the end
* we will request the prefetch amount of data unless there is not enough to service this request.
*/
if (len.compareTo(prefetchSize) > 0) {
prefetchSize = BigInteger.ZERO;
} else {
prefetchSize = prefetchSize.subtract(len);
}
if (address.subtract(prefetchSize).compareTo(start) < 0) {
/*
* Just get what we can from the beginning up to the current required address.
*/
prefetchSize = address.subtract(start);
fetchAddress = start;
} else {
/*
* It fits so just start reading from the calculated position prior to the requested point.
*/
fetchAddress = address.subtract(prefetchSize);
}
fetchSize = len.add(prefetchSize);
}
/*
* OK, we have determined where to start reading the data and how much. Just get the data
* and store it in the cache.
*/
MemoryByte bytes[] = fMemoryBlock.getBytesFromAddress(fetchAddress, fetchSize.longValue());
cache.memoryCacheStartAddress = fetchAddress;
cache.memoryCacheData = bytes;
}
/*
* Either it was already cached or just has been, either way we have the data so copy what we can
* back to the user buffer.
*/
MemoryByte bytes[] = new MemoryByte[length];
System.arraycopy(cache.memoryCacheData, address.subtract(cache.memoryCacheStartAddress).intValue(), bytes, 0,
length);
return bytes;
}
private BigInteger parseHexBigInteger(String s) {
if (s.toUpperCase().startsWith("0X")) //$NON-NLS-1$
return new BigInteger(s.substring(2), 16);
else
return new BigInteger(s, 16);
}
protected void performFindNext() {
try {
BigInteger start = parseHexBigInteger(fProperties.getProperty(SEARCH_LAST_START));
BigInteger end = parseHexBigInteger(fProperties.getProperty(SEARCH_LAST_END));
boolean searchForward = Boolean
.parseBoolean(fProperties.getProperty(SEARCH_FORMAT_FORWARD, Boolean.FALSE.toString()));
boolean caseInSensitive = Boolean
.parseBoolean(fProperties.getProperty(SEARCH_FORMAT_CASEINSENSTIVE, Boolean.FALSE.toString()));
SearchPhrase phrase = null;
String findText = fProperties.getProperty(SEARCH_FIND);
if (fProperties.getProperty(SEARCH_FORMAT).equals(SEARCH_FORMAT_ASCII))
phrase = new AsciiSearchPhrase(findText, caseInSensitive);
else if (fProperties.getProperty(SEARCH_FORMAT).equals(SEARCH_FORMAT_HEX))
phrase = new BigIntegerSearchPhrase(
new BigInteger(findText.toUpperCase().startsWith("0X") ? findText.substring(2) : findText, 16), //$NON-NLS-1$
16);
else if (fProperties.getProperty(SEARCH_FORMAT).equals(SEARCH_FORMAT_OCTAL))
phrase = new BigIntegerSearchPhrase(
new BigInteger(findText.startsWith("0") ? findText.substring(1) : findText, 8), 8); //$NON-NLS-1$
else if (fProperties.getProperty(SEARCH_FORMAT).equals(SEARCH_FORMAT_BINARY))
phrase = new BigIntegerSearchPhrase(
new BigInteger(findText.toUpperCase().startsWith("0B") ? findText.substring(2) : findText, 2), //$NON-NLS-1$
2);
else if (fProperties.getProperty(SEARCH_FORMAT).equals(SEARCH_FORMAT_DECIMAL))
phrase = new BigIntegerSearchPhrase(new BigInteger(findText, 10), 10);
else if (fProperties.getProperty(SEARCH_FORMAT).equals(SEARCH_FORMAT_BYTESEQUENCE))
phrase = new ByteSequenceSearchPhrase(findText);
performFind(start, end, phrase, searchForward, null, false, false);
} catch (Exception e) {
MemorySearchPlugin.logError(Messages.getString("FindReplaceDialog.MemorySearchFailure"), e); //$NON-NLS-1$
}
}
private void performFind(final BigInteger start, final BigInteger end, final SearchPhrase searchPhrase,
final boolean searchForward, final byte[] replaceData, final boolean all, final boolean replaceThenFind) {
final ISearchQuery query = new IMemorySearchQuery() {
private ISearchResult fSearchResult = null;
@Override
public boolean canRerun() {
return false;
}
@Override
public boolean canRunInBackground() {
return true;
}
@Override
public String getLabel() {
return Messages.getString("FindReplaceDialog.SearchingMemoryFor") + searchPhrase; //$NON-NLS-1$
}
@Override
public ISearchResult getSearchResult() {
if (fSearchResult == null)
fSearchResult = new MemorySearchResult(this,
Messages.getString("FindReplaceDialog.SearchingMemoryFor") + searchPhrase); //$NON-NLS-1$
return fSearchResult;
}
@Override
public IStatus run(IProgressMonitor monitor) throws OperationCanceledException {
final BigInteger searchPhraseLength = BigInteger.valueOf(searchPhrase.getByteLength());
BigInteger range = end.subtract(start).add(BigInteger.ONE);
BigInteger currentPosition = searchForward ? start : end.subtract(searchPhraseLength);
if (searchPhraseLength.compareTo(range) >= 0) {
return Status.OK_STATUS;
}
boolean isReplace = replaceData != null;
BigInteger jobs = range;
BigInteger factor = BigInteger.ONE;
if (jobs.compareTo(BigInteger.valueOf(0x07FFFFFF)) > 0) {
factor = jobs.divide(BigInteger.valueOf(0x07FFFFFF));
jobs = jobs.divide(factor);
}
BigInteger jobCount = BigInteger.ZERO;
BigInteger replaceCount = BigInteger.ZERO;
FindReplaceMemoryCache cache = new FindReplaceMemoryCache();
monitor.beginTask(Messages.getString("FindReplaceDialog.SearchingMemoryFor") + searchPhrase, //$NON-NLS-1$
jobs.intValue());
boolean matched = false;
while (((searchForward && currentPosition.compareTo(end.subtract(searchPhraseLength)) < 0)
|| (!searchForward && currentPosition.compareTo(start) > 0)) && !monitor.isCanceled()) {
try {
MemoryByte bytes[] = getSearchableBytes(start, end, searchForward, currentPosition,
searchPhraseLength.intValue(), cache);
matched = searchPhrase.isMatch(bytes);
if (matched) {
if (all && !isReplace)
((MemorySearchResult) getSearchResult())
.addMatch(new MemoryMatch(currentPosition, searchPhraseLength));
if (isReplace) {
try {
if ((searchPhrase instanceof BigIntegerSearchPhrase) && (bytes.length > 0)
&& bytes[0].isEndianessKnown() && !bytes[0].isBigEndian()) {
// swap the bytes when replacing an integer on little-endian targets
fMemoryBlock.setValue(
currentPosition.subtract(fMemoryBlock.getBigBaseAddress()),
swapBytes(replaceData));
} else {
fMemoryBlock.setValue(
currentPosition.subtract(fMemoryBlock.getBigBaseAddress()),
replaceData);
}
} catch (DebugException de) {
MemorySearchPlugin
.logError(Messages.getString("FindReplaceDialog.MemoryReadFailed"), de); //$NON-NLS-1$
}
replaceCount = replaceCount.add(BigInteger.ONE);
}
if (isReplace && replaceThenFind && replaceCount.compareTo(BigInteger.ONE) == 0) {
isReplace = false;
matched = false;
}
if (matched && !all) {
final BigInteger finalCurrentPosition = currentPosition;
final BigInteger finalStart = start;
final BigInteger finalEnd = end;
Display.getDefault().asyncExec(() -> {
IMemoryRenderingContainer containers[] = getMemoryView()
.getMemoryRenderingContainers();
for (int i = 0; i < containers.length; i++) {
IMemoryRendering rendering = containers[i].getActiveRendering();
if (rendering instanceof IRepositionableMemoryRendering) {
try {
((IRepositionableMemoryRendering) rendering)
.goToAddress(finalCurrentPosition);
} catch (DebugException e1) {
MemorySearchPlugin.logError(
Messages.getString(
"FindReplaceDialog.RepositioningMemoryViewFailed"), //$NON-NLS-1$
e1);
}
}
if (rendering != null) {
// Temporary, until platform accepts/adds new interface for setting the selection
try {
Method m = rendering.getClass().getMethod("setSelection", //$NON-NLS-1$
new Class[] { BigInteger.class, BigInteger.class });
if (m != null)
m.invoke(rendering, finalCurrentPosition,
finalCurrentPosition.add(searchPhraseLength));
} catch (Exception e2) {
// do nothing
}
}
}
});
fProperties.setProperty(SEARCH_ENABLE_FIND_NEXT, Boolean.TRUE.toString());
if (searchForward) {
BigInteger newFinalStart = finalCurrentPosition.add(BigInteger.ONE);
fProperties.setProperty(SEARCH_LAST_START, "0x" + newFinalStart.toString(16)); //$NON-NLS-1$
fProperties.setProperty(SEARCH_LAST_END, "0x" + finalEnd.toString(16)); //$NON-NLS-1$
} else {
BigInteger newFinalEnd = finalCurrentPosition.subtract(BigInteger.ONE);
fProperties.setProperty(SEARCH_LAST_START, "0x" + finalStart.toString(16)); //$NON-NLS-1$
fProperties.setProperty(SEARCH_LAST_END, "0x" + newFinalEnd.toString(16)); //$NON-NLS-1$
}
if (fFindAction != null) {
fFindAction.setEnabled(true);
}
return Status.OK_STATUS;
}
}
matched = false;
if (searchForward)
currentPosition = currentPosition.add(BigInteger.ONE);
else
currentPosition = currentPosition.subtract(BigInteger.ONE);
} catch (DebugException e) {
MemorySearchPlugin.logError(Messages.getString("FindReplaceDialog.MemorySearchFailure"), e); //$NON-NLS-1$
return Status.CANCEL_STATUS;
}
jobCount = jobCount.add(BigInteger.ONE);
if (jobCount.compareTo(factor) == 0) {
jobCount = BigInteger.ZERO;
monitor.worked(1);
}
}
if (monitor.isCanceled())
return Status.CANCEL_STATUS;
return Status.OK_STATUS;
}
@Override
public IMemoryRenderingSite getMemoryView() {
return fMemoryView;
}
};
if (all && replaceData == null) {
Display.getDefault().asyncExec(() -> {
NewSearchUI.activateSearchResultView();
NewSearchUI.runQueryInBackground(query);
});
} else {
Job job = new Job("Searching memory for " + searchPhrase) { //$NON-NLS-1$
@Override
public IStatus run(IProgressMonitor monitor) {
return query.run(monitor);
}
};
job.schedule();
}
}
interface SearchPhrase {
boolean isMatch(MemoryByte[] bytes);
int getByteLength();
@Override
String toString();
}
class AsciiSearchPhrase implements SearchPhrase {
private String fPhrase;
private boolean fIsCaseInsensitive;
public AsciiSearchPhrase(String phrase, boolean isCaseInsensitive) {
fPhrase = phrase;
fIsCaseInsensitive = isCaseInsensitive;
}
@Override
public int getByteLength() {
return fPhrase.length();
}
@Override
public String toString() {
return fPhrase;
}
@Override
public boolean isMatch(MemoryByte[] bytes) {
byte[] targetBytes = new byte[bytes.length];
for (int i = 0; i < bytes.length; i++)
targetBytes[i] = bytes[i].getValue();
String searchString = fPhrase;
String targetString = new String(targetBytes);
if (fIsCaseInsensitive) {
searchString = searchString.toUpperCase();
targetString = targetString.toUpperCase();
}
return searchString.equals(targetString);
}
}
class ByteSequenceSearchPhrase implements SearchPhrase {
private byte[] fBytes = null;
public ByteSequenceSearchPhrase(String phrase) {
fBytes = parseByteSequence(phrase);
}
@Override
public int getByteLength() {
if (fBytes != null) {
return fBytes.length;
} else {
return 0;
}
}
@Override
public String toString() {
if (fBytes == null)
return ""; //$NON-NLS-1$
StringBuilder buf = new StringBuilder();
for (int i = 0; i < fBytes.length; i++)
buf.append(BigInteger.valueOf(fBytes[i]).toString(16)).append(' ');
return buf.toString();
}
@Override
public boolean isMatch(MemoryByte[] bytes) {
if (fBytes == null)
return false;
for (int i = 0; i < bytes.length; i++)
if (bytes[i].getValue() != fBytes[i])
return false;
return true;
}
}
class BigIntegerSearchPhrase implements SearchPhrase {
private BigInteger fPhrase;
private int fRadix;
public BigIntegerSearchPhrase(BigInteger phrase, int radix) {
fPhrase = phrase;
fRadix = radix;
}
@Override
public int getByteLength() {
return removeZeroPrefixByte(fPhrase.toByteArray()).length;
}
@Override
public String toString() {
return fPhrase.toString(fRadix);
}
@Override
public boolean isMatch(MemoryByte[] bytes) {
byte[] targetBytes = new byte[bytes.length + 1];
targetBytes[0] = 0;
for (int i = 0; i < bytes.length; i++) {
if (bytes[i].isEndianessKnown() && !bytes[i].isBigEndian()) {
// swap the bytes when matching an integer on little-endian targets
targetBytes[i + 1] = bytes[bytes.length - i - 1].getValue();
} else {
targetBytes[i + 1] = bytes[i].getValue();
}
}
BigInteger targetBigInteger = new BigInteger(targetBytes);
return fPhrase.equals(targetBigInteger);
}
}
private byte[] removeZeroPrefixByte(byte[] bytes) {
if (bytes[0] != 0 || bytes.length == 1)
return bytes;
byte[] processedBytes = new byte[bytes.length - 1];
System.arraycopy(bytes, 1, processedBytes, 0, processedBytes.length);
return processedBytes;
}
private byte[] swapBytes(byte[] bytes) {
byte[] processedBytes = new byte[bytes.length];
for (int i = 0; i < bytes.length; i++)
processedBytes[i] = bytes[bytes.length - i - 1];
return processedBytes;
}
interface IMemorySearchQuery extends ISearchQuery {
public IMemoryRenderingSite getMemoryView();
}
}