664 lines
23 KiB
Java
664 lines
23 KiB
Java
/**
|
|
* Copyright (C) 2009 - 2021 <a href="https://www.wudsn.com" target="_top">Peter Dell</a>
|
|
*
|
|
* This file is part of WUDSN IDE.
|
|
*
|
|
* WUDSN IDE is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* WUDSN IDE is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with WUDSN IDE. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
package com.wudsn.ide.hex;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.List;
|
|
|
|
import org.eclipse.core.runtime.IStatus;
|
|
import org.eclipse.jface.preference.JFacePreferences;
|
|
import org.eclipse.jface.viewers.StyledString;
|
|
import org.eclipse.jface.viewers.StyledString.Styler;
|
|
import org.eclipse.swt.graphics.TextStyle;
|
|
|
|
import com.wudsn.ide.base.BasePlugin;
|
|
import com.wudsn.ide.base.common.EnumUtility;
|
|
import com.wudsn.ide.base.common.HexUtility;
|
|
import com.wudsn.ide.base.common.NumberUtility;
|
|
import com.wudsn.ide.base.common.Profiler;
|
|
import com.wudsn.ide.base.common.TextUtility;
|
|
import com.wudsn.ide.base.gui.MessageManager;
|
|
import com.wudsn.ide.base.hardware.HardwareCharacterSet;
|
|
import com.wudsn.ide.hex.HexEditor.MessageIds;
|
|
import com.wudsn.ide.hex.parser.AtariDiskImageKFileParser;
|
|
import com.wudsn.ide.hex.parser.AtariMADSParser;
|
|
import com.wudsn.ide.hex.parser.AtariParser;
|
|
import com.wudsn.ide.hex.parser.AtariSDXParser;
|
|
import com.wudsn.ide.hex.parser.IFFParser;
|
|
|
|
final class HexEditorParserComponent {
|
|
|
|
public static final long UNDEFINED_OFFSET = -1;
|
|
private final static int BYTES_PER_ROW = 16;
|
|
|
|
// Callback API.
|
|
private MessageManager messageManager;
|
|
|
|
// Style components.
|
|
private Styler offsetStyler;
|
|
private Styler addressStyler;
|
|
private Styler charStyler;
|
|
private Styler errorStyler;
|
|
|
|
// File content and state.
|
|
private boolean fileContentParsed;
|
|
private HexEditorFileContentMode fileContentMode;
|
|
private byte[] fileContent;
|
|
private int bytesPerRow;
|
|
private HardwareCharacterSet characterSet;
|
|
|
|
// Previous state with regards to parsing.
|
|
private HexEditorFileContentMode oldFileContentMode;
|
|
private byte[] oldFileContent;
|
|
private int oldBytesPerRow;
|
|
private HardwareCharacterSet oldCharacterSet;
|
|
|
|
// Parsing state.
|
|
private List<HexEditorFileContentMode> possibleFileContentModes;
|
|
private List<HexEditorContentOutlineTreeObject> outlineBlocks;
|
|
private long[] byteTextOffsets;
|
|
private int byteTextIndex;
|
|
|
|
// Line buffers for binary to hex and char conversion.
|
|
private char[] hexChars;
|
|
private char[] hexBuffer;
|
|
private char[] charBuffer;
|
|
|
|
public HexEditorParserComponent(MessageManager messageManager) {
|
|
if (messageManager == null) {
|
|
throw new IllegalArgumentException("Parameter 'messageManager' must not be null.");
|
|
}
|
|
this.messageManager = messageManager;
|
|
|
|
// Get static stylers for the styled string.
|
|
offsetStyler = StyledString.createColorRegistryStyler(JFacePreferences.COUNTER_COLOR, null);
|
|
addressStyler = StyledString.createColorRegistryStyler(JFacePreferences.QUALIFIER_COLOR, null);
|
|
charStyler = StyledString.createColorRegistryStyler(JFacePreferences.HYPERLINK_COLOR, null);
|
|
|
|
charStyler = new Styler() {
|
|
|
|
@Override
|
|
public void applyStyles(TextStyle textStyle) {
|
|
textStyle.font = null;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
errorStyler = StyledString.createColorRegistryStyler(JFacePreferences.ERROR_COLOR, null);
|
|
|
|
// Initialize hex chars and normal character set type.
|
|
hexChars = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
|
|
characterSet = HardwareCharacterSet.ASCII;
|
|
|
|
clear();
|
|
|
|
}
|
|
|
|
private void clear() {
|
|
// Initialize with empty file.
|
|
fileContentParsed = false;
|
|
fileContentMode = HexEditorFileContentMode.BINARY;
|
|
setFileContent(new byte[0]);
|
|
characterSet = HardwareCharacterSet.ASCII;
|
|
bytesPerRow = BYTES_PER_ROW;
|
|
|
|
oldFileContentMode = null;
|
|
oldFileContent = null;
|
|
oldCharacterSet = null;
|
|
oldBytesPerRow = 0;
|
|
|
|
possibleFileContentModes = new ArrayList<HexEditorFileContentMode>();
|
|
outlineBlocks = new ArrayList<HexEditorContentOutlineTreeObject>();
|
|
}
|
|
|
|
public void setFileContent(byte[] fileContent) {
|
|
if (fileContent == null) {
|
|
throw new IllegalArgumentException("Parameter 'fileContent' must not be null.");
|
|
}
|
|
this.fileContent = fileContent;
|
|
initByteTextOffsets();
|
|
}
|
|
|
|
/**
|
|
* Reserve enough space for the lookup table that maps text offsets to file
|
|
* offsets.
|
|
*/
|
|
private void initByteTextOffsets() {
|
|
// Twice the space, because some formats display the content twice, for
|
|
// example ATARI_DISK_IMAGE_K_FILE.
|
|
byteTextOffsets = new long[fileContent.length * 2];
|
|
Arrays.fill(byteTextOffsets, -1);
|
|
byteTextIndex = 0;
|
|
}
|
|
|
|
/**
|
|
* Determines the possible file content modes based on the file content.
|
|
*
|
|
* @return The suggested default file content mode, not <code>null</code>.
|
|
*/
|
|
public HexEditorFileContentMode determinePossibleFileContentModes() {
|
|
|
|
HexEditorFileContentMode result = HexEditorFileContentMode.BINARY;
|
|
possibleFileContentModes.clear();
|
|
possibleFileContentModes.add(fileContentMode);
|
|
|
|
HexEditorFileContentMode defaultMode = result;
|
|
FileContent fileContent = getFileContent();
|
|
// COM header present?
|
|
if (fileContent.getLength() > 6) {
|
|
// AtariDOS COM file?
|
|
if (fileContent.getWord(0) == AtariParser.COM_HEADER) {
|
|
int startAddress = fileContent.getWord(2);
|
|
int endAddress = fileContent.getWord(4);
|
|
if (startAddress >= 0 && endAddress >= startAddress) {
|
|
defaultMode = HexEditorFileContentMode.ATARI_COM_FILE;
|
|
possibleFileContentModes.add(defaultMode);
|
|
|
|
if (fileContent.getLength() > 16) {
|
|
if (fileContent.getWord(6) == AtariMADSParser.RELOC_HEADER) {
|
|
defaultMode = HexEditorFileContentMode.ATARI_MADS_FILE;
|
|
possibleFileContentModes.add(defaultMode);
|
|
}
|
|
}
|
|
// New default?
|
|
if (result.equals(HexEditorFileContentMode.BINARY)) {
|
|
result = defaultMode;
|
|
}
|
|
}
|
|
} // SpartaDOS X non relocatable file?
|
|
else if (fileContent.getWord(0) == AtariSDXParser.NON_RELOC_HEADER) {
|
|
int startAddress = fileContent.getWord(2);
|
|
int endAddress = fileContent.getWord(4);
|
|
if (startAddress > 0 && endAddress >= startAddress) {
|
|
defaultMode = HexEditorFileContentMode.ATARI_SDX_FILE;
|
|
possibleFileContentModes.add(defaultMode);
|
|
// New default?
|
|
if (result.equals(HexEditorFileContentMode.BINARY)) {
|
|
result = defaultMode;
|
|
}
|
|
}
|
|
} // SpartaDOS X relocatable file?
|
|
else if (fileContent.getWord(0) == AtariSDXParser.RELOC_HEADER && fileContent.getLength() > 8) {
|
|
int blockNumber = fileContent.getByte(2);
|
|
if (blockNumber > 0) {
|
|
defaultMode = HexEditorFileContentMode.ATARI_SDX_FILE;
|
|
possibleFileContentModes.add(defaultMode);
|
|
// New default?
|
|
if (result.equals(HexEditorFileContentMode.BINARY)) {
|
|
result = defaultMode;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ATR header present?
|
|
if ((fileContent.getLength() > 16 && fileContent.getByte(0) == 0x96 && fileContent.getByte(1) == 0x02)) {
|
|
defaultMode = HexEditorFileContentMode.ATARI_DISK_IMAGE;
|
|
possibleFileContentModes.add(defaultMode);
|
|
|
|
// Special case of k-file (converted COM file)
|
|
int offset = AtariDiskImageKFileParser.ATARI_DISK_IMAGE_K_FILE_COM_FILE_OFFSET;
|
|
if (fileContent.getLength() > offset + 2 && fileContent.getWord(offset) == 0xffff) {
|
|
final int[] kFileBootHeader = new int[] { 0x00, 0x03, 0x00, 0x07, 0x14, 0x07, 0x4C, 0x14, 0x07 };
|
|
boolean kFileBootHeaderFound = true;
|
|
for (int i = 0; i < kFileBootHeader.length; i++) {
|
|
if (fileContent.getByte(16 + i) != kFileBootHeader[i]) {
|
|
kFileBootHeaderFound = false;
|
|
}
|
|
}
|
|
if (kFileBootHeaderFound) {
|
|
defaultMode = HexEditorFileContentMode.ATARI_DISK_IMAGE_K_FILE;
|
|
possibleFileContentModes.add(defaultMode);
|
|
}
|
|
}
|
|
|
|
// New default?
|
|
if (result.equals(HexEditorFileContentMode.BINARY)) {
|
|
result = defaultMode;
|
|
}
|
|
}
|
|
|
|
// SAP header present?
|
|
if ((fileContent.getLength() > 11 && fileContent.getByte(0) == 0x53 && fileContent.getByte(1) == 0x41)
|
|
&& fileContent.getByte(2) == 0x50) {
|
|
possibleFileContentModes.add(HexEditorFileContentMode.ATARI_SAP_FILE);
|
|
// New default?
|
|
if (result.equals(HexEditorFileContentMode.BINARY)) {
|
|
result = HexEditorFileContentMode.ATARI_SAP_FILE;
|
|
}
|
|
}
|
|
|
|
// PRG header present?
|
|
if ((fileContent.getLength() > 2 && fileContent.getWord(0) + fileContent.getLength() - 2 < 0x10000)) {
|
|
possibleFileContentModes.add(HexEditorFileContentMode.C64_PRG_FILE);
|
|
int loadAddress = fileContent.getWord(0);
|
|
if (result.equals(HexEditorFileContentMode.BINARY) && loadAddress >= 0x800 && loadAddress < 0x2000) {
|
|
result = HexEditorFileContentMode.C64_PRG_FILE;
|
|
}
|
|
}
|
|
|
|
// IFF files always have an even number of bytes
|
|
if (fileContent.getLength() > 8 && (fileContent.getLength() & 0x1) == 0) {
|
|
char[] id = new char[4];
|
|
int offset = 0;
|
|
id[0] = (char) fileContent.getByte(0);
|
|
id[1] = (char) fileContent.getByte(1);
|
|
id[2] = (char) fileContent.getByte(2);
|
|
id[3] = (char) fileContent.getByte(3);
|
|
String chunkName = String.copyValueOf(id);
|
|
offset += 4;
|
|
var chunkLength = fileContent.getDoubleWordBigEndian(4);
|
|
offset += 4;
|
|
if (IFFParser.isValidChunkName(chunkName) && offset + chunkLength <= fileContent.getLength()) {
|
|
possibleFileContentModes.add(HexEditorFileContentMode.IFF_FILE);
|
|
boolean iff = chunkName.equals("FORM") || chunkName.equals("LIST") || chunkName.equals("CAT ");
|
|
if (result.equals(HexEditorFileContentMode.BINARY) && iff) {
|
|
result = HexEditorFileContentMode.IFF_FILE;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Sets the file content for {@link #parseFileContent()}.
|
|
*
|
|
* @param fileContentMode The file content mode, not <code>null</code>.
|
|
*/
|
|
public void setFileContentMode(HexEditorFileContentMode fileContentMode) {
|
|
if (fileContentMode == null) {
|
|
throw new IllegalArgumentException("Parameter 'fileContentMode' must not be null.");
|
|
}
|
|
this.fileContentMode = fileContentMode;
|
|
}
|
|
|
|
/**
|
|
* Gets the file content for {@link #parseFileContent()}.
|
|
*
|
|
* @return fileContentMode The file content mode, not <code>null</code>.
|
|
*/
|
|
public HexEditorFileContentMode getFileContentMode() {
|
|
return fileContentMode;
|
|
}
|
|
|
|
/**
|
|
* Sets the character set type.
|
|
*
|
|
* @param characterSet The character set type, not <code>null</code>.
|
|
*/
|
|
public void setCharacterSet(HardwareCharacterSet characterSet) {
|
|
if (characterSet == null) {
|
|
throw new IllegalArgumentException("Parameter 'characterSet' must not be null.");
|
|
}
|
|
|
|
this.characterSet = characterSet;
|
|
}
|
|
|
|
/**
|
|
* Gets the character set type.
|
|
*
|
|
* @return characterSet The character set type, not <code>null</code>.
|
|
*/
|
|
public HardwareCharacterSet getCharacterSet() {
|
|
return characterSet;
|
|
}
|
|
|
|
/**
|
|
* Sets the number of bytes per row for {@link #parseFileContent()}.
|
|
*
|
|
* @param bytesPerRow The number of bytes per row, a positive integer.
|
|
*/
|
|
public void setBytesPerRow(int bytesPerRow) {
|
|
if (bytesPerRow < 1) {
|
|
throw new IllegalArgumentException(
|
|
"Parameter 'bytesPerRow' must not be positive. Specified valie was " + bytesPerRow + ".");
|
|
}
|
|
this.bytesPerRow = bytesPerRow;
|
|
}
|
|
|
|
/**
|
|
* Gets the number of bytes per row for {@link #parseFileContent()}.
|
|
*
|
|
* @return The number of bytes per row, a positive integer.
|
|
*/
|
|
public int getBytesPerRow() {
|
|
return bytesPerRow;
|
|
}
|
|
|
|
/**
|
|
* Determines if parsing is required.
|
|
*
|
|
* @return <code>true</code> if parsing is required, <code>false</code>
|
|
* otherwise.
|
|
*/
|
|
public boolean isParsingFileContentRequired() {
|
|
return !fileContentParsed || !Arrays.equals(fileContent, oldFileContent)
|
|
|| !fileContentMode.equals(oldFileContentMode) || !characterSet.equals(oldCharacterSet)
|
|
|| bytesPerRow != oldBytesPerRow;
|
|
}
|
|
|
|
/**
|
|
* Parse the file content set with {@link #setFileContent(byte[])} according to
|
|
* the parameters set with
|
|
* {@link #setFileContentMode(HexEditorFileContentMode)},
|
|
* {@link #setBytesPerRow(int)} and
|
|
* {@link #setCharacterSet(HardwareCharacterSet)}.
|
|
*
|
|
* @return The styles string representing the content.
|
|
*/
|
|
public StyledString parseFileContent() {
|
|
|
|
Profiler profiler = new Profiler(this);
|
|
profiler.begin("parseFileContent", fileContent.length + " bytes");
|
|
|
|
outlineBlocks.clear();
|
|
initByteTextOffsets();
|
|
|
|
StyledString contentBuilder = new StyledString();
|
|
HexEditorContentOutlineTreeObject treeObject;
|
|
String text = TextUtility.format(Texts.HEX_EDITOR_FILE_SIZE,
|
|
HexUtility.getLongValueHexString(fileContent.length),
|
|
NumberUtility.getLongValueDecimalString(fileContent.length));
|
|
contentBuilder.append(text);
|
|
treeObject = new HexEditorContentOutlineTreeObject(contentBuilder);
|
|
treeObject.setFileStartOffset(0);
|
|
treeObject.setTextStartOffset(contentBuilder.length());
|
|
outlineBlocks.add(treeObject);
|
|
|
|
contentBuilder = new StyledString();
|
|
if (!possibleFileContentModes.contains(fileContentMode)) {
|
|
messageManager.sendMessage(MessageIds.FILE_CONTENT_MODE, IStatus.ERROR, Texts.MESSAGE_E300,
|
|
EnumUtility.getText(fileContentMode));
|
|
return contentBuilder;
|
|
}
|
|
|
|
if (fileContent.length > 0) {
|
|
boolean error;
|
|
HexEditorParser parser = fileContentMode.createParser();
|
|
|
|
// Initialize the buffers for the hex and char conversion.
|
|
hexBuffer = new char[3 + bytesPerRow * 3 + 2];
|
|
for (int i = 0; i < hexBuffer.length; i++) {
|
|
hexBuffer[i] = ' ';
|
|
}
|
|
hexBuffer[1] = ':';
|
|
hexBuffer[hexBuffer.length - 2] = '|';
|
|
charBuffer = new char[bytesPerRow + 1];
|
|
charBuffer[charBuffer.length - 1] = '\n';
|
|
|
|
parser.init(this, offsetStyler, addressStyler);
|
|
error = parser.parse(contentBuilder);
|
|
if (error) {
|
|
messageManager.sendMessage(MessageIds.FILE_CONTENT_MODE, IStatus.ERROR, Texts.MESSAGE_E301,
|
|
EnumUtility.getText(fileContentMode));
|
|
}
|
|
}
|
|
|
|
profiler.end("parseFileContent");
|
|
|
|
// Copy current state to state backup for change detection in {@link
|
|
// #isParsingFileContentRequired},
|
|
fileContentParsed = true;
|
|
oldFileContentMode = fileContentMode;
|
|
oldFileContent = fileContent;
|
|
oldCharacterSet = characterSet;
|
|
oldBytesPerRow = bytesPerRow;
|
|
|
|
return contentBuilder;
|
|
}
|
|
|
|
/**
|
|
* Gets the file content.
|
|
*
|
|
* @return The file content, not <code>null</code>.
|
|
*/
|
|
final FileContent getFileContent() {
|
|
return new FileContentImpl(fileContent); // TODO Do not create newly
|
|
}
|
|
|
|
/**
|
|
* Prints a block header in the context area and adds a block to the outline.
|
|
*
|
|
* @param contentBuilder The content builder, not <code>null</code>.
|
|
* @param headerStyledString The style string for the block header in the
|
|
* outline, not <code>null</code>.
|
|
* @param offset The start offset, a non-negative integer.
|
|
*
|
|
* @return The tree object representing the block.
|
|
*/
|
|
final HexEditorContentOutlineTreeObject printBlockHeader(StyledString contentBuilder,
|
|
StyledString headerStyledString, long offset) {
|
|
|
|
if (contentBuilder == null) {
|
|
throw new IllegalArgumentException("Parameter 'contentBuilder' must not be null.");
|
|
}
|
|
if (headerStyledString == null) {
|
|
throw new IllegalArgumentException("Parameter 'styledString' must not be null.");
|
|
}
|
|
HexEditorContentOutlineTreeObject treeObject;
|
|
|
|
treeObject = new HexEditorContentOutlineTreeObject(headerStyledString);
|
|
treeObject.setFileStartOffset(offset);
|
|
treeObject.setTextStartOffset(contentBuilder.length());
|
|
outlineBlocks.add(treeObject);
|
|
return treeObject;
|
|
}
|
|
|
|
/**
|
|
* Prints the last block in case if contains an error like the wrong number of
|
|
* bytes.
|
|
*
|
|
* @param contentBuilder The content builder, not <code>null</code>.
|
|
* @param errorText The error text, not empty and not <code>null</code>.
|
|
* @param length The length of the last block, a non-negative integer.
|
|
* @param offset The offset of the last block, a non-negative integer.
|
|
*/
|
|
final void printBlockWithError(StyledString contentBuilder, String errorText, long length, long offset) {
|
|
if (contentBuilder == null) {
|
|
throw new IllegalArgumentException("Parameter 'contentBuilder' must not be null.");
|
|
}
|
|
|
|
if (errorText == null) {
|
|
throw new IllegalArgumentException("Parameter 'errorText' must not be null.");
|
|
}
|
|
HexEditorContentOutlineTreeObject treeObject;
|
|
StyledString styledString = new StyledString(errorText, errorStyler);
|
|
treeObject = new HexEditorContentOutlineTreeObject(styledString);
|
|
treeObject.setFileStartOffset(UNDEFINED_OFFSET);
|
|
treeObject.setFileEndOffset(UNDEFINED_OFFSET);
|
|
treeObject.setTextStartOffset(contentBuilder.length());
|
|
treeObject.setTextEndOffset(contentBuilder.length());
|
|
|
|
outlineBlocks.add(treeObject);
|
|
contentBuilder.append(styledString);
|
|
contentBuilder.append("\n");
|
|
offset = printBytes(treeObject, contentBuilder, offset, length - 1, true, 0);
|
|
}
|
|
|
|
final void skipByteTextIndex(long offset) {
|
|
byteTextIndex += offset;
|
|
}
|
|
|
|
final long printBytes(HexEditorContentOutlineTreeObject treeObject, StyledString contentBuilder, long offset,
|
|
long maxOffset, boolean withStartAddress, int startAddress) {
|
|
|
|
if (offset < 0) {
|
|
throw new IllegalArgumentException(
|
|
"Parameter 'offset' must not be negative, specified value is " + offset + ".");
|
|
}
|
|
if (maxOffset < 0) {
|
|
throw new IllegalArgumentException(
|
|
"Parameter 'offset' must not be negative, specified value is " + maxOffset + ".");
|
|
}
|
|
int length = Math.max(4, HexUtility.getLongValueHexLength(fileContent.length));
|
|
char[] characterMapping = characterSet.getCharacterMapping();
|
|
while (offset <= maxOffset) {
|
|
int contentBuilderLineStartOffset = contentBuilder.length();
|
|
|
|
contentBuilder.append(HexUtility.getLongValueHexString(offset, length), offsetStyler);
|
|
|
|
if (withStartAddress) {
|
|
contentBuilder.append(" : ");
|
|
contentBuilder.append(HexUtility.getLongValueHexString(startAddress, length), addressStyler);
|
|
|
|
}
|
|
|
|
// Remember byte offset where the new line starts.
|
|
int contentBuilderStartOffset = contentBuilder.length();
|
|
int h = 3;
|
|
for (int b = 0; b < bytesPerRow; b++) {
|
|
char highChar;
|
|
char lowChar;
|
|
char charValue;
|
|
if (offset > maxOffset) {
|
|
highChar = ' ';
|
|
lowChar = ' ';
|
|
charValue = ' ';
|
|
} else {
|
|
int byteValue = getFileContent().getByte(offset);
|
|
highChar = hexChars[byteValue >> 4];
|
|
lowChar = hexChars[byteValue & 0xf];
|
|
charValue = characterMapping[byteValue];
|
|
byteTextOffsets[byteTextIndex++] = (b == 0 ? contentBuilderLineStartOffset
|
|
: contentBuilderStartOffset + h);
|
|
offset++;
|
|
startAddress++;
|
|
}
|
|
hexBuffer[h++] = highChar;
|
|
hexBuffer[h++] = lowChar;
|
|
h++;
|
|
charBuffer[b] = charValue;
|
|
}
|
|
contentBuilder.append(hexBuffer);
|
|
contentBuilder.append(charBuffer, charStyler);
|
|
}
|
|
treeObject.setFileEndOffset(offset);
|
|
treeObject.setTextEndOffset(contentBuilder.length());
|
|
return offset;
|
|
}
|
|
|
|
/**
|
|
* Gets the list of outline blocks determined by {@link #parseFileContent()} .
|
|
*
|
|
* @return The list of outline blocks, may be empty, not <code>null</code>.
|
|
*/
|
|
public List<HexEditorContentOutlineTreeObject> getOutlineBlocks() {
|
|
return outlineBlocks;
|
|
}
|
|
|
|
/**
|
|
* Gets the selection represented by the start and end offset in the text field.
|
|
*
|
|
* @param x is the offset of the first selected character
|
|
* @param y is the offset after the last selected character.
|
|
* @return The selection or <code>null</code>.
|
|
*/
|
|
public HexEditorSelection getSelection(int x, int y) {
|
|
|
|
if (x > y) {
|
|
throw new IllegalArgumentException("x is greater than y");
|
|
}
|
|
long startOffset = UNDEFINED_OFFSET;
|
|
for (long i = 0; i < byteTextIndex; i++) {
|
|
// BasePlugin.getInstance().log(
|
|
// "HexEditor.getSelection(): i={0} textOffset={1}
|
|
// nextTextOffset={2} startOffset={3} endOffset={4}",
|
|
// new Object[] { String.valueOf(i), String.valueOf(textOffset),
|
|
// String.valueOf(nextTextOffset),
|
|
// String.valueOf(startOffset), String.valueOf(endOffset) });
|
|
if (getByteTextOffset(i) >= x) {
|
|
startOffset = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (startOffset == UNDEFINED_OFFSET) {
|
|
return null;
|
|
}
|
|
|
|
long endOffset = UNDEFINED_OFFSET;
|
|
for (long i = startOffset; i < byteTextIndex; i++) {
|
|
// BasePlugin.getInstance().log(
|
|
// "HexEditor.getSelection(): i={0} textOffset={1}
|
|
// nextTextOffset={2} startOffset={3} endOffset={4}",
|
|
// new Object[] { String.valueOf(i), String.valueOf(textOffset),
|
|
// String.valueOf(nextTextOffset),
|
|
// String.valueOf(startOffset), String.valueOf(endOffset) });
|
|
|
|
if (getByteTextOffset(i) >= y) {
|
|
endOffset = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (endOffset == UNDEFINED_OFFSET) {
|
|
return null;
|
|
}
|
|
|
|
long length;
|
|
byte[] bytes;
|
|
|
|
length = endOffset - startOffset + 1;
|
|
BasePlugin.getInstance().log("HexEditor.getSelection(): startOffset={2} endoffset={3} length={4}",
|
|
new Object[] { String.valueOf(x), String.valueOf(y), String.valueOf(startOffset),
|
|
String.valueOf(endOffset), String.valueOf(length) });
|
|
|
|
// Length not empty or negative?
|
|
if (length > 0 && length < fileContent.length) {
|
|
// Reposition into first occurrence of in the file.
|
|
// This is relevant for the format that display the content more
|
|
// than once.
|
|
startOffset = startOffset % fileContent.length;
|
|
endOffset = endOffset % fileContent.length;
|
|
// Selection does not cross file end boundary?
|
|
if (startOffset <= endOffset) {
|
|
bytes = new byte[(int) length];
|
|
System.arraycopy(fileContent, (int) startOffset, bytes, 0, bytes.length);
|
|
|
|
} else {
|
|
|
|
endOffset = startOffset;
|
|
bytes = new byte[0];
|
|
|
|
}
|
|
} else {
|
|
endOffset = startOffset;
|
|
bytes = new byte[0];
|
|
}
|
|
HexEditorSelection hexEditorSelection = new HexEditorSelection(startOffset, endOffset, bytes);
|
|
return hexEditorSelection;
|
|
}
|
|
|
|
/**
|
|
* Gets the text offset for a byte offset.
|
|
*
|
|
* @param byteOffset The byte offset in the original byte array.
|
|
* @return The text offset where the byte is represented or
|
|
* <code>UNDEFINED_OFFSET</code> if there is no such text offset.
|
|
*/
|
|
public long getByteTextOffset(long byteOffset) {
|
|
if (byteOffset < byteTextOffsets.length) {
|
|
return byteTextOffsets[(int) byteOffset];
|
|
}
|
|
return UNDEFINED_OFFSET;
|
|
}
|
|
|
|
} |