mirror of
https://github.com/revanced/smali.git
synced 2025-05-05 17:14:25 +02:00
Revamp the CLI usage/help formatting
This commit is contained in:
parent
e474301e60
commit
ca48e6f7d0
@ -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) {
|
||||||
|
@ -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.
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
92
baksmali/src/main/java/org/jf/baksmali/ListHelpCommand.java
Normal file
92
baksmali/src/main/java/org/jf/baksmali/ListHelpCommand.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
184
util/src/main/java/org/jf/util/OldWrappedIndentingWriter.java
Normal file
184
util/src/main/java/org/jf/util/OldWrappedIndentingWriter.java
Normal 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;
|
||||||
|
|
||||||
|
/** > 0; the maximum output width */
|
||||||
|
private final int width;
|
||||||
|
|
||||||
|
/** > 0; the maximum indent */
|
||||||
|
private final int maxIndent;
|
||||||
|
|
||||||
|
/** >= 0; current output column (zero-based) */
|
||||||
|
private int column;
|
||||||
|
|
||||||
|
/** whether indent spaces are currently being collected */
|
||||||
|
private boolean collectingIndent;
|
||||||
|
|
||||||
|
/** >= 0; current indent amount */
|
||||||
|
private int indent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance.
|
||||||
|
*
|
||||||
|
* @param out non-null; writer to send final output to
|
||||||
|
* @param width >= 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 >= 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;
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
|
||||||
|
|
||||||
/** > 0; the maximum output width */
|
|
||||||
private final int width;
|
|
||||||
|
|
||||||
/** > 0; the maximum indent */
|
|
||||||
private final int maxIndent;
|
private final int maxIndent;
|
||||||
|
private final int maxWidth;
|
||||||
|
|
||||||
/** >= 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;
|
|
||||||
|
|
||||||
/** >= 0; current indent amount */
|
|
||||||
private int indent;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs an instance.
|
|
||||||
*
|
|
||||||
* @param out non-null; writer to send final output to
|
|
||||||
* @param width >= 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 >= 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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
69
util/src/main/java/org/jf/util/jcommander/Command.java
Normal file
69
util/src/main/java/org/jf/util/jcommander/Command.java
Normal 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();
|
||||||
|
}
|
148
util/src/main/java/org/jf/util/jcommander/ExtendedCommands.java
Normal file
148
util/src/main/java/org/jf/util/jcommander/ExtendedCommands.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
}
|
}
|
@ -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 "";
|
||||||
}
|
}
|
319
util/src/main/java/org/jf/util/jcommander/HelpFormatter.java
Normal file
319
util/src/main/java/org/jf/util/jcommander/HelpFormatter.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user