docs: @Override
      void scanPaths(final LogNode log) {
          if (logicalZipFile == null) {
              skipClasspathElement = true;
          }
          if (skipClasspathElement) {
              return;
          }
          if (scanned.getAndSet(true)) {
              // Should not happen
              throw new IllegalArgumentException("Already scanned classpath element " + getZipFilePath());
          }
  
          final LogNode subLog = log == null ? null
                  : log.log(getZipFilePath(), "Scanning jarfile classpath element " + getZipFilePath());
  
          Set<String> loggedNestedClasspathRootPrefixes = null;
          String prevParentRelativePath = null;
          ScanSpecPathMatch prevParentMatchStatus = null;
          for (final FastZipEntry zipEntry : logicalZipFile.entries) {
              String relativePath = zipEntry.entryNameUnversioned;
  
              // Check if the relative path is within a nested classpath root
              if (nestedClasspathRootPrefixes != null) {
                  // This is O(mn), which is inefficient, but the number of nested classpath roots should be small
                  boolean reachedNestedRoot = false;
                  for (final String nestedClasspathRoot : nestedClasspathRootPrefixes) {
                      if (relativePath.startsWith(nestedClasspathRoot)) {
                          // relativePath has a prefix of nestedClasspathRoot
                          if (subLog != null) {
                              if (loggedNestedClasspathRootPrefixes == null) {
                                  loggedNestedClasspathRootPrefixes = new HashSet<>();
                              }
                              if (loggedNestedClasspathRootPrefixes.add(nestedClasspathRoot)) {
                                  subLog.log("Reached nested classpath root, stopping recursion to avoid duplicate "
                                          + "scanning: " + nestedClasspathRoot);
                              }
                          }
                          reachedNestedRoot = true;
                          break;
                      }
                  }
                  if (reachedNestedRoot) {
                      continue;
                  }
              }
  
              // Ignore entries without the correct classpath root prefix
              if (!packageRootPrefix.isEmpty() && !relativePath.startsWith(packageRootPrefix)) {
                  continue;
              }
  
              // Strip the package root prefix from the relative path
              // N.B. these semantics should mirror those in getResource()
              if (!packageRootPrefix.isEmpty()) {
                  relativePath = relativePath.substring(packageRootPrefix.length());
              } else {
                  // Strip any package root prefix from the relative path
                  for (int i = 0; i < ClassLoaderHandlerRegistry.AUTOMATIC_PACKAGE_ROOT_PREFIXES.length; i++) {
                      if (relativePath.startsWith(ClassLoaderHandlerRegistry.AUTOMATIC_PACKAGE_ROOT_PREFIXES[i])) {
                          relativePath = relativePath
                                  .substring(ClassLoaderHandlerRegistry.AUTOMATIC_PACKAGE_ROOT_PREFIXES[i].length());
                      }
                  }
              }
  
              // Whitelist/blacklist classpath elements based on file resource paths
              checkResourcePathWhiteBlackList(relativePath, log);
              if (skipClasspathElement) {
                  return;
              }
  
              // Get match status of the parent directory of this ZipEntry file's relative path (or reuse the last
              // match status for speed, if the directory name hasn't changed).
              final int lastSlashIdx = relativePath.lastIndexOf('/');
              final String parentRelativePath = lastSlashIdx < 0 ? "/" : relativePath.substring(0, lastSlashIdx + 1);
              final boolean parentRelativePathChanged = !parentRelativePath.equals(prevParentRelativePath);
              final ScanSpecPathMatch parentMatchStatus = //
                      parentRelativePathChanged ? scanSpec.dirWhitelistMatchStatus(parentRelativePath)
                              : prevParentMatchStatus;
              prevParentRelativePath = parentRelativePath;
              prevParentMatchStatus = parentMatchStatus;
  
              if (parentMatchStatus == ScanSpecPathMatch.HAS_BLACKLISTED_PATH_PREFIX) {
                  // The parent dir or one of its ancestral dirs is blacklisted
                  if (subLog != null) {
                      subLog.log("Skipping blacklisted path: " + relativePath);
                  }
                  continue;
              }
  
              // Add the ZipEntry path as a Resource
              final Resource resource = newResource(zipEntry, relativePath);
              if (relativePathToResource.putIfAbsent(relativePath, resource) == null
                      // If resource is whitelisted
                      && (parentMatchStatus == ScanSpecPathMatch.HAS_WHITELISTED_PATH_PREFIX
                              || parentMatchStatus == ScanSpecPathMatch.AT_WHITELISTED_PATH
                              || (parentMatchStatus == ScanSpecPathMatch.AT_WHITELISTED_CLASS_PACKAGE
                                      && scanSpec.classfileIsSpecificallyWhitelisted(relativePath))
                              || (scanSpec.enableClassInfo && relativePath.equals("module-info.class")))) {
                  // Resource is whitelisted
                  addWhitelistedResource(resource, parentMatchStatus, subLog);
              }
          }
  
          // Save the last modified time for the zipfile
          final File zipfile = getFile();
          fileToLastModified.put(zipfile, zipfile.lastModified());
  
          finishScanPaths(subLog);
      }