/* * Copyright (c) 2014-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ package com.facebook.stetho.dumpapp; import java.io.InputStream; import java.io.PrintStream; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.GnuParser; import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.ParseException; public class Dumper { /** * Map of dumper plugin name to the plugin itself. */ private final Map<String, DumperPlugin> mDumperPlugins; private final CommandLineParser mParser; private final GlobalOptions mGlobalOptions; public Dumper(Iterable<DumperPlugin> dumperPlugins) { this(dumperPlugins, new GnuParser()); } public Dumper( Iterable<DumperPlugin> dumperPlugins, CommandLineParser parser) { mDumperPlugins = generatePluginMap(dumperPlugins); mParser = parser; mGlobalOptions = new GlobalOptions(); } private static Map<String, DumperPlugin> generatePluginMap(Iterable<DumperPlugin> plugins) { Map<String, DumperPlugin> map = new HashMap<String, DumperPlugin>(); for (DumperPlugin plugin : plugins) { map.put(plugin.getName(), plugin); } return Collections.unmodifiableMap(map); } /** * Execute the dump method as if it were a fragile external command. Swallows exceptions * and translates them to dumped output text. * * @param input Input that came from the CLI's stdin. * @param out Output that will eventually make its way to the CLI's stdout. * @param err Output that will eventually make its way to the CLI's stderr. * @param args Argument list as passed from the CLI. * * @return Exit code as if this were a command-line invocation. */ public int dump(InputStream input, PrintStream out, PrintStream err, String[] args) { try { return doDump(input, out, err, args); } catch (ParseException e) { err.println(e.getMessage()); dumpUsage(err); return 1; } catch (DumpException e) { err.println(e.getMessage()); return 1; } catch (DumpappOutputBrokenException e) { // The peer is already gone, no sense in sending the exception stack to them. throw e; } catch (RuntimeException e) { e.printStackTrace(err); return 1; } } /** * @throws ParseException If any args syntax constraint is violated and the dump was not able to * proceed. * @throws DumpException Human readable error executing the dump command. */ private int doDump(InputStream input, PrintStream out, PrintStream err, String[] args) throws ParseException, DumpException { CommandLine parsedArgs = mParser.parse(mGlobalOptions.options, args, true /* stopAtNonOption */); if (parsedArgs.hasOption(mGlobalOptions.optionHelp.getOpt())) { dumpUsage(out); return 0; } else if (parsedArgs.hasOption(mGlobalOptions.optionListPlugins.getOpt())) { dumpAvailablePlugins(out); return 0; } else if (!parsedArgs.getArgList().isEmpty()) { dumpPluginOutput(input, out, err, parsedArgs); return 0; } else { // Didn't understand the options, spit out help but use a non-success exit code. dumpUsage(err); return 1; } } private void dumpAvailablePlugins(PrintStream output) { List<String> pluginNames = new ArrayList<String>(); for (DumperPlugin pluginToDump : mDumperPlugins.values()) { pluginNames.add(pluginToDump.getName()); } Collections.sort(pluginNames); for (String pluginName : pluginNames) { output.println(pluginName); } } private void dumpPluginOutput(InputStream input, PrintStream out, PrintStream err, CommandLine parsedArgs) throws DumpException { List<String> args = new ArrayList(parsedArgs.getArgList()); if (args.size() < 1) { throw new DumpException("Expected plugin argument"); } String pluginName = args.remove(0); DumperPlugin plugin = mDumperPlugins.get(pluginName); if (plugin == null) { throw new DumpException("No plugin named '" + pluginName + "'"); } DumperContext dumperContext = new DumperContext(input, out, err, mParser, args); plugin.dump(dumperContext); } private void dumpUsage(PrintStream output) { final String cmdName = "dumpapp"; HelpFormatter formatter = new HelpFormatter(); output.println("Usage: " + cmdName + " [options] <plugin> [plugin-options]"); PrintWriter writer = new PrintWriter(output); try { formatter.printOptions( writer, formatter.getWidth(), mGlobalOptions.options, formatter.getLeftPadding(), formatter.getDescPadding()); } finally { writer.flush(); } } }