/**
   * Scan the provided ServletContext and classloader for JAR files. Each JAR file found will be
   * passed to the callback handler to be processed.
   *
   * @param context The ServletContext - used to locate and access WEB-INF/lib
   * @param classloader The classloader - used to access JARs not in WEB-INF/lib
   * @param callback The handler to process any JARs found
   * @param jarsToSkip List of JARs to ignore. If this list is null, a default list will be read
   *     from the system property defined by {@link Constants#SKIP_JARS_PROPERTY}
   */
  @Override
  public void scan(
      ServletContext context,
      ClassLoader classloader,
      JarScannerCallback callback,
      Set<String> jarsToSkip) {

    if (log.isTraceEnabled()) {
      log.trace(sm.getString("jarScan.webinflibStart"));
    }

    final Set<String> ignoredJars;
    if (jarsToSkip == null) {
      ignoredJars = defaultJarsToSkip;
    } else {
      ignoredJars = jarsToSkip;
    }

    // Scan WEB-INF/lib
    Set<String> dirList = context.getResourcePaths(Constants.WEB_INF_LIB);
    if (dirList != null) {
      Iterator<String> it = dirList.iterator();
      while (it.hasNext()) {
        String path = it.next();
        if (path.endsWith(Constants.JAR_EXT)
            && !Matcher.matchName(ignoredJars, path.substring(path.lastIndexOf('/') + 1))) {
          // Need to scan this JAR
          if (log.isDebugEnabled()) {
            log.debug(sm.getString("jarScan.webinflibJarScan", path));
          }
          URL url = null;
          try {
            // File URLs are always faster to work with so use them
            // if available.
            String realPath = context.getRealPath(path);
            if (realPath == null) {
              url = context.getResource(path);
            } else {
              url = (new File(realPath)).toURI().toURL();
            }
            process(callback, url);
          } catch (IOException e) {
            log.warn(sm.getString("jarScan.webinflibFail", url), e);
          }
        } else {
          if (log.isTraceEnabled()) {
            log.trace(sm.getString("jarScan.webinflibJarNoScan", path));
          }
        }
      }
    }

    // Scan the classpath
    if (scanClassPath && classloader != null) {
      if (log.isTraceEnabled()) {
        log.trace(sm.getString("jarScan.classloaderStart"));
      }

      ClassLoader loader = classloader;

      ClassLoader stopLoader = null;
      if (!scanBootstrapClassPath) {
        // Stop when we reach the bootstrap class loader
        stopLoader = ClassLoader.getSystemClassLoader().getParent();
      }

      while (loader != null && loader != stopLoader) {
        if (loader instanceof URLClassLoader) {
          URL[] urls = ((URLClassLoader) loader).getURLs();
          for (int i = 0; i < urls.length; i++) {
            // Extract the jarName if there is one to be found
            String jarName = getJarName(urls[i]);

            // Skip JARs known not to be interesting and JARs
            // in WEB-INF/lib we have already scanned
            if (jarName != null
                && !(Matcher.matchName(ignoredJars, jarName)
                    || urls[i].toString().contains(Constants.WEB_INF_LIB + jarName))) {
              if (log.isDebugEnabled()) {
                log.debug(sm.getString("jarScan.classloaderJarScan", urls[i]));
              }
              try {
                process(callback, urls[i]);
              } catch (IOException ioe) {
                log.warn(sm.getString("jarScan.classloaderFail", urls[i]), ioe);
              }
            } else {
              if (log.isTraceEnabled()) {
                log.trace(sm.getString("jarScan.classloaderJarNoScan", urls[i]));
              }
            }
          }
        }
        loader = loader.getParent();
      }
    }
  }
  /**
   * Scan the provided ServletContext and classloader for JAR files. Each JAR file found will be
   * passed to the callback handler to be processed.
   *
   * @param context The ServletContext - used to locate and access WEB-INF/lib
   * @param classloader The classloader - used to access JARs not in WEB-INF/lib
   * @param callback The handler to process any JARs found
   * @param jarsToSkip List of JARs to ignore. If this list is null, a default list will be read
   *     from the system property defined by {@link Constants#SKIP_JARS_PROPERTY}
   */
  @Override
  public void scan(
      ServletContext context,
      ClassLoader classloader,
      JarScannerCallback callback,
      Set<String> jarsToSkip) {

    if (log.isTraceEnabled()) {
      log.trace(sm.getString("jarScan.webinflibStart"));
    }

    Set<String> ignoredJars;
    if (jarsToSkip == null) {
      ignoredJars = defaultJarsToSkip;
    } else {
      ignoredJars = jarsToSkip;
    }
    Set<String[]> ignoredJarsTokens = new HashSet<String[]>();
    for (String pattern : ignoredJars) {
      ignoredJarsTokens.add(Matcher.tokenizePathAsArray(pattern));
    }

    // Scan WEB-INF/lib
    Set<String> dirList = context.getResourcePaths(Constants.WEB_INF_LIB);
    if (dirList != null) {
      Iterator<String> it = dirList.iterator();
      while (it.hasNext()) {
        String path = it.next();
        if (path.endsWith(Constants.JAR_EXT)
            && !Matcher.matchPath(ignoredJarsTokens, path.substring(path.lastIndexOf('/') + 1))) {
          // Need to scan this JAR
          if (log.isDebugEnabled()) {
            log.debug(sm.getString("jarScan.webinflibJarScan", path));
          }
          URL url = null;
          try {
            url = context.getResource(path);
            process(callback, url);
          } catch (IOException e) {
            log.warn(sm.getString("jarScan.webinflibFail", url), e);
          }
        } else {
          if (log.isTraceEnabled()) {
            log.trace(sm.getString("jarScan.webinflibJarNoScan", path));
          }
        }
      }
    }

    // Scan the classpath
    if (scanClassPath) {
      if (log.isTraceEnabled()) {
        log.trace(sm.getString("jarScan.classloaderStart"));
      }

      ClassLoader loader = Thread.currentThread().getContextClassLoader();

      while (loader != null) {
        if (loader instanceof URLClassLoader) {
          URL[] urls = ((URLClassLoader) loader).getURLs();
          for (int i = 0; i < urls.length; i++) {
            // Extract the jarName if there is one to be found
            String jarName = getJarName(urls[i]);

            // Skip JARs known not to be interesting and JARs
            // in WEB-INF/lib we have already scanned
            if (jarName != null
                && !(Matcher.matchPath(ignoredJarsTokens, jarName)
                    || urls[i].toString().contains(Constants.WEB_INF_LIB + jarName))) {
              if (log.isDebugEnabled()) {
                log.debug(sm.getString("jarScan.classloaderJarScan", urls[i]));
              }
              try {
                process(callback, urls[i]);
              } catch (IOException ioe) {
                log.warn(sm.getString("jarScan.classloaderFail", urls[i]), ioe);
              }
            } else {
              if (log.isTraceEnabled()) {
                log.trace(sm.getString("jarScan.classloaderJarNoScan", urls[i]));
              }
            }
          }
        }
        loader = loader.getParent();
      }
    }
  }