diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/data/ProjectFileManagerTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/data/ProjectFileManagerTest.java index 577a3f7b32..4a1a59efac 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/data/ProjectFileManagerTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/data/ProjectFileManagerTest.java @@ -19,8 +19,8 @@ import static org.junit.Assert.*; import java.io.File; import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; +import java.util.*; +import java.util.concurrent.TimeUnit; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -29,13 +29,13 @@ import org.junit.*; import generic.test.TestUtils; import ghidra.framework.model.*; import ghidra.framework.store.FileSystem; -import ghidra.framework.store.FileSystemListenerList; +import ghidra.framework.store.FileSystemEventManager; import ghidra.framework.store.local.LocalFileSystem; import ghidra.program.database.ProgramDB; import ghidra.program.model.lang.Language; import ghidra.program.model.listing.Program; import ghidra.test.AbstractGhidraHeadedIntegrationTest; -import ghidra.util.task.TaskMonitorAdapter; +import ghidra.util.task.TaskMonitor; public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest { @@ -45,11 +45,7 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest private LocalFileSystem privateFS; private ProjectFileManager fileMgr; private DomainFolder root; - private ArrayList events = new ArrayList<>(); - - public ProjectFileManagerTest() { - super(); - } + private List events = new ArrayList<>(); @Before public void setUp() throws Exception { @@ -107,16 +103,16 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest } private void flushFileSystemEvents() { - FileSystemListenerList privateListenerList = - (FileSystemListenerList) TestUtils.getInstanceField("listeners", privateFS); - FileSystemListenerList sharedListenerList = - (FileSystemListenerList) TestUtils.getInstanceField("listeners", sharedFS); + FileSystemEventManager privateEventManager = + (FileSystemEventManager) TestUtils.getInstanceField("eventManager", privateFS); + FileSystemEventManager sharedEventManager = + (FileSystemEventManager) TestUtils.getInstanceField("eventManager", sharedFS); - flushTheseEvents(privateListenerList); - flushTheseEvents(sharedListenerList); + flushTheseEvents(privateEventManager); + flushTheseEvents(sharedEventManager); } - private void flushTheseEvents(FileSystemListenerList listenerList) { + private void flushTheseEvents(FileSystemEventManager eventManager) { // Events get added synchronously, but processed asynchronously, so we can check to see // if any have been added by an action we triggered without waiting. Also, we know that // no more events will get added, since we are the thread (the main thread) doing the @@ -124,14 +120,12 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest // // If there are queued actions, then we have to kick the handling thread and // let it finish running. - while (listenerList.isProcessingEvents()) { - // give the event tread some time to send events - try { - Thread.sleep(100); - } - catch (InterruptedException e) { - // don't care, we will try again - } + + try { + assertTrue(eventManager.flushEvents(DEFAULT_WAIT_TIMEOUT, TimeUnit.MILLISECONDS)); + } + catch (InterruptedException e) { + failWithException("Interrupted waiting for filesystem events", e); } } @@ -180,7 +174,7 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest Language language = getSLEIGH_X86_LANGUAGE(); Program p = new ProgramDB(name, language, language.getDefaultCompilerSpec(), this); try { - return folder.createFile(name, p, TaskMonitorAdapter.DUMMY_MONITOR); + return folder.createFile(name, p, TaskMonitor.DUMMY); } finally { p.release(this); @@ -215,7 +209,7 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest assertNotNull(df2); assertEquals("file2", df.getName()); - df1.addToVersionControl("", false, TaskMonitorAdapter.DUMMY_MONITOR); + df1.addToVersionControl("", false, TaskMonitor.DUMMY); df = fileMgr.getFileByID(fileID1); assertNotNull(df1); @@ -262,7 +256,7 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest DomainFile df1 = createFile(folder, "file1"); String fileID = df1.getFileID(); - df1.addToVersionControl("", true, TaskMonitorAdapter.DUMMY_MONITOR); + df1.addToVersionControl("", true, TaskMonitor.DUMMY); assertEquals(fileID, df1.getFileID()); df1.undoCheckout(true); @@ -282,7 +276,7 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest // create shared file /a/file1 and keep checked-out DomainFile df1 = createFile(folder, "file1"); String fileID = df1.getFileID(); - df1.addToVersionControl("", true, TaskMonitorAdapter.DUMMY_MONITOR); + df1.addToVersionControl("", true, TaskMonitor.DUMMY); assertEquals(fileID, df1.getFileID()); // Force Hijack - terminate checkout at versioned file-system diff --git a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/FileSystemListenerList.java b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/FileSystemEventManager.java similarity index 59% rename from Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/FileSystemListenerList.java rename to Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/FileSystemEventManager.java index 16b3c4f24e..6df9e92f98 100644 --- a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/FileSystemListenerList.java +++ b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/FileSystemEventManager.java @@ -15,8 +15,8 @@ */ package ghidra.framework.store; -import java.util.*; -import java.util.concurrent.CopyOnWriteArrayList; +import java.util.List; +import java.util.concurrent.*; /** * FileSystemListenerList maintains a list of FileSystemListener's. @@ -24,32 +24,31 @@ import java.util.concurrent.CopyOnWriteArrayList; * all FileSystemListener's within its list. Employs either a synchronous * and asynchronous notification mechanism. */ -public class FileSystemListenerList implements FileSystemListener { +public class FileSystemEventManager implements FileSystemListener { - private List listenerList = new CopyOnWriteArrayList<>(); + private List listeners = new CopyOnWriteArrayList<>(); + private BlockingQueue eventQueue = new LinkedBlockingQueue<>(); - private List events = - Collections.synchronizedList(new LinkedList()); - - private boolean enableAsynchronousDispatching; - private boolean isEventProcessingThreadWaiting; - private boolean alive = true; - private Object lock = new Object(); + private volatile boolean disposed = false; private Thread thread; /** - * Construct FileSystemListenerList + * Constructor * @param enableAsynchronousDispatching if true a separate dispatch thread will be used * to notify listeners. If false, blocking notification will be performed. */ - public FileSystemListenerList(boolean enableAsynchronousDispatching) { - this.enableAsynchronousDispatching = enableAsynchronousDispatching; + public FileSystemEventManager(boolean enableAsynchronousDispatching) { + + if (enableAsynchronousDispatching) { + thread = new FileSystemEventProcessingThread(); + thread.start(); + } } public void dispose() { - alive = false; - synchronized (lock) { - lock.notify(); + disposed = true; + if (thread != null) { + thread.interrupt(); } } @@ -57,13 +56,8 @@ public class FileSystemListenerList implements FileSystemListener { * Add a listener to this list. * @param listener the listener */ - public synchronized void add(FileSystemListener listener) { - listenerList.add(listener); - if (thread == null && enableAsynchronousDispatching) { - thread = new FileSystemEventProcessingThread(); - thread.setName("File System Listener"); - thread.start(); - } + public void add(FileSystemListener listener) { + listeners.add(listener); } /** @@ -71,157 +65,106 @@ public class FileSystemListenerList implements FileSystemListener { * @param listener the listener */ public void remove(FileSystemListener listener) { - listenerList.remove(listener); - } - - /** - * Remove all listeners from this list. - */ - public void clear() { - listenerList.clear(); + listeners.remove(listener); } @Override public void itemMoved(String parentPath, String name, String newParentPath, String newName) { - if (enableAsynchronousDispatching) { - add(new ItemMovedEvent(parentPath, name, newParentPath, newName)); - } - else { - for (FileSystemListener l : listenerList) { - l.itemMoved(parentPath, name, newParentPath, newName); - } - } + handleEvent(new ItemMovedEvent(parentPath, name, newParentPath, newName)); } @Override public void itemRenamed(String parentPath, String itemName, String newName) { - if (enableAsynchronousDispatching) { - add(new ItemRenamedEvent(parentPath, itemName, newName)); - } - else { - for (FileSystemListener l : listenerList) { - l.itemRenamed(parentPath, itemName, newName); - } - } + handleEvent(new ItemRenamedEvent(parentPath, itemName, newName)); } @Override public void itemDeleted(String parentPath, String itemName) { - if (enableAsynchronousDispatching) { - add(new ItemDeletedEvent(parentPath, itemName)); - } - else { - for (FileSystemListener l : listenerList) { - l.itemDeleted(parentPath, itemName); - } - } + handleEvent(new ItemDeletedEvent(parentPath, itemName)); } @Override public void folderRenamed(String parentPath, String folderName, String newFolderName) { - if (enableAsynchronousDispatching) { - add(new FolderRenamedEvent(parentPath, folderName, newFolderName)); - } - else { - for (FileSystemListener l : listenerList) { - l.folderRenamed(parentPath, folderName, newFolderName); - } - } + handleEvent(new FolderRenamedEvent(parentPath, folderName, newFolderName)); } @Override public void folderMoved(String parentPath, String folderName, String newParentPath) { - if (enableAsynchronousDispatching) { - add(new FolderMovedEvent(parentPath, folderName, newParentPath)); - } - else { - for (FileSystemListener l : listenerList) { - l.folderMoved(parentPath, folderName, newParentPath); - } - } + handleEvent(new FolderMovedEvent(parentPath, folderName, newParentPath)); } @Override public void folderDeleted(String parentPath, String folderName) { - if (enableAsynchronousDispatching) { - add(new FolderDeletedEvent(parentPath, folderName)); - } - else { - for (FileSystemListener l : listenerList) { - l.folderDeleted(parentPath, folderName); - } - } + handleEvent(new FolderDeletedEvent(parentPath, folderName)); } @Override public void itemCreated(String parentPath, String itemName) { - if (enableAsynchronousDispatching) { - add(new ItemCreatedEvent(parentPath, itemName)); - } - else { - for (FileSystemListener l : listenerList) { - l.itemCreated(parentPath, itemName); - } - } + handleEvent(new ItemCreatedEvent(parentPath, itemName)); } @Override public void folderCreated(String parentPath, String folderName) { - if (enableAsynchronousDispatching) { - add(new FolderCreatedEvent(parentPath, folderName)); - } - else { - for (FileSystemListener l : listenerList) { - l.folderCreated(parentPath, folderName); - } - } + handleEvent(new FolderCreatedEvent(parentPath, folderName)); } @Override public void itemChanged(String parentPath, String itemName) { - if (enableAsynchronousDispatching) { - add(new ItemChangedEvent(parentPath, itemName)); - } - else { - for (FileSystemListener l : listenerList) { - l.itemChanged(parentPath, itemName); - } - } + handleEvent(new ItemChangedEvent(parentPath, itemName)); } @Override public void syncronize() { - if (enableAsynchronousDispatching) { + // Note: synchronize calls will only work when using a threaded event queue + if (isAsynchronous()) { add(new SynchronizeEvent()); } } + private boolean isAsynchronous() { + return thread != null; + } + private void add(FileSystemEvent ev) { - if (!listenerList.isEmpty()) { - events.add(ev); - synchronized (lock) { - lock.notify(); - } + if (!listeners.isEmpty()) { + eventQueue.add(ev); + } + } + + private void handleEvent(FileSystemEvent e) { + if (disposed) { + return; + } + + if (isAsynchronous()) { + add(e); + } + else { + e.process(listeners); } } /** - * Returns true if this class is processing events or needs to process events that are - * in its event queue. + * Blocks until all current events have been processed. + *

+ * Note: clients should only use this method when {@link #isAsynchronous()} returns true, since + * this class cannot track when non-threaded events have finished broadcasting to listeners. + * In a synchronous use case, any test that needs to know when client events have been processed + * must use some other mechanism to know when event processing is finished. * - * @return true if this class is processing events or needs to process events that are - * in its event queue. + * @param timeout the maximum time to wait + * @param unit the time unit of the {@code time} argument + * @return true if the events were processed in the given timeout + * @throws InterruptedException if this waiting thread is interrupted */ - public boolean isProcessingEvents() { - synchronized (this) { - if (thread == null) { - return false; // non-threaded; does not 'process' events, done synchronously - } + public boolean flushEvents(long timeout, TimeUnit unit) throws InterruptedException { + if (!isAsynchronous()) { + return true; // each thread processes its own event } - synchronized (lock) { // lock so nobody adds new events - return !isEventProcessingThreadWaiting || (events.size() > 0); - } + MarkerEvent event = new MarkerEvent(); + eventQueue.add(event); + return event.waitForEvent(timeout, unit); } //================================================================================================== @@ -231,42 +174,25 @@ public class FileSystemListenerList implements FileSystemListener { private class FileSystemEventProcessingThread extends Thread { FileSystemEventProcessingThread() { - super("File System Event Processor"); + super("File System Listener"); setDaemon(true); } @Override public void run() { - while (alive) { - while (!events.isEmpty()) { - FileSystemEvent event; - synchronized (lock) { - event = events.remove(0); - } - synchronized (FileSystemListenerList.this) { - for (FileSystemListener l : listenerList) { - event.dispatch(l); - } - } - } - doWait(); - } - } + while (!disposed) { - private void doWait() { - try { - synchronized (lock) { - if (alive && events.isEmpty()) { - isEventProcessingThreadWaiting = true; - lock.wait(); - } + FileSystemEvent event; + try { + event = eventQueue.take(); + event.process(listeners); + } + catch (InterruptedException e) { + // interrupt has been cleared; if other threads rely on this interrupted state, + // then mark the thread as interrupted again by calling: + // Thread.currentThread().interrupt(); + // For now, this code relies on the 'alive' flag to know when to terminate } - } - catch (InterruptedException e) { - // not sure why we are ignoring this - } - finally { - isEventProcessingThreadWaiting = false; } } } @@ -284,7 +210,18 @@ public class FileSystemListenerList implements FileSystemListener { this.newName = newName; } + void process(List listeners) { + for (FileSystemListener l : listeners) { + dispatch(l); + } + } + abstract void dispatch(FileSystemListener listener); + + @Override + public String toString() { + return getClass().getSimpleName(); + } } private static class ItemMovedEvent extends FileSystemEvent { @@ -396,4 +333,29 @@ public class FileSystemListenerList implements FileSystemListener { listener.syncronize(); } } + + // an event used by the flush method to mark when current events have been processed + private static class MarkerEvent extends FileSystemEvent { + + private CountDownLatch latch = new CountDownLatch(1); + + MarkerEvent() { + super(null, null, null, null); + } + + @Override + void dispatch(FileSystemListener listener) { + // we don't actually process the event + } + + @Override + void process(List listeners) { + latch.countDown(); + } + + boolean waitForEvent(long timeout, TimeUnit unit) throws InterruptedException { + return latch.await(timeout, unit); + } + } + } diff --git a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/local/IndexedLocalFileSystem.java b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/local/IndexedLocalFileSystem.java index 8aa032032c..23017290fd 100644 --- a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/local/IndexedLocalFileSystem.java +++ b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/local/IndexedLocalFileSystem.java @@ -81,7 +81,7 @@ public class IndexedLocalFileSystem extends LocalFileSystem { /** * Constructor. - * @param file path path for root directory. + * @param rootPath path path for root directory. * @param isVersioned if true item versioning will be enabled. * @param readOnly if true modifications within this file-system will not be allowed * and result in an ReadOnlyException @@ -740,7 +740,7 @@ public class IndexedLocalFileSystem extends LocalFileSystem { subfolder.name = name; folder.folders.put(name, subfolder); if (option == GetFolderOption.CREATE_ALL_NOTIFY) { - listeners.folderCreated(folder.getPathname(), name); + eventManager.folderCreated(folder.getPathname(), name); } } else { @@ -943,7 +943,7 @@ public class IndexedLocalFileSystem extends LocalFileSystem { indexJournal.close(); } - listeners.folderCreated(parentPath, getName(path)); + eventManager.folderCreated(parentPath, getName(path)); } /* @@ -978,7 +978,7 @@ public class IndexedLocalFileSystem extends LocalFileSystem { indexJournal.close(); } - listeners.folderDeleted(getParentPath(folderPath), getName(folderPath)); + eventManager.folderDeleted(getParentPath(folderPath), getName(folderPath)); } void migrateItem(LocalFolderItem item) throws IOException { @@ -1085,10 +1085,10 @@ public class IndexedLocalFileSystem extends LocalFileSystem { } if (folderPath.equals(newFolderPath)) { - listeners.itemRenamed(folderPath, name, newName); + eventManager.itemRenamed(folderPath, name, newName); } else { - listeners.itemMoved(folderPath, name, newFolderPath, newName); + eventManager.itemMoved(folderPath, name, newFolderPath, newName); } deleteEmptyVersionedFolders(folderPath); @@ -1142,7 +1142,7 @@ public class IndexedLocalFileSystem extends LocalFileSystem { updateAffectedItemPaths(folder); - listeners.folderMoved(parentPath, folderName, newParentPath); + eventManager.folderMoved(parentPath, folderName, newParentPath); deleteEmptyVersionedFolders(parentPath); } @@ -1198,7 +1198,7 @@ public class IndexedLocalFileSystem extends LocalFileSystem { updateAffectedItemPaths(folder); - listeners.folderRenamed(parentPath, folderName, newFolderName); + eventManager.folderRenamed(parentPath, folderName, newFolderName); } /* diff --git a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/local/LocalFileSystem.java b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/local/LocalFileSystem.java index c8bf0d68d5..a0b3725cde 100644 --- a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/local/LocalFileSystem.java +++ b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/local/LocalFileSystem.java @@ -74,7 +74,7 @@ public abstract class LocalFileSystem implements FileSystem { protected final File root; protected final boolean isVersioned; protected final boolean readOnly; - protected final FileSystemListenerList listeners; + protected final FileSystemEventManager eventManager; private RepositoryLogger repositoryLogger; @@ -187,7 +187,7 @@ public abstract class LocalFileSystem implements FileSystem { this.isVersioned = isVersioned; this.readOnly = readOnly; - listeners = new FileSystemListenerList(enableAsyncronousDispatching); + eventManager = new FileSystemEventManager(enableAsyncronousDispatching); } @@ -205,7 +205,7 @@ public abstract class LocalFileSystem implements FileSystem { this.root = null; this.isVersioned = false; this.readOnly = true; - listeners = null; + eventManager = null; } private void cleanupTemporaryFiles(String folderPath) { @@ -542,7 +542,7 @@ public abstract class LocalFileSystem implements FileSystem { } } - listeners.itemCreated(parentPath, name); + eventManager.itemCreated(parentPath, name); return dataFile; } @@ -632,10 +632,10 @@ public abstract class LocalFileSystem implements FileSystem { success = true; if (folderPath.equals(newFolderPath)) { - listeners.itemRenamed(folderPath, name, newName); + eventManager.itemRenamed(folderPath, name, newName); } else { - listeners.itemMoved(folderPath, name, newFolderPath, newName); + eventManager.itemMoved(folderPath, name, newFolderPath, newName); } deleteEmptyVersionedFolders(folderPath); deallocateItemStorage(folderPath, name); @@ -675,8 +675,8 @@ public abstract class LocalFileSystem implements FileSystem { */ @Override public void addFileSystemListener(FileSystemListener listener) { - if (listeners != null) { - listeners.add(listener); + if (eventManager != null) { + eventManager.add(listener); } } @@ -685,8 +685,8 @@ public abstract class LocalFileSystem implements FileSystem { */ @Override public void removeFileSystemListener(FileSystemListener listener) { - if (listeners != null) { - listeners.remove(listener); + if (eventManager != null) { + eventManager.remove(listener); } } @@ -694,7 +694,7 @@ public abstract class LocalFileSystem implements FileSystem { * Returns file system listener. */ FileSystemListener getListener() { - return listeners; + return eventManager; } /** @@ -844,8 +844,8 @@ public abstract class LocalFileSystem implements FileSystem { @Override public void dispose() { - if (listeners != null) { - listeners.dispose(); + if (eventManager != null) { + eventManager.dispose(); } } diff --git a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/local/MangledLocalFileSystem.java b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/local/MangledLocalFileSystem.java index f37c7b97fd..df6695c79a 100644 --- a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/local/MangledLocalFileSystem.java +++ b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/local/MangledLocalFileSystem.java @@ -1,6 +1,5 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,14 +15,13 @@ */ package ghidra.framework.store.local; -import ghidra.framework.store.FolderNotEmptyException; -import ghidra.util.*; -import ghidra.util.exception.DuplicateFileException; - import java.io.*; import java.util.ArrayList; import java.util.Collections; +import ghidra.framework.store.FolderNotEmptyException; +import ghidra.util.*; +import ghidra.util.exception.DuplicateFileException; import utilities.util.FileUtilities; /** @@ -150,12 +148,12 @@ public class MangledLocalFileSystem extends LocalFileSystem { if (dirList == null) { throw new FileNotFoundException("Folder " + folderPath + " not found"); } - ArrayList fileList = new ArrayList(dirList.length); - for (int i = 0; i < dirList.length; i++) { - String name = dirList[i].getName(); - if (name.endsWith(PROPERTY_EXT) && dirList[i].isFile()) { - if (!NamingUtilities.isValidMangledName(dirList[i].getName())) { - log.warn("Ignoring property file with bad name: " + dirList[i]); + ArrayList fileList = new ArrayList<>(dirList.length); + for (File element : dirList) { + String name = element.getName(); + if (name.endsWith(PROPERTY_EXT) && element.isFile()) { + if (!NamingUtilities.isValidMangledName(element.getName())) { + log.warn("Ignoring property file with bad name: " + element); continue; } int index = name.lastIndexOf(PROPERTY_EXT); @@ -172,6 +170,7 @@ public class MangledLocalFileSystem extends LocalFileSystem { /* * @see ghidra.framework.store.FileSystem#getFolders(java.lang.String) */ + @Override public synchronized String[] getFolderNames(String folderPath) throws IOException { File dir = getFile(folderPath); @@ -179,12 +178,12 @@ public class MangledLocalFileSystem extends LocalFileSystem { if (dirList == null) { throw new FileNotFoundException("Folder " + folderPath + " not found"); } - ArrayList folderList = new ArrayList(dirList.length); - for (int i = 0; i < dirList.length; i++) { - if (!dirList[i].isDirectory()) { + ArrayList folderList = new ArrayList<>(dirList.length); + for (File element : dirList) { + if (!element.isDirectory()) { continue; } - String name = demangleName(dirList[i].getName()); + String name = demangleName(element.getName()); if (name != null) { folderList.add(name); } @@ -196,6 +195,7 @@ public class MangledLocalFileSystem extends LocalFileSystem { /* * @see ghidra.framework.store.FileSystem#createFolder(java.lang.String, java.lang.String) */ + @Override public synchronized void createFolder(String parentPath, String folderName) throws InvalidNameException, IOException { @@ -218,6 +218,7 @@ public class MangledLocalFileSystem extends LocalFileSystem { /* * @see ghidra.framework.store.FileSystem#deleteFolder(java.lang.String) */ + @Override public synchronized void deleteFolder(String folderPath) throws IOException { if (readOnly) { @@ -243,12 +244,13 @@ public class MangledLocalFileSystem extends LocalFileSystem { } FileUtilities.deleteDir(file); - listeners.folderDeleted(getParentPath(folderPath), getName(folderPath)); + eventManager.folderDeleted(getParentPath(folderPath), getName(folderPath)); } /* * @see ghidra.framework.store.FileSystem#moveFolder(java.lang.String, java.lang.String, java.lang.String) */ + @Override public synchronized void moveFolder(String parentPath, String folderName, String newParentPath) throws InvalidNameException, IOException { @@ -277,7 +279,7 @@ public class MangledLocalFileSystem extends LocalFileSystem { throw new IOException("move failed for unknown reason"); } - listeners.folderMoved(parentPath, folderName, newParentPath); + eventManager.folderMoved(parentPath, folderName, newParentPath); deleteEmptyVersionedFolders(parentPath); } finally { @@ -290,7 +292,9 @@ public class MangledLocalFileSystem extends LocalFileSystem { /* * @see ghidra.framework.store.FileSystem#renameFolder(java.lang.String, java.lang.String, java.lang.String) */ - public synchronized void renameFolder(String parentPath, String folderName, String newFolderName) + @Override + public synchronized void renameFolder(String parentPath, String folderName, + String newFolderName) throws InvalidNameException, IOException { if (readOnly) { @@ -315,7 +319,7 @@ public class MangledLocalFileSystem extends LocalFileSystem { throw new IOException("Folder may contain files that are in use"); } - listeners.folderRenamed(parentPath, folderName, newFolderName); + eventManager.folderRenamed(parentPath, folderName, newFolderName); } /** @@ -329,7 +333,8 @@ public class MangledLocalFileSystem extends LocalFileSystem { throw new FileNotFoundException("Empty read-only file system"); } if (path.charAt(0) != SEPARATOR_CHAR) { - throw new FileNotFoundException("Path names must begin with \'" + SEPARATOR_CHAR + "\'"); + throw new FileNotFoundException( + "Path names must begin with \'" + SEPARATOR_CHAR + "\'"); } if (path.length() == 1) { return root; @@ -344,9 +349,9 @@ public class MangledLocalFileSystem extends LocalFileSystem { } StringBuilder buf = new StringBuilder(); String[] split = path.split(SEPARATOR); - for (int i = 0; i < split.length; i++) { + for (String element : split) { buf.append(SEPARATOR_CHAR); - buf.append(escapeHiddenDirPrefixChars(split[i])); + buf.append(escapeHiddenDirPrefixChars(element)); } return NamingUtilities.mangle(buf.toString()); } @@ -399,7 +404,7 @@ public class MangledLocalFileSystem extends LocalFileSystem { String parentPath = getParentPath(folderPath); createFolders(parentDir, parentPath); folderDir.mkdir(); - listeners.folderCreated(parentPath, getName(folderPath)); + eventManager.folderCreated(parentPath, getName(folderPath)); } /* @@ -443,7 +448,8 @@ public class MangledLocalFileSystem extends LocalFileSystem { } IndexedV1LocalFileSystem indexedFs = - new IndexedV1LocalFileSystem(tmpRoot.getAbsolutePath(), isVersioned, false, false, true); + new IndexedV1LocalFileSystem(tmpRoot.getAbsolutePath(), isVersioned, false, false, + true); migrationInProgress = true; migrateFolder(SEPARATOR, indexedFs); diff --git a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/remote/RemoteFileSystem.java b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/remote/RemoteFileSystem.java index a19cb2d84b..78ab9205a8 100644 --- a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/remote/RemoteFileSystem.java +++ b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/remote/RemoteFileSystem.java @@ -41,7 +41,7 @@ import ghidra.util.task.TaskMonitor; public class RemoteFileSystem implements FileSystem, RemoteAdapterListener { private RepositoryAdapter repository; - private FileSystemListenerList listeners = new FileSystemListenerList(true); + private FileSystemEventManager eventManager = new FileSystemEventManager(true); /** * Construct a new remote file system which corresponds to a remote repository. @@ -49,7 +49,7 @@ public class RemoteFileSystem implements FileSystem, RemoteAdapterListener { */ public RemoteFileSystem(RepositoryAdapter repository) { this.repository = repository; - repository.setFileSystemListener(listeners); + repository.setFileSystemListener(eventManager); repository.addListener(this); } @@ -65,12 +65,12 @@ public class RemoteFileSystem implements FileSystem, RemoteAdapterListener { @Override public void addFileSystemListener(FileSystemListener listener) { - listeners.add(listener); + eventManager.add(listener); } @Override public void removeFileSystemListener(FileSystemListener listener) { - listeners.remove(listener); + eventManager.remove(listener); } @Override @@ -230,13 +230,13 @@ public class RemoteFileSystem implements FileSystem, RemoteAdapterListener { @Override public void connectionStateChanged(Object adapter) { if (adapter == repository) { - listeners.syncronize(); + eventManager.syncronize(); } } @Override public void dispose() { - listeners.dispose(); + eventManager.dispose(); } } diff --git a/Ghidra/Framework/FileSystem/src/test.slow/java/ghidra/framework/store/local/AbstractLocalFileSystemTest.java b/Ghidra/Framework/FileSystem/src/test.slow/java/ghidra/framework/store/local/AbstractLocalFileSystemTest.java index 23975e7b91..f74cfa8538 100644 --- a/Ghidra/Framework/FileSystem/src/test.slow/java/ghidra/framework/store/local/AbstractLocalFileSystemTest.java +++ b/Ghidra/Framework/FileSystem/src/test.slow/java/ghidra/framework/store/local/AbstractLocalFileSystemTest.java @@ -18,15 +18,14 @@ package ghidra.framework.store.local; import static org.junit.Assert.*; import java.io.*; -import java.util.ArrayList; -import java.util.List; +import java.util.*; +import java.util.concurrent.TimeUnit; import org.junit.*; import db.*; import db.buffers.BufferFile; -import generic.test.AbstractGenericTest; -import generic.test.TestUtils; +import generic.test.*; import ghidra.framework.store.*; import ghidra.util.InvalidNameException; import ghidra.util.PropertyFile; @@ -41,7 +40,7 @@ public abstract class AbstractLocalFileSystemTest extends AbstractGenericTest { LocalFileSystem fs; File projectDir; - ArrayList events = new ArrayList<>(); + List events = new ArrayList<>(); public AbstractLocalFileSystemTest(boolean useIndexedFileSystem) { super(); @@ -51,7 +50,7 @@ public abstract class AbstractLocalFileSystemTest extends AbstractGenericTest { @Before public void setUp() throws Exception { - File tempDir = new File(AbstractGenericTest.getTestDirectoryPath()); + File tempDir = new File(AbstractGTest.getTestDirectoryPath()); projectDir = new File(tempDir, "testproject"); FileUtilities.deleteDir(projectDir); projectDir.mkdir(); @@ -114,6 +113,7 @@ public abstract class AbstractLocalFileSystemTest extends AbstractGenericTest { Assert.fail(); } catch (IOException e) { + // expected } try { @@ -121,6 +121,7 @@ public abstract class AbstractLocalFileSystemTest extends AbstractGenericTest { Assert.fail(); } catch (IOException e) { + // expected } } @@ -161,78 +162,6 @@ public abstract class AbstractLocalFileSystemTest extends AbstractGenericTest { checkEvent("Folder Created", "/a1/a2/a3", "a4", null, null, events.get(3)); } -// @Test -// public void testFolderPathTooLong() throws Exception { -// -// StringBuffer sb = new StringBuffer(); -// int projectDirLength = projectDir.getAbsolutePath().length(); -// int i = 0; -// while (sb.length() + projectDirLength < 248) { -// sb.append("/a" + i); -// ++i; -// } -// try { -// fs.createFolder(sb.toString(), "aaaa"); -// Assert.fail("Should have gotten IO exception on too long of a filename!"); -// } -// catch (IOException e) { -// } -// } -// -// @Test -// public void testDataFilePathTooLong() throws Exception { -// fs.createFolder("/", "abc"); -// String data = "This is a test"; -// byte[] dataBytes = data.getBytes(); -// -// StringBuffer sb = new StringBuffer(); -// int projectDirLength = projectDir.getAbsolutePath().length(); -// int i = 0; -// while (sb.length() + projectDirLength < 248) { -// sb.append("/a" + i); -// ++i; -// } -// try { -// fs.createDataFile(sb.toString(), "freddxxxx", new ByteArrayInputStream(dataBytes), null, -// "Data", null); -// Assert.fail("Should have gotten IO Exception!"); -// } -// catch (IOException e) { -// } -// -// } -// -// public void testDataBasePathTooLong() throws Exception { -// fs.createFolder("/", "abc"); -// DBHandle dbh = new DBHandle(); -// long id = dbh.startTransaction(); -// dbh.createTable("test", new Schema(0, "key", new Class[] { IntField.class }, -// new String[] { "dummy" })); -// dbh.endTransaction(id, true); -// int projectDirLength = projectDir.getAbsolutePath().length(); -// StringBuffer sb = new StringBuffer(); -// int i = 0; -// while (sb.length() + projectDirLength < 248) { -// sb.append("/a" + i); -// ++i; -// } -// try { -// fs.createDatabase(sb.toString(), "freddxxxx", null, "Database", dbh.getBufferSize(), -// "bob", null); -// Assert.fail("Should have gotten IO Exception!"); -// } -// catch (IOException e) { -// } -// } - - /** - * @param string - * @param string2 - * @param string3 - * @param object - * @param object2 - * @param object3 - */ private void checkEvent(String op, String path, String name, String newPath, String newName, Object evObj) { MyEvent event = (MyEvent) evObj; @@ -262,6 +191,7 @@ public abstract class AbstractLocalFileSystemTest extends AbstractGenericTest { Assert.fail(); } catch (FolderNotEmptyException e) { + // expected } } @@ -337,12 +267,14 @@ public abstract class AbstractLocalFileSystemTest extends AbstractGenericTest { Assert.fail(); } catch (FileNotFoundException e) { + // expected } fs.createFolder("/b", "def"); try { fs.moveFolder("/mno/abc", "def", "/b"); } catch (DuplicateFileException e) { + // expected } } @@ -675,8 +607,6 @@ public abstract class AbstractLocalFileSystemTest extends AbstractGenericTest { DataFileItem df = fs.createDataFile("/abc", "fred", new ByteArrayInputStream(dataBytes), null, "Data", null); - DataFileItem df2 = fs.createDataFile("/abc", "bob", new ByteArrayInputStream(dataBytes), - null, "Data", null); createDatabase("/abc", "greg", "123"); String[] items = fs.getItemNames("/abc"); @@ -991,25 +921,43 @@ public abstract class AbstractLocalFileSystemTest extends AbstractGenericTest { @Override public void syncronize() { + // not tracked } } private void flushFileSystemEvents() { - FileSystemListenerList listenerList = - (FileSystemListenerList) TestUtils.getInstanceField("listeners", fs); - while (listenerList.isProcessingEvents()) { - // give the event tread some time to send events - try { - Thread.sleep(100); - } - catch (InterruptedException e) { - // don't care, we will try again - } + FileSystemEventManager eventManager = + (FileSystemEventManager) TestUtils.getInstanceField("eventManager", fs); + + try { + eventManager.flushEvents(DEFAULT_WAIT_TIMEOUT, TimeUnit.MILLISECONDS); + } + catch (InterruptedException e) { + failWithException("Interrupted waiting for filesystem events", e); } } } class MyEvent { + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((name == null) ? 0 : name.hashCode()); + result = prime * result + ((newName == null) ? 0 : newName.hashCode()); + result = prime * result + ((newParentPath == null) ? 0 : newParentPath.hashCode()); + result = prime * result + ((op == null) ? 0 : op.hashCode()); + result = prime * result + ((parentPath == null) ? 0 : parentPath.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + MyEvent other = (MyEvent) obj; + return eq(op, other.op) && eq(parentPath, other.parentPath) && eq(name, other.name) && + eq(newParentPath, other.newParentPath) && eq(newName, other.newName); + } + String op; String parentPath; String name; @@ -1024,18 +972,8 @@ class MyEvent { this.newName = newName; } - @Override - public boolean equals(Object obj) { - MyEvent other = (MyEvent) obj; - return eq(op, other.op) && eq(parentPath, other.parentPath) && eq(name, other.name) && - eq(newParentPath, other.newParentPath) && eq(newName, other.newName); - } - private boolean eq(String s1, String s2) { - if (s1 == null) { - return s2 == null; - } - return s1.equals(s2); + return Objects.equals(s1, s2); } @Override