blob: cb4bdf765cb27105b5185b20f4ed4f1d802d9b77 [file] [log] [blame]
/*
* Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of Oracle nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* This source code is provided to illustrate the usage of a given feature
* or technique and has been deliberately simplified. Additional steps
* required for a production-quality application, such as security checks,
* input validation, and proper error handling, might not be present in
* this sample code.
*/
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import static java.util.stream.Collectors.toList;
/**
* Grep prints lines matching a regex. See {@link #printUsageAndExit(String...)}
* method for instructions and command line parameters. This sample shows
* examples of using next features:
* <ul>
* <li>Lambda and bulk operations. Working with streams:
* map(...),filter(...),flatMap(...),limit(...) methods.</li>
* <li>Static method reference for printing values.</li>
* <li>New Collections API forEach(...) method.</li>
* <li>Try-with-resources feature.</li>
* <li>new Files.walk(...), Files.lines(...) API.</li>
* <li>Streams that need to be closed.</li>
* </ul>
*
*/
public class Grep {
private static void printUsageAndExit(String... str) {
System.out.println("Usage: " + Grep.class.getSimpleName()
+ " [OPTION]... PATTERN FILE...");
System.out.println("Search for PATTERN in each FILE. "
+ "If FILE is a directory then whole file tree of the directory"
+ " will be processed.");
System.out.println("Example: grep -m 100 'hello world' menu.h main.c");
System.out.println("Options:");
System.out.println(" -m NUM: stop analysis after NUM matches");
Arrays.asList(str).forEach(System.err::println);
System.exit(1);
}
/**
* The main method for the Grep program. Run program with empty argument
* list to see possible arguments.
*
* @param args the argument list for Grep.
* @throws java.io.IOException If an I/O error occurs.
*/
public static void main(String[] args) throws IOException {
long maxCount = Long.MAX_VALUE;
if (args.length < 2) {
printUsageAndExit();
}
int i = 0;
//parse OPTIONS
while (args[i].startsWith("-")) {
switch (args[i]) {
case "-m":
try {
maxCount = Long.parseLong(args[++i]);
} catch (NumberFormatException ex) {
printUsageAndExit(ex.toString());
}
break;
default:
printUsageAndExit("Unexpected option " + args[i]);
}
i++;
}
//parse PATTERN
Pattern pattern = Pattern.compile(args[i++]);
if (i == args.length) {
printUsageAndExit("There are no files for input");
}
try {
/*
* First obtain the list of all paths.
* For a small number of arguments there is little to be gained
* by producing this list in parallel. For one argument
* there will be no parallelism.
*
* File names are converted to paths. If a path is a directory then
* Stream is populated with whole file tree of the directory by
* flatMap() method. Files are filtered from directories.
*/
List<Path> files = Arrays.stream(args, i, args.length)
.map(Paths::get)
// flatMap will ensure each I/O-based stream will be closed
.flatMap(Grep::getPathStream)
.filter(Files::isRegularFile)
.collect(toList());
/*
* Then operate on that list in parallel.
* This is likely to give a more even distribution of work for
* parallel execution.
*
* Lines are extracted from files. Lines are filtered by pattern.
* Stream is limited by number of matches. Each remaining string is
* displayed in std output by method reference System.out::println.
*/
files.parallelStream()
// flatMap will ensure each I/O-based stream will be closed
.flatMap(Grep::path2Lines)
.filter(pattern.asPredicate())
.limit(maxCount)
.forEachOrdered(System.out::println);
} catch (UncheckedIOException ioe) {
printUsageAndExit(ioe.toString());
}
}
/**
* Flattens file system hierarchy into a stream. This code is not inlined
* for the reason of Files.walk() throwing a checked IOException that must
* be caught.
*
* @param path - the file or directory
* @return Whole file tree starting from path, a stream with one element -
* the path itself - if it is a file.
*/
private static Stream<Path> getPathStream(Path path) {
try {
return Files.walk(path);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
/**
* Produces a stream of lines from a file. The result is a stream in order
* to close it later. This code is not inlined for the reason of
* Files.lines() throwing a checked IOException that must be caught.
*
* @param path - the file to read
* @return stream of lines from the file
*/
private static Stream<String> path2Lines(Path path) {
try {
return Files.lines(path);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
}