001/* 002 * This file is part of McIDAS-V 003 * 004 * Copyright 2007-2016 005 * Space Science and Engineering Center (SSEC) 006 * University of Wisconsin - Madison 007 * 1225 W. Dayton Street, Madison, WI 53706, USA 008 * https://www.ssec.wisc.edu/mcidas 009 * 010 * All Rights Reserved 011 * 012 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and 013 * some McIDAS-V source code is based on IDV and VisAD source code. 014 * 015 * McIDAS-V is free software; you can redistribute it and/or modify 016 * it under the terms of the GNU Lesser Public License as published by 017 * the Free Software Foundation; either version 3 of the License, or 018 * (at your option) any later version. 019 * 020 * McIDAS-V is distributed in the hope that it will be useful, 021 * but WITHOUT ANY WARRANTY; without even the implied warranty of 022 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 023 * GNU Lesser Public License for more details. 024 * 025 * You should have received a copy of the GNU Lesser Public License 026 * along with this program. If not, see http://www.gnu.org/licenses. 027 */ 028 029package edu.wisc.ssec.mcidasv.util; 030 031import static java.nio.file.FileVisitResult.CONTINUE; 032 033import java.io.IOException; 034 035import java.nio.file.FileSystem; 036import java.nio.file.FileSystems; 037import java.nio.file.FileVisitOption; 038import java.nio.file.FileVisitResult; 039import java.nio.file.Files; 040import java.nio.file.Path; 041import java.nio.file.PathMatcher; 042import java.nio.file.Paths; 043import java.nio.file.SimpleFileVisitor; 044import java.nio.file.attribute.BasicFileAttributes; 045 046import java.util.ArrayList; 047import java.util.Collections; 048import java.util.EnumSet; 049import java.util.List; 050 051import org.slf4j.Logger; 052import org.slf4j.LoggerFactory; 053 054/** 055 * Allows for easy searching of files matching {@literal "glob"} patterns 056 * (e.g. {@code *.py}) in a given directories and its subdirectories. 057 * 058 * <p>Note: the {@code findFiles(...)} methods will block until the search 059 * finishes! If this is a concern, for the time being, please consider using 060 * {@link #findFiles(String, String, int)} with a reasonable depth value.</p> 061 */ 062public class FileFinder { 063 064 // TODO(jon): make this async somehow 065 066 // adapted from 067 // https://docs.oracle.com/javase/tutorial/essential/io/find.html 068 069 /** Logging object. */ 070 private static final Logger logger = 071 LoggerFactory.getLogger(FileFinder.class); 072 073 /** 074 * Internal class used by the {@code findFiles(...)} methods to actually 075 * {@literal "walk"} the directory tree. 076 */ 077 private static class Finder extends SimpleFileVisitor<Path> { 078 079 /** Pattern matcher. */ 080 private final PathMatcher matcher; 081 082 /** {@code String} representations of matching {@link Path Paths}. */ 083 private final List<String> matches; 084 085 /** 086 * Creates a new file searcher. 087 * 088 * <p>Please see {@link FileSystem#getPathMatcher(String)} for more 089 * details concerning patterns.</p> 090 * 091 * @param pattern Pattern to match against. 092 */ 093 Finder(String pattern) { 094 matches = new ArrayList<>(); 095 matcher = 096 FileSystems.getDefault().getPathMatcher("glob:" + pattern); 097 } 098 099 /** 100 * Compare the given file or directory against the glob pattern. 101 * 102 * <p>If {@code file} matches, it is added to {@link #matches}.</p> 103 * 104 * @param file File (or directory) to compare against the glob pattern. 105 * 106 * @see #results() 107 */ 108 void find(Path file) { 109 Path name = file.getFileName(); 110 if ((name != null) && matcher.matches(name)) { 111 matches.add(file.toString()); 112 } 113 } 114 115 /** 116 * Prints the total number of matches to standard out. 117 */ 118 void done() { 119 // TODO(jon): not the most useful method... 120 System.out.println("Matched: " + matches.size()); 121 } 122 123 /** 124 * Returns the matching paths as strings. 125 * 126 * @return {@code List} of the matching paths as {@code String} values. 127 */ 128 List<String> results() { 129 List<String> results = Collections.emptyList(); 130 if (!matches.isEmpty()) { 131 results = new ArrayList<>(matches); 132 } 133 return results; 134 } 135 136 /** 137 * Invokes pattern matching method on the given file. 138 * 139 * @param file File in question. 140 * @param attrs Attributes of {@code dir}. Not currently used. 141 * 142 * @return Always returns {@link FileVisitResult#CONTINUE} (for now). 143 */ 144 @Override public FileVisitResult visitFile(Path file, 145 BasicFileAttributes attrs) 146 { 147 find(file); 148 return CONTINUE; 149 } 150 151 /** 152 * Invokes the pattern matching method on the given directory. 153 * 154 * @param dir Directory in question. 155 * @param attrs Attributes of {@code dir}. Not currently used. 156 * 157 * @return Always returns {@link FileVisitResult#CONTINUE} (for now). 158 */ 159 @Override public FileVisitResult preVisitDirectory( 160 Path dir, 161 BasicFileAttributes attrs) 162 { 163 find(dir); 164 return CONTINUE; 165 } 166 167 /** 168 * Handle file {@literal "visitation"} errors. 169 * 170 * @param file File that could not be {@literal "visited"}. 171 * @param exc Exception associated with {@literal "visit"} to 172 * {@code file}. 173 * 174 * @return Always returns {@link FileVisitResult#CONTINUE} (for now). 175 * 176 */ 177 @Override public FileVisitResult visitFileFailed(Path file, 178 IOException exc) 179 { 180 logger.warn("file='"+file+"'", exc); 181 return CONTINUE; 182 } 183 } 184 185 /** 186 * Find files matching the specified {@literal "glob"} pattern in the given 187 * directory (and all of its subdirectories). 188 * 189 * <p>Note: {@literal "glob"} patterns are simple DOS/UNIX style. Think 190 * {@literal "*.py"}.</p> 191 * 192 * @param path Directory to search. 193 * @param globPattern Pattern to match against. 194 * 195 * @return {@code List} of {@code String} versions of matching paths. The 196 * list will be empty ({@link Collections#emptyList()} if there were no 197 * matches. 198 */ 199 public static List<String> findFiles(String path, String globPattern) { 200 return findFiles(path, globPattern, Integer.MAX_VALUE); 201 } 202 203 /** 204 * Find files matching the specified {@literal "glob"} pattern in the given 205 * directory (and not exceeding the given {@literal "depth"}). 206 * 207 * <p>Note: {@literal "glob"} patterns are simple DOS/UNIX style. Think 208 * {@literal "*.py"}.</p> 209 * 210 * @param path Directory to search. 211 * @param globPattern Pattern to match against. 212 * @param depth Maximum number of directory levels to visit. 213 * 214 * @return {@code List} of {@code String} versions of matching paths. The 215 * list will be empty ({@link Collections#emptyList()} if there were no 216 * matches. 217 */ 218 public static List<String> findFiles(String path, 219 String globPattern, 220 int depth) 221 { 222 Path p = Paths.get(path); 223 Finder f = new Finder(globPattern); 224 List<String> results; 225 try { 226 Files.walkFileTree(p, 227 EnumSet.noneOf(FileVisitOption.class), 228 depth, 229 f); 230 231 results = f.results(); 232 } catch (IOException e) { 233 results = Collections.emptyList(); 234 logger.error("Could not search '"+path+"'", e); 235 } 236 return results; 237 } 238}