1 /*
2  * Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package jdk.jfr.internal.tool;
27 
28 import java.io.File;
29 import java.io.FileNotFoundException;
30 import java.io.IOError;
31 import java.io.IOException;
32 import java.io.PrintStream;
33 import java.io.RandomAccessFile;
34 import java.nio.file.Files;
35 import java.nio.file.InvalidPathException;
36 import java.nio.file.Path;
37 import java.nio.file.Paths;
38 import java.util.ArrayList;
39 import java.util.Collections;
40 import java.util.Deque;
41 import java.util.List;
42 
43 abstract class Command {
44     public final static String title = "Tool for working with Flight Recorder files (.jfr)";
45     private final static Command HELP = new Help();
46     private final static List<Command> COMMANDS = createCommands();
47 
createCommands()48     private static List<Command> createCommands() {
49         List<Command> commands = new ArrayList<>();
50         commands.add(new Print());
51         commands.add(new Metadata());
52         commands.add(new Summary());
53         commands.add(new Assemble());
54         commands.add(new Disassemble());
55         commands.add(new Version());
56         commands.add(HELP);
57         return Collections.unmodifiableList(commands);
58     }
59 
displayHelp()60     static void displayHelp() {
61         System.out.println(title);
62         System.out.println();
63         displayAvailableCommands(System.out);
64     }
65 
getName()66     abstract public String getName();
67 
getDescription()68     abstract public String getDescription();
69 
execute(Deque<String> argList)70     abstract public void execute(Deque<String> argList) throws UserSyntaxException, UserDataException;
71 
getTitle()72     protected String getTitle() {
73         return getDescription();
74     }
75 
displayAvailableCommands(PrintStream stream)76     static void displayAvailableCommands(PrintStream stream) {
77         boolean first = true;
78         for (Command c : Command.COMMANDS) {
79             if (!first) {
80                 System.out.println();
81             }
82             displayCommand(stream, c);
83             stream.println("     " + c.getDescription());
84             first = false;
85         }
86     }
87 
displayCommand(PrintStream stream, Command c)88     protected static void displayCommand(PrintStream stream, Command c) {
89         boolean firstSyntax = true;
90         String alias = buildAlias(c);
91         String initial = " jfr " + c.getName();
92         for (String syntaxLine : c.getOptionSyntax()) {
93             if (firstSyntax) {
94                 if (syntaxLine.length() != 0) {
95                    stream.println(initial + " " + syntaxLine + alias);
96                 } else {
97                    stream.println(initial + alias);
98                 }
99             } else {
100                 for (int i = 0; i < initial.length(); i++) {
101                     stream.print(" ");
102                 }
103                 stream.println(" " + syntaxLine);
104             }
105             firstSyntax = false;
106         }
107     }
108 
buildAlias(Command c)109     private static String buildAlias(Command c) {
110         List<String> aliases = c.getAliases();
111         if (aliases.isEmpty()) {
112             return "";
113         }
114         StringBuilder sb = new StringBuilder();
115         if (aliases.size() == 1) {
116             sb.append(" (alias ");
117             sb.append(aliases.get(0));
118             sb.append(")");
119             return sb.toString();
120          }
121          sb.append(" (aliases ");
122          for (int i = 0; i< aliases.size(); i ++ ) {
123              sb.append(aliases.get(i));
124              if (i < aliases.size() -1) {
125                  sb.append(", ");
126              }
127          }
128          sb.append(")");
129          return sb.toString();
130     }
131 
getCommands()132     public static List<Command> getCommands() {
133         return COMMANDS;
134     }
135 
valueOf(String commandName)136     public static Command valueOf(String commandName) {
137         for (Command command : COMMANDS) {
138             if (command.getName().equals(commandName)) {
139                 return command;
140             }
141         }
142         return null;
143     }
144 
getOptionSyntax()145     public List<String> getOptionSyntax() {
146         return Collections.singletonList("");
147     }
148 
displayOptionUsage(PrintStream stream)149     public void displayOptionUsage(PrintStream stream) {
150     }
151 
acceptOption(Deque<String> options, String expected)152     protected boolean acceptOption(Deque<String> options, String expected) throws UserSyntaxException {
153         if (expected.equals(options.peek())) {
154             if (options.size() < 2) {
155                 throw new UserSyntaxException("missing value for " + options.peek());
156             }
157             options.remove();
158             return true;
159         }
160         return false;
161     }
162 
warnForWildcardExpansion(String option, String filter)163     protected void warnForWildcardExpansion(String option, String filter) throws UserDataException {
164         // Users should quote their wildcards to avoid expansion by the shell
165         try {
166             if (!filter.contains(File.pathSeparator)) {
167                 Path p = Path.of(".", filter);
168                 if (!Files.exists(p)) {
169                     return;
170                 }
171             }
172             throw new UserDataException("wildcards should be quoted, for example " + option + " \"Foo*\"");
173         } catch (InvalidPathException ipe) {
174             // ignore
175         }
176     }
177 
acceptFilterOption(Deque<String> options, String expected)178     protected boolean acceptFilterOption(Deque<String> options, String expected) throws UserSyntaxException {
179         if (!acceptOption(options, expected)) {
180             return false;
181         }
182         if (options.isEmpty()) {
183             throw new UserSyntaxException("missing filter after " + expected);
184         }
185         String filter = options.peek();
186         if (filter.startsWith("--")) {
187             throw new UserSyntaxException("missing filter after " + expected);
188         }
189         return true;
190     }
191 
ensureMaxArgumentCount(Deque<String> options, int maxCount)192     final protected void ensureMaxArgumentCount(Deque<String> options, int maxCount) throws UserSyntaxException {
193         if (options.size() > maxCount) {
194             throw new UserSyntaxException("too many arguments");
195         }
196     }
197 
ensureMinArgumentCount(Deque<String> options, int minCount)198     final protected void ensureMinArgumentCount(Deque<String> options, int minCount) throws UserSyntaxException {
199         if (options.size() < minCount) {
200             throw new UserSyntaxException("too few arguments");
201         }
202     }
203 
getDirectory(String pathText)204     final protected Path getDirectory(String pathText) throws UserDataException {
205         try {
206             Path path = Paths.get(pathText).toAbsolutePath();
207             if (!Files.exists((path))) {
208                 throw new UserDataException("directory does not exist, " + pathText);
209             }
210             if (!Files.isDirectory(path)) {
211                 throw new UserDataException("path must be directory, " + pathText);
212             }
213             return path;
214         } catch (InvalidPathException ipe) {
215             throw new UserDataException("invalid path '" + pathText + "'");
216         }
217     }
218 
getJFRInputFile(Deque<String> options)219     final protected Path getJFRInputFile(Deque<String> options) throws UserSyntaxException, UserDataException {
220         if (options.isEmpty()) {
221             throw new UserSyntaxException("missing file");
222         }
223         String file = options.removeLast();
224         if (file.startsWith("--")) {
225             throw new UserSyntaxException("missing file");
226         }
227         try {
228             Path path = Paths.get(file).toAbsolutePath();
229             ensureAccess(path);
230             ensureJFRFile(path);
231             return path;
232         } catch (IOError ioe) {
233             throw new UserDataException("i/o error reading file '" + file + "', " + ioe.getMessage());
234         } catch (InvalidPathException ipe) {
235             throw new UserDataException("invalid path '" + file + "'");
236         }
237     }
238 
ensureAccess(Path path)239     private void ensureAccess(Path path) throws UserDataException {
240         try (RandomAccessFile rad = new RandomAccessFile(path.toFile(), "r")) {
241             if (rad.length() == 0) {
242                 throw new UserDataException("file is empty '" + path + "'");
243             }
244             rad.read(); // try to read 1 byte
245         } catch (FileNotFoundException e) {
246             throw new UserDataException("could not open file " + e.getMessage());
247         } catch (IOException e) {
248             throw new UserDataException("i/o error reading file '" + path + "', " + e.getMessage());
249         }
250     }
251 
couldNotReadError(Path p, IOException e)252     final protected void couldNotReadError(Path p, IOException e) throws UserDataException {
253         throw new UserDataException("could not read recording at " + p.toAbsolutePath() + ". " + e.getMessage());
254     }
255 
ensureFileDoesNotExist(Path file)256     final protected Path ensureFileDoesNotExist(Path file) throws UserDataException {
257         if (Files.exists(file)) {
258             throw new UserDataException("file '" + file + "' already exists");
259         }
260         return file;
261     }
262 
ensureJFRFile(Path path)263     final protected void ensureJFRFile(Path path) throws UserDataException {
264         if (!path.toString().endsWith(".jfr")) {
265             throw new UserDataException("filename must end with '.jfr'");
266         }
267     }
268 
displayUsage(PrintStream stream)269     protected void displayUsage(PrintStream stream) {
270         displayCommand(stream, this);
271         stream.println();
272         displayOptionUsage(stream);
273     }
274 
println()275     final protected void println() {
276         System.out.println();
277     }
278 
print(String text)279     final protected void print(String text) {
280         System.out.print(text);
281     }
282 
println(String text)283     final protected void println(String text) {
284         System.out.println(text);
285     }
286 
matches(String command)287     final protected boolean matches(String command) {
288         for (String s : getNames()) {
289             if (s.equals(command)) {
290                 return true;
291             }
292         }
293         return false;
294     }
295 
getAliases()296     protected List<String> getAliases() {
297         return Collections.emptyList();
298     }
299 
getNames()300     public List<String> getNames() {
301         List<String> names = new ArrayList<>();
302         names.add(getName());
303         names.addAll(getAliases());
304         return names;
305     }
306 }
307