docs: void externalScan(Set<File> notifiedCreated, Set<File> notifiedDeleted, Set<File> notifiedModified, boolean doFilterPaths, String listenerFilter) {
          // Don't perform the external scan if this monitor holder is paused
          if (isStopped)
              return;
  
          // only do anything if this is an 'external' monitor
          if (!!!FileMonitor.MONITOR_TYPE_EXTERNAL.equals(monitorRef.getProperty(FileMonitor.MONITOR_TYPE)))
              return;
  
          // Give monitoring activity on other threads a chance to catch up before requesting a scan
          // (This is most likely to affect unit test behaviour rather than mbean invocations, but be safe)
          Thread.yield();
  
          // Multiple threads can call the FileNotificationMBean simultaneously so we need to lock
          scanLock.lock();
          try {
              // Always try destroy when we obtain the lock: it will return true if this is in destroy or destroyed state
              // Also (after we have tried doDestroy) ensure that we are in active state
              if (!doDestroy() && (monitorState.get() == MonitorState.ACTIVE.ordinal())) {
                  if (coreService.isDetailedScanTraceEnabled() && TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                      Tr.debug(this, tc, "File monitor scan: begin", updateMonitors);
                  }
  
                  List<File> actualCreated = new ArrayList<File>();
                  List<File> actualDeleted = new ArrayList<File>();
                  List<File> actualModified = new ArrayList<File>();
  
                  scanForUpdates(actualCreated, actualDeleted, actualModified);
  
                  // use the correct case forms of the files we found in our internal scan
                  Set<File> created = PathUtils.fixPathFiles(actualCreated);
                  Set<File> deleted = PathUtils.fixPathFiles(actualDeleted);
                  Set<File> modified = PathUtils.fixPathFiles(actualModified);
  
                  // SPI PathUtils.fixpathFiles returns an empty collection if the file
                  // list is empty, create an actual set so we can add to it later if needed
                  if (created == Collections.EMPTY_SET)
                      created = new HashSet<File>();
                  if (deleted == Collections.EMPTY_SET)
                      deleted = new HashSet<File>();
                  if (modified == Collections.EMPTY_SET)
                      modified = new HashSet<File>();
  
                  // Take the previously unnotified/unrequested changes
                  // and resolve them against the result of the latest
                  // filesystem scan to make sure they are still
                  // valid
                  resolveChangesForExternalScan(unnotifiedFileCreates,
                                                unnotifiedFileDeletes,
                                                unnotifiedFileModifies,
                                                created,
                                                deleted,
                                                modified);
  
                  // Now merge the result of the current filesystem scan with
                  // previous unnotified changes. This represents the complete
                  // set of valid/current choices they can now notify about
                  created.addAll(unnotifiedFileCreates);
                  deleted.addAll(unnotifiedFileDeletes);
                  modified.addAll(unnotifiedFileModifies);
  
                  // We are going to rebuild these lists from anything left over in the next block
                  unnotifiedFileCreates.clear();
                  unnotifiedFileDeletes.clear();
                  unnotifiedFileModifies.clear();
  
                  // If a filter was specified, all pending updates are to be processed.
                  if (doFilterPaths) {
                      // Now take the notified changes and compare it against all the possible
                      // valid choices, unrequested changes are placed into the unnotified set
                      // so they can be used by the caller on subsequent calls
                      filterSets(created, notifiedCreated, unnotifiedFileCreates);
                      filterSets(deleted, notifiedDeleted, unnotifiedFileDeletes);
                      filterSets(modified, notifiedModified, unnotifiedFileModifies);
                  }
  
                  if (!created.isEmpty() || !modified.isEmpty() || !deleted.isEmpty()) {
                      // changes were discovered: trace & call the registered file monitor
                      if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                          Tr.debug(this, tc, "File monitor scan: end; resources changed",
                                   created.size() + " created",
                                   modified.size() + " modified",
                                   deleted.size() + " deleted");
                      }
  
                      if (monitor != null) {
                          try {
                              // If we are processing all pending events, call the extended version of the FileMonitor.
                              if (!doFilterPaths && monitor instanceof com.ibm.ws.kernel.filemonitor.FileMonitor) {
                                  ((com.ibm.ws.kernel.filemonitor.FileMonitor) monitor).onChange(created, modified, deleted, listenerFilter);
                              } else {
                                  monitor.onChange(created, modified, deleted);
                              }
  
                          } catch (RuntimeException e) {
                              // FFDC instrumentation will go here
                              // Catch the exception so we can FFDC it
                              // Don't increment the exception counter since this is externally triggered
                              Tr.warning(tc, "fileMonitorException", created, modified, deleted, monitor.getClass(), e.getLocalizedMessage());
                          }
                      }
                  } else if (coreService.isDetailedScanTraceEnabled() && TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                      // If super detailed trace is enabled, we trace the begin/end of all file scans
                      Tr.debug(this, tc, "File monitor scan: end; no changes");
                  }
              }
          } catch (RuntimeException e) {
              // TODO: MUST CATCH exceptions here (to at least get FFDC)... ick
          } finally {
              try {
                  doDestroy(); // always attempt destroy while we hold the lock
              } finally {
                  scanLock.unlock();
              }
          }
  
      }