001/*
002  Copyright 2010-2016 Boxfuse GmbH
003  <p/>
004  Licensed under the Apache License, Version 2.0 (the "License");
005  you may not use this file except in compliance with the License.
006  You may obtain a copy of the License at
007  <p/>
008  http://www.apache.org/licenses/LICENSE-2.0
009  <p/>
010  Unless required by applicable law or agreed to in writing, software
011  distributed under the License is distributed on an "AS IS" BASIS,
012  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013  See the License for the specific language governing permissions and
014  limitations under the License.
015 */
016package io.avaje.classpath.scanner.internal.scanner.filesystem;
017
018import io.avaje.classpath.scanner.Resource;
019import io.avaje.classpath.scanner.ResourceFilter;
020import io.avaje.classpath.scanner.core.ClassPathScanException;
021import io.avaje.classpath.scanner.core.Location;
022import org.slf4j.Logger;
023import org.slf4j.LoggerFactory;
024
025import java.io.File;
026import java.util.ArrayList;
027import java.util.Collections;
028import java.util.List;
029import java.util.Set;
030import java.util.TreeSet;
031
032/**
033 * FileSystem scanner.
034 */
035public class FileSystemScanner {
036
037  private static final Logger LOG = LoggerFactory.getLogger(FileSystemScanner.class);
038
039  /**
040   * Scans the FileSystem for resources under the specified location, starting with the specified prefix and ending with
041   * the specified suffix.
042   *
043   * @param location  The location in the filesystem to start searching. Subdirectories are also searched.
044   * @param predicate The predicate used to match resources.
045   * @return The resources that were found.
046   */
047  public List<Resource> scanForResources(Location location, ResourceFilter predicate) {
048
049    String path = location.getPath();
050
051    File dir = new File(path);
052    if (!dir.isDirectory() || !dir.canRead()) {
053      LOG.debug("Unable to resolve location filesystem:{}", path);
054      return Collections.emptyList();
055    }
056
057    List<Resource> resources = new ArrayList<>();
058
059    try {
060      Set<String> resourceNames = findResourceNames(path, predicate);
061      for (String resourceName : resourceNames) {
062        resources.add(new FileSystemResource(resourceName));
063        LOG.debug("Found filesystem resource: " + resourceName);
064      }
065      return resources;
066
067    } catch (Exception e) {
068      throw new ClassPathScanException(e);
069    }
070  }
071
072  /**
073   * Finds the resources names present at this location and below on the classpath starting with this prefix and
074   * ending with this suffix.
075   */
076  private Set<String> findResourceNames(String path, ResourceFilter predicate) {
077    Set<String> resourceNames = findResourceNamesFromFileSystem(path, new File(path));
078    return filterResourceNames(resourceNames, predicate);
079  }
080
081  /**
082   * Finds all the resource names contained in this file system folder.
083   *
084   * @param scanRootLocation The root location of the scan on disk.
085   * @param folder           The folder to look for resources under on disk.
086   * @return The resource names;
087   */
088  Set<String> findResourceNamesFromFileSystem(String scanRootLocation, File folder) {
089
090    LOG.debug("scanning in path: {} ({})", folder.getPath(), scanRootLocation);
091
092    Set<String> resourceNames = new TreeSet<>();
093
094    File[] files = folder.listFiles();
095    if (files != null) {
096      for (File file : files) {
097        if (file.canRead()) {
098          if (file.isDirectory()) {
099            resourceNames.addAll(findResourceNamesFromFileSystem(scanRootLocation, file));
100          } else {
101            resourceNames.add(file.getPath());
102          }
103        }
104      }
105    }
106
107    return resourceNames;
108  }
109
110  /**
111   * Filters this list of resource names to only include the ones whose filename matches this prefix and this suffix.
112   */
113  private Set<String> filterResourceNames(Set<String> resourceNames, ResourceFilter predicate) {
114    Set<String> filteredResourceNames = new TreeSet<>();
115    for (String resourceName : resourceNames) {
116      if (predicate.isMatch(resourceName)) {
117        filteredResourceNames.add(resourceName);
118      }
119    }
120    return filteredResourceNames;
121  }
122}