Revamp the CLI usage/help formatting

This commit is contained in:
Ben Gruver 2016-05-23 15:50:28 -07:00
parent e474301e60
commit ca48e6f7d0
28 changed files with 1266 additions and 303 deletions

View File

@ -38,12 +38,18 @@ import org.jf.dexlib2.analysis.CustomInlineMethodResolver;
import org.jf.dexlib2.analysis.InlineMethodResolver; import org.jf.dexlib2.analysis.InlineMethodResolver;
import org.jf.dexlib2.dexbacked.DexBackedOdexFile; import org.jf.dexlib2.dexbacked.DexBackedOdexFile;
import org.jf.dexlib2.iface.DexFile; import org.jf.dexlib2.iface.DexFile;
import org.jf.util.jcommander.ExtendedParameter;
import org.jf.util.jcommander.ExtendedParameters;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.List;
@Parameters(commandDescription = "Deodexes an odex/oat file") @Parameters(commandDescription = "Deodexes an odex/oat file")
@ExtendedParameters(
commandName = "deodex",
commandAliases = { "de", "x" })
public class DeodexCommand extends DisassembleCommand { public class DeodexCommand extends DisassembleCommand {
@Parameter(names = "--check-package-private-access", @Parameter(names = "--check-package-private-access",
description = "Use the package-private access check when calculating vtable indexes. This should " + description = "Use the package-private access check when calculating vtable indexes. This should " +
@ -54,10 +60,11 @@ public class DeodexCommand extends DisassembleCommand {
description = "Specify a file containing a custom inline method table to use. See the " + description = "Specify a file containing a custom inline method table to use. See the " +
"\"deodexerant\" tool in the smali github repository to dump the inline method table from a " + "\"deodexerant\" tool in the smali github repository to dump the inline method table from a " +
"device that uses dalvik.") "device that uses dalvik.")
@ExtendedParameter(argumentNames = "file")
private String inlineTable; private String inlineTable;
public DeodexCommand(@Nonnull JCommander jc) { public DeodexCommand(@Nonnull List<JCommander> commandAncestors) {
super(jc); super(commandAncestors);
} }
@Override protected BaksmaliOptions getOptions(DexFile dexFile) { @Override protected BaksmaliOptions getOptions(DexFile dexFile) {

View File

@ -31,19 +31,26 @@
package org.jf.baksmali; package org.jf.baksmali;
import com.beust.jcommander.JCommander;
import org.jf.dexlib2.DexFileFactory; import org.jf.dexlib2.DexFileFactory;
import org.jf.dexlib2.dexbacked.DexBackedDexFile; import org.jf.dexlib2.dexbacked.DexBackedDexFile;
import org.jf.dexlib2.dexbacked.OatFile; import org.jf.dexlib2.dexbacked.OatFile;
import org.jf.util.jcommander.Command;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.List;
/** /**
* This class implements common functionality for commands that need to load a dex file based on * This class implements common functionality for commands that need to load a dex file based on
* command line input * command line input
*/ */
public abstract class DexInputCommand implements Command { public abstract class DexInputCommand extends Command {
public DexInputCommand(@Nonnull List<JCommander> commandAncestors) {
super(commandAncestors);
}
/** /**
* Parses a dex file input from the user and loads the given dex file. * Parses a dex file input from the user and loads the given dex file.

View File

@ -45,6 +45,8 @@ import org.jf.dexlib2.iface.DexFile;
import org.jf.dexlib2.util.SyntheticAccessorResolver; import org.jf.dexlib2.util.SyntheticAccessorResolver;
import org.jf.util.StringWrapper; import org.jf.util.StringWrapper;
import org.jf.util.jcommander.CommaColonParameterSplitter; import org.jf.util.jcommander.CommaColonParameterSplitter;
import org.jf.util.jcommander.ExtendedParameter;
import org.jf.util.jcommander.ExtendedParameters;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.io.File; import java.io.File;
@ -53,29 +55,32 @@ import java.util.List;
import java.util.Map; import java.util.Map;
@Parameters(commandDescription = "Disassembles a dex file.") @Parameters(commandDescription = "Disassembles a dex file.")
@ExtendedParameters(
commandName = "disassemble",
commandAliases = { "dis", "d" })
public class DisassembleCommand extends DexInputCommand { public class DisassembleCommand extends DexInputCommand {
@Nonnull private final JCommander jc;
@Parameter(names = {"-h", "-?", "--help"}, help = true, @Parameter(names = {"-h", "-?", "--help"}, help = true,
description = "Show usage information for this command.") description = "Show usage information for this command.")
private boolean help; private boolean help;
@Parameter(names = {"-a", "--api"}, @Parameter(names = {"-a", "--api"},
description = "The numeric api level of the file being disassembled.") description = "The numeric api level of the file being disassembled.")
@ExtendedParameter(argumentNames = "api")
private int apiLevel = 15; private int apiLevel = 15;
@Parameter(names = "--debug-info", arity = 1, @Parameter(names = "--debug-info", arity = 1,
description = "Whether to include debug information in the output (.local, .param, .line, etc.). Use " + description = "Whether to include debug information in the output (.local, .param, .line, etc.). Use " +
"--debug-info=false to disable.") "--debug-info=false to disable.")
@ExtendedParameter(argumentNames = "boolean")
private boolean debugInfo = true; private boolean debugInfo = true;
@Parameter(names = {"-b", "--bootclasspath"}, @Parameter(names = {"-b", "--bootclasspath"},
description = "A comma/colon separated list of the bootclasspath jar/oat files to include in the " + description = "A comma/colon separated list of the jar/oat files to include in the " +
"classpath when analyzing the dex file. This will override any automatic selection of " + "bootclasspath when analyzing the dex file. If not specified, baksmali will attempt to choose an " +
"bootclasspath files that baksmali would otherwise perform. This is analogous to Android's " + "appropriate default. This is analogous to Android's BOOTCLASSPATH environment variable.",
"BOOTCLASSPATH environment variable.",
splitter = CommaColonParameterSplitter.class) splitter = CommaColonParameterSplitter.class)
@ExtendedParameter(argumentNames = "classpath")
private List<String> bootClassPath = null; private List<String> bootClassPath = null;
@Parameter(names = {"-c", "--classpath"}, @Parameter(names = {"-c", "--classpath"},
@ -83,16 +88,17 @@ public class DisassembleCommand extends DexInputCommand {
"when analyzing the dex file. These will be added to the classpath after any bootclasspath " + "when analyzing the dex file. These will be added to the classpath after any bootclasspath " +
"entries.", "entries.",
splitter = CommaColonParameterSplitter.class) splitter = CommaColonParameterSplitter.class)
@ExtendedParameter(argumentNames = "classpath")
private List<String> classPath = Lists.newArrayList(); private List<String> classPath = Lists.newArrayList();
@Parameter(names = {"-d", "--classpath-dir"}, @Parameter(names = {"-d", "--classpath-dir"},
description = "A directory to search for classpath files. This option can be used multiple times to " + description = "A directory to search for classpath files. This option can be used multiple times to " +
"specify multiple directories to search. They will be searched in the order they are provided.") "specify multiple directories to search. They will be searched in the order they are provided.")
@ExtendedParameter(argumentNames = "dirs")
private List<String> classPathDirectories = Lists.newArrayList("."); private List<String> classPathDirectories = Lists.newArrayList(".");
@Parameter(names = {"--code-offsets"}, @Parameter(names = {"--code-offsets"},
description = "Add comments to the disassembly containing the code offset within the method for each " + description = "Add a comment before each instruction with it's code offset within the method.")
"instruction.")
private boolean codeOffsets = false; private boolean codeOffsets = false;
@Parameter(names = "--resolve-resources", arity=1, @Parameter(names = "--resolve-resources", arity=1,
@ -100,11 +106,13 @@ public class DisassembleCommand extends DexInputCommand {
"comment with the name of the resource being referenced. The value should be a comma/colon" + "comment with the name of the resource being referenced. The value should be a comma/colon" +
"separated list of prefix=file pairs. For example R=res/values/public.xml:android.R=" + "separated list of prefix=file pairs. For example R=res/values/public.xml:android.R=" +
"$ANDROID_HOME/platforms/android-19/data/res/values/public.xml") "$ANDROID_HOME/platforms/android-19/data/res/values/public.xml")
@ExtendedParameter(argumentNames = "resource spec")
private List<String> resourceIdFiles = Lists.newArrayList(); private List<String> resourceIdFiles = Lists.newArrayList();
@Parameter(names = {"-j", "--jobs"}, @Parameter(names = {"-j", "--jobs"},
description = "The number of threads to use. Defaults to the number of cores available.", description = "The number of threads to use. Defaults to the number of cores available.",
validateWith = PositiveInteger.class) validateWith = PositiveInteger.class)
@ExtendedParameter(argumentNames = "n")
private int jobs = Runtime.getRuntime().availableProcessors(); private int jobs = Runtime.getRuntime().availableProcessors();
@Parameter(names = {"-l", "--use-locals"}, @Parameter(names = {"-l", "--use-locals"},
@ -113,7 +121,9 @@ public class DisassembleCommand extends DexInputCommand {
private boolean localsDirective = false; private boolean localsDirective = false;
@Parameter(names = "--accessor-comments", arity = 1, @Parameter(names = "--accessor-comments", arity = 1,
description = "Generate helper comments for synthetic accessors. Use --accessor-comments=false to disable.") description = "Generate helper comments for synthetic accessors. Use --accessor-comments=false to " +
"disable.")
@ExtendedParameter(argumentNames = "boolean")
private boolean accessorComments = true; private boolean accessorComments = true;
@Parameter(names = "--normalize-virtual-methods", @Parameter(names = "--normalize-virtual-methods",
@ -123,17 +133,20 @@ public class DisassembleCommand extends DexInputCommand {
@Parameter(names = {"-o", "--output"}, @Parameter(names = {"-o", "--output"},
description = "The directory to write the disassembled files to.") description = "The directory to write the disassembled files to.")
@ExtendedParameter(argumentNames = "dir")
private String outputDir = "out"; private String outputDir = "out";
@Parameter(names = "--parameter-registers", arity = 1, @Parameter(names = "--parameter-registers", arity = 1,
description = "Use the pNN syntax for registers that refer to a method parameter on method entry. Use" + description = "Use the pNN syntax for registers that refer to a method parameter on method entry. Use" +
"--parameter-registers=false to disable.") "--parameter-registers=false to disable.")
@ExtendedParameter(argumentNames = "boolean")
private boolean parameterRegisters = true; private boolean parameterRegisters = true;
@Parameter(names = {"-r", "--register-info"}, arity=1, @Parameter(names = {"-r", "--register-info"}, arity=1,
description = "Add comments before/after each instruction with information about register types. " + description = "Add comments before/after each instruction with information about register types. " +
"The value is a comma-separated list of any of ALL, ALLPRE, ALLPOST, ARGS, DEST, MERGE and " + "The value is a comma-separated list of any of ALL, ALLPRE, ALLPOST, ARGS, DEST, MERGE and " +
"FULLMERGE. See \"baksmali help register-info\" for more information.") "FULLMERGE. See \"baksmali help register-info\" for more information.")
@ExtendedParameter(argumentNames = "register info specifier")
private List<String> registerInfoTypes = Lists.newArrayList(); private List<String> registerInfoTypes = Lists.newArrayList();
@Parameter(names = "--sequential-labels", @Parameter(names = "--sequential-labels",
@ -142,7 +155,7 @@ public class DisassembleCommand extends DexInputCommand {
private boolean sequentialLabels = false; private boolean sequentialLabels = false;
@Parameter(names = "--implicit-references", @Parameter(names = "--implicit-references",
description = "Use implicit (without the class name) method and field references for methods and " + description = "Use implicit method and field references (without the class name) for methods and " +
"fields from the current class.") "fields from the current class.")
private boolean implicitReferences = false; private boolean implicitReferences = false;
@ -151,24 +164,25 @@ public class DisassembleCommand extends DexInputCommand {
"supported in the Android runtime yet.") "supported in the Android runtime yet.")
private boolean experimentalOpcodes = false; private boolean experimentalOpcodes = false;
@Parameter(description = "<file> - A dex/apk/oat/odex file. For apk or oat files that contain multiple dex " + @Parameter(description = "A dex/apk/oat/odex file. For apk or oat files that contain multiple dex " +
"files, you can specify which dex file to disassemble by appending the name of the dex file with a " + "files, you can specify which dex file to disassemble by appending the name of the dex file with a " +
"colon. E.g. \"something.apk:classes2.dex\"") "colon. E.g. \"something.apk:classes2.dex\"")
@ExtendedParameter(argumentNames = "file")
private List<String> inputList = Lists.newArrayList(); private List<String> inputList = Lists.newArrayList();
public DisassembleCommand(@Nonnull JCommander jc) { public DisassembleCommand(@Nonnull List<JCommander> commandAncestors) {
this.jc = jc; super(commandAncestors);
} }
public void run() { public void run() {
if (help || inputList == null || inputList.isEmpty()) { if (help || inputList == null || inputList.isEmpty()) {
jc.usage(jc.getParsedCommand()); usage();
return; return;
} }
if (inputList.size() > 1) { if (inputList.size() > 1) {
System.err.println("Too many files specified"); System.err.println("Too many files specified");
jc.usage(jc.getParsedCommand()); usage();
return; return;
} }
@ -261,7 +275,7 @@ public class DisassembleCommand extends DexInputCommand {
int separatorIndex = resourceIdFileSpec.indexOf('='); int separatorIndex = resourceIdFileSpec.indexOf('=');
if (separatorIndex == -1) { if (separatorIndex == -1) {
System.err.println(String.format("Invalid resource id spec: %s", resourceIdFileSpec)); System.err.println(String.format("Invalid resource id spec: %s", resourceIdFileSpec));
jc.usage(jc.getParsedCommand()); usage();
System.exit(-1); System.exit(-1);
} }
String prefix = resourceIdFileSpec.substring(0, separatorIndex); String prefix = resourceIdFileSpec.substring(0, separatorIndex);
@ -308,7 +322,7 @@ public class DisassembleCommand extends DexInputCommand {
options.registerInfo |= BaksmaliOptions.FULLMERGE; options.registerInfo |= BaksmaliOptions.FULLMERGE;
} else { } else {
System.err.println(String.format("Invalid register info type: %s", registerInfoType)); System.err.println(String.format("Invalid register info type: %s", registerInfoType));
jc.usage(jc.getParsedCommand()); usage();
System.exit(-1); System.exit(-1);
} }

View File

@ -41,15 +41,19 @@ import org.jf.dexlib2.dexbacked.OatFile;
import org.jf.dexlib2.dexbacked.raw.RawDexFile; import org.jf.dexlib2.dexbacked.raw.RawDexFile;
import org.jf.dexlib2.dexbacked.raw.util.DexAnnotator; import org.jf.dexlib2.dexbacked.raw.util.DexAnnotator;
import org.jf.util.ConsoleUtil; import org.jf.util.ConsoleUtil;
import org.jf.util.jcommander.Command;
import org.jf.util.jcommander.ExtendedParameter;
import org.jf.util.jcommander.ExtendedParameters;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.io.*; import java.io.*;
import java.util.List;
@Parameters(commandDescription = "Prints an annotated hex dump for the given dex file") @Parameters(commandDescription = "Prints an annotated hex dump for the given dex file")
public class DumpCommand implements Command { @ExtendedParameters(
commandName = "dump",
@Nonnull commandAliases = "du")
private final JCommander jc; public class DumpCommand extends Command {
@Parameter(names = {"-h", "-?", "--help"}, help = true, @Parameter(names = {"-h", "-?", "--help"}, help = true,
description = "Show usage information for this command.") description = "Show usage information for this command.")
@ -57,6 +61,7 @@ public class DumpCommand implements Command {
@Parameter(names = {"-a", "--api"}, @Parameter(names = {"-a", "--api"},
description = "The numeric api level of the file being disassembled.") description = "The numeric api level of the file being disassembled.")
@ExtendedParameter(argumentNames = "api")
private int apiLevel = 15; private int apiLevel = 15;
@Parameter(names = "--experimental", @Parameter(names = "--experimental",
@ -67,15 +72,16 @@ public class DumpCommand implements Command {
@Parameter(description = "<file> - A dex/apk/oat/odex file. For apk or oat files that contain multiple dex " + @Parameter(description = "<file> - A dex/apk/oat/odex file. For apk or oat files that contain multiple dex " +
"files, you can specify which dex file to disassemble by appending the name of the dex file with a " + "files, you can specify which dex file to disassemble by appending the name of the dex file with a " +
"colon. E.g. \"something.apk:classes2.dex\"") "colon. E.g. \"something.apk:classes2.dex\"")
@ExtendedParameter(argumentNames = "file")
private String input; private String input;
public DumpCommand(@Nonnull JCommander jc) { public DumpCommand(@Nonnull List<JCommander> commandAncestors) {
this.jc = jc; super(commandAncestors);
} }
public void run() { public void run() {
if (help || input == null || input.isEmpty()) { if (help || input == null || input.isEmpty()) {
jc.usage("dump"); usage();
return; return;
} }

View File

@ -37,27 +37,37 @@ import com.beust.jcommander.Parameters;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import org.jf.util.ConsoleUtil; import org.jf.util.ConsoleUtil;
import org.jf.util.StringWrapper; import org.jf.util.StringWrapper;
import org.jf.util.jcommander.*;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.util.List; import java.util.List;
@Parameters(commandDescription = "Shows usage information") @Parameters(commandDescription = "Shows usage information")
public class HelpCommand implements Command { @ExtendedParameters(
@Nonnull private final JCommander jc; commandName = "help",
commandAliases = "h")
public class HelpCommand extends Command {
public HelpCommand(@Nonnull JCommander jc) { public HelpCommand(@Nonnull List<JCommander> commandAncestors) {
this.jc = jc; super(commandAncestors);
} }
@Parameter(description = "If specified, only show the usage information for the given commands") @Parameter(description = "If specified, show the detailed usage information for the given commands")
@ExtendedParameter(argumentNames = "commands")
private List<String> commands = Lists.newArrayList(); private List<String> commands = Lists.newArrayList();
public void run() { public void run() {
JCommander parentJc = commandAncestors.get(commandAncestors.size() - 1);
if (commands == null || commands.isEmpty()) { if (commands == null || commands.isEmpty()) {
jc.usage(); System.out.println(new HelpFormatter()
.width(ConsoleUtil.getConsoleWidth())
.format(commandAncestors));
} else { } else {
boolean printedHelp = false;
for (String cmd : commands) { for (String cmd : commands) {
if (cmd.equals("register-info")) { if (cmd.equals("register-info")) {
printedHelp = true;
String registerInfoHelp = "The --register-info parameter will cause baksmali to generate " + String registerInfoHelp = "The --register-info parameter will cause baksmali to generate " +
"comments before and after every instruction containing register type " + "comments before and after every instruction containing register type " +
"information about some subset of registers. This parameter optionally accepts a " + "information about some subset of registers. This parameter optionally accepts a " +
@ -80,16 +90,30 @@ public class HelpCommand implements Command {
System.out.println(line); System.out.println(line);
} }
} else { } else {
jc.usage(cmd); JCommander command = ExtendedCommands.getSubcommand(parentJc, cmd);
if (command == null) {
System.err.println("No such command: " + cmd);
} else {
printedHelp = true;
System.out.println(new HelpFormatter()
.width(ConsoleUtil.getConsoleWidth())
.format(((Command)command.getObjects().get(0)).getCommandHierarchy()));
}
} }
} }
if (!printedHelp) {
System.out.println(new HelpFormatter()
.width(ConsoleUtil.getConsoleWidth())
.format(commandAncestors));
}
} }
} }
@Parameters(hidden = true) @Parameters(hidden = true)
@ExtendedParameters(commandName = "hlep")
public static class HlepCommand extends HelpCommand { public static class HlepCommand extends HelpCommand {
public HlepCommand(@Nonnull JCommander jc) { public HlepCommand(@Nonnull List<JCommander> commandAncestors) {
super(jc); super(commandAncestors);
} }
} }
} }

View File

@ -39,36 +39,41 @@ import org.jf.dexlib2.Opcodes;
import org.jf.dexlib2.dexbacked.DexBackedDexFile; import org.jf.dexlib2.dexbacked.DexBackedDexFile;
import org.jf.dexlib2.dexbacked.DexBackedOdexFile; import org.jf.dexlib2.dexbacked.DexBackedOdexFile;
import org.jf.dexlib2.dexbacked.OatFile; import org.jf.dexlib2.dexbacked.OatFile;
import org.jf.util.jcommander.Command;
import org.jf.util.jcommander.ExtendedParameter;
import org.jf.util.jcommander.ExtendedParameters;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.io.*; import java.io.*;
import java.util.List; import java.util.List;
@Parameters(commandDescription = "Lists the stored classpath entries in an odex/oat file.") @Parameters(commandDescription = "Lists the stored classpath entries in an odex/oat file.")
public class ListClassPathCommand implements Command { @ExtendedParameters(
commandName = "classpath",
@Nonnull private final JCommander jc; commandAliases = { "bootclasspath", "cp", "bcp" })
public class ListClassPathCommand extends Command {
@Parameter(names = {"-h", "-?", "--help"}, help = true, @Parameter(names = {"-h", "-?", "--help"}, help = true,
description = "Show usage information") description = "Show usage information")
private boolean help; private boolean help;
@Parameter(description = "<file> - An oat/odex file") @Parameter(description = "An oat/odex file")
@ExtendedParameter(argumentNames = "file")
private List<String> inputList = Lists.newArrayList(); private List<String> inputList = Lists.newArrayList();
public ListClassPathCommand(@Nonnull JCommander jc) { public ListClassPathCommand(@Nonnull List<JCommander> commandAncestors) {
this.jc = jc; super(commandAncestors);
} }
@Override public void run() { @Override public void run() {
if (help || inputList == null || inputList.isEmpty()) { if (help || inputList == null || inputList.isEmpty()) {
jc.usage(jc.getParsedCommand()); usage();
return; return;
} }
if (inputList.size() > 1) { if (inputList.size() > 1) {
System.err.println("Too many files specified"); System.err.println("Too many files specified");
jc.usage(jc.getParsedCommand()); usage();
return; return;
} }

View File

@ -38,37 +38,41 @@ import com.google.common.collect.Lists;
import org.jf.dexlib2.dexbacked.DexBackedDexFile; import org.jf.dexlib2.dexbacked.DexBackedDexFile;
import org.jf.dexlib2.iface.reference.Reference; import org.jf.dexlib2.iface.reference.Reference;
import org.jf.dexlib2.util.ReferenceUtil; import org.jf.dexlib2.util.ReferenceUtil;
import org.jf.util.jcommander.ExtendedParameter;
import org.jf.util.jcommander.ExtendedParameters;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.util.List; import java.util.List;
@Parameters(commandDescription = "Lists the classes in a dex file.") @Parameters(commandDescription = "Lists the classes in a dex file.")
@ExtendedParameters(
commandName = "classes",
commandAliases = { "class", "c" })
public class ListClassesCommand extends DexInputCommand { public class ListClassesCommand extends DexInputCommand {
@Nonnull private final JCommander jc;
@Parameter(names = {"-h", "-?", "--help"}, help = true, @Parameter(names = {"-h", "-?", "--help"}, help = true,
description = "Show usage information") description = "Show usage information")
private boolean help; private boolean help;
@Parameter(description = "<file> - A dex/apk/oat/odex file. For apk or oat files that contain multiple dex " + @Parameter(description = "A dex/apk/oat/odex file. For apk or oat files that contain multiple dex " +
"files, you can specify which dex file to disassemble by appending the name of the dex file with a " + "files, you can specify which dex file to disassemble by appending the name of the dex file with a " +
"colon. E.g. \"something.apk:classes2.dex\"") "colon. E.g. \"something.apk:classes2.dex\"")
@ExtendedParameter(argumentNames = "file")
private List<String> inputList = Lists.newArrayList(); private List<String> inputList = Lists.newArrayList();
public ListClassesCommand(@Nonnull JCommander jc) { public ListClassesCommand(@Nonnull List<JCommander> commandAncestors) {
this.jc = jc; super(commandAncestors);
} }
@Override public void run() { @Override public void run() {
if (help || inputList == null || inputList.isEmpty()) { if (help || inputList == null || inputList.isEmpty()) {
jc.usage(jc.getParsedCommand()); usage();
return; return;
} }
if (inputList.size() > 1) { if (inputList.size() > 1) {
System.err.println("Too many files specified"); System.err.println("Too many files specified");
jc.usage(jc.getParsedCommand()); usage();
return; return;
} }

View File

@ -34,43 +34,53 @@ package org.jf.baksmali;
import com.beust.jcommander.JCommander; import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters; import com.beust.jcommander.Parameters;
import org.jf.baksmali.ListHelpCommand.ListHlepCommand;
import org.jf.util.jcommander.Command;
import org.jf.util.jcommander.ExtendedCommands;
import org.jf.util.jcommander.ExtendedParameters;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.util.List;
@Parameters(commandDescription = "Lists various objects in a dex file.") @Parameters(commandDescription = "Lists various objects in a dex file.")
public class ListCommand implements Command { @ExtendedParameters(
commandName = "list",
@Nonnull private final JCommander jc; commandAliases = "l")
@Nonnull private JCommander subJc; public class ListCommand extends Command {
@Parameter(names = {"-h", "-?", "--help"}, help = true, @Parameter(names = {"-h", "-?", "--help"}, help = true,
description = "Show usage information") description = "Show usage information")
private boolean help; private boolean help;
public ListCommand(@Nonnull JCommander jc) { public ListCommand(@Nonnull List<JCommander> commandAncestors) {
this.jc = jc; super(commandAncestors);
} }
public void registerSubCommands() { public void registerSubCommands() {
subJc = jc.getCommands().get("list"); JCommander subJc = getJCommander();
subJc.addCommand("strings", new ListStringsCommand(subJc), "string", "str", "s"); List<JCommander> hierarchy = getCommandHierarchy();
subJc.addCommand("methods", new ListMethodsCommand(subJc), "method", "m");
subJc.addCommand("fields", new ListFieldsCommand(subJc), "field", "f"); ExtendedCommands.addExtendedCommand(subJc, new ListStringsCommand(hierarchy));
subJc.addCommand("types", new ListTypesCommand(subJc), "type", "t"); ExtendedCommands.addExtendedCommand(subJc, new ListMethodsCommand(hierarchy));
subJc.addCommand("classes", new ListClassesCommand(subJc), "class", "c"); ExtendedCommands.addExtendedCommand(subJc, new ListFieldsCommand(hierarchy));
subJc.addCommand("dex", new ListDexCommand(subJc), "d"); ExtendedCommands.addExtendedCommand(subJc, new ListTypesCommand(hierarchy));
subJc.addCommand("vtables", new ListVtablesCommand(subJc), "vtable", "v"); ExtendedCommands.addExtendedCommand(subJc, new ListClassesCommand(hierarchy));
subJc.addCommand("fieldoffsets", new ListFieldOffsetsCommand(subJc), "fieldoffset", "fo"); ExtendedCommands.addExtendedCommand(subJc, new ListDexCommand(hierarchy));
subJc.addCommand("classpath", new ListClassPathCommand(subJc), "bootclasspath", "cp", "bcp"); ExtendedCommands.addExtendedCommand(subJc, new ListVtablesCommand(hierarchy));
ExtendedCommands.addExtendedCommand(subJc, new ListFieldOffsetsCommand(hierarchy));
ExtendedCommands.addExtendedCommand(subJc, new ListClassPathCommand(hierarchy));
ExtendedCommands.addExtendedCommand(subJc, new ListHelpCommand(hierarchy));
ExtendedCommands.addExtendedCommand(subJc, new ListHlepCommand(hierarchy));
} }
@Override public void run() { @Override public void run() {
if (help || subJc.getParsedCommand() == null) { JCommander jc = getJCommander();
subJc.usage(); if (help || jc.getParsedCommand() == null) {
usage();
return; return;
} }
Command command = (Command)subJc.getCommands().get(subJc.getParsedCommand()).getObjects().get(0); Command command = (Command)jc.getCommands().get(jc.getParsedCommand()).getObjects().get(0);
command.run(); command.run();
} }
} }

View File

@ -38,6 +38,9 @@ import com.google.common.collect.Lists;
import org.jf.dexlib2.dexbacked.OatFile; import org.jf.dexlib2.dexbacked.OatFile;
import org.jf.dexlib2.dexbacked.raw.HeaderItem; import org.jf.dexlib2.dexbacked.raw.HeaderItem;
import org.jf.dexlib2.dexbacked.raw.OdexHeaderItem; import org.jf.dexlib2.dexbacked.raw.OdexHeaderItem;
import org.jf.util.jcommander.Command;
import org.jf.util.jcommander.ExtendedParameter;
import org.jf.util.jcommander.ExtendedParameters;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.io.*; import java.io.*;
@ -48,30 +51,32 @@ import java.util.zip.ZipException;
import java.util.zip.ZipFile; import java.util.zip.ZipFile;
@Parameters(commandDescription = "Lists the dex files in an apk/oat file.") @Parameters(commandDescription = "Lists the dex files in an apk/oat file.")
public class ListDexCommand implements Command { @ExtendedParameters(
commandName = "dex",
@Nonnull private final JCommander jc; commandAliases = "d")
public class ListDexCommand extends Command {
@Parameter(names = {"-h", "-?", "--help"}, help = true, @Parameter(names = {"-h", "-?", "--help"}, help = true,
description = "Show usage information") description = "Show usage information")
private boolean help; private boolean help;
@Parameter(description = "<file> - An apk or oat file.") @Parameter(description = "An apk or oat file.")
@ExtendedParameter(argumentNames = "file")
private List<String> inputList = Lists.newArrayList(); private List<String> inputList = Lists.newArrayList();
public ListDexCommand(@Nonnull JCommander jc) { public ListDexCommand(@Nonnull List<JCommander> commandAncestors) {
this.jc = jc; super(commandAncestors);
} }
@Override public void run() { @Override public void run() {
if (help || inputList == null || inputList.isEmpty()) { if (help || inputList == null || inputList.isEmpty()) {
jc.usage(jc.getParsedCommand()); usage();
return; return;
} }
if (inputList.size() > 1) { if (inputList.size() > 1) {
System.err.println("Too many files specified"); System.err.println("Too many files specified");
jc.usage(jc.getParsedCommand()); usage();
return; return;
} }

View File

@ -43,6 +43,8 @@ import org.jf.dexlib2.iface.DexFile;
import org.jf.dexlib2.iface.reference.FieldReference; import org.jf.dexlib2.iface.reference.FieldReference;
import org.jf.util.SparseArray; import org.jf.util.SparseArray;
import org.jf.util.jcommander.CommaColonParameterSplitter; import org.jf.util.jcommander.CommaColonParameterSplitter;
import org.jf.util.jcommander.ExtendedParameter;
import org.jf.util.jcommander.ExtendedParameters;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.io.IOException; import java.io.IOException;
@ -50,40 +52,46 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
@Parameters(commandDescription = "Lists the instance field offsets for classes in a dex file.") @Parameters(commandDescription = "Lists the instance field offsets for classes in a dex file.")
@ExtendedParameters(
commandName = "fieldoffsets",
commandAliases = { "fieldoffset", "fo" })
public class ListFieldOffsetsCommand extends DexInputCommand { public class ListFieldOffsetsCommand extends DexInputCommand {
@Nonnull private final JCommander jc;
@Parameter(names = {"-h", "-?", "--help"}, help = true, @Parameter(names = {"-h", "-?", "--help"}, help = true,
description = "Show usage information") description = "Show usage information")
private boolean help; private boolean help;
@Parameter(names = {"-a", "--api"}, @Parameter(names = {"-a", "--api"},
description = "The numeric api level of the file being loaded.") description = "The numeric api level of the file being disassembled.")
@ExtendedParameter(argumentNames = "api")
private int apiLevel = 15; private int apiLevel = 15;
@Parameter(description = "<file> - A dex/apk/oat/odex file. For apk or oat files that contain multiple dex " + @Parameter(description = "A dex/apk/oat/odex file. For apk or oat files that contain multiple dex " +
"files, you can specify which dex file to disassemble by appending the name of the dex file with a " + "files, you can specify which dex file to disassemble by appending the name of the dex file with a " +
"colon. E.g. \"something.apk:classes2.dex\"") "colon. E.g. \"something.apk:classes2.dex\"")
@ExtendedParameter(argumentNames = "file")
private List<String> inputList = Lists.newArrayList(); private List<String> inputList = Lists.newArrayList();
@Parameter(names = {"-b", "--bootclasspath"}, @Parameter(names = {"-b", "--bootclasspath"},
description = "A comma/colon separated list of the bootclasspath jar/oat files to include in the " + description = "A comma/colon separated list of the jar/oat files to include in the " +
"classpath when analyzing the dex file. This will override any automatic selection of " + "bootclasspath when analyzing the dex file. If not specified, baksmali will attempt to choose an " +
"bootclasspath files that baksmali would otherwise perform. This is analogous to Android's " + "appropriate default. This is analogous to Android's BOOTCLASSPATH environment variable.",
"BOOTCLASSPATH environment variable.",
splitter = CommaColonParameterSplitter.class) splitter = CommaColonParameterSplitter.class)
private List<String> bootClassPath = new ArrayList<String>(); @ExtendedParameter(argumentNames = "classpath")
private List<String> bootClassPath = null;
@Parameter(names = {"-c", "--classpath"}, @Parameter(names = {"-c", "--classpath"},
description = "A comma/colon separated list of additional jar/oat files to include in the classpath " + description = "A comma/colon separated list of additional jar/oat files to include in the classpath " +
"when analyzing the dex file. These will be added to the classpath after any bootclasspath " + "when analyzing the dex file. These will be added to the classpath after any bootclasspath " +
"entries.", "entries.",
splitter = CommaColonParameterSplitter.class) splitter = CommaColonParameterSplitter.class)
@ExtendedParameter(argumentNames = "classpath")
private List<String> classPath = new ArrayList<String>(); private List<String> classPath = new ArrayList<String>();
@Parameter(names = {"-d", "--classpath-dir"}, @Parameter(names = {"-d", "--classpath-dir"},
description = "baksmali will search these directories in order for any classpath entries.") description = "A directory to search for classpath files. This option can be used multiple times to " +
"specify multiple directories to search. They will be searched in the order they are provided.")
@ExtendedParameter(argumentNames = "dirs")
private List<String> classPathDirectories = Lists.newArrayList("."); private List<String> classPathDirectories = Lists.newArrayList(".");
@Parameter(names = "--check-package-private-access", @Parameter(names = "--check-package-private-access",
@ -96,19 +104,19 @@ public class ListFieldOffsetsCommand extends DexInputCommand {
"supported in the Android runtime yet.") "supported in the Android runtime yet.")
private boolean experimentalOpcodes = false; private boolean experimentalOpcodes = false;
public ListFieldOffsetsCommand(@Nonnull JCommander jc) { public ListFieldOffsetsCommand(@Nonnull List<JCommander> commandAncestors) {
this.jc = jc; super(commandAncestors);
} }
@Override public void run() { @Override public void run() {
if (help || inputList == null || inputList.isEmpty()) { if (help || inputList == null || inputList.isEmpty()) {
jc.usage(jc.getParsedCommand()); usage();
return; return;
} }
if (inputList.size() > 1) { if (inputList.size() > 1) {
System.err.println("Too many files specified"); System.err.println("Too many files specified");
jc.usage(jc.getParsedCommand()); usage();
return; return;
} }

View File

@ -34,12 +34,17 @@ package org.jf.baksmali;
import com.beust.jcommander.JCommander; import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameters; import com.beust.jcommander.Parameters;
import org.jf.dexlib2.ReferenceType; import org.jf.dexlib2.ReferenceType;
import org.jf.util.jcommander.ExtendedParameters;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.util.List;
@Parameters(commandDescription = "Lists the fields in a dex file's field table.") @Parameters(commandDescription = "Lists the fields in a dex file's field table.")
@ExtendedParameters(
commandName = "fields",
commandAliases = { "field", "f" })
public class ListFieldsCommand extends ListReferencesCommand { public class ListFieldsCommand extends ListReferencesCommand {
public ListFieldsCommand(@Nonnull JCommander jc) { public ListFieldsCommand(@Nonnull List<JCommander> commandAncestors) {
super(jc, ReferenceType.FIELD); super(commandAncestors, ReferenceType.FIELD);
} }
} }

View File

@ -0,0 +1,92 @@
/*
* Copyright 2016, Google Inc.
* 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 Google Inc. 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.
*/
package org.jf.baksmali;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.google.common.collect.Iterables;
import org.jf.util.ConsoleUtil;
import org.jf.util.jcommander.*;
import javax.annotation.Nonnull;
import java.util.List;
@Parameters(commandDescription = "Shows usage information")
@ExtendedParameters(
commandName = "help",
commandAliases = "h")
public class ListHelpCommand extends Command {
@Parameter(description = "If specified, show the detailed usage information for the given commands")
@ExtendedParameter(argumentNames = "commands")
private List<String> commands;
public ListHelpCommand(@Nonnull List<JCommander> commandAncestors) {
super(commandAncestors);
}
public void run() {
if (commands == null || commands.isEmpty()) {
System.out.println(new HelpFormatter()
.width(ConsoleUtil.getConsoleWidth())
.format(commandAncestors));
} else {
boolean printedHelp = false;
JCommander parentJc = Iterables.getLast(commandAncestors);
for (String cmd : commands) {
JCommander command = ExtendedCommands.getSubcommand(parentJc, cmd);
if (command == null) {
System.err.println("No such command: " + cmd);
} else {
printedHelp = true;
System.out.println(new HelpFormatter()
.width(ConsoleUtil.getConsoleWidth())
.format(((Command)command.getObjects().get(0)).getCommandHierarchy()));
}
}
if (!printedHelp) {
System.out.println(new HelpFormatter()
.width(ConsoleUtil.getConsoleWidth())
.format(commandAncestors));
}
}
}
@Parameters(hidden = true)
@ExtendedParameters(commandName = "hlep")
public static class ListHlepCommand extends ListHelpCommand {
public ListHlepCommand(@Nonnull List<JCommander> commandAncestors) {
super(commandAncestors);
}
}
}

View File

@ -34,12 +34,17 @@ package org.jf.baksmali;
import com.beust.jcommander.JCommander; import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameters; import com.beust.jcommander.Parameters;
import org.jf.dexlib2.ReferenceType; import org.jf.dexlib2.ReferenceType;
import org.jf.util.jcommander.ExtendedParameters;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.util.List;
@Parameters(commandDescription = "Lists the methods in a dex file's method table.") @Parameters(commandDescription = "Lists the methods in a dex file's method table.")
@ExtendedParameters(
commandName = "methods",
commandAliases = { "method", "m" })
public class ListMethodsCommand extends ListReferencesCommand { public class ListMethodsCommand extends ListReferencesCommand {
public ListMethodsCommand(@Nonnull JCommander jc) { public ListMethodsCommand(@Nonnull List<JCommander> commandAncestors) {
super(jc, ReferenceType.METHOD); super(commandAncestors, ReferenceType.METHOD);
} }
} }

View File

@ -37,38 +37,39 @@ import com.google.common.collect.Lists;
import org.jf.dexlib2.dexbacked.DexBackedDexFile; import org.jf.dexlib2.dexbacked.DexBackedDexFile;
import org.jf.dexlib2.iface.reference.Reference; import org.jf.dexlib2.iface.reference.Reference;
import org.jf.dexlib2.util.ReferenceUtil; import org.jf.dexlib2.util.ReferenceUtil;
import org.jf.util.jcommander.ExtendedParameter;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.util.List; import java.util.List;
public abstract class ListReferencesCommand extends DexInputCommand { public abstract class ListReferencesCommand extends DexInputCommand {
@Nonnull private final JCommander jc;
private final int referenceType; private final int referenceType;
@Parameter(names = {"-h", "-?", "--help"}, help = true, @Parameter(names = {"-h", "-?", "--help"}, help = true,
description = "Show usage information") description = "Show usage information")
private boolean help; private boolean help;
@Parameter(description = "<file> - A dex/apk/oat/odex file. For apk or oat files that contain multiple dex " + @Parameter(description = "A dex/apk/oat/odex file. For apk or oat files that contain multiple dex " +
"files, you can specify which dex file to disassemble by appending the name of the dex file with a " + "files, you can specify which dex file to disassemble by appending the name of the dex file with a " +
"colon. E.g. \"something.apk:classes2.dex\"") "colon. E.g. \"something.apk:classes2.dex\"")
@ExtendedParameter(argumentNames = "file")
private List<String> inputList = Lists.newArrayList(); private List<String> inputList = Lists.newArrayList();
public ListReferencesCommand(@Nonnull JCommander jc, int referenceType) { public ListReferencesCommand(@Nonnull List<JCommander> commandAncestors, int referenceType) {
this.jc = jc; super(commandAncestors);
this.referenceType = referenceType; this.referenceType = referenceType;
} }
@Override public void run() { @Override public void run() {
if (help || inputList == null || inputList.isEmpty()) { if (help || inputList == null || inputList.isEmpty()) {
jc.usage(jc.getParsedCommand()); usage();
return; return;
} }
if (inputList.size() > 1) { if (inputList.size() > 1) {
System.err.println("Too many files specified"); System.err.println("Too many files specified");
jc.usage(jc.getParsedCommand()); usage();
return; return;
} }

View File

@ -34,12 +34,17 @@ package org.jf.baksmali;
import com.beust.jcommander.JCommander; import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameters; import com.beust.jcommander.Parameters;
import org.jf.dexlib2.ReferenceType; import org.jf.dexlib2.ReferenceType;
import org.jf.util.jcommander.ExtendedParameters;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.util.List;
@Parameters(commandDescription = "Lists the strings in a dex file's string table.") @Parameters(commandDescription = "Lists the strings in a dex file's string table.")
@ExtendedParameters(
commandName = "strings",
commandAliases = { "string", "str", "s" })
public class ListStringsCommand extends ListReferencesCommand { public class ListStringsCommand extends ListReferencesCommand {
public ListStringsCommand(@Nonnull JCommander jc) { public ListStringsCommand(@Nonnull List<JCommander> commandAncestors) {
super(jc, ReferenceType.STRING); super(commandAncestors, ReferenceType.STRING);
} }
} }

View File

@ -34,12 +34,17 @@ package org.jf.baksmali;
import com.beust.jcommander.JCommander; import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameters; import com.beust.jcommander.Parameters;
import org.jf.dexlib2.ReferenceType; import org.jf.dexlib2.ReferenceType;
import org.jf.util.jcommander.ExtendedParameters;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.util.List;
@Parameters(commandDescription = "Lists the type ids in a dex file's type table.") @Parameters(commandDescription = "Lists the type ids in a dex file's type table.")
@ExtendedParameters(
commandName = "types",
commandAliases = { "type", "t" })
public class ListTypesCommand extends ListReferencesCommand { public class ListTypesCommand extends ListReferencesCommand {
public ListTypesCommand(@Nonnull JCommander jc) { public ListTypesCommand(@Nonnull List<JCommander> commandAncestors) {
super(jc, ReferenceType.TYPE); super(commandAncestors, ReferenceType.TYPE);
} }
} }

View File

@ -42,6 +42,8 @@ import org.jf.dexlib2.iface.ClassDef;
import org.jf.dexlib2.iface.DexFile; import org.jf.dexlib2.iface.DexFile;
import org.jf.dexlib2.iface.Method; import org.jf.dexlib2.iface.Method;
import org.jf.util.jcommander.CommaColonParameterSplitter; import org.jf.util.jcommander.CommaColonParameterSplitter;
import org.jf.util.jcommander.ExtendedParameter;
import org.jf.util.jcommander.ExtendedParameters;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.io.IOException; import java.io.IOException;
@ -49,40 +51,46 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
@Parameters(commandDescription = "Lists the virtual method tables for classes in a dex file.") @Parameters(commandDescription = "Lists the virtual method tables for classes in a dex file.")
@ExtendedParameters(
commandName = "vtables",
commandAliases = { "vtable", "v" })
public class ListVtablesCommand extends DexInputCommand { public class ListVtablesCommand extends DexInputCommand {
@Nonnull private final JCommander jc;
@Parameter(names = {"-h", "-?", "--help"}, help = true, @Parameter(names = {"-h", "-?", "--help"}, help = true,
description = "Show usage information") description = "Show usage information")
private boolean help; private boolean help;
@Parameter(names = {"-a", "--api"}, @Parameter(names = {"-a", "--api"},
description = "The numeric api level of the file being loaded.") description = "The numeric api level of the file being loaded.")
@ExtendedParameter(argumentNames = "api")
public int apiLevel = 15; public int apiLevel = 15;
@Parameter(description = "<file> - A dex/apk/oat/odex file. For apk or oat files that contain multiple dex " + @Parameter(description = "A dex/apk/oat/odex file. For apk or oat files that contain multiple dex " +
"files, you can specify which dex file to disassemble by appending the name of the dex file with a " + "files, you can specify which dex file to disassemble by appending the name of the dex file with a " +
"colon. E.g. \"something.apk:classes2.dex\"") "colon. E.g. \"something.apk:classes2.dex\"")
@ExtendedParameter(argumentNames = "file")
private List<String> inputList = Lists.newArrayList(); private List<String> inputList = Lists.newArrayList();
@Parameter(names = {"-b", "--bootclasspath"}, @Parameter(names = {"-b", "--bootclasspath"},
description = "A comma/colon separated list of the bootclasspath jar/oat files to include in the " + description = "A comma/colon separated list of the jar/oat files to include in the " +
"classpath when analyzing the dex file. This will override any automatic selection of " + "bootclasspath when analyzing the dex file. If not specified, baksmali will attempt to choose an " +
"bootclasspath files that baksmali would otherwise perform. This is analogous to Android's " + "appropriate default. This is analogous to Android's BOOTCLASSPATH environment variable.",
"BOOTCLASSPATH environment variable.",
splitter = CommaColonParameterSplitter.class) splitter = CommaColonParameterSplitter.class)
private List<String> bootClassPath = new ArrayList<String>(); @ExtendedParameter(argumentNames = "classpath")
private List<String> bootClassPath = null;
@Parameter(names = {"-c", "--classpath"}, @Parameter(names = {"-c", "--classpath"},
description = "A comma/colon separated list of additional jar/oat files to include in the classpath " + description = "A comma/colon separated list of additional jar/oat files to include in the classpath " +
"when analyzing the dex file. These will be added to the classpath after any bootclasspath " + "when analyzing the dex file. These will be added to the classpath after any bootclasspath " +
"entries.", "entries.",
splitter = CommaColonParameterSplitter.class) splitter = CommaColonParameterSplitter.class)
@ExtendedParameter(argumentNames = "classpath")
private List<String> classPath = new ArrayList<String>(); private List<String> classPath = new ArrayList<String>();
@Parameter(names = {"-d", "--classpath-dir"}, @Parameter(names = {"-d", "--classpath-dir"},
description = "baksmali will search these directories in order for any classpath entries.") description = "A directory to search for classpath files. This option can be used multiple times to " +
"specify multiple directories to search. They will be searched in the order they are provided.")
@ExtendedParameter(argumentNames = "dirs")
private List<String> classPathDirectories = Lists.newArrayList("."); private List<String> classPathDirectories = Lists.newArrayList(".");
@Parameter(names = "--check-package-private-access", @Parameter(names = "--check-package-private-access",
@ -97,21 +105,22 @@ public class ListVtablesCommand extends DexInputCommand {
@Parameter(names = "--classes", @Parameter(names = "--classes",
description = "A comma separated list of classes: Only print the vtable for these classes") description = "A comma separated list of classes: Only print the vtable for these classes")
@ExtendedParameter(argumentNames = "classes")
private String classes = null; private String classes = null;
public ListVtablesCommand(@Nonnull JCommander jc) { public ListVtablesCommand(@Nonnull List<JCommander> commandAncestors) {
this.jc = jc; super(commandAncestors);
} }
@Override public void run() { @Override public void run() {
if (help || inputList == null || inputList.isEmpty()) { if (help || inputList == null || inputList.isEmpty()) {
jc.usage(jc.getParsedCommand()); usage();
return; return;
} }
if (inputList.size() > 1) { if (inputList.size() > 1) {
System.err.println("Too many files specified"); System.err.println("Too many files specified");
jc.usage(jc.getParsedCommand()); usage();
return; return;
} }

View File

@ -33,47 +33,71 @@ package org.jf.baksmali;
import com.beust.jcommander.JCommander; import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameter;
import com.google.common.collect.Lists;
import org.jf.baksmali.HelpCommand.HlepCommand; import org.jf.baksmali.HelpCommand.HlepCommand;
import org.jf.util.jcommander.Command;
import org.jf.util.jcommander.ExtendedCommands;
import org.jf.util.jcommander.ExtendedParameters;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.List;
import java.util.Properties; import java.util.Properties;
public class Main { @ExtendedParameters(
includeParametersInUsage = true,
commandName = "baksmali",
postfixDescription = "See baksmali help <command> for more information about a specific command")
public class Main extends Command {
public static final String VERSION = loadVersion(); public static final String VERSION = loadVersion();
@Parameter(names = {"-h", "-?", "--help"}, help = true, @Parameter(names = {"--help", "-h", "-?"}, help = true,
description = "Show usage information") description = "Show usage information")
private boolean help; private boolean help;
@Parameter(names = {"-v", "--version"}, help = true, @Parameter(names = {"--version", "-v"}, help = true,
description = "Print the version of baksmali and then exit") description = "Print the version of baksmali and then exit")
public boolean version; public boolean version;
private JCommander jc;
public Main() {
super(Lists.<JCommander>newArrayList());
}
@Override public void run() {
}
@Override protected JCommander getJCommander() {
return jc;
}
public static void main(String[] args) { public static void main(String[] args) {
Main main = new Main(); Main main = new Main();
JCommander jc = new JCommander(main); JCommander jc = new JCommander(main);
main.jc = jc;
jc.setProgramName("baksmali");
List<JCommander> commandHierarchy = main.getCommandHierarchy();
jc.addCommand("disassemble", new DisassembleCommand(jc), "dis", "d"); ExtendedCommands.addExtendedCommand(jc, new DisassembleCommand(commandHierarchy));
jc.addCommand("deodex", new DeodexCommand(jc), "de", "x"); ExtendedCommands.addExtendedCommand(jc, new DeodexCommand(commandHierarchy));
jc.addCommand("dump", new DumpCommand(jc), "du"); ExtendedCommands.addExtendedCommand(jc, new DumpCommand(commandHierarchy));
jc.addCommand("help", new HelpCommand(jc), "h"); ExtendedCommands.addExtendedCommand(jc, new HelpCommand(commandHierarchy));
jc.addCommand("hlep", new HlepCommand(jc)); ExtendedCommands.addExtendedCommand(jc, new HlepCommand(commandHierarchy));
ListCommand listCommand = new ListCommand(jc); ListCommand listCommand = new ListCommand(commandHierarchy);
jc.addCommand("list", listCommand, "l"); ExtendedCommands.addExtendedCommand(jc, listCommand);
listCommand.registerSubCommands(); listCommand.registerSubCommands();
jc.parse(args); jc.parse(args);
if (jc.getParsedCommand() == null || main.help) {
jc.usage();
return;
}
if (main.version) { if (main.version) {
version(); version();
}
if (jc.getParsedCommand() == null || main.help) {
main.usage();
return; return;
} }

View File

@ -35,15 +35,19 @@ import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters; import com.beust.jcommander.Parameters;
import com.beust.jcommander.validators.PositiveInteger; import com.beust.jcommander.validators.PositiveInteger;
import org.jf.util.jcommander.Command;
import org.jf.util.jcommander.ExtendedParameter;
import org.jf.util.jcommander.ExtendedParameters;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
@Parameters(commandDescription = "Assembles smali files into a dex file.") @Parameters(commandDescription = "Assembles smali files into a dex file.")
public class AssembleCommand implements Command { @ExtendedParameters(
commandName = "assemble",
@Nonnull private final JCommander jc; commandAliases = { "ass", "as", "a" })
public class AssembleCommand extends Command {
@Parameter(names = {"-h", "-?", "--help"}, help = true, @Parameter(names = {"-h", "-?", "--help"}, help = true,
description = "Show usage information for this command.") description = "Show usage information for this command.")
@ -52,14 +56,17 @@ public class AssembleCommand implements Command {
@Parameter(names = {"-j", "--jobs"}, @Parameter(names = {"-j", "--jobs"},
description = "The number of threads to use. Defaults to the number of cores available.", description = "The number of threads to use. Defaults to the number of cores available.",
validateWith = PositiveInteger.class) validateWith = PositiveInteger.class)
@ExtendedParameter(argumentNames = "n")
private int jobs = Runtime.getRuntime().availableProcessors(); private int jobs = Runtime.getRuntime().availableProcessors();
@Parameter(names = {"-a", "--api"}, @Parameter(names = {"-a", "--api"},
description = "The numeric api level to use while assembling.") description = "The numeric api level to use while assembling.")
@ExtendedParameter(argumentNames = "api")
private int apiLevel = 15; private int apiLevel = 15;
@Parameter(names = {"-o", "--output"}, @Parameter(names = {"-o", "--output"},
description = "The location of the dex file to write.") description = "The name/path of the dex file to write.")
@ExtendedParameter(argumentNames = "file")
private String output = "out.dex"; private String output = "out.dex";
@Parameter(names = "--experimental", @Parameter(names = "--experimental",
@ -75,17 +82,18 @@ public class AssembleCommand implements Command {
description = "Allows the odex opcodes that dalvik doesn't reject to be assembled.") description = "Allows the odex opcodes that dalvik doesn't reject to be assembled.")
private boolean allowOdexOpcodes; private boolean allowOdexOpcodes;
@Parameter(description = "[<file>|<dir>]+ - Assembles the given files. If a directory is specified, it will be " + @Parameter(description = "Assembles the given files. If a directory is specified, it will be " +
"recursively searched for any file with a .smali prefix") "recursively searched for any files with a .smali prefix")
@ExtendedParameter(argumentNames = "[<file>|<dir>]+")
private List<String> input; private List<String> input;
public AssembleCommand(@Nonnull JCommander jc) { public AssembleCommand(@Nonnull List<JCommander> commandAncestors) {
this.jc = jc; super(commandAncestors);
} }
@Override public void run() { @Override public void run() {
if (help || input == null || input.isEmpty()) { if (help || input == null || input.isEmpty()) {
jc.usage(jc.getParsedCommand()); usage();
return; return;
} }

View File

@ -34,35 +34,59 @@ package org.jf.smali;
import com.beust.jcommander.JCommander; import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters; import com.beust.jcommander.Parameters;
import org.jf.util.ConsoleUtil;
import org.jf.util.jcommander.*;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.util.List; import java.util.List;
@Parameters(commandDescription = "Shows usage information") @Parameters(commandDescription = "Shows usage information")
public class HelpCommand implements Command { @ExtendedParameters(
@Nonnull private final JCommander jc; commandName = "help",
commandAliases = "h")
public class HelpCommand extends Command {
public HelpCommand(@Nonnull JCommander jc) { @Parameter(description = "If specified, show the detailed usage information for the given commands")
this.jc = jc; @ExtendedParameter(argumentNames = "commands")
}
@Parameter(description = "If specified, only show the usage information for the given commands")
private List<String> commands; private List<String> commands;
public HelpCommand(@Nonnull List<JCommander> commandAncestors) {
super(commandAncestors);
}
public void run() { public void run() {
JCommander parentJc = commandAncestors.get(commandAncestors.size() - 1);
if (commands == null || commands.isEmpty()) { if (commands == null || commands.isEmpty()) {
jc.usage(); System.out.println(new HelpFormatter()
.width(ConsoleUtil.getConsoleWidth())
.format(commandAncestors));
} else { } else {
boolean printedHelp = false;
for (String cmd : commands) { for (String cmd : commands) {
jc.usage(cmd); JCommander command = ExtendedCommands.getSubcommand(parentJc, cmd);
if (command == null) {
System.err.println("No such command: " + cmd);
} else {
printedHelp = true;
System.out.println(new HelpFormatter()
.width(ConsoleUtil.getConsoleWidth())
.format(((Command)command.getObjects().get(0)).getCommandHierarchy()));
}
}
if (!printedHelp) {
System.out.println(new HelpFormatter()
.width(ConsoleUtil.getConsoleWidth())
.format(commandAncestors));
} }
} }
} }
@Parameters(hidden = true) @Parameters(hidden = true)
@ExtendedParameters(commandName = "hlep")
public static class HlepCommand extends HelpCommand { public static class HlepCommand extends HelpCommand {
public HlepCommand(@Nonnull JCommander jc) { public HlepCommand(@Nonnull List<JCommander> commandAncestors) {
super(jc); super(commandAncestors);
} }
} }
} }

View File

@ -33,13 +33,22 @@ package org.jf.smali;
import com.beust.jcommander.JCommander; import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameter;
import com.google.common.collect.Lists;
import org.jf.smali.HelpCommand.HlepCommand; import org.jf.smali.HelpCommand.HlepCommand;
import org.jf.util.jcommander.Command;
import org.jf.util.jcommander.ExtendedCommands;
import org.jf.util.jcommander.ExtendedParameters;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.List;
import java.util.Properties; import java.util.Properties;
public class Main { @ExtendedParameters(
includeParametersInUsage = true,
commandName = "smali",
postfixDescription = "See smali help <command> for more information about a specific command")
public class Main extends Command {
public static final String VERSION = loadVersion(); public static final String VERSION = loadVersion();
@Parameter(names = {"-h", "-?", "--help"}, help = true, @Parameter(names = {"-h", "-?", "--help"}, help = true,
@ -50,24 +59,39 @@ public class Main {
description = "Print the version of baksmali and then exit") description = "Print the version of baksmali and then exit")
public boolean version; public boolean version;
private JCommander jc;
@Override public void run() {
}
@Override protected JCommander getJCommander() {
return jc;
}
public Main() {
super(Lists.<JCommander>newArrayList());
}
public static void main(String[] args) { public static void main(String[] args) {
Main main = new Main(); Main main = new Main();
JCommander jc = new JCommander(main); JCommander jc = new JCommander(main);
main.jc = jc;
jc.setProgramName("smali");
List<JCommander> commandHierarchy = main.getCommandHierarchy();
jc.addCommand("assemble", new AssembleCommand(jc), "a", "as"); ExtendedCommands.addExtendedCommand(jc, new AssembleCommand(commandHierarchy));
jc.addCommand("help", new HelpCommand(jc), "h"); ExtendedCommands.addExtendedCommand(jc, new HelpCommand(commandHierarchy));
jc.addCommand("hlep", new HlepCommand(jc)); ExtendedCommands.addExtendedCommand(jc, new HlepCommand(commandHierarchy));
jc.parse(args); jc.parse(args);
if (jc.getParsedCommand() == null || main.help) {
jc.usage();
return;
}
if (main.version) { if (main.version) {
version(); version();
}
if (jc.getParsedCommand() == null || main.help) {
main.usage();
return; return;
} }

View File

@ -0,0 +1,184 @@
/*
* Copyright 2013, Google Inc.
* 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 Google Inc. 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.
*/
package org.jf.util;
import java.io.FilterWriter;
import java.io.IOException;
import java.io.Writer;
/**
* Writer that wraps another writer and passes width-limited and
* optionally-prefixed output to its subordinate. When lines are
* wrapped they are automatically indented based on the start of the
* line.
*/
public final class OldWrappedIndentingWriter extends FilterWriter {
/** null-ok; optional prefix for every line */
private final String prefix;
/** &gt; 0; the maximum output width */
private final int width;
/** &gt; 0; the maximum indent */
private final int maxIndent;
/** &gt;= 0; current output column (zero-based) */
private int column;
/** whether indent spaces are currently being collected */
private boolean collectingIndent;
/** &gt;= 0; current indent amount */
private int indent;
/**
* Constructs an instance.
*
* @param out non-null; writer to send final output to
* @param width &gt;= 0; the maximum output width (not including
* <code>prefix</code>), or <code>0</code> for no maximum
* @param prefix non-null; the prefix for each line
*/
public OldWrappedIndentingWriter(Writer out, int width, String prefix) {
super(out);
if (out == null) {
throw new NullPointerException("out == null");
}
if (width < 0) {
throw new IllegalArgumentException("width < 0");
}
if (prefix == null) {
throw new NullPointerException("prefix == null");
}
this.width = (width != 0) ? width : Integer.MAX_VALUE;
this.maxIndent = width >> 1;
this.prefix = (prefix.length() == 0) ? null : prefix;
bol();
}
/**
* Constructs a no-prefix instance.
*
* @param out non-null; writer to send final output to
* @param width &gt;= 0; the maximum output width (not including
* <code>prefix</code>), or <code>0</code> for no maximum
*/
public OldWrappedIndentingWriter(Writer out, int width) {
this(out, width, "");
}
/** {@inheritDoc} */
@Override
public void write(int c) throws IOException {
synchronized (lock) {
if (collectingIndent) {
if (c == ' ') {
indent++;
if (indent >= maxIndent) {
indent = maxIndent;
collectingIndent = false;
}
} else {
collectingIndent = false;
}
}
if ((column == width) && (c != '\n')) {
out.write('\n');
column = 0;
/*
* Note: No else, so this should fall through to the next
* if statement.
*/
}
if (column == 0) {
if (prefix != null) {
out.write(prefix);
}
if (!collectingIndent) {
for (int i = 0; i < indent; i++) {
out.write(' ');
}
column = indent;
}
}
out.write(c);
if (c == '\n') {
bol();
} else {
column++;
}
}
}
/** {@inheritDoc} */
@Override
public void write(char[] cbuf, int off, int len) throws IOException {
synchronized (lock) {
while (len > 0) {
write(cbuf[off]);
off++;
len--;
}
}
}
/** {@inheritDoc} */
@Override
public void write(String str, int off, int len) throws IOException {
synchronized (lock) {
while (len > 0) {
write(str.charAt(off));
off++;
len--;
}
}
}
/**
* Indicates that output is at the beginning of a line.
*/
private void bol() {
column = 0;
collectingIndent = (maxIndent != 0);
indent = 0;
}
}

View File

@ -1,18 +1,18 @@
/* /*
* Copyright 2013, Google Inc. * Copyright 2016, Google Inc.
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are * modification, are permitted provided that the following conditions are
* met: * met:
* *
* * Redistributions of source code must retain the above copyright * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer. * notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer * copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the * in the documentation and/or other materials provided with the
* distribution. * distribution.
* * Neither the name of Google Inc. nor the names of its * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from * contributors may be used to endorse or promote products derived from
* this software without specific prior written permission. * this software without specific prior written permission.
* *
@ -31,154 +31,94 @@
package org.jf.util; package org.jf.util;
import com.google.common.collect.Lists;
import java.io.FilterWriter; import java.io.FilterWriter;
import java.io.IOException; import java.io.IOException;
import java.io.Writer; import java.io.Writer;
import java.util.List;
/** public class WrappedIndentingWriter extends FilterWriter {
* Writer that wraps another writer and passes width-limited and
* optionally-prefixed output to its subordinate. When lines are
* wrapped they are automatically indented based on the start of the
* line.
*/
public final class WrappedIndentingWriter extends FilterWriter {
/** null-ok; optional prefix for every line */
private final String prefix;
/** &gt; 0; the maximum output width */
private final int width;
/** &gt; 0; the maximum indent */
private final int maxIndent; private final int maxIndent;
private final int maxWidth;
/** &gt;= 0; current output column (zero-based) */ private int currentIndent = 0;
private int column; private final StringBuilder line = new StringBuilder();
/** whether indent spaces are currently being collected */ public WrappedIndentingWriter(Writer out, int maxIndent, int maxWidth) {
private boolean collectingIndent;
/** &gt;= 0; current indent amount */
private int indent;
/**
* Constructs an instance.
*
* @param out non-null; writer to send final output to
* @param width &gt;= 0; the maximum output width (not including
* <code>prefix</code>), or <code>0</code> for no maximum
* @param prefix non-null; the prefix for each line
*/
public WrappedIndentingWriter(Writer out, int width, String prefix) {
super(out); super(out);
this.maxIndent = maxIndent;
if (out == null) { this.maxWidth = maxWidth;
throw new NullPointerException("out == null");
}
if (width < 0) {
throw new IllegalArgumentException("width < 0");
}
if (prefix == null) {
throw new NullPointerException("prefix == null");
}
this.width = (width != 0) ? width : Integer.MAX_VALUE;
this.maxIndent = width >> 1;
this.prefix = (prefix.length() == 0) ? null : prefix;
bol();
} }
/** private void writeIndent() throws IOException {
* Constructs a no-prefix instance. for (int i=0; i<getIndent(); i++) {
* write(' ');
* @param out non-null; writer to send final output to }
* @param width &gt;= 0; the maximum output width (not including
* <code>prefix</code>), or <code>0</code> for no maximum
*/
public WrappedIndentingWriter(Writer out, int width) {
this(out, width, "");
} }
/** {@inheritDoc} */ private int getIndent() {
@Override if (currentIndent < 0) {
public void write(int c) throws IOException { return 0;
synchronized (lock) { }
if (collectingIndent) { if (currentIndent > maxIndent) {
if (c == ' ') { return maxIndent;
indent++; }
if (indent >= maxIndent) { return currentIndent;
indent = maxIndent; }
collectingIndent = false;
} public void indent(int indent) {
} else { currentIndent += indent;
collectingIndent = false; }
}
} public void deindent(int indent) {
currentIndent -= indent;
if ((column == width) && (c != '\n')) { }
out.write('\n');
column = 0; private void wrapLine() throws IOException {
/* List<String> wrapped = Lists.newArrayList(StringWrapper.wrapStringOnBreaks(line.toString(), maxWidth));
* Note: No else, so this should fall through to the next out.write(wrapped.get(0), 0, wrapped.get(0).length());
* if statement. out.write('\n');
*/
} line.replace(0, line.length(), "");
writeIndent();
if (column == 0) { for (int i=1; i<wrapped.size(); i++) {
if (prefix != null) { if (i > 1) {
out.write(prefix); write('\n');
}
if (!collectingIndent) {
for (int i = 0; i < indent; i++) {
out.write(' ');
}
column = indent;
}
} }
write(wrapped.get(i));
}
}
@Override public void write(int c) throws IOException {
if (c == '\n') {
out.write(line.toString());
out.write(c); out.write(c);
line.replace(0, line.length(), "");
if (c == '\n') { writeIndent();
bol(); } else {
} else { line.append((char)c);
column++; if (line.length() > maxWidth) {
wrapLine();
} }
} }
} }
/** {@inheritDoc} */ @Override public void write(char[] cbuf, int off, int len) throws IOException {
@Override for (int i=0; i<len; i++) {
public void write(char[] cbuf, int off, int len) throws IOException { write(cbuf[i+off]);
synchronized (lock) {
while (len > 0) {
write(cbuf[off]);
off++;
len--;
}
} }
} }
/** {@inheritDoc} */ @Override public void write(String str, int off, int len) throws IOException {
@Override for (int i=0; i<len; i++) {
public void write(String str, int off, int len) throws IOException { write(str.charAt(i+off));
synchronized (lock) {
while (len > 0) {
write(str.charAt(off));
off++;
len--;
}
} }
} }
/** @Override public void flush() throws IOException {
* Indicates that output is at the beginning of a line. out.write(line.toString());
*/ line.replace(0, line.length(), "");
private void bol() {
column = 0;
collectingIndent = (maxIndent != 0);
indent = 0;
} }
} }

View File

@ -0,0 +1,69 @@
/*
* Copyright 2016, Google Inc.
* 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 Google Inc. 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.
*/
package org.jf.util.jcommander;
import com.beust.jcommander.JCommander;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import org.jf.util.ConsoleUtil;
import javax.annotation.Nonnull;
import java.util.List;
public abstract class Command {
@Nonnull
protected final List<JCommander> commandAncestors;
public Command(@Nonnull List<JCommander> commandAncestors) {
this.commandAncestors = commandAncestors;
}
public void usage() {
System.out.println(new HelpFormatter()
.width(ConsoleUtil.getConsoleWidth())
.format(getCommandHierarchy()));
}
protected JCommander getJCommander() {
JCommander parentJc = Iterables.getLast(commandAncestors);
return parentJc.getCommands().get(this.getClass().getAnnotation(ExtendedParameters.class).commandName());
}
public List<JCommander> getCommandHierarchy() {
List<JCommander> commandHierarchy = Lists.newArrayList(commandAncestors);
commandHierarchy.add(getJCommander());
return commandHierarchy;
}
public abstract void run();
}

View File

@ -0,0 +1,148 @@
/*
* Copyright 2016, Google Inc.
* 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 Google Inc. 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.
*/
package org.jf.util.jcommander;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameterized;
import com.beust.jcommander.Parameters;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.lang.reflect.Field;
/**
* Utilities related to "extended" commands - JCommander commands with additional information
*/
public class ExtendedCommands {
@Nonnull
private static ExtendedParameters getExtendedParameters(Object command) {
ExtendedParameters anno = command.getClass().getAnnotation(ExtendedParameters.class);
if (anno == null) {
throw new IllegalStateException("All extended commands should have an ExtendedParameters annotation: " +
command.getClass().getCanonicalName());
}
return anno;
}
@Nonnull
public static String commandName(JCommander jc) {
return getExtendedParameters(jc.getObjects().get(0)).commandName();
}
@Nonnull
public static String commandName(Object command) {
return getExtendedParameters(command).commandName();
}
@Nonnull
public static String[] commandAliases(JCommander jc) {
return commandAliases(jc.getObjects().get(0));
}
@Nonnull
public static String[] commandAliases(Object command) {
return getExtendedParameters(command).commandAliases();
}
public static boolean includeParametersInUsage(JCommander jc) {
return includeParametersInUsage(jc.getObjects().get(0));
}
public static boolean includeParametersInUsage(Object command) {
return getExtendedParameters(command).includeParametersInUsage();
}
@Nonnull
public static String postfixDescription(JCommander jc) {
return postfixDescription(jc.getObjects().get(0));
}
@Nonnull
public static String postfixDescription(Object command) {
return getExtendedParameters(command).postfixDescription();
}
public static void addExtendedCommand(JCommander jc, Object command) {
jc.addCommand(commandName(command), command, commandAliases(command));
}
@Nonnull
public static String[] parameterArgumentNames(JCommander jc, Parameterized parameterized) {
// TODO: this won't work if we're using additional objects to collect parameters
Class cls = jc.getObjects().get(0).getClass();
Field field = null;
while (cls != Object.class) {
try {
field = cls.getDeclaredField(parameterized.getName());
} catch (NoSuchFieldException ex) {
cls = cls.getSuperclass();
continue;
}
break;
}
assert field != null;
ExtendedParameter extendedParameter = field.getAnnotation(ExtendedParameter.class);
if (extendedParameter != null) {
return extendedParameter.argumentNames();
}
return new String[0];
}
@Nullable
public static JCommander getSubcommand(JCommander jc, String commandName) {
if (jc.getCommands().containsKey(commandName)) {
return jc.getCommands().get(commandName);
} else {
for (JCommander command : jc.getCommands().values()) {
for (String alias: commandAliases(command)) {
if (commandName.equals(alias)) {
return command;
}
}
}
}
return null;
}
@Nullable
public static String getCommandDescription(@Nonnull JCommander jc) {
Parameters parameters = jc.getObjects().get(0).getClass().getAnnotation(Parameters.class);
if (parameters == null) {
return null;
}
return parameters.commandDescription();
}
}

View File

@ -6,13 +6,13 @@
* modification, are permitted provided that the following conditions are * modification, are permitted provided that the following conditions are
* met: * met:
* *
* * Redistributions of source code must retain the above copyright * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer. * notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer * copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the * in the documentation and/or other materials provided with the
* distribution. * distribution.
* * Neither the name of Google Inc. nor the names of its * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from * contributors may be used to endorse or promote products derived from
* this software without specific prior written permission. * this software without specific prior written permission.
* *
@ -29,8 +29,12 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
package org.jf.baksmali; package org.jf.util.jcommander;
public interface Command { import java.lang.annotation.Retention;
void run(); import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface ExtendedParameter {
String[] argumentNames();
} }

View File

@ -6,13 +6,13 @@
* modification, are permitted provided that the following conditions are * modification, are permitted provided that the following conditions are
* met: * met:
* *
* * Redistributions of source code must retain the above copyright * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer. * notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer * copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the * in the documentation and/or other materials provided with the
* distribution. * distribution.
* * Neither the name of Google Inc. nor the names of its * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from * contributors may be used to endorse or promote products derived from
* this software without specific prior written permission. * this software without specific prior written permission.
* *
@ -29,8 +29,15 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
package org.jf.smali; package org.jf.util.jcommander;
public interface Command { import java.lang.annotation.Retention;
void run(); import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface ExtendedParameters {
boolean includeParametersInUsage() default false;
String commandName();
String[] commandAliases() default { };
String postfixDescription() default "";
} }

View File

@ -0,0 +1,319 @@
/*
* Copyright 2016, Google Inc.
* 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 Google Inc. 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.
*/
package org.jf.util.jcommander;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.ParameterDescription;
import com.beust.jcommander.Parameters;
import com.beust.jcommander.internal.Lists;
import com.google.common.base.Joiner;
import com.google.common.collect.Iterables;
import org.jf.util.WrappedIndentingWriter;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class HelpFormatter {
private int width = 80;
@Nonnull
public HelpFormatter width(int width) {
this.width = width;
return this;
}
@Nonnull
private static ExtendedParameters getExtendedParameters(JCommander jc) {
ExtendedParameters anno = jc.getObjects().get(0).getClass().getAnnotation(ExtendedParameters.class);
if (anno == null) {
throw new IllegalStateException("All commands should have an ExtendedParameters annotation");
}
return anno;
}
@Nonnull
private static List<String> getCommandAliases(JCommander jc) {
return Lists.newArrayList(getExtendedParameters(jc).commandAliases());
}
private static boolean includeParametersInUsage(@Nonnull JCommander jc) {
return getExtendedParameters(jc).includeParametersInUsage();
}
@Nonnull
private static String getPostfixDescription(@Nonnull JCommander jc) {
return getExtendedParameters(jc).postfixDescription();
}
private int getParameterArity(ParameterDescription param) {
if (param.getParameter().arity() > 0) {
return param.getParameter().arity();
}
Class<?> type = param.getParameterized().getType();
if ((type == boolean.class || type == Boolean.class)) {
return 0;
}
return 1;
}
private List<ParameterDescription> getSortedParameters(JCommander jc) {
List<ParameterDescription> parameters = Lists.newArrayList(jc.getParameters());
final Pattern pattern = Pattern.compile("^-*(.*)$");
Collections.sort(parameters, new Comparator<ParameterDescription>() {
@Override public int compare(ParameterDescription o1, ParameterDescription o2) {
String s1;
Matcher matcher = pattern.matcher(o1.getParameter().names()[0]);
if (matcher.matches()) {
s1 = matcher.group(1);
} else {
throw new IllegalStateException();
}
String s2;
matcher = pattern.matcher(o2.getParameter().names()[0]);
if (matcher.matches()) {
s2 = matcher.group(1);
} else {
throw new IllegalStateException();
}
return s1.compareTo(s2);
}
});
return parameters;
}
@Nonnull
public String format(@Nonnull JCommander... jc) {
return format(Arrays.asList(jc));
}
@Nonnull
public String format(@Nonnull List<JCommander> commandHierarchy) {
try {
StringWriter stringWriter = new StringWriter();
WrappedIndentingWriter writer = new WrappedIndentingWriter(stringWriter, width - 5, width);
JCommander leafJc = Iterables.getLast(commandHierarchy);
writer.write("usage:");
writer.indent(2);
for (JCommander jc: commandHierarchy) {
writer.write(" ");
writer.write(ExtendedCommands.commandName(jc));
}
if (includeParametersInUsage(leafJc)) {
for (ParameterDescription param : leafJc.getParameters()) {
if (!param.getParameter().hidden()) {
writer.write(" [");
writer.write(param.getParameter().getParameter().names()[0]);
writer.write("]");
}
}
} else {
if (!leafJc.getParameters().isEmpty()) {
writer.write(" [<options>]");
}
}
if (!leafJc.getCommands().isEmpty()) {
writer.write(" [<command [<args>]]");
}
if (leafJc.getMainParameter() != null) {
String[] argumentNames = ExtendedCommands.parameterArgumentNames(
leafJc, leafJc.getMainParameter().getParameterized());
if (argumentNames.length == 0) {
writer.write(" <args>");
} else {
String argumentName = argumentNames[0];
boolean writeAngleBrackets = !argumentName.startsWith("<") && !argumentName.startsWith("[");
writer.write(" ");
if (writeAngleBrackets) {
writer.write("<");
}
writer.write(argumentNames[0]);
if (writeAngleBrackets) {
writer.write(">");
}
}
}
writer.deindent(2);
String commandDescription = ExtendedCommands.getCommandDescription(leafJc);
if (commandDescription != null) {
writer.write("\n");
writer.write(commandDescription);
}
if (!leafJc.getParameters().isEmpty() || leafJc.getMainParameter() != null) {
writer.write("\n\nOptions:");
writer.indent(2);
for (ParameterDescription param : getSortedParameters(leafJc)) {
if (!param.getParameter().hidden()) {
writer.write("\n");
writer.indent(4);
if (!param.getNames().isEmpty()) {
writer.write(Joiner.on(',').join(param.getParameter().names()));
}
if (getParameterArity(param) > 0) {
String[] argumentNames = ExtendedCommands.parameterArgumentNames(
leafJc, param.getParameterized());
for (int i = 0; i < getParameterArity(param); i++) {
writer.write(" ");
if (i < argumentNames.length) {
writer.write("<");
writer.write(argumentNames[i]);
writer.write(">");
} else {
writer.write("<arg>");
}
}
}
if (param.getDescription() != null && !param.getDescription().isEmpty()) {
writer.write(" - ");
writer.write(param.getDescription());
}
if (param.getDefault() != null) {
String defaultValue = null;
if (param.getParameterized().getType() == Boolean.class ||
param.getParameterized().getType() == Boolean.TYPE) {
if ((Boolean)param.getDefault()) {
defaultValue = "True";
}
} else if (List.class.isAssignableFrom(param.getParameterized().getType())) {
if (!((List)param.getDefault()).isEmpty()) {
defaultValue = param.getDefault().toString();
}
} else {
defaultValue = param.getDefault().toString();
}
if (defaultValue != null) {
writer.write(" (default: ");
writer.write(defaultValue);
writer.write(")");
}
}
writer.deindent(4);
}
}
if (leafJc.getMainParameter() != null) {
String[] argumentNames = ExtendedCommands.parameterArgumentNames(leafJc,
leafJc.getMainParameter().getParameterized());
writer.write("\n");
writer.indent(4);
if (argumentNames.length > 0) {
writer.write("<");
writer.write(argumentNames[0]);
writer.write(">");
} else {
writer.write("<args>");
}
if (leafJc.getMainParameterDescription() != null) {
writer.write(" - ");
writer.write(leafJc.getMainParameterDescription());
}
writer.deindent(4);
}
writer.deindent(2);
}
if (!leafJc.getCommands().isEmpty()) {
writer.write("\n\nCommands:");
writer.indent(2);
List<Entry<String, JCommander>> entryList = Lists.newArrayList(leafJc.getCommands().entrySet());
Collections.sort(entryList, new Comparator<Entry<String, JCommander>>() {
@Override public int compare(Entry<String, JCommander> o1, Entry<String, JCommander> o2) {
return o1.getKey().compareTo(o2.getKey());
}
});
for (Entry<String, JCommander> entry : entryList) {
String commandName = entry.getKey();
JCommander command = entry.getValue();
Object arg = command.getObjects().get(0);
Parameters parametersAnno = arg.getClass().getAnnotation(Parameters.class);
if (!parametersAnno.hidden()) {
writer.write("\n");
writer.indent(4);
writer.write(commandName);
List<String> aliases = getCommandAliases(command);
if (!aliases.isEmpty()) {
writer.write("(");
writer.write(Joiner.on(',').join(aliases));
writer.write(")");
}
String commandDesc = leafJc.getCommandDescription(commandName);
if (commandDesc != null) {
writer.write(" - ");
writer.write(commandDesc);
}
writer.deindent(4);
}
}
writer.deindent(2);
}
String postfixDescription = getPostfixDescription(leafJc);
if (!postfixDescription.isEmpty()) {
writer.write("\n\n");
writer.write(postfixDescription);
}
writer.flush();
return stringWriter.getBuffer().toString();
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
}