auto copy original apk to assets
This commit is contained in:
parent
2c80821646
commit
581aaf74f7
|
|
@ -1,309 +1,315 @@
|
||||||
package com.storm.wind.xpatch;
|
package com.storm.wind.xpatch;
|
||||||
|
|
||||||
import com.storm.wind.xpatch.base.BaseCommand;
|
import com.storm.wind.xpatch.base.BaseCommand;
|
||||||
import com.storm.wind.xpatch.task.ApkModifyTask;
|
import com.storm.wind.xpatch.task.ApkModifyTask;
|
||||||
import com.storm.wind.xpatch.task.BuildAndSignApkTask;
|
import com.storm.wind.xpatch.task.BuildAndSignApkTask;
|
||||||
import com.storm.wind.xpatch.task.SaveApkSignatureTask;
|
import com.storm.wind.xpatch.task.SaveApkSignatureTask;
|
||||||
import com.storm.wind.xpatch.task.SaveOriginalApplicationNameTask;
|
import com.storm.wind.xpatch.task.SaveOriginalApplicationNameTask;
|
||||||
import com.storm.wind.xpatch.task.SoAndDexCopyTask;
|
import com.storm.wind.xpatch.task.SoAndDexCopyTask;
|
||||||
import com.storm.wind.xpatch.util.FileUtils;
|
import com.storm.wind.xpatch.util.FileUtils;
|
||||||
import com.storm.wind.xpatch.util.ManifestParser;
|
import com.storm.wind.xpatch.util.ManifestParser;
|
||||||
import com.wind.meditor.core.FileProcesser;
|
import com.wind.meditor.core.FileProcesser;
|
||||||
import com.wind.meditor.property.AttributeItem;
|
import com.wind.meditor.property.AttributeItem;
|
||||||
import com.wind.meditor.property.ModificationProperty;
|
import com.wind.meditor.property.ModificationProperty;
|
||||||
import com.wind.meditor.utils.NodeValue;
|
import com.wind.meditor.utils.NodeValue;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
public class MainCommand extends BaseCommand {
|
public class MainCommand extends BaseCommand {
|
||||||
|
|
||||||
private String apkPath;
|
private String apkPath;
|
||||||
|
|
||||||
private String unzipApkFilePath;
|
private String unzipApkFilePath;
|
||||||
|
|
||||||
@Opt(opt = "o", longOpt = "output", description = "output .apk file, default is " +
|
@Opt(opt = "o", longOpt = "output", description = "output .apk file, default is " +
|
||||||
"$source_apk_dir/[file-name]-xposed-signed.apk", argName = "out-apk-file")
|
"$source_apk_dir/[file-name]-xposed-signed.apk", argName = "out-apk-file")
|
||||||
private String output; // 输出的apk文件的目录以及名称
|
private String output; // 输出的apk文件的目录以及名称
|
||||||
|
|
||||||
@Opt(opt = "f", longOpt = "force", hasArg = false, description = "force overwrite")
|
@Opt(opt = "f", longOpt = "force", hasArg = false, description = "force overwrite")
|
||||||
private boolean forceOverwrite = false;
|
private boolean forceOverwrite = false;
|
||||||
|
|
||||||
@Opt(opt = "k", longOpt = "keep", hasArg = false, description = "not delete the jar file " +
|
@Opt(opt = "k", longOpt = "keep", hasArg = false, description = "not delete the jar file " +
|
||||||
"that is changed by dex2jar and the apk zip files")
|
"that is changed by dex2jar and the apk zip files")
|
||||||
private boolean keepBuildFiles = false;
|
private boolean keepBuildFiles = false;
|
||||||
|
|
||||||
@Opt(opt = "l", longOpt = "log", hasArg = false, description = "show all the debug logs")
|
@Opt(opt = "l", longOpt = "log", hasArg = false, description = "show all the debug logs")
|
||||||
private boolean showAllLogs = false;
|
private boolean showAllLogs = false;
|
||||||
|
|
||||||
@Opt(opt = "c", longOpt = "crach", hasArg = false,
|
@Opt(opt = "c", longOpt = "crach", hasArg = false,
|
||||||
description = "disable craching the apk's signature.")
|
description = "disable craching the apk's signature.")
|
||||||
private boolean disableCrackSignature = false;
|
private boolean disableCrackSignature = false;
|
||||||
|
|
||||||
@Opt(opt = "xm", longOpt = "xposed-modules", description = "the xposed module files to be packaged into the apk, " +
|
@Opt(opt = "xm", longOpt = "xposed-modules", description = "the xposed module files to be packaged into the apk, " +
|
||||||
"multi files should be seperated by :(mac) or ;(win) ", argName = "xposed module file path")
|
"multi files should be seperated by :(mac) or ;(win) ", argName = "xposed module file path")
|
||||||
private String xposedModules;
|
private String xposedModules;
|
||||||
|
|
||||||
// 使用dex文件中插入代码的方式修改apk,而不是默认的修改Manifest中Application name的方式
|
// 使用dex文件中插入代码的方式修改apk,而不是默认的修改Manifest中Application name的方式
|
||||||
@Opt(opt = "dex", longOpt = "dex", hasArg = false, description = "insert code into the dex file, not modify manifest application name attribute")
|
@Opt(opt = "dex", longOpt = "dex", hasArg = false, description = "insert code into the dex file, not modify manifest application name attribute")
|
||||||
private boolean dexModificationMode = false;
|
private boolean dexModificationMode = false;
|
||||||
|
|
||||||
@Opt(opt = "pkg", longOpt = "packageName", description = "modify the apk package name", argName = "new package name")
|
@Opt(opt = "pkg", longOpt = "packageName", description = "modify the apk package name", argName = "new package name")
|
||||||
private String newPackageName;
|
private String newPackageName;
|
||||||
|
|
||||||
@Opt(opt = "d", longOpt = "debuggable", description = "set 1 to make the app debuggable = true, " +
|
@Opt(opt = "d", longOpt = "debuggable", description = "set 1 to make the app debuggable = true, " +
|
||||||
"set 0 to make the app debuggable = false", argName = "0 or 1")
|
"set 0 to make the app debuggable = false", argName = "0 or 1")
|
||||||
private int debuggable = -1; // 0: debuggable = false 1: debuggable = true
|
private int debuggable = -1; // 0: debuggable = false 1: debuggable = true
|
||||||
|
|
||||||
@Opt(opt = "vc", longOpt = "versionCode", description = "set the app version code",
|
@Opt(opt = "vc", longOpt = "versionCode", description = "set the app version code",
|
||||||
argName = "new-version-code")
|
argName = "new-version-code")
|
||||||
private int versionCode;
|
private int versionCode;
|
||||||
|
|
||||||
@Opt(opt = "vn", longOpt = "versionName", description = "set the app version name",
|
@Opt(opt = "vn", longOpt = "versionName", description = "set the app version name",
|
||||||
argName = "new-version-name")
|
argName = "new-version-name")
|
||||||
private String versionName;
|
private String versionName;
|
||||||
|
|
||||||
@Opt(opt = "w", longOpt = "whale", hasArg = false, description = "Change hook framework to Lody's whale")
|
@Opt(opt = "w", longOpt = "whale", hasArg = false, description = "Change hook framework to Lody's whale")
|
||||||
private boolean useWhaleHookFramework = false; // 是否使用whale hook框架,默认使用的是SandHook
|
private boolean useWhaleHookFramework = false; // 是否使用whale hook框架,默认使用的是SandHook
|
||||||
|
|
||||||
// 原来apk中dex文件的数量
|
// 原来apk中dex文件的数量
|
||||||
private int dexFileCount = 0;
|
private int dexFileCount = 0;
|
||||||
|
|
||||||
private static final String UNZIP_APK_FILE_NAME = "apk-unzip-files";
|
private static final String UNZIP_APK_FILE_NAME = "apk-unzip-files";
|
||||||
|
|
||||||
private static final String PROXY_APPLICATION_NAME = "com.wind.xpatch.proxy.XpatchProxyApplication";
|
private static final String PROXY_APPLICATION_NAME = "com.wind.xpatch.proxy.XpatchProxyApplication";
|
||||||
|
|
||||||
private List<Runnable> mXpatchTasks = new ArrayList<>();
|
private List<Runnable> mXpatchTasks = new ArrayList<>();
|
||||||
|
|
||||||
public static void main(String... args) {
|
public static void main(String... args) {
|
||||||
new MainCommand().doMain(args);
|
new MainCommand().doMain(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doCommandLine() {
|
protected void doCommandLine() {
|
||||||
if (remainingArgs.length != 1) {
|
if (remainingArgs.length != 1) {
|
||||||
if (remainingArgs.length == 0) {
|
if (remainingArgs.length == 0) {
|
||||||
System.out.println("Please choose one apk file you want to process. ");
|
System.out.println("Please choose one apk file you want to process. ");
|
||||||
}
|
}
|
||||||
if (remainingArgs.length > 1) {
|
if (remainingArgs.length > 1) {
|
||||||
System.out.println("This tool can only used with one apk file.");
|
System.out.println("This tool can only used with one apk file.");
|
||||||
}
|
}
|
||||||
usage();
|
usage();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
apkPath = remainingArgs[0];
|
apkPath = remainingArgs[0];
|
||||||
|
|
||||||
File srcApkFile = new File(apkPath);
|
File srcApkFile = new File(apkPath);
|
||||||
|
|
||||||
if (!srcApkFile.exists()) {
|
if (!srcApkFile.exists()) {
|
||||||
System.out.println(" The source apk file not exsit, please choose another one. " +
|
System.out.println(" The source apk file not exsit, please choose another one. " +
|
||||||
"current apk file is = " + apkPath);
|
"current apk file is = " + apkPath);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String currentDir = new File(".").getAbsolutePath(); // 当前命令行所在的目录
|
String currentDir = new File(".").getAbsolutePath(); // 当前命令行所在的目录
|
||||||
if (showAllLogs) {
|
if (showAllLogs) {
|
||||||
System.out.println(" currentDir = " + currentDir + " \n apkPath = " + apkPath);
|
System.out.println(" currentDir = " + currentDir + " \n apkPath = " + apkPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (output == null || output.length() == 0) {
|
if (output == null || output.length() == 0) {
|
||||||
output = getBaseName(apkPath) + "-xposed-signed.apk";
|
output = getBaseName(apkPath) + "-xposed-signed.apk";
|
||||||
}
|
}
|
||||||
|
|
||||||
File outputFile = new File(output);
|
File outputFile = new File(output);
|
||||||
if (outputFile.exists() && !forceOverwrite) {
|
if (outputFile.exists() && !forceOverwrite) {
|
||||||
System.err.println(output + " exists, use --force to overwrite");
|
System.err.println(output + " exists, use --force to overwrite");
|
||||||
usage();
|
usage();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String outputApkFileParentPath = outputFile.getParent();
|
String outputApkFileParentPath = outputFile.getParent();
|
||||||
if (outputApkFileParentPath == null) {
|
if (outputApkFileParentPath == null) {
|
||||||
String absPath = outputFile.getAbsolutePath();
|
String absPath = outputFile.getAbsolutePath();
|
||||||
int index = absPath.lastIndexOf(File.separatorChar);
|
int index = absPath.lastIndexOf(File.separatorChar);
|
||||||
outputApkFileParentPath = absPath.substring(0, index);
|
outputApkFileParentPath = absPath.substring(0, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
System.out.println(" !!!!! output apk path --> " + output +
|
System.out.println(" !!!!! output apk path --> " + output +
|
||||||
" disableCrackSignature --> " + disableCrackSignature);
|
" disableCrackSignature --> " + disableCrackSignature);
|
||||||
|
|
||||||
String apkFileName = getBaseName(srcApkFile);
|
String apkFileName = getBaseName(srcApkFile);
|
||||||
|
|
||||||
// 中间文件临时存储的位置
|
// 中间文件临时存储的位置
|
||||||
String tempFilePath = outputApkFileParentPath + File.separator +
|
String tempFilePath = outputApkFileParentPath + File.separator +
|
||||||
currentTimeStr() + "-tmp" + File.separator;
|
currentTimeStr() + "-tmp" + File.separator;
|
||||||
|
|
||||||
// apk文件解压的目录
|
// apk文件解压的目录
|
||||||
unzipApkFilePath = tempFilePath + apkFileName + "-" + UNZIP_APK_FILE_NAME + File.separator;
|
unzipApkFilePath = tempFilePath + apkFileName + "-" + UNZIP_APK_FILE_NAME + File.separator;
|
||||||
|
|
||||||
if (showAllLogs) {
|
if (showAllLogs) {
|
||||||
System.out.println(" !!!!! outputApkFileParentPath = " + outputApkFileParentPath +
|
System.out.println(" !!!!! outputApkFileParentPath = " + outputApkFileParentPath +
|
||||||
"\n unzipApkFilePath = " + unzipApkFilePath);
|
"\n unzipApkFilePath = " + unzipApkFilePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!disableCrackSignature) {
|
if (!disableCrackSignature) {
|
||||||
// save the apk original signature info, to support crach signature.
|
// save the apk original signature info, to support crach signature.
|
||||||
new SaveApkSignatureTask(apkPath, unzipApkFilePath).run();
|
new SaveApkSignatureTask(apkPath, unzipApkFilePath).run();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 先解压apk到指定目录下
|
// 先解压apk到指定目录下
|
||||||
long currentTime = System.currentTimeMillis();
|
long currentTime = System.currentTimeMillis();
|
||||||
FileUtils.decompressZip(apkPath, unzipApkFilePath);
|
FileUtils.decompressZip(apkPath, unzipApkFilePath);
|
||||||
|
|
||||||
if (showAllLogs) {
|
if (showAllLogs) {
|
||||||
System.out.println(" decompress apk cost time: " + (System.currentTimeMillis() - currentTime));
|
System.out.println(" decompress apk cost time: " + (System.currentTimeMillis() - currentTime));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the dex count in the apk zip file
|
// Get the dex count in the apk zip file
|
||||||
dexFileCount = findDexFileCount(unzipApkFilePath);
|
dexFileCount = findDexFileCount(unzipApkFilePath);
|
||||||
|
|
||||||
if (showAllLogs) {
|
if (showAllLogs) {
|
||||||
System.out.println(" --- dexFileCount = " + dexFileCount);
|
System.out.println(" --- dexFileCount = " + dexFileCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
String manifestFilePath = unzipApkFilePath + "AndroidManifest.xml";
|
String manifestFilePath = unzipApkFilePath + "AndroidManifest.xml";
|
||||||
|
|
||||||
currentTime = System.currentTimeMillis();
|
currentTime = System.currentTimeMillis();
|
||||||
|
|
||||||
// parse the app main application full name from the manifest file
|
// parse the app main application full name from the manifest file
|
||||||
ManifestParser.Pair pair = ManifestParser.parseManifestFile(manifestFilePath);
|
ManifestParser.Pair pair = ManifestParser.parseManifestFile(manifestFilePath);
|
||||||
String applicationName = null;
|
String applicationName = null;
|
||||||
if (pair != null && pair.applicationName != null) {
|
if (pair != null && pair.applicationName != null) {
|
||||||
applicationName = pair.applicationName;
|
applicationName = pair.applicationName;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showAllLogs) {
|
if (showAllLogs) {
|
||||||
System.out.println(" Get application name cost time: " + (System.currentTimeMillis() - currentTime));
|
System.out.println(" Get application name cost time: " + (System.currentTimeMillis() - currentTime));
|
||||||
System.out.println(" Get the application name --> " + applicationName);
|
System.out.println(" Get the application name --> " + applicationName);
|
||||||
}
|
}
|
||||||
|
|
||||||
// modify manifest
|
// modify manifest
|
||||||
File manifestFile = new File(manifestFilePath);
|
File manifestFile = new File(manifestFilePath);
|
||||||
String manifestFilePathNew = unzipApkFilePath + "AndroidManifest" + "-" + currentTimeStr() + ".xml";
|
String manifestFilePathNew = unzipApkFilePath + "AndroidManifest" + "-" + currentTimeStr() + ".xml";
|
||||||
File manifestFileNew = new File(manifestFilePathNew);
|
File manifestFileNew = new File(manifestFilePathNew);
|
||||||
manifestFile.renameTo(manifestFileNew);
|
manifestFile.renameTo(manifestFileNew);
|
||||||
|
|
||||||
modifyManifestFile(manifestFilePathNew, manifestFilePath, applicationName);
|
modifyManifestFile(manifestFilePathNew, manifestFilePath, applicationName);
|
||||||
|
|
||||||
// new manifest may not exist
|
// new manifest may not exist
|
||||||
if (manifestFile.exists() && manifestFile.length() > 0) {
|
if (manifestFile.exists() && manifestFile.length() > 0) {
|
||||||
manifestFileNew.delete();
|
manifestFileNew.delete();
|
||||||
} else {
|
} else {
|
||||||
manifestFileNew.renameTo(manifestFile);
|
manifestFileNew.renameTo(manifestFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
// save original main application name to asset file
|
// save original main application name to asset file
|
||||||
if (isNotEmpty(applicationName)) {
|
if (isNotEmpty(applicationName)) {
|
||||||
mXpatchTasks.add(new SaveOriginalApplicationNameTask(applicationName, unzipApkFilePath));
|
mXpatchTasks.add(new SaveOriginalApplicationNameTask(applicationName, unzipApkFilePath));
|
||||||
}
|
}
|
||||||
|
|
||||||
// modify the apk dex file to make xposed can run in it
|
// modify the apk dex file to make xposed can run in it
|
||||||
if (dexModificationMode && isNotEmpty(applicationName)) {
|
if (dexModificationMode && isNotEmpty(applicationName)) {
|
||||||
mXpatchTasks.add(new ApkModifyTask(showAllLogs, keepBuildFiles, unzipApkFilePath, applicationName,
|
mXpatchTasks.add(new ApkModifyTask(showAllLogs, keepBuildFiles, unzipApkFilePath, applicationName,
|
||||||
dexFileCount));
|
dexFileCount));
|
||||||
}
|
}
|
||||||
|
|
||||||
// copy xposed so and dex files into the unzipped apk
|
// copy xposed so and dex files into the unzipped apk
|
||||||
mXpatchTasks.add(new SoAndDexCopyTask(dexFileCount, unzipApkFilePath,
|
mXpatchTasks.add(new SoAndDexCopyTask(dexFileCount, unzipApkFilePath,
|
||||||
getXposedModules(xposedModules), useWhaleHookFramework));
|
getXposedModules(xposedModules), useWhaleHookFramework));
|
||||||
|
|
||||||
// compress all files into an apk and then sign it.
|
// compress all files into an apk and then sign it.
|
||||||
mXpatchTasks.add(new BuildAndSignApkTask(keepBuildFiles, unzipApkFilePath, output));
|
mXpatchTasks.add(new BuildAndSignApkTask(keepBuildFiles, unzipApkFilePath, output));
|
||||||
|
|
||||||
// excute these tasks
|
// copy origin apk to assets
|
||||||
for (Runnable executor : mXpatchTasks) {
|
// convenient to bypass some check like CRC
|
||||||
currentTime = System.currentTimeMillis();
|
if(!FileUtils.copyFile(srcApkFile, new File(unzipApkFilePath, "assets/origin_apk.bin"))){
|
||||||
executor.run();
|
throw new IllegalStateException("orignal apk copy fail");
|
||||||
|
}
|
||||||
if (showAllLogs) {
|
|
||||||
System.out.println(executor.getClass().getSimpleName() + " cost time: "
|
// excute these tasks
|
||||||
+ (System.currentTimeMillis() - currentTime));
|
for (Runnable executor : mXpatchTasks) {
|
||||||
}
|
currentTime = System.currentTimeMillis();
|
||||||
}
|
executor.run();
|
||||||
|
|
||||||
// 5. delete all the build files that is useless now
|
if (showAllLogs) {
|
||||||
File unzipApkFile = new File(unzipApkFilePath);
|
System.out.println(executor.getClass().getSimpleName() + " cost time: "
|
||||||
if (!keepBuildFiles && unzipApkFile.exists()) {
|
+ (System.currentTimeMillis() - currentTime));
|
||||||
FileUtils.deleteDir(unzipApkFile);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
File tempFile = new File(tempFilePath);
|
// 5. delete all the build files that is useless now
|
||||||
if (!keepBuildFiles && tempFile.exists()) {
|
File unzipApkFile = new File(unzipApkFilePath);
|
||||||
tempFile.delete();
|
if (!keepBuildFiles && unzipApkFile.exists()) {
|
||||||
}
|
FileUtils.deleteDir(unzipApkFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void modifyManifestFile(String filePath, String dstFilePath, String originalApplicationName) {
|
File tempFile = new File(tempFilePath);
|
||||||
ModificationProperty property = new ModificationProperty();
|
if (!keepBuildFiles && tempFile.exists()) {
|
||||||
boolean modifyEnabled = false;
|
tempFile.delete();
|
||||||
if (isNotEmpty(newPackageName)) {
|
}
|
||||||
modifyEnabled = true;
|
}
|
||||||
property.addManifestAttribute(new AttributeItem(NodeValue.Manifest.PACKAGE, newPackageName).setNamespace(null));
|
|
||||||
}
|
private void modifyManifestFile(String filePath, String dstFilePath, String originalApplicationName) {
|
||||||
|
ModificationProperty property = new ModificationProperty();
|
||||||
if (versionCode > 0) {
|
boolean modifyEnabled = false;
|
||||||
modifyEnabled = true;
|
if (isNotEmpty(newPackageName)) {
|
||||||
property.addManifestAttribute(new AttributeItem(NodeValue.Manifest.VERSION_CODE, versionCode));
|
modifyEnabled = true;
|
||||||
}
|
property.addManifestAttribute(new AttributeItem(NodeValue.Manifest.PACKAGE, newPackageName).setNamespace(null));
|
||||||
|
}
|
||||||
if (isNotEmpty(versionName)) {
|
|
||||||
modifyEnabled = true;
|
if (versionCode > 0) {
|
||||||
property.addManifestAttribute(new AttributeItem(NodeValue.Manifest.VERSION_NAME, versionName));
|
modifyEnabled = true;
|
||||||
}
|
property.addManifestAttribute(new AttributeItem(NodeValue.Manifest.VERSION_CODE, versionCode));
|
||||||
|
}
|
||||||
if (debuggable >= 0) {
|
|
||||||
modifyEnabled = true;
|
if (isNotEmpty(versionName)) {
|
||||||
property.addApplicationAttribute(new AttributeItem(NodeValue.Application.DEBUGGABLE, debuggable != 0));
|
modifyEnabled = true;
|
||||||
}
|
property.addManifestAttribute(new AttributeItem(NodeValue.Manifest.VERSION_NAME, versionName));
|
||||||
|
}
|
||||||
if (!dexModificationMode || !isNotEmpty(originalApplicationName)) {
|
|
||||||
modifyEnabled = true;
|
if (debuggable >= 0) {
|
||||||
property.addApplicationAttribute(new AttributeItem(NodeValue.Application.NAME, PROXY_APPLICATION_NAME));
|
modifyEnabled = true;
|
||||||
}
|
property.addApplicationAttribute(new AttributeItem(NodeValue.Application.DEBUGGABLE, debuggable != 0));
|
||||||
|
}
|
||||||
if (modifyEnabled) {
|
|
||||||
FileProcesser.processManifestFile(filePath, dstFilePath, property);
|
if (!dexModificationMode || !isNotEmpty(originalApplicationName)) {
|
||||||
}
|
modifyEnabled = true;
|
||||||
}
|
property.addApplicationAttribute(new AttributeItem(NodeValue.Application.NAME, PROXY_APPLICATION_NAME));
|
||||||
|
}
|
||||||
private int findDexFileCount(String unzipApkFilePath) {
|
|
||||||
File zipfileRoot = new File(unzipApkFilePath);
|
if (modifyEnabled) {
|
||||||
if (!zipfileRoot.exists()) {
|
FileProcesser.processManifestFile(filePath, dstFilePath, property);
|
||||||
return 0;
|
}
|
||||||
}
|
}
|
||||||
File[] childFiles = zipfileRoot.listFiles();
|
|
||||||
if (childFiles == null || childFiles.length == 0) {
|
private int findDexFileCount(String unzipApkFilePath) {
|
||||||
return 0;
|
File zipfileRoot = new File(unzipApkFilePath);
|
||||||
}
|
if (!zipfileRoot.exists()) {
|
||||||
int count = 0;
|
return 0;
|
||||||
for (File file : childFiles) {
|
}
|
||||||
String fileName = file.getName();
|
File[] childFiles = zipfileRoot.listFiles();
|
||||||
if (Pattern.matches("classes.*\\.dex", fileName)) {
|
if (childFiles == null || childFiles.length == 0) {
|
||||||
count++;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
int count = 0;
|
||||||
return count;
|
for (File file : childFiles) {
|
||||||
}
|
String fileName = file.getName();
|
||||||
|
if (Pattern.matches("classes.*\\.dex", fileName)) {
|
||||||
// Use the current timestamp as the name of the build file
|
count++;
|
||||||
private String currentTimeStr() {
|
}
|
||||||
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");//设置日期格式
|
}
|
||||||
return df.format(new Date());
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String[] getXposedModules(String modules) {
|
// Use the current timestamp as the name of the build file
|
||||||
if (modules == null || modules.isEmpty()) {
|
private String currentTimeStr() {
|
||||||
return null;
|
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");//设置日期格式
|
||||||
}
|
return df.format(new Date());
|
||||||
return modules.split(File.pathSeparator);
|
}
|
||||||
}
|
|
||||||
|
private String[] getXposedModules(String modules) {
|
||||||
private static boolean isNotEmpty(String str) {
|
if (modules == null || modules.isEmpty()) {
|
||||||
return str != null && !str.isEmpty();
|
return null;
|
||||||
}
|
}
|
||||||
}
|
return modules.split(File.pathSeparator);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isNotEmpty(String str) {
|
||||||
|
return str != null && !str.isEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,478 +1,478 @@
|
||||||
package com.storm.wind.xpatch.base;
|
package com.storm.wind.xpatch.base;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.OutputStreamWriter;
|
import java.io.OutputStreamWriter;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.lang.annotation.ElementType;
|
import java.lang.annotation.ElementType;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.ParameterizedType;
|
import java.lang.reflect.ParameterizedType;
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by Wind
|
* Created by Wind
|
||||||
*/
|
*/
|
||||||
public abstract class BaseCommand {
|
public abstract class BaseCommand {
|
||||||
|
|
||||||
private String onlineHelp;
|
private String onlineHelp;
|
||||||
|
|
||||||
protected Map<String, Option> optMap = new HashMap<String, Option>();
|
protected Map<String, Option> optMap = new HashMap<String, Option>();
|
||||||
|
|
||||||
@Opt(opt = "h", longOpt = "help", hasArg = false, description = "Print this help message")
|
@Opt(opt = "h", longOpt = "help", hasArg = false, description = "Print this help message")
|
||||||
private boolean printHelp = false;
|
private boolean printHelp = false;
|
||||||
|
|
||||||
protected String remainingArgs[];
|
protected String[] remainingArgs;
|
||||||
protected String orginalArgs[];
|
protected String[] orginalArgs;
|
||||||
|
|
||||||
@Retention(value = RetentionPolicy.RUNTIME)
|
@Retention(value = RetentionPolicy.RUNTIME)
|
||||||
@Target(value = { ElementType.FIELD })
|
@Target(value = { ElementType.FIELD })
|
||||||
static public @interface Opt {
|
static public @interface Opt {
|
||||||
String argName() default "";
|
String argName() default "";
|
||||||
|
|
||||||
String description() default "";
|
String description() default "";
|
||||||
|
|
||||||
boolean hasArg() default true;
|
boolean hasArg() default true;
|
||||||
|
|
||||||
String longOpt() default "";
|
String longOpt() default "";
|
||||||
|
|
||||||
String opt() default "";
|
String opt() default "";
|
||||||
|
|
||||||
boolean required() default false;
|
boolean required() default false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static protected class Option implements Comparable<Option> {
|
static protected class Option implements Comparable<Option> {
|
||||||
public String argName = "arg";
|
public String argName = "arg";
|
||||||
public String description;
|
public String description;
|
||||||
public Field field;
|
public Field field;
|
||||||
public boolean hasArg = true;
|
public boolean hasArg = true;
|
||||||
public String longOpt;
|
public String longOpt;
|
||||||
public String opt;
|
public String opt;
|
||||||
public boolean required = false;
|
public boolean required = false;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compareTo(Option o) {
|
public int compareTo(Option o) {
|
||||||
int result = s(this.opt, o.opt);
|
int result = s(this.opt, o.opt);
|
||||||
if (result == 0) {
|
if (result == 0) {
|
||||||
result = s(this.longOpt, o.longOpt);
|
result = s(this.longOpt, o.longOpt);
|
||||||
if (result == 0) {
|
if (result == 0) {
|
||||||
result = s(this.argName, o.argName);
|
result = s(this.argName, o.argName);
|
||||||
if (result == 0) {
|
if (result == 0) {
|
||||||
result = s(this.description, o.description);
|
result = s(this.description, o.description);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int s(String a, String b) {
|
private static int s(String a, String b) {
|
||||||
if (a != null && b != null) {
|
if (a != null && b != null) {
|
||||||
return a.compareTo(b);
|
return a.compareTo(b);
|
||||||
} else if (a != null) {
|
} else if (a != null) {
|
||||||
return 1;
|
return 1;
|
||||||
} else if (b != null) {
|
} else if (b != null) {
|
||||||
return -1;
|
return -1;
|
||||||
} else {
|
} else {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getOptAndLongOpt() {
|
public String getOptAndLongOpt() {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
boolean havePrev = false;
|
boolean havePrev = false;
|
||||||
if (opt != null && opt.length() > 0) {
|
if (opt != null && opt.length() > 0) {
|
||||||
sb.append("-").append(opt);
|
sb.append("-").append(opt);
|
||||||
havePrev = true;
|
havePrev = true;
|
||||||
}
|
}
|
||||||
if (longOpt != null && longOpt.length() > 0) {
|
if (longOpt != null && longOpt.length() > 0) {
|
||||||
if (havePrev) {
|
if (havePrev) {
|
||||||
sb.append(",");
|
sb.append(",");
|
||||||
}
|
}
|
||||||
sb.append("--").append(longOpt);
|
sb.append("--").append(longOpt);
|
||||||
}
|
}
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Retention(value = RetentionPolicy.RUNTIME)
|
@Retention(value = RetentionPolicy.RUNTIME)
|
||||||
@Target(value = { ElementType.TYPE })
|
@Target(value = { ElementType.TYPE })
|
||||||
static public @interface Syntax {
|
static public @interface Syntax {
|
||||||
|
|
||||||
String cmd();
|
String cmd();
|
||||||
|
|
||||||
String desc() default "";
|
String desc() default "";
|
||||||
|
|
||||||
String onlineHelp() default "";
|
String onlineHelp() default "";
|
||||||
|
|
||||||
String syntax() default "";
|
String syntax() default "";
|
||||||
}
|
}
|
||||||
|
|
||||||
public void doMain(String... args) {
|
public void doMain(String... args) {
|
||||||
try {
|
try {
|
||||||
initOptions();
|
initOptions();
|
||||||
parseSetArgs(args);
|
parseSetArgs(args);
|
||||||
doCommandLine();
|
doCommandLine();
|
||||||
} catch (HelpException e) {
|
} catch (HelpException e) {
|
||||||
String msg = e.getMessage();
|
String msg = e.getMessage();
|
||||||
if (msg != null && msg.length() > 0) {
|
if (msg != null && msg.length() > 0) {
|
||||||
System.err.println("ERROR: " + msg);
|
System.err.println("ERROR: " + msg);
|
||||||
}
|
}
|
||||||
usage();
|
usage();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace(System.err);
|
e.printStackTrace(System.err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract void doCommandLine() throws Exception;
|
protected abstract void doCommandLine() throws Exception;
|
||||||
|
|
||||||
protected String getVersionString() {
|
protected String getVersionString() {
|
||||||
return getClass().getPackage().getImplementationVersion();
|
return getClass().getPackage().getImplementationVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void initOptions() {
|
protected void initOptions() {
|
||||||
initOptionFromClass(this.getClass());
|
initOptionFromClass(this.getClass());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void initOptionFromClass(Class<?> clz) {
|
protected void initOptionFromClass(Class<?> clz) {
|
||||||
if (clz == null) {
|
if (clz == null) {
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
initOptionFromClass(clz.getSuperclass());
|
initOptionFromClass(clz.getSuperclass());
|
||||||
}
|
}
|
||||||
|
|
||||||
Syntax syntax = clz.getAnnotation(Syntax.class);
|
Syntax syntax = clz.getAnnotation(Syntax.class);
|
||||||
if (syntax != null) {
|
if (syntax != null) {
|
||||||
this.onlineHelp = syntax.onlineHelp();
|
this.onlineHelp = syntax.onlineHelp();
|
||||||
}
|
}
|
||||||
|
|
||||||
Field[] fs = clz.getDeclaredFields();
|
Field[] fs = clz.getDeclaredFields();
|
||||||
for (Field f : fs) {
|
for (Field f : fs) {
|
||||||
Opt opt = f.getAnnotation(Opt.class);
|
Opt opt = f.getAnnotation(Opt.class);
|
||||||
if (opt != null) {
|
if (opt != null) {
|
||||||
f.setAccessible(true);
|
f.setAccessible(true);
|
||||||
Option option = new Option();
|
Option option = new Option();
|
||||||
option.field = f;
|
option.field = f;
|
||||||
option.description = opt.description();
|
option.description = opt.description();
|
||||||
option.hasArg = opt.hasArg();
|
option.hasArg = opt.hasArg();
|
||||||
option.required = opt.required();
|
option.required = opt.required();
|
||||||
if ("".equals(opt.longOpt()) && "".equals(opt.opt())) { // into automode
|
if ("".equals(opt.longOpt()) && "".equals(opt.opt())) { // into automode
|
||||||
option.longOpt = fromCamel(f.getName());
|
option.longOpt = fromCamel(f.getName());
|
||||||
if (f.getType().equals(boolean.class)) {
|
if (f.getType().equals(boolean.class)) {
|
||||||
option.hasArg=false;
|
option.hasArg=false;
|
||||||
try {
|
try {
|
||||||
if (f.getBoolean(this)) {
|
if (f.getBoolean(this)) {
|
||||||
throw new RuntimeException("the value of " + f +
|
throw new RuntimeException("the value of " + f +
|
||||||
" must be false, as it is declared as no args");
|
" must be false, as it is declared as no args");
|
||||||
}
|
}
|
||||||
} catch (IllegalAccessException e) {
|
} catch (IllegalAccessException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
checkConflict(option, "--" + option.longOpt);
|
checkConflict(option, "--" + option.longOpt);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!opt.hasArg()) {
|
if (!opt.hasArg()) {
|
||||||
if (!f.getType().equals(boolean.class)) {
|
if (!f.getType().equals(boolean.class)) {
|
||||||
throw new RuntimeException("the type of " + f
|
throw new RuntimeException("the type of " + f
|
||||||
+ " must be boolean, as it is declared as no args");
|
+ " must be boolean, as it is declared as no args");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (f.getBoolean(this)) {
|
if (f.getBoolean(this)) {
|
||||||
throw new RuntimeException("the value of " + f +
|
throw new RuntimeException("the value of " + f +
|
||||||
" must be false, as it is declared as no args");
|
" must be false, as it is declared as no args");
|
||||||
}
|
}
|
||||||
} catch (IllegalAccessException e) {
|
} catch (IllegalAccessException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
boolean haveLongOpt = false;
|
boolean haveLongOpt = false;
|
||||||
if (!"".equals(opt.longOpt())) {
|
if (!"".equals(opt.longOpt())) {
|
||||||
option.longOpt = opt.longOpt();
|
option.longOpt = opt.longOpt();
|
||||||
checkConflict(option, "--" + option.longOpt);
|
checkConflict(option, "--" + option.longOpt);
|
||||||
haveLongOpt = true;
|
haveLongOpt = true;
|
||||||
}
|
}
|
||||||
if (!"".equals(opt.argName())) {
|
if (!"".equals(opt.argName())) {
|
||||||
option.argName = opt.argName();
|
option.argName = opt.argName();
|
||||||
}
|
}
|
||||||
if (!"".equals(opt.opt())) {
|
if (!"".equals(opt.opt())) {
|
||||||
option.opt = opt.opt();
|
option.opt = opt.opt();
|
||||||
checkConflict(option, "-" + option.opt);
|
checkConflict(option, "-" + option.opt);
|
||||||
} else {
|
} else {
|
||||||
if (!haveLongOpt) {
|
if (!haveLongOpt) {
|
||||||
throw new RuntimeException("opt or longOpt is not set in @Opt(...) " + f);
|
throw new RuntimeException("opt or longOpt is not set in @Opt(...) " + f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkConflict(Option option, String key) {
|
private void checkConflict(Option option, String key) {
|
||||||
if (optMap.containsKey(key)) {
|
if (optMap.containsKey(key)) {
|
||||||
Option preOption = optMap.get(key);
|
Option preOption = optMap.get(key);
|
||||||
throw new RuntimeException(String.format("[@Opt(...) %s] conflict with [@Opt(...) %s]",
|
throw new RuntimeException(String.format("[@Opt(...) %s] conflict with [@Opt(...) %s]",
|
||||||
preOption.field.toString(), option.field
|
preOption.field.toString(), option.field
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
optMap.put(key, option);
|
optMap.put(key, option);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String fromCamel(String name) {
|
private static String fromCamel(String name) {
|
||||||
if (name.length() == 0) {
|
if (name.length() == 0) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
char[] charArray = name.toCharArray();
|
char[] charArray = name.toCharArray();
|
||||||
sb.append(Character.toLowerCase(charArray[0]));
|
sb.append(Character.toLowerCase(charArray[0]));
|
||||||
for (int i = 1; i < charArray.length; i++) {
|
for (int i = 1; i < charArray.length; i++) {
|
||||||
char c = charArray[i];
|
char c = charArray[i];
|
||||||
if (Character.isUpperCase(c)) {
|
if (Character.isUpperCase(c)) {
|
||||||
sb.append("-").append(Character.toLowerCase(c));
|
sb.append("-").append(Character.toLowerCase(c));
|
||||||
} else {
|
} else {
|
||||||
sb.append(c);
|
sb.append(c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void parseSetArgs(String... args) throws IllegalArgumentException, IllegalAccessException {
|
protected void parseSetArgs(String... args) throws IllegalArgumentException, IllegalAccessException {
|
||||||
this.orginalArgs = args;
|
this.orginalArgs = args;
|
||||||
List<String> remainsOptions = new ArrayList<String>();
|
List<String> remainsOptions = new ArrayList<String>();
|
||||||
Set<Option> requiredOpts = collectRequriedOptions(optMap);
|
Set<Option> requiredOpts = collectRequriedOptions(optMap);
|
||||||
Option needArgOpt = null;
|
Option needArgOpt = null;
|
||||||
for (String s : args) {
|
for (String s : args) {
|
||||||
if (needArgOpt != null) {
|
if (needArgOpt != null) {
|
||||||
Field field = needArgOpt.field;
|
Field field = needArgOpt.field;
|
||||||
Class clazz = field.getType();
|
Class clazz = field.getType();
|
||||||
if (clazz.equals(List.class)) {
|
if (clazz.equals(List.class)) {
|
||||||
try {
|
try {
|
||||||
List<Object> object = ((List<Object>) field.get(this));
|
List<Object> object = ((List<Object>) field.get(this));
|
||||||
|
|
||||||
// 获取List对象的泛型类型
|
// 获取List对象的泛型类型
|
||||||
ParameterizedType listGenericType = (ParameterizedType) field.getGenericType();
|
ParameterizedType listGenericType = (ParameterizedType) field.getGenericType();
|
||||||
Type[] listActualTypeArguments = listGenericType.getActualTypeArguments();
|
Type[] listActualTypeArguments = listGenericType.getActualTypeArguments();
|
||||||
Class typeClazz = (Class) listActualTypeArguments[0];
|
Class typeClazz = (Class) listActualTypeArguments[0];
|
||||||
object.add(convert(s, typeClazz));
|
object.add(convert(s, typeClazz));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
field.set(this, convert(s, clazz));
|
field.set(this, convert(s, clazz));
|
||||||
}
|
}
|
||||||
needArgOpt = null;
|
needArgOpt = null;
|
||||||
} else if (s.startsWith("-")) {// its a short or long option
|
} else if (s.startsWith("-")) {// its a short or long option
|
||||||
Option opt = optMap.get(s);
|
Option opt = optMap.get(s);
|
||||||
requiredOpts.remove(opt);
|
requiredOpts.remove(opt);
|
||||||
if (opt == null) {
|
if (opt == null) {
|
||||||
System.err.println("ERROR: Unrecognized option: " + s);
|
System.err.println("ERROR: Unrecognized option: " + s);
|
||||||
throw new HelpException();
|
throw new HelpException();
|
||||||
} else {
|
} else {
|
||||||
if (opt.hasArg) {
|
if (opt.hasArg) {
|
||||||
needArgOpt = opt;
|
needArgOpt = opt;
|
||||||
} else {
|
} else {
|
||||||
opt.field.set(this, true);
|
opt.field.set(this, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
remainsOptions.add(s);
|
remainsOptions.add(s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (needArgOpt != null) {
|
if (needArgOpt != null) {
|
||||||
System.err.println("ERROR: Option " + needArgOpt.getOptAndLongOpt() + " need an argument value");
|
System.err.println("ERROR: Option " + needArgOpt.getOptAndLongOpt() + " need an argument value");
|
||||||
throw new HelpException();
|
throw new HelpException();
|
||||||
}
|
}
|
||||||
this.remainingArgs = remainsOptions.toArray(new String[remainsOptions.size()]);
|
this.remainingArgs = remainsOptions.toArray(new String[remainsOptions.size()]);
|
||||||
if (this.printHelp) {
|
if (this.printHelp) {
|
||||||
throw new HelpException();
|
throw new HelpException();
|
||||||
}
|
}
|
||||||
if (!requiredOpts.isEmpty()) {
|
if (!requiredOpts.isEmpty()) {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.append("ERROR: Options: ");
|
sb.append("ERROR: Options: ");
|
||||||
boolean first = true;
|
boolean first = true;
|
||||||
for (Option option : requiredOpts) {
|
for (Option option : requiredOpts) {
|
||||||
if (first) {
|
if (first) {
|
||||||
first = false;
|
first = false;
|
||||||
} else {
|
} else {
|
||||||
sb.append(" and ");
|
sb.append(" and ");
|
||||||
}
|
}
|
||||||
sb.append(option.getOptAndLongOpt());
|
sb.append(option.getOptAndLongOpt());
|
||||||
}
|
}
|
||||||
sb.append(" is required");
|
sb.append(" is required");
|
||||||
System.err.println(sb.toString());
|
System.err.println(sb.toString());
|
||||||
throw new HelpException();
|
throw new HelpException();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||||
protected Object convert(String value, Class type) {
|
protected Object convert(String value, Class type) {
|
||||||
if (type.equals(String.class)) {
|
if (type.equals(String.class)) {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
if (type.equals(int.class) || type.equals(Integer.class)) {
|
if (type.equals(int.class) || type.equals(Integer.class)) {
|
||||||
return Integer.parseInt(value);
|
return Integer.parseInt(value);
|
||||||
}
|
}
|
||||||
if (type.equals(long.class) || type.equals(Long.class)) {
|
if (type.equals(long.class) || type.equals(Long.class)) {
|
||||||
return Long.parseLong(value);
|
return Long.parseLong(value);
|
||||||
}
|
}
|
||||||
if (type.equals(float.class) || type.equals(Float.class)) {
|
if (type.equals(float.class) || type.equals(Float.class)) {
|
||||||
return Float.parseFloat(value);
|
return Float.parseFloat(value);
|
||||||
}
|
}
|
||||||
if (type.equals(double.class) || type.equals(Double.class)) {
|
if (type.equals(double.class) || type.equals(Double.class)) {
|
||||||
return Double.parseDouble(value);
|
return Double.parseDouble(value);
|
||||||
}
|
}
|
||||||
if (type.equals(boolean.class) || type.equals(Boolean.class)) {
|
if (type.equals(boolean.class) || type.equals(Boolean.class)) {
|
||||||
return Boolean.parseBoolean(value);
|
return Boolean.parseBoolean(value);
|
||||||
}
|
}
|
||||||
if (type.equals(File.class)) {
|
if (type.equals(File.class)) {
|
||||||
return new File(value);
|
return new File(value);
|
||||||
}
|
}
|
||||||
if (type.equals(Path.class)) {
|
if (type.equals(Path.class)) {
|
||||||
return new File(value).toPath();
|
return new File(value).toPath();
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
type.asSubclass(Enum.class);
|
type.asSubclass(Enum.class);
|
||||||
return Enum.valueOf(type, value);
|
return Enum.valueOf(type, value);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new RuntimeException("can't convert [" + value + "] to type " + type);
|
throw new RuntimeException("can't convert [" + value + "] to type " + type);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Set<Option> collectRequriedOptions(Map<String, Option> optMap) {
|
private Set<Option> collectRequriedOptions(Map<String, Option> optMap) {
|
||||||
Set<Option> options = new HashSet<Option>();
|
Set<Option> options = new HashSet<Option>();
|
||||||
for (Map.Entry<String, Option> e : optMap.entrySet()) {
|
for (Map.Entry<String, Option> e : optMap.entrySet()) {
|
||||||
Option option = e.getValue();
|
Option option = e.getValue();
|
||||||
if (option.required) {
|
if (option.required) {
|
||||||
options.add(option);
|
options.add(option);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("serial")
|
@SuppressWarnings("serial")
|
||||||
protected static class HelpException extends RuntimeException {
|
protected static class HelpException extends RuntimeException {
|
||||||
|
|
||||||
public HelpException() {
|
public HelpException() {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
public HelpException(String message) {
|
public HelpException(String message) {
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void usage() {
|
protected void usage() {
|
||||||
PrintWriter out = new PrintWriter(new OutputStreamWriter(System.err, StandardCharsets.UTF_8), true);
|
PrintWriter out = new PrintWriter(new OutputStreamWriter(System.err, StandardCharsets.UTF_8), true);
|
||||||
|
|
||||||
final int maxLength = 80;
|
final int maxLength = 80;
|
||||||
final int maxPaLength = 40;
|
final int maxPaLength = 40;
|
||||||
// out.println(this.cmdName + " -- " + desc);
|
// out.println(this.cmdName + " -- " + desc);
|
||||||
// out.println("usage: " + this.cmdName + " " + cmdLineSyntax);
|
// out.println("usage: " + this.cmdName + " " + cmdLineSyntax);
|
||||||
if (this.optMap.size() > 0) {
|
if (this.optMap.size() > 0) {
|
||||||
out.println("options:");
|
out.println("options:");
|
||||||
}
|
}
|
||||||
// [PART.A.........][Part.B
|
// [PART.A.........][Part.B
|
||||||
// .-a,--aa.<arg>...desc1
|
// .-a,--aa.<arg>...desc1
|
||||||
// .................desc2
|
// .................desc2
|
||||||
// .-b,--bb
|
// .-b,--bb
|
||||||
TreeSet<Option> options = new TreeSet<Option>(this.optMap.values());
|
TreeSet<Option> options = new TreeSet<Option>(this.optMap.values());
|
||||||
int palength = -1;
|
int palength = -1;
|
||||||
for (Option option : options) {
|
for (Option option : options) {
|
||||||
int pa = 4 + option.getOptAndLongOpt().length();
|
int pa = 4 + option.getOptAndLongOpt().length();
|
||||||
if (option.hasArg) {
|
if (option.hasArg) {
|
||||||
pa += 3 + option.argName.length();
|
pa += 3 + option.argName.length();
|
||||||
}
|
}
|
||||||
if (pa < maxPaLength) {
|
if (pa < maxPaLength) {
|
||||||
if (pa > palength) {
|
if (pa > palength) {
|
||||||
palength = pa;
|
palength = pa;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
int pblength = maxLength - palength;
|
int pblength = maxLength - palength;
|
||||||
|
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
for (Option option : options) {
|
for (Option option : options) {
|
||||||
sb.setLength(0);
|
sb.setLength(0);
|
||||||
sb.append(" ").append(option.getOptAndLongOpt());
|
sb.append(" ").append(option.getOptAndLongOpt());
|
||||||
if (option.hasArg) {
|
if (option.hasArg) {
|
||||||
sb.append(" <").append(option.argName).append(">");
|
sb.append(" <").append(option.argName).append(">");
|
||||||
}
|
}
|
||||||
String desc = option.description;
|
String desc = option.description;
|
||||||
if (desc == null || desc.length() == 0) {// no description
|
if (desc == null || desc.length() == 0) {// no description
|
||||||
out.println(sb);
|
out.println(sb);
|
||||||
} else {
|
} else {
|
||||||
for (int i = palength - sb.length(); i > 0; i--) {
|
for (int i = palength - sb.length(); i > 0; i--) {
|
||||||
sb.append(' ');
|
sb.append(' ');
|
||||||
}
|
}
|
||||||
if (sb.length() > maxPaLength) {// to huge part A
|
if (sb.length() > maxPaLength) {// to huge part A
|
||||||
out.println(sb);
|
out.println(sb);
|
||||||
sb.setLength(0);
|
sb.setLength(0);
|
||||||
for (int i = 0; i < palength; i++) {
|
for (int i = 0; i < palength; i++) {
|
||||||
sb.append(' ');
|
sb.append(' ');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
int nextStart = 0;
|
int nextStart = 0;
|
||||||
while (nextStart < desc.length()) {
|
while (nextStart < desc.length()) {
|
||||||
if (desc.length() - nextStart < pblength) {// can put in one line
|
if (desc.length() - nextStart < pblength) {// can put in one line
|
||||||
sb.append(desc.substring(nextStart));
|
sb.append(desc.substring(nextStart));
|
||||||
out.println(sb);
|
out.println(sb);
|
||||||
nextStart = desc.length();
|
nextStart = desc.length();
|
||||||
sb.setLength(0);
|
sb.setLength(0);
|
||||||
} else {
|
} else {
|
||||||
sb.append(desc.substring(nextStart, nextStart + pblength));
|
sb.append(desc.substring(nextStart, nextStart + pblength));
|
||||||
out.println(sb);
|
out.println(sb);
|
||||||
nextStart += pblength;
|
nextStart += pblength;
|
||||||
sb.setLength(0);
|
sb.setLength(0);
|
||||||
if (nextStart < desc.length()) {
|
if (nextStart < desc.length()) {
|
||||||
for (int i = 0; i < palength; i++) {
|
for (int i = 0; i < palength; i++) {
|
||||||
sb.append(' ');
|
sb.append(' ');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (sb.length() > 0) {
|
if (sb.length() > 0) {
|
||||||
out.println(sb);
|
out.println(sb);
|
||||||
sb.setLength(0);
|
sb.setLength(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
String ver = getVersionString();
|
String ver = getVersionString();
|
||||||
if (ver != null && !"".equals(ver)) {
|
if (ver != null && !"".equals(ver)) {
|
||||||
out.println("version: " + ver);
|
out.println("version: " + ver);
|
||||||
}
|
}
|
||||||
if (onlineHelp != null && !"".equals(onlineHelp)) {
|
if (onlineHelp != null && !"".equals(onlineHelp)) {
|
||||||
if (onlineHelp.length() + "online help: ".length() > maxLength) {
|
if (onlineHelp.length() + "online help: ".length() > maxLength) {
|
||||||
out.println("online help: ");
|
out.println("online help: ");
|
||||||
out.println(onlineHelp);
|
out.println(onlineHelp);
|
||||||
} else {
|
} else {
|
||||||
out.println("online help: " + onlineHelp);
|
out.println("online help: " + onlineHelp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
out.flush();
|
out.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getBaseName(String fn) {
|
public static String getBaseName(String fn) {
|
||||||
int x = fn.lastIndexOf('.');
|
int x = fn.lastIndexOf('.');
|
||||||
return x >= 0 ? fn.substring(0, x) : fn;
|
return x >= 0 ? fn.substring(0, x) : fn;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取文件不包含后缀的名称
|
// 获取文件不包含后缀的名称
|
||||||
public static String getBaseName(File fn) {
|
public static String getBaseName(File fn) {
|
||||||
return getBaseName(fn.getName());
|
return getBaseName(fn.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -125,7 +125,7 @@ public class FileUtils {
|
||||||
copyFile(new File(sourcePath), new File(targetPath));
|
copyFile(new File(sourcePath), new File(targetPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void copyFile(File source, File target) {
|
public static boolean copyFile(File source, File target) {
|
||||||
|
|
||||||
FileInputStream inputStream = null;
|
FileInputStream inputStream = null;
|
||||||
FileOutputStream outputStream = null;
|
FileOutputStream outputStream = null;
|
||||||
|
|
@ -146,12 +146,14 @@ public class FileUtils {
|
||||||
buffer.position(0);
|
buffer.position(0);
|
||||||
oChannel.write(buffer);
|
oChannel.write(buffer);
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
} finally {
|
} finally {
|
||||||
close(inputStream);
|
close(inputStream);
|
||||||
close(outputStream);
|
close(outputStream);
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void deleteDir(File file) {
|
public static void deleteDir(File file) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue