- Являются ли файлы большими (например, более 2 ГБ каждый) или маленькими?
- Какая кодировка используется в файлах?
- Существует ли конкретное c расширение имен файлов для search?
- Является ли шаблон поиска простым строковым литералом или регулярным выражением?
- Можно ли анализировать строку за строкой или шаблон содержит символы новой строки?
- Что вы хотите сделать с соответствующими файлами? Вернуть их все, вернуть только в первый раз или просто вернуть флаг, чтобы указать, что он был найден?
- Что вы хотите сделать с несоответствующими файлами?
- Хотите выполнить поиск? имена путей к каталогам / файлам themsevles?
- Хотите отфильтровать нетекстовые файлы или нет?
Я реализовал поисковый код, который отвечает на поставленные выше вопросы, например так:
- Маленький.
- UTF-8 по умолчанию, но может использовать любой
Charset
. - При желании можно указать включенные расширения или исключенные из поиска.
- Может быть одним из них.
- Вы не разбираете построчно. Это делает файлы небольшими, но позволяет искать шаблоны с символами новой строки.
- Возвращать набор, содержащий все сопоставленные файлы.
- Игнорировать их.
- Нет. Только содержимое файла. Но вы можете использовать тот же лог c для имен путей (ie сканировать их как
String
объекты с Scanner
). - Опционально (указав их расширения как в 3).
Код:
import java.io.File;
import java.io.FileFilter;
import java.io.FileNotFoundException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Locale;
import java.util.Objects;
import java.util.Scanner;
import java.util.regex.Pattern;
public class Searcher {
public static class Builder {
private String pattern;
private int patternFlags;
private Charset charset;
private FileFilter filter;
private Iterable<String> extentions;
private boolean extentionsInclusives;
private Locale caseInsenstivityExtentionLocale;
private short maxDepth;
public Builder() {
pattern = null;
patternFlags = 0;
charset = StandardCharsets.UTF_8;
filter = null;
extentions = Collections.emptyList();
extentionsInclusives = false;
caseInsenstivityExtentionLocale = Locale.getDefault();
maxDepth = -1;
}
/**
* Specifies the {@code Pattern} used to check against the files while searching. Here you
* specify a regular expression instead of a literal {@code String} and also supply the
* {@code Pattern} flags you want (according to {@link Pattern#compile(java.lang.String, int)}
* method). The default value is not set.
* @param regex
* @param flags
* @return
* @see #literal(java.lang.String)
*/
public Builder regex(final String regex, final int flags) {
pattern = regex;
patternFlags = flags;
return this;
}
/**
* Specifies the {@code Pattern} used to check against the files while searching. Here you
* specify a literal {@code String} to be used instead of a regular expression. The default
* value is not set.
* @param s
* @return
* @see #regex(java.lang.String, int)
*/
public Builder literal(final String s) {
pattern = Pattern.quote(s);
patternFlags = 0;
return this;
}
/**
* The default value is {@link StandardCharsets#UTF_8}.
* @param charset The {@code Charset} the files will be decoded with, in order to scan for the search pattern.
* @return
*/
public Builder charset(final Charset charset) {
this.charset = charset;
return this;
}
/**
* Specifies a {@code FileFilter} to be used when listing the files and subdirectories of a
* directory (used only when the {@link #depth(short) depth} allows subdirectory searching,
* ie is not equal to zero). Supply {@code null} in order to not use a particular filter.
* The default value is {@code null}.
* @param filter
* @return
* @see #depth(short)
*/
public Builder filter(final FileFilter filter) {
this.filter = filter;
return this;
}
/**
* Specifies the file extentions of files to be checked. For example, if one wants to include
* nothing but only files with {@code "txt"} extention, then should call this method with
* the first argument to {@code true} and then the literal {@code "txt"}. Extentions are
* always plain literal {@code String}s (without beginning with the dot character) and not
* regular expressions. For another example, if one wants to exclude nothing but only files
* with {@code "png"} and {@code "jpg"} extentions, then should call this method with the
* first argument to {@code false} and then the literals {@code "png"} and {@code "jpg"}. If
* one wants to include every file, no matter what extention it has, then he should call
* this method with an empty array of extentions and {@code false} for the first argument
* (something like <i>excluding the nothing</i>, ie <i>include everything</i>). The default
* value is to include everything. Refer to {@link #extentionCaseInsenstivityLocale(java.util.Locale)}
* to specify whether the extentions will be checked as case sensitive or not.
* @param inclusives
* @param extentions
* @return
* @see #extentionCaseInsenstivityLocale(java.util.Locale)
*/
public Builder extentions(final boolean inclusives, final String... extentions) {
this.extentions = new HashSet<>(Arrays.asList(extentions));
extentionsInclusives = inclusives;
return this;
}
/**
* Specifies whether the extentions specified by {@link #extentions(boolean, java.lang.String...)}
* are case sensitive or not. Use any non-null {@code Locale} to make them case insensitive.
* Use {@code null} to make the extentions case sensitive. The default value is the
* {@link Locale#getDefault() default Locale} (which means the extentions are case
* insensitive by default).
* @param locale
* @return
* @see #extentions(boolean, java.lang.String...)
*/
public Builder extentionCaseInsenstivityLocale(final Locale locale) {
this.caseInsenstivityExtentionLocale = locale;
return this;
}
/**
* Specifies the <b>maximum</b> depth the recursion in subdirectories will take place while
* searching for the pattern. Any negative value means there is not limit to the depth of
* the recursion. A value of zero means that only the given file in
* {@link Searcher#search(java.io.File) the search method} will be checked. A value of
* {@code 1} means that the given path will be recursed once (if it is a directory), ie the
* given path is a file so it will be scanned, or the given path is a directory in which
* case the files inside it (and only) will be scanned.
* @param d
* @return
*/
public Builder depth(final short d) {
maxDepth = d;
return this;
}
/**
* Builder's state is preserved.
* @return the newly created {@code Searcher}.
*/
public Searcher build() {
Objects.requireNonNull(pattern, "Pattern is not set. You must call at least once the method 'regex(...)' or the method 'literal(...)'.");
return new Searcher(Pattern.compile(pattern, patternFlags), charset, filter, extentions, extentionsInclusives, caseInsenstivityExtentionLocale, maxDepth);
}
}
private static String getFilePathNameExtention(final String path) {
final int dotI = path.lastIndexOf('.'),
sepI = path.lastIndexOf(File.separatorChar);
if (sepI < dotI)
return path.substring(dotI + 1);
return "";
}
private final Pattern pattern;
private final FileFilter filter;
private final String charset;
private final Locale caseInsenstivityExtentionLocale;
private final HashSet<String> extentions;
private final short maxDepth;
private final boolean extentionsInclusives;
public Searcher(final Pattern pattern,
final Charset charset,
final FileFilter filter, //Can be null, to list everything.
final Iterable<String> extentions,
final boolean extentionsInclusives, //Indicates whether extentions are inclusives or exclusives.
final Locale caseInsenstivityExtentionLocale, //Can be null, for case sensitive extentions.
final short maxDepth) { //Negative for no max, zero for only current path, etc...
this.pattern = Objects.requireNonNull(pattern);
this.charset = charset.name();
this.extentions = new HashSet<>();
if (caseInsenstivityExtentionLocale != null)
extentions.forEach(extention -> this.extentions.add(extention.toLowerCase(caseInsenstivityExtentionLocale)));
else
extentions.forEach(extention -> this.extentions.add(extention));
this.extentionsInclusives = extentionsInclusives;
this.filter = filter;
this.caseInsenstivityExtentionLocale = caseInsenstivityExtentionLocale;
this.maxDepth = maxDepth;
}
protected boolean isExtentionScanableContents(String extention) {
if (caseInsenstivityExtentionLocale != null)
extention = extention.toLowerCase(caseInsenstivityExtentionLocale);
return !(extentionsInclusives ^ extentions.contains(extention)); //XNOR requires equal both sides.
}
protected boolean canScanContents(final File file) {
return file.isFile() && isExtentionScanableContents(getFilePathNameExtention(file.toString()));
}
protected boolean canListContents(final File directory, final short currentDepth) {
return directory.isDirectory() && (maxDepth < 0 || currentDepth < maxDepth);
}
protected void search(final File fileOrDirectory, final short currentDepth, final Collection<File> found) throws FileNotFoundException {
if (canListContents(fileOrDirectory, currentDepth))
for (final File f: (filter == null? fileOrDirectory.listFiles(): fileOrDirectory.listFiles(filter)))
search(f, (short) (currentDepth + 1), found);
if (canScanContents(fileOrDirectory) && (null != new Scanner(fileOrDirectory, charset).findWithinHorizon(pattern, 0)))
found.add(fileOrDirectory);
}
/**
* Searches the contents of the files in the given path if the path is a directory, or scans the
* contents of the given path itself if the path is a normal file. All search options are
* specified at construction time.
* @param fileOrDirectory
* @return All the files/paths that contain in their contents the search pattern.
* @throws FileNotFoundException If the file is successfully listed but then moved and unreachable before its scanning operation takes place.
*/
public HashSet<File> search(final File fileOrDirectory) throws FileNotFoundException {
final HashSet<File> found = new HashSet<>();
search(fileOrDirectory, (short) 0, found);
return found;
}
}
Свойства поисковика (и строителя):
- pattern : шаблон для поиска. Определяется методами
regex
и literal
класса Searcher.Builder
(или Pattern
непосредственно в конструкторе Searcher
). - кодировка : кодировка использовать для декодирования файлов. По умолчанию используется UTF-8.
- filter : фильтрация при выводе списка файлов из каталогов. По умолчанию
null
, что означает отсутствие фильтра (ie перечислить все). - extensions : расширения имен файлов, которые нужно включить или исключить из поиска. По умолчанию используется пустой набор.
- extentionsInclusives : указывает, включены ли extensions в поиск или исключены из него. По умолчанию это исключение (но с пустым набором, приведенным выше, это означает, что ничего не нужно исключать, ie включить каждый файл).
- caseInsenstivityExtentionLocale :
Locale
, который будет использоваться при сравнении нечувствительные к регистру расширения имени файла. По умолчанию это Locale.getDefault
, что делает их нечувствительными к регистру для пользователя Locale
. - глубина : максимальная глубина каталогов, которую нужно достичь. Отрицательное значение без ограничений, нулевое значение только для текущего заданного файла, значение 1 для перечисления файлов данного каталога и т. Д. ...
Как его использовать?
Вы можно просто скопировать и вставить его в java файл с именем Searcher. java, и вы готовы к go.
Я вложил достаточно javadoc в методы класса Searcher.Builder
, чтобы дать более подробную информацию.
Вот пример кода о том, как его использовать:
import java.io.File;
import java.io.FileNotFoundException;
import javax.swing.JFileChooser;
public class Main {
public static void main(final String[] args) {
final Searcher.Builder builder = new Searcher.Builder();
final Searcher searcher =
builder.literal("String to search")
.extentions(true, "txt", "java")
.build();
final JFileChooser jfc = new JFileChooser();
jfc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
jfc.setMultiSelectionEnabled(false);
if (JFileChooser.APPROVE_OPTION == jfc.showOpenDialog(null)) {
final File selectedFile = jfc.getSelectedFile();
if (selectedFile != null) {
try {
System.out.println("Only txt and java files: " + searcher.search(selectedFile));
}
catch (final FileNotFoundException fnfx) {
System.err.println("Error: " + fnfx);
}
}
else
System.err.println("null file.");
}
else
System.out.println("Cancelled.");
}
}