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 org.avaje.classpath.scanner.internal.scanner.filesystem;
017
018import org.avaje.classpath.scanner.core.ClassPathScanException;
019import org.avaje.classpath.scanner.core.Location;
020import org.avaje.classpath.scanner.ResourceFilter;
021import org.avaje.classpath.scanner.Resource;
022import org.slf4j.Logger;
023import org.slf4j.LoggerFactory;
024
025import java.io.File;
026import java.io.IOException;
027import java.util.ArrayList;
028import java.util.Collections;
029import java.util.List;
030import java.util.Set;
031import java.util.TreeSet;
032
033/**
034 * FileSystem scanner.
035 */
036public class FileSystemScanner {
037
038  private static final Logger LOG = LoggerFactory.getLogger(FileSystemScanner.class);
039
040  /**
041   * Scans the FileSystem for resources under the specified location, starting with the specified prefix and ending with
042   * the specified suffix.
043   *
044   * @param location  The location in the filesystem to start searching. Subdirectories are also searched.
045   * @param predicate The predicate used to match resources.
046   * @return The resources that were found.
047   */
048  public List<Resource> scanForResources(Location location, ResourceFilter predicate) {
049
050    String path = location.getPath();
051
052    File dir = new File(path);
053    if (!dir.isDirectory() || !dir.canRead()) {
054      LOG.warn("Unable to resolve location filesystem:" + path);
055      return Collections.emptyList();
056    }
057
058    List<Resource> resources = new ArrayList<Resource>();
059
060    try {
061      Set<String> resourceNames = findResourceNames(path, predicate);
062      for (String resourceName : resourceNames) {
063        resources.add(new FileSystemResource(resourceName));
064        LOG.debug("Found filesystem resource: " + resourceName);
065      }
066      return resources;
067
068    } catch (IOException e) {
069      throw new ClassPathScanException(e);
070    }
071  }
072
073  /**
074   * Finds the resources names present at this location and below on the classpath starting with this prefix and
075   * ending with this suffix.
076   */
077  private Set<String> findResourceNames(String path, ResourceFilter predicate) throws IOException {
078    Set<String> resourceNames = findResourceNamesFromFileSystem(path, new File(path));
079    return filterResourceNames(resourceNames, predicate);
080  }
081
082  /**
083   * Finds all the resource names contained in this file system folder.
084   *
085   * @param scanRootLocation The root location of the scan on disk.
086   * @param folder           The folder to look for resources under on disk.
087   * @return The resource names;
088   * @throws IOException when the folder could not be read.
089   */
090  @SuppressWarnings("ConstantConditions")
091  Set<String> findResourceNamesFromFileSystem(String scanRootLocation, File folder) throws IOException {
092
093    LOG.debug("scanning in path: {} ({})", folder.getPath(), scanRootLocation);
094
095    Set<String> resourceNames = new TreeSet<String>();
096
097    File[] files = folder.listFiles();
098    if (files != null) {
099      for (File file : files) {
100        if (file.canRead()) {
101          if (file.isDirectory()) {
102            resourceNames.addAll(findResourceNamesFromFileSystem(scanRootLocation, file));
103          } else {
104            resourceNames.add(file.getPath());
105          }
106        }
107      }
108    }
109
110    return resourceNames;
111  }
112
113  /**
114   * Filters this list of resource names to only include the ones whose filename matches this prefix and this suffix.
115   */
116  private Set<String> filterResourceNames(Set<String> resourceNames, ResourceFilter predicate) {
117    Set<String> filteredResourceNames = new TreeSet<String>();
118    for (String resourceName : resourceNames) {
119      if (predicate.isMatch(resourceName)) {
120        filteredResourceNames.add(resourceName);
121      }
122    }
123    return filteredResourceNames;
124  }
125}