Merge remote-tracking branch 'origin/GP-2184-dragonmacher-remove-jmockit-6'

This commit is contained in:
Ryan Kurtz
2022-06-17 00:26:18 -04:00
3 changed files with 378 additions and 323 deletions

View File

@@ -22,7 +22,8 @@ import ghidra.util.BigEndianDataConverter;
import ghidra.util.Msg; import ghidra.util.Msg;
import ghidra.util.datastruct.IntSet; import ghidra.util.datastruct.IntSet;
import ghidra.util.exception.*; import ghidra.util.exception.*;
import ghidra.util.task.*; import ghidra.util.task.CancelledListener;
import ghidra.util.task.TaskMonitor;
/** /**
* <code>LocalBufferFile</code> implements a BufferFile as block-oriented * <code>LocalBufferFile</code> implements a BufferFile as block-oriented
@@ -30,7 +31,6 @@ import ghidra.util.task.*;
* not support the save operation. * not support the save operation.
*/ */
public class LocalBufferFile implements BufferFile { public class LocalBufferFile implements BufferFile {
// static final Logger log = Logger.getLogger(LocalBufferFile.class);
static final long MAGIC_NUMBER = 0x2f30312c34292c2aL; static final long MAGIC_NUMBER = 0x2f30312c34292c2aL;
@@ -118,24 +118,29 @@ public class LocalBufferFile implements BufferFile {
private int[] freeIndexes = new int[0]; private int[] freeIndexes = new int[0];
/** /**
* <code>file</code> is the underlying storage file for this buffer file. * The underlying storage file for this buffer file.
*/ */
private File file; private File file;
/** /**
* <code>raf</code> is the random-access object for the underlying file. * The random-access object for the underlying file.
*/ */
private RandomAccessFile raf; private RandomAccessFile raf;
/** /**
* <code>activeBlockStream</code> provides a handle to the active * Provides a handle to the active OutputBlockStream used to update file via this class's
* OutputBlockStream used to update file via raf. This should be * random access file. This should be checked during {@link #close()} to guard against
* checked during {@link #close()} to guard against partially * partially written file. A LocalOutputBlockStream should only be used for files open for
* written file. A LocalOutputBlockStream should only be used * writing (i.e., !readOnly).
* for files open for writing (i.e., !readOnly).
*/ */
private volatile LocalOutputBlockStream activeOutputBlockStream; private volatile LocalOutputBlockStream activeOutputBlockStream;
/**
* A simple factory that allows this class to support dependency injection.
*/
private static InputBlockStreamFactory inputBlockStreamFactory =
new DefaultInputBlockStreamFactory();
/** /**
* When <code>temporary</code> is true and this file is writable (!readOnly) * When <code>temporary</code> is true and this file is writable (!readOnly)
* it will be deleted when disposed or closed. * it will be deleted when disposed or closed.
@@ -152,8 +157,8 @@ public class LocalBufferFile implements BufferFile {
/** /**
* If <code>readOnly</code> is true, this file may not be modified * If <code>readOnly</code> is true, this file may not be modified
* via the buffer put method. * via the buffer put method.
* A read-only file may be considered "updateable" if the canSave * A read-only file may be considered "updatable" if the canSave
* method returns true. The term "updateable" means that a Save file * method returns true. The term "updatable" means that a Save file
* can be obtained via the getSaveFile method. * can be obtained via the getSaveFile method.
*/ */
private boolean readOnly; private boolean readOnly;
@@ -185,6 +190,7 @@ public class LocalBufferFile implements BufferFile {
* @param bufferSize user buffer size * @param bufferSize user buffer size
* @param tmpPrefix temporary file prefix * @param tmpPrefix temporary file prefix
* @param tmpExtension temporary file extension * @param tmpExtension temporary file extension
* @throws IOException if there is an exception creating underlying files
*/ */
LocalBufferFile(int bufferSize, String tmpPrefix, String tmpExtension) throws IOException { LocalBufferFile(int bufferSize, String tmpPrefix, String tmpExtension) throws IOException {
this.bufferSize = bufferSize; this.bufferSize = bufferSize;
@@ -223,8 +229,7 @@ public class LocalBufferFile implements BufferFile {
* Open an existing block file. * Open an existing block file.
* @param file block file * @param file block file
* @param readOnly if true the file will be opened read-only * @param readOnly if true the file will be opened read-only
* @throws IOException if an IO error occurs or the incorrect magicNumber * @throws IOException if an error occurs or the incorrect magicNumber was read from the file.
* was read from the file.
*/ */
public LocalBufferFile(File file, boolean readOnly) throws IOException { public LocalBufferFile(File file, boolean readOnly) throws IOException {
this.file = file; this.file = file;
@@ -236,13 +241,14 @@ public class LocalBufferFile implements BufferFile {
/** /**
* Modify an existing buffer file. * Modify an existing buffer file.
* WARNING! Use with extreme caution since this modifies * <p>
* the original file and could destroy data if used * WARNING! Use with extreme caution since this modifies the original file and could destroy
* improperly. * data if used improperly.
* @param file *
* @param bufferIndex * @param file block file
* @param buf * @param bufferIndex the index at which to place the buffer
* @throws IOException * @param buf the buffer add to the file
* @throws IOException if an I/O error occurs
*/ */
public static void poke(File file, int bufferIndex, DataBuffer buf) throws IOException { public static void poke(File file, int bufferIndex, DataBuffer buf) throws IOException {
LocalBufferFile bf = new LocalBufferFile(file, false); LocalBufferFile bf = new LocalBufferFile(file, false);
@@ -256,10 +262,10 @@ public class LocalBufferFile implements BufferFile {
/** /**
* Read a buffer from an existing buffer file. * Read a buffer from an existing buffer file.
* @param file * @param file block file
* @param bufferIndex * @param bufferIndex the index from which to read the buffer
* @return * @return the buffer
* @throws IOException * @throws IOException if an I/O error occurs
*/ */
public static DataBuffer peek(File file, int bufferIndex) throws IOException { public static DataBuffer peek(File file, int bufferIndex) throws IOException {
LocalBufferFile bf = new LocalBufferFile(file, false); LocalBufferFile bf = new LocalBufferFile(file, false);
@@ -275,6 +281,7 @@ public class LocalBufferFile implements BufferFile {
/** /**
* Returns the physical file associated with this BufferFile. * Returns the physical file associated with this BufferFile.
* @return the file
*/ */
public File getFile() { public File getFile() {
return file; return file;
@@ -296,7 +303,7 @@ public class LocalBufferFile implements BufferFile {
@Override @Override
public void setParameter(String name, int value) { public void setParameter(String name, int value) {
userParms.put(name, new Integer(value)); userParms.put(name, value);
} }
@Override @Override
@@ -335,24 +342,24 @@ public class LocalBufferFile implements BufferFile {
} }
/** /**
* Assigns the file ID for this file. This should only be done * Assigns the file ID for this file. This should only be done when reconstructing a file.
* when reconstructing a file. * @param id file ID
* @param fileId file ID
*/ */
void setFileId(long id) { void setFileId(long id) {
fileId = id; fileId = id;
} }
/** /**
* @return number of buffers countained within this file * The number of buffers contained within this file.
* @return the number of buffers contained within this file
*/ */
int getBufferCount() { int getBufferCount() {
return bufferCount; return bufferCount;
} }
/** /**
* Assigns the buffer count for this file. This should only be done * Assigns the buffer count for this file. This should only be done when reconstructing a
* when reconstructing a file. * file.
* @param count number of valid buffers contained within this file * @param count number of valid buffers contained within this file
*/ */
void setBufferCount(int count) { void setBufferCount(int count) {
@@ -360,9 +367,9 @@ public class LocalBufferFile implements BufferFile {
} }
/** /**
* Set the temporary status of this buffer file. * Set the temporary status of this buffer file. Temporary buffer files are deleted when
* Temporary buffer files are deleted when closed. * closed.
* @param isTemporary * @param isTemporary true if temporary
*/ */
void setTemporary(boolean isTemporary) { void setTemporary(boolean isTemporary) {
temporary = isTemporary; temporary = isTemporary;
@@ -370,9 +377,9 @@ public class LocalBufferFile implements BufferFile {
/** /**
* Rename underlying file * Rename underlying file
* @param newFile * @param newFile the new file for this class
* @return true if rename successful * @return true if rename successful
* @throws IOException * @throws IOException if an I/O error occurs
*/ */
boolean renameFile(File newFile) throws IOException { boolean renameFile(File newFile) throws IOException {
if (raf != null) { if (raf != null) {
@@ -389,17 +396,17 @@ public class LocalBufferFile implements BufferFile {
} }
/** /**
* Set random access file (raf) position to the file block containing the specified buffer * Set the random access file position to the file block containing the specified buffer
* identified by its bufferIndex. It is important to understand the distinction between * identified by its bufferIndex. It is important to understand the distinction between
* blocks and buffers, where buffers are stored within file blocks which are slightly larger. * blocks and buffers, where buffers are stored within file blocks which are slightly larger.
* In addition, the first file block stores the file header and is not used to store a buffer. * In addition, the first file block stores the file header and is not used to store a buffer.
* @param bufferIndex buffer index * @param bufferIndex buffer index
* @return file block index (=bufferIndex+1) * @return file block index (=bufferIndex+1)
* @throws IOException * @throws IOException if an I/O error occurs
*/ */
private int seekBufferBlock(int bufferIndex) throws IOException { private int seekBufferBlock(int bufferIndex) throws IOException {
// Perform long multiplication to support file sizes greater than 2-GBytes // Perform long multiplication to support file sizes greater than 2-GBytes
// Add 1 to buffer index to obtain block index (first useable buffer, buffer#0, is // Add 1 to buffer index to obtain block index (first usable buffer, buffer#0, is
// contained within block#1 since block#0 contains file header) // contained within block#1 since block#0 contains file header)
int blockIndex = bufferIndex + 1; int blockIndex = bufferIndex + 1;
long offset = (long) blockIndex * (long) blockSize; long offset = (long) blockIndex * (long) blockSize;
@@ -408,11 +415,13 @@ public class LocalBufferFile implements BufferFile {
} }
/** /**
* Set random access file (raf) position to the specified file block and offset * Set the random access file position to the specified file block and offset within that
* within that block. NOTE: block#0 contains the file header. * block.
*
* <p>NOTE: block#0 contains the file header.
* @param blockIndex block index * @param blockIndex block index
* @param offsetWithinBlock offset within file block * @param offsetWithinBlock offset within file block
* @throws IOException * @throws IOException if there is an exception reading the file
*/ */
private void seekBlock(int blockIndex, int offsetWithinBlock) throws IOException { private void seekBlock(int blockIndex, int offsetWithinBlock) throws IOException {
// Perform long multiplication to support file sizes greater than 2-GBytes // Perform long multiplication to support file sizes greater than 2-GBytes
@@ -422,7 +431,7 @@ public class LocalBufferFile implements BufferFile {
/** /**
* Read file header and initialize the user parameter and free buffer index lists. * Read file header and initialize the user parameter and free buffer index lists.
* @throws IOException if an I/O error occurs while reading file * @throws IOException if an I/O error occurs
*/ */
private void readHeader() throws IOException { private void readHeader() throws IOException {
@@ -466,9 +475,8 @@ public class LocalBufferFile implements BufferFile {
} }
/** /**
* Store the user parameter and free buffer index lists and write the * Store the user parameter and free buffer index lists and write the file header.
* file header. * @throws IOException if an I/O error occurs
* @throws IOException if an I/O error occurs while writing file
*/ */
private void writeHeader() throws IOException { private void writeHeader() throws IOException {
@@ -518,7 +526,7 @@ public class LocalBufferFile implements BufferFile {
while (nextIndex >= 0) { while (nextIndex >= 0) {
// Push index on stack // Push index on stack
freeIndexList.add(new Integer(nextIndex)); freeIndexList.add(nextIndex);
// Read block to get next index // Read block to get next index
seekBufferBlock(nextIndex); seekBufferBlock(nextIndex);
@@ -541,9 +549,9 @@ public class LocalBufferFile implements BufferFile {
/** /**
* Update a storage block as free and link to the next free block. * Update a storage block as free and link to the next free block.
* @param index block index of free block * @param index block index of free block
* @param nextFreeIndex block index of next free block, a -1 should be * @param nextFreeIndex block index of next free block, a -1 should be specified to mark the
* specified to mark the end of the linked list. * end of the linked list.
* @throws IOException thrown if an IO error occurs * @throws IOException if an I/O error occurs
*/ */
void putFreeBlock(int index, int nextFreeIndex) throws IOException { void putFreeBlock(int index, int nextFreeIndex) throws IOException {
@@ -716,7 +724,8 @@ public class LocalBufferFile implements BufferFile {
/** /**
* Truncate the buffer file length to the specified index count. * Truncate the buffer file length to the specified index count.
* @param indexCount * @param indexCount the index count
* @throws IOException if an I/O error occurs
*/ */
void truncate(int indexCount) throws IOException { void truncate(int indexCount) throws IOException {
@@ -802,6 +811,7 @@ public class LocalBufferFile implements BufferFile {
} }
/** /**
* True if this is a temporary buffer file
* @return true if this is a temporary buffer file * @return true if this is a temporary buffer file
*/ */
boolean isTemporary() { boolean isTemporary() {
@@ -809,6 +819,7 @@ public class LocalBufferFile implements BufferFile {
} }
/** /**
* True if buffer file is closed
* @return true if buffer file is closed * @return true if buffer file is closed
*/ */
boolean isClosed() { boolean isClosed() {
@@ -875,9 +886,8 @@ public class LocalBufferFile implements BufferFile {
} }
/** /**
* Clone this buffer file to the specified file. The file must not * Clone this buffer file to the specified file. The file must not already exist. If the
* already exist. If the operation is cancelled or an error occurs * operation is cancelled or an error occurs the file is not created.
* the file is not created.
* @param destinationFile destination file * @param destinationFile destination file
* @param monitor progress monitor * @param monitor progress monitor
* @throws IOException if IO error occurs. * @throws IOException if IO error occurs.
@@ -905,6 +915,210 @@ public class LocalBufferFile implements BufferFile {
return file.toString(); return file.toString();
} }
/**
* Obtain a direct stream to read all blocks of this buffer file
* @return input block stream
* @throws IOException if there is an exception creating the stream
*/
public InputBlockStream getInputBlockStream() throws IOException {
return inputBlockStreamFactory.createInputBlockStream(this);
}
/**
* Obtain a direct stream to write blocks to this buffer file
* @param blockCount number of blocks to be transferred
* @return output block stream
* @throws IOException if an I/O error occurs
*/
public OutputBlockStream getOutputBlockStream(int blockCount) throws IOException {
return new LocalOutputBlockStream(blockCount);
}
/**
* Factory method for generating the appropriate type of {@link InputBlockStream}
* for the specified read-only bufferFile. Input stream may not supply header block in which case
* free list and file parameters may need to be set separately.
* @param bufferFile buffer file opened read-only
* @return input block stream object
* @throws IOException if an I/O error occurs
*/
private static InputBlockStream getInputBlockStream(BufferFile bufferFile) throws IOException {
// This method is used so we can utilize a package method and avoid putting
// it on the BufferFile interface
if (bufferFile instanceof BufferFileAdapter) {
return ((BufferFileAdapter) bufferFile).getInputBlockStream();
}
if (bufferFile instanceof LocalBufferFile) {
return ((LocalBufferFile) bufferFile).getInputBlockStream();
}
throw new IllegalArgumentException(
"Unsupported buffer file implementation: " + bufferFile.getClass().getName());
}
/**
* Factory method for generating the appropriate type of {@link InputBlockStream}
* for the specified read-only bufferFile with an optional changeMap used
* to select which buffer should be transferred. Input stream may not supply header block
* in which case free list and file parameters may need to be set separately.
* @param bufferFile buffer file opened read-only
* @return input block stream object
* @throws IOException if an I/O error occurs
*/
private static InputBlockStream getInputBlockStream(BufferFile bufferFile, ChangeMap changeMap)
throws IOException {
if (changeMap == null) {
return getInputBlockStream(bufferFile);
}
// This method is used so we can utilize a package method and avoid putting
// it on the BufferFile interface
if (bufferFile instanceof ManagedBufferFileAdapter) {
return ((ManagedBufferFileAdapter) bufferFile).getInputBlockStream(changeMap.getData());
}
if (bufferFile instanceof LocalManagedBufferFile) {
return ((LocalManagedBufferFile) bufferFile).getInputBlockStream(changeMap.getData());
}
throw new IllegalArgumentException(
"Unsupported buffer file implementation: " + bufferFile.getClass().getName());
}
/**
* Factory method for generating the appropriate type of {@link OutputBlockStream}
* for the specified write-able bufferFile.
* @param bufferFile write-able buffer file
* @param blockCount number of blocks to be written. This should be available from
* the corresponding {@link InputBlockStream}.
* @return output block stream object
* @throws IOException if an I/O error occurs
*/
static OutputBlockStream getOutputBlockStream(BufferFile bufferFile, int blockCount)
throws IOException {
// This method is used so we can utilize a package method and avoid putting
// it on the BufferFile interface
if (bufferFile instanceof BufferFileAdapter) {
return ((BufferFileAdapter) bufferFile).getOutputBlockStream(blockCount);
}
if (bufferFile instanceof LocalBufferFile) {
return ((LocalBufferFile) bufferFile).getOutputBlockStream(blockCount);
}
throw new IllegalArgumentException(
"Unsupported buffer file implementation: " + bufferFile.getClass().getName());
}
/**
* Copy the complete content of a specified srcFile into a destFile excluding file ID. Both
* files remain open.
* @param srcFile open buffer file
* @param destFile empty buffer file which is open for writing.
* @param changeMap optional change map which indicates those buffers which must be copied.
* Any buffer index outside the range of the change map will also be copied.
* @param monitor progress monitor
* @throws IOException if IO error occurs.
* @throws CancelledException if the monitor cancels the operation.
*/
public static void copyFile(BufferFile srcFile, BufferFile destFile, ChangeMap changeMap,
TaskMonitor monitor) throws IOException, CancelledException {
if (destFile.isReadOnly()) {
throw new IOException("File is read-only");
}
if (srcFile.getBufferSize() != destFile.getBufferSize()) {
throw new IOException("Buffer sizes differ");
}
monitor = TaskMonitor.dummyIfNull(monitor);
int srcBlockCnt;
boolean headerTransferRequired;
try (InputBlockStream in = getInputBlockStream(srcFile, changeMap)) {
headerTransferRequired = !in.includesHeaderBlock();
srcBlockCnt = in.getBlockCount();
monitor.initialize(srcBlockCnt + 2);
try (OutputBlockStream out = getOutputBlockStream(destFile, in.getBlockCount())) {
completeBlockStreamTransfer(in, out, monitor);
}
}
finally {
// circumvent other exceptions if cancelled
monitor.checkCanceled();
}
if (headerTransferRequired) {
destFile.clearParameters();
String[] parmNames = srcFile.getParameterNames();
for (String name : parmNames) {
destFile.setParameter(name, srcFile.getParameter(name));
}
monitor.setProgress(srcBlockCnt + 1);
// Copy free index list
destFile.setFreeIndexes(srcFile.getFreeIndexes());
}
monitor.setProgress(srcBlockCnt + 2);
}
/**
* Perform a complete block stream transfer from in to out
* @param in input block stream
* @param out output block stream
* @param monitor progress and cancel monitor
* @throws CancelledException the the operation is cancelled
* @throws IOException if an I/O error occurs
*/
static void completeBlockStreamTransfer(InputBlockStream in, OutputBlockStream out,
TaskMonitor monitor) throws CancelledException, IOException {
int count = 0;
try (BlockStreamCancelMonitor cancelMonitor =
new BlockStreamCancelMonitor(monitor, in, out)) {
int srcBlockCnt = in.getBlockCount();
BufferFileBlock block;
while ((block = in.readBlock()) != null) {
monitor.checkCanceled();
out.writeBlock(block);
monitor.setProgress(count++);
}
if (count != srcBlockCnt) {
throw new IOException("unexpected block transfer count");
}
}
}
/**
* Attempt to remove all pre-save files.
* Those still open by an existing process should
* not be removed by the operating system.
* @param dir data directory containing pre-save files
* @param beforeNow if not 0, file mod time must be less than the specified time
*/
public static void cleanupOldPreSaveFiles(File dir, long beforeNow) {
File[] oldFiles = dir.listFiles(new BufferFileFilter(null, PRESAVE_FILE_EXT));
if (oldFiles == null) {
return;
}
for (File oldFile : oldFiles) {
if ((beforeNow == 0 || oldFile.lastModified() < beforeNow) && oldFile.delete()) {
Msg.info(LocalBufferFile.class, "Removed old presave file: " + oldFile);
}
}
}
/**
* Get the recommended buffer size given a target buffer size.
* @param requestedBufferSize target buffer size
* @return recommended buffer size
*/
static int getRecommendedBufferSize(int requestedBufferSize) {
int size = (requestedBufferSize + BUFFER_PREFIX_SIZE) & -MINIMUM_BLOCK_SIZE;
if (size <= 0) {
size = MINIMUM_BLOCK_SIZE;
}
return size - BUFFER_PREFIX_SIZE;
}
//=================================================================================================
// Inner Classes
//=================================================================================================
/** /**
* <code>LocalBufferInputBlockStream</code> provides an input BlockStream for * <code>LocalBufferInputBlockStream</code> provides an input BlockStream for
* transferring the entire file content associated with a read-only buffer * transferring the entire file content associated with a read-only buffer
@@ -917,7 +1131,7 @@ public class LocalBufferFile implements BufferFile {
/** /**
* Construct input block stream for this buffer file. * Construct input block stream for this buffer file.
* @throws IOException * @throws IOException if an I/O error occurs
*/ */
LocalBufferInputBlockStream() throws IOException { LocalBufferInputBlockStream() throws IOException {
if (!isReadOnly()) { if (!isReadOnly()) {
@@ -991,7 +1205,7 @@ public class LocalBufferFile implements BufferFile {
/** /**
* Construct input block stream for entire file * Construct input block stream for entire file
* @throws IOException * @throws IOException if an I/O error occurs
*/ */
LocalFileInputBlockStream() throws IOException { LocalFileInputBlockStream() throws IOException {
if (!readOnly) { if (!readOnly) {
@@ -1071,7 +1285,7 @@ public class LocalBufferFile implements BufferFile {
* will never be included in the transfer. * will never be included in the transfer.
* @param changeMapData ChangeMap data which is used in determining which * @param changeMapData ChangeMap data which is used in determining which
* buffers should be streamed * buffers should be streamed
* @throws IOException * @throws IOException if an I/O error occurs
*/ */
LocalRandomInputBlockStream(byte[] changeMapData) throws IOException { LocalRandomInputBlockStream(byte[] changeMapData) throws IOException {
if (!readOnly) { if (!readOnly) {
@@ -1230,187 +1444,13 @@ public class LocalBufferFile implements BufferFile {
public int getBlockSize() { public int getBlockSize() {
return blockSize; return blockSize;
} }
}
/**
* Obtain a direct stream to read all blocks of this buffer file
* @return input block stream
* @throws IOException
*/
public InputBlockStream getInputBlockStream() throws IOException {
if (!readOnly) {
throw new IOException("Read stream only permitted on read-only buffer file");
}
return new LocalFileInputBlockStream();
}
/**
* Obtain a direct stream to write blocks to this buffer file
* @param blockCount number of blocks to be transferred
* @return output block stream
* @throws IOException
*/
public OutputBlockStream getOutputBlockStream(int blockCount) throws IOException {
return new LocalOutputBlockStream(blockCount);
}
/**
* Factory method for generating the appropriate type of {@link InputBlockStream}
* for the specified read-only bufferFile. Input stream may not supply header block in which case
* free list and file parameters may need to be set separately.
* @param bufferFile buffer file opened read-only
* @return input block stream object
* @throws IOException
*/
private static InputBlockStream getInputBlockStream(BufferFile bufferFile) throws IOException {
// This method is used so we can utilize a package method and avoid putting
// it on the BufferFile interface
if (bufferFile instanceof BufferFileAdapter) {
return ((BufferFileAdapter) bufferFile).getInputBlockStream();
}
if (bufferFile instanceof LocalBufferFile) {
return ((LocalBufferFile) bufferFile).getInputBlockStream();
}
throw new IllegalArgumentException(
"Unsupported buffer file implementation: " + bufferFile.getClass().getName());
}
/**
* Factory method for generating the appropriate type of {@link InputBlockStream}
* for the specified read-only bufferFile with an optional changeMap used
* to select which buffer should be transferred. Input stream may not supply header block
* in which case free list and file parameters may need to be set separately.
* @param bufferFile buffer file opened read-only
* @return input block stream object
* @throws IOException
*/
private static InputBlockStream getInputBlockStream(BufferFile bufferFile, ChangeMap changeMap)
throws IOException {
if (changeMap == null) {
return getInputBlockStream(bufferFile);
}
// This method is used so we can utilize a package method and avoid putting
// it on the BufferFile interface
if (bufferFile instanceof ManagedBufferFileAdapter) {
return ((ManagedBufferFileAdapter) bufferFile).getInputBlockStream(changeMap.getData());
}
if (bufferFile instanceof LocalManagedBufferFile) {
return ((LocalManagedBufferFile) bufferFile).getInputBlockStream(changeMap.getData());
}
throw new IllegalArgumentException(
"Unsupported buffer file implementation: " + bufferFile.getClass().getName());
}
/**
* Factory method for generating the appropriate type of {@link OutputBlockStream}
* for the specified write-able bufferFile.
* @param bufferFile write-able buffer file
* @param blockCount number of blocks to be written. This should be available from
* the corresponding {@link InputBlockStream}.
* @return output block stream object
* @throws IOException
*/
static OutputBlockStream getOutputBlockStream(BufferFile bufferFile, int blockCount)
throws IOException {
// This method is used so we can utilize a package method and avoid putting
// it on the BufferFile interface
if (bufferFile instanceof BufferFileAdapter) {
return ((BufferFileAdapter) bufferFile).getOutputBlockStream(blockCount);
}
if (bufferFile instanceof LocalBufferFile) {
return ((LocalBufferFile) bufferFile).getOutputBlockStream(blockCount);
}
throw new IllegalArgumentException(
"Unsupported buffer file implementation: " + bufferFile.getClass().getName());
}
/**
* Copy the complete content of a specfied srcFile into a destFile
* excluding file ID. Both files remain open.
* @param srcFile open buffer file
* @param destFile empty buffer file which is open for writing.
* @param changeMap optional change map which indicates those buffers which must be copied.
* Any buffer index outside the range of the change map will also be copied.
* @param monitor progress monitor
* @throws IOException if IO error occurs.
* @throws CancelledException if the monitor cancels the operation.
*/
public static void copyFile(BufferFile srcFile, BufferFile destFile, ChangeMap changeMap,
TaskMonitor monitor) throws IOException, CancelledException {
if (destFile.isReadOnly()) {
throw new IOException("File is read-only");
}
if (srcFile.getBufferSize() != destFile.getBufferSize()) {
throw new IOException("Buffer sizes differ");
}
if (monitor == null) {
monitor = TaskMonitorAdapter.DUMMY_MONITOR;
}
int srcBlockCnt;
boolean headerTransferRequired;
try (InputBlockStream in = getInputBlockStream(srcFile, changeMap)) {
headerTransferRequired = !in.includesHeaderBlock();
srcBlockCnt = in.getBlockCount();
monitor.initialize(srcBlockCnt + 2);
try (OutputBlockStream out = getOutputBlockStream(destFile, in.getBlockCount())) {
completeBlockStreamTransfer(in, out, monitor);
}
}
finally {
// circumvent other exceptions if cancelled
monitor.checkCanceled();
}
if (headerTransferRequired) {
destFile.clearParameters();
String[] parmNames = srcFile.getParameterNames();
for (String name : parmNames) {
destFile.setParameter(name, srcFile.getParameter(name));
}
monitor.setProgress(srcBlockCnt + 1);
// Copy free index list
destFile.setFreeIndexes(srcFile.getFreeIndexes());
}
monitor.setProgress(srcBlockCnt + 2);
}
/**
* Perform a complete block stream transfer from in to out
* @param in input block stream
* @param out output block stream
* @param monitor progress and cancel monitor
* @throws CancelledException
* @throws IOException
*/
static void completeBlockStreamTransfer(InputBlockStream in, OutputBlockStream out,
TaskMonitor monitor) throws CancelledException, IOException {
int count = 0;
try (BlockStreamCancelMonitor cancelMonitor =
new BlockStreamCancelMonitor(monitor, in, out)) {
int srcBlockCnt = in.getBlockCount();
BufferFileBlock block;
while ((block = in.readBlock()) != null) {
monitor.checkCanceled();
out.writeBlock(block);
monitor.setProgress(count++);
}
if (count != srcBlockCnt) {
throw new IOException("unexpected block transfer count");
}
}
} }
/** /**
* <code>BlockStreamCancelMonitor</code> is used to close associated BlockStreams * <code>BlockStreamCancelMonitor</code> is used to close associated BlockStreams
* when a TaskMonitor is cancelled * when a TaskMonitor is cancelled
*/ */
static class BlockStreamCancelMonitor implements Closeable, CancelledListener { private static class BlockStreamCancelMonitor implements Closeable, CancelledListener {
private TaskMonitor monitor; private TaskMonitor monitor;
private BlockStream[] blockStreams; private BlockStream[] blockStreams;
@@ -1467,36 +1507,23 @@ public class LocalBufferFile implements BufferFile {
} }
/** /**
* Attempt to remove all pre-save files. * A simple interface that allows for dependency injection
* Those still open by an existing process should
* not be removed by the operating system.
* @param dir data directory containing presave files
* @param beforeNow if not 0, file mod time must be less than the specified time
*/ */
public static void cleanupOldPreSaveFiles(File dir, long beforeNow) { public static interface InputBlockStreamFactory {
File[] oldFiles = dir.listFiles(new BufferFileFilter(null, PRESAVE_FILE_EXT)); public InputBlockStream createInputBlockStream(LocalBufferFile bf) throws IOException;
if (oldFiles == null) {
return;
}
for (File oldFile : oldFiles) {
if ((beforeNow == 0 || oldFile.lastModified() < beforeNow) &&
oldFile.delete()) {
Msg.info(LocalBufferFile.class, "Removed old presave file: " + oldFile);
}
}
} }
/** /**
* Get the recommended buffer size given a target buffer size. * A factory to supply the default implementation for create an {@link InputBlockStream}
* @param requestedBufferSize target buffer size
* @return recommended buffer size
*/ */
static int getRecommendedBufferSize(int requestedBufferSize) { private static class DefaultInputBlockStreamFactory implements InputBlockStreamFactory {
int size = (requestedBufferSize + BUFFER_PREFIX_SIZE) & -MINIMUM_BLOCK_SIZE; @Override
if (size <= 0) { public InputBlockStream createInputBlockStream(LocalBufferFile bf) throws IOException {
size = MINIMUM_BLOCK_SIZE; if (!bf.readOnly) {
throw new IOException("Read stream only permitted on read-only buffer file");
}
return bf.new LocalFileInputBlockStream();
} }
return size - BUFFER_PREFIX_SIZE;
} }
// private void checkSameContent(BufferFile expectedBf, BufferFile inspectedBf) throws IOException { // private void checkSameContent(BufferFile expectedBf, BufferFile inspectedBf) throws IOException {

View File

@@ -27,14 +27,17 @@ import javax.swing.table.TableColumn;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import docking.test.AbstractDockingTest;
import docking.widgets.table.*; import docking.widgets.table.*;
import docking.widgets.table.constraint.ColumnConstraint; import docking.widgets.table.constraint.ColumnConstraint;
import docking.widgets.table.constraint.MappedColumnConstraint; import docking.widgets.table.constraint.MappedColumnConstraint;
import docking.widgets.table.constraint.dialog.*; import docking.widgets.table.constraint.dialog.*;
import docking.widgets.table.constraint.provider.*; import docking.widgets.table.constraint.provider.*;
import ghidra.framework.options.SaveState; import ghidra.framework.options.SaveState;
import mockit.Mock; import ghidra.util.Msg;
import mockit.MockUp; import ghidra.util.classfinder.ClassSearcher;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
/** /**
* This test performs operations on swing components in the test thread. I believe this is ok * This test performs operations on swing components in the test thread. I believe this is ok
@@ -42,29 +45,17 @@ import mockit.MockUp;
* involved. If this test has displays intermittent failures, then more work will be needed to * involved. If this test has displays intermittent failures, then more work will be needed to
* fix the threading when accessing swing components. * fix the threading when accessing swing components.
*/ */
public class ColumnTableFilterTest { public class ColumnTableFilterTest extends AbstractDockingTest {
private SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("MM/dd/yyyy"); private SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("MM/dd/yyyy");
private RowObjectFilterModel<Integer> tableModel; private RowObjectFilterModel<Integer> tableModel;
private GTable gTable; private GTable gTable;
private ColumnFilterDialogModel<Integer> filterModel; private ColumnFilterDialogModel<Integer> filterModel;
private List<ColumnConstraint<?>> allConstraints;
@Before @Before
public void setup() { public void setup() throws Exception {
allConstraints = loadConstraints();
// using a mock up to load discoverable column filters without performing a class search. ClassSearcher.search(TaskMonitor.DUMMY);
new MockUp<DiscoverableTableUtils>() {
@Mock
public List<ColumnConstraint<?>> getColumnConstraints(Class<?> columnType) {
List<ColumnConstraint<?>> matches = new ArrayList<>();
for (ColumnConstraint<?> columnConstraint : allConstraints) {
if (columnConstraint.getColumnType().equals(columnType)) {
matches.add(columnConstraint);
}
}
return matches;
}
};
tableModel = createTableModel(); tableModel = createTableModel();
gTable = new GTable(tableModel); gTable = new GTable(tableModel);
filterModel = new ColumnFilterDialogModel<>(tableModel, gTable.getColumnModel(), null); filterModel = new ColumnFilterDialogModel<>(tableModel, gTable.getColumnModel(), null);

View File

@@ -23,13 +23,13 @@ import java.io.IOException;
import org.junit.*; import org.junit.*;
import db.buffers.*; import db.buffers.*;
import db.buffers.LocalBufferFile.InputBlockStreamFactory;
import generic.test.AbstractGenericTest; import generic.test.AbstractGenericTest;
import ghidra.framework.store.local.LocalFileSystem; import ghidra.framework.store.local.LocalFileSystem;
import ghidra.framework.store.local.LocalFolderItem; import ghidra.framework.store.local.LocalFolderItem;
import ghidra.util.InvalidNameException; import ghidra.util.InvalidNameException;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
import mockit.*;
import utilities.util.FileUtilities; import utilities.util.FileUtilities;
public class VersionFailureRecoveryTest extends AbstractGenericTest { public class VersionFailureRecoveryTest extends AbstractGenericTest {
@@ -65,7 +65,7 @@ public class VersionFailureRecoveryTest extends AbstractGenericTest {
@Test @Test
public void testAddToVersionControlFailure() { public void testAddToVersionControlFailure() {
new FakeBadBufferFile(); // setup for mocking setInstanceField("inputBlockStreamFactory", LocalBufferFile.class, factory);
LocalBufferFile fakeBadBufferFile = null; LocalBufferFile fakeBadBufferFile = null;
try { try {
@@ -99,17 +99,54 @@ public class VersionFailureRecoveryTest extends AbstractGenericTest {
} }
private class FakeBadBufferFile extends MockUp<LocalBufferFile> { private InputBlockStreamFactory factory = new InputBlockStreamFactory() {
@Mock @Override
public InputBlockStream createInputBlockStream(LocalBufferFile bf) throws IOException {
return new InputBlockStream() {
@Override
public boolean includesHeaderBlock() {
return true;
}
@Override
public void close() throws IOException {
// ignore
}
@Override
public int getBlockSize() {
return bf.getBufferSize();
}
@Override
public BufferFileBlock readBlock() throws IOException {
throw new IOException("forced block read failure");
}
@Override
public int getBlockCount() {
return bf.getIndexCount();
}
};
}
};
/*
private class FakeBadBufferFile extends LocalBufferFile {
@Override
public int getIndexCount() { public int getIndexCount() {
return 10; return 10;
} }
@Mock @Override
public InputBlockStream getInputBlockStream(Invocation invocation) { public InputBlockStream getInputBlockStream() {
LocalBufferFile bufferFile = invocation.getInvokedInstance(); LocalBufferFile bufferFile = null;
return new InputBlockStream() { return new InputBlockStream() {
@@ -140,5 +177,5 @@ public class VersionFailureRecoveryTest extends AbstractGenericTest {
}; };
} }
} }*/
} }