1 /* 2 * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 8 * - Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 11 * - Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * - Neither the name of Oracle nor the names of its 16 * contributors may be used to endorse or promote products derived 17 * from this software without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 20 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 21 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 23 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 24 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 25 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 26 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 27 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 28 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 /* 33 * This source code is provided to illustrate the usage of a given feature 34 * or technique and has been deliberately simplified. Additional steps 35 * required for a production-quality application, such as security checks, 36 * input validation, and proper error handling, might not be present in 37 * this sample code. 38 */ 39 40 import java.io.IOException; 41 import java.io.UncheckedIOException; 42 import java.nio.file.Files; 43 import java.nio.file.Path; 44 import java.nio.file.Paths; 45 import java.util.Arrays; 46 import java.util.List; 47 import java.util.regex.Pattern; 48 import java.util.stream.Stream; 49 50 import static java.util.stream.Collectors.toList; 51 52 /** 53 * Grep prints lines matching a regex. See {@link #printUsageAndExit(String...)} 54 * method for instructions and command line parameters. This sample shows 55 * examples of using next features: 56 * <ul> 57 * <li>Lambda and bulk operations. Working with streams: 58 * map(...),filter(...),flatMap(...),limit(...) methods.</li> 59 * <li>Static method reference for printing values.</li> 60 * <li>New Collections API forEach(...) method.</li> 61 * <li>Try-with-resources feature.</li> 62 * <li>new Files.walk(...), Files.lines(...) API.</li> 63 * <li>Streams that need to be closed.</li> 64 * </ul> 65 * 66 */ 67 public class Grep { 68 printUsageAndExit(String... str)69 private static void printUsageAndExit(String... str) { 70 System.out.println("Usage: " + Grep.class.getSimpleName() 71 + " [OPTION]... PATTERN FILE..."); 72 System.out.println("Search for PATTERN in each FILE. " 73 + "If FILE is a directory then whole file tree of the directory" 74 + " will be processed."); 75 System.out.println("Example: grep -m 100 'hello world' menu.h main.c"); 76 System.out.println("Options:"); 77 System.out.println(" -m NUM: stop analysis after NUM matches"); 78 Arrays.asList(str).forEach(System.err::println); 79 System.exit(1); 80 } 81 82 /** 83 * The main method for the Grep program. Run program with empty argument 84 * list to see possible arguments. 85 * 86 * @param args the argument list for Grep. 87 * @throws java.io.IOException If an I/O error occurs. 88 */ main(String[] args)89 public static void main(String[] args) throws IOException { 90 long maxCount = Long.MAX_VALUE; 91 if (args.length < 2) { 92 printUsageAndExit(); 93 } 94 int i = 0; 95 //parse OPTIONS 96 while (args[i].startsWith("-")) { 97 switch (args[i]) { 98 case "-m": 99 try { 100 maxCount = Long.parseLong(args[++i]); 101 } catch (NumberFormatException ex) { 102 printUsageAndExit(ex.toString()); 103 } 104 break; 105 default: 106 printUsageAndExit("Unexpected option " + args[i]); 107 } 108 i++; 109 } 110 //parse PATTERN 111 Pattern pattern = Pattern.compile(args[i++]); 112 if (i == args.length) { 113 printUsageAndExit("There are no files for input"); 114 } 115 116 try { 117 /* 118 * First obtain the list of all paths. 119 * For a small number of arguments there is little to be gained 120 * by producing this list in parallel. For one argument 121 * there will be no parallelism. 122 * 123 * File names are converted to paths. If a path is a directory then 124 * Stream is populated with whole file tree of the directory by 125 * flatMap() method. Files are filtered from directories. 126 */ 127 List<Path> files = Arrays.stream(args, i, args.length) 128 .map(Paths::get) 129 // flatMap will ensure each I/O-based stream will be closed 130 .flatMap(Grep::getPathStream) 131 .filter(Files::isRegularFile) 132 .collect(toList()); 133 /* 134 * Then operate on that list in parallel. 135 * This is likely to give a more even distribution of work for 136 * parallel execution. 137 * 138 * Lines are extracted from files. Lines are filtered by pattern. 139 * Stream is limited by number of matches. Each remaining string is 140 * displayed in std output by method reference System.out::println. 141 */ 142 files.parallelStream() 143 // flatMap will ensure each I/O-based stream will be closed 144 .flatMap(Grep::path2Lines) 145 .filter(pattern.asPredicate()) 146 .limit(maxCount) 147 .forEachOrdered(System.out::println); 148 } catch (UncheckedIOException ioe) { 149 printUsageAndExit(ioe.toString()); 150 } 151 } 152 153 /** 154 * Flattens file system hierarchy into a stream. This code is not inlined 155 * for the reason of Files.walk() throwing a checked IOException that must 156 * be caught. 157 * 158 * @param path - the file or directory 159 * @return Whole file tree starting from path, a stream with one element - 160 * the path itself - if it is a file. 161 */ getPathStream(Path path)162 private static Stream<Path> getPathStream(Path path) { 163 try { 164 return Files.walk(path); 165 } catch (IOException e) { 166 throw new UncheckedIOException(e); 167 } 168 } 169 170 /** 171 * Produces a stream of lines from a file. The result is a stream in order 172 * to close it later. This code is not inlined for the reason of 173 * Files.lines() throwing a checked IOException that must be caught. 174 * 175 * @param path - the file to read 176 * @return stream of lines from the file 177 */ path2Lines(Path path)178 private static Stream<String> path2Lines(Path path) { 179 try { 180 return Files.lines(path); 181 } catch (IOException e) { 182 throw new UncheckedIOException(e); 183 } 184 } 185 } 186