Fix variant detection
This commit is contained in:
parent
de040b9cda
commit
aab70be0ca
|
|
@ -61,6 +61,11 @@ namespace edxp {
|
|||
return env->NewStringUTF(result.c_str());
|
||||
}
|
||||
|
||||
static jstring ConfigManager_getMiscPath(JNI_START) {
|
||||
auto result = ConfigManager::GetInstance()->GetMiscPath();
|
||||
return env->NewStringUTF(result.c_str());
|
||||
}
|
||||
|
||||
static jstring ConfigManager_getModulesList(JNI_START) {
|
||||
auto module_list = Context::GetInstance()->GetAppModulesList();
|
||||
std::ostringstream join;
|
||||
|
|
@ -85,6 +90,7 @@ namespace edxp {
|
|||
NATIVE_METHOD(ConfigManager, getCachePath,
|
||||
"(Ljava/lang/String;)Ljava/lang/String;"),
|
||||
NATIVE_METHOD(ConfigManager, getBaseConfigPath,"()Ljava/lang/String;"),
|
||||
NATIVE_METHOD(ConfigManager, getMiscPath,"()Ljava/lang/String;"),
|
||||
NATIVE_METHOD(ConfigManager, getModulesList, "()Ljava/lang/String;"),
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -47,6 +47,8 @@ public class ConfigManager {
|
|||
|
||||
public static native String getBaseConfigPath();
|
||||
|
||||
public static native String getMiscPath();
|
||||
|
||||
public static native String getDataPathPrefix();
|
||||
|
||||
public static native String getModulesList();
|
||||
|
|
|
|||
|
|
@ -70,13 +70,13 @@ public class Main implements KeepAll {
|
|||
}
|
||||
|
||||
private static void loadEdxpImpls() {
|
||||
String file_name = ConfigManager.getConfigPath("variant");
|
||||
String file_name = ConfigManager.getMiscPath() + "/variant";
|
||||
int variant = EdxpImpl.NONE;
|
||||
try {
|
||||
String f = new String(Files.readAllBytes(Paths.get(file_name)));
|
||||
String f = new String(Files.readAllBytes(Paths.get(file_name))).trim();
|
||||
variant = Integer.parseInt(f);
|
||||
} catch (Exception ignored) {
|
||||
|
||||
} catch (Exception e) {
|
||||
Utils.logE("loadEdxpImpls: ", e);
|
||||
}
|
||||
|
||||
Utils.logD("Loading variant " + variant);
|
||||
|
|
@ -90,7 +90,7 @@ public class Main implements KeepAll {
|
|||
Class.forName("com.elderdrivers.riru.edxp.sandhook.core.SandHookEdxpImpl");
|
||||
break;
|
||||
default:
|
||||
Utils.logE("Unsupported variant" + variant);
|
||||
Utils.logE("Unsupported variant " + variant);
|
||||
|
||||
}
|
||||
} catch (ClassNotFoundException e) {
|
||||
|
|
|
|||
|
|
@ -27,6 +27,21 @@ import de.robv.android.xposed.XposedHelpers;
|
|||
public class XposedInstallerHooker {
|
||||
|
||||
public static void hookXposedInstaller(final ClassLoader classLoader) {
|
||||
String variant_ = "None";
|
||||
switch (Main.getEdxpVariant()) {
|
||||
case EdxpImpl.YAHFA:
|
||||
variant_ = "YAHFA";
|
||||
break;
|
||||
case EdxpImpl.SANDHOOK:
|
||||
variant_ = "SandHook";
|
||||
break;
|
||||
case EdxpImpl.NONE:
|
||||
default:
|
||||
variant_ = "Unknown";
|
||||
break;
|
||||
}
|
||||
final String variant = variant_;
|
||||
|
||||
// EdXposed Manager R
|
||||
try {
|
||||
XposedHelpers.findAndHookMethod("org.meowcat.edxposed.manager.Constants", classLoader, "getXposedApiVersion", new XC_MethodReplacement() {
|
||||
|
|
@ -56,17 +71,6 @@ public class XposedInstallerHooker {
|
|||
XposedHelpers.findAndHookMethod("org.meowcat.edxposed.manager.Constants", classLoader, "getXposedVariant", new XC_MethodReplacement() {
|
||||
@Override
|
||||
protected Object replaceHookedMethod(MethodHookParam param) {
|
||||
String variant = "None";
|
||||
switch (Main.getEdxpVariant()) {
|
||||
case EdxpImpl.NONE:
|
||||
break;
|
||||
case EdxpImpl.YAHFA:
|
||||
variant = "YAHFA";
|
||||
break;
|
||||
case EdxpImpl.SANDHOOK:
|
||||
variant = "SandHook";
|
||||
break;
|
||||
}
|
||||
return variant;
|
||||
}
|
||||
});
|
||||
|
|
@ -112,16 +116,6 @@ public class XposedInstallerHooker {
|
|||
stringBuilder.append(BuildConfig.VERSION_NAME);
|
||||
stringBuilder.append(" (");
|
||||
String variant = "None";
|
||||
switch (Main.getEdxpVariant()) {
|
||||
case EdxpImpl.NONE:
|
||||
break;
|
||||
case EdxpImpl.YAHFA:
|
||||
variant = "YAHFA";
|
||||
break;
|
||||
case EdxpImpl.SANDHOOK:
|
||||
variant = "SandHook";
|
||||
break;
|
||||
}
|
||||
stringBuilder.append(variant);
|
||||
stringBuilder.append(")");
|
||||
try (ByteArrayInputStream is = new ByteArrayInputStream(stringBuilder.toString().getBytes())) {
|
||||
|
|
|
|||
|
|
@ -158,16 +158,12 @@ if [ "$ARCH" = "x86" ] || [ "$ARCH" = "x64" ]; then
|
|||
else
|
||||
ui_print "- ${LANG_CUST_INST_EXT_LIB_ARM}"
|
||||
extract "$ZIPFILE" 'system/lib/libriru_edxp.so' "${MODPATH}"
|
||||
if [[ "${VARIANT}" == "SandHook" ]]; then
|
||||
extract "$ZIPFILE" 'system/lib/libsandhook.edxp.so' "${MODPATH}"
|
||||
fi
|
||||
extract "$ZIPFILE" 'system/lib/libsandhook.edxp.so' "${MODPATH}"
|
||||
|
||||
if [ "$IS64BIT" = true ]; then
|
||||
ui_print "- ${LANG_CUST_INST_EXT_LIB_ARM64}"
|
||||
extract "$ZIPFILE" 'system/lib64/libriru_edxp.so' "${MODPATH}"
|
||||
if [[ "${VARIANT}" == "SandHook" ]]; then
|
||||
extract "$ZIPFILE" 'system/lib64/libsandhook.edxp.so' "${MODPATH}"
|
||||
fi
|
||||
extract "$ZIPFILE" 'system/lib64/libsandhook.edxp.so' "${MODPATH}"
|
||||
fi
|
||||
fi
|
||||
|
||||
|
|
@ -189,9 +185,11 @@ fi
|
|||
|
||||
ui_print "- ${LANG_CUST_INST_CONF_CREATE}"
|
||||
if [[ -f /data/adb/edxp/misc_path ]]; then
|
||||
# read current MISC_PATH
|
||||
MISC_PATH=$(cat /data/adb/edxp/misc_path)
|
||||
ui_print " - ${LANG_CUST_INST_CONF_OLD} $MISC_PATH"
|
||||
else
|
||||
# generate random MISC_PATH
|
||||
MISC_RAND=$(tr -cd 'A-Za-z0-9' < /dev/urandom | head -c16)
|
||||
MISC_PATH="edxp_${MISC_RAND}"
|
||||
ui_print " - ${LANG_CUST_INST_CONF_NEW} ${MISC_RAND}"
|
||||
|
|
@ -209,20 +207,22 @@ mkdir -p /data/misc/$MISC_PATH || abortC "! ${LANG_CUST_ERR_CONF_CREATE}"
|
|||
set_perm /data/misc/$MISC_PATH root root 0771 "u:object_r:magisk_file:s0" || abortC "! ${LANG_CUST_ERR_PERM}"
|
||||
echo "[[ -f /data/adb/edxp/keep_data ]] || rm -rf /data/misc/$MISC_PATH" >> "${MODPATH}/uninstall.sh" || abortC "! ${LANG_CUST_ERR_CONF_UNINST}"
|
||||
echo "[[ -f /data/adb/edxp/new_install ]] || rm -rf /data/adb/edxp" >> "${MODPATH}/uninstall.sh" || abortC "! ${LANG_CUST_ERR_CONF_UNINST}"
|
||||
# TODO: let user select variant
|
||||
echo "1" > /data/misc/$MISC_PATH/variant
|
||||
|
||||
ui_print "- ${LANG_CUST_INST_COPY_LIB}"
|
||||
|
||||
rm -rf "/data/misc/$MISC_PATH/framework"
|
||||
mv "${MODPATH}/system/framework" "/data/misc/$MISC_PATH/framework"
|
||||
|
||||
if [[ "${VARIANT}" == "SandHook" ]]; then
|
||||
mkdir -p "/data/misc/$MISC_PATH/framework/lib"
|
||||
mv "${MODPATH}/system/lib/libsandhook.edxp.so" "/data/misc/$MISC_PATH/framework/lib/libsandhook.edxp.so"
|
||||
if [ "$IS64BIT" = true ]; then
|
||||
mkdir -p "/data/misc/$MISC_PATH/framework/lib64"
|
||||
mv "${MODPATH}/system/lib64/libsandhook.edxp.so" "/data/misc/$MISC_PATH/framework/lib64/libsandhook.edxp.so"
|
||||
fi
|
||||
|
||||
mkdir -p "/data/misc/$MISC_PATH/framework/lib"
|
||||
mv "${MODPATH}/system/lib/libsandhook.edxp.so" "/data/misc/$MISC_PATH/framework/lib/libsandhook.edxp.so"
|
||||
if [ "$IS64BIT" = true ]; then
|
||||
mkdir -p "/data/misc/$MISC_PATH/framework/lib64"
|
||||
mv "${MODPATH}/system/lib64/libsandhook.edxp.so" "/data/misc/$MISC_PATH/framework/lib64/libsandhook.edxp.so"
|
||||
fi
|
||||
|
||||
set_perm_recursive /data/misc/$MISC_PATH/framework root root 0755 0644 "u:object_r:magisk_file:s0" || abortC "! ${LANG_CUST_ERR_PERM}"
|
||||
|
||||
mkdir -p /data/misc/$MISC_PATH/cache
|
||||
|
|
|
|||
|
|
@ -59,22 +59,15 @@ require_new_android() {
|
|||
}
|
||||
|
||||
edxp_check_architecture() {
|
||||
if [[ "${MODID}" == "riru_edxposed_sandhook" ]]; then
|
||||
VARIANT="SandHook"
|
||||
[[ -d "${MODPATH}/../../${MODULES_PATH}/riru_edxposed" ]] && duplicate_installation "YAHFA"
|
||||
else
|
||||
VARIANT="YAHFA"
|
||||
[[ -d "${MODPATH}/../../${MODULES_PATH}/riru_edxposed_sandhook" ]] && duplicate_installation "SandHook"
|
||||
fi
|
||||
if [[ "${ARCH}" != "arm" && "${ARCH}" != "arm64" && "${ARCH}" != "x86" && "${ARCH}" != "x64" ]]; then
|
||||
abortC "! ${LANG_UTIL_ERR_PLATFORM_UNSUPPORT}: ${ARCH}"
|
||||
else
|
||||
ui_print "- ${LANG_UTIL_PLATFORM}: ${ARCH}"
|
||||
if [[ "${ARCH}" == "x86" || "${ARCH}" == "x64" ]]; then
|
||||
if [[ "${VARIANT}" == "SandHook" ]]; then
|
||||
require_yahfa
|
||||
fi
|
||||
fi
|
||||
# if [[ "${ARCH}" == "x86" || "${ARCH}" == "x64" ]]; then
|
||||
# if [[ "${VARIANT}" == "SandHook" ]]; then
|
||||
# require_yahfa
|
||||
# fi
|
||||
# fi
|
||||
fi
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,8 +31,8 @@ extract() {
|
|||
unzip $opts "$zip" "$file" -d "$dir" >&2
|
||||
[ -f "$file_path" ] || abort_verify "$file ${LANG_VERIFY_ERR_NOT_EXIST}"
|
||||
|
||||
unzip $opts "$zip" "$file.s" -d "$TMPDIR_FOR_VERIFY" >&2
|
||||
[ -f "$hash_path" ] || abort_verify "$file.s ${LANG_VERIFY_ERR_NOT_EXIST}"
|
||||
unzip $opts "$zip" "$file.sha256" -d "$TMPDIR_FOR_VERIFY" >&2
|
||||
[ -f "$hash_path" ] || abort_verify "$file.sha256 ${LANG_VERIFY_ERR_NOT_EXIST}"
|
||||
|
||||
(echo "$(cat "$hash_path") $file_path" | sha256sum -c -s -) || abort_verify "${LANG_VERIFY_ERR_MISMATCH} $file"
|
||||
ui_print "- ${LANG_VERIFY_SUCCESS} $file" >&1
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
id=${moduleId}
|
||||
name=Riru - Lsposed
|
||||
name=Riru - LSPosed
|
||||
version=${versionName}
|
||||
versionCode=${versionCode}
|
||||
author=${authorList}
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
/build
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion androidCompileSdkVersion.toInteger()
|
||||
ndkVersion androidCompileNdkVersion
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion androidMinSdkVersion.toInteger()
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java.srcDirs += ['src/main/apacheCommonsLang']
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
buildConfigField "int", "API_CODE", "$apiCode"
|
||||
buildConfigField "boolean", "DEBUG", "false"
|
||||
}
|
||||
debug {
|
||||
buildConfigField "int", "API_CODE", "$apiCode"
|
||||
buildConfigField "boolean", "DEBUG", "true"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure that hiddenapistubs are placed before the Android SDK in xposed-bridge.iml
|
||||
// as there doesn't seem to be any way to configure this in Android Studio.
|
||||
preBuild.doLast {
|
||||
def imlFile = file(project.name + ".iml")
|
||||
try {
|
||||
def parsedXml = (new groovy.util.XmlParser()).parse(imlFile)
|
||||
def jdkNode = parsedXml.component[1].orderEntry.find { it.'@type' == 'jdk' }
|
||||
parsedXml.component[1].remove(jdkNode)
|
||||
def sdkString = "Android API " + android.compileSdkVersion.substring("android-".length()) + " Platform"
|
||||
new groovy.util.Node(parsedXml.component[1], 'orderEntry', ['type': 'jdk', 'jdkName': sdkString, 'jdkType': 'Android SDK'])
|
||||
groovy.xml.XmlUtil.serialize(parsedXml, new FileOutputStream(imlFile))
|
||||
} catch (FileNotFoundException e) {
|
||||
// nop, iml not found
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly files(project(":hiddenapi-stubs").tasks.getByName("makeStubJar").outputs)
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
options.compilerArgs.add("-Xbootclasspath/p:${hiddenApiStubJarFilePath}")
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
|
||||
-dontobfuscate
|
||||
-dontoptimize
|
||||
-keep class de.robv.android.xposed.** {*;}
|
||||
-keep class android.** { *; }
|
||||
|
|
@ -1 +0,0 @@
|
|||
<manifest package="com.elderdrivers.riru.edxp.bridge" />
|
||||
|
|
@ -1,202 +0,0 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
This is the original source code of the Apache Commons Lang library version 3.1
|
||||
as downloaded from http://commons.apache.org/lang/download_lang.cgi, except for
|
||||
these modifications:
|
||||
- Class MemberUtils changed to public
|
||||
- Method compareParameterTypes in MemberUtils changed to public
|
||||
- Prefix "external." for packages to avoid conflicts with other apps
|
||||
- Removed unused sub-packages for smaller file size:
|
||||
concurrent/
|
||||
event/
|
||||
math/
|
||||
text/
|
||||
time/
|
||||
- Removed unused classes for smaller file size:
|
||||
AnnotationUtils.java
|
||||
BitField.java
|
||||
BooleanUtils.java
|
||||
CharEncoding.java
|
||||
CharRange.java
|
||||
CharSet.java
|
||||
CharSetUtils.java
|
||||
EnumUtils.java
|
||||
LocaleUtils.java
|
||||
RandomStringUtils.java
|
||||
Range.java
|
||||
SerializationException.java
|
||||
SerializationUtils.java
|
||||
StringEscapeUtils.java
|
||||
builder/StandardToStringStyle.java
|
||||
exception/ContextedException.java
|
||||
exception/ContextedRuntimeException.java
|
||||
exception/DefaultExceptionContext.java
|
||||
exception/ExceptionContext.java
|
||||
exception/ExceptionUtils.java
|
||||
mutable/MutableBoolean.java
|
||||
mutable/MutableByte.java
|
||||
mutable/MutableDouble.java
|
||||
mutable/MutableFloat.java
|
||||
mutable/MutableLong.java
|
||||
mutable/MutableObject.java
|
||||
mutable/MutableShort.java
|
||||
reflect/ConstructorUtils.java
|
||||
reflect/FieldUtils.java
|
||||
reflect/TypeUtils.java
|
||||
tuple/MutablePair.java
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
Apache Commons Lang
|
||||
Copyright 2001-2011 The Apache Software Foundation
|
||||
|
||||
This product includes software developed by
|
||||
The Apache Software Foundation (http://www.apache.org/).
|
||||
|
||||
This product includes software from the Spring Framework,
|
||||
under the Apache License 2.0 (see: StringUtils.containsWhitespace())
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
$Id: RELEASE-NOTES.txt 1199820 2011-11-09 16:14:52Z bayard $
|
||||
|
||||
Commons Lang Package
|
||||
Version 3.1
|
||||
Release Notes
|
||||
|
||||
|
||||
INTRODUCTION:
|
||||
|
||||
This document contains the release notes for the 3.1 version of Apache Commons Lang.
|
||||
Commons Lang is a set of utility functions and reusable components that should be of use in any
|
||||
Java environment.
|
||||
|
||||
Lang 3.0 and onwards now targets Java 5.0, making use of features that arrived with Java 5.0 such as generics,
|
||||
variable arguments, autoboxing, concurrency and formatted output.
|
||||
|
||||
For the advice on upgrading from 2.x to 3.x, see the following page:
|
||||
|
||||
http://commons.apache.org/lang/article3_0.html
|
||||
|
||||
CHANGES IN 3.1
|
||||
================
|
||||
|
||||
[LANG-760] Add API StringUtils.toString(byte[] intput, String charsetName)
|
||||
[LANG-756] Add APIs ClassUtils.isPrimitiveWrapper(Class<?>) and isPrimitiveOrWrapper(Class<?>)
|
||||
[LANG-758] Add an example with whitespace in StringUtils.defaultIfEmpty
|
||||
[LANG-752] Fix createLong() so it behaves like createInteger()
|
||||
[LANG-751] Include the actual type in the Validate.isInstance and isAssignableFrom exception messages
|
||||
[LANG-748] Deprecating chomp(String, String)
|
||||
[LANG-736] CharUtils static final array CHAR_STRING is not needed to compute CHAR_STRING_ARRAY
|
||||
[LANG-695] SystemUtils.IS_OS_UNIX doesn't recognize FreeBSD as a Unix system
|
||||
|
||||
BUG FIXES IN 3.1
|
||||
==================
|
||||
|
||||
[LANG-749] Incorrect Bundle-SymbolicName in Manifest
|
||||
[LANG-746] NumberUtils does not handle upper-case hex: 0X and -0X
|
||||
[LANG-744] StringUtils throws java.security.AccessControlException on Google App Engine
|
||||
[LANG-741] Ant build has wrong component.name
|
||||
[LANG-698] Document that the Mutable numbers don't work as expected with String.format
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,197 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package external.org.apache.commons.lang3;
|
||||
|
||||
/**
|
||||
* <p>Operations on {@link java.lang.CharSequence} that are
|
||||
* {@code null} safe.</p>
|
||||
*
|
||||
* @see java.lang.CharSequence
|
||||
* @since 3.0
|
||||
* @version $Id: CharSequenceUtils.java 1199894 2011-11-09 17:53:59Z ggregory $
|
||||
*/
|
||||
public class CharSequenceUtils {
|
||||
|
||||
/**
|
||||
* <p>{@code CharSequenceUtils} instances should NOT be constructed in
|
||||
* standard programming. </p>
|
||||
*
|
||||
* <p>This constructor is public to permit tools that require a JavaBean
|
||||
* instance to operate.</p>
|
||||
*/
|
||||
public CharSequenceUtils() {
|
||||
super();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Returns a new {@code CharSequence} that is a subsequence of this
|
||||
* sequence starting with the {@code char} value at the specified index.</p>
|
||||
*
|
||||
* <p>This provides the {@code CharSequence} equivalent to {@link String#substring(int)}.
|
||||
* The length (in {@code char}) of the returned sequence is {@code length() - start},
|
||||
* so if {@code start == end} then an empty sequence is returned.</p>
|
||||
*
|
||||
* @param cs the specified subsequence, null returns null
|
||||
* @param start the start index, inclusive, valid
|
||||
* @return a new subsequence, may be null
|
||||
* @throws IndexOutOfBoundsException if {@code start} is negative or if
|
||||
* {@code start} is greater than {@code length()}
|
||||
*/
|
||||
public static CharSequence subSequence(CharSequence cs, int start) {
|
||||
return cs == null ? null : cs.subSequence(start, cs.length());
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Finds the first index in the {@code CharSequence} that matches the
|
||||
* specified character.</p>
|
||||
*
|
||||
* @param cs the {@code CharSequence} to be processed, not null
|
||||
* @param searchChar the char to be searched for
|
||||
* @param start the start index, negative starts at the string start
|
||||
* @return the index where the search char was found, -1 if not found
|
||||
*/
|
||||
static int indexOf(CharSequence cs, int searchChar, int start) {
|
||||
if (cs instanceof String) {
|
||||
return ((String) cs).indexOf(searchChar, start);
|
||||
} else {
|
||||
int sz = cs.length();
|
||||
if (start < 0) {
|
||||
start = 0;
|
||||
}
|
||||
for (int i = start; i < sz; i++) {
|
||||
if (cs.charAt(i) == searchChar) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by the indexOf(CharSequence methods) as a green implementation of indexOf.
|
||||
*
|
||||
* @param cs the {@code CharSequence} to be processed
|
||||
* @param searchChar the {@code CharSequence} to be searched for
|
||||
* @param start the start index
|
||||
* @return the index where the search sequence was found
|
||||
*/
|
||||
static int indexOf(CharSequence cs, CharSequence searchChar, int start) {
|
||||
return cs.toString().indexOf(searchChar.toString(), start);
|
||||
// if (cs instanceof String && searchChar instanceof String) {
|
||||
// // TODO: Do we assume searchChar is usually relatively small;
|
||||
// // If so then calling toString() on it is better than reverting to
|
||||
// // the green implementation in the else block
|
||||
// return ((String) cs).indexOf((String) searchChar, start);
|
||||
// } else {
|
||||
// // TODO: Implement rather than convert to String
|
||||
// return cs.toString().indexOf(searchChar.toString(), start);
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Finds the last index in the {@code CharSequence} that matches the
|
||||
* specified character.</p>
|
||||
*
|
||||
* @param cs the {@code CharSequence} to be processed
|
||||
* @param searchChar the char to be searched for
|
||||
* @param start the start index, negative returns -1, beyond length starts at end
|
||||
* @return the index where the search char was found, -1 if not found
|
||||
*/
|
||||
static int lastIndexOf(CharSequence cs, int searchChar, int start) {
|
||||
if (cs instanceof String) {
|
||||
return ((String) cs).lastIndexOf(searchChar, start);
|
||||
} else {
|
||||
int sz = cs.length();
|
||||
if (start < 0) {
|
||||
return -1;
|
||||
}
|
||||
if (start >= sz) {
|
||||
start = sz - 1;
|
||||
}
|
||||
for (int i = start; i >= 0; --i) {
|
||||
if (cs.charAt(i) == searchChar) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by the lastIndexOf(CharSequence methods) as a green implementation of lastIndexOf
|
||||
*
|
||||
* @param cs the {@code CharSequence} to be processed
|
||||
* @param searchChar the {@code CharSequence} to be searched for
|
||||
* @param start the start index
|
||||
* @return the index where the search sequence was found
|
||||
*/
|
||||
static int lastIndexOf(CharSequence cs, CharSequence searchChar, int start) {
|
||||
return cs.toString().lastIndexOf(searchChar.toString(), start);
|
||||
// if (cs instanceof String && searchChar instanceof String) {
|
||||
// // TODO: Do we assume searchChar is usually relatively small;
|
||||
// // If so then calling toString() on it is better than reverting to
|
||||
// // the green implementation in the else block
|
||||
// return ((String) cs).lastIndexOf((String) searchChar, start);
|
||||
// } else {
|
||||
// // TODO: Implement rather than convert to String
|
||||
// return cs.toString().lastIndexOf(searchChar.toString(), start);
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* Green implementation of toCharArray.
|
||||
*
|
||||
* @param cs the {@code CharSequence} to be processed
|
||||
* @return the resulting char array
|
||||
*/
|
||||
static char[] toCharArray(CharSequence cs) {
|
||||
if (cs instanceof String) {
|
||||
return ((String) cs).toCharArray();
|
||||
} else {
|
||||
int sz = cs.length();
|
||||
char[] array = new char[cs.length()];
|
||||
for (int i = 0; i < sz; i++) {
|
||||
array[i] = cs.charAt(i);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Green implementation of regionMatches.
|
||||
*
|
||||
* @param cs the {@code CharSequence} to be processed
|
||||
* @param ignoreCase whether or not to be case insensitive
|
||||
* @param thisStart the index to start on the {@code cs} CharSequence
|
||||
* @param substring the {@code CharSequence} to be looked for
|
||||
* @param start the index to start on the {@code substring} CharSequence
|
||||
* @param length character length of the region
|
||||
* @return whether the region matched
|
||||
*/
|
||||
static boolean regionMatches(CharSequence cs, boolean ignoreCase, int thisStart,
|
||||
CharSequence substring, int start, int length) {
|
||||
if (cs instanceof String && substring instanceof String) {
|
||||
return ((String) cs).regionMatches(ignoreCase, thisStart, (String) substring, start, length);
|
||||
} else {
|
||||
// TODO: Implement rather than convert to String
|
||||
return cs.toString().regionMatches(ignoreCase, thisStart, substring.toString(), start, length);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,539 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package external.org.apache.commons.lang3;
|
||||
|
||||
/**
|
||||
* <p>Operations on char primitives and Character objects.</p>
|
||||
*
|
||||
* <p>This class tries to handle {@code null} input gracefully.
|
||||
* An exception will not be thrown for a {@code null} input.
|
||||
* Each method documents its behaviour in more detail.</p>
|
||||
*
|
||||
* <p>#ThreadSafe#</p>
|
||||
* @since 2.1
|
||||
* @version $Id: CharUtils.java 1158279 2011-08-16 14:06:45Z ggregory $
|
||||
*/
|
||||
public class CharUtils {
|
||||
|
||||
private static final String[] CHAR_STRING_ARRAY = new String[128];
|
||||
|
||||
/**
|
||||
* {@code \u000a} linefeed LF ('\n').
|
||||
*
|
||||
* @see <a href="http://java.sun.com/docs/books/jls/third_edition/html/lexical.html#101089">JLF: Escape Sequences
|
||||
* for Character and String Literals</a>
|
||||
* @since 2.2
|
||||
*/
|
||||
public static final char LF = '\n';
|
||||
|
||||
/**
|
||||
* {@code \u000d} carriage return CR ('\r').
|
||||
*
|
||||
* @see <a href="http://java.sun.com/docs/books/jls/third_edition/html/lexical.html#101089">JLF: Escape Sequences
|
||||
* for Character and String Literals</a>
|
||||
* @since 2.2
|
||||
*/
|
||||
public static final char CR = '\r';
|
||||
|
||||
|
||||
static {
|
||||
for (char c = 0; c < CHAR_STRING_ARRAY.length; c++) {
|
||||
CHAR_STRING_ARRAY[c] = String.valueOf(c);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>{@code CharUtils} instances should NOT be constructed in standard programming.
|
||||
* Instead, the class should be used as {@code CharUtils.toString('c');}.</p>
|
||||
*
|
||||
* <p>This constructor is public to permit tools that require a JavaBean instance
|
||||
* to operate.</p>
|
||||
*/
|
||||
public CharUtils() {
|
||||
super();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Converts the character to a Character.</p>
|
||||
*
|
||||
* <p>For ASCII 7 bit characters, this uses a cache that will return the
|
||||
* same Character object each time.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.toCharacterObject(' ') = ' '
|
||||
* CharUtils.toCharacterObject('A') = 'A'
|
||||
* </pre>
|
||||
*
|
||||
* @deprecated Java 5 introduced {@link Character#valueOf(char)} which caches chars 0 through 127.
|
||||
* @param ch the character to convert
|
||||
* @return a Character of the specified character
|
||||
*/
|
||||
@Deprecated
|
||||
public static Character toCharacterObject(char ch) {
|
||||
return Character.valueOf(ch);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Converts the String to a Character using the first character, returning
|
||||
* null for empty Strings.</p>
|
||||
*
|
||||
* <p>For ASCII 7 bit characters, this uses a cache that will return the
|
||||
* same Character object each time.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.toCharacterObject(null) = null
|
||||
* CharUtils.toCharacterObject("") = null
|
||||
* CharUtils.toCharacterObject("A") = 'A'
|
||||
* CharUtils.toCharacterObject("BA") = 'B'
|
||||
* </pre>
|
||||
*
|
||||
* @param str the character to convert
|
||||
* @return the Character value of the first letter of the String
|
||||
*/
|
||||
public static Character toCharacterObject(String str) {
|
||||
if (StringUtils.isEmpty(str)) {
|
||||
return null;
|
||||
}
|
||||
return Character.valueOf(str.charAt(0));
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Converts the Character to a char throwing an exception for {@code null}.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.toChar(' ') = ' '
|
||||
* CharUtils.toChar('A') = 'A'
|
||||
* CharUtils.toChar(null) throws IllegalArgumentException
|
||||
* </pre>
|
||||
*
|
||||
* @param ch the character to convert
|
||||
* @return the char value of the Character
|
||||
* @throws IllegalArgumentException if the Character is null
|
||||
*/
|
||||
public static char toChar(Character ch) {
|
||||
if (ch == null) {
|
||||
throw new IllegalArgumentException("The Character must not be null");
|
||||
}
|
||||
return ch.charValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Converts the Character to a char handling {@code null}.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.toChar(null, 'X') = 'X'
|
||||
* CharUtils.toChar(' ', 'X') = ' '
|
||||
* CharUtils.toChar('A', 'X') = 'A'
|
||||
* </pre>
|
||||
*
|
||||
* @param ch the character to convert
|
||||
* @param defaultValue the value to use if the Character is null
|
||||
* @return the char value of the Character or the default if null
|
||||
*/
|
||||
public static char toChar(Character ch, char defaultValue) {
|
||||
if (ch == null) {
|
||||
return defaultValue;
|
||||
}
|
||||
return ch.charValue();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Converts the String to a char using the first character, throwing
|
||||
* an exception on empty Strings.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.toChar("A") = 'A'
|
||||
* CharUtils.toChar("BA") = 'B'
|
||||
* CharUtils.toChar(null) throws IllegalArgumentException
|
||||
* CharUtils.toChar("") throws IllegalArgumentException
|
||||
* </pre>
|
||||
*
|
||||
* @param str the character to convert
|
||||
* @return the char value of the first letter of the String
|
||||
* @throws IllegalArgumentException if the String is empty
|
||||
*/
|
||||
public static char toChar(String str) {
|
||||
if (StringUtils.isEmpty(str)) {
|
||||
throw new IllegalArgumentException("The String must not be empty");
|
||||
}
|
||||
return str.charAt(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Converts the String to a char using the first character, defaulting
|
||||
* the value on empty Strings.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.toChar(null, 'X') = 'X'
|
||||
* CharUtils.toChar("", 'X') = 'X'
|
||||
* CharUtils.toChar("A", 'X') = 'A'
|
||||
* CharUtils.toChar("BA", 'X') = 'B'
|
||||
* </pre>
|
||||
*
|
||||
* @param str the character to convert
|
||||
* @param defaultValue the value to use if the Character is null
|
||||
* @return the char value of the first letter of the String or the default if null
|
||||
*/
|
||||
public static char toChar(String str, char defaultValue) {
|
||||
if (StringUtils.isEmpty(str)) {
|
||||
return defaultValue;
|
||||
}
|
||||
return str.charAt(0);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Converts the character to the Integer it represents, throwing an
|
||||
* exception if the character is not numeric.</p>
|
||||
*
|
||||
* <p>This method coverts the char '1' to the int 1 and so on.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.toIntValue('3') = 3
|
||||
* CharUtils.toIntValue('A') throws IllegalArgumentException
|
||||
* </pre>
|
||||
*
|
||||
* @param ch the character to convert
|
||||
* @return the int value of the character
|
||||
* @throws IllegalArgumentException if the character is not ASCII numeric
|
||||
*/
|
||||
public static int toIntValue(char ch) {
|
||||
if (isAsciiNumeric(ch) == false) {
|
||||
throw new IllegalArgumentException("The character " + ch + " is not in the range '0' - '9'");
|
||||
}
|
||||
return ch - 48;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Converts the character to the Integer it represents, throwing an
|
||||
* exception if the character is not numeric.</p>
|
||||
*
|
||||
* <p>This method coverts the char '1' to the int 1 and so on.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.toIntValue('3', -1) = 3
|
||||
* CharUtils.toIntValue('A', -1) = -1
|
||||
* </pre>
|
||||
*
|
||||
* @param ch the character to convert
|
||||
* @param defaultValue the default value to use if the character is not numeric
|
||||
* @return the int value of the character
|
||||
*/
|
||||
public static int toIntValue(char ch, int defaultValue) {
|
||||
if (isAsciiNumeric(ch) == false) {
|
||||
return defaultValue;
|
||||
}
|
||||
return ch - 48;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Converts the character to the Integer it represents, throwing an
|
||||
* exception if the character is not numeric.</p>
|
||||
*
|
||||
* <p>This method coverts the char '1' to the int 1 and so on.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.toIntValue('3') = 3
|
||||
* CharUtils.toIntValue(null) throws IllegalArgumentException
|
||||
* CharUtils.toIntValue('A') throws IllegalArgumentException
|
||||
* </pre>
|
||||
*
|
||||
* @param ch the character to convert, not null
|
||||
* @return the int value of the character
|
||||
* @throws IllegalArgumentException if the Character is not ASCII numeric or is null
|
||||
*/
|
||||
public static int toIntValue(Character ch) {
|
||||
if (ch == null) {
|
||||
throw new IllegalArgumentException("The character must not be null");
|
||||
}
|
||||
return toIntValue(ch.charValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Converts the character to the Integer it represents, throwing an
|
||||
* exception if the character is not numeric.</p>
|
||||
*
|
||||
* <p>This method coverts the char '1' to the int 1 and so on.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.toIntValue(null, -1) = -1
|
||||
* CharUtils.toIntValue('3', -1) = 3
|
||||
* CharUtils.toIntValue('A', -1) = -1
|
||||
* </pre>
|
||||
*
|
||||
* @param ch the character to convert
|
||||
* @param defaultValue the default value to use if the character is not numeric
|
||||
* @return the int value of the character
|
||||
*/
|
||||
public static int toIntValue(Character ch, int defaultValue) {
|
||||
if (ch == null) {
|
||||
return defaultValue;
|
||||
}
|
||||
return toIntValue(ch.charValue(), defaultValue);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Converts the character to a String that contains the one character.</p>
|
||||
*
|
||||
* <p>For ASCII 7 bit characters, this uses a cache that will return the
|
||||
* same String object each time.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.toString(' ') = " "
|
||||
* CharUtils.toString('A') = "A"
|
||||
* </pre>
|
||||
*
|
||||
* @param ch the character to convert
|
||||
* @return a String containing the one specified character
|
||||
*/
|
||||
public static String toString(char ch) {
|
||||
if (ch < 128) {
|
||||
return CHAR_STRING_ARRAY[ch];
|
||||
}
|
||||
return new String(new char[] {ch});
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Converts the character to a String that contains the one character.</p>
|
||||
*
|
||||
* <p>For ASCII 7 bit characters, this uses a cache that will return the
|
||||
* same String object each time.</p>
|
||||
*
|
||||
* <p>If {@code null} is passed in, {@code null} will be returned.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.toString(null) = null
|
||||
* CharUtils.toString(' ') = " "
|
||||
* CharUtils.toString('A') = "A"
|
||||
* </pre>
|
||||
*
|
||||
* @param ch the character to convert
|
||||
* @return a String containing the one specified character
|
||||
*/
|
||||
public static String toString(Character ch) {
|
||||
if (ch == null) {
|
||||
return null;
|
||||
}
|
||||
return toString(ch.charValue());
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Converts the string to the Unicode format '\u0020'.</p>
|
||||
*
|
||||
* <p>This format is the Java source code format.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.unicodeEscaped(' ') = "\u0020"
|
||||
* CharUtils.unicodeEscaped('A') = "\u0041"
|
||||
* </pre>
|
||||
*
|
||||
* @param ch the character to convert
|
||||
* @return the escaped Unicode string
|
||||
*/
|
||||
public static String unicodeEscaped(char ch) {
|
||||
if (ch < 0x10) {
|
||||
return "\\u000" + Integer.toHexString(ch);
|
||||
} else if (ch < 0x100) {
|
||||
return "\\u00" + Integer.toHexString(ch);
|
||||
} else if (ch < 0x1000) {
|
||||
return "\\u0" + Integer.toHexString(ch);
|
||||
}
|
||||
return "\\u" + Integer.toHexString(ch);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Converts the string to the Unicode format '\u0020'.</p>
|
||||
*
|
||||
* <p>This format is the Java source code format.</p>
|
||||
*
|
||||
* <p>If {@code null} is passed in, {@code null} will be returned.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.unicodeEscaped(null) = null
|
||||
* CharUtils.unicodeEscaped(' ') = "\u0020"
|
||||
* CharUtils.unicodeEscaped('A') = "\u0041"
|
||||
* </pre>
|
||||
*
|
||||
* @param ch the character to convert, may be null
|
||||
* @return the escaped Unicode string, null if null input
|
||||
*/
|
||||
public static String unicodeEscaped(Character ch) {
|
||||
if (ch == null) {
|
||||
return null;
|
||||
}
|
||||
return unicodeEscaped(ch.charValue());
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Checks whether the character is ASCII 7 bit.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.isAscii('a') = true
|
||||
* CharUtils.isAscii('A') = true
|
||||
* CharUtils.isAscii('3') = true
|
||||
* CharUtils.isAscii('-') = true
|
||||
* CharUtils.isAscii('\n') = true
|
||||
* CharUtils.isAscii('©') = false
|
||||
* </pre>
|
||||
*
|
||||
* @param ch the character to check
|
||||
* @return true if less than 128
|
||||
*/
|
||||
public static boolean isAscii(char ch) {
|
||||
return ch < 128;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Checks whether the character is ASCII 7 bit printable.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.isAsciiPrintable('a') = true
|
||||
* CharUtils.isAsciiPrintable('A') = true
|
||||
* CharUtils.isAsciiPrintable('3') = true
|
||||
* CharUtils.isAsciiPrintable('-') = true
|
||||
* CharUtils.isAsciiPrintable('\n') = false
|
||||
* CharUtils.isAsciiPrintable('©') = false
|
||||
* </pre>
|
||||
*
|
||||
* @param ch the character to check
|
||||
* @return true if between 32 and 126 inclusive
|
||||
*/
|
||||
public static boolean isAsciiPrintable(char ch) {
|
||||
return ch >= 32 && ch < 127;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Checks whether the character is ASCII 7 bit control.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.isAsciiControl('a') = false
|
||||
* CharUtils.isAsciiControl('A') = false
|
||||
* CharUtils.isAsciiControl('3') = false
|
||||
* CharUtils.isAsciiControl('-') = false
|
||||
* CharUtils.isAsciiControl('\n') = true
|
||||
* CharUtils.isAsciiControl('©') = false
|
||||
* </pre>
|
||||
*
|
||||
* @param ch the character to check
|
||||
* @return true if less than 32 or equals 127
|
||||
*/
|
||||
public static boolean isAsciiControl(char ch) {
|
||||
return ch < 32 || ch == 127;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Checks whether the character is ASCII 7 bit alphabetic.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.isAsciiAlpha('a') = true
|
||||
* CharUtils.isAsciiAlpha('A') = true
|
||||
* CharUtils.isAsciiAlpha('3') = false
|
||||
* CharUtils.isAsciiAlpha('-') = false
|
||||
* CharUtils.isAsciiAlpha('\n') = false
|
||||
* CharUtils.isAsciiAlpha('©') = false
|
||||
* </pre>
|
||||
*
|
||||
* @param ch the character to check
|
||||
* @return true if between 65 and 90 or 97 and 122 inclusive
|
||||
*/
|
||||
public static boolean isAsciiAlpha(char ch) {
|
||||
return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z');
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Checks whether the character is ASCII 7 bit alphabetic upper case.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.isAsciiAlphaUpper('a') = false
|
||||
* CharUtils.isAsciiAlphaUpper('A') = true
|
||||
* CharUtils.isAsciiAlphaUpper('3') = false
|
||||
* CharUtils.isAsciiAlphaUpper('-') = false
|
||||
* CharUtils.isAsciiAlphaUpper('\n') = false
|
||||
* CharUtils.isAsciiAlphaUpper('©') = false
|
||||
* </pre>
|
||||
*
|
||||
* @param ch the character to check
|
||||
* @return true if between 65 and 90 inclusive
|
||||
*/
|
||||
public static boolean isAsciiAlphaUpper(char ch) {
|
||||
return ch >= 'A' && ch <= 'Z';
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Checks whether the character is ASCII 7 bit alphabetic lower case.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.isAsciiAlphaLower('a') = true
|
||||
* CharUtils.isAsciiAlphaLower('A') = false
|
||||
* CharUtils.isAsciiAlphaLower('3') = false
|
||||
* CharUtils.isAsciiAlphaLower('-') = false
|
||||
* CharUtils.isAsciiAlphaLower('\n') = false
|
||||
* CharUtils.isAsciiAlphaLower('©') = false
|
||||
* </pre>
|
||||
*
|
||||
* @param ch the character to check
|
||||
* @return true if between 97 and 122 inclusive
|
||||
*/
|
||||
public static boolean isAsciiAlphaLower(char ch) {
|
||||
return ch >= 'a' && ch <= 'z';
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Checks whether the character is ASCII 7 bit numeric.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.isAsciiNumeric('a') = false
|
||||
* CharUtils.isAsciiNumeric('A') = false
|
||||
* CharUtils.isAsciiNumeric('3') = true
|
||||
* CharUtils.isAsciiNumeric('-') = false
|
||||
* CharUtils.isAsciiNumeric('\n') = false
|
||||
* CharUtils.isAsciiNumeric('©') = false
|
||||
* </pre>
|
||||
*
|
||||
* @param ch the character to check
|
||||
* @return true if between 48 and 57 inclusive
|
||||
*/
|
||||
public static boolean isAsciiNumeric(char ch) {
|
||||
return ch >= '0' && ch <= '9';
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Checks whether the character is ASCII 7 bit numeric.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.isAsciiAlphanumeric('a') = true
|
||||
* CharUtils.isAsciiAlphanumeric('A') = true
|
||||
* CharUtils.isAsciiAlphanumeric('3') = true
|
||||
* CharUtils.isAsciiAlphanumeric('-') = false
|
||||
* CharUtils.isAsciiAlphanumeric('\n') = false
|
||||
* CharUtils.isAsciiAlphanumeric('©') = false
|
||||
* </pre>
|
||||
*
|
||||
* @param ch the character to check
|
||||
* @return true if between 48 and 57 or 65 and 90 or 97 and 122 inclusive
|
||||
*/
|
||||
public static boolean isAsciiAlphanumeric(char ch) {
|
||||
return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9');
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,168 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package external.org.apache.commons.lang3;
|
||||
|
||||
/**
|
||||
* <p>An enum representing all the versions of the Java specification.
|
||||
* This is intended to mirror available values from the
|
||||
* <em>java.specification.version</em> System property. </p>
|
||||
*
|
||||
* @since 3.0
|
||||
* @version $Id: $
|
||||
*/
|
||||
public enum JavaVersion {
|
||||
|
||||
/**
|
||||
* The Java version reported by Android. This is not an official Java version number.
|
||||
*/
|
||||
JAVA_0_9(1.5f, "0.9"),
|
||||
|
||||
/**
|
||||
* Java 1.1.
|
||||
*/
|
||||
JAVA_1_1(1.1f, "1.1"),
|
||||
|
||||
/**
|
||||
* Java 1.2.
|
||||
*/
|
||||
JAVA_1_2(1.2f, "1.2"),
|
||||
|
||||
/**
|
||||
* Java 1.3.
|
||||
*/
|
||||
JAVA_1_3(1.3f, "1.3"),
|
||||
|
||||
/**
|
||||
* Java 1.4.
|
||||
*/
|
||||
JAVA_1_4(1.4f, "1.4"),
|
||||
|
||||
/**
|
||||
* Java 1.5.
|
||||
*/
|
||||
JAVA_1_5(1.5f, "1.5"),
|
||||
|
||||
/**
|
||||
* Java 1.6.
|
||||
*/
|
||||
JAVA_1_6(1.6f, "1.6"),
|
||||
|
||||
/**
|
||||
* Java 1.7.
|
||||
*/
|
||||
JAVA_1_7(1.7f, "1.7"),
|
||||
|
||||
/**
|
||||
* Java 1.8.
|
||||
*/
|
||||
JAVA_1_8(1.8f, "1.8");
|
||||
|
||||
/**
|
||||
* The float value.
|
||||
*/
|
||||
private float value;
|
||||
/**
|
||||
* The standard name.
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param value the float value
|
||||
* @param name the standard name, not null
|
||||
*/
|
||||
JavaVersion(final float value, final String name) {
|
||||
this.value = value;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Whether this version of Java is at least the version of Java passed in.</p>
|
||||
*
|
||||
* <p>For example:<br />
|
||||
* {@code myVersion.atLeast(JavaVersion.JAVA_1_4)}<p>
|
||||
*
|
||||
* @param requiredVersion the version to check against, not null
|
||||
* @return true if this version is equal to or greater than the specified version
|
||||
*/
|
||||
public boolean atLeast(JavaVersion requiredVersion) {
|
||||
return this.value >= requiredVersion.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms the given string with a Java version number to the
|
||||
* corresponding constant of this enumeration class. This method is used
|
||||
* internally.
|
||||
*
|
||||
* @param nom the Java version as string
|
||||
* @return the corresponding enumeration constant or <b>null</b> if the
|
||||
* version is unknown
|
||||
*/
|
||||
// helper for static importing
|
||||
static JavaVersion getJavaVersion(final String nom) {
|
||||
return get(nom);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms the given string with a Java version number to the
|
||||
* corresponding constant of this enumeration class. This method is used
|
||||
* internally.
|
||||
*
|
||||
* @param nom the Java version as string
|
||||
* @return the corresponding enumeration constant or <b>null</b> if the
|
||||
* version is unknown
|
||||
*/
|
||||
static JavaVersion get(final String nom) {
|
||||
if ("0.9".equals(nom)) {
|
||||
return JAVA_0_9;
|
||||
} else if ("1.1".equals(nom)) {
|
||||
return JAVA_1_1;
|
||||
} else if ("1.2".equals(nom)) {
|
||||
return JAVA_1_2;
|
||||
} else if ("1.3".equals(nom)) {
|
||||
return JAVA_1_3;
|
||||
} else if ("1.4".equals(nom)) {
|
||||
return JAVA_1_4;
|
||||
} else if ("1.5".equals(nom)) {
|
||||
return JAVA_1_5;
|
||||
} else if ("1.6".equals(nom)) {
|
||||
return JAVA_1_6;
|
||||
} else if ("1.7".equals(nom)) {
|
||||
return JAVA_1_7;
|
||||
} else if ("1.8".equals(nom)) {
|
||||
return JAVA_1_8;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>The string value is overridden to return the standard name.</p>
|
||||
*
|
||||
* <p>For example, <code>"1.5"</code>.</p>
|
||||
*
|
||||
* @return the name, not null
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,609 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package external.org.apache.commons.lang3;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.TreeSet;
|
||||
|
||||
|
||||
import external.org.apache.commons.lang3.exception.CloneFailedException;
|
||||
import external.org.apache.commons.lang3.mutable.MutableInt;
|
||||
|
||||
/**
|
||||
* <p>Operations on {@code Object}.</p>
|
||||
*
|
||||
* <p>This class tries to handle {@code null} input gracefully.
|
||||
* An exception will generally not be thrown for a {@code null} input.
|
||||
* Each method documents its behaviour in more detail.</p>
|
||||
*
|
||||
* <p>#ThreadSafe#</p>
|
||||
* @since 1.0
|
||||
* @version $Id: ObjectUtils.java 1199894 2011-11-09 17:53:59Z ggregory $
|
||||
*/
|
||||
//@Immutable
|
||||
public class ObjectUtils {
|
||||
|
||||
/**
|
||||
* <p>Singleton used as a {@code null} placeholder where
|
||||
* {@code null} has another meaning.</p>
|
||||
*
|
||||
* <p>For example, in a {@code HashMap} the
|
||||
* {@link java.util.HashMap#get(java.lang.Object)} method returns
|
||||
* {@code null} if the {@code Map} contains {@code null} or if there
|
||||
* is no matching key. The {@code Null} placeholder can be used to
|
||||
* distinguish between these two cases.</p>
|
||||
*
|
||||
* <p>Another example is {@code Hashtable}, where {@code null}
|
||||
* cannot be stored.</p>
|
||||
*
|
||||
* <p>This instance is Serializable.</p>
|
||||
*/
|
||||
public static final Null NULL = new Null();
|
||||
|
||||
/**
|
||||
* <p>{@code ObjectUtils} instances should NOT be constructed in
|
||||
* standard programming. Instead, the static methods on the class should
|
||||
* be used, such as {@code ObjectUtils.defaultIfNull("a","b");}.</p>
|
||||
*
|
||||
* <p>This constructor is public to permit tools that require a JavaBean
|
||||
* instance to operate.</p>
|
||||
*/
|
||||
public ObjectUtils() {
|
||||
super();
|
||||
}
|
||||
|
||||
// Defaulting
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Returns a default value if the object passed is {@code null}.</p>
|
||||
*
|
||||
* <pre>
|
||||
* ObjectUtils.defaultIfNull(null, null) = null
|
||||
* ObjectUtils.defaultIfNull(null, "") = ""
|
||||
* ObjectUtils.defaultIfNull(null, "zz") = "zz"
|
||||
* ObjectUtils.defaultIfNull("abc", *) = "abc"
|
||||
* ObjectUtils.defaultIfNull(Boolean.TRUE, *) = Boolean.TRUE
|
||||
* </pre>
|
||||
*
|
||||
* @param <T> the type of the object
|
||||
* @param object the {@code Object} to test, may be {@code null}
|
||||
* @param defaultValue the default value to return, may be {@code null}
|
||||
* @return {@code object} if it is not {@code null}, defaultValue otherwise
|
||||
*/
|
||||
public static <T> T defaultIfNull(T object, T defaultValue) {
|
||||
return object != null ? object : defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the first value in the array which is not {@code null}.
|
||||
* If all the values are {@code null} or the array is {@code null}
|
||||
* or empty then {@code null} is returned.</p>
|
||||
*
|
||||
* <pre>
|
||||
* ObjectUtils.firstNonNull(null, null) = null
|
||||
* ObjectUtils.firstNonNull(null, "") = ""
|
||||
* ObjectUtils.firstNonNull(null, null, "") = ""
|
||||
* ObjectUtils.firstNonNull(null, "zz") = "zz"
|
||||
* ObjectUtils.firstNonNull("abc", *) = "abc"
|
||||
* ObjectUtils.firstNonNull(null, "xyz", *) = "xyz"
|
||||
* ObjectUtils.firstNonNull(Boolean.TRUE, *) = Boolean.TRUE
|
||||
* ObjectUtils.firstNonNull() = null
|
||||
* </pre>
|
||||
*
|
||||
* @param <T> the component type of the array
|
||||
* @param values the values to test, may be {@code null} or empty
|
||||
* @return the first value from {@code values} which is not {@code null},
|
||||
* or {@code null} if there are no non-null values
|
||||
* @since 3.0
|
||||
*/
|
||||
public static <T> T firstNonNull(T... values) {
|
||||
if (values != null) {
|
||||
for (T val : values) {
|
||||
if (val != null) {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Null-safe equals/hashCode
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Compares two objects for equality, where either one or both
|
||||
* objects may be {@code null}.</p>
|
||||
*
|
||||
* <pre>
|
||||
* ObjectUtils.equals(null, null) = true
|
||||
* ObjectUtils.equals(null, "") = false
|
||||
* ObjectUtils.equals("", null) = false
|
||||
* ObjectUtils.equals("", "") = true
|
||||
* ObjectUtils.equals(Boolean.TRUE, null) = false
|
||||
* ObjectUtils.equals(Boolean.TRUE, "true") = false
|
||||
* ObjectUtils.equals(Boolean.TRUE, Boolean.TRUE) = true
|
||||
* ObjectUtils.equals(Boolean.TRUE, Boolean.FALSE) = false
|
||||
* </pre>
|
||||
*
|
||||
* @param object1 the first object, may be {@code null}
|
||||
* @param object2 the second object, may be {@code null}
|
||||
* @return {@code true} if the values of both objects are the same
|
||||
*/
|
||||
public static boolean equals(Object object1, Object object2) {
|
||||
if (object1 == object2) {
|
||||
return true;
|
||||
}
|
||||
if (object1 == null || object2 == null) {
|
||||
return false;
|
||||
}
|
||||
return object1.equals(object2);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Compares two objects for inequality, where either one or both
|
||||
* objects may be {@code null}.</p>
|
||||
*
|
||||
* <pre>
|
||||
* ObjectUtils.notEqual(null, null) = false
|
||||
* ObjectUtils.notEqual(null, "") = true
|
||||
* ObjectUtils.notEqual("", null) = true
|
||||
* ObjectUtils.notEqual("", "") = false
|
||||
* ObjectUtils.notEqual(Boolean.TRUE, null) = true
|
||||
* ObjectUtils.notEqual(Boolean.TRUE, "true") = true
|
||||
* ObjectUtils.notEqual(Boolean.TRUE, Boolean.TRUE) = false
|
||||
* ObjectUtils.notEqual(Boolean.TRUE, Boolean.FALSE) = true
|
||||
* </pre>
|
||||
*
|
||||
* @param object1 the first object, may be {@code null}
|
||||
* @param object2 the second object, may be {@code null}
|
||||
* @return {@code false} if the values of both objects are the same
|
||||
*/
|
||||
public static boolean notEqual(Object object1, Object object2) {
|
||||
return ObjectUtils.equals(object1, object2) == false;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Gets the hash code of an object returning zero when the
|
||||
* object is {@code null}.</p>
|
||||
*
|
||||
* <pre>
|
||||
* ObjectUtils.hashCode(null) = 0
|
||||
* ObjectUtils.hashCode(obj) = obj.hashCode()
|
||||
* </pre>
|
||||
*
|
||||
* @param obj the object to obtain the hash code of, may be {@code null}
|
||||
* @return the hash code of the object, or zero if null
|
||||
* @since 2.1
|
||||
*/
|
||||
public static int hashCode(Object obj) {
|
||||
// hashCode(Object) retained for performance, as hash code is often critical
|
||||
return obj == null ? 0 : obj.hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Gets the hash code for multiple objects.</p>
|
||||
*
|
||||
* <p>This allows a hash code to be rapidly calculated for a number of objects.
|
||||
* The hash code for a single object is the <em>not</em> same as {@link #hashCode(Object)}.
|
||||
* The hash code for multiple objects is the same as that calculated by an
|
||||
* {@code ArrayList} containing the specified objects.</p>
|
||||
*
|
||||
* <pre>
|
||||
* ObjectUtils.hashCodeMulti() = 1
|
||||
* ObjectUtils.hashCodeMulti((Object[]) null) = 1
|
||||
* ObjectUtils.hashCodeMulti(a) = 31 + a.hashCode()
|
||||
* ObjectUtils.hashCodeMulti(a,b) = (31 + a.hashCode()) * 31 + b.hashCode()
|
||||
* ObjectUtils.hashCodeMulti(a,b,c) = ((31 + a.hashCode()) * 31 + b.hashCode()) * 31 + c.hashCode()
|
||||
* </pre>
|
||||
*
|
||||
* @param objects the objects to obtain the hash code of, may be {@code null}
|
||||
* @return the hash code of the objects, or zero if null
|
||||
* @since 3.0
|
||||
*/
|
||||
public static int hashCodeMulti(Object... objects) {
|
||||
int hash = 1;
|
||||
if (objects != null) {
|
||||
for (Object object : objects) {
|
||||
hash = hash * 31 + ObjectUtils.hashCode(object);
|
||||
}
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
// Identity ToString
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Gets the toString that would be produced by {@code Object}
|
||||
* if a class did not override toString itself. {@code null}
|
||||
* will return {@code null}.</p>
|
||||
*
|
||||
* <pre>
|
||||
* ObjectUtils.identityToString(null) = null
|
||||
* ObjectUtils.identityToString("") = "java.lang.String@1e23"
|
||||
* ObjectUtils.identityToString(Boolean.TRUE) = "java.lang.Boolean@7fa"
|
||||
* </pre>
|
||||
*
|
||||
* @param object the object to create a toString for, may be
|
||||
* {@code null}
|
||||
* @return the default toString text, or {@code null} if
|
||||
* {@code null} passed in
|
||||
*/
|
||||
public static String identityToString(Object object) {
|
||||
if (object == null) {
|
||||
return null;
|
||||
}
|
||||
StringBuffer buffer = new StringBuffer();
|
||||
identityToString(buffer, object);
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Appends the toString that would be produced by {@code Object}
|
||||
* if a class did not override toString itself. {@code null}
|
||||
* will throw a NullPointerException for either of the two parameters. </p>
|
||||
*
|
||||
* <pre>
|
||||
* ObjectUtils.identityToString(buf, "") = buf.append("java.lang.String@1e23"
|
||||
* ObjectUtils.identityToString(buf, Boolean.TRUE) = buf.append("java.lang.Boolean@7fa"
|
||||
* ObjectUtils.identityToString(buf, Boolean.TRUE) = buf.append("java.lang.Boolean@7fa")
|
||||
* </pre>
|
||||
*
|
||||
* @param buffer the buffer to append to
|
||||
* @param object the object to create a toString for
|
||||
* @since 2.4
|
||||
*/
|
||||
public static void identityToString(StringBuffer buffer, Object object) {
|
||||
if (object == null) {
|
||||
throw new NullPointerException("Cannot get the toString of a null identity");
|
||||
}
|
||||
buffer.append(object.getClass().getName())
|
||||
.append('@')
|
||||
.append(Integer.toHexString(System.identityHashCode(object)));
|
||||
}
|
||||
|
||||
// ToString
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Gets the {@code toString} of an {@code Object} returning
|
||||
* an empty string ("") if {@code null} input.</p>
|
||||
*
|
||||
* <pre>
|
||||
* ObjectUtils.toString(null) = ""
|
||||
* ObjectUtils.toString("") = ""
|
||||
* ObjectUtils.toString("bat") = "bat"
|
||||
* ObjectUtils.toString(Boolean.TRUE) = "true"
|
||||
* </pre>
|
||||
*
|
||||
* @see StringUtils#defaultString(String)
|
||||
* @see String#valueOf(Object)
|
||||
* @param obj the Object to {@code toString}, may be null
|
||||
* @return the passed in Object's toString, or nullStr if {@code null} input
|
||||
* @since 2.0
|
||||
*/
|
||||
public static String toString(Object obj) {
|
||||
return obj == null ? "" : obj.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Gets the {@code toString} of an {@code Object} returning
|
||||
* a specified text if {@code null} input.</p>
|
||||
*
|
||||
* <pre>
|
||||
* ObjectUtils.toString(null, null) = null
|
||||
* ObjectUtils.toString(null, "null") = "null"
|
||||
* ObjectUtils.toString("", "null") = ""
|
||||
* ObjectUtils.toString("bat", "null") = "bat"
|
||||
* ObjectUtils.toString(Boolean.TRUE, "null") = "true"
|
||||
* </pre>
|
||||
*
|
||||
* @see StringUtils#defaultString(String,String)
|
||||
* @see String#valueOf(Object)
|
||||
* @param obj the Object to {@code toString}, may be null
|
||||
* @param nullStr the String to return if {@code null} input, may be null
|
||||
* @return the passed in Object's toString, or nullStr if {@code null} input
|
||||
* @since 2.0
|
||||
*/
|
||||
public static String toString(Object obj, String nullStr) {
|
||||
return obj == null ? nullStr : obj.toString();
|
||||
}
|
||||
|
||||
// Comparable
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Null safe comparison of Comparables.</p>
|
||||
*
|
||||
* @param <T> type of the values processed by this method
|
||||
* @param values the set of comparable values, may be null
|
||||
* @return
|
||||
* <ul>
|
||||
* <li>If any objects are non-null and unequal, the lesser object.
|
||||
* <li>If all objects are non-null and equal, the first.
|
||||
* <li>If any of the comparables are null, the lesser of the non-null objects.
|
||||
* <li>If all the comparables are null, null is returned.
|
||||
* </ul>
|
||||
*/
|
||||
public static <T extends Comparable<? super T>> T min(T... values) {
|
||||
T result = null;
|
||||
if (values != null) {
|
||||
for (T value : values) {
|
||||
if (compare(value, result, true) < 0) {
|
||||
result = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Null safe comparison of Comparables.</p>
|
||||
*
|
||||
* @param <T> type of the values processed by this method
|
||||
* @param values the set of comparable values, may be null
|
||||
* @return
|
||||
* <ul>
|
||||
* <li>If any objects are non-null and unequal, the greater object.
|
||||
* <li>If all objects are non-null and equal, the first.
|
||||
* <li>If any of the comparables are null, the greater of the non-null objects.
|
||||
* <li>If all the comparables are null, null is returned.
|
||||
* </ul>
|
||||
*/
|
||||
public static <T extends Comparable<? super T>> T max(T... values) {
|
||||
T result = null;
|
||||
if (values != null) {
|
||||
for (T value : values) {
|
||||
if (compare(value, result, false) > 0) {
|
||||
result = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Null safe comparison of Comparables.
|
||||
* {@code null} is assumed to be less than a non-{@code null} value.</p>
|
||||
*
|
||||
* @param <T> type of the values processed by this method
|
||||
* @param c1 the first comparable, may be null
|
||||
* @param c2 the second comparable, may be null
|
||||
* @return a negative value if c1 < c2, zero if c1 = c2
|
||||
* and a positive value if c1 > c2
|
||||
*/
|
||||
public static <T extends Comparable<? super T>> int compare(T c1, T c2) {
|
||||
return compare(c1, c2, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Null safe comparison of Comparables.</p>
|
||||
*
|
||||
* @param <T> type of the values processed by this method
|
||||
* @param c1 the first comparable, may be null
|
||||
* @param c2 the second comparable, may be null
|
||||
* @param nullGreater if true {@code null} is considered greater
|
||||
* than a non-{@code null} value or if false {@code null} is
|
||||
* considered less than a Non-{@code null} value
|
||||
* @return a negative value if c1 < c2, zero if c1 = c2
|
||||
* and a positive value if c1 > c2
|
||||
* @see java.util.Comparator#compare(Object, Object)
|
||||
*/
|
||||
public static <T extends Comparable<? super T>> int compare(T c1, T c2, boolean nullGreater) {
|
||||
if (c1 == c2) {
|
||||
return 0;
|
||||
} else if (c1 == null) {
|
||||
return nullGreater ? 1 : -1;
|
||||
} else if (c2 == null) {
|
||||
return nullGreater ? -1 : 1;
|
||||
}
|
||||
return c1.compareTo(c2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the "best guess" middle value among comparables. If there is an even
|
||||
* number of total values, the lower of the two middle values will be returned.
|
||||
* @param <T> type of values processed by this method
|
||||
* @param items to compare
|
||||
* @return T at middle position
|
||||
* @throws NullPointerException if items is {@code null}
|
||||
* @throws IllegalArgumentException if items is empty or contains {@code null} values
|
||||
* @since 3.0.1
|
||||
*/
|
||||
public static <T extends Comparable<? super T>> T median(T... items) {
|
||||
Validate.notEmpty(items);
|
||||
Validate.noNullElements(items);
|
||||
TreeSet<T> sort = new TreeSet<T>();
|
||||
Collections.addAll(sort, items);
|
||||
@SuppressWarnings("unchecked") //we know all items added were T instances
|
||||
T result = (T) sort.toArray()[(sort.size() - 1) / 2];
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the "best guess" middle value among comparables. If there is an even
|
||||
* number of total values, the lower of the two middle values will be returned.
|
||||
* @param <T> type of values processed by this method
|
||||
* @param comparator to use for comparisons
|
||||
* @param items to compare
|
||||
* @return T at middle position
|
||||
* @throws NullPointerException if items or comparator is {@code null}
|
||||
* @throws IllegalArgumentException if items is empty or contains {@code null} values
|
||||
* @since 3.0.1
|
||||
*/
|
||||
public static <T> T median(Comparator<T> comparator, T... items) {
|
||||
Validate.notEmpty(items, "null/empty items");
|
||||
Validate.noNullElements(items);
|
||||
Validate.notNull(comparator, "null comparator");
|
||||
TreeSet<T> sort = new TreeSet<T>(comparator);
|
||||
Collections.addAll(sort, items);
|
||||
@SuppressWarnings("unchecked") //we know all items added were T instances
|
||||
T result = (T) sort.toArray()[(sort.size() - 1) / 2];
|
||||
return result;
|
||||
}
|
||||
|
||||
// Mode
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* Find the most frequently occurring item.
|
||||
*
|
||||
* @param <T> type of values processed by this method
|
||||
* @param items to check
|
||||
* @return most populous T, {@code null} if non-unique or no items supplied
|
||||
* @since 3.0.1
|
||||
*/
|
||||
public static <T> T mode(T... items) {
|
||||
if (ArrayUtils.isNotEmpty(items)) {
|
||||
HashMap<T, MutableInt> occurrences = new HashMap<T, MutableInt>(items.length);
|
||||
for (T t : items) {
|
||||
MutableInt count = occurrences.get(t);
|
||||
if (count == null) {
|
||||
occurrences.put(t, new MutableInt(1));
|
||||
} else {
|
||||
count.increment();
|
||||
}
|
||||
}
|
||||
T result = null;
|
||||
int max = 0;
|
||||
for (Map.Entry<T, MutableInt> e : occurrences.entrySet()) {
|
||||
int cmp = e.getValue().intValue();
|
||||
if (cmp == max) {
|
||||
result = null;
|
||||
} else if (cmp > max) {
|
||||
max = cmp;
|
||||
result = e.getKey();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// cloning
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Clone an object.</p>
|
||||
*
|
||||
* @param <T> the type of the object
|
||||
* @param obj the object to clone, null returns null
|
||||
* @return the clone if the object implements {@link Cloneable} otherwise {@code null}
|
||||
* @throws CloneFailedException if the object is cloneable and the clone operation fails
|
||||
* @since 3.0
|
||||
*/
|
||||
public static <T> T clone(final T obj) {
|
||||
if (obj instanceof Cloneable) {
|
||||
final Object result;
|
||||
if (obj.getClass().isArray()) {
|
||||
final Class<?> componentType = obj.getClass().getComponentType();
|
||||
if (!componentType.isPrimitive()) {
|
||||
result = ((Object[]) obj).clone();
|
||||
} else {
|
||||
int length = Array.getLength(obj);
|
||||
result = Array.newInstance(componentType, length);
|
||||
while (length-- > 0) {
|
||||
Array.set(result, length, Array.get(obj, length));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
final Method clone = obj.getClass().getMethod("clone");
|
||||
result = clone.invoke(obj);
|
||||
} catch (final NoSuchMethodException e) {
|
||||
throw new CloneFailedException("Cloneable type "
|
||||
+ obj.getClass().getName()
|
||||
+ " has no clone method", e);
|
||||
} catch (final IllegalAccessException e) {
|
||||
throw new CloneFailedException("Cannot clone Cloneable type "
|
||||
+ obj.getClass().getName(), e);
|
||||
} catch (final InvocationTargetException e) {
|
||||
throw new CloneFailedException("Exception cloning Cloneable type "
|
||||
+ obj.getClass().getName(), e.getCause());
|
||||
}
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
final T checked = (T) result;
|
||||
return checked;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Clone an object if possible.</p>
|
||||
*
|
||||
* <p>This method is similar to {@link #clone(Object)}, but will return the provided
|
||||
* instance as the return value instead of {@code null} if the instance
|
||||
* is not cloneable. This is more convenient if the caller uses different
|
||||
* implementations (e.g. of a service) and some of the implementations do not allow concurrent
|
||||
* processing or have state. In such cases the implementation can simply provide a proper
|
||||
* clone implementation and the caller's code does not have to change.</p>
|
||||
*
|
||||
* @param <T> the type of the object
|
||||
* @param obj the object to clone, null returns null
|
||||
* @return the clone if the object implements {@link Cloneable} otherwise the object itself
|
||||
* @throws CloneFailedException if the object is cloneable and the clone operation fails
|
||||
* @since 3.0
|
||||
*/
|
||||
public static <T> T cloneIfPossible(final T obj) {
|
||||
final T clone = clone(obj);
|
||||
return clone == null ? obj : clone;
|
||||
}
|
||||
|
||||
// Null
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Class used as a null placeholder where {@code null}
|
||||
* has another meaning.</p>
|
||||
*
|
||||
* <p>For example, in a {@code HashMap} the
|
||||
* {@link java.util.HashMap#get(java.lang.Object)} method returns
|
||||
* {@code null} if the {@code Map} contains {@code null} or if there is
|
||||
* no matching key. The {@code Null} placeholder can be used to distinguish
|
||||
* between these two cases.</p>
|
||||
*
|
||||
* <p>Another example is {@code Hashtable}, where {@code null}
|
||||
* cannot be stored.</p>
|
||||
*/
|
||||
public static class Null implements Serializable {
|
||||
/**
|
||||
* Required for serialization support. Declare serialization compatibility with Commons Lang 1.0
|
||||
*
|
||||
* @see java.io.Serializable
|
||||
*/
|
||||
private static final long serialVersionUID = 7092611880189329093L;
|
||||
|
||||
/**
|
||||
* Restricted constructor - singleton.
|
||||
*/
|
||||
Null() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Ensure singleton.</p>
|
||||
*
|
||||
* @return the singleton value
|
||||
*/
|
||||
private Object readResolve() {
|
||||
return ObjectUtils.NULL;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -1,89 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package external.org.apache.commons.lang3.builder;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* The Builder interface is designed to designate a class as a <em>builder</em>
|
||||
* object in the Builder design pattern. Builders are capable of creating and
|
||||
* configuring objects or results that normally take multiple steps to construct
|
||||
* or are very complex to derive.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* The builder interface defines a single method, {@link #build()}, that
|
||||
* classes must implement. The result of this method should be the final
|
||||
* configured object or result after all building operations are performed.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* It is a recommended practice that the methods supplied to configure the
|
||||
* object or result being built return a reference to {@code this} so that
|
||||
* method calls can be chained together.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Example Builder:
|
||||
* <code><pre>
|
||||
* class FontBuilder implements Builder<Font> {
|
||||
* private Font font;
|
||||
*
|
||||
* public FontBuilder(String fontName) {
|
||||
* this.font = new Font(fontName, Font.PLAIN, 12);
|
||||
* }
|
||||
*
|
||||
* public FontBuilder bold() {
|
||||
* this.font = this.font.deriveFont(Font.BOLD);
|
||||
* return this; // Reference returned so calls can be chained
|
||||
* }
|
||||
*
|
||||
* public FontBuilder size(float pointSize) {
|
||||
* this.font = this.font.deriveFont(pointSize);
|
||||
* return this; // Reference returned so calls can be chained
|
||||
* }
|
||||
*
|
||||
* // Other Font construction methods
|
||||
*
|
||||
* public Font build() {
|
||||
* return this.font;
|
||||
* }
|
||||
* }
|
||||
* </pre></code>
|
||||
*
|
||||
* Example Builder Usage:
|
||||
* <code><pre>
|
||||
* Font bold14ptSansSerifFont = new FontBuilder(Font.SANS_SERIF).bold()
|
||||
* .size(14.0f)
|
||||
* .build();
|
||||
* </pre></code>
|
||||
* </p>
|
||||
*
|
||||
* @param <T> the type of object that the builder will construct or compute.
|
||||
*
|
||||
* @since 3.0
|
||||
* @version $Id: Builder.java 1088899 2011-04-05 05:31:27Z bayard $
|
||||
*/
|
||||
public interface Builder<T> {
|
||||
|
||||
/**
|
||||
* Returns a reference to the object being constructed or result being
|
||||
* calculated by the builder.
|
||||
*
|
||||
* @return the object constructed or result calculated by the builder.
|
||||
*/
|
||||
public T build();
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,945 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package external.org.apache.commons.lang3.builder;
|
||||
|
||||
import java.lang.reflect.AccessibleObject;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
import external.org.apache.commons.lang3.ArrayUtils;
|
||||
import external.org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
/**
|
||||
* <p>Assists in implementing {@link Object#equals(Object)} methods.</p>
|
||||
*
|
||||
* <p> This class provides methods to build a good equals method for any
|
||||
* class. It follows rules laid out in
|
||||
* <a href="http://java.sun.com/docs/books/effective/index.html">Effective Java</a>
|
||||
* , by Joshua Bloch. In particular the rule for comparing <code>doubles</code>,
|
||||
* <code>floats</code>, and arrays can be tricky. Also, making sure that
|
||||
* <code>equals()</code> and <code>hashCode()</code> are consistent can be
|
||||
* difficult.</p>
|
||||
*
|
||||
* <p>Two Objects that compare as equals must generate the same hash code,
|
||||
* but two Objects with the same hash code do not have to be equal.</p>
|
||||
*
|
||||
* <p>All relevant fields should be included in the calculation of equals.
|
||||
* Derived fields may be ignored. In particular, any field used in
|
||||
* generating a hash code must be used in the equals method, and vice
|
||||
* versa.</p>
|
||||
*
|
||||
* <p>Typical use for the code is as follows:</p>
|
||||
* <pre>
|
||||
* public boolean equals(Object obj) {
|
||||
* if (obj == null) { return false; }
|
||||
* if (obj == this) { return true; }
|
||||
* if (obj.getClass() != getClass()) {
|
||||
* return false;
|
||||
* }
|
||||
* MyClass rhs = (MyClass) obj;
|
||||
* return new EqualsBuilder()
|
||||
* .appendSuper(super.equals(obj))
|
||||
* .append(field1, rhs.field1)
|
||||
* .append(field2, rhs.field2)
|
||||
* .append(field3, rhs.field3)
|
||||
* .isEquals();
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <p> Alternatively, there is a method that uses reflection to determine
|
||||
* the fields to test. Because these fields are usually private, the method,
|
||||
* <code>reflectionEquals</code>, uses <code>AccessibleObject.setAccessible</code> to
|
||||
* change the visibility of the fields. This will fail under a security
|
||||
* manager, unless the appropriate permissions are set up correctly. It is
|
||||
* also slower than testing explicitly.</p>
|
||||
*
|
||||
* <p> A typical invocation for this method would look like:</p>
|
||||
* <pre>
|
||||
* public boolean equals(Object obj) {
|
||||
* return EqualsBuilder.reflectionEquals(this, obj);
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @since 1.0
|
||||
* @version $Id: EqualsBuilder.java 1091531 2011-04-12 18:29:49Z ggregory $
|
||||
*/
|
||||
public class EqualsBuilder implements Builder<Boolean> {
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* A registry of objects used by reflection methods to detect cyclical object references and avoid infinite loops.
|
||||
* </p>
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
private static final ThreadLocal<Set<Pair<IDKey, IDKey>>> REGISTRY = new ThreadLocal<Set<Pair<IDKey, IDKey>>>();
|
||||
|
||||
/*
|
||||
* NOTE: we cannot store the actual objects in a HashSet, as that would use the very hashCode()
|
||||
* we are in the process of calculating.
|
||||
*
|
||||
* So we generate a one-to-one mapping from the original object to a new object.
|
||||
*
|
||||
* Now HashSet uses equals() to determine if two elements with the same hashcode really
|
||||
* are equal, so we also need to ensure that the replacement objects are only equal
|
||||
* if the original objects are identical.
|
||||
*
|
||||
* The original implementation (2.4 and before) used the System.indentityHashCode()
|
||||
* method - however this is not guaranteed to generate unique ids (e.g. LANG-459)
|
||||
*
|
||||
* We now use the IDKey helper class (adapted from org.apache.axis.utils.IDKey)
|
||||
* to disambiguate the duplicate ids.
|
||||
*/
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Returns the registry of object pairs being traversed by the reflection
|
||||
* methods in the current thread.
|
||||
* </p>
|
||||
*
|
||||
* @return Set the registry of objects being traversed
|
||||
* @since 3.0
|
||||
*/
|
||||
static Set<Pair<IDKey, IDKey>> getRegistry() {
|
||||
return REGISTRY.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Converters value pair into a register pair.
|
||||
* </p>
|
||||
*
|
||||
* @param lhs <code>this</code> object
|
||||
* @param rhs the other object
|
||||
*
|
||||
* @return the pair
|
||||
*/
|
||||
static Pair<IDKey, IDKey> getRegisterPair(Object lhs, Object rhs) {
|
||||
IDKey left = new IDKey(lhs);
|
||||
IDKey right = new IDKey(rhs);
|
||||
return Pair.of(left, right);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Returns <code>true</code> if the registry contains the given object pair.
|
||||
* Used by the reflection methods to avoid infinite loops.
|
||||
* Objects might be swapped therefore a check is needed if the object pair
|
||||
* is registered in given or swapped order.
|
||||
* </p>
|
||||
*
|
||||
* @param lhs <code>this</code> object to lookup in registry
|
||||
* @param rhs the other object to lookup on registry
|
||||
* @return boolean <code>true</code> if the registry contains the given object.
|
||||
* @since 3.0
|
||||
*/
|
||||
static boolean isRegistered(Object lhs, Object rhs) {
|
||||
Set<Pair<IDKey, IDKey>> registry = getRegistry();
|
||||
Pair<IDKey, IDKey> pair = getRegisterPair(lhs, rhs);
|
||||
Pair<IDKey, IDKey> swappedPair = Pair.of(pair.getLeft(), pair.getRight());
|
||||
|
||||
return registry != null
|
||||
&& (registry.contains(pair) || registry.contains(swappedPair));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Registers the given object pair.
|
||||
* Used by the reflection methods to avoid infinite loops.
|
||||
* </p>
|
||||
*
|
||||
* @param lhs <code>this</code> object to register
|
||||
* @param rhs the other object to register
|
||||
*/
|
||||
static void register(Object lhs, Object rhs) {
|
||||
synchronized (EqualsBuilder.class) {
|
||||
if (getRegistry() == null) {
|
||||
REGISTRY.set(new HashSet<Pair<IDKey, IDKey>>());
|
||||
}
|
||||
}
|
||||
|
||||
Set<Pair<IDKey, IDKey>> registry = getRegistry();
|
||||
Pair<IDKey, IDKey> pair = getRegisterPair(lhs, rhs);
|
||||
registry.add(pair);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Unregisters the given object pair.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Used by the reflection methods to avoid infinite loops.
|
||||
*
|
||||
* @param lhs <code>this</code> object to unregister
|
||||
* @param rhs the other object to unregister
|
||||
* @since 3.0
|
||||
*/
|
||||
static void unregister(Object lhs, Object rhs) {
|
||||
Set<Pair<IDKey, IDKey>> registry = getRegistry();
|
||||
if (registry != null) {
|
||||
Pair<IDKey, IDKey> pair = getRegisterPair(lhs, rhs);
|
||||
registry.remove(pair);
|
||||
synchronized (EqualsBuilder.class) {
|
||||
//read again
|
||||
registry = getRegistry();
|
||||
if (registry != null && registry.isEmpty()) {
|
||||
REGISTRY.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the fields tested are equals.
|
||||
* The default value is <code>true</code>.
|
||||
*/
|
||||
private boolean isEquals = true;
|
||||
|
||||
/**
|
||||
* <p>Constructor for EqualsBuilder.</p>
|
||||
*
|
||||
* <p>Starts off assuming that equals is <code>true</code>.</p>
|
||||
* @see Object#equals(Object)
|
||||
*/
|
||||
public EqualsBuilder() {
|
||||
// do nothing for now.
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* <p>This method uses reflection to determine if the two <code>Object</code>s
|
||||
* are equal.</p>
|
||||
*
|
||||
* <p>It uses <code>AccessibleObject.setAccessible</code> to gain access to private
|
||||
* fields. This means that it will throw a security exception if run under
|
||||
* a security manager, if the permissions are not set up correctly. It is also
|
||||
* not as efficient as testing explicitly.</p>
|
||||
*
|
||||
* <p>Transient members will be not be tested, as they are likely derived
|
||||
* fields, and not part of the value of the Object.</p>
|
||||
*
|
||||
* <p>Static fields will not be tested. Superclass fields will be included.</p>
|
||||
*
|
||||
* @param lhs <code>this</code> object
|
||||
* @param rhs the other object
|
||||
* @param excludeFields Collection of String field names to exclude from testing
|
||||
* @return <code>true</code> if the two Objects have tested equals.
|
||||
*/
|
||||
public static boolean reflectionEquals(Object lhs, Object rhs, Collection<String> excludeFields) {
|
||||
return reflectionEquals(lhs, rhs, ReflectionToStringBuilder.toNoNullStringArray(excludeFields));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>This method uses reflection to determine if the two <code>Object</code>s
|
||||
* are equal.</p>
|
||||
*
|
||||
* <p>It uses <code>AccessibleObject.setAccessible</code> to gain access to private
|
||||
* fields. This means that it will throw a security exception if run under
|
||||
* a security manager, if the permissions are not set up correctly. It is also
|
||||
* not as efficient as testing explicitly.</p>
|
||||
*
|
||||
* <p>Transient members will be not be tested, as they are likely derived
|
||||
* fields, and not part of the value of the Object.</p>
|
||||
*
|
||||
* <p>Static fields will not be tested. Superclass fields will be included.</p>
|
||||
*
|
||||
* @param lhs <code>this</code> object
|
||||
* @param rhs the other object
|
||||
* @param excludeFields array of field names to exclude from testing
|
||||
* @return <code>true</code> if the two Objects have tested equals.
|
||||
*/
|
||||
public static boolean reflectionEquals(Object lhs, Object rhs, String... excludeFields) {
|
||||
return reflectionEquals(lhs, rhs, false, null, excludeFields);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>This method uses reflection to determine if the two <code>Object</code>s
|
||||
* are equal.</p>
|
||||
*
|
||||
* <p>It uses <code>AccessibleObject.setAccessible</code> to gain access to private
|
||||
* fields. This means that it will throw a security exception if run under
|
||||
* a security manager, if the permissions are not set up correctly. It is also
|
||||
* not as efficient as testing explicitly.</p>
|
||||
*
|
||||
* <p>If the TestTransients parameter is set to <code>true</code>, transient
|
||||
* members will be tested, otherwise they are ignored, as they are likely
|
||||
* derived fields, and not part of the value of the <code>Object</code>.</p>
|
||||
*
|
||||
* <p>Static fields will not be tested. Superclass fields will be included.</p>
|
||||
*
|
||||
* @param lhs <code>this</code> object
|
||||
* @param rhs the other object
|
||||
* @param testTransients whether to include transient fields
|
||||
* @return <code>true</code> if the two Objects have tested equals.
|
||||
*/
|
||||
public static boolean reflectionEquals(Object lhs, Object rhs, boolean testTransients) {
|
||||
return reflectionEquals(lhs, rhs, testTransients, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>This method uses reflection to determine if the two <code>Object</code>s
|
||||
* are equal.</p>
|
||||
*
|
||||
* <p>It uses <code>AccessibleObject.setAccessible</code> to gain access to private
|
||||
* fields. This means that it will throw a security exception if run under
|
||||
* a security manager, if the permissions are not set up correctly. It is also
|
||||
* not as efficient as testing explicitly.</p>
|
||||
*
|
||||
* <p>If the testTransients parameter is set to <code>true</code>, transient
|
||||
* members will be tested, otherwise they are ignored, as they are likely
|
||||
* derived fields, and not part of the value of the <code>Object</code>.</p>
|
||||
*
|
||||
* <p>Static fields will not be included. Superclass fields will be appended
|
||||
* up to and including the specified superclass. A null superclass is treated
|
||||
* as java.lang.Object.</p>
|
||||
*
|
||||
* @param lhs <code>this</code> object
|
||||
* @param rhs the other object
|
||||
* @param testTransients whether to include transient fields
|
||||
* @param reflectUpToClass the superclass to reflect up to (inclusive),
|
||||
* may be <code>null</code>
|
||||
* @param excludeFields array of field names to exclude from testing
|
||||
* @return <code>true</code> if the two Objects have tested equals.
|
||||
* @since 2.0
|
||||
*/
|
||||
public static boolean reflectionEquals(Object lhs, Object rhs, boolean testTransients, Class<?> reflectUpToClass,
|
||||
String... excludeFields) {
|
||||
if (lhs == rhs) {
|
||||
return true;
|
||||
}
|
||||
if (lhs == null || rhs == null) {
|
||||
return false;
|
||||
}
|
||||
// Find the leaf class since there may be transients in the leaf
|
||||
// class or in classes between the leaf and root.
|
||||
// If we are not testing transients or a subclass has no ivars,
|
||||
// then a subclass can test equals to a superclass.
|
||||
Class<?> lhsClass = lhs.getClass();
|
||||
Class<?> rhsClass = rhs.getClass();
|
||||
Class<?> testClass;
|
||||
if (lhsClass.isInstance(rhs)) {
|
||||
testClass = lhsClass;
|
||||
if (!rhsClass.isInstance(lhs)) {
|
||||
// rhsClass is a subclass of lhsClass
|
||||
testClass = rhsClass;
|
||||
}
|
||||
} else if (rhsClass.isInstance(lhs)) {
|
||||
testClass = rhsClass;
|
||||
if (!lhsClass.isInstance(rhs)) {
|
||||
// lhsClass is a subclass of rhsClass
|
||||
testClass = lhsClass;
|
||||
}
|
||||
} else {
|
||||
// The two classes are not related.
|
||||
return false;
|
||||
}
|
||||
EqualsBuilder equalsBuilder = new EqualsBuilder();
|
||||
try {
|
||||
reflectionAppend(lhs, rhs, testClass, equalsBuilder, testTransients, excludeFields);
|
||||
while (testClass.getSuperclass() != null && testClass != reflectUpToClass) {
|
||||
testClass = testClass.getSuperclass();
|
||||
reflectionAppend(lhs, rhs, testClass, equalsBuilder, testTransients, excludeFields);
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
// In this case, we tried to test a subclass vs. a superclass and
|
||||
// the subclass has ivars or the ivars are transient and
|
||||
// we are testing transients.
|
||||
// If a subclass has ivars that we are trying to test them, we get an
|
||||
// exception and we know that the objects are not equal.
|
||||
return false;
|
||||
}
|
||||
return equalsBuilder.isEquals();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Appends the fields and values defined by the given object of the
|
||||
* given Class.</p>
|
||||
*
|
||||
* @param lhs the left hand object
|
||||
* @param rhs the right hand object
|
||||
* @param clazz the class to append details of
|
||||
* @param builder the builder to append to
|
||||
* @param useTransients whether to test transient fields
|
||||
* @param excludeFields array of field names to exclude from testing
|
||||
*/
|
||||
private static void reflectionAppend(
|
||||
Object lhs,
|
||||
Object rhs,
|
||||
Class<?> clazz,
|
||||
EqualsBuilder builder,
|
||||
boolean useTransients,
|
||||
String[] excludeFields) {
|
||||
|
||||
if (isRegistered(lhs, rhs)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
register(lhs, rhs);
|
||||
Field[] fields = clazz.getDeclaredFields();
|
||||
AccessibleObject.setAccessible(fields, true);
|
||||
for (int i = 0; i < fields.length && builder.isEquals; i++) {
|
||||
Field f = fields[i];
|
||||
if (!ArrayUtils.contains(excludeFields, f.getName())
|
||||
&& (f.getName().indexOf('$') == -1)
|
||||
&& (useTransients || !Modifier.isTransient(f.getModifiers()))
|
||||
&& (!Modifier.isStatic(f.getModifiers()))) {
|
||||
try {
|
||||
builder.append(f.get(lhs), f.get(rhs));
|
||||
} catch (IllegalAccessException e) {
|
||||
//this can't happen. Would get a Security exception instead
|
||||
//throw a runtime exception in case the impossible happens.
|
||||
throw new InternalError("Unexpected IllegalAccessException");
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
unregister(lhs, rhs);
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* <p>Adds the result of <code>super.equals()</code> to this builder.</p>
|
||||
*
|
||||
* @param superEquals the result of calling <code>super.equals()</code>
|
||||
* @return EqualsBuilder - used to chain calls.
|
||||
* @since 2.0
|
||||
*/
|
||||
public EqualsBuilder appendSuper(boolean superEquals) {
|
||||
if (isEquals == false) {
|
||||
return this;
|
||||
}
|
||||
isEquals = superEquals;
|
||||
return this;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* <p>Test if two <code>Object</code>s are equal using their
|
||||
* <code>equals</code> method.</p>
|
||||
*
|
||||
* @param lhs the left hand object
|
||||
* @param rhs the right hand object
|
||||
* @return EqualsBuilder - used to chain calls.
|
||||
*/
|
||||
public EqualsBuilder append(Object lhs, Object rhs) {
|
||||
if (isEquals == false) {
|
||||
return this;
|
||||
}
|
||||
if (lhs == rhs) {
|
||||
return this;
|
||||
}
|
||||
if (lhs == null || rhs == null) {
|
||||
this.setEquals(false);
|
||||
return this;
|
||||
}
|
||||
Class<?> lhsClass = lhs.getClass();
|
||||
if (!lhsClass.isArray()) {
|
||||
// The simple case, not an array, just test the element
|
||||
isEquals = lhs.equals(rhs);
|
||||
} else if (lhs.getClass() != rhs.getClass()) {
|
||||
// Here when we compare different dimensions, for example: a boolean[][] to a boolean[]
|
||||
this.setEquals(false);
|
||||
}
|
||||
// 'Switch' on type of array, to dispatch to the correct handler
|
||||
// This handles multi dimensional arrays of the same depth
|
||||
else if (lhs instanceof long[]) {
|
||||
append((long[]) lhs, (long[]) rhs);
|
||||
} else if (lhs instanceof int[]) {
|
||||
append((int[]) lhs, (int[]) rhs);
|
||||
} else if (lhs instanceof short[]) {
|
||||
append((short[]) lhs, (short[]) rhs);
|
||||
} else if (lhs instanceof char[]) {
|
||||
append((char[]) lhs, (char[]) rhs);
|
||||
} else if (lhs instanceof byte[]) {
|
||||
append((byte[]) lhs, (byte[]) rhs);
|
||||
} else if (lhs instanceof double[]) {
|
||||
append((double[]) lhs, (double[]) rhs);
|
||||
} else if (lhs instanceof float[]) {
|
||||
append((float[]) lhs, (float[]) rhs);
|
||||
} else if (lhs instanceof boolean[]) {
|
||||
append((boolean[]) lhs, (boolean[]) rhs);
|
||||
} else {
|
||||
// Not an array of primitives
|
||||
append((Object[]) lhs, (Object[]) rhs);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Test if two <code>long</code> s are equal.
|
||||
* </p>
|
||||
*
|
||||
* @param lhs
|
||||
* the left hand <code>long</code>
|
||||
* @param rhs
|
||||
* the right hand <code>long</code>
|
||||
* @return EqualsBuilder - used to chain calls.
|
||||
*/
|
||||
public EqualsBuilder append(long lhs, long rhs) {
|
||||
if (isEquals == false) {
|
||||
return this;
|
||||
}
|
||||
isEquals = (lhs == rhs);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Test if two <code>int</code>s are equal.</p>
|
||||
*
|
||||
* @param lhs the left hand <code>int</code>
|
||||
* @param rhs the right hand <code>int</code>
|
||||
* @return EqualsBuilder - used to chain calls.
|
||||
*/
|
||||
public EqualsBuilder append(int lhs, int rhs) {
|
||||
if (isEquals == false) {
|
||||
return this;
|
||||
}
|
||||
isEquals = (lhs == rhs);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Test if two <code>short</code>s are equal.</p>
|
||||
*
|
||||
* @param lhs the left hand <code>short</code>
|
||||
* @param rhs the right hand <code>short</code>
|
||||
* @return EqualsBuilder - used to chain calls.
|
||||
*/
|
||||
public EqualsBuilder append(short lhs, short rhs) {
|
||||
if (isEquals == false) {
|
||||
return this;
|
||||
}
|
||||
isEquals = (lhs == rhs);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Test if two <code>char</code>s are equal.</p>
|
||||
*
|
||||
* @param lhs the left hand <code>char</code>
|
||||
* @param rhs the right hand <code>char</code>
|
||||
* @return EqualsBuilder - used to chain calls.
|
||||
*/
|
||||
public EqualsBuilder append(char lhs, char rhs) {
|
||||
if (isEquals == false) {
|
||||
return this;
|
||||
}
|
||||
isEquals = (lhs == rhs);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Test if two <code>byte</code>s are equal.</p>
|
||||
*
|
||||
* @param lhs the left hand <code>byte</code>
|
||||
* @param rhs the right hand <code>byte</code>
|
||||
* @return EqualsBuilder - used to chain calls.
|
||||
*/
|
||||
public EqualsBuilder append(byte lhs, byte rhs) {
|
||||
if (isEquals == false) {
|
||||
return this;
|
||||
}
|
||||
isEquals = (lhs == rhs);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Test if two <code>double</code>s are equal by testing that the
|
||||
* pattern of bits returned by <code>doubleToLong</code> are equal.</p>
|
||||
*
|
||||
* <p>This handles NaNs, Infinities, and <code>-0.0</code>.</p>
|
||||
*
|
||||
* <p>It is compatible with the hash code generated by
|
||||
* <code>HashCodeBuilder</code>.</p>
|
||||
*
|
||||
* @param lhs the left hand <code>double</code>
|
||||
* @param rhs the right hand <code>double</code>
|
||||
* @return EqualsBuilder - used to chain calls.
|
||||
*/
|
||||
public EqualsBuilder append(double lhs, double rhs) {
|
||||
if (isEquals == false) {
|
||||
return this;
|
||||
}
|
||||
return append(Double.doubleToLongBits(lhs), Double.doubleToLongBits(rhs));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Test if two <code>float</code>s are equal byt testing that the
|
||||
* pattern of bits returned by doubleToLong are equal.</p>
|
||||
*
|
||||
* <p>This handles NaNs, Infinities, and <code>-0.0</code>.</p>
|
||||
*
|
||||
* <p>It is compatible with the hash code generated by
|
||||
* <code>HashCodeBuilder</code>.</p>
|
||||
*
|
||||
* @param lhs the left hand <code>float</code>
|
||||
* @param rhs the right hand <code>float</code>
|
||||
* @return EqualsBuilder - used to chain calls.
|
||||
*/
|
||||
public EqualsBuilder append(float lhs, float rhs) {
|
||||
if (isEquals == false) {
|
||||
return this;
|
||||
}
|
||||
return append(Float.floatToIntBits(lhs), Float.floatToIntBits(rhs));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Test if two <code>booleans</code>s are equal.</p>
|
||||
*
|
||||
* @param lhs the left hand <code>boolean</code>
|
||||
* @param rhs the right hand <code>boolean</code>
|
||||
* @return EqualsBuilder - used to chain calls.
|
||||
*/
|
||||
public EqualsBuilder append(boolean lhs, boolean rhs) {
|
||||
if (isEquals == false) {
|
||||
return this;
|
||||
}
|
||||
isEquals = (lhs == rhs);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Performs a deep comparison of two <code>Object</code> arrays.</p>
|
||||
*
|
||||
* <p>This also will be called for the top level of
|
||||
* multi-dimensional, ragged, and multi-typed arrays.</p>
|
||||
*
|
||||
* @param lhs the left hand <code>Object[]</code>
|
||||
* @param rhs the right hand <code>Object[]</code>
|
||||
* @return EqualsBuilder - used to chain calls.
|
||||
*/
|
||||
public EqualsBuilder append(Object[] lhs, Object[] rhs) {
|
||||
if (isEquals == false) {
|
||||
return this;
|
||||
}
|
||||
if (lhs == rhs) {
|
||||
return this;
|
||||
}
|
||||
if (lhs == null || rhs == null) {
|
||||
this.setEquals(false);
|
||||
return this;
|
||||
}
|
||||
if (lhs.length != rhs.length) {
|
||||
this.setEquals(false);
|
||||
return this;
|
||||
}
|
||||
for (int i = 0; i < lhs.length && isEquals; ++i) {
|
||||
append(lhs[i], rhs[i]);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Deep comparison of array of <code>long</code>. Length and all
|
||||
* values are compared.</p>
|
||||
*
|
||||
* <p>The method {@link #append(long, long)} is used.</p>
|
||||
*
|
||||
* @param lhs the left hand <code>long[]</code>
|
||||
* @param rhs the right hand <code>long[]</code>
|
||||
* @return EqualsBuilder - used to chain calls.
|
||||
*/
|
||||
public EqualsBuilder append(long[] lhs, long[] rhs) {
|
||||
if (isEquals == false) {
|
||||
return this;
|
||||
}
|
||||
if (lhs == rhs) {
|
||||
return this;
|
||||
}
|
||||
if (lhs == null || rhs == null) {
|
||||
this.setEquals(false);
|
||||
return this;
|
||||
}
|
||||
if (lhs.length != rhs.length) {
|
||||
this.setEquals(false);
|
||||
return this;
|
||||
}
|
||||
for (int i = 0; i < lhs.length && isEquals; ++i) {
|
||||
append(lhs[i], rhs[i]);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Deep comparison of array of <code>int</code>. Length and all
|
||||
* values are compared.</p>
|
||||
*
|
||||
* <p>The method {@link #append(int, int)} is used.</p>
|
||||
*
|
||||
* @param lhs the left hand <code>int[]</code>
|
||||
* @param rhs the right hand <code>int[]</code>
|
||||
* @return EqualsBuilder - used to chain calls.
|
||||
*/
|
||||
public EqualsBuilder append(int[] lhs, int[] rhs) {
|
||||
if (isEquals == false) {
|
||||
return this;
|
||||
}
|
||||
if (lhs == rhs) {
|
||||
return this;
|
||||
}
|
||||
if (lhs == null || rhs == null) {
|
||||
this.setEquals(false);
|
||||
return this;
|
||||
}
|
||||
if (lhs.length != rhs.length) {
|
||||
this.setEquals(false);
|
||||
return this;
|
||||
}
|
||||
for (int i = 0; i < lhs.length && isEquals; ++i) {
|
||||
append(lhs[i], rhs[i]);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Deep comparison of array of <code>short</code>. Length and all
|
||||
* values are compared.</p>
|
||||
*
|
||||
* <p>The method {@link #append(short, short)} is used.</p>
|
||||
*
|
||||
* @param lhs the left hand <code>short[]</code>
|
||||
* @param rhs the right hand <code>short[]</code>
|
||||
* @return EqualsBuilder - used to chain calls.
|
||||
*/
|
||||
public EqualsBuilder append(short[] lhs, short[] rhs) {
|
||||
if (isEquals == false) {
|
||||
return this;
|
||||
}
|
||||
if (lhs == rhs) {
|
||||
return this;
|
||||
}
|
||||
if (lhs == null || rhs == null) {
|
||||
this.setEquals(false);
|
||||
return this;
|
||||
}
|
||||
if (lhs.length != rhs.length) {
|
||||
this.setEquals(false);
|
||||
return this;
|
||||
}
|
||||
for (int i = 0; i < lhs.length && isEquals; ++i) {
|
||||
append(lhs[i], rhs[i]);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Deep comparison of array of <code>char</code>. Length and all
|
||||
* values are compared.</p>
|
||||
*
|
||||
* <p>The method {@link #append(char, char)} is used.</p>
|
||||
*
|
||||
* @param lhs the left hand <code>char[]</code>
|
||||
* @param rhs the right hand <code>char[]</code>
|
||||
* @return EqualsBuilder - used to chain calls.
|
||||
*/
|
||||
public EqualsBuilder append(char[] lhs, char[] rhs) {
|
||||
if (isEquals == false) {
|
||||
return this;
|
||||
}
|
||||
if (lhs == rhs) {
|
||||
return this;
|
||||
}
|
||||
if (lhs == null || rhs == null) {
|
||||
this.setEquals(false);
|
||||
return this;
|
||||
}
|
||||
if (lhs.length != rhs.length) {
|
||||
this.setEquals(false);
|
||||
return this;
|
||||
}
|
||||
for (int i = 0; i < lhs.length && isEquals; ++i) {
|
||||
append(lhs[i], rhs[i]);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Deep comparison of array of <code>byte</code>. Length and all
|
||||
* values are compared.</p>
|
||||
*
|
||||
* <p>The method {@link #append(byte, byte)} is used.</p>
|
||||
*
|
||||
* @param lhs the left hand <code>byte[]</code>
|
||||
* @param rhs the right hand <code>byte[]</code>
|
||||
* @return EqualsBuilder - used to chain calls.
|
||||
*/
|
||||
public EqualsBuilder append(byte[] lhs, byte[] rhs) {
|
||||
if (isEquals == false) {
|
||||
return this;
|
||||
}
|
||||
if (lhs == rhs) {
|
||||
return this;
|
||||
}
|
||||
if (lhs == null || rhs == null) {
|
||||
this.setEquals(false);
|
||||
return this;
|
||||
}
|
||||
if (lhs.length != rhs.length) {
|
||||
this.setEquals(false);
|
||||
return this;
|
||||
}
|
||||
for (int i = 0; i < lhs.length && isEquals; ++i) {
|
||||
append(lhs[i], rhs[i]);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Deep comparison of array of <code>double</code>. Length and all
|
||||
* values are compared.</p>
|
||||
*
|
||||
* <p>The method {@link #append(double, double)} is used.</p>
|
||||
*
|
||||
* @param lhs the left hand <code>double[]</code>
|
||||
* @param rhs the right hand <code>double[]</code>
|
||||
* @return EqualsBuilder - used to chain calls.
|
||||
*/
|
||||
public EqualsBuilder append(double[] lhs, double[] rhs) {
|
||||
if (isEquals == false) {
|
||||
return this;
|
||||
}
|
||||
if (lhs == rhs) {
|
||||
return this;
|
||||
}
|
||||
if (lhs == null || rhs == null) {
|
||||
this.setEquals(false);
|
||||
return this;
|
||||
}
|
||||
if (lhs.length != rhs.length) {
|
||||
this.setEquals(false);
|
||||
return this;
|
||||
}
|
||||
for (int i = 0; i < lhs.length && isEquals; ++i) {
|
||||
append(lhs[i], rhs[i]);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Deep comparison of array of <code>float</code>. Length and all
|
||||
* values are compared.</p>
|
||||
*
|
||||
* <p>The method {@link #append(float, float)} is used.</p>
|
||||
*
|
||||
* @param lhs the left hand <code>float[]</code>
|
||||
* @param rhs the right hand <code>float[]</code>
|
||||
* @return EqualsBuilder - used to chain calls.
|
||||
*/
|
||||
public EqualsBuilder append(float[] lhs, float[] rhs) {
|
||||
if (isEquals == false) {
|
||||
return this;
|
||||
}
|
||||
if (lhs == rhs) {
|
||||
return this;
|
||||
}
|
||||
if (lhs == null || rhs == null) {
|
||||
this.setEquals(false);
|
||||
return this;
|
||||
}
|
||||
if (lhs.length != rhs.length) {
|
||||
this.setEquals(false);
|
||||
return this;
|
||||
}
|
||||
for (int i = 0; i < lhs.length && isEquals; ++i) {
|
||||
append(lhs[i], rhs[i]);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Deep comparison of array of <code>boolean</code>. Length and all
|
||||
* values are compared.</p>
|
||||
*
|
||||
* <p>The method {@link #append(boolean, boolean)} is used.</p>
|
||||
*
|
||||
* @param lhs the left hand <code>boolean[]</code>
|
||||
* @param rhs the right hand <code>boolean[]</code>
|
||||
* @return EqualsBuilder - used to chain calls.
|
||||
*/
|
||||
public EqualsBuilder append(boolean[] lhs, boolean[] rhs) {
|
||||
if (isEquals == false) {
|
||||
return this;
|
||||
}
|
||||
if (lhs == rhs) {
|
||||
return this;
|
||||
}
|
||||
if (lhs == null || rhs == null) {
|
||||
this.setEquals(false);
|
||||
return this;
|
||||
}
|
||||
if (lhs.length != rhs.length) {
|
||||
this.setEquals(false);
|
||||
return this;
|
||||
}
|
||||
for (int i = 0; i < lhs.length && isEquals; ++i) {
|
||||
append(lhs[i], rhs[i]);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns <code>true</code> if the fields that have been checked
|
||||
* are all equal.</p>
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public boolean isEquals() {
|
||||
return this.isEquals;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns <code>true</code> if the fields that have been checked
|
||||
* are all equal.</p>
|
||||
*
|
||||
* @return <code>true</code> if all of the fields that have been checked
|
||||
* are equal, <code>false</code> otherwise.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
public Boolean build() {
|
||||
return Boolean.valueOf(isEquals());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the <code>isEquals</code> value.
|
||||
*
|
||||
* @param isEquals The value to set.
|
||||
* @since 2.1
|
||||
*/
|
||||
protected void setEquals(boolean isEquals) {
|
||||
this.isEquals = isEquals;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the EqualsBuilder so you can use the same object again
|
||||
* @since 2.5
|
||||
*/
|
||||
public void reset() {
|
||||
this.isEquals = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,961 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package external.org.apache.commons.lang3.builder;
|
||||
|
||||
import java.lang.reflect.AccessibleObject;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import external.org.apache.commons.lang3.ArrayUtils;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Assists in implementing {@link Object#hashCode()} methods.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* This class enables a good <code>hashCode</code> method to be built for any class. It follows the rules laid out in
|
||||
* the book <a href="http://java.sun.com/docs/books/effective/index.html">Effective Java</a> by Joshua Bloch. Writing a
|
||||
* good <code>hashCode</code> method is actually quite difficult. This class aims to simplify the process.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* The following is the approach taken. When appending a data field, the current total is multiplied by the
|
||||
* multiplier then a relevant value
|
||||
* for that data type is added. For example, if the current hashCode is 17, and the multiplier is 37, then
|
||||
* appending the integer 45 will create a hashcode of 674, namely 17 * 37 + 45.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* All relevant fields from the object should be included in the <code>hashCode</code> method. Derived fields may be
|
||||
* excluded. In general, any field used in the <code>equals</code> method must be used in the <code>hashCode</code>
|
||||
* method.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* To use this class write code as follows:
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* public class Person {
|
||||
* String name;
|
||||
* int age;
|
||||
* boolean smoker;
|
||||
* ...
|
||||
*
|
||||
* public int hashCode() {
|
||||
* // you pick a hard-coded, randomly chosen, non-zero, odd number
|
||||
* // ideally different for each class
|
||||
* return new HashCodeBuilder(17, 37).
|
||||
* append(name).
|
||||
* append(age).
|
||||
* append(smoker).
|
||||
* toHashCode();
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* If required, the superclass <code>hashCode()</code> can be added using {@link #appendSuper}.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Alternatively, there is a method that uses reflection to determine the fields to test. Because these fields are
|
||||
* usually private, the method, <code>reflectionHashCode</code>, uses <code>AccessibleObject.setAccessible</code>
|
||||
* to change the visibility of the fields. This will fail under a security manager, unless the appropriate permissions
|
||||
* are set up correctly. It is also slower than testing explicitly.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* A typical invocation for this method would look like:
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* public int hashCode() {
|
||||
* return HashCodeBuilder.reflectionHashCode(this);
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @since 1.0
|
||||
* @version $Id: HashCodeBuilder.java 1144929 2011-07-10 18:26:16Z ggregory $
|
||||
*/
|
||||
public class HashCodeBuilder implements Builder<Integer> {
|
||||
/**
|
||||
* <p>
|
||||
* A registry of objects used by reflection methods to detect cyclical object references and avoid infinite loops.
|
||||
* </p>
|
||||
*
|
||||
* @since 2.3
|
||||
*/
|
||||
private static final ThreadLocal<Set<IDKey>> REGISTRY = new ThreadLocal<Set<IDKey>>();
|
||||
|
||||
/*
|
||||
* NOTE: we cannot store the actual objects in a HashSet, as that would use the very hashCode()
|
||||
* we are in the process of calculating.
|
||||
*
|
||||
* So we generate a one-to-one mapping from the original object to a new object.
|
||||
*
|
||||
* Now HashSet uses equals() to determine if two elements with the same hashcode really
|
||||
* are equal, so we also need to ensure that the replacement objects are only equal
|
||||
* if the original objects are identical.
|
||||
*
|
||||
* The original implementation (2.4 and before) used the System.indentityHashCode()
|
||||
* method - however this is not guaranteed to generate unique ids (e.g. LANG-459)
|
||||
*
|
||||
* We now use the IDKey helper class (adapted from org.apache.axis.utils.IDKey)
|
||||
* to disambiguate the duplicate ids.
|
||||
*/
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Returns the registry of objects being traversed by the reflection methods in the current thread.
|
||||
* </p>
|
||||
*
|
||||
* @return Set the registry of objects being traversed
|
||||
* @since 2.3
|
||||
*/
|
||||
static Set<IDKey> getRegistry() {
|
||||
return REGISTRY.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Returns <code>true</code> if the registry contains the given object. Used by the reflection methods to avoid
|
||||
* infinite loops.
|
||||
* </p>
|
||||
*
|
||||
* @param value
|
||||
* The object to lookup in the registry.
|
||||
* @return boolean <code>true</code> if the registry contains the given object.
|
||||
* @since 2.3
|
||||
*/
|
||||
static boolean isRegistered(Object value) {
|
||||
Set<IDKey> registry = getRegistry();
|
||||
return registry != null && registry.contains(new IDKey(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Appends the fields and values defined by the given object of the given <code>Class</code>.
|
||||
* </p>
|
||||
*
|
||||
* @param object
|
||||
* the object to append details of
|
||||
* @param clazz
|
||||
* the class to append details of
|
||||
* @param builder
|
||||
* the builder to append to
|
||||
* @param useTransients
|
||||
* whether to use transient fields
|
||||
* @param excludeFields
|
||||
* Collection of String field names to exclude from use in calculation of hash code
|
||||
*/
|
||||
private static void reflectionAppend(Object object, Class<?> clazz, HashCodeBuilder builder, boolean useTransients,
|
||||
String[] excludeFields) {
|
||||
if (isRegistered(object)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
register(object);
|
||||
Field[] fields = clazz.getDeclaredFields();
|
||||
AccessibleObject.setAccessible(fields, true);
|
||||
for (Field field : fields) {
|
||||
if (!ArrayUtils.contains(excludeFields, field.getName())
|
||||
&& (field.getName().indexOf('$') == -1)
|
||||
&& (useTransients || !Modifier.isTransient(field.getModifiers()))
|
||||
&& (!Modifier.isStatic(field.getModifiers()))) {
|
||||
try {
|
||||
Object fieldValue = field.get(object);
|
||||
builder.append(fieldValue);
|
||||
} catch (IllegalAccessException e) {
|
||||
// this can't happen. Would get a Security exception instead
|
||||
// throw a runtime exception in case the impossible happens.
|
||||
throw new InternalError("Unexpected IllegalAccessException");
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
unregister(object);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* This method uses reflection to build a valid hash code.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* It uses <code>AccessibleObject.setAccessible</code> to gain access to private fields. This means that it will
|
||||
* throw a security exception if run under a security manager, if the permissions are not set up correctly. It is
|
||||
* also not as efficient as testing explicitly.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Transient members will be not be used, as they are likely derived fields, and not part of the value of the
|
||||
* <code>Object</code>.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Static fields will not be tested. Superclass fields will be included.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Two randomly chosen, non-zero, odd numbers must be passed in. Ideally these should be different for each class,
|
||||
* however this is not vital. Prime numbers are preferred, especially for the multiplier.
|
||||
* </p>
|
||||
*
|
||||
* @param initialNonZeroOddNumber
|
||||
* a non-zero, odd number used as the initial value
|
||||
* @param multiplierNonZeroOddNumber
|
||||
* a non-zero, odd number used as the multiplier
|
||||
* @param object
|
||||
* the Object to create a <code>hashCode</code> for
|
||||
* @return int hash code
|
||||
* @throws IllegalArgumentException
|
||||
* if the Object is <code>null</code>
|
||||
* @throws IllegalArgumentException
|
||||
* if the number is zero or even
|
||||
*/
|
||||
public static int reflectionHashCode(int initialNonZeroOddNumber, int multiplierNonZeroOddNumber, Object object) {
|
||||
return reflectionHashCode(initialNonZeroOddNumber, multiplierNonZeroOddNumber, object, false, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* This method uses reflection to build a valid hash code.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* It uses <code>AccessibleObject.setAccessible</code> to gain access to private fields. This means that it will
|
||||
* throw a security exception if run under a security manager, if the permissions are not set up correctly. It is
|
||||
* also not as efficient as testing explicitly.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* If the TestTransients parameter is set to <code>true</code>, transient members will be tested, otherwise they
|
||||
* are ignored, as they are likely derived fields, and not part of the value of the <code>Object</code>.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Static fields will not be tested. Superclass fields will be included.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Two randomly chosen, non-zero, odd numbers must be passed in. Ideally these should be different for each class,
|
||||
* however this is not vital. Prime numbers are preferred, especially for the multiplier.
|
||||
* </p>
|
||||
*
|
||||
* @param initialNonZeroOddNumber
|
||||
* a non-zero, odd number used as the initial value
|
||||
* @param multiplierNonZeroOddNumber
|
||||
* a non-zero, odd number used as the multiplier
|
||||
* @param object
|
||||
* the Object to create a <code>hashCode</code> for
|
||||
* @param testTransients
|
||||
* whether to include transient fields
|
||||
* @return int hash code
|
||||
* @throws IllegalArgumentException
|
||||
* if the Object is <code>null</code>
|
||||
* @throws IllegalArgumentException
|
||||
* if the number is zero or even
|
||||
*/
|
||||
public static int reflectionHashCode(int initialNonZeroOddNumber, int multiplierNonZeroOddNumber, Object object,
|
||||
boolean testTransients) {
|
||||
return reflectionHashCode(initialNonZeroOddNumber, multiplierNonZeroOddNumber, object, testTransients, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* This method uses reflection to build a valid hash code.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* It uses <code>AccessibleObject.setAccessible</code> to gain access to private fields. This means that it will
|
||||
* throw a security exception if run under a security manager, if the permissions are not set up correctly. It is
|
||||
* also not as efficient as testing explicitly.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* If the TestTransients parameter is set to <code>true</code>, transient members will be tested, otherwise they
|
||||
* are ignored, as they are likely derived fields, and not part of the value of the <code>Object</code>.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Static fields will not be included. Superclass fields will be included up to and including the specified
|
||||
* superclass. A null superclass is treated as java.lang.Object.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Two randomly chosen, non-zero, odd numbers must be passed in. Ideally these should be different for each class,
|
||||
* however this is not vital. Prime numbers are preferred, especially for the multiplier.
|
||||
* </p>
|
||||
*
|
||||
* @param <T>
|
||||
* the type of the object involved
|
||||
* @param initialNonZeroOddNumber
|
||||
* a non-zero, odd number used as the initial value
|
||||
* @param multiplierNonZeroOddNumber
|
||||
* a non-zero, odd number used as the multiplier
|
||||
* @param object
|
||||
* the Object to create a <code>hashCode</code> for
|
||||
* @param testTransients
|
||||
* whether to include transient fields
|
||||
* @param reflectUpToClass
|
||||
* the superclass to reflect up to (inclusive), may be <code>null</code>
|
||||
* @param excludeFields
|
||||
* array of field names to exclude from use in calculation of hash code
|
||||
* @return int hash code
|
||||
* @throws IllegalArgumentException
|
||||
* if the Object is <code>null</code>
|
||||
* @throws IllegalArgumentException
|
||||
* if the number is zero or even
|
||||
* @since 2.0
|
||||
*/
|
||||
public static <T> int reflectionHashCode(int initialNonZeroOddNumber, int multiplierNonZeroOddNumber, T object,
|
||||
boolean testTransients, Class<? super T> reflectUpToClass, String... excludeFields) {
|
||||
|
||||
if (object == null) {
|
||||
throw new IllegalArgumentException("The object to build a hash code for must not be null");
|
||||
}
|
||||
HashCodeBuilder builder = new HashCodeBuilder(initialNonZeroOddNumber, multiplierNonZeroOddNumber);
|
||||
Class<?> clazz = object.getClass();
|
||||
reflectionAppend(object, clazz, builder, testTransients, excludeFields);
|
||||
while (clazz.getSuperclass() != null && clazz != reflectUpToClass) {
|
||||
clazz = clazz.getSuperclass();
|
||||
reflectionAppend(object, clazz, builder, testTransients, excludeFields);
|
||||
}
|
||||
return builder.toHashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* This method uses reflection to build a valid hash code.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* This constructor uses two hard coded choices for the constants needed to build a hash code.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* It uses <code>AccessibleObject.setAccessible</code> to gain access to private fields. This means that it will
|
||||
* throw a security exception if run under a security manager, if the permissions are not set up correctly. It is
|
||||
* also not as efficient as testing explicitly.
|
||||
* </p>
|
||||
*
|
||||
* <P>
|
||||
* If the TestTransients parameter is set to <code>true</code>, transient members will be tested, otherwise they
|
||||
* are ignored, as they are likely derived fields, and not part of the value of the <code>Object</code>.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Static fields will not be tested. Superclass fields will be included.
|
||||
* </p>
|
||||
*
|
||||
* @param object
|
||||
* the Object to create a <code>hashCode</code> for
|
||||
* @param testTransients
|
||||
* whether to include transient fields
|
||||
* @return int hash code
|
||||
* @throws IllegalArgumentException
|
||||
* if the object is <code>null</code>
|
||||
*/
|
||||
public static int reflectionHashCode(Object object, boolean testTransients) {
|
||||
return reflectionHashCode(17, 37, object, testTransients, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* This method uses reflection to build a valid hash code.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* This constructor uses two hard coded choices for the constants needed to build a hash code.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* It uses <code>AccessibleObject.setAccessible</code> to gain access to private fields. This means that it will
|
||||
* throw a security exception if run under a security manager, if the permissions are not set up correctly. It is
|
||||
* also not as efficient as testing explicitly.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Transient members will be not be used, as they are likely derived fields, and not part of the value of the
|
||||
* <code>Object</code>.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Static fields will not be tested. Superclass fields will be included.
|
||||
* </p>
|
||||
*
|
||||
* @param object
|
||||
* the Object to create a <code>hashCode</code> for
|
||||
* @param excludeFields
|
||||
* Collection of String field names to exclude from use in calculation of hash code
|
||||
* @return int hash code
|
||||
* @throws IllegalArgumentException
|
||||
* if the object is <code>null</code>
|
||||
*/
|
||||
public static int reflectionHashCode(Object object, Collection<String> excludeFields) {
|
||||
return reflectionHashCode(object, ReflectionToStringBuilder.toNoNullStringArray(excludeFields));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* This method uses reflection to build a valid hash code.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* This constructor uses two hard coded choices for the constants needed to build a hash code.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* It uses <code>AccessibleObject.setAccessible</code> to gain access to private fields. This means that it will
|
||||
* throw a security exception if run under a security manager, if the permissions are not set up correctly. It is
|
||||
* also not as efficient as testing explicitly.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Transient members will be not be used, as they are likely derived fields, and not part of the value of the
|
||||
* <code>Object</code>.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Static fields will not be tested. Superclass fields will be included.
|
||||
* </p>
|
||||
*
|
||||
* @param object
|
||||
* the Object to create a <code>hashCode</code> for
|
||||
* @param excludeFields
|
||||
* array of field names to exclude from use in calculation of hash code
|
||||
* @return int hash code
|
||||
* @throws IllegalArgumentException
|
||||
* if the object is <code>null</code>
|
||||
*/
|
||||
public static int reflectionHashCode(Object object, String... excludeFields) {
|
||||
return reflectionHashCode(17, 37, object, false, null, excludeFields);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Registers the given object. Used by the reflection methods to avoid infinite loops.
|
||||
* </p>
|
||||
*
|
||||
* @param value
|
||||
* The object to register.
|
||||
*/
|
||||
static void register(Object value) {
|
||||
synchronized (HashCodeBuilder.class) {
|
||||
if (getRegistry() == null) {
|
||||
REGISTRY.set(new HashSet<IDKey>());
|
||||
}
|
||||
}
|
||||
getRegistry().add(new IDKey(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Unregisters the given object.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Used by the reflection methods to avoid infinite loops.
|
||||
*
|
||||
* @param value
|
||||
* The object to unregister.
|
||||
* @since 2.3
|
||||
*/
|
||||
static void unregister(Object value) {
|
||||
Set<IDKey> registry = getRegistry();
|
||||
if (registry != null) {
|
||||
registry.remove(new IDKey(value));
|
||||
synchronized (HashCodeBuilder.class) {
|
||||
//read again
|
||||
registry = getRegistry();
|
||||
if (registry != null && registry.isEmpty()) {
|
||||
REGISTRY.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constant to use in building the hashCode.
|
||||
*/
|
||||
private final int iConstant;
|
||||
|
||||
/**
|
||||
* Running total of the hashCode.
|
||||
*/
|
||||
private int iTotal = 0;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Uses two hard coded choices for the constants needed to build a <code>hashCode</code>.
|
||||
* </p>
|
||||
*/
|
||||
public HashCodeBuilder() {
|
||||
iConstant = 37;
|
||||
iTotal = 17;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Two randomly chosen, non-zero, odd numbers must be passed in. Ideally these should be different for each class,
|
||||
* however this is not vital.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Prime numbers are preferred, especially for the multiplier.
|
||||
* </p>
|
||||
*
|
||||
* @param initialNonZeroOddNumber
|
||||
* a non-zero, odd number used as the initial value
|
||||
* @param multiplierNonZeroOddNumber
|
||||
* a non-zero, odd number used as the multiplier
|
||||
* @throws IllegalArgumentException
|
||||
* if the number is zero or even
|
||||
*/
|
||||
public HashCodeBuilder(int initialNonZeroOddNumber, int multiplierNonZeroOddNumber) {
|
||||
if (initialNonZeroOddNumber == 0) {
|
||||
throw new IllegalArgumentException("HashCodeBuilder requires a non zero initial value");
|
||||
}
|
||||
if (initialNonZeroOddNumber % 2 == 0) {
|
||||
throw new IllegalArgumentException("HashCodeBuilder requires an odd initial value");
|
||||
}
|
||||
if (multiplierNonZeroOddNumber == 0) {
|
||||
throw new IllegalArgumentException("HashCodeBuilder requires a non zero multiplier");
|
||||
}
|
||||
if (multiplierNonZeroOddNumber % 2 == 0) {
|
||||
throw new IllegalArgumentException("HashCodeBuilder requires an odd multiplier");
|
||||
}
|
||||
iConstant = multiplierNonZeroOddNumber;
|
||||
iTotal = initialNonZeroOddNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Append a <code>hashCode</code> for a <code>boolean</code>.
|
||||
* </p>
|
||||
* <p>
|
||||
* This adds <code>1</code> when true, and <code>0</code> when false to the <code>hashCode</code>.
|
||||
* </p>
|
||||
* <p>
|
||||
* This is in contrast to the standard <code>java.lang.Boolean.hashCode</code> handling, which computes
|
||||
* a <code>hashCode</code> value of <code>1231</code> for <code>java.lang.Boolean</code> instances
|
||||
* that represent <code>true</code> or <code>1237</code> for <code>java.lang.Boolean</code> instances
|
||||
* that represent <code>false</code>.
|
||||
* </p>
|
||||
* <p>
|
||||
* This is in accordance with the <quote>Effective Java</quote> design.
|
||||
* </p>
|
||||
*
|
||||
* @param value
|
||||
* the boolean to add to the <code>hashCode</code>
|
||||
* @return this
|
||||
*/
|
||||
public HashCodeBuilder append(boolean value) {
|
||||
iTotal = iTotal * iConstant + (value ? 0 : 1);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Append a <code>hashCode</code> for a <code>boolean</code> array.
|
||||
* </p>
|
||||
*
|
||||
* @param array
|
||||
* the array to add to the <code>hashCode</code>
|
||||
* @return this
|
||||
*/
|
||||
public HashCodeBuilder append(boolean[] array) {
|
||||
if (array == null) {
|
||||
iTotal = iTotal * iConstant;
|
||||
} else {
|
||||
for (boolean element : array) {
|
||||
append(element);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Append a <code>hashCode</code> for a <code>byte</code>.
|
||||
* </p>
|
||||
*
|
||||
* @param value
|
||||
* the byte to add to the <code>hashCode</code>
|
||||
* @return this
|
||||
*/
|
||||
public HashCodeBuilder append(byte value) {
|
||||
iTotal = iTotal * iConstant + value;
|
||||
return this;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Append a <code>hashCode</code> for a <code>byte</code> array.
|
||||
* </p>
|
||||
*
|
||||
* @param array
|
||||
* the array to add to the <code>hashCode</code>
|
||||
* @return this
|
||||
*/
|
||||
public HashCodeBuilder append(byte[] array) {
|
||||
if (array == null) {
|
||||
iTotal = iTotal * iConstant;
|
||||
} else {
|
||||
for (byte element : array) {
|
||||
append(element);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Append a <code>hashCode</code> for a <code>char</code>.
|
||||
* </p>
|
||||
*
|
||||
* @param value
|
||||
* the char to add to the <code>hashCode</code>
|
||||
* @return this
|
||||
*/
|
||||
public HashCodeBuilder append(char value) {
|
||||
iTotal = iTotal * iConstant + value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Append a <code>hashCode</code> for a <code>char</code> array.
|
||||
* </p>
|
||||
*
|
||||
* @param array
|
||||
* the array to add to the <code>hashCode</code>
|
||||
* @return this
|
||||
*/
|
||||
public HashCodeBuilder append(char[] array) {
|
||||
if (array == null) {
|
||||
iTotal = iTotal * iConstant;
|
||||
} else {
|
||||
for (char element : array) {
|
||||
append(element);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Append a <code>hashCode</code> for a <code>double</code>.
|
||||
* </p>
|
||||
*
|
||||
* @param value
|
||||
* the double to add to the <code>hashCode</code>
|
||||
* @return this
|
||||
*/
|
||||
public HashCodeBuilder append(double value) {
|
||||
return append(Double.doubleToLongBits(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Append a <code>hashCode</code> for a <code>double</code> array.
|
||||
* </p>
|
||||
*
|
||||
* @param array
|
||||
* the array to add to the <code>hashCode</code>
|
||||
* @return this
|
||||
*/
|
||||
public HashCodeBuilder append(double[] array) {
|
||||
if (array == null) {
|
||||
iTotal = iTotal * iConstant;
|
||||
} else {
|
||||
for (double element : array) {
|
||||
append(element);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Append a <code>hashCode</code> for a <code>float</code>.
|
||||
* </p>
|
||||
*
|
||||
* @param value
|
||||
* the float to add to the <code>hashCode</code>
|
||||
* @return this
|
||||
*/
|
||||
public HashCodeBuilder append(float value) {
|
||||
iTotal = iTotal * iConstant + Float.floatToIntBits(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Append a <code>hashCode</code> for a <code>float</code> array.
|
||||
* </p>
|
||||
*
|
||||
* @param array
|
||||
* the array to add to the <code>hashCode</code>
|
||||
* @return this
|
||||
*/
|
||||
public HashCodeBuilder append(float[] array) {
|
||||
if (array == null) {
|
||||
iTotal = iTotal * iConstant;
|
||||
} else {
|
||||
for (float element : array) {
|
||||
append(element);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Append a <code>hashCode</code> for an <code>int</code>.
|
||||
* </p>
|
||||
*
|
||||
* @param value
|
||||
* the int to add to the <code>hashCode</code>
|
||||
* @return this
|
||||
*/
|
||||
public HashCodeBuilder append(int value) {
|
||||
iTotal = iTotal * iConstant + value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Append a <code>hashCode</code> for an <code>int</code> array.
|
||||
* </p>
|
||||
*
|
||||
* @param array
|
||||
* the array to add to the <code>hashCode</code>
|
||||
* @return this
|
||||
*/
|
||||
public HashCodeBuilder append(int[] array) {
|
||||
if (array == null) {
|
||||
iTotal = iTotal * iConstant;
|
||||
} else {
|
||||
for (int element : array) {
|
||||
append(element);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Append a <code>hashCode</code> for a <code>long</code>.
|
||||
* </p>
|
||||
*
|
||||
* @param value
|
||||
* the long to add to the <code>hashCode</code>
|
||||
* @return this
|
||||
*/
|
||||
// NOTE: This method uses >> and not >>> as Effective Java and
|
||||
// Long.hashCode do. Ideally we should switch to >>> at
|
||||
// some stage. There are backwards compat issues, so
|
||||
// that will have to wait for the time being. cf LANG-342.
|
||||
public HashCodeBuilder append(long value) {
|
||||
iTotal = iTotal * iConstant + ((int) (value ^ (value >> 32)));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Append a <code>hashCode</code> for a <code>long</code> array.
|
||||
* </p>
|
||||
*
|
||||
* @param array
|
||||
* the array to add to the <code>hashCode</code>
|
||||
* @return this
|
||||
*/
|
||||
public HashCodeBuilder append(long[] array) {
|
||||
if (array == null) {
|
||||
iTotal = iTotal * iConstant;
|
||||
} else {
|
||||
for (long element : array) {
|
||||
append(element);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Append a <code>hashCode</code> for an <code>Object</code>.
|
||||
* </p>
|
||||
*
|
||||
* @param object
|
||||
* the Object to add to the <code>hashCode</code>
|
||||
* @return this
|
||||
*/
|
||||
public HashCodeBuilder append(Object object) {
|
||||
if (object == null) {
|
||||
iTotal = iTotal * iConstant;
|
||||
|
||||
} else {
|
||||
if(object.getClass().isArray()) {
|
||||
// 'Switch' on type of array, to dispatch to the correct handler
|
||||
// This handles multi dimensional arrays
|
||||
if (object instanceof long[]) {
|
||||
append((long[]) object);
|
||||
} else if (object instanceof int[]) {
|
||||
append((int[]) object);
|
||||
} else if (object instanceof short[]) {
|
||||
append((short[]) object);
|
||||
} else if (object instanceof char[]) {
|
||||
append((char[]) object);
|
||||
} else if (object instanceof byte[]) {
|
||||
append((byte[]) object);
|
||||
} else if (object instanceof double[]) {
|
||||
append((double[]) object);
|
||||
} else if (object instanceof float[]) {
|
||||
append((float[]) object);
|
||||
} else if (object instanceof boolean[]) {
|
||||
append((boolean[]) object);
|
||||
} else {
|
||||
// Not an array of primitives
|
||||
append((Object[]) object);
|
||||
}
|
||||
} else {
|
||||
iTotal = iTotal * iConstant + object.hashCode();
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Append a <code>hashCode</code> for an <code>Object</code> array.
|
||||
* </p>
|
||||
*
|
||||
* @param array
|
||||
* the array to add to the <code>hashCode</code>
|
||||
* @return this
|
||||
*/
|
||||
public HashCodeBuilder append(Object[] array) {
|
||||
if (array == null) {
|
||||
iTotal = iTotal * iConstant;
|
||||
} else {
|
||||
for (Object element : array) {
|
||||
append(element);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Append a <code>hashCode</code> for a <code>short</code>.
|
||||
* </p>
|
||||
*
|
||||
* @param value
|
||||
* the short to add to the <code>hashCode</code>
|
||||
* @return this
|
||||
*/
|
||||
public HashCodeBuilder append(short value) {
|
||||
iTotal = iTotal * iConstant + value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Append a <code>hashCode</code> for a <code>short</code> array.
|
||||
* </p>
|
||||
*
|
||||
* @param array
|
||||
* the array to add to the <code>hashCode</code>
|
||||
* @return this
|
||||
*/
|
||||
public HashCodeBuilder append(short[] array) {
|
||||
if (array == null) {
|
||||
iTotal = iTotal * iConstant;
|
||||
} else {
|
||||
for (short element : array) {
|
||||
append(element);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Adds the result of super.hashCode() to this builder.
|
||||
* </p>
|
||||
*
|
||||
* @param superHashCode
|
||||
* the result of calling <code>super.hashCode()</code>
|
||||
* @return this HashCodeBuilder, used to chain calls.
|
||||
* @since 2.0
|
||||
*/
|
||||
public HashCodeBuilder appendSuper(int superHashCode) {
|
||||
iTotal = iTotal * iConstant + superHashCode;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Return the computed <code>hashCode</code>.
|
||||
* </p>
|
||||
*
|
||||
* @return <code>hashCode</code> based on the fields appended
|
||||
*/
|
||||
public int toHashCode() {
|
||||
return iTotal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the computed <code>hashCode</code>.
|
||||
*
|
||||
* @return <code>hashCode</code> based on the fields appended
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
public Integer build() {
|
||||
return Integer.valueOf(toHashCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* The computed <code>hashCode</code> from toHashCode() is returned due to the likelihood
|
||||
* of bugs in mis-calling toHashCode() and the unlikeliness of it mattering what the hashCode for
|
||||
* HashCodeBuilder itself is.</p>
|
||||
*
|
||||
* @return <code>hashCode</code> based on the fields appended
|
||||
* @since 2.5
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return toHashCode();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package external.org.apache.commons.lang3.builder;
|
||||
|
||||
// adapted from org.apache.axis.utils.IDKey
|
||||
|
||||
/**
|
||||
* Wrap an identity key (System.identityHashCode())
|
||||
* so that an object can only be equal() to itself.
|
||||
*
|
||||
* This is necessary to disambiguate the occasional duplicate
|
||||
* identityHashCodes that can occur.
|
||||
*
|
||||
*/
|
||||
final class IDKey {
|
||||
private final Object value;
|
||||
private final int id;
|
||||
|
||||
/**
|
||||
* Constructor for IDKey
|
||||
* @param _value The value
|
||||
*/
|
||||
public IDKey(Object _value) {
|
||||
// This is the Object hashcode
|
||||
id = System.identityHashCode(_value);
|
||||
// There have been some cases (LANG-459) that return the
|
||||
// same identity hash code for different objects. So
|
||||
// the value is also added to disambiguate these cases.
|
||||
value = _value;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns hashcode - i.e. the system identity hashcode.
|
||||
* @return the hashcode
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* checks if instances are equal
|
||||
* @param other The other object to compare to
|
||||
* @return if the instances are for the same object
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (!(other instanceof IDKey)) {
|
||||
return false;
|
||||
}
|
||||
IDKey idKey = (IDKey) other;
|
||||
if (id != idKey.id) {
|
||||
return false;
|
||||
}
|
||||
// Note that identity equals is used.
|
||||
return value == idKey.value;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,691 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package external.org.apache.commons.lang3.builder;
|
||||
|
||||
import java.lang.reflect.AccessibleObject;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import external.org.apache.commons.lang3.ArrayUtils;
|
||||
import external.org.apache.commons.lang3.ClassUtils;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Assists in implementing {@link Object#toString()} methods using reflection.
|
||||
* </p>
|
||||
* <p>
|
||||
* This class uses reflection to determine the fields to append. Because these fields are usually private, the class
|
||||
* uses {@link java.lang.reflect.AccessibleObject#setAccessible(java.lang.reflect.AccessibleObject[], boolean)} to
|
||||
* change the visibility of the fields. This will fail under a security manager, unless the appropriate permissions are
|
||||
* set up correctly.
|
||||
* </p>
|
||||
* <p>
|
||||
* Using reflection to access (private) fields circumvents any synchronization protection guarding access to these
|
||||
* fields. If a toString method cannot safely read a field, you should exclude it from the toString method, or use
|
||||
* synchronization consistent with the class' lock management around the invocation of the method. Take special care to
|
||||
* exclude non-thread-safe collection classes, because these classes may throw ConcurrentModificationException if
|
||||
* modified while the toString method is executing.
|
||||
* </p>
|
||||
* <p>
|
||||
* A typical invocation for this method would look like:
|
||||
* </p>
|
||||
* <pre>
|
||||
* public String toString() {
|
||||
* return ReflectionToStringBuilder.toString(this);
|
||||
* }
|
||||
* </pre>
|
||||
* <p>
|
||||
* You can also use the builder to debug 3rd party objects:
|
||||
* </p>
|
||||
* <pre>
|
||||
* System.out.println("An object: " + ReflectionToStringBuilder.toString(anObject));
|
||||
* </pre>
|
||||
* <p>
|
||||
* A subclass can control field output by overriding the methods:
|
||||
* <ul>
|
||||
* <li>{@link #accept(java.lang.reflect.Field)}</li>
|
||||
* <li>{@link #getValue(java.lang.reflect.Field)}</li>
|
||||
* </ul>
|
||||
* </p>
|
||||
* <p>
|
||||
* For example, this method does <i>not</i> include the <code>password</code> field in the returned <code>String</code>:
|
||||
* </p>
|
||||
* <pre>
|
||||
* public String toString() {
|
||||
* return (new ReflectionToStringBuilder(this) {
|
||||
* protected boolean accept(Field f) {
|
||||
* return super.accept(f) && !f.getName().equals("password");
|
||||
* }
|
||||
* }).toString();
|
||||
* }
|
||||
* </pre>
|
||||
* <p>
|
||||
* The exact format of the <code>toString</code> is determined by the {@link ToStringStyle} passed into the constructor.
|
||||
* </p>
|
||||
*
|
||||
* @since 2.0
|
||||
* @version $Id: ReflectionToStringBuilder.java 1200177 2011-11-10 06:14:33Z ggregory $
|
||||
*/
|
||||
public class ReflectionToStringBuilder extends ToStringBuilder {
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Builds a <code>toString</code> value using the default <code>ToStringStyle</code> through reflection.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* It uses <code>AccessibleObject.setAccessible</code> to gain access to private fields. This means that it will
|
||||
* throw a security exception if run under a security manager, if the permissions are not set up correctly. It is
|
||||
* also not as efficient as testing explicitly.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Transient members will be not be included, as they are likely derived. Static fields will not be included.
|
||||
* Superclass fields will be appended.
|
||||
* </p>
|
||||
*
|
||||
* @param object
|
||||
* the Object to be output
|
||||
* @return the String result
|
||||
* @throws IllegalArgumentException
|
||||
* if the Object is <code>null</code>
|
||||
*/
|
||||
public static String toString(Object object) {
|
||||
return toString(object, null, false, false, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Builds a <code>toString</code> value through reflection.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* It uses <code>AccessibleObject.setAccessible</code> to gain access to private fields. This means that it will
|
||||
* throw a security exception if run under a security manager, if the permissions are not set up correctly. It is
|
||||
* also not as efficient as testing explicitly.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Transient members will be not be included, as they are likely derived. Static fields will not be included.
|
||||
* Superclass fields will be appended.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* If the style is <code>null</code>, the default <code>ToStringStyle</code> is used.
|
||||
* </p>
|
||||
*
|
||||
* @param object
|
||||
* the Object to be output
|
||||
* @param style
|
||||
* the style of the <code>toString</code> to create, may be <code>null</code>
|
||||
* @return the String result
|
||||
* @throws IllegalArgumentException
|
||||
* if the Object or <code>ToStringStyle</code> is <code>null</code>
|
||||
*/
|
||||
public static String toString(Object object, ToStringStyle style) {
|
||||
return toString(object, style, false, false, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Builds a <code>toString</code> value through reflection.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* It uses <code>AccessibleObject.setAccessible</code> to gain access to private fields. This means that it will
|
||||
* throw a security exception if run under a security manager, if the permissions are not set up correctly. It is
|
||||
* also not as efficient as testing explicitly.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* If the <code>outputTransients</code> is <code>true</code>, transient members will be output, otherwise they
|
||||
* are ignored, as they are likely derived fields, and not part of the value of the Object.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Static fields will not be included. Superclass fields will be appended.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* If the style is <code>null</code>, the default <code>ToStringStyle</code> is used.
|
||||
* </p>
|
||||
*
|
||||
* @param object
|
||||
* the Object to be output
|
||||
* @param style
|
||||
* the style of the <code>toString</code> to create, may be <code>null</code>
|
||||
* @param outputTransients
|
||||
* whether to include transient fields
|
||||
* @return the String result
|
||||
* @throws IllegalArgumentException
|
||||
* if the Object is <code>null</code>
|
||||
*/
|
||||
public static String toString(Object object, ToStringStyle style, boolean outputTransients) {
|
||||
return toString(object, style, outputTransients, false, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Builds a <code>toString</code> value through reflection.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* It uses <code>AccessibleObject.setAccessible</code> to gain access to private fields. This means that it will
|
||||
* throw a security exception if run under a security manager, if the permissions are not set up correctly. It is
|
||||
* also not as efficient as testing explicitly.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* If the <code>outputTransients</code> is <code>true</code>, transient fields will be output, otherwise they
|
||||
* are ignored, as they are likely derived fields, and not part of the value of the Object.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* If the <code>outputStatics</code> is <code>true</code>, static fields will be output, otherwise they are
|
||||
* ignored.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Static fields will not be included. Superclass fields will be appended.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* If the style is <code>null</code>, the default <code>ToStringStyle</code> is used.
|
||||
* </p>
|
||||
*
|
||||
* @param object
|
||||
* the Object to be output
|
||||
* @param style
|
||||
* the style of the <code>toString</code> to create, may be <code>null</code>
|
||||
* @param outputTransients
|
||||
* whether to include transient fields
|
||||
* @param outputStatics
|
||||
* whether to include transient fields
|
||||
* @return the String result
|
||||
* @throws IllegalArgumentException
|
||||
* if the Object is <code>null</code>
|
||||
* @since 2.1
|
||||
*/
|
||||
public static String toString(Object object, ToStringStyle style, boolean outputTransients, boolean outputStatics) {
|
||||
return toString(object, style, outputTransients, outputStatics, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Builds a <code>toString</code> value through reflection.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* It uses <code>AccessibleObject.setAccessible</code> to gain access to private fields. This means that it will
|
||||
* throw a security exception if run under a security manager, if the permissions are not set up correctly. It is
|
||||
* also not as efficient as testing explicitly.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* If the <code>outputTransients</code> is <code>true</code>, transient fields will be output, otherwise they
|
||||
* are ignored, as they are likely derived fields, and not part of the value of the Object.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* If the <code>outputStatics</code> is <code>true</code>, static fields will be output, otherwise they are
|
||||
* ignored.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Superclass fields will be appended up to and including the specified superclass. A null superclass is treated as
|
||||
* <code>java.lang.Object</code>.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* If the style is <code>null</code>, the default <code>ToStringStyle</code> is used.
|
||||
* </p>
|
||||
*
|
||||
* @param <T>
|
||||
* the type of the object
|
||||
* @param object
|
||||
* the Object to be output
|
||||
* @param style
|
||||
* the style of the <code>toString</code> to create, may be <code>null</code>
|
||||
* @param outputTransients
|
||||
* whether to include transient fields
|
||||
* @param outputStatics
|
||||
* whether to include static fields
|
||||
* @param reflectUpToClass
|
||||
* the superclass to reflect up to (inclusive), may be <code>null</code>
|
||||
* @return the String result
|
||||
* @throws IllegalArgumentException
|
||||
* if the Object is <code>null</code>
|
||||
* @since 2.1
|
||||
*/
|
||||
public static <T> String toString(
|
||||
T object, ToStringStyle style, boolean outputTransients,
|
||||
boolean outputStatics, Class<? super T> reflectUpToClass) {
|
||||
return new ReflectionToStringBuilder(object, style, null, reflectUpToClass, outputTransients, outputStatics)
|
||||
.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a String for a toString method excluding the given field names.
|
||||
*
|
||||
* @param object
|
||||
* The object to "toString".
|
||||
* @param excludeFieldNames
|
||||
* The field names to exclude. Null excludes nothing.
|
||||
* @return The toString value.
|
||||
*/
|
||||
public static String toStringExclude(Object object, Collection<String> excludeFieldNames) {
|
||||
return toStringExclude(object, toNoNullStringArray(excludeFieldNames));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given Collection into an array of Strings. The returned array does not contain <code>null</code>
|
||||
* entries. Note that {@link Arrays#sort(Object[])} will throw an {@link NullPointerException} if an array element
|
||||
* is <code>null</code>.
|
||||
*
|
||||
* @param collection
|
||||
* The collection to convert
|
||||
* @return A new array of Strings.
|
||||
*/
|
||||
static String[] toNoNullStringArray(Collection<String> collection) {
|
||||
if (collection == null) {
|
||||
return ArrayUtils.EMPTY_STRING_ARRAY;
|
||||
}
|
||||
return toNoNullStringArray(collection.toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new array of Strings without null elements. Internal method used to normalize exclude lists
|
||||
* (arrays and collections). Note that {@link Arrays#sort(Object[])} will throw an {@link NullPointerException}
|
||||
* if an array element is <code>null</code>.
|
||||
*
|
||||
* @param array
|
||||
* The array to check
|
||||
* @return The given array or a new array without null.
|
||||
*/
|
||||
static String[] toNoNullStringArray(Object[] array) {
|
||||
List<String> list = new ArrayList<String>(array.length);
|
||||
for (Object e : array) {
|
||||
if (e != null) {
|
||||
list.add(e.toString());
|
||||
}
|
||||
}
|
||||
return list.toArray(ArrayUtils.EMPTY_STRING_ARRAY);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Builds a String for a toString method excluding the given field names.
|
||||
*
|
||||
* @param object
|
||||
* The object to "toString".
|
||||
* @param excludeFieldNames
|
||||
* The field names to exclude
|
||||
* @return The toString value.
|
||||
*/
|
||||
public static String toStringExclude(Object object, String... excludeFieldNames) {
|
||||
return new ReflectionToStringBuilder(object).setExcludeFieldNames(excludeFieldNames).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not to append static fields.
|
||||
*/
|
||||
private boolean appendStatics = false;
|
||||
|
||||
/**
|
||||
* Whether or not to append transient fields.
|
||||
*/
|
||||
private boolean appendTransients = false;
|
||||
|
||||
/**
|
||||
* Which field names to exclude from output. Intended for fields like <code>"password"</code>.
|
||||
*
|
||||
* @since 3.0 this is protected instead of private
|
||||
*/
|
||||
protected String[] excludeFieldNames;
|
||||
|
||||
/**
|
||||
* The last super class to stop appending fields for.
|
||||
*/
|
||||
private Class<?> upToClass = null;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* This constructor outputs using the default style set with <code>setDefaultStyle</code>.
|
||||
* </p>
|
||||
*
|
||||
* @param object
|
||||
* the Object to build a <code>toString</code> for, must not be <code>null</code>
|
||||
* @throws IllegalArgumentException
|
||||
* if the Object passed in is <code>null</code>
|
||||
*/
|
||||
public ReflectionToStringBuilder(Object object) {
|
||||
super(object);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* If the style is <code>null</code>, the default style is used.
|
||||
* </p>
|
||||
*
|
||||
* @param object
|
||||
* the Object to build a <code>toString</code> for, must not be <code>null</code>
|
||||
* @param style
|
||||
* the style of the <code>toString</code> to create, may be <code>null</code>
|
||||
* @throws IllegalArgumentException
|
||||
* if the Object passed in is <code>null</code>
|
||||
*/
|
||||
public ReflectionToStringBuilder(Object object, ToStringStyle style) {
|
||||
super(object, style);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* If the style is <code>null</code>, the default style is used.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* If the buffer is <code>null</code>, a new one is created.
|
||||
* </p>
|
||||
*
|
||||
* @param object
|
||||
* the Object to build a <code>toString</code> for
|
||||
* @param style
|
||||
* the style of the <code>toString</code> to create, may be <code>null</code>
|
||||
* @param buffer
|
||||
* the <code>StringBuffer</code> to populate, may be <code>null</code>
|
||||
* @throws IllegalArgumentException
|
||||
* if the Object passed in is <code>null</code>
|
||||
*/
|
||||
public ReflectionToStringBuilder(Object object, ToStringStyle style, StringBuffer buffer) {
|
||||
super(object, style, buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param <T>
|
||||
* the type of the object
|
||||
* @param object
|
||||
* the Object to build a <code>toString</code> for
|
||||
* @param style
|
||||
* the style of the <code>toString</code> to create, may be <code>null</code>
|
||||
* @param buffer
|
||||
* the <code>StringBuffer</code> to populate, may be <code>null</code>
|
||||
* @param reflectUpToClass
|
||||
* the superclass to reflect up to (inclusive), may be <code>null</code>
|
||||
* @param outputTransients
|
||||
* whether to include transient fields
|
||||
* @param outputStatics
|
||||
* whether to include static fields
|
||||
* @since 2.1
|
||||
*/
|
||||
public <T> ReflectionToStringBuilder(
|
||||
T object, ToStringStyle style, StringBuffer buffer,
|
||||
Class<? super T> reflectUpToClass, boolean outputTransients, boolean outputStatics) {
|
||||
super(object, style, buffer);
|
||||
this.setUpToClass(reflectUpToClass);
|
||||
this.setAppendTransients(outputTransients);
|
||||
this.setAppendStatics(outputStatics);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not to append the given <code>Field</code>.
|
||||
* <ul>
|
||||
* <li>Transient fields are appended only if {@link #isAppendTransients()} returns <code>true</code>.
|
||||
* <li>Static fields are appended only if {@link #isAppendStatics()} returns <code>true</code>.
|
||||
* <li>Inner class fields are not appened.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param field
|
||||
* The Field to test.
|
||||
* @return Whether or not to append the given <code>Field</code>.
|
||||
*/
|
||||
protected boolean accept(Field field) {
|
||||
if (field.getName().indexOf(ClassUtils.INNER_CLASS_SEPARATOR_CHAR) != -1) {
|
||||
// Reject field from inner class.
|
||||
return false;
|
||||
}
|
||||
if (Modifier.isTransient(field.getModifiers()) && !this.isAppendTransients()) {
|
||||
// Reject transient fields.
|
||||
return false;
|
||||
}
|
||||
if (Modifier.isStatic(field.getModifiers()) && !this.isAppendStatics()) {
|
||||
// Reject static fields.
|
||||
return false;
|
||||
}
|
||||
if (this.excludeFieldNames != null
|
||||
&& Arrays.binarySearch(this.excludeFieldNames, field.getName()) >= 0) {
|
||||
// Reject fields from the getExcludeFieldNames list.
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Appends the fields and values defined by the given object of the given Class.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* If a cycle is detected as an object is "toString()'ed", such an object is rendered as if
|
||||
* <code>Object.toString()</code> had been called and not implemented by the object.
|
||||
* </p>
|
||||
*
|
||||
* @param clazz
|
||||
* The class of object parameter
|
||||
*/
|
||||
protected void appendFieldsIn(Class<?> clazz) {
|
||||
if (clazz.isArray()) {
|
||||
this.reflectionAppendArray(this.getObject());
|
||||
return;
|
||||
}
|
||||
Field[] fields = clazz.getDeclaredFields();
|
||||
AccessibleObject.setAccessible(fields, true);
|
||||
for (Field field : fields) {
|
||||
String fieldName = field.getName();
|
||||
if (this.accept(field)) {
|
||||
try {
|
||||
// Warning: Field.get(Object) creates wrappers objects
|
||||
// for primitive types.
|
||||
Object fieldValue = this.getValue(field);
|
||||
this.append(fieldName, fieldValue);
|
||||
} catch (IllegalAccessException ex) {
|
||||
//this can't happen. Would get a Security exception
|
||||
// instead
|
||||
//throw a runtime exception in case the impossible
|
||||
// happens.
|
||||
throw new InternalError("Unexpected IllegalAccessException: " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the excludeFieldNames.
|
||||
*/
|
||||
public String[] getExcludeFieldNames() {
|
||||
return this.excludeFieldNames.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Gets the last super class to stop appending fields for.
|
||||
* </p>
|
||||
*
|
||||
* @return The last super class to stop appending fields for.
|
||||
*/
|
||||
public Class<?> getUpToClass() {
|
||||
return this.upToClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Calls <code>java.lang.reflect.Field.get(Object)</code>.
|
||||
* </p>
|
||||
*
|
||||
* @param field
|
||||
* The Field to query.
|
||||
* @return The Object from the given Field.
|
||||
*
|
||||
* @throws IllegalArgumentException
|
||||
* see {@link java.lang.reflect.Field#get(Object)}
|
||||
* @throws IllegalAccessException
|
||||
* see {@link java.lang.reflect.Field#get(Object)}
|
||||
*
|
||||
* @see java.lang.reflect.Field#get(Object)
|
||||
*/
|
||||
protected Object getValue(Field field) throws IllegalArgumentException, IllegalAccessException {
|
||||
return field.get(this.getObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Gets whether or not to append static fields.
|
||||
* </p>
|
||||
*
|
||||
* @return Whether or not to append static fields.
|
||||
* @since 2.1
|
||||
*/
|
||||
public boolean isAppendStatics() {
|
||||
return this.appendStatics;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Gets whether or not to append transient fields.
|
||||
* </p>
|
||||
*
|
||||
* @return Whether or not to append transient fields.
|
||||
*/
|
||||
public boolean isAppendTransients() {
|
||||
return this.appendTransients;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Append to the <code>toString</code> an <code>Object</code> array.
|
||||
* </p>
|
||||
*
|
||||
* @param array
|
||||
* the array to add to the <code>toString</code>
|
||||
* @return this
|
||||
*/
|
||||
public ReflectionToStringBuilder reflectionAppendArray(Object array) {
|
||||
this.getStyle().reflectionAppendArrayDetail(this.getStringBuffer(), null, array);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Sets whether or not to append static fields.
|
||||
* </p>
|
||||
*
|
||||
* @param appendStatics
|
||||
* Whether or not to append static fields.
|
||||
* @since 2.1
|
||||
*/
|
||||
public void setAppendStatics(boolean appendStatics) {
|
||||
this.appendStatics = appendStatics;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Sets whether or not to append transient fields.
|
||||
* </p>
|
||||
*
|
||||
* @param appendTransients
|
||||
* Whether or not to append transient fields.
|
||||
*/
|
||||
public void setAppendTransients(boolean appendTransients) {
|
||||
this.appendTransients = appendTransients;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the field names to exclude.
|
||||
*
|
||||
* @param excludeFieldNamesParam
|
||||
* The excludeFieldNames to excluding from toString or <code>null</code>.
|
||||
* @return <code>this</code>
|
||||
*/
|
||||
public ReflectionToStringBuilder setExcludeFieldNames(String... excludeFieldNamesParam) {
|
||||
if (excludeFieldNamesParam == null) {
|
||||
this.excludeFieldNames = null;
|
||||
} else {
|
||||
//clone and remove nulls
|
||||
this.excludeFieldNames = toNoNullStringArray(excludeFieldNamesParam);
|
||||
Arrays.sort(this.excludeFieldNames);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Sets the last super class to stop appending fields for.
|
||||
* </p>
|
||||
*
|
||||
* @param clazz
|
||||
* The last super class to stop appending fields for.
|
||||
*/
|
||||
public void setUpToClass(Class<?> clazz) {
|
||||
if (clazz != null) {
|
||||
Object object = getObject();
|
||||
if (object != null && clazz.isInstance(object) == false) {
|
||||
throw new IllegalArgumentException("Specified class is not a superclass of the object");
|
||||
}
|
||||
}
|
||||
this.upToClass = clazz;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Gets the String built by this builder.
|
||||
* </p>
|
||||
*
|
||||
* @return the built string
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
if (this.getObject() == null) {
|
||||
return this.getStyle().getNullText();
|
||||
}
|
||||
Class<?> clazz = this.getObject().getClass();
|
||||
this.appendFieldsIn(clazz);
|
||||
while (clazz.getSuperclass() != null && clazz != this.getUpToClass()) {
|
||||
clazz = clazz.getSuperclass();
|
||||
this.appendFieldsIn(clazz);
|
||||
}
|
||||
return super.toString();
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -1,28 +0,0 @@
|
|||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<html>
|
||||
<body>
|
||||
Assists in creating consistent <code>equals(Object)</code>, <code>toString()</code>,
|
||||
<code>hashCode()</code>, and <code>compareTo(Object)</code> methods.
|
||||
@see java.lang.Object#equals(Object)
|
||||
@see java.lang.Object#toString()
|
||||
@see java.lang.Object#hashCode()
|
||||
@see java.lang.Comparable#compareTo(Object)
|
||||
@since 1.0
|
||||
<p>These classes are not thread-safe.</p>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package external.org.apache.commons.lang3.exception;
|
||||
|
||||
/**
|
||||
* Exception thrown when a clone cannot be created. In contrast to
|
||||
* {@link CloneNotSupportedException} this is a {@link RuntimeException}.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
public class CloneFailedException extends RuntimeException {
|
||||
// ~ Static fields/initializers ---------------------------------------------
|
||||
|
||||
private static final long serialVersionUID = 20091223L;
|
||||
|
||||
// ~ Constructors -----------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Constructs a CloneFailedException.
|
||||
*
|
||||
* @param message description of the exception
|
||||
* @since upcoming
|
||||
*/
|
||||
public CloneFailedException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a CloneFailedException.
|
||||
*
|
||||
* @param cause cause of the exception
|
||||
* @since upcoming
|
||||
*/
|
||||
public CloneFailedException(final Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a CloneFailedException.
|
||||
*
|
||||
* @param message description of the exception
|
||||
* @param cause cause of the exception
|
||||
* @since upcoming
|
||||
*/
|
||||
public CloneFailedException(final String message, final Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<html>
|
||||
<body>
|
||||
Provides functionality for Exceptions.
|
||||
<p>Contains the concept of an exception with context i.e. such an exception
|
||||
will contain a map with keys and values. This provides an easy way to pass valuable
|
||||
state information at exception time in useful form to a calling process.</p>
|
||||
<p>Lastly, {@link org.apache.commons.lang3.exception.ExceptionUtils}
|
||||
also contains <code>Throwable</code> manipulation and examination routines.</p>
|
||||
@since 1.0
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package external.org.apache.commons.lang3.mutable;
|
||||
|
||||
/**
|
||||
* Provides mutable access to a value.
|
||||
* <p>
|
||||
* <code>Mutable</code> is used as a generic interface to the implementations in this package.
|
||||
* <p>
|
||||
* A typical use case would be to enable a primitive or string to be passed to a method and allow that method to
|
||||
* effectively change the value of the primitive/string. Another use case is to store a frequently changing primitive in
|
||||
* a collection (for example a total in a map) without needing to create new Integer/Long wrapper objects.
|
||||
*
|
||||
* @since 2.1
|
||||
* @param <T> the type to set and get
|
||||
* @version $Id: Mutable.java 1153213 2011-08-02 17:35:39Z ggregory $
|
||||
*/
|
||||
public interface Mutable<T> {
|
||||
|
||||
/**
|
||||
* Gets the value of this mutable.
|
||||
*
|
||||
* @return the stored value
|
||||
*/
|
||||
T getValue();
|
||||
|
||||
/**
|
||||
* Sets the value of this mutable.
|
||||
*
|
||||
* @param value
|
||||
* the value to store
|
||||
* @throws NullPointerException
|
||||
* if the object is null and null is invalid
|
||||
* @throws ClassCastException
|
||||
* if the type is invalid
|
||||
*/
|
||||
void setValue(T value);
|
||||
|
||||
}
|
||||
|
|
@ -1,273 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package external.org.apache.commons.lang3.mutable;
|
||||
|
||||
/**
|
||||
* A mutable <code>int</code> wrapper.
|
||||
* <p>
|
||||
* Note that as MutableInt does not extend Integer, it is not treated by String.format as an Integer parameter.
|
||||
*
|
||||
* @see Integer
|
||||
* @since 2.1
|
||||
* @version $Id: MutableInt.java 1160571 2011-08-23 07:36:08Z bayard $
|
||||
*/
|
||||
public class MutableInt extends Number implements Comparable<MutableInt>, Mutable<Number> {
|
||||
|
||||
/**
|
||||
* Required for serialization support.
|
||||
*
|
||||
* @see java.io.Serializable
|
||||
*/
|
||||
private static final long serialVersionUID = 512176391864L;
|
||||
|
||||
/** The mutable value. */
|
||||
private int value;
|
||||
|
||||
/**
|
||||
* Constructs a new MutableInt with the default value of zero.
|
||||
*/
|
||||
public MutableInt() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new MutableInt with the specified value.
|
||||
*
|
||||
* @param value the initial value to store
|
||||
*/
|
||||
public MutableInt(int value) {
|
||||
super();
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new MutableInt with the specified value.
|
||||
*
|
||||
* @param value the initial value to store, not null
|
||||
* @throws NullPointerException if the object is null
|
||||
*/
|
||||
public MutableInt(Number value) {
|
||||
super();
|
||||
this.value = value.intValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new MutableInt parsing the given string.
|
||||
*
|
||||
* @param value the string to parse, not null
|
||||
* @throws NumberFormatException if the string cannot be parsed into an int
|
||||
* @since 2.5
|
||||
*/
|
||||
public MutableInt(String value) throws NumberFormatException {
|
||||
super();
|
||||
this.value = Integer.parseInt(value);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* Gets the value as a Integer instance.
|
||||
*
|
||||
* @return the value as a Integer, never null
|
||||
*/
|
||||
public Integer getValue() {
|
||||
return Integer.valueOf(this.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value.
|
||||
*
|
||||
* @param value the value to set
|
||||
*/
|
||||
public void setValue(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value from any Number instance.
|
||||
*
|
||||
* @param value the value to set, not null
|
||||
* @throws NullPointerException if the object is null
|
||||
*/
|
||||
public void setValue(Number value) {
|
||||
this.value = value.intValue();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* Increments the value.
|
||||
*
|
||||
* @since Commons Lang 2.2
|
||||
*/
|
||||
public void increment() {
|
||||
value++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrements the value.
|
||||
*
|
||||
* @since Commons Lang 2.2
|
||||
*/
|
||||
public void decrement() {
|
||||
value--;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* Adds a value to the value of this instance.
|
||||
*
|
||||
* @param operand the value to add, not null
|
||||
* @since Commons Lang 2.2
|
||||
*/
|
||||
public void add(int operand) {
|
||||
this.value += operand;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a value to the value of this instance.
|
||||
*
|
||||
* @param operand the value to add, not null
|
||||
* @throws NullPointerException if the object is null
|
||||
* @since Commons Lang 2.2
|
||||
*/
|
||||
public void add(Number operand) {
|
||||
this.value += operand.intValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Subtracts a value from the value of this instance.
|
||||
*
|
||||
* @param operand the value to subtract, not null
|
||||
* @since Commons Lang 2.2
|
||||
*/
|
||||
public void subtract(int operand) {
|
||||
this.value -= operand;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subtracts a value from the value of this instance.
|
||||
*
|
||||
* @param operand the value to subtract, not null
|
||||
* @throws NullPointerException if the object is null
|
||||
* @since Commons Lang 2.2
|
||||
*/
|
||||
public void subtract(Number operand) {
|
||||
this.value -= operand.intValue();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
// shortValue and byteValue rely on Number implementation
|
||||
/**
|
||||
* Returns the value of this MutableInt as an int.
|
||||
*
|
||||
* @return the numeric value represented by this object after conversion to type int.
|
||||
*/
|
||||
@Override
|
||||
public int intValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of this MutableInt as a long.
|
||||
*
|
||||
* @return the numeric value represented by this object after conversion to type long.
|
||||
*/
|
||||
@Override
|
||||
public long longValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of this MutableInt as a float.
|
||||
*
|
||||
* @return the numeric value represented by this object after conversion to type float.
|
||||
*/
|
||||
@Override
|
||||
public float floatValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of this MutableInt as a double.
|
||||
*
|
||||
* @return the numeric value represented by this object after conversion to type double.
|
||||
*/
|
||||
@Override
|
||||
public double doubleValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* Gets this mutable as an instance of Integer.
|
||||
*
|
||||
* @return a Integer instance containing the value from this mutable, never null
|
||||
*/
|
||||
public Integer toInteger() {
|
||||
return Integer.valueOf(intValue());
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* Compares this object to the specified object. The result is <code>true</code> if and only if the argument is
|
||||
* not <code>null</code> and is a <code>MutableInt</code> object that contains the same <code>int</code> value
|
||||
* as this object.
|
||||
*
|
||||
* @param obj the object to compare with, null returns false
|
||||
* @return <code>true</code> if the objects are the same; <code>false</code> otherwise.
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof MutableInt) {
|
||||
return value == ((MutableInt) obj).intValue();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a suitable hash code for this mutable.
|
||||
*
|
||||
* @return a suitable hash code
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return value;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* Compares this mutable to another in ascending order.
|
||||
*
|
||||
* @param other the other mutable to compare to, not null
|
||||
* @return negative if this is less, zero if equal, positive if greater
|
||||
*/
|
||||
public int compareTo(MutableInt other) {
|
||||
int anotherVal = other.value;
|
||||
return value < anotherVal ? -1 : (value == anotherVal ? 0 : 1);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* Returns the String value of this mutable.
|
||||
*
|
||||
* @return the mutable value as a string
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
<html>
|
||||
<head>
|
||||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
-->
|
||||
<title></title>
|
||||
</head>
|
||||
<body>
|
||||
Provides typed mutable wrappers to primitive values and Object.
|
||||
@since 2.1
|
||||
<p>These classes are not thread-safe.</p>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
This document is the API specification for the Apache Commons Lang library.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<html>
|
||||
<body>
|
||||
Provides highly reusable static utility methods, chiefly concerned
|
||||
with adding value to the {@link java.lang} classes.
|
||||
@since 1.0
|
||||
<p>Most of these classes are immutable and thus thread-safe.
|
||||
However Charset is not currently guaranteed thread-safe under all circumstances.</p>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,185 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package external.org.apache.commons.lang3.reflect;
|
||||
|
||||
import java.lang.reflect.AccessibleObject;
|
||||
import java.lang.reflect.Member;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
import external.org.apache.commons.lang3.ClassUtils;
|
||||
|
||||
/**
|
||||
* Contains common code for working with Methods/Constructors, extracted and
|
||||
* refactored from <code>MethodUtils</code> when it was imported from Commons
|
||||
* BeanUtils.
|
||||
*
|
||||
* @since 2.5
|
||||
* @version $Id: MemberUtils.java 1143537 2011-07-06 19:30:22Z joehni $
|
||||
*/
|
||||
public abstract class MemberUtils {
|
||||
// TODO extract an interface to implement compareParameterSets(...)?
|
||||
|
||||
private static final int ACCESS_TEST = Modifier.PUBLIC | Modifier.PROTECTED | Modifier.PRIVATE;
|
||||
|
||||
/** Array of primitive number types ordered by "promotability" */
|
||||
private static final Class<?>[] ORDERED_PRIMITIVE_TYPES = { Byte.TYPE, Short.TYPE,
|
||||
Character.TYPE, Integer.TYPE, Long.TYPE, Float.TYPE, Double.TYPE };
|
||||
|
||||
/**
|
||||
* XXX Default access superclass workaround
|
||||
*
|
||||
* When a public class has a default access superclass with public members,
|
||||
* these members are accessible. Calling them from compiled code works fine.
|
||||
* Unfortunately, on some JVMs, using reflection to invoke these members
|
||||
* seems to (wrongly) prevent access even when the modifier is public.
|
||||
* Calling setAccessible(true) solves the problem but will only work from
|
||||
* sufficiently privileged code. Better workarounds would be gratefully
|
||||
* accepted.
|
||||
* @param o the AccessibleObject to set as accessible
|
||||
*/
|
||||
static void setAccessibleWorkaround(AccessibleObject o) {
|
||||
if (o == null || o.isAccessible()) {
|
||||
return;
|
||||
}
|
||||
Member m = (Member) o;
|
||||
if (Modifier.isPublic(m.getModifiers())
|
||||
&& isPackageAccess(m.getDeclaringClass().getModifiers())) {
|
||||
try {
|
||||
o.setAccessible(true);
|
||||
} catch (SecurityException e) { // NOPMD
|
||||
// ignore in favor of subsequent IllegalAccessException
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a given set of modifiers implies package access.
|
||||
* @param modifiers to test
|
||||
* @return true unless package/protected/private modifier detected
|
||||
*/
|
||||
static boolean isPackageAccess(int modifiers) {
|
||||
return (modifiers & ACCESS_TEST) == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a Member is accessible.
|
||||
* @param m Member to check
|
||||
* @return true if <code>m</code> is accessible
|
||||
*/
|
||||
static boolean isAccessible(Member m) {
|
||||
return m != null && Modifier.isPublic(m.getModifiers()) && !m.isSynthetic();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the relative fitness of two sets of parameter types in terms of
|
||||
* matching a third set of runtime parameter types, such that a list ordered
|
||||
* by the results of the comparison would return the best match first
|
||||
* (least).
|
||||
*
|
||||
* @param left the "left" parameter set
|
||||
* @param right the "right" parameter set
|
||||
* @param actual the runtime parameter types to match against
|
||||
* <code>left</code>/<code>right</code>
|
||||
* @return int consistent with <code>compare</code> semantics
|
||||
*/
|
||||
public static int compareParameterTypes(Class<?>[] left, Class<?>[] right, Class<?>[] actual) {
|
||||
float leftCost = getTotalTransformationCost(actual, left);
|
||||
float rightCost = getTotalTransformationCost(actual, right);
|
||||
return leftCost < rightCost ? -1 : rightCost < leftCost ? 1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the sum of the object transformation cost for each class in the
|
||||
* source argument list.
|
||||
* @param srcArgs The source arguments
|
||||
* @param destArgs The destination arguments
|
||||
* @return The total transformation cost
|
||||
*/
|
||||
private static float getTotalTransformationCost(Class<?>[] srcArgs, Class<?>[] destArgs) {
|
||||
float totalCost = 0.0f;
|
||||
for (int i = 0; i < srcArgs.length; i++) {
|
||||
Class<?> srcClass, destClass;
|
||||
srcClass = srcArgs[i];
|
||||
destClass = destArgs[i];
|
||||
totalCost += getObjectTransformationCost(srcClass, destClass);
|
||||
}
|
||||
return totalCost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of steps required needed to turn the source class into
|
||||
* the destination class. This represents the number of steps in the object
|
||||
* hierarchy graph.
|
||||
* @param srcClass The source class
|
||||
* @param destClass The destination class
|
||||
* @return The cost of transforming an object
|
||||
*/
|
||||
private static float getObjectTransformationCost(Class<?> srcClass, Class<?> destClass) {
|
||||
if (destClass.isPrimitive()) {
|
||||
return getPrimitivePromotionCost(srcClass, destClass);
|
||||
}
|
||||
float cost = 0.0f;
|
||||
while (srcClass != null && !destClass.equals(srcClass)) {
|
||||
if (destClass.isInterface() && ClassUtils.isAssignable(srcClass, destClass)) {
|
||||
// slight penalty for interface match.
|
||||
// we still want an exact match to override an interface match,
|
||||
// but
|
||||
// an interface match should override anything where we have to
|
||||
// get a superclass.
|
||||
cost += 0.25f;
|
||||
break;
|
||||
}
|
||||
cost++;
|
||||
srcClass = srcClass.getSuperclass();
|
||||
}
|
||||
/*
|
||||
* If the destination class is null, we've travelled all the way up to
|
||||
* an Object match. We'll penalize this by adding 1.5 to the cost.
|
||||
*/
|
||||
if (srcClass == null) {
|
||||
cost += 1.5f;
|
||||
}
|
||||
return cost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of steps required to promote a primitive number to another
|
||||
* type.
|
||||
* @param srcClass the (primitive) source class
|
||||
* @param destClass the (primitive) destination class
|
||||
* @return The cost of promoting the primitive
|
||||
*/
|
||||
private static float getPrimitivePromotionCost(final Class<?> srcClass, final Class<?> destClass) {
|
||||
float cost = 0.0f;
|
||||
Class<?> cls = srcClass;
|
||||
if (!cls.isPrimitive()) {
|
||||
// slight unwrapping penalty
|
||||
cost += 0.1f;
|
||||
cls = ClassUtils.wrapperToPrimitive(cls);
|
||||
}
|
||||
for (int i = 0; cls != destClass && i < ORDERED_PRIMITIVE_TYPES.length; i++) {
|
||||
if (cls == ORDERED_PRIMITIVE_TYPES[i]) {
|
||||
cost += 0.1f;
|
||||
if (i < ORDERED_PRIMITIVE_TYPES.length - 1) {
|
||||
cls = ORDERED_PRIMITIVE_TYPES[i + 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
return cost;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,537 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package external.org.apache.commons.lang3.reflect;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
import external.org.apache.commons.lang3.ArrayUtils;
|
||||
import external.org.apache.commons.lang3.ClassUtils;
|
||||
|
||||
/**
|
||||
* <p>Utility reflection methods focused on methods, originally from Commons BeanUtils.
|
||||
* Differences from the BeanUtils version may be noted, especially where similar functionality
|
||||
* already existed within Lang.
|
||||
* </p>
|
||||
*
|
||||
* <h3>Known Limitations</h3>
|
||||
* <h4>Accessing Public Methods In A Default Access Superclass</h4>
|
||||
* <p>There is an issue when invoking public methods contained in a default access superclass on JREs prior to 1.4.
|
||||
* Reflection locates these methods fine and correctly assigns them as public.
|
||||
* However, an <code>IllegalAccessException</code> is thrown if the method is invoked.</p>
|
||||
*
|
||||
* <p><code>MethodUtils</code> contains a workaround for this situation.
|
||||
* It will attempt to call <code>setAccessible</code> on this method.
|
||||
* If this call succeeds, then the method can be invoked as normal.
|
||||
* This call will only succeed when the application has sufficient security privileges.
|
||||
* If this call fails then the method may fail.</p>
|
||||
*
|
||||
* @since 2.5
|
||||
* @version $Id: MethodUtils.java 1166253 2011-09-07 16:27:42Z ggregory $
|
||||
*/
|
||||
public class MethodUtils {
|
||||
|
||||
/**
|
||||
* <p>MethodUtils instances should NOT be constructed in standard programming.
|
||||
* Instead, the class should be used as
|
||||
* <code>MethodUtils.getAccessibleMethod(method)</code>.</p>
|
||||
*
|
||||
* <p>This constructor is public to permit tools that require a JavaBean
|
||||
* instance to operate.</p>
|
||||
*/
|
||||
public MethodUtils() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Invokes a named method whose parameter type matches the object type.</p>
|
||||
*
|
||||
* <p>This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.</p>
|
||||
*
|
||||
* <p>This method supports calls to methods taking primitive parameters
|
||||
* via passing in wrapping classes. So, for example, a <code>Boolean</code> object
|
||||
* would match a <code>boolean</code> primitive.</p>
|
||||
*
|
||||
* <p>This is a convenient wrapper for
|
||||
* {@link #invokeMethod(Object object,String methodName, Object[] args, Class[] parameterTypes)}.
|
||||
* </p>
|
||||
*
|
||||
* @param object invoke method on this object
|
||||
* @param methodName get method with this name
|
||||
* @param args use these arguments - treat null as empty array
|
||||
* @return The value returned by the invoked method
|
||||
*
|
||||
* @throws NoSuchMethodException if there is no such accessible method
|
||||
* @throws InvocationTargetException wraps an exception thrown by the method invoked
|
||||
* @throws IllegalAccessException if the requested method is not accessible via reflection
|
||||
*/
|
||||
public static Object invokeMethod(Object object, String methodName,
|
||||
Object... args) throws NoSuchMethodException,
|
||||
IllegalAccessException, InvocationTargetException {
|
||||
if (args == null) {
|
||||
args = ArrayUtils.EMPTY_OBJECT_ARRAY;
|
||||
}
|
||||
int arguments = args.length;
|
||||
Class<?>[] parameterTypes = new Class[arguments];
|
||||
for (int i = 0; i < arguments; i++) {
|
||||
parameterTypes[i] = args[i].getClass();
|
||||
}
|
||||
return invokeMethod(object, methodName, args, parameterTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Invokes a named method whose parameter type matches the object type.</p>
|
||||
*
|
||||
* <p>This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.</p>
|
||||
*
|
||||
* <p>This method supports calls to methods taking primitive parameters
|
||||
* via passing in wrapping classes. So, for example, a <code>Boolean</code> object
|
||||
* would match a <code>boolean</code> primitive.</p>
|
||||
*
|
||||
* @param object invoke method on this object
|
||||
* @param methodName get method with this name
|
||||
* @param args use these arguments - treat null as empty array
|
||||
* @param parameterTypes match these parameters - treat null as empty array
|
||||
* @return The value returned by the invoked method
|
||||
*
|
||||
* @throws NoSuchMethodException if there is no such accessible method
|
||||
* @throws InvocationTargetException wraps an exception thrown by the method invoked
|
||||
* @throws IllegalAccessException if the requested method is not accessible via reflection
|
||||
*/
|
||||
public static Object invokeMethod(Object object, String methodName,
|
||||
Object[] args, Class<?>[] parameterTypes)
|
||||
throws NoSuchMethodException, IllegalAccessException,
|
||||
InvocationTargetException {
|
||||
if (parameterTypes == null) {
|
||||
parameterTypes = ArrayUtils.EMPTY_CLASS_ARRAY;
|
||||
}
|
||||
if (args == null) {
|
||||
args = ArrayUtils.EMPTY_OBJECT_ARRAY;
|
||||
}
|
||||
Method method = getMatchingAccessibleMethod(object.getClass(),
|
||||
methodName, parameterTypes);
|
||||
if (method == null) {
|
||||
throw new NoSuchMethodException("No such accessible method: "
|
||||
+ methodName + "() on object: "
|
||||
+ object.getClass().getName());
|
||||
}
|
||||
return method.invoke(object, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Invokes a method whose parameter types match exactly the object
|
||||
* types.</p>
|
||||
*
|
||||
* <p>This uses reflection to invoke the method obtained from a call to
|
||||
* <code>getAccessibleMethod()</code>.</p>
|
||||
*
|
||||
* @param object invoke method on this object
|
||||
* @param methodName get method with this name
|
||||
* @param args use these arguments - treat null as empty array
|
||||
* @return The value returned by the invoked method
|
||||
*
|
||||
* @throws NoSuchMethodException if there is no such accessible method
|
||||
* @throws InvocationTargetException wraps an exception thrown by the
|
||||
* method invoked
|
||||
* @throws IllegalAccessException if the requested method is not accessible
|
||||
* via reflection
|
||||
*/
|
||||
public static Object invokeExactMethod(Object object, String methodName,
|
||||
Object... args) throws NoSuchMethodException,
|
||||
IllegalAccessException, InvocationTargetException {
|
||||
if (args == null) {
|
||||
args = ArrayUtils.EMPTY_OBJECT_ARRAY;
|
||||
}
|
||||
int arguments = args.length;
|
||||
Class<?>[] parameterTypes = new Class[arguments];
|
||||
for (int i = 0; i < arguments; i++) {
|
||||
parameterTypes[i] = args[i].getClass();
|
||||
}
|
||||
return invokeExactMethod(object, methodName, args, parameterTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Invokes a method whose parameter types match exactly the parameter
|
||||
* types given.</p>
|
||||
*
|
||||
* <p>This uses reflection to invoke the method obtained from a call to
|
||||
* <code>getAccessibleMethod()</code>.</p>
|
||||
*
|
||||
* @param object invoke method on this object
|
||||
* @param methodName get method with this name
|
||||
* @param args use these arguments - treat null as empty array
|
||||
* @param parameterTypes match these parameters - treat null as empty array
|
||||
* @return The value returned by the invoked method
|
||||
*
|
||||
* @throws NoSuchMethodException if there is no such accessible method
|
||||
* @throws InvocationTargetException wraps an exception thrown by the
|
||||
* method invoked
|
||||
* @throws IllegalAccessException if the requested method is not accessible
|
||||
* via reflection
|
||||
*/
|
||||
public static Object invokeExactMethod(Object object, String methodName,
|
||||
Object[] args, Class<?>[] parameterTypes)
|
||||
throws NoSuchMethodException, IllegalAccessException,
|
||||
InvocationTargetException {
|
||||
if (args == null) {
|
||||
args = ArrayUtils.EMPTY_OBJECT_ARRAY;
|
||||
}
|
||||
if (parameterTypes == null) {
|
||||
parameterTypes = ArrayUtils.EMPTY_CLASS_ARRAY;
|
||||
}
|
||||
Method method = getAccessibleMethod(object.getClass(), methodName,
|
||||
parameterTypes);
|
||||
if (method == null) {
|
||||
throw new NoSuchMethodException("No such accessible method: "
|
||||
+ methodName + "() on object: "
|
||||
+ object.getClass().getName());
|
||||
}
|
||||
return method.invoke(object, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Invokes a static method whose parameter types match exactly the parameter
|
||||
* types given.</p>
|
||||
*
|
||||
* <p>This uses reflection to invoke the method obtained from a call to
|
||||
* {@link #getAccessibleMethod(Class, String, Class[])}.</p>
|
||||
*
|
||||
* @param cls invoke static method on this class
|
||||
* @param methodName get method with this name
|
||||
* @param args use these arguments - treat null as empty array
|
||||
* @param parameterTypes match these parameters - treat null as empty array
|
||||
* @return The value returned by the invoked method
|
||||
*
|
||||
* @throws NoSuchMethodException if there is no such accessible method
|
||||
* @throws InvocationTargetException wraps an exception thrown by the
|
||||
* method invoked
|
||||
* @throws IllegalAccessException if the requested method is not accessible
|
||||
* via reflection
|
||||
*/
|
||||
public static Object invokeExactStaticMethod(Class<?> cls, String methodName,
|
||||
Object[] args, Class<?>[] parameterTypes)
|
||||
throws NoSuchMethodException, IllegalAccessException,
|
||||
InvocationTargetException {
|
||||
if (args == null) {
|
||||
args = ArrayUtils.EMPTY_OBJECT_ARRAY;
|
||||
}
|
||||
if (parameterTypes == null) {
|
||||
parameterTypes = ArrayUtils.EMPTY_CLASS_ARRAY;
|
||||
}
|
||||
Method method = getAccessibleMethod(cls, methodName, parameterTypes);
|
||||
if (method == null) {
|
||||
throw new NoSuchMethodException("No such accessible method: "
|
||||
+ methodName + "() on class: " + cls.getName());
|
||||
}
|
||||
return method.invoke(null, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Invokes a named static method whose parameter type matches the object type.</p>
|
||||
*
|
||||
* <p>This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.</p>
|
||||
*
|
||||
* <p>This method supports calls to methods taking primitive parameters
|
||||
* via passing in wrapping classes. So, for example, a <code>Boolean</code> class
|
||||
* would match a <code>boolean</code> primitive.</p>
|
||||
*
|
||||
* <p>This is a convenient wrapper for
|
||||
* {@link #invokeStaticMethod(Class objectClass,String methodName,Object [] args,Class[] parameterTypes)}.
|
||||
* </p>
|
||||
*
|
||||
* @param cls invoke static method on this class
|
||||
* @param methodName get method with this name
|
||||
* @param args use these arguments - treat null as empty array
|
||||
* @return The value returned by the invoked method
|
||||
*
|
||||
* @throws NoSuchMethodException if there is no such accessible method
|
||||
* @throws InvocationTargetException wraps an exception thrown by the
|
||||
* method invoked
|
||||
* @throws IllegalAccessException if the requested method is not accessible
|
||||
* via reflection
|
||||
*/
|
||||
public static Object invokeStaticMethod(Class<?> cls, String methodName,
|
||||
Object... args) throws NoSuchMethodException,
|
||||
IllegalAccessException, InvocationTargetException {
|
||||
if (args == null) {
|
||||
args = ArrayUtils.EMPTY_OBJECT_ARRAY;
|
||||
}
|
||||
int arguments = args.length;
|
||||
Class<?>[] parameterTypes = new Class[arguments];
|
||||
for (int i = 0; i < arguments; i++) {
|
||||
parameterTypes[i] = args[i].getClass();
|
||||
}
|
||||
return invokeStaticMethod(cls, methodName, args, parameterTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Invokes a named static method whose parameter type matches the object type.</p>
|
||||
*
|
||||
* <p>This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.</p>
|
||||
*
|
||||
* <p>This method supports calls to methods taking primitive parameters
|
||||
* via passing in wrapping classes. So, for example, a <code>Boolean</code> class
|
||||
* would match a <code>boolean</code> primitive.</p>
|
||||
*
|
||||
*
|
||||
* @param cls invoke static method on this class
|
||||
* @param methodName get method with this name
|
||||
* @param args use these arguments - treat null as empty array
|
||||
* @param parameterTypes match these parameters - treat null as empty array
|
||||
* @return The value returned by the invoked method
|
||||
*
|
||||
* @throws NoSuchMethodException if there is no such accessible method
|
||||
* @throws InvocationTargetException wraps an exception thrown by the
|
||||
* method invoked
|
||||
* @throws IllegalAccessException if the requested method is not accessible
|
||||
* via reflection
|
||||
*/
|
||||
public static Object invokeStaticMethod(Class<?> cls, String methodName,
|
||||
Object[] args, Class<?>[] parameterTypes)
|
||||
throws NoSuchMethodException, IllegalAccessException,
|
||||
InvocationTargetException {
|
||||
if (parameterTypes == null) {
|
||||
parameterTypes = ArrayUtils.EMPTY_CLASS_ARRAY;
|
||||
}
|
||||
if (args == null) {
|
||||
args = ArrayUtils.EMPTY_OBJECT_ARRAY;
|
||||
}
|
||||
Method method = getMatchingAccessibleMethod(cls, methodName,
|
||||
parameterTypes);
|
||||
if (method == null) {
|
||||
throw new NoSuchMethodException("No such accessible method: "
|
||||
+ methodName + "() on class: " + cls.getName());
|
||||
}
|
||||
return method.invoke(null, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Invokes a static method whose parameter types match exactly the object
|
||||
* types.</p>
|
||||
*
|
||||
* <p>This uses reflection to invoke the method obtained from a call to
|
||||
* {@link #getAccessibleMethod(Class, String, Class[])}.</p>
|
||||
*
|
||||
* @param cls invoke static method on this class
|
||||
* @param methodName get method with this name
|
||||
* @param args use these arguments - treat null as empty array
|
||||
* @return The value returned by the invoked method
|
||||
*
|
||||
* @throws NoSuchMethodException if there is no such accessible method
|
||||
* @throws InvocationTargetException wraps an exception thrown by the
|
||||
* method invoked
|
||||
* @throws IllegalAccessException if the requested method is not accessible
|
||||
* via reflection
|
||||
*/
|
||||
public static Object invokeExactStaticMethod(Class<?> cls, String methodName,
|
||||
Object... args) throws NoSuchMethodException,
|
||||
IllegalAccessException, InvocationTargetException {
|
||||
if (args == null) {
|
||||
args = ArrayUtils.EMPTY_OBJECT_ARRAY;
|
||||
}
|
||||
int arguments = args.length;
|
||||
Class<?>[] parameterTypes = new Class[arguments];
|
||||
for (int i = 0; i < arguments; i++) {
|
||||
parameterTypes[i] = args[i].getClass();
|
||||
}
|
||||
return invokeExactStaticMethod(cls, methodName, args, parameterTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns an accessible method (that is, one that can be invoked via
|
||||
* reflection) with given name and parameters. If no such method
|
||||
* can be found, return <code>null</code>.
|
||||
* This is just a convenient wrapper for
|
||||
* {@link #getAccessibleMethod(Method method)}.</p>
|
||||
*
|
||||
* @param cls get method from this class
|
||||
* @param methodName get method with this name
|
||||
* @param parameterTypes with these parameters types
|
||||
* @return The accessible method
|
||||
*/
|
||||
public static Method getAccessibleMethod(Class<?> cls, String methodName,
|
||||
Class<?>... parameterTypes) {
|
||||
try {
|
||||
return getAccessibleMethod(cls.getMethod(methodName,
|
||||
parameterTypes));
|
||||
} catch (NoSuchMethodException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns an accessible method (that is, one that can be invoked via
|
||||
* reflection) that implements the specified Method. If no such method
|
||||
* can be found, return <code>null</code>.</p>
|
||||
*
|
||||
* @param method The method that we wish to call
|
||||
* @return The accessible method
|
||||
*/
|
||||
public static Method getAccessibleMethod(Method method) {
|
||||
if (!MemberUtils.isAccessible(method)) {
|
||||
return null;
|
||||
}
|
||||
// If the declaring class is public, we are done
|
||||
Class<?> cls = method.getDeclaringClass();
|
||||
if (Modifier.isPublic(cls.getModifiers())) {
|
||||
return method;
|
||||
}
|
||||
String methodName = method.getName();
|
||||
Class<?>[] parameterTypes = method.getParameterTypes();
|
||||
|
||||
// Check the implemented interfaces and subinterfaces
|
||||
method = getAccessibleMethodFromInterfaceNest(cls, methodName,
|
||||
parameterTypes);
|
||||
|
||||
// Check the superclass chain
|
||||
if (method == null) {
|
||||
method = getAccessibleMethodFromSuperclass(cls, methodName,
|
||||
parameterTypes);
|
||||
}
|
||||
return method;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns an accessible method (that is, one that can be invoked via
|
||||
* reflection) by scanning through the superclasses. If no such method
|
||||
* can be found, return <code>null</code>.</p>
|
||||
*
|
||||
* @param cls Class to be checked
|
||||
* @param methodName Method name of the method we wish to call
|
||||
* @param parameterTypes The parameter type signatures
|
||||
* @return the accessible method or <code>null</code> if not found
|
||||
*/
|
||||
private static Method getAccessibleMethodFromSuperclass(Class<?> cls,
|
||||
String methodName, Class<?>... parameterTypes) {
|
||||
Class<?> parentClass = cls.getSuperclass();
|
||||
while (parentClass != null) {
|
||||
if (Modifier.isPublic(parentClass.getModifiers())) {
|
||||
try {
|
||||
return parentClass.getMethod(methodName, parameterTypes);
|
||||
} catch (NoSuchMethodException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
parentClass = parentClass.getSuperclass();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns an accessible method (that is, one that can be invoked via
|
||||
* reflection) that implements the specified method, by scanning through
|
||||
* all implemented interfaces and subinterfaces. If no such method
|
||||
* can be found, return <code>null</code>.</p>
|
||||
*
|
||||
* <p>There isn't any good reason why this method must be private.
|
||||
* It is because there doesn't seem any reason why other classes should
|
||||
* call this rather than the higher level methods.</p>
|
||||
*
|
||||
* @param cls Parent class for the interfaces to be checked
|
||||
* @param methodName Method name of the method we wish to call
|
||||
* @param parameterTypes The parameter type signatures
|
||||
* @return the accessible method or <code>null</code> if not found
|
||||
*/
|
||||
private static Method getAccessibleMethodFromInterfaceNest(Class<?> cls,
|
||||
String methodName, Class<?>... parameterTypes) {
|
||||
Method method = null;
|
||||
|
||||
// Search up the superclass chain
|
||||
for (; cls != null; cls = cls.getSuperclass()) {
|
||||
|
||||
// Check the implemented interfaces of the parent class
|
||||
Class<?>[] interfaces = cls.getInterfaces();
|
||||
for (int i = 0; i < interfaces.length; i++) {
|
||||
// Is this interface public?
|
||||
if (!Modifier.isPublic(interfaces[i].getModifiers())) {
|
||||
continue;
|
||||
}
|
||||
// Does the method exist on this interface?
|
||||
try {
|
||||
method = interfaces[i].getDeclaredMethod(methodName,
|
||||
parameterTypes);
|
||||
} catch (NoSuchMethodException e) { // NOPMD
|
||||
/*
|
||||
* Swallow, if no method is found after the loop then this
|
||||
* method returns null.
|
||||
*/
|
||||
}
|
||||
if (method != null) {
|
||||
break;
|
||||
}
|
||||
// Recursively check our parent interfaces
|
||||
method = getAccessibleMethodFromInterfaceNest(interfaces[i],
|
||||
methodName, parameterTypes);
|
||||
if (method != null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return method;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Finds an accessible method that matches the given name and has compatible parameters.
|
||||
* Compatible parameters mean that every method parameter is assignable from
|
||||
* the given parameters.
|
||||
* In other words, it finds a method with the given name
|
||||
* that will take the parameters given.<p>
|
||||
*
|
||||
* <p>This method is used by
|
||||
* {@link
|
||||
* #invokeMethod(Object object, String methodName, Object[] args, Class[] parameterTypes)}.
|
||||
*
|
||||
* <p>This method can match primitive parameter by passing in wrapper classes.
|
||||
* For example, a <code>Boolean</code> will match a primitive <code>boolean</code>
|
||||
* parameter.
|
||||
*
|
||||
* @param cls find method in this class
|
||||
* @param methodName find method with this name
|
||||
* @param parameterTypes find method with most compatible parameters
|
||||
* @return The accessible method
|
||||
*/
|
||||
public static Method getMatchingAccessibleMethod(Class<?> cls,
|
||||
String methodName, Class<?>... parameterTypes) {
|
||||
try {
|
||||
Method method = cls.getMethod(methodName, parameterTypes);
|
||||
MemberUtils.setAccessibleWorkaround(method);
|
||||
return method;
|
||||
} catch (NoSuchMethodException e) { // NOPMD - Swallow the exception
|
||||
}
|
||||
// search through all methods
|
||||
Method bestMatch = null;
|
||||
Method[] methods = cls.getMethods();
|
||||
for (Method method : methods) {
|
||||
// compare name and parameters
|
||||
if (method.getName().equals(methodName) && ClassUtils.isAssignable(parameterTypes, method.getParameterTypes(), true)) {
|
||||
// get accessible version of method
|
||||
Method accessibleMethod = getAccessibleMethod(method);
|
||||
if (accessibleMethod != null && (bestMatch == null || MemberUtils.compareParameterTypes(
|
||||
accessibleMethod.getParameterTypes(),
|
||||
bestMatch.getParameterTypes(),
|
||||
parameterTypes) < 0)) {
|
||||
bestMatch = accessibleMethod;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bestMatch != null) {
|
||||
MemberUtils.setAccessibleWorkaround(bestMatch);
|
||||
}
|
||||
return bestMatch;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
<html>
|
||||
<head>
|
||||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
-->
|
||||
<title></title>
|
||||
</head>
|
||||
<body>
|
||||
Accumulates common high-level uses of the <code>java.lang.reflect</code> APIs.
|
||||
@since 3.0
|
||||
<p>These classes are immutable, and therefore thread-safe.</p>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,103 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package external.org.apache.commons.lang3.tuple;
|
||||
|
||||
/**
|
||||
* <p>An immutable pair consisting of two {@code Object} elements.</p>
|
||||
*
|
||||
* <p>Although the implementation is immutable, there is no restriction on the objects
|
||||
* that may be stored. If mutable objects are stored in the pair, then the pair
|
||||
* itself effectively becomes mutable. The class is also not {@code final}, so a subclass
|
||||
* could add undesirable behaviour.</p>
|
||||
*
|
||||
* <p>#ThreadSafe# if the objects are threadsafe</p>
|
||||
*
|
||||
* @param <L> the left element type
|
||||
* @param <R> the right element type
|
||||
*
|
||||
* @since Lang 3.0
|
||||
* @version $Id: ImmutablePair.java 1127544 2011-05-25 14:35:42Z scolebourne $
|
||||
*/
|
||||
public final class ImmutablePair<L, R> extends Pair<L, R> {
|
||||
|
||||
/** Serialization version */
|
||||
private static final long serialVersionUID = 4954918890077093841L;
|
||||
|
||||
/** Left object */
|
||||
public final L left;
|
||||
/** Right object */
|
||||
public final R right;
|
||||
|
||||
/**
|
||||
* <p>Obtains an immutable pair of from two objects inferring the generic types.</p>
|
||||
*
|
||||
* <p>This factory allows the pair to be created using inference to
|
||||
* obtain the generic types.</p>
|
||||
*
|
||||
* @param <L> the left element type
|
||||
* @param <R> the right element type
|
||||
* @param left the left element, may be null
|
||||
* @param right the right element, may be null
|
||||
* @return a pair formed from the two parameters, not null
|
||||
*/
|
||||
public static <L, R> ImmutablePair<L, R> of(L left, R right) {
|
||||
return new ImmutablePair<L, R>(left, right);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new pair instance.
|
||||
*
|
||||
* @param left the left value, may be null
|
||||
* @param right the right value, may be null
|
||||
*/
|
||||
public ImmutablePair(L left, R right) {
|
||||
super();
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public L getLeft() {
|
||||
return left;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public R getRight() {
|
||||
return right;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Throws {@code UnsupportedOperationException}.</p>
|
||||
*
|
||||
* <p>This pair is immutable, so this operation is not supported.</p>
|
||||
*
|
||||
* @param value the value to set
|
||||
* @return never
|
||||
* @throws UnsupportedOperationException as this operation is not supported
|
||||
*/
|
||||
public R setValue(R value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,177 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package external.org.apache.commons.lang3.tuple;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
import external.org.apache.commons.lang3.ObjectUtils;
|
||||
import external.org.apache.commons.lang3.builder.CompareToBuilder;
|
||||
|
||||
/**
|
||||
* <p>A pair consisting of two elements.</p>
|
||||
*
|
||||
* <p>This class is an abstract implementation defining the basic API.
|
||||
* It refers to the elements as 'left' and 'right'. It also implements the
|
||||
* {@code Map.Entry} interface where the key is 'left' and the value is 'right'.</p>
|
||||
*
|
||||
* <p>Subclass implementations may be mutable or immutable.
|
||||
* However, there is no restriction on the type of the stored objects that may be stored.
|
||||
* If mutable objects are stored in the pair, then the pair itself effectively becomes mutable.</p>
|
||||
*
|
||||
* @param <L> the left element type
|
||||
* @param <R> the right element type
|
||||
*
|
||||
* @since Lang 3.0
|
||||
* @version $Id: Pair.java 1142401 2011-07-03 08:30:12Z bayard $
|
||||
*/
|
||||
public abstract class Pair<L, R> implements Map.Entry<L, R>, Comparable<Pair<L, R>>, Serializable {
|
||||
|
||||
/** Serialization version */
|
||||
private static final long serialVersionUID = 4954918890077093841L;
|
||||
|
||||
/**
|
||||
* <p>Obtains an immutable pair of from two objects inferring the generic types.</p>
|
||||
*
|
||||
* <p>This factory allows the pair to be created using inference to
|
||||
* obtain the generic types.</p>
|
||||
*
|
||||
* @param <L> the left element type
|
||||
* @param <R> the right element type
|
||||
* @param left the left element, may be null
|
||||
* @param right the right element, may be null
|
||||
* @return a pair formed from the two parameters, not null
|
||||
*/
|
||||
public static <L, R> Pair<L, R> of(L left, R right) {
|
||||
return new ImmutablePair<L, R>(left, right);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Gets the left element from this pair.</p>
|
||||
*
|
||||
* <p>When treated as a key-value pair, this is the key.</p>
|
||||
*
|
||||
* @return the left element, may be null
|
||||
*/
|
||||
public abstract L getLeft();
|
||||
|
||||
/**
|
||||
* <p>Gets the right element from this pair.</p>
|
||||
*
|
||||
* <p>When treated as a key-value pair, this is the value.</p>
|
||||
*
|
||||
* @return the right element, may be null
|
||||
*/
|
||||
public abstract R getRight();
|
||||
|
||||
/**
|
||||
* <p>Gets the key from this pair.</p>
|
||||
*
|
||||
* <p>This method implements the {@code Map.Entry} interface returning the
|
||||
* left element as the key.</p>
|
||||
*
|
||||
* @return the left element as the key, may be null
|
||||
*/
|
||||
public final L getKey() {
|
||||
return getLeft();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Gets the value from this pair.</p>
|
||||
*
|
||||
* <p>This method implements the {@code Map.Entry} interface returning the
|
||||
* right element as the value.</p>
|
||||
*
|
||||
* @return the right element as the value, may be null
|
||||
*/
|
||||
public R getValue() {
|
||||
return getRight();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Compares the pair based on the left element followed by the right element.
|
||||
* The types must be {@code Comparable}.</p>
|
||||
*
|
||||
* @param other the other pair, not null
|
||||
* @return negative if this is less, zero if equal, positive if greater
|
||||
*/
|
||||
public int compareTo(Pair<L, R> other) {
|
||||
return new CompareToBuilder().append(getLeft(), other.getLeft())
|
||||
.append(getRight(), other.getRight()).toComparison();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Compares this pair to another based on the two elements.</p>
|
||||
*
|
||||
* @param obj the object to compare to, null returns false
|
||||
* @return true if the elements of the pair are equal
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
if (obj instanceof Map.Entry<?, ?>) {
|
||||
Map.Entry<?, ?> other = (Map.Entry<?, ?>) obj;
|
||||
return ObjectUtils.equals(getKey(), other.getKey())
|
||||
&& ObjectUtils.equals(getValue(), other.getValue());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns a suitable hash code.
|
||||
* The hash code follows the definition in {@code Map.Entry}.</p>
|
||||
*
|
||||
* @return the hash code
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
// see Map.Entry API specification
|
||||
return (getKey() == null ? 0 : getKey().hashCode()) ^
|
||||
(getValue() == null ? 0 : getValue().hashCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns a String representation of this pair using the format {@code ($left,$right)}.</p>
|
||||
*
|
||||
* @return a string describing this object, not null
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return new StringBuilder().append('(').append(getLeft()).append(',').append(getRight()).append(')').toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Formats the receiver using the given format.</p>
|
||||
*
|
||||
* <p>This uses {@link java.util.Formattable} to perform the formatting. Two variables may
|
||||
* be used to embed the left and right elements. Use {@code %1$s} for the left
|
||||
* element (key) and {@code %2$s} for the right element (value).
|
||||
* The default format used by {@code toString()} is {@code (%1$s,%2$s)}.</p>
|
||||
*
|
||||
* @param format the format string, optionally containing {@code %1$s} and {@code %2$s}, not null
|
||||
* @return the formatted string, not null
|
||||
*/
|
||||
public String toString(String format) {
|
||||
return String.format(format, getLeft(), getRight());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<html>
|
||||
<body>
|
||||
Tuple classes, starting with a Pair class in version 3.0.
|
||||
@since 3.0
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,223 +0,0 @@
|
|||
package android.app;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.res.CompatibilityInfo;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.view.Display;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.Map;
|
||||
|
||||
import de.robv.android.xposed.XSharedPreferences;
|
||||
import de.robv.android.xposed.XposedBridge;
|
||||
|
||||
import static de.robv.android.xposed.XposedHelpers.findClass;
|
||||
import static de.robv.android.xposed.XposedHelpers.findFieldIfExists;
|
||||
import static de.robv.android.xposed.XposedHelpers.findMethodExactIfExists;
|
||||
import static de.robv.android.xposed.XposedHelpers.getObjectField;
|
||||
import static de.robv.android.xposed.XposedHelpers.newInstance;
|
||||
import static de.robv.android.xposed.XposedHelpers.setFloatField;
|
||||
|
||||
/**
|
||||
* Contains various methods for information about the current app.
|
||||
*
|
||||
* <p>For historical reasons, this class is in the {@code android.app} package. It can't be moved
|
||||
* without breaking compatibility with existing modules.
|
||||
*/
|
||||
public final class AndroidAppHelper {
|
||||
private AndroidAppHelper() {}
|
||||
|
||||
private static final Class<?> CLASS_RESOURCES_KEY;
|
||||
private static final boolean HAS_IS_THEMEABLE;
|
||||
private static final boolean HAS_THEME_CONFIG_PARAMETER;
|
||||
|
||||
static {
|
||||
CLASS_RESOURCES_KEY = (Build.VERSION.SDK_INT < 19) ?
|
||||
findClass("android.app.ActivityThread$ResourcesKey", null)
|
||||
: findClass("android.content.res.ResourcesKey", null);
|
||||
|
||||
HAS_IS_THEMEABLE = findFieldIfExists(CLASS_RESOURCES_KEY, "mIsThemeable") != null;
|
||||
HAS_THEME_CONFIG_PARAMETER = HAS_IS_THEMEABLE && Build.VERSION.SDK_INT >= 21
|
||||
&& findMethodExactIfExists("android.app.ResourcesManager", null, "getThemeConfig") != null;
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
private static Map<Object, WeakReference> getResourcesMap(ActivityThread activityThread) {
|
||||
if (Build.VERSION.SDK_INT >= 24) {
|
||||
Object resourcesManager = getObjectField(activityThread, "mResourcesManager");
|
||||
return (Map) getObjectField(resourcesManager, "mResourceImpls");
|
||||
} else if (Build.VERSION.SDK_INT >= 19) {
|
||||
Object resourcesManager = getObjectField(activityThread, "mResourcesManager");
|
||||
return (Map) getObjectField(resourcesManager, "mActiveResources");
|
||||
} else {
|
||||
return (Map) getObjectField(activityThread, "mActiveResources");
|
||||
}
|
||||
}
|
||||
|
||||
/* For SDK 15 & 16 */
|
||||
private static Object createResourcesKey(String resDir, float scale) {
|
||||
try {
|
||||
if (HAS_IS_THEMEABLE)
|
||||
return newInstance(CLASS_RESOURCES_KEY, resDir, scale, false);
|
||||
else
|
||||
return newInstance(CLASS_RESOURCES_KEY, resDir, scale);
|
||||
} catch (Throwable t) {
|
||||
XposedBridge.log(t);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/* For SDK 17 & 18 & 23 */
|
||||
private static Object createResourcesKey(String resDir, int displayId, Configuration overrideConfiguration, float scale) {
|
||||
try {
|
||||
if (HAS_THEME_CONFIG_PARAMETER)
|
||||
return newInstance(CLASS_RESOURCES_KEY, resDir, displayId, overrideConfiguration, scale, false, null);
|
||||
else if (HAS_IS_THEMEABLE)
|
||||
return newInstance(CLASS_RESOURCES_KEY, resDir, displayId, overrideConfiguration, scale, false);
|
||||
else
|
||||
return newInstance(CLASS_RESOURCES_KEY, resDir, displayId, overrideConfiguration, scale);
|
||||
} catch (Throwable t) {
|
||||
XposedBridge.log(t);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/* For SDK 19 - 22 */
|
||||
private static Object createResourcesKey(String resDir, int displayId, Configuration overrideConfiguration, float scale, IBinder token) {
|
||||
try {
|
||||
if (HAS_THEME_CONFIG_PARAMETER)
|
||||
return newInstance(CLASS_RESOURCES_KEY, resDir, displayId, overrideConfiguration, scale, false, null, token);
|
||||
else if (HAS_IS_THEMEABLE)
|
||||
return newInstance(CLASS_RESOURCES_KEY, resDir, displayId, overrideConfiguration, scale, false, token);
|
||||
else
|
||||
return newInstance(CLASS_RESOURCES_KEY, resDir, displayId, overrideConfiguration, scale, token);
|
||||
} catch (Throwable t) {
|
||||
XposedBridge.log(t);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/* For SDK 24+ */
|
||||
private static Object createResourcesKey(String resDir, String[] splitResDirs, String[] overlayDirs, String[] libDirs, int displayId, Configuration overrideConfiguration, CompatibilityInfo compatInfo) {
|
||||
try {
|
||||
return newInstance(CLASS_RESOURCES_KEY, resDir, splitResDirs, overlayDirs, libDirs, displayId, overrideConfiguration, compatInfo);
|
||||
} catch (Throwable t) {
|
||||
XposedBridge.log(t);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public static void addActiveResource(String resDir, float scale, boolean isThemeable, Resources resources) {
|
||||
addActiveResource(resDir, resources);
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public static void addActiveResource(String resDir, Resources resources) {
|
||||
ActivityThread thread = ActivityThread.currentActivityThread();
|
||||
if (thread == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object resourcesKey;
|
||||
if (Build.VERSION.SDK_INT >= 24) {
|
||||
CompatibilityInfo compatInfo = (CompatibilityInfo) newInstance(CompatibilityInfo.class);
|
||||
setFloatField(compatInfo, "applicationScale", resources.hashCode());
|
||||
resourcesKey = createResourcesKey(resDir, null, null, null, Display.DEFAULT_DISPLAY, null, compatInfo);
|
||||
} else if (Build.VERSION.SDK_INT == 23) {
|
||||
resourcesKey = createResourcesKey(resDir, Display.DEFAULT_DISPLAY, null, resources.hashCode());
|
||||
} else if (Build.VERSION.SDK_INT >= 19) {
|
||||
resourcesKey = createResourcesKey(resDir, Display.DEFAULT_DISPLAY, null, resources.hashCode(), null);
|
||||
} else if (Build.VERSION.SDK_INT >= 17) {
|
||||
resourcesKey = createResourcesKey(resDir, Display.DEFAULT_DISPLAY, null, resources.hashCode());
|
||||
} else {
|
||||
resourcesKey = createResourcesKey(resDir, resources.hashCode());
|
||||
}
|
||||
|
||||
if (resourcesKey != null) {
|
||||
if (Build.VERSION.SDK_INT >= 24) {
|
||||
Object resImpl = getObjectField(resources, "mResourcesImpl");
|
||||
getResourcesMap(thread).put(resourcesKey, new WeakReference<>(resImpl));
|
||||
} else {
|
||||
getResourcesMap(thread).put(resourcesKey, new WeakReference<>(resources));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the current process. It's usually the same as the main package name.
|
||||
*/
|
||||
public static String currentProcessName() {
|
||||
String processName = ActivityThread.currentPackageName();
|
||||
if (processName == null)
|
||||
return "android";
|
||||
return processName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns information about the main application in the current process.
|
||||
*
|
||||
* <p>In a few cases, multiple apps might run in the same process, e.g. the SystemUI and the
|
||||
* Keyguard which both have {@code android:process="com.android.systemui"} set in their
|
||||
* manifest. In those cases, the first application that was initialized will be returned.
|
||||
*/
|
||||
public static ApplicationInfo currentApplicationInfo() {
|
||||
ActivityThread am = ActivityThread.currentActivityThread();
|
||||
if (am == null)
|
||||
return null;
|
||||
|
||||
Object boundApplication = getObjectField(am, "mBoundApplication");
|
||||
if (boundApplication == null)
|
||||
return null;
|
||||
|
||||
return (ApplicationInfo) getObjectField(boundApplication, "appInfo");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Android package name of the main application in the current process.
|
||||
*
|
||||
* <p>In a few cases, multiple apps might run in the same process, e.g. the SystemUI and the
|
||||
* Keyguard which both have {@code android:process="com.android.systemui"} set in their
|
||||
* manifest. In those cases, the first application that was initialized will be returned.
|
||||
*/
|
||||
public static String currentPackageName() {
|
||||
ApplicationInfo ai = currentApplicationInfo();
|
||||
return (ai != null) ? ai.packageName : "android";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the main {@link android.app.Application} object in the current process.
|
||||
*
|
||||
* <p>In a few cases, multiple apps might run in the same process, e.g. the SystemUI and the
|
||||
* Keyguard which both have {@code android:process="com.android.systemui"} set in their
|
||||
* manifest. In those cases, the first application that was initialized will be returned.
|
||||
*/
|
||||
public static Application currentApplication() {
|
||||
return ActivityThread.currentApplication();
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link XSharedPreferences} instead. */
|
||||
@SuppressWarnings("UnusedParameters")
|
||||
@Deprecated
|
||||
public static SharedPreferences getSharedPreferencesForPackage(String packageName, String prefFileName, int mode) {
|
||||
return new XSharedPreferences(packageName, prefFileName);
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link XSharedPreferences} instead. */
|
||||
@Deprecated
|
||||
public static SharedPreferences getDefaultSharedPreferencesForPackage(String packageName) {
|
||||
return new XSharedPreferences(packageName);
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link XSharedPreferences#reload} instead. */
|
||||
@Deprecated
|
||||
public static void reloadSharedPreferencesIfNeeded(SharedPreferences pref) {
|
||||
if (pref instanceof XSharedPreferences) {
|
||||
((XSharedPreferences) pref).reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
/**
|
||||
* Contains {@link android.app.AndroidAppHelper} with various methods for information about the current app.
|
||||
*/
|
||||
package android.app;
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
package android.content.res;
|
||||
|
||||
import android.app.AndroidAppHelper;
|
||||
import android.util.DisplayMetrics;
|
||||
|
||||
import de.robv.android.xposed.IXposedHookInitPackageResources;
|
||||
import de.robv.android.xposed.IXposedHookZygoteInit;
|
||||
import de.robv.android.xposed.IXposedHookZygoteInit.StartupParam;
|
||||
import de.robv.android.xposed.callbacks.XC_InitPackageResources.InitPackageResourcesParam;
|
||||
|
||||
/**
|
||||
* Provides access to resources from a certain path (usually the module's own path).
|
||||
*/
|
||||
public class XModuleResources extends Resources {
|
||||
private XModuleResources(AssetManager assets, DisplayMetrics metrics, Configuration config) {
|
||||
super(assets, metrics, config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* <p>This is usually called with {@link StartupParam#modulePath} from
|
||||
* {@link IXposedHookZygoteInit#initZygote} and {@link InitPackageResourcesParam#res} from
|
||||
* {@link IXposedHookInitPackageResources#handleInitPackageResources} (or {@code null} for
|
||||
* system-wide replacements).
|
||||
*
|
||||
* @param path The path to the APK from which the resources should be loaded.
|
||||
* @param origRes The resources object from which settings like the display metrics and the
|
||||
* configuration should be copied. May be {@code null}.
|
||||
*/
|
||||
public static XModuleResources createInstance(String path, XResources origRes) {
|
||||
if (path == null)
|
||||
throw new IllegalArgumentException("path must not be null");
|
||||
|
||||
AssetManager assets = new AssetManager();
|
||||
assets.addAssetPath(path);
|
||||
|
||||
XModuleResources res;
|
||||
if (origRes != null)
|
||||
res = new XModuleResources(assets, origRes.getDisplayMetrics(), origRes.getConfiguration());
|
||||
else
|
||||
res = new XModuleResources(assets, null, null);
|
||||
|
||||
AndroidAppHelper.addActiveResource(path, res);
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an {@link XResForwarder} instance that forwards requests to {@code id} in this resource.
|
||||
*/
|
||||
public XResForwarder fwd(int id) {
|
||||
return new XResForwarder(this, id);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
package android.content.res;
|
||||
|
||||
/**
|
||||
* Instances of this class can be used for {@link XResources#setReplacement(String, String, String, Object)}
|
||||
* and its variants. They forward the resource request to a different {@link android.content.res.Resources}
|
||||
* instance with a possibly different ID.
|
||||
*
|
||||
* <p>Usually, instances aren't created directly but via {@link XModuleResources#fwd}.
|
||||
*/
|
||||
public class XResForwarder {
|
||||
private final Resources res;
|
||||
private final int id;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param res The target {@link android.content.res.Resources} instance to forward requests to.
|
||||
* @param id The target resource ID.
|
||||
*/
|
||||
public XResForwarder(Resources res, int id) {
|
||||
this.res = res;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
/** Returns the target {@link android.content.res.Resources} instance. */
|
||||
public Resources getResources() {
|
||||
return res;
|
||||
}
|
||||
|
||||
/** Returns the target resource ID. */
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,4 +0,0 @@
|
|||
/**
|
||||
* Contains classes that are required for replacing resources.
|
||||
*/
|
||||
package android.content.res;
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
package com.elderdrivers.riru.edxp.config;
|
||||
|
||||
import com.elderdrivers.riru.edxp.hook.HookProvider;
|
||||
|
||||
public class EdXpConfigGlobal {
|
||||
|
||||
public static volatile EdxpConfig sConfig;
|
||||
public static volatile HookProvider sHookProvider;
|
||||
|
||||
public static EdxpConfig getConfig() {
|
||||
if (sConfig == null) {
|
||||
throw new IllegalArgumentException("sConfig should not be null.");
|
||||
}
|
||||
return sConfig;
|
||||
}
|
||||
|
||||
public static HookProvider getHookProvider() {
|
||||
if (sHookProvider == null) {
|
||||
throw new IllegalArgumentException("sHookProvider should not be null.");
|
||||
}
|
||||
return sHookProvider;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
package com.elderdrivers.riru.edxp.config;
|
||||
|
||||
public interface EdxpConfig {
|
||||
|
||||
String getConfigPath(String suffix);
|
||||
|
||||
String getDataPathPrefix();
|
||||
|
||||
String getInstallerPackageName();
|
||||
|
||||
String getLibSandHookName();
|
||||
|
||||
boolean isNoModuleLogEnabled();
|
||||
|
||||
boolean isResourcesHookEnabled();
|
||||
|
||||
boolean isBlackWhiteListMode();
|
||||
|
||||
String getModulesList();
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
package com.elderdrivers.riru.edxp.hook;
|
||||
|
||||
import java.lang.reflect.Member;
|
||||
|
||||
import de.robv.android.xposed.XposedBridge;
|
||||
|
||||
public interface HookProvider {
|
||||
|
||||
void hookMethod(Member method, XposedBridge.AdditionalHookInfo additionalInfo);
|
||||
|
||||
void unhookMethod(Member method);
|
||||
|
||||
Object invokeOriginalMethod(Member method, long methodId, Object thisObject, Object[] args) throws Throwable;
|
||||
|
||||
Member findMethodNative(Member hookMethod);
|
||||
|
||||
void deoptMethods(String packageName, ClassLoader classLoader);
|
||||
|
||||
long getMethodId(Member member);
|
||||
|
||||
Object findMethodNative(Class clazz, String methodName, String methodSig);
|
||||
|
||||
void deoptMethodNative(Object method);
|
||||
|
||||
boolean initXResourcesNative();
|
||||
|
||||
boolean removeFinalFlagNative(Class clazz);
|
||||
|
||||
boolean methodHooked(Member target);
|
||||
|
||||
}
|
||||
|
|
@ -1,120 +0,0 @@
|
|||
package com.elderdrivers.riru.edxp.util;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
import pxb.android.axml.AxmlReader;
|
||||
import pxb.android.axml.AxmlVisitor;
|
||||
import pxb.android.axml.NodeVisitor;
|
||||
|
||||
public class MetaDataReader {
|
||||
private final HashMap<String, Object> metaData = new HashMap<>();
|
||||
|
||||
public static Map<String, Object> getMetaData(File apk) throws IOException {
|
||||
return new MetaDataReader(apk).metaData;
|
||||
}
|
||||
|
||||
private MetaDataReader(File apk) throws IOException {
|
||||
try(JarFile zip = new JarFile(apk)) {
|
||||
InputStream is = zip.getInputStream(zip.getEntry("AndroidManifest.xml"));
|
||||
byte[] bytes = getBytesFromInputStream(is);
|
||||
AxmlReader reader = new AxmlReader(bytes);
|
||||
reader.accept(new AxmlVisitor() {
|
||||
@Override
|
||||
public NodeVisitor child(String ns, String name) {
|
||||
NodeVisitor child = super.child(ns, name);
|
||||
return new ManifestTagVisitor(child);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] getBytesFromInputStream(InputStream inputStream) throws IOException {
|
||||
try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
|
||||
byte[] b = new byte[1024];
|
||||
int n;
|
||||
while ((n = inputStream.read(b)) != -1) {
|
||||
bos.write(b, 0, n);
|
||||
}
|
||||
byte[] data = bos.toByteArray();
|
||||
return data;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private class ManifestTagVisitor extends NodeVisitor {
|
||||
public ManifestTagVisitor(NodeVisitor child) {
|
||||
super(child);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NodeVisitor child(String ns, String name) {
|
||||
NodeVisitor child = super.child(ns, name);
|
||||
if ("application".equals(name)) {
|
||||
return new ApplicationTagVisitor(child);
|
||||
}
|
||||
return child;
|
||||
}
|
||||
|
||||
private class ApplicationTagVisitor extends NodeVisitor {
|
||||
public ApplicationTagVisitor(NodeVisitor child) {
|
||||
super(child);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NodeVisitor child(String ns, String name) {
|
||||
NodeVisitor child = super.child(ns, name);
|
||||
if("meta-data".equals(name)) {
|
||||
return new MetaDataVisitor(child);
|
||||
}
|
||||
return child;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class MetaDataVisitor extends NodeVisitor {
|
||||
public String name = null;
|
||||
public Object value = null;
|
||||
public MetaDataVisitor(NodeVisitor child) {
|
||||
super(child);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attr(String ns, String name, int resourceId, int type, Object obj) {
|
||||
if (type == 3 && "name".equals(name)) {
|
||||
this.name = (String)obj;
|
||||
}
|
||||
if ("value".equals(name) ) {
|
||||
value = obj;
|
||||
}
|
||||
super.attr(ns, name, resourceId, type, obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void end() {
|
||||
if(name != null && value != null) {
|
||||
metaData.put(name, value);
|
||||
}
|
||||
super.end();
|
||||
}
|
||||
}
|
||||
|
||||
public static int extractIntPart(String str) {
|
||||
int result = 0, length = str.length();
|
||||
for (int offset = 0; offset < length; offset++) {
|
||||
char c = str.charAt(offset);
|
||||
if ('0' <= c && c <= '9')
|
||||
result = result * 10 + (c - '0');
|
||||
else
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,235 +0,0 @@
|
|||
package de.robv.android.xposed;
|
||||
|
||||
import android.os.Environment;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.security.DigestException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.zip.Adler32;
|
||||
|
||||
import static de.robv.android.xposed.XposedHelpers.inputStreamToByteArray;
|
||||
|
||||
/**
|
||||
* Helper class which can create a very simple .dex file, containing only a class definition
|
||||
* with a super class (no methods, fields, ...).
|
||||
*/
|
||||
/*package*/ class DexCreator {
|
||||
public static File DALVIK_CACHE = new File(Environment.getDataDirectory(), "dalvik-cache");
|
||||
|
||||
/** Returns the default dex file name for the class. */
|
||||
public static File getDefaultFile(String childClz) {
|
||||
return new File(DALVIK_CACHE, "xposed_" + childClz.substring(childClz.lastIndexOf('.') + 1) + ".dex");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates (or returns) the path to a dex file which defines the superclass of {@clz} as extending
|
||||
* {@code realSuperClz}, which by itself must extend {@code topClz}.
|
||||
*/
|
||||
public static File ensure(String clz, Class<?> realSuperClz, Class<?> topClz) throws IOException {
|
||||
if (!topClz.isAssignableFrom(realSuperClz)) {
|
||||
throw new ClassCastException("Cannot initialize " + clz + " because " + realSuperClz + " does not extend " + topClz);
|
||||
}
|
||||
|
||||
try {
|
||||
return ensure("xposed.dummy." + clz + "SuperClass", realSuperClz);
|
||||
} catch (IOException e) {
|
||||
throw new IOException("Failed to create a superclass for " + clz, e);
|
||||
}
|
||||
}
|
||||
|
||||
/** Like {@link #ensure(File, String, String)}, just for the default dex file name. */
|
||||
public static File ensure(String childClz, Class<?> superClz) throws IOException {
|
||||
return ensure(getDefaultFile(childClz), childClz, superClz.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure that the given file is a simple dex file containing the given classes.
|
||||
* Creates the file if that's not the case.
|
||||
*/
|
||||
public static File ensure(File file, String childClz, String superClz) throws IOException {
|
||||
// First check if a valid file exists.
|
||||
try {
|
||||
byte[] dex = inputStreamToByteArray(new FileInputStream(file));
|
||||
if (matches(dex, childClz, superClz)) {
|
||||
return file;
|
||||
} else {
|
||||
file.delete();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
file.delete();
|
||||
}
|
||||
|
||||
// If not, create a new dex file.
|
||||
byte[] dex = create(childClz, superClz);
|
||||
FileOutputStream fos = new FileOutputStream(file);
|
||||
fos.write(dex);
|
||||
fos.close();
|
||||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the Dex file fits to the class names.
|
||||
* Assumes that the file has been created with this class.
|
||||
*/
|
||||
public static boolean matches(byte[] dex, String childClz, String superClz) throws IOException {
|
||||
boolean childFirst = childClz.compareTo(superClz) < 0;
|
||||
byte[] childBytes = stringToBytes("L" + childClz.replace('.', '/') + ";");
|
||||
byte[] superBytes = stringToBytes("L" + superClz.replace('.', '/') + ";");
|
||||
|
||||
int pos = 0xa0;
|
||||
if (pos + childBytes.length + superBytes.length >= dex.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (byte b : childFirst ? childBytes : superBytes) {
|
||||
if (dex[pos++] != b) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (byte b : childFirst ? superBytes: childBytes) {
|
||||
if (dex[pos++] != b) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Creates the byte array for the dex file. */
|
||||
public static byte[] create(String childClz, String superClz) throws IOException {
|
||||
boolean childFirst = childClz.compareTo(superClz) < 0;
|
||||
byte[] childBytes = stringToBytes("L" + childClz.replace('.', '/') + ";");
|
||||
byte[] superBytes = stringToBytes("L" + superClz.replace('.', '/') + ";");
|
||||
int stringsSize = childBytes.length + superBytes.length;
|
||||
int padding = -stringsSize & 3;
|
||||
stringsSize += padding;
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
|
||||
// header
|
||||
out.write("dex\n035\0".getBytes()); // magic
|
||||
out.write(new byte[24]); // placeholder for checksum and signature
|
||||
writeInt(out, 0xfc + stringsSize); // file size
|
||||
writeInt(out, 0x70); // header size
|
||||
writeInt(out, 0x12345678); // endian constant
|
||||
writeInt(out, 0); // link size
|
||||
writeInt(out, 0); // link offset
|
||||
writeInt(out, 0xa4 + stringsSize); // map offset
|
||||
writeInt(out, 2); // strings count
|
||||
writeInt(out, 0x70); // strings offset
|
||||
writeInt(out, 2); // types count
|
||||
writeInt(out, 0x78); // types offset
|
||||
writeInt(out, 0); // prototypes count
|
||||
writeInt(out, 0); // prototypes offset
|
||||
writeInt(out, 0); // fields count
|
||||
writeInt(out, 0); // fields offset
|
||||
writeInt(out, 0); // methods count
|
||||
writeInt(out, 0); // methods offset
|
||||
writeInt(out, 1); // classes count
|
||||
writeInt(out, 0x80); // classes offset
|
||||
writeInt(out, 0x5c + stringsSize); // data size
|
||||
writeInt(out, 0xa0); // data offset
|
||||
|
||||
// string map
|
||||
writeInt(out, 0xa0);
|
||||
writeInt(out, 0xa0 + (childFirst ? childBytes.length : superBytes.length));
|
||||
|
||||
// types
|
||||
writeInt(out, 0); // first type = first string
|
||||
writeInt(out, 1); // second type = second string
|
||||
|
||||
// class definitions
|
||||
writeInt(out, childFirst ? 0 : 1); // class to define = child type
|
||||
writeInt(out, 1); // access flags = public
|
||||
writeInt(out, childFirst ? 1 : 0); // super class = super type
|
||||
writeInt(out, 0); // no interface
|
||||
writeInt(out, -1); // no source file
|
||||
writeInt(out, 0); // no annotations
|
||||
writeInt(out, 0); // no class data
|
||||
writeInt(out, 0); // no static values
|
||||
|
||||
// string data
|
||||
out.write(childFirst ? childBytes : superBytes);
|
||||
out.write(childFirst ? superBytes : childBytes);
|
||||
out.write(new byte[padding]);
|
||||
|
||||
// annotations
|
||||
writeInt(out, 0); // no items
|
||||
|
||||
// map
|
||||
writeInt(out, 7); // items count
|
||||
writeMapItem(out, 0, 1, 0); // header
|
||||
writeMapItem(out, 1, 2, 0x70); // strings
|
||||
writeMapItem(out, 2, 2, 0x78); // types
|
||||
writeMapItem(out, 6, 1, 0x80); // classes
|
||||
writeMapItem(out, 0x2002, 2, 0xa0); // string data
|
||||
writeMapItem(out, 0x1003, 1, 0xa0 + stringsSize); // annotations
|
||||
writeMapItem(out, 0x1000, 1, 0xa4 + stringsSize); // map list
|
||||
|
||||
byte[] buf = out.toByteArray();
|
||||
updateSignature(buf);
|
||||
updateChecksum(buf);
|
||||
return buf;
|
||||
}
|
||||
|
||||
private static void updateSignature(byte[] dex) {
|
||||
// Update SHA-1 signature
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-1");
|
||||
md.update(dex, 32, dex.length - 32);
|
||||
md.digest(dex, 12, 20);
|
||||
} catch (NoSuchAlgorithmException | DigestException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void updateChecksum(byte[] dex) {
|
||||
// Update Adler32 checksum
|
||||
Adler32 a32 = new Adler32();
|
||||
a32.update(dex, 12, dex.length - 12);
|
||||
int chksum = (int) a32.getValue();
|
||||
dex[8] = (byte) (chksum & 0xff);
|
||||
dex[9] = (byte) (chksum >> 8 & 0xff);
|
||||
dex[10] = (byte) (chksum >> 16 & 0xff);
|
||||
dex[11] = (byte) (chksum >> 24 & 0xff);
|
||||
}
|
||||
|
||||
private static void writeUleb128(OutputStream out, int value) throws IOException {
|
||||
while (value > 0x7f) {
|
||||
out.write((value & 0x7f) | 0x80);
|
||||
value >>>= 7;
|
||||
}
|
||||
out.write(value);
|
||||
}
|
||||
|
||||
private static void writeInt(OutputStream out, int value) throws IOException {
|
||||
out.write(value);
|
||||
out.write(value >> 8);
|
||||
out.write(value >> 16);
|
||||
out.write(value >> 24);
|
||||
}
|
||||
|
||||
private static void writeMapItem(OutputStream out, int type, int count, int offset) throws IOException {
|
||||
writeInt(out, type);
|
||||
writeInt(out, count);
|
||||
writeInt(out, offset);
|
||||
}
|
||||
|
||||
private static byte[] stringToBytes(String s) throws IOException {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
writeUleb128(bytes, s.length());
|
||||
// This isn't MUTF-8, but should be OK.
|
||||
bytes.write(s.getBytes("UTF-8"));
|
||||
bytes.write(0);
|
||||
return bytes.toByteArray();
|
||||
}
|
||||
|
||||
private DexCreator() {}
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
package de.robv.android.xposed;
|
||||
|
||||
public interface IModuleContext {
|
||||
|
||||
String getApkPath();
|
||||
}
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
package de.robv.android.xposed;
|
||||
|
||||
|
||||
/**
|
||||
* Hook the initialization of Java-based command-line tools (like pm).
|
||||
*
|
||||
* @hide Xposed no longer hooks command-line tools, therefore this interface shouldn't be
|
||||
* implemented anymore.
|
||||
*/
|
||||
public interface IXposedHookCmdInit extends IXposedMod {
|
||||
/**
|
||||
* Called very early during startup of a command-line tool.
|
||||
* @param startupParam Details about the module itself and the started process.
|
||||
* @throws Throwable Everything is caught, but it will prevent further initialization of the module.
|
||||
*/
|
||||
void initCmdApp(StartupParam startupParam) throws Throwable;
|
||||
|
||||
/** Data holder for {@link #initCmdApp}. */
|
||||
final class StartupParam {
|
||||
/*package*/ StartupParam() {}
|
||||
|
||||
/** The path to the module's APK. */
|
||||
public String modulePath;
|
||||
|
||||
/** The class name of the tools that the hook was invoked for. */
|
||||
public String startClassName;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
package de.robv.android.xposed;
|
||||
|
||||
import android.content.res.XResources;
|
||||
|
||||
import de.robv.android.xposed.callbacks.XC_InitPackageResources;
|
||||
import de.robv.android.xposed.callbacks.XC_InitPackageResources.InitPackageResourcesParam;
|
||||
|
||||
/**
|
||||
* Get notified when the resources for an app are initialized.
|
||||
* In {@link #handleInitPackageResources}, resource replacements can be created.
|
||||
*
|
||||
* <p>This interface should be implemented by the module's main class. Xposed will take care of
|
||||
* registering it as a callback automatically.
|
||||
*/
|
||||
public interface IXposedHookInitPackageResources extends IXposedMod {
|
||||
/**
|
||||
* This method is called when resources for an app are being initialized.
|
||||
* Modules can call special methods of the {@link XResources} class in order to replace resources.
|
||||
*
|
||||
* @param resparam Information about the resources.
|
||||
* @throws Throwable Everything the callback throws is caught and logged.
|
||||
*/
|
||||
void handleInitPackageResources(InitPackageResourcesParam resparam) throws Throwable;
|
||||
|
||||
/** @hide */
|
||||
final class Wrapper extends XC_InitPackageResources {
|
||||
private final IXposedHookInitPackageResources instance;
|
||||
private final String apkPath;
|
||||
|
||||
public Wrapper(IXposedHookInitPackageResources instance, String apkPath) {
|
||||
this.instance = instance;
|
||||
this.apkPath = apkPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleInitPackageResources(InitPackageResourcesParam resparam) throws Throwable {
|
||||
instance.handleInitPackageResources(resparam);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getApkPath() {
|
||||
return apkPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
package de.robv.android.xposed;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
import de.robv.android.xposed.callbacks.XC_LoadPackage;
|
||||
import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam;
|
||||
|
||||
/**
|
||||
* Get notified when an app ("Android package") is loaded.
|
||||
* This is especially useful to hook some app-specific methods.
|
||||
*
|
||||
* <p>This interface should be implemented by the module's main class. Xposed will take care of
|
||||
* registering it as a callback automatically.
|
||||
*/
|
||||
public interface IXposedHookLoadPackage extends IXposedMod {
|
||||
/**
|
||||
* This method is called when an app is loaded. It's called very early, even before
|
||||
* {@link Application#onCreate} is called.
|
||||
* Modules can set up their app-specific hooks here.
|
||||
*
|
||||
* @param lpparam Information about the app.
|
||||
* @throws Throwable Everything the callback throws is caught and logged.
|
||||
*/
|
||||
void handleLoadPackage(LoadPackageParam lpparam) throws Throwable;
|
||||
|
||||
/** @hide */
|
||||
final class Wrapper extends XC_LoadPackage {
|
||||
private final IXposedHookLoadPackage instance;
|
||||
private final String apkPath;
|
||||
|
||||
public Wrapper(IXposedHookLoadPackage instance, String apkPath) {
|
||||
this.instance = instance;
|
||||
this.apkPath = apkPath;
|
||||
}
|
||||
@Override
|
||||
public void handleLoadPackage(LoadPackageParam lpparam) throws Throwable {
|
||||
instance.handleLoadPackage(lpparam);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getApkPath() {
|
||||
return apkPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
package de.robv.android.xposed;
|
||||
|
||||
import de.robv.android.xposed.callbacks.XC_InitZygote;
|
||||
import de.robv.android.xposed.callbacks.XCallback;
|
||||
|
||||
/**
|
||||
* Hook the initialization of Zygote process(es), from which all the apps are forked.
|
||||
*
|
||||
* <p>Implement this interface in your module's main class in order to be notified when Android is
|
||||
* starting up. In {@link IXposedHookZygoteInit}, you can modify objects and place hooks that should
|
||||
* be applied for every app. Only the Android framework/system classes are available at that point
|
||||
* in time. Use {@code null} as class loader for {@link XposedHelpers#findAndHookMethod(String, ClassLoader, String, Object...)}
|
||||
* and its variants.
|
||||
*
|
||||
* <p>If you want to hook one/multiple specific apps, use {@link IXposedHookLoadPackage} instead.
|
||||
*/
|
||||
public interface IXposedHookZygoteInit extends IXposedMod {
|
||||
/**
|
||||
* Called very early during startup of Zygote.
|
||||
* @param startupParam Details about the module itself and the started process.
|
||||
* @throws Throwable everything is caught, but will prevent further initialization of the module.
|
||||
*/
|
||||
void initZygote(StartupParam startupParam) throws Throwable;
|
||||
|
||||
/** Data holder for {@link #initZygote}. */
|
||||
final class StartupParam extends XCallback.Param {
|
||||
/*package*/ StartupParam() {}
|
||||
|
||||
/**
|
||||
* @param callbacks
|
||||
* @hide
|
||||
*/
|
||||
public StartupParam(XposedBridge.CopyOnWriteSortedSet<? extends XCallback> callbacks) {
|
||||
super(callbacks);
|
||||
}
|
||||
|
||||
/** The path to the module's APK. */
|
||||
public String modulePath;
|
||||
|
||||
/**
|
||||
* Always {@code true} on 32-bit ROMs. On 64-bit, it's only {@code true} for the primary
|
||||
* process that starts the system_server.
|
||||
*/
|
||||
public boolean startsSystemServer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
final class Wrapper extends XC_InitZygote {
|
||||
private final IXposedHookZygoteInit instance;
|
||||
private final StartupParam startupParam;
|
||||
|
||||
public Wrapper(IXposedHookZygoteInit instance, StartupParam startupParam) {
|
||||
this.instance = instance;
|
||||
this.startupParam = startupParam;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initZygote(StartupParam startupParam) throws Throwable {
|
||||
// NOTE: parameter startupParam not used
|
||||
// cause startupParam info is generated and saved along with instance here
|
||||
instance.initZygote(this.startupParam);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getApkPath() {
|
||||
return startupParam.modulePath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
package de.robv.android.xposed;
|
||||
|
||||
/** Marker interface for Xposed modules. Cannot be implemented directly. */
|
||||
/* package */ interface IXposedMod {}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
package de.robv.android.xposed;
|
||||
|
||||
import java.lang.reflect.Member;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static de.robv.android.xposed.XposedBridge.hookMethodNative;
|
||||
|
||||
public final class PendingHooks {
|
||||
|
||||
// GuardedBy("PendingHooks.class")
|
||||
private static final ConcurrentHashMap<Class<?>, ConcurrentHashMap<Member, XposedBridge.AdditionalHookInfo>>
|
||||
sPendingHooks = new ConcurrentHashMap<>();
|
||||
|
||||
public synchronized static void hookPendingMethod(Class<?> clazz) {
|
||||
if (sPendingHooks.containsKey(clazz)) {
|
||||
for (Map.Entry<Member, XposedBridge.AdditionalHookInfo> hook : sPendingHooks.get(clazz).entrySet()) {
|
||||
hookMethodNative(hook.getKey(), clazz, 0, hook.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized static void recordPendingMethod(Member hookMethod,
|
||||
XposedBridge.AdditionalHookInfo additionalInfo) {
|
||||
ConcurrentHashMap<Member, XposedBridge.AdditionalHookInfo> pending =
|
||||
sPendingHooks.computeIfAbsent(hookMethod.getDeclaringClass(),
|
||||
new Function<Class<?>, ConcurrentHashMap<Member, XposedBridge.AdditionalHookInfo>>() {
|
||||
@Override
|
||||
public ConcurrentHashMap<Member, XposedBridge.AdditionalHookInfo> apply(Class<?> aClass) {
|
||||
return new ConcurrentHashMap<>();
|
||||
}
|
||||
});
|
||||
|
||||
pending.put(hookMethod, additionalInfo);
|
||||
recordPendingMethodNative(hookMethod.getDeclaringClass());
|
||||
}
|
||||
|
||||
public synchronized void cleanUp() {
|
||||
sPendingHooks.clear();
|
||||
}
|
||||
|
||||
private static native void recordPendingMethodNative(Class clazz);
|
||||
}
|
||||
|
|
@ -1,118 +0,0 @@
|
|||
package de.robv.android.xposed;
|
||||
|
||||
import android.os.SELinux;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import de.robv.android.xposed.services.BaseService;
|
||||
import de.robv.android.xposed.services.BinderService;
|
||||
import de.robv.android.xposed.services.DirectAccessService;
|
||||
import de.robv.android.xposed.services.ZygoteService;
|
||||
|
||||
/**
|
||||
* A helper to work with (or without) SELinux, abstracting much of its big complexity.
|
||||
*/
|
||||
public final class SELinuxHelper {
|
||||
private SELinuxHelper() {}
|
||||
|
||||
/**
|
||||
* Determines whether SELinux is disabled or enabled.
|
||||
*
|
||||
* @return A boolean indicating whether SELinux is enabled.
|
||||
*/
|
||||
public static boolean isSELinuxEnabled() {
|
||||
return sIsSELinuxEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether SELinux is permissive or enforcing.
|
||||
*
|
||||
* @return A boolean indicating whether SELinux is enforcing.
|
||||
*/
|
||||
public static boolean isSELinuxEnforced() {
|
||||
if (!sIsSELinuxEnabled) {
|
||||
return false;
|
||||
}
|
||||
boolean result = false;
|
||||
final File SELINUX_STATUS_FILE = new File("/sys/fs/selinux/enforce");
|
||||
if (SELINUX_STATUS_FILE.exists()) {
|
||||
try {
|
||||
FileInputStream fis = new FileInputStream(SELINUX_STATUS_FILE);
|
||||
int status = fis.read();
|
||||
switch (status) {
|
||||
case 49:
|
||||
result = true;
|
||||
break;
|
||||
case 48:
|
||||
result = false;
|
||||
break;
|
||||
default:
|
||||
XposedBridge.log("Unexpected byte " + status + " in /sys/fs/selinux/enforce");
|
||||
}
|
||||
fis.close();
|
||||
} catch (IOException e) {
|
||||
if (e.getMessage().contains("Permission denied")) {
|
||||
result = true;
|
||||
} else {
|
||||
XposedBridge.log("Failed to read SELinux status: " + e.getMessage());
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the security context of the current process.
|
||||
*
|
||||
* @return A String representing the security context of the current process.
|
||||
*/
|
||||
public static String getContext() {
|
||||
return sIsSELinuxEnabled ? SELinux.getContext() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the service to be used when accessing files in {@code /data/data/*}.
|
||||
*
|
||||
* <p class="caution"><strong>IMPORTANT:</strong> If you call this from the Zygote process,
|
||||
* don't re-use the result in different process!
|
||||
*
|
||||
* @return An instance of the service.
|
||||
*/
|
||||
public static BaseService getAppDataFileService() {
|
||||
if (sServiceAppDataFile != null)
|
||||
return sServiceAppDataFile;
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// TODO: SELinux status
|
||||
private static boolean sIsSELinuxEnabled = false;
|
||||
private static BaseService sServiceAppDataFile = new DirectAccessService(); // ed: initialized directly
|
||||
|
||||
/*package*/ public static void initOnce() {
|
||||
// ed: we assume all selinux policies have been added lively using magiskpolicy
|
||||
try {
|
||||
sIsSELinuxEnabled = SELinux.isSELinuxEnabled();
|
||||
} catch (NoClassDefFoundError ignored) {}
|
||||
}
|
||||
|
||||
/*package*/ static void initForProcess(String packageName) {
|
||||
// ed: sServiceAppDataFile has been initialized with default value
|
||||
// if (sIsSELinuxEnabled) {
|
||||
// if (packageName == null) { // Zygote
|
||||
// sServiceAppDataFile = new ZygoteService();
|
||||
// } else if (packageName.equals("android")) { //system_server
|
||||
// sServiceAppDataFile = BinderService.getService(BinderService.TARGET_APP);
|
||||
// } else { // app
|
||||
// sServiceAppDataFile = new DirectAccessService();
|
||||
// }
|
||||
// } else {
|
||||
// sServiceAppDataFile = new DirectAccessService();
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,168 +0,0 @@
|
|||
package de.robv.android.xposed;
|
||||
|
||||
import java.lang.reflect.Member;
|
||||
|
||||
import de.robv.android.xposed.callbacks.IXUnhook;
|
||||
import de.robv.android.xposed.callbacks.XCallback;
|
||||
|
||||
/**
|
||||
* Callback class for method hooks.
|
||||
*
|
||||
* <p>Usually, anonymous subclasses of this class are created which override
|
||||
* {@link #beforeHookedMethod} and/or {@link #afterHookedMethod}.
|
||||
*/
|
||||
public abstract class XC_MethodHook extends XCallback {
|
||||
/**
|
||||
* Creates a new callback with default priority.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public XC_MethodHook() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new callback with a specific priority.
|
||||
*
|
||||
* <p class="note">Note that {@link #afterHookedMethod} will be called in reversed order, i.e.
|
||||
* the callback with the highest priority will be called last. This way, the callback has the
|
||||
* final control over the return value. {@link #beforeHookedMethod} is called as usual, i.e.
|
||||
* highest priority first.
|
||||
*
|
||||
* @param priority See {@link XCallback#priority}.
|
||||
*/
|
||||
public XC_MethodHook(int priority) {
|
||||
super(priority);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called before the invocation of the method.
|
||||
*
|
||||
* <p>You can use {@link MethodHookParam#setResult} and {@link MethodHookParam#setThrowable}
|
||||
* to prevent the original method from being called.
|
||||
*
|
||||
* <p>Note that implementations shouldn't call {@code super(param)}, it's not necessary.
|
||||
*
|
||||
* @param param Information about the method call.
|
||||
* @throws Throwable Everything the callback throws is caught and logged.
|
||||
*/
|
||||
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {}
|
||||
|
||||
public void callBeforeHookedMethod(MethodHookParam param) throws Throwable {
|
||||
beforeHookedMethod(param);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after the invocation of the method.
|
||||
*
|
||||
* <p>You can use {@link MethodHookParam#setResult} and {@link MethodHookParam#setThrowable}
|
||||
* to modify the return value of the original method.
|
||||
*
|
||||
* <p>Note that implementations shouldn't call {@code super(param)}, it's not necessary.
|
||||
*
|
||||
* @param param Information about the method call.
|
||||
* @throws Throwable Everything the callback throws is caught and logged.
|
||||
*/
|
||||
protected void afterHookedMethod(MethodHookParam param) throws Throwable {}
|
||||
|
||||
public void callAfterHookedMethod(MethodHookParam param) throws Throwable {
|
||||
afterHookedMethod(param);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps information about the method call and allows to influence it.
|
||||
*/
|
||||
public static final class MethodHookParam extends XCallback.Param {
|
||||
/** @hide */
|
||||
@SuppressWarnings("deprecation")
|
||||
public MethodHookParam() {
|
||||
super();
|
||||
}
|
||||
|
||||
/** The hooked method/constructor. */
|
||||
public Member method;
|
||||
|
||||
/** The {@code this} reference for an instance method, or {@code null} for static methods. */
|
||||
public Object thisObject;
|
||||
|
||||
/** Arguments to the method call. */
|
||||
public Object[] args;
|
||||
|
||||
private Object result = null;
|
||||
private Throwable throwable = null;
|
||||
public boolean returnEarly = false;
|
||||
|
||||
/** Returns the result of the method call. */
|
||||
public Object getResult() {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify the result of the method call.
|
||||
*
|
||||
* <p>If called from {@link #beforeHookedMethod}, it prevents the call to the original method.
|
||||
*/
|
||||
public void setResult(Object result) {
|
||||
this.result = result;
|
||||
this.throwable = null;
|
||||
this.returnEarly = true;
|
||||
}
|
||||
|
||||
/** Returns the {@link Throwable} thrown by the method, or {@code null}. */
|
||||
public Throwable getThrowable() {
|
||||
return throwable;
|
||||
}
|
||||
|
||||
/** Returns true if an exception was thrown by the method. */
|
||||
public boolean hasThrowable() {
|
||||
return throwable != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify the exception thrown of the method call.
|
||||
*
|
||||
* <p>If called from {@link #beforeHookedMethod}, it prevents the call to the original method.
|
||||
*/
|
||||
public void setThrowable(Throwable throwable) {
|
||||
this.throwable = throwable;
|
||||
this.result = null;
|
||||
this.returnEarly = true;
|
||||
}
|
||||
|
||||
/** Returns the result of the method call, or throws the Throwable caused by it. */
|
||||
public Object getResultOrThrowable() throws Throwable {
|
||||
if (throwable != null)
|
||||
throw throwable;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An object with which the method/constructor can be unhooked.
|
||||
*/
|
||||
public class Unhook implements IXUnhook<XC_MethodHook> {
|
||||
private final Member hookMethod;
|
||||
|
||||
/*package*/ Unhook(Member hookMethod) {
|
||||
this.hookMethod = hookMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the method/constructor that has been hooked.
|
||||
*/
|
||||
public Member getHookedMethod() {
|
||||
return hookMethod;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XC_MethodHook getCallback() {
|
||||
return XC_MethodHook.this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public void unhook() {
|
||||
XposedBridge.unhookMethod(hookMethod, XC_MethodHook.this);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
package de.robv.android.xposed;
|
||||
|
||||
import de.robv.android.xposed.callbacks.XCallback;
|
||||
|
||||
/**
|
||||
* A special case of {@link XC_MethodHook} which completely replaces the original method.
|
||||
*/
|
||||
public abstract class XC_MethodReplacement extends XC_MethodHook {
|
||||
/**
|
||||
* Creates a new callback with default priority.
|
||||
*/
|
||||
public XC_MethodReplacement() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new callback with a specific priority.
|
||||
*
|
||||
* @param priority See {@link XCallback#priority}.
|
||||
*/
|
||||
public XC_MethodReplacement(int priority) {
|
||||
super(priority);
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@Override
|
||||
protected final void beforeHookedMethod(MethodHookParam param) throws Throwable {
|
||||
try {
|
||||
Object result = replaceHookedMethod(param);
|
||||
param.setResult(result);
|
||||
} catch (Throwable t) {
|
||||
XposedBridge.log(t);
|
||||
param.setThrowable(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@Override
|
||||
@SuppressWarnings("EmptyMethod")
|
||||
protected final void afterHookedMethod(MethodHookParam param) throws Throwable {}
|
||||
|
||||
/**
|
||||
* Shortcut for replacing a method completely. Whatever is returned/thrown here is taken
|
||||
* instead of the result of the original method (which will not be called).
|
||||
*
|
||||
* <p>Note that implementations shouldn't call {@code super(param)}, it's not necessary.
|
||||
*
|
||||
* @param param Information about the method call.
|
||||
* @throws Throwable Anything that is thrown by the callback will be passed on to the original caller.
|
||||
*/
|
||||
@SuppressWarnings("UnusedParameters")
|
||||
protected abstract Object replaceHookedMethod(MethodHookParam param) throws Throwable;
|
||||
|
||||
/**
|
||||
* Predefined callback that skips the method without replacements.
|
||||
*/
|
||||
public static final XC_MethodReplacement DO_NOTHING = new XC_MethodReplacement(PRIORITY_HIGHEST*2) {
|
||||
@Override
|
||||
protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a callback which always returns a specific value.
|
||||
*
|
||||
* @param result The value that should be returned to callers of the hooked method.
|
||||
*/
|
||||
public static XC_MethodReplacement returnConstant(final Object result) {
|
||||
return returnConstant(PRIORITY_DEFAULT, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Like {@link #returnConstant(Object)}, but allows to specify a priority for the callback.
|
||||
*
|
||||
* @param priority See {@link XCallback#priority}.
|
||||
* @param result The value that should be returned to callers of the hooked method.
|
||||
*/
|
||||
public static XC_MethodReplacement returnConstant(int priority, final Object result) {
|
||||
return new XC_MethodReplacement(priority) {
|
||||
@Override
|
||||
protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
|
||||
return result;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,538 +0,0 @@
|
|||
package de.robv.android.xposed;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Environment;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.util.XmlUtils;
|
||||
import com.elderdrivers.riru.edxp.bridge.BuildConfig;
|
||||
import com.elderdrivers.riru.edxp.util.MetaDataReader;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.AccessDeniedException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardWatchEventKinds;
|
||||
import java.nio.file.WatchEvent;
|
||||
import java.nio.file.WatchKey;
|
||||
import java.nio.file.WatchService;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import de.robv.android.xposed.services.FileResult;
|
||||
|
||||
/**
|
||||
* This class is basically the same as SharedPreferencesImpl from AOSP, but
|
||||
* read-only and without listeners support. Instead, it is made to be
|
||||
* compatible with all ROMs.
|
||||
*/
|
||||
public final class XSharedPreferences implements SharedPreferences {
|
||||
private static final String TAG = "XSharedPreferences";
|
||||
private static final HashMap<Path, PrefsData> sInstances = new HashMap<>();
|
||||
private static final Object sContent = new Object();
|
||||
private static Thread sDaemon = null;
|
||||
private static WatchService sWatcher;
|
||||
|
||||
private final HashMap<OnSharedPreferenceChangeListener, Object> mListeners = new HashMap<>();
|
||||
private final File mFile;
|
||||
private final String mFilename;
|
||||
private Map<String, Object> mMap;
|
||||
private boolean mLoaded = false;
|
||||
private long mLastModified;
|
||||
private long mFileSize;
|
||||
private boolean mWatcherEnabled;
|
||||
|
||||
private static synchronized WatchService getWatcher() {
|
||||
if (sWatcher == null) {
|
||||
try {
|
||||
sWatcher = new File(XposedInit.prefsBasePath).toPath().getFileSystem().newWatchService();
|
||||
if (BuildConfig.DEBUG) Log.d(TAG, "Created WatchService instance");
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Failed to create WatchService", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (sWatcher != null && (sDaemon == null || !sDaemon.isAlive())) {
|
||||
initWatcherDaemon();
|
||||
}
|
||||
|
||||
return sWatcher;
|
||||
}
|
||||
|
||||
private static void initWatcherDaemon() {
|
||||
sDaemon = new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
Log.d(TAG, "Watcher daemon thread started");
|
||||
while (true) {
|
||||
WatchKey key;
|
||||
try {
|
||||
key = sWatcher.take();
|
||||
} catch (InterruptedException ignored) {
|
||||
return;
|
||||
}
|
||||
for (WatchEvent<?> event : key.pollEvents()) {
|
||||
WatchEvent.Kind<?> kind = event.kind();
|
||||
if (kind == StandardWatchEventKinds.OVERFLOW) {
|
||||
continue;
|
||||
}
|
||||
Path dir = (Path) key.watchable();
|
||||
Path path = dir.resolve((Path) event.context());
|
||||
String pathStr = path.toString();
|
||||
if (BuildConfig.DEBUG) Log.v(TAG, "File " + path.toString() + " event: " + kind.name());
|
||||
// We react to both real and backup files due to rare race conditions
|
||||
if (pathStr.endsWith(".bak")) {
|
||||
if (kind != StandardWatchEventKinds.ENTRY_DELETE) {
|
||||
continue;
|
||||
} else {
|
||||
pathStr = path.getFileName().toString();
|
||||
path = dir.resolve(pathStr.substring(0, pathStr.length() - 4));
|
||||
}
|
||||
} else if (SELinuxHelper.getAppDataFileService().checkFileExists(pathStr + ".bak")) {
|
||||
continue;
|
||||
}
|
||||
PrefsData data = sInstances.get(path);
|
||||
if (data != null && data.hasChanged()) {
|
||||
for (OnSharedPreferenceChangeListener l : data.mPrefs.mListeners.keySet()) {
|
||||
try {
|
||||
l.onSharedPreferenceChanged(data.mPrefs, null);
|
||||
} catch (Throwable t) {
|
||||
if (BuildConfig.DEBUG) Log.e(TAG, "Fail in preference change listener", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
key.reset();
|
||||
}
|
||||
}
|
||||
};
|
||||
sDaemon.setName(TAG + "-Daemon");
|
||||
sDaemon.setDaemon(true);
|
||||
sDaemon.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Read settings from the specified file.
|
||||
*
|
||||
* @param prefFile The file to read the preferences from.
|
||||
* @param enableWatcher Whether to enable support for preference change listeners
|
||||
*/
|
||||
public XSharedPreferences(File prefFile, boolean enableWatcher) {
|
||||
mFile = prefFile;
|
||||
mFilename = prefFile.getAbsolutePath();
|
||||
mWatcherEnabled = enableWatcher;
|
||||
init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Read settings from the specified file.
|
||||
*
|
||||
* @param prefFile The file to read the preferences from.
|
||||
*/
|
||||
public XSharedPreferences(File prefFile) {
|
||||
this(prefFile, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read settings from the default preferences for a package.
|
||||
* These preferences are returned by {@link PreferenceManager#getDefaultSharedPreferences}.
|
||||
*
|
||||
* @param packageName The package name.
|
||||
*/
|
||||
public XSharedPreferences(String packageName) {
|
||||
this(packageName, packageName + "_preferences");
|
||||
}
|
||||
|
||||
/**
|
||||
* Read settings from a custom preferences file for a package.
|
||||
* These preferences are returned by {@link Context#getSharedPreferences(String, int)}.
|
||||
*
|
||||
* @param packageName The package name.
|
||||
* @param prefFileName The file name without ".xml".
|
||||
*/
|
||||
public XSharedPreferences(String packageName, String prefFileName) {
|
||||
boolean newModule = false;
|
||||
Set<String> modules = XposedInit.getLoadedModules();
|
||||
for (String m : modules) {
|
||||
if (m.contains("/" + packageName + "-")) {
|
||||
boolean isModule = false;
|
||||
int xposedminversion = -1;
|
||||
boolean xposedsharedprefs = false;
|
||||
try {
|
||||
Map<String, Object> metaData = MetaDataReader.getMetaData(new File(m));
|
||||
isModule = metaData.containsKey("xposedmodule");
|
||||
if (isModule) {
|
||||
Object minVersionRaw = metaData.get("xposedminversion");
|
||||
if (minVersionRaw instanceof Integer) {
|
||||
xposedminversion = (Integer) minVersionRaw;
|
||||
} else if (minVersionRaw instanceof String) {
|
||||
xposedminversion = MetaDataReader.extractIntPart((String) minVersionRaw);
|
||||
}
|
||||
xposedsharedprefs = metaData.containsKey("xposedsharedprefs");
|
||||
mWatcherEnabled = metaData.containsKey("xposedsharedprefswatcher");
|
||||
}
|
||||
} catch (NumberFormatException | IOException e) {
|
||||
Log.w(TAG, "Apk parser fails: " + e);
|
||||
}
|
||||
newModule = isModule && (xposedminversion > 92 || xposedsharedprefs);
|
||||
}
|
||||
}
|
||||
if (newModule && XposedInit.prefsBasePath != null) {
|
||||
mFile = new File(XposedInit.prefsBasePath, packageName + "/" + prefFileName + ".xml");
|
||||
} else {
|
||||
mFile = new File(Environment.getDataDirectory(), "data/" + packageName + "/shared_prefs/" + prefFileName + ".xml");
|
||||
}
|
||||
mFilename = mFile.getAbsolutePath();
|
||||
init();
|
||||
}
|
||||
|
||||
private void tryRegisterWatcher() {
|
||||
if (!mWatcherEnabled) {
|
||||
return;
|
||||
}
|
||||
Path path = mFile.toPath();
|
||||
if (sInstances.containsKey(path)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
path.getParent().register(getWatcher(), StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_DELETE);
|
||||
sInstances.put(path, new PrefsData(this));
|
||||
if (BuildConfig.DEBUG) Log.d(TAG, "tryRegisterWatcher: registered file watcher for " + path);
|
||||
} catch (AccessDeniedException accDeniedEx) {
|
||||
if (BuildConfig.DEBUG) Log.d(TAG, "tryRegisterWatcher: access denied to " + path);
|
||||
} catch (Exception e) {
|
||||
Log.d(TAG, "tryRegisterWatcher: failed to register file watcher", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void init() {
|
||||
tryRegisterWatcher();
|
||||
startLoadFromDisk();
|
||||
}
|
||||
|
||||
private static long tryGetFileSize(String filename) {
|
||||
try {
|
||||
return SELinuxHelper.getAppDataFileService().getFileSize(filename);
|
||||
} catch (IOException ignored) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] tryGetFileHash(String filename) {
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||
try (InputStream is = SELinuxHelper.getAppDataFileService().getFileInputStream(filename)) {
|
||||
byte[] buf = new byte[4096];
|
||||
int read;
|
||||
while ((read = is.read(buf)) != -1) {
|
||||
md.update(buf, 0, read);
|
||||
}
|
||||
}
|
||||
return md.digest();
|
||||
} catch (Exception ignored) {
|
||||
return new byte[0];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to make the preferences file world-readable.
|
||||
*
|
||||
* <p><strong>Warning:</strong> This is only meant to work around permission "fix" functions that are part
|
||||
* of some recoveries. It doesn't replace the need to open preferences with {@code MODE_WORLD_READABLE}
|
||||
* in the module's UI code. Otherwise, Android will set stricter permissions again during the next save.
|
||||
*
|
||||
* <p>This will only work if executed as root (e.g. {@code initZygote()}) and only if SELinux is disabled.
|
||||
*
|
||||
* @return {@code true} in case the file could be made world-readable.
|
||||
*/
|
||||
@SuppressLint("SetWorldReadable")
|
||||
public boolean makeWorldReadable() {
|
||||
if (!SELinuxHelper.getAppDataFileService().hasDirectFileAccess())
|
||||
return false; // It doesn't make much sense to make the file readable if we wouldn't be able to access it anyway.
|
||||
|
||||
if (!mFile.exists()) // Just in case - the file should never be created if it doesn't exist.
|
||||
return false;
|
||||
|
||||
if (!mFile.setReadable(true, false))
|
||||
return false;
|
||||
|
||||
tryRegisterWatcher();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the file that is backing these preferences.
|
||||
*
|
||||
* <p><strong>Warning:</strong> The file might not be accessible directly.
|
||||
*/
|
||||
public File getFile() {
|
||||
return mFile;
|
||||
}
|
||||
|
||||
private void startLoadFromDisk() {
|
||||
synchronized (this) {
|
||||
mLoaded = false;
|
||||
}
|
||||
new Thread("XSharedPreferences-load") {
|
||||
@Override
|
||||
public void run() {
|
||||
synchronized (XSharedPreferences.this) {
|
||||
loadFromDiskLocked();
|
||||
}
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
private void loadFromDiskLocked() {
|
||||
if (mLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
Map map = null;
|
||||
FileResult result = null;
|
||||
try {
|
||||
result = SELinuxHelper.getAppDataFileService().getFileInputStream(mFilename, mFileSize, mLastModified);
|
||||
if (result.stream != null) {
|
||||
map = XmlUtils.readMapXml(result.stream);
|
||||
result.stream.close();
|
||||
} else {
|
||||
// The file is unchanged, keep the current values
|
||||
map = mMap;
|
||||
}
|
||||
} catch (XmlPullParserException e) {
|
||||
Log.w(TAG, "getSharedPreferences failed for: " + mFilename, e);
|
||||
} catch (FileNotFoundException ignored) {
|
||||
// SharedPreferencesImpl has a canRead() check, so it doesn't log anything in case the file doesn't exist
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "getSharedPreferences failed for: " + mFilename, e);
|
||||
} finally {
|
||||
if (result != null && result.stream != null) {
|
||||
try {
|
||||
result.stream.close();
|
||||
} catch (RuntimeException rethrown) {
|
||||
throw rethrown;
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mLoaded = true;
|
||||
if (map != null) {
|
||||
mMap = map;
|
||||
mLastModified = result.mtime;
|
||||
mFileSize = result.size;
|
||||
} else {
|
||||
mMap = new HashMap<>();
|
||||
}
|
||||
notifyAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload the settings from file if they have changed.
|
||||
*
|
||||
* <p><strong>Warning:</strong> With enforcing SELinux, this call might be quite expensive.
|
||||
*/
|
||||
public synchronized void reload() {
|
||||
if (hasFileChanged()) {
|
||||
init();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the file has changed since the last time it has been loaded.
|
||||
*
|
||||
* <p><strong>Warning:</strong> With enforcing SELinux, this call might be quite expensive.
|
||||
*/
|
||||
public synchronized boolean hasFileChanged() {
|
||||
try {
|
||||
FileResult result = SELinuxHelper.getAppDataFileService().statFile(mFilename);
|
||||
return mLastModified != result.mtime || mFileSize != result.size;
|
||||
} catch (FileNotFoundException ignored) {
|
||||
// SharedPreferencesImpl doesn't log anything in case the file doesn't exist
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "hasFileChanged", e);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private void awaitLoadedLocked() {
|
||||
while (!mLoaded) {
|
||||
try {
|
||||
wait();
|
||||
} catch (InterruptedException unused) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@Override
|
||||
public Map<String, ?> getAll() {
|
||||
synchronized (this) {
|
||||
awaitLoadedLocked();
|
||||
return new HashMap<>(mMap);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@Override
|
||||
public String getString(String key, String defValue) {
|
||||
synchronized (this) {
|
||||
awaitLoadedLocked();
|
||||
String v = (String) mMap.get(key);
|
||||
return v != null ? v : defValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Set<String> getStringSet(String key, Set<String> defValues) {
|
||||
synchronized (this) {
|
||||
awaitLoadedLocked();
|
||||
Set<String> v = (Set<String>) mMap.get(key);
|
||||
return v != null ? v : defValues;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@Override
|
||||
public int getInt(String key, int defValue) {
|
||||
synchronized (this) {
|
||||
awaitLoadedLocked();
|
||||
Integer v = (Integer) mMap.get(key);
|
||||
return v != null ? v : defValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@Override
|
||||
public long getLong(String key, long defValue) {
|
||||
synchronized (this) {
|
||||
awaitLoadedLocked();
|
||||
Long v = (Long) mMap.get(key);
|
||||
return v != null ? v : defValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@Override
|
||||
public float getFloat(String key, float defValue) {
|
||||
synchronized (this) {
|
||||
awaitLoadedLocked();
|
||||
Float v = (Float) mMap.get(key);
|
||||
return v != null ? v : defValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@Override
|
||||
public boolean getBoolean(String key, boolean defValue) {
|
||||
synchronized (this) {
|
||||
awaitLoadedLocked();
|
||||
Boolean v = (Boolean) mMap.get(key);
|
||||
return v != null ? v : defValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@Override
|
||||
public boolean contains(String key) {
|
||||
synchronized (this) {
|
||||
awaitLoadedLocked();
|
||||
return mMap.containsKey(key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Not supported by this implementation.
|
||||
*/
|
||||
@Deprecated
|
||||
@Override
|
||||
public Editor edit() {
|
||||
throw new UnsupportedOperationException("read-only implementation");
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
|
||||
if (!mWatcherEnabled)
|
||||
throw new UnsupportedOperationException("File watcher feature is disabled for this instance");
|
||||
|
||||
synchronized(this) {
|
||||
mListeners.put(listener, sContent);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
|
||||
if (!mWatcherEnabled)
|
||||
throw new UnsupportedOperationException("File watcher feature is disabled for this instance");
|
||||
|
||||
synchronized(this) {
|
||||
mListeners.remove(listener);
|
||||
}
|
||||
}
|
||||
|
||||
private static class PrefsData {
|
||||
public final XSharedPreferences mPrefs;
|
||||
private long mSize;
|
||||
private byte[] mHash;
|
||||
|
||||
public PrefsData(XSharedPreferences prefs) {
|
||||
mPrefs = prefs;
|
||||
mSize = tryGetFileSize(prefs.mFilename);
|
||||
mHash = tryGetFileHash(prefs.mFilename);
|
||||
}
|
||||
|
||||
public boolean hasChanged() {
|
||||
long size = tryGetFileSize(mPrefs.mFilename);
|
||||
if (size < 1) {
|
||||
if (BuildConfig.DEBUG) Log.d(TAG, "Ignoring empty prefs file");
|
||||
return false;
|
||||
}
|
||||
if (size != mSize) {
|
||||
mSize = size;
|
||||
mHash = tryGetFileHash(mPrefs.mFilename);
|
||||
if (BuildConfig.DEBUG) Log.d(TAG, "Prefs file size changed");
|
||||
return true;
|
||||
}
|
||||
byte[] hash = tryGetFileHash(mPrefs.mFilename);
|
||||
if (!Arrays.equals(hash, mHash)) {
|
||||
mHash = hash;
|
||||
if (BuildConfig.DEBUG) Log.d(TAG, "Prefs file hash changed");
|
||||
return true;
|
||||
}
|
||||
if (BuildConfig.DEBUG) Log.d(TAG, "Prefs file not changed");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,615 +0,0 @@
|
|||
package de.robv.android.xposed;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
import android.util.Log;
|
||||
|
||||
import com.elderdrivers.riru.edxp.bridge.BuildConfig;
|
||||
import com.elderdrivers.riru.edxp.config.EdXpConfigGlobal;
|
||||
|
||||
import java.lang.reflect.AccessibleObject;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Member;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import dalvik.system.InMemoryDexClassLoader;
|
||||
import de.robv.android.xposed.XC_MethodHook.MethodHookParam;
|
||||
import de.robv.android.xposed.annotation.ApiSensitive;
|
||||
import de.robv.android.xposed.annotation.Level;
|
||||
import de.robv.android.xposed.callbacks.XC_InitPackageResources;
|
||||
import de.robv.android.xposed.callbacks.XC_InitZygote;
|
||||
import de.robv.android.xposed.callbacks.XC_LoadPackage;
|
||||
import de.robv.android.xposed.callbacks.XCallback;
|
||||
import external.com.android.dx.DexMaker;
|
||||
import external.com.android.dx.TypeId;
|
||||
|
||||
import static de.robv.android.xposed.XposedHelpers.getIntField;
|
||||
import static de.robv.android.xposed.XposedHelpers.setObjectField;
|
||||
|
||||
/**
|
||||
* This class contains most of Xposed's central logic, such as initialization and callbacks used by
|
||||
* the native side. It also includes methods to add new hooks.
|
||||
*/
|
||||
@SuppressWarnings("JniMissingFunction")
|
||||
public final class XposedBridge {
|
||||
/**
|
||||
* The system class loader which can be used to locate Android framework classes.
|
||||
* Application classes cannot be retrieved from it.
|
||||
*
|
||||
* @see ClassLoader#getSystemClassLoader
|
||||
*/
|
||||
public static final ClassLoader BOOTCLASSLOADER = XposedBridge.class.getClassLoader();
|
||||
|
||||
/** @hide */
|
||||
public static final String TAG = "EdXposed-Bridge";
|
||||
|
||||
/** @deprecated Use {@link #getXposedVersion()} instead. */
|
||||
@Deprecated
|
||||
public static int XPOSED_BRIDGE_VERSION;
|
||||
|
||||
/*package*/ static boolean isZygote = true; // ed: RuntimeInit.main() tool process not supported yet
|
||||
|
||||
private static int runtime = 2; // ed: only support art
|
||||
private static final int RUNTIME_DALVIK = 1;
|
||||
private static final int RUNTIME_ART = 2;
|
||||
|
||||
public static boolean disableHooks = false;
|
||||
|
||||
// This field is set "magically" on MIUI.
|
||||
/*package*/ static long BOOT_START_TIME;
|
||||
|
||||
private static final Object[] EMPTY_ARRAY = new Object[0];
|
||||
|
||||
// built-in handlers
|
||||
public static final Map<Member, CopyOnWriteSortedSet<XC_MethodHook>> sHookedMethodCallbacks = new HashMap<>();
|
||||
public static final CopyOnWriteSortedSet<XC_LoadPackage> sLoadedPackageCallbacks = new CopyOnWriteSortedSet<>();
|
||||
/*package*/ static final CopyOnWriteSortedSet<XC_InitPackageResources> sInitPackageResourcesCallbacks = new CopyOnWriteSortedSet<>();
|
||||
/*package*/ static final CopyOnWriteSortedSet<XC_InitZygote> sInitZygoteCallbacks = new CopyOnWriteSortedSet<>();
|
||||
|
||||
private XposedBridge() {}
|
||||
|
||||
/**
|
||||
* Called when native methods and other things are initialized, but before preloading classes etc.
|
||||
* @hide
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public static void main(String[] args) {
|
||||
// ed: moved
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
// protected static final class ToolEntryPoint {
|
||||
// protected static void main(String[] args) {
|
||||
// isZygote = false;
|
||||
// XposedBridge.main(args);
|
||||
// }
|
||||
// }
|
||||
|
||||
public static volatile ClassLoader dummyClassLoader = null;
|
||||
|
||||
@ApiSensitive(Level.MIDDLE)
|
||||
public static void initXResources() {
|
||||
if (dummyClassLoader != null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Resources res = Resources.getSystem();
|
||||
Class resClass = res.getClass();
|
||||
Class taClass = TypedArray.class;
|
||||
try {
|
||||
TypedArray ta = res.obtainTypedArray(res.getIdentifier(
|
||||
"preloaded_drawables", "array", "android"));
|
||||
taClass = ta.getClass();
|
||||
ta.recycle();
|
||||
} catch (Resources.NotFoundException nfe) {
|
||||
XposedBridge.log(nfe);
|
||||
}
|
||||
XposedBridge.removeFinalFlagNative(resClass);
|
||||
XposedBridge.removeFinalFlagNative(taClass);
|
||||
DexMaker dexMaker = new DexMaker();
|
||||
dexMaker.declare(TypeId.get("Lxposed/dummy/XResourcesSuperClass;"),
|
||||
"XResourcesSuperClass.java",
|
||||
Modifier.PUBLIC, TypeId.get(resClass));
|
||||
dexMaker.declare(TypeId.get("Lxposed/dummy/XTypedArraySuperClass;"),
|
||||
"XTypedArraySuperClass.java",
|
||||
Modifier.PUBLIC, TypeId.get(taClass));
|
||||
ClassLoader myCL = XposedBridge.class.getClassLoader();
|
||||
dummyClassLoader = new InMemoryDexClassLoader(
|
||||
ByteBuffer.wrap(dexMaker.generate()), myCL.getParent());
|
||||
dummyClassLoader.loadClass("xposed.dummy.XResourcesSuperClass");
|
||||
dummyClassLoader.loadClass("xposed.dummy.XTypedArraySuperClass");
|
||||
setObjectField(myCL, "parent", dummyClassLoader);
|
||||
} catch (Throwable throwable) {
|
||||
XposedBridge.log(throwable);
|
||||
XposedInit.disableResources = true;
|
||||
}
|
||||
}
|
||||
|
||||
// private static boolean hadInitErrors() {
|
||||
// // ed: assuming never had errors
|
||||
// return false;
|
||||
// }
|
||||
// private static native int getRuntime();
|
||||
// /*package*/ static native boolean startsSystemServer();
|
||||
// /*package*/ static native String getStartClassName();
|
||||
// /*package*/ native static boolean initXResourcesNative();
|
||||
|
||||
/**
|
||||
* Returns the currently installed version of the Xposed framework.
|
||||
*/
|
||||
public static int getXposedVersion() {
|
||||
return BuildConfig.API_CODE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a message to the Xposed modules log.
|
||||
*
|
||||
* <p class="warning"><b>DON'T FLOOD THE LOG!!!</b> This is only meant for error logging.
|
||||
* If you want to write information/debug messages, use logcat.
|
||||
*
|
||||
* @param text The log message.
|
||||
*/
|
||||
public synchronized static void log(String text) {
|
||||
if (EdXpConfigGlobal.getConfig().isNoModuleLogEnabled()) {
|
||||
return;
|
||||
}
|
||||
Log.i(TAG, text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a stack trace to the Xposed modules log.
|
||||
*
|
||||
* <p class="warning"><b>DON'T FLOOD THE LOG!!!</b> This is only meant for error logging.
|
||||
* If you want to write information/debug messages, use logcat.
|
||||
*
|
||||
* @param t The Throwable object for the stack trace.
|
||||
*/
|
||||
public synchronized static void log(Throwable t) {
|
||||
Log.e(TAG, Log.getStackTraceString(t));
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook any method (or constructor) with the specified callback. See below for some wrappers
|
||||
* that make it easier to find a method/constructor in one step.
|
||||
*
|
||||
* @param hookMethod The method to be hooked.
|
||||
* @param callback The callback to be executed when the hooked method is called.
|
||||
* @return An object that can be used to remove the hook.
|
||||
*
|
||||
* @see XposedHelpers#findAndHookMethod(String, ClassLoader, String, Object...)
|
||||
* @see XposedHelpers#findAndHookMethod(Class, String, Object...)
|
||||
* @see #hookAllMethods
|
||||
* @see XposedHelpers#findAndHookConstructor(String, ClassLoader, Object...)
|
||||
* @see XposedHelpers#findAndHookConstructor(Class, Object...)
|
||||
* @see #hookAllConstructors
|
||||
*/
|
||||
public static XC_MethodHook.Unhook hookMethod(Member hookMethod, XC_MethodHook callback) {
|
||||
if (!(hookMethod instanceof Method) && !(hookMethod instanceof Constructor<?>)) {
|
||||
throw new IllegalArgumentException("Only methods and constructors can be hooked: " + hookMethod.toString());
|
||||
} else if (hookMethod.getDeclaringClass().isInterface()) {
|
||||
throw new IllegalArgumentException("Cannot hook interfaces: " + hookMethod.toString());
|
||||
} else if (Modifier.isAbstract(hookMethod.getModifiers())) {
|
||||
throw new IllegalArgumentException("Cannot hook abstract methods: " + hookMethod.toString());
|
||||
}
|
||||
|
||||
if (callback == null) {
|
||||
throw new IllegalArgumentException("callback should not be null!");
|
||||
}
|
||||
|
||||
boolean newMethod = false;
|
||||
CopyOnWriteSortedSet<XC_MethodHook> callbacks;
|
||||
synchronized (sHookedMethodCallbacks) {
|
||||
callbacks = sHookedMethodCallbacks.get(hookMethod);
|
||||
if (callbacks == null) {
|
||||
callbacks = new CopyOnWriteSortedSet<>();
|
||||
sHookedMethodCallbacks.put(hookMethod, callbacks);
|
||||
newMethod = true;
|
||||
}
|
||||
}
|
||||
callbacks.add(callback);
|
||||
|
||||
if (newMethod) {
|
||||
Class<?> declaringClass = hookMethod.getDeclaringClass();
|
||||
int slot;
|
||||
Class<?>[] parameterTypes;
|
||||
Class<?> returnType;
|
||||
if (runtime == RUNTIME_ART) {
|
||||
slot = 0;
|
||||
parameterTypes = null;
|
||||
returnType = null;
|
||||
} else if (hookMethod instanceof Method) {
|
||||
slot = getIntField(hookMethod, "slot");
|
||||
parameterTypes = ((Method) hookMethod).getParameterTypes();
|
||||
returnType = ((Method) hookMethod).getReturnType();
|
||||
} else {
|
||||
slot = getIntField(hookMethod, "slot");
|
||||
parameterTypes = ((Constructor<?>) hookMethod).getParameterTypes();
|
||||
returnType = null;
|
||||
}
|
||||
|
||||
AdditionalHookInfo additionalInfo = new AdditionalHookInfo(callbacks, parameterTypes, returnType);
|
||||
Member reflectMethod = EdXpConfigGlobal.getHookProvider().findMethodNative(hookMethod);
|
||||
if (reflectMethod != null) {
|
||||
hookMethodNative(reflectMethod, declaringClass, slot, additionalInfo);
|
||||
} else {
|
||||
PendingHooks.recordPendingMethod(hookMethod, additionalInfo);
|
||||
}
|
||||
}
|
||||
|
||||
return callback.new Unhook(hookMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the callback for a hooked method/constructor.
|
||||
*
|
||||
* @deprecated Use {@link XC_MethodHook.Unhook#unhook} instead. An instance of the {@code Unhook}
|
||||
* class is returned when you hook the method.
|
||||
*
|
||||
* @param hookMethod The method for which the callback should be removed.
|
||||
* @param callback The reference to the callback as specified in {@link #hookMethod}.
|
||||
*/
|
||||
@Deprecated
|
||||
public static void unhookMethod(Member hookMethod, XC_MethodHook callback) {
|
||||
EdXpConfigGlobal.getHookProvider().unhookMethod(hookMethod);
|
||||
CopyOnWriteSortedSet<XC_MethodHook> callbacks;
|
||||
synchronized (sHookedMethodCallbacks) {
|
||||
callbacks = sHookedMethodCallbacks.get(hookMethod);
|
||||
if (callbacks == null)
|
||||
return;
|
||||
}
|
||||
callbacks.remove(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooks all methods with a certain name that were declared in the specified class. Inherited
|
||||
* methods and constructors are not considered. For constructors, use
|
||||
* {@link #hookAllConstructors} instead.
|
||||
*
|
||||
* @param hookClass The class to check for declared methods.
|
||||
* @param methodName The name of the method(s) to hook.
|
||||
* @param callback The callback to be executed when the hooked methods are called.
|
||||
* @return A set containing one object for each found method which can be used to unhook it.
|
||||
*/
|
||||
@SuppressWarnings("UnusedReturnValue")
|
||||
public static Set<XC_MethodHook.Unhook> hookAllMethods(Class<?> hookClass, String methodName, XC_MethodHook callback) {
|
||||
Set<XC_MethodHook.Unhook> unhooks = new HashSet<>();
|
||||
for (Member method : hookClass.getDeclaredMethods())
|
||||
if (method.getName().equals(methodName))
|
||||
unhooks.add(hookMethod(method, callback));
|
||||
return unhooks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook all constructors of the specified class.
|
||||
*
|
||||
* @param hookClass The class to check for constructors.
|
||||
* @param callback The callback to be executed when the hooked constructors are called.
|
||||
* @return A set containing one object for each found constructor which can be used to unhook it.
|
||||
*/
|
||||
@SuppressWarnings("UnusedReturnValue")
|
||||
public static Set<XC_MethodHook.Unhook> hookAllConstructors(Class<?> hookClass, XC_MethodHook callback) {
|
||||
Set<XC_MethodHook.Unhook> unhooks = new HashSet<>();
|
||||
for (Member constructor : hookClass.getDeclaredConstructors())
|
||||
unhooks.add(hookMethod(constructor, callback));
|
||||
return unhooks;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called as a replacement for hooked methods.
|
||||
*/
|
||||
public static Object handleHookedMethod(Member method, long originalMethodId, Object additionalInfoObj,
|
||||
Object thisObject, Object[] args) throws Throwable {
|
||||
AdditionalHookInfo additionalInfo = (AdditionalHookInfo) additionalInfoObj;
|
||||
|
||||
if (disableHooks) {
|
||||
try {
|
||||
return invokeOriginalMethodNative(method, originalMethodId, additionalInfo.parameterTypes,
|
||||
additionalInfo.returnType, thisObject, args);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw e.getCause();
|
||||
}
|
||||
}
|
||||
|
||||
Object[] callbacksSnapshot = additionalInfo.callbacks.getSnapshot();
|
||||
final int callbacksLength = callbacksSnapshot.length;
|
||||
if (callbacksLength == 0) {
|
||||
try {
|
||||
return invokeOriginalMethodNative(method, originalMethodId, additionalInfo.parameterTypes,
|
||||
additionalInfo.returnType, thisObject, args);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw e.getCause();
|
||||
}
|
||||
}
|
||||
|
||||
MethodHookParam param = new MethodHookParam();
|
||||
param.method = method;
|
||||
param.thisObject = thisObject;
|
||||
param.args = args;
|
||||
|
||||
// call "before method" callbacks
|
||||
int beforeIdx = 0;
|
||||
do {
|
||||
try {
|
||||
((XC_MethodHook) callbacksSnapshot[beforeIdx]).beforeHookedMethod(param);
|
||||
} catch (Throwable t) {
|
||||
XposedBridge.log(t);
|
||||
|
||||
// reset result (ignoring what the unexpectedly exiting callback did)
|
||||
param.setResult(null);
|
||||
param.returnEarly = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (param.returnEarly) {
|
||||
// skip remaining "before" callbacks and corresponding "after" callbacks
|
||||
beforeIdx++;
|
||||
break;
|
||||
}
|
||||
} while (++beforeIdx < callbacksLength);
|
||||
|
||||
// call original method if not requested otherwise
|
||||
if (!param.returnEarly) {
|
||||
try {
|
||||
param.setResult(invokeOriginalMethodNative(method, originalMethodId,
|
||||
additionalInfo.parameterTypes, additionalInfo.returnType, param.thisObject, param.args));
|
||||
} catch (InvocationTargetException e) {
|
||||
param.setThrowable(e.getCause());
|
||||
}
|
||||
}
|
||||
|
||||
// call "after method" callbacks
|
||||
int afterIdx = beforeIdx - 1;
|
||||
do {
|
||||
Object lastResult = param.getResult();
|
||||
Throwable lastThrowable = param.getThrowable();
|
||||
|
||||
try {
|
||||
((XC_MethodHook) callbacksSnapshot[afterIdx]).afterHookedMethod(param);
|
||||
} catch (Throwable t) {
|
||||
XposedBridge.log(t);
|
||||
|
||||
// reset to last result (ignoring what the unexpectedly exiting callback did)
|
||||
if (lastThrowable == null)
|
||||
param.setResult(lastResult);
|
||||
else
|
||||
param.setThrowable(lastThrowable);
|
||||
}
|
||||
} while (--afterIdx >= 0);
|
||||
|
||||
// return
|
||||
if (param.hasThrowable())
|
||||
throw param.getThrowable();
|
||||
else
|
||||
return param.getResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a callback to be executed when an app ("Android package") is loaded.
|
||||
*
|
||||
* <p class="note">You probably don't need to call this. Simply implement {@link IXposedHookLoadPackage}
|
||||
* in your module class and Xposed will take care of registering it as a callback.
|
||||
*
|
||||
* @param callback The callback to be executed.
|
||||
* @hide
|
||||
*/
|
||||
public static void hookLoadPackage(XC_LoadPackage callback) {
|
||||
synchronized (sLoadedPackageCallbacks) {
|
||||
sLoadedPackageCallbacks.add(callback);
|
||||
}
|
||||
}
|
||||
|
||||
public static void clearLoadedPackages() {
|
||||
synchronized (sLoadedPackageCallbacks) {
|
||||
sLoadedPackageCallbacks.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a callback to be executed when the resources for an app are initialized.
|
||||
*
|
||||
* <p class="note">You probably don't need to call this. Simply implement {@link IXposedHookInitPackageResources}
|
||||
* in your module class and Xposed will take care of registering it as a callback.
|
||||
*
|
||||
* @param callback The callback to be executed.
|
||||
* @hide
|
||||
*/
|
||||
public static void hookInitPackageResources(XC_InitPackageResources callback) {
|
||||
synchronized (sInitPackageResourcesCallbacks) {
|
||||
sInitPackageResourcesCallbacks.add(callback);
|
||||
}
|
||||
}
|
||||
|
||||
public static void clearInitPackageResources() {
|
||||
synchronized (sInitPackageResourcesCallbacks) {
|
||||
sInitPackageResourcesCallbacks.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public static void hookInitZygote(XC_InitZygote callback) {
|
||||
synchronized (sInitZygoteCallbacks) {
|
||||
sInitZygoteCallbacks.add(callback);
|
||||
}
|
||||
}
|
||||
|
||||
public static void clearInitZygotes() {
|
||||
synchronized (sInitZygoteCallbacks) {
|
||||
sInitZygoteCallbacks.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public static void callInitZygotes() {
|
||||
XCallback.callAll(new IXposedHookZygoteInit.StartupParam(sInitZygoteCallbacks));
|
||||
}
|
||||
|
||||
public static void clearAllCallbacks() {
|
||||
clearLoadedPackages();
|
||||
clearInitPackageResources();
|
||||
clearInitZygotes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Intercept every call to the specified method and call a handler function instead.
|
||||
* @param method The method to intercept
|
||||
*/
|
||||
/*package*/ synchronized static void hookMethodNative(final Member method, Class<?> declaringClass,
|
||||
int slot, final Object additionalInfoObj) {
|
||||
EdXpConfigGlobal.getHookProvider().hookMethod(method, (AdditionalHookInfo) additionalInfoObj);
|
||||
}
|
||||
|
||||
private static Object invokeOriginalMethodNative(Member method, long methodId,
|
||||
Class<?>[] parameterTypes,
|
||||
Class<?> returnType,
|
||||
Object thisObject, Object[] args)
|
||||
throws Throwable {
|
||||
return EdXpConfigGlobal.getHookProvider().invokeOriginalMethod(method, methodId, thisObject, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Basically the same as {@link Method#invoke}, but calls the original method
|
||||
* as it was before the interception by Xposed. Also, access permissions are not checked.
|
||||
*
|
||||
* <p class="caution">There are very few cases where this method is needed. A common mistake is
|
||||
* to replace a method and then invoke the original one based on dynamic conditions. This
|
||||
* creates overhead and skips further hooks by other modules. Instead, just hook (don't replace)
|
||||
* the method and call {@code param.setResult(null)} in {@link XC_MethodHook#beforeHookedMethod}
|
||||
* if the original method should be skipped.
|
||||
*
|
||||
* @param method The method to be called.
|
||||
* @param thisObject For non-static calls, the "this" pointer, otherwise {@code null}.
|
||||
* @param args Arguments for the method call as Object[] array.
|
||||
* @return The result returned from the invoked method.
|
||||
* @throws NullPointerException
|
||||
* if {@code receiver == null} for a non-static method
|
||||
* @throws IllegalAccessException
|
||||
* if this method is not accessible (see {@link AccessibleObject})
|
||||
* @throws IllegalArgumentException
|
||||
* if the number of arguments doesn't match the number of parameters, the receiver
|
||||
* is incompatible with the declaring class, or an argument could not be unboxed
|
||||
* or converted by a widening conversion to the corresponding parameter type
|
||||
* @throws InvocationTargetException
|
||||
* if an exception was thrown by the invoked method
|
||||
*/
|
||||
public static Object invokeOriginalMethod(Member method, Object thisObject, Object[] args)
|
||||
throws Throwable {
|
||||
if (args == null) {
|
||||
args = EMPTY_ARRAY;
|
||||
}
|
||||
|
||||
Class<?>[] parameterTypes;
|
||||
Class<?> returnType;
|
||||
if (runtime == RUNTIME_ART && (method instanceof Method || method instanceof Constructor)) {
|
||||
parameterTypes = null;
|
||||
returnType = null;
|
||||
} else if (method instanceof Method) {
|
||||
parameterTypes = ((Method) method).getParameterTypes();
|
||||
returnType = ((Method) method).getReturnType();
|
||||
} else if (method instanceof Constructor) {
|
||||
parameterTypes = ((Constructor<?>) method).getParameterTypes();
|
||||
returnType = null;
|
||||
} else {
|
||||
throw new IllegalArgumentException("method must be of type Method or Constructor");
|
||||
}
|
||||
|
||||
long methodId = EdXpConfigGlobal.getHookProvider().getMethodId(method);
|
||||
return invokeOriginalMethodNative(method, methodId, parameterTypes, returnType, thisObject, args);
|
||||
}
|
||||
|
||||
/*package*/ static void setObjectClass(Object obj, Class<?> clazz) {
|
||||
if (clazz.isAssignableFrom(obj.getClass())) {
|
||||
throw new IllegalArgumentException("Cannot transfer object from " + obj.getClass() + " to " + clazz);
|
||||
}
|
||||
setObjectClassNative(obj, clazz);
|
||||
}
|
||||
|
||||
private static native void setObjectClassNative(Object obj, Class<?> clazz);
|
||||
/*package*/ static native void dumpObjectNative(Object obj);
|
||||
|
||||
/*package*/ static Object cloneToSubclass(Object obj, Class<?> targetClazz) {
|
||||
if (obj == null)
|
||||
return null;
|
||||
|
||||
if (!obj.getClass().isAssignableFrom(targetClazz))
|
||||
throw new ClassCastException(targetClazz + " doesn't extend " + obj.getClass());
|
||||
|
||||
return cloneToSubclassNative(obj, targetClazz);
|
||||
}
|
||||
|
||||
private static native Object cloneToSubclassNative(Object obj, Class<?> targetClazz);
|
||||
|
||||
private static void removeFinalFlagNative(Class clazz) {
|
||||
EdXpConfigGlobal.getHookProvider().removeFinalFlagNative(clazz);
|
||||
}
|
||||
|
||||
// /*package*/ static native void closeFilesBeforeForkNative();
|
||||
// /*package*/ static native void reopenFilesAfterForkNative();
|
||||
//
|
||||
// /*package*/ static native void invalidateCallersNative(Member[] methods);
|
||||
|
||||
/** @hide */
|
||||
public static final class CopyOnWriteSortedSet<E> {
|
||||
private transient volatile Object[] elements = EMPTY_ARRAY;
|
||||
|
||||
@SuppressWarnings("UnusedReturnValue")
|
||||
public synchronized boolean add(E e) {
|
||||
int index = indexOf(e);
|
||||
if (index >= 0)
|
||||
return false;
|
||||
|
||||
Object[] newElements = new Object[elements.length + 1];
|
||||
System.arraycopy(elements, 0, newElements, 0, elements.length);
|
||||
newElements[elements.length] = e;
|
||||
Arrays.sort(newElements);
|
||||
elements = newElements;
|
||||
return true;
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnusedReturnValue")
|
||||
public synchronized boolean remove(E e) {
|
||||
int index = indexOf(e);
|
||||
if (index == -1)
|
||||
return false;
|
||||
|
||||
Object[] newElements = new Object[elements.length - 1];
|
||||
System.arraycopy(elements, 0, newElements, 0, index);
|
||||
System.arraycopy(elements, index + 1, newElements, index, elements.length - index - 1);
|
||||
elements = newElements;
|
||||
return true;
|
||||
}
|
||||
|
||||
private int indexOf(Object o) {
|
||||
for (int i = 0; i < elements.length; i++) {
|
||||
if (o.equals(elements[i]))
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public Object[] getSnapshot() {
|
||||
return elements;
|
||||
}
|
||||
|
||||
public synchronized void clear() {
|
||||
elements = EMPTY_ARRAY;
|
||||
}
|
||||
}
|
||||
|
||||
public static class AdditionalHookInfo {
|
||||
public final CopyOnWriteSortedSet<XC_MethodHook> callbacks;
|
||||
public final Class<?>[] parameterTypes;
|
||||
public final Class<?> returnType;
|
||||
|
||||
private AdditionalHookInfo(CopyOnWriteSortedSet<XC_MethodHook> callbacks, Class<?>[] parameterTypes, Class<?> returnType) {
|
||||
this.callbacks = callbacks;
|
||||
this.parameterTypes = parameterTypes;
|
||||
this.returnType = returnType;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,511 +0,0 @@
|
|||
package de.robv.android.xposed;
|
||||
|
||||
import android.app.ActivityThread;
|
||||
import android.app.AndroidAppHelper;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.ResourcesImpl;
|
||||
import android.content.res.TypedArray;
|
||||
import android.content.res.XResources;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.os.Process;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.os.ZygoteInit;
|
||||
import com.elderdrivers.riru.edxp.config.EdXpConfigGlobal;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
import dalvik.system.PathClassLoader;
|
||||
import de.robv.android.xposed.annotation.ApiSensitive;
|
||||
import de.robv.android.xposed.annotation.Level;
|
||||
import de.robv.android.xposed.callbacks.XC_InitPackageResources;
|
||||
import de.robv.android.xposed.callbacks.XC_InitZygote;
|
||||
import de.robv.android.xposed.callbacks.XC_LoadPackage;
|
||||
import de.robv.android.xposed.callbacks.XCallback;
|
||||
import de.robv.android.xposed.services.BaseService;
|
||||
|
||||
import static de.robv.android.xposed.XposedBridge.clearAllCallbacks;
|
||||
import static de.robv.android.xposed.XposedBridge.hookAllConstructors;
|
||||
import static de.robv.android.xposed.XposedBridge.hookAllMethods;
|
||||
import static de.robv.android.xposed.XposedBridge.sInitPackageResourcesCallbacks;
|
||||
import static de.robv.android.xposed.XposedBridge.sInitZygoteCallbacks;
|
||||
import static de.robv.android.xposed.XposedBridge.sLoadedPackageCallbacks;
|
||||
import static de.robv.android.xposed.XposedHelpers.callMethod;
|
||||
import static de.robv.android.xposed.XposedHelpers.closeSilently;
|
||||
import static de.robv.android.xposed.XposedHelpers.findAndHookMethod;
|
||||
import static de.robv.android.xposed.XposedHelpers.findClass;
|
||||
import static de.robv.android.xposed.XposedHelpers.findFieldIfExists;
|
||||
import static de.robv.android.xposed.XposedHelpers.getObjectField;
|
||||
import static de.robv.android.xposed.XposedHelpers.getParameterIndexByType;
|
||||
import static de.robv.android.xposed.XposedHelpers.setStaticBooleanField;
|
||||
import static de.robv.android.xposed.XposedHelpers.setStaticLongField;
|
||||
import static de.robv.android.xposed.XposedHelpers.setStaticObjectField;
|
||||
|
||||
public final class XposedInit {
|
||||
private static final String TAG = XposedBridge.TAG;
|
||||
public static boolean startsSystemServer = false;
|
||||
private static final String startClassName = ""; // ed: no support for tool process anymore
|
||||
|
||||
private static final String INSTANT_RUN_CLASS = "com.android.tools.fd.runtime.BootstrapApplication";
|
||||
public static volatile boolean disableResources = false;
|
||||
private static final String[] XRESOURCES_CONFLICTING_PACKAGES = {"com.sygic.aura"};
|
||||
public static String prefsBasePath = null;
|
||||
|
||||
private XposedInit() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook some methods which we want to create an easier interface for developers.
|
||||
*/
|
||||
/*package*/
|
||||
public static void initForZygote(boolean isSystem) throws Throwable {
|
||||
// TODO Are these still needed for us?
|
||||
// MIUI
|
||||
if (findFieldIfExists(ZygoteInit.class, "BOOT_START_TIME") != null) {
|
||||
setStaticLongField(ZygoteInit.class, "BOOT_START_TIME", XposedBridge.BOOT_START_TIME);
|
||||
}
|
||||
// Samsung
|
||||
if (Build.VERSION.SDK_INT >= 24) {
|
||||
Class<?> zygote = findClass("com.android.internal.os.Zygote", null);
|
||||
try {
|
||||
setStaticBooleanField(zygote, "isEnhancedZygoteASLREnabled", false);
|
||||
} catch (NoSuchFieldError ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
hookResources();
|
||||
}
|
||||
|
||||
@ApiSensitive(Level.MIDDLE)
|
||||
private static void hookResources() throws Throwable {
|
||||
if (!EdXpConfigGlobal.getConfig().isResourcesHookEnabled() || disableResources) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!EdXpConfigGlobal.getHookProvider().initXResourcesNative()) {
|
||||
Log.e(TAG, "Cannot hook resources");
|
||||
disableResources = true;
|
||||
return;
|
||||
}
|
||||
|
||||
findAndHookMethod("android.app.ApplicationPackageManager", null, "getResourcesForApplication",
|
||||
ApplicationInfo.class, new XC_MethodHook() {
|
||||
@Override
|
||||
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
|
||||
ApplicationInfo app = (ApplicationInfo) param.args[0];
|
||||
XResources.setPackageNameForResDir(app.packageName,
|
||||
app.uid == Process.myUid() ? app.sourceDir : app.publicSourceDir);
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
* getTopLevelResources(a)
|
||||
* -> getTopLevelResources(b)
|
||||
* -> key = new ResourcesKey()
|
||||
* -> r = new Resources()
|
||||
* -> mActiveResources.put(key, r)
|
||||
* -> return r
|
||||
*/
|
||||
|
||||
final Class<?> classGTLR;
|
||||
final Class<?> classResKey;
|
||||
final ThreadLocal<Object> latestResKey = new ThreadLocal<>();
|
||||
|
||||
if (Build.VERSION.SDK_INT <= 18) {
|
||||
classGTLR = ActivityThread.class;
|
||||
classResKey = Class.forName("android.app.ActivityThread$ResourcesKey");
|
||||
} else {
|
||||
classGTLR = Class.forName("android.app.ResourcesManager");
|
||||
classResKey = Class.forName("android.content.res.ResourcesKey");
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 24) {
|
||||
hookAllMethods(classGTLR, "getOrCreateResources", new XC_MethodHook() {
|
||||
@Override
|
||||
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
|
||||
// At least on OnePlus 5, the method has an additional parameter compared to AOSP.
|
||||
final int activityTokenIdx = getParameterIndexByType(param.method, IBinder.class);
|
||||
final int resKeyIdx = getParameterIndexByType(param.method, classResKey);
|
||||
|
||||
String resDir = (String) getObjectField(param.args[resKeyIdx], "mResDir");
|
||||
XResources newRes = cloneToXResources(param, resDir);
|
||||
if (newRes == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object activityToken = param.args[activityTokenIdx];
|
||||
synchronized (param.thisObject) {
|
||||
ArrayList<WeakReference<Resources>> resourceReferences;
|
||||
if (activityToken != null) {
|
||||
Object activityResources = callMethod(param.thisObject, "getOrCreateActivityResourcesStructLocked", activityToken);
|
||||
resourceReferences = (ArrayList<WeakReference<Resources>>) getObjectField(activityResources, "activityResources");
|
||||
} else {
|
||||
resourceReferences = (ArrayList<WeakReference<Resources>>) getObjectField(param.thisObject, "mResourceReferences");
|
||||
}
|
||||
resourceReferences.add(new WeakReference(newRes));
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
hookAllConstructors(classResKey, new XC_MethodHook() {
|
||||
@Override
|
||||
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
|
||||
latestResKey.set(param.thisObject);
|
||||
}
|
||||
});
|
||||
|
||||
hookAllMethods(classGTLR, "getTopLevelResources", new XC_MethodHook() {
|
||||
@Override
|
||||
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
|
||||
latestResKey.set(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
|
||||
Object key = latestResKey.get();
|
||||
if (key == null) {
|
||||
return;
|
||||
}
|
||||
latestResKey.set(null);
|
||||
|
||||
String resDir = (String) getObjectField(key, "mResDir");
|
||||
XResources newRes = cloneToXResources(param, resDir);
|
||||
if (newRes == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<Object, WeakReference<Resources>> mActiveResources =
|
||||
(Map<Object, WeakReference<Resources>>) getObjectField(param.thisObject, "mActiveResources");
|
||||
Object lockObject = (Build.VERSION.SDK_INT <= 18)
|
||||
? getObjectField(param.thisObject, "mPackages") : param.thisObject;
|
||||
|
||||
synchronized (lockObject) {
|
||||
WeakReference<Resources> existing = mActiveResources.put(key, new WeakReference<Resources>(newRes));
|
||||
if (existing != null && existing.get() != null && existing.get().getAssets() != newRes.getAssets()) {
|
||||
existing.get().getAssets().close();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 19) {
|
||||
// This method exists only on CM-based ROMs
|
||||
hookAllMethods(classGTLR, "getTopLevelThemedResources", new XC_MethodHook() {
|
||||
@Override
|
||||
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
|
||||
String resDir = (String) param.args[0];
|
||||
cloneToXResources(param, resDir);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Invalidate callers of methods overridden by XTypedArray
|
||||
// if (Build.VERSION.SDK_INT >= 24) {
|
||||
// Set<Method> methods = getOverriddenMethods(XResources.XTypedArray.class);
|
||||
// XposedBridge.invalidateCallersNative(methods.toArray(new Member[methods.size()]));
|
||||
// }
|
||||
|
||||
// Replace TypedArrays with XTypedArrays
|
||||
// hookAllConstructors(TypedArray.class, new XC_MethodHook() {
|
||||
// @Override
|
||||
// protected void afterHookedMethod(MethodHookParam param) throws Throwable {
|
||||
// TypedArray typedArray = (TypedArray) param.thisObject;
|
||||
// Resources res = typedArray.getResources();
|
||||
// if (res instanceof XResources) {
|
||||
// XResources.XTypedArray newTypedArray = new XResources.XTypedArray(res);
|
||||
// XposedBridge.setObjectClass(typedArray, XResources.XTypedArray.class);
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
|
||||
findAndHookMethod(TypedArray.class, "obtain", Resources.class, int.class,
|
||||
new XC_MethodHook() {
|
||||
@Override
|
||||
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
|
||||
if (param.getResult() instanceof XResources.XTypedArray) {
|
||||
return;
|
||||
}
|
||||
if (!(param.args[0] instanceof XResources)) {
|
||||
return;
|
||||
}
|
||||
XResources.XTypedArray newResult =
|
||||
new XResources.XTypedArray((Resources) param.args[0]);
|
||||
int len = (int) param.args[1];
|
||||
Method resizeMethod = XposedHelpers.findMethodBestMatch(
|
||||
TypedArray.class, "resize", new Class[]{int.class});
|
||||
resizeMethod.setAccessible(true);
|
||||
resizeMethod.invoke(newResult, len);
|
||||
param.setResult(newResult);
|
||||
}
|
||||
});
|
||||
|
||||
// Replace system resources
|
||||
XResources systemRes = new XResources(
|
||||
(ClassLoader) XposedHelpers.getObjectField(Resources.getSystem(), "mClassLoader"));
|
||||
systemRes.setImpl((ResourcesImpl) XposedHelpers.getObjectField(Resources.getSystem(), "mResourcesImpl"));
|
||||
systemRes.initObject(null);
|
||||
setStaticObjectField(Resources.class, "mSystem", systemRes);
|
||||
|
||||
XResources.init(latestResKey);
|
||||
}
|
||||
|
||||
@ApiSensitive(Level.MIDDLE)
|
||||
private static XResources cloneToXResources(XC_MethodHook.MethodHookParam param, String resDir) {
|
||||
Object result = param.getResult();
|
||||
if (result == null || result instanceof XResources ||
|
||||
Arrays.binarySearch(XRESOURCES_CONFLICTING_PACKAGES, AndroidAppHelper.currentPackageName()) == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Replace the returned resources with our subclass.
|
||||
XResources newRes = new XResources(
|
||||
(ClassLoader) XposedHelpers.getObjectField(param.getResult(), "mClassLoader"));
|
||||
newRes.setImpl((ResourcesImpl) XposedHelpers.getObjectField(param.getResult(), "mResourcesImpl"));
|
||||
newRes.initObject(resDir);
|
||||
|
||||
// Invoke handleInitPackageResources().
|
||||
if (newRes.isFirstLoad()) {
|
||||
String packageName = newRes.getPackageName();
|
||||
XC_InitPackageResources.InitPackageResourcesParam resparam = new XC_InitPackageResources.InitPackageResourcesParam(XposedBridge.sInitPackageResourcesCallbacks);
|
||||
resparam.packageName = packageName;
|
||||
resparam.res = newRes;
|
||||
XCallback.callAll(resparam);
|
||||
}
|
||||
|
||||
param.setResult(newRes);
|
||||
return newRes;
|
||||
}
|
||||
|
||||
private static boolean needsToCloseFilesForFork() {
|
||||
// ed: we always start to do our work after forking finishes
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to load all modules defined in <code>INSTALLER_DATA_BASE_DIR/conf/modules.list</code>
|
||||
*/
|
||||
private static final AtomicBoolean modulesLoaded = new AtomicBoolean(false);
|
||||
private static final Object moduleLoadLock = new Object();
|
||||
// @GuardedBy("moduleLoadLock")
|
||||
private static final ArraySet<String> loadedModules = new ArraySet<>();
|
||||
|
||||
public static ArraySet<String> getLoadedModules() {
|
||||
synchronized (moduleLoadLock) {
|
||||
return loadedModules;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean loadModules(boolean callInitZygote) throws IOException {
|
||||
boolean hasLoaded = !modulesLoaded.compareAndSet(false, true);
|
||||
if (hasLoaded) {
|
||||
return false;
|
||||
}
|
||||
synchronized (moduleLoadLock) {
|
||||
ClassLoader topClassLoader = XposedBridge.BOOTCLASSLOADER;
|
||||
ClassLoader parent;
|
||||
while ((parent = topClassLoader.getParent()) != null) {
|
||||
topClassLoader = parent;
|
||||
}
|
||||
|
||||
String moduleList = EdXpConfigGlobal.getConfig().getModulesList();
|
||||
InputStream stream = new ByteArrayInputStream(moduleList.getBytes());
|
||||
BufferedReader apks = new BufferedReader(new InputStreamReader(stream));
|
||||
ArraySet<String> newLoadedApk = new ArraySet<>();
|
||||
String apk;
|
||||
while ((apk = apks.readLine()) != null) {
|
||||
if (loadedModules.contains(apk)) {
|
||||
newLoadedApk.add(apk);
|
||||
} else {
|
||||
loadedModules.add(apk); // temporarily add it for XSharedPreference
|
||||
boolean loadSuccess = loadModule(apk, topClassLoader, callInitZygote);
|
||||
if (loadSuccess) {
|
||||
newLoadedApk.add(apk);
|
||||
}
|
||||
}
|
||||
}
|
||||
loadedModules.clear();
|
||||
loadedModules.addAll(newLoadedApk);
|
||||
apks.close();
|
||||
|
||||
// refresh callback according to current loaded module list
|
||||
pruneCallbacks(loadedModules);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// remove deactivated or outdated module callbacks
|
||||
private static void pruneCallbacks(Set<String> loadedModules) {
|
||||
synchronized (moduleLoadLock) {
|
||||
Object[] loadedPkgSnapshot = sLoadedPackageCallbacks.getSnapshot();
|
||||
Object[] initPkgResSnapshot = sInitPackageResourcesCallbacks.getSnapshot();
|
||||
Object[] initZygoteSnapshot = sInitZygoteCallbacks.getSnapshot();
|
||||
for (Object loadedPkg : loadedPkgSnapshot) {
|
||||
if (loadedPkg instanceof IModuleContext) {
|
||||
if (!loadedModules.contains(((IModuleContext) loadedPkg).getApkPath())) {
|
||||
sLoadedPackageCallbacks.remove((XC_LoadPackage) loadedPkg);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (Object initPkgRes : initPkgResSnapshot) {
|
||||
if (initPkgRes instanceof IModuleContext) {
|
||||
if (!loadedModules.contains(((IModuleContext) initPkgRes).getApkPath())) {
|
||||
sInitPackageResourcesCallbacks.remove((XC_InitPackageResources) initPkgRes);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (Object initZygote : initZygoteSnapshot) {
|
||||
if (initZygote instanceof IModuleContext) {
|
||||
if (!loadedModules.contains(((IModuleContext) initZygote).getApkPath())) {
|
||||
sInitZygoteCallbacks.remove((XC_InitZygote) initZygote);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a module from an APK by calling the init(String) method for all classes defined
|
||||
* in <code>assets/xposed_init</code>.
|
||||
*/
|
||||
private static boolean loadModule(String apk, ClassLoader topClassLoader, boolean callInitZygote) {
|
||||
Log.i(TAG, "Loading modules from " + apk);
|
||||
|
||||
if (!new File(apk).exists()) {
|
||||
Log.e(TAG, " File does not exist");
|
||||
return false;
|
||||
}
|
||||
|
||||
ClassLoader mcl = new PathClassLoader(apk, topClassLoader);
|
||||
try {
|
||||
if (mcl.loadClass(INSTANT_RUN_CLASS) != null) {
|
||||
Log.e(TAG, " Cannot load module, please disable \"Instant Run\" in Android Studio.");
|
||||
return false;
|
||||
}
|
||||
} catch (ClassNotFoundException ignored) {
|
||||
}
|
||||
|
||||
try {
|
||||
if (mcl.loadClass(XposedBridge.class.getName()) != null) {
|
||||
Log.e(TAG, " Cannot load module:");
|
||||
Log.e(TAG, " The Xposed API classes are compiled into the module's APK.");
|
||||
Log.e(TAG, " This may cause strange issues and must be fixed by the module developer.");
|
||||
Log.e(TAG, " For details, see: http://api.xposed.info/using.html");
|
||||
return false;
|
||||
}
|
||||
} catch (ClassNotFoundException ignored) {
|
||||
}
|
||||
|
||||
try {
|
||||
Field parentField = ClassLoader.class.getDeclaredField("parent");
|
||||
parentField.setAccessible(true);
|
||||
parentField.set(mcl, XposedInit.class.getClassLoader());
|
||||
} catch (NoSuchFieldException | IllegalAccessException e) {
|
||||
Log.e(TAG, " Cannot load module:");
|
||||
Log.e(TAG, " Classloader cannot change parent.");
|
||||
return false;
|
||||
}
|
||||
|
||||
ZipFile zipFile = null;
|
||||
InputStream is;
|
||||
try {
|
||||
zipFile = new ZipFile(apk);
|
||||
ZipEntry zipEntry = zipFile.getEntry("assets/xposed_init");
|
||||
if (zipEntry == null) {
|
||||
Log.e(TAG, " assets/xposed_init not found in the APK");
|
||||
closeSilently(zipFile);
|
||||
return false;
|
||||
}
|
||||
is = zipFile.getInputStream(zipEntry);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, " Cannot read assets/xposed_init in the APK", e);
|
||||
closeSilently(zipFile);
|
||||
return false;
|
||||
}
|
||||
|
||||
BufferedReader moduleClassesReader = new BufferedReader(new InputStreamReader(is));
|
||||
try {
|
||||
String moduleClassName;
|
||||
while ((moduleClassName = moduleClassesReader.readLine()) != null) {
|
||||
moduleClassName = moduleClassName.trim();
|
||||
if (moduleClassName.isEmpty() || moduleClassName.startsWith("#"))
|
||||
continue;
|
||||
|
||||
try {
|
||||
Log.i(TAG, " Loading class " + moduleClassName);
|
||||
Class<?> moduleClass = mcl.loadClass(moduleClassName);
|
||||
|
||||
if (!IXposedMod.class.isAssignableFrom(moduleClass)) {
|
||||
Log.e(TAG, " This class doesn't implement any sub-interface of IXposedMod, skipping it");
|
||||
continue;
|
||||
} else if (disableResources && IXposedHookInitPackageResources.class.isAssignableFrom(moduleClass)) {
|
||||
Log.e(TAG, " This class requires resource-related hooks (which are disabled), skipping it.");
|
||||
continue;
|
||||
}
|
||||
|
||||
final Object moduleInstance = moduleClass.newInstance();
|
||||
if (XposedBridge.isZygote) {
|
||||
if (moduleInstance instanceof IXposedHookZygoteInit) {
|
||||
IXposedHookZygoteInit.StartupParam param = new IXposedHookZygoteInit.StartupParam();
|
||||
param.modulePath = apk;
|
||||
param.startsSystemServer = startsSystemServer;
|
||||
|
||||
XposedBridge.hookInitZygote(new IXposedHookZygoteInit.Wrapper(
|
||||
(IXposedHookZygoteInit) moduleInstance, param));
|
||||
if (callInitZygote) {
|
||||
((IXposedHookZygoteInit) moduleInstance).initZygote(param);
|
||||
}
|
||||
}
|
||||
|
||||
if (moduleInstance instanceof IXposedHookLoadPackage)
|
||||
XposedBridge.hookLoadPackage(new IXposedHookLoadPackage.Wrapper(
|
||||
(IXposedHookLoadPackage) moduleInstance, apk));
|
||||
|
||||
if (moduleInstance instanceof IXposedHookInitPackageResources)
|
||||
XposedBridge.hookInitPackageResources(new IXposedHookInitPackageResources.Wrapper(
|
||||
(IXposedHookInitPackageResources) moduleInstance, apk));
|
||||
} else {
|
||||
if (moduleInstance instanceof IXposedHookCmdInit) {
|
||||
IXposedHookCmdInit.StartupParam param = new IXposedHookCmdInit.StartupParam();
|
||||
param.modulePath = apk;
|
||||
param.startClassName = startClassName;
|
||||
((IXposedHookCmdInit) moduleInstance).initCmdApp(param);
|
||||
}
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
Log.e(TAG, " Failed to load class " + moduleClassName, t);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, " Failed to load module from " + apk, e);
|
||||
return false;
|
||||
} finally {
|
||||
closeSilently(is);
|
||||
closeSilently(zipFile);
|
||||
}
|
||||
}
|
||||
|
||||
public final static HashSet<String> loadedPackagesInProcess = new HashSet<>(1);
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
package de.robv.android.xposed.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Annotation that indicates a element is sensitive to Android API level.
|
||||
* <p>
|
||||
* Annotated elements' compatibility should be checked when adapting to new Android versions.
|
||||
*/
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE})
|
||||
public @interface ApiSensitive {
|
||||
Level value() default Level.HIGH;
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
package de.robv.android.xposed.annotation;
|
||||
|
||||
public enum Level {
|
||||
LOW, MIDDLE, HIGH;
|
||||
|
||||
private Level() {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
package de.robv.android.xposed.callbacks;
|
||||
|
||||
import de.robv.android.xposed.IXposedHookZygoteInit;
|
||||
|
||||
/**
|
||||
* Interface for objects that can be used to remove callbacks.
|
||||
*
|
||||
* <p class="warning">Just like hooking methods etc., unhooking applies only to the current process.
|
||||
* In other process (or when the app is removed from memory and then restarted), the hook will still
|
||||
* be active. The Zygote process (see {@link IXposedHookZygoteInit}) is an exception, the hook won't
|
||||
* be inherited by any future processes forked from it in the future.
|
||||
*
|
||||
* @param <T> The class of the callback.
|
||||
*/
|
||||
public interface IXUnhook<T> {
|
||||
/**
|
||||
* Returns the callback that has been registered.
|
||||
*/
|
||||
T getCallback();
|
||||
|
||||
/**
|
||||
* Removes the callback.
|
||||
*/
|
||||
void unhook();
|
||||
}
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
package de.robv.android.xposed.callbacks;
|
||||
|
||||
import android.content.res.XResources;
|
||||
|
||||
import de.robv.android.xposed.IXposedHookInitPackageResources;
|
||||
import de.robv.android.xposed.XposedBridge.CopyOnWriteSortedSet;
|
||||
|
||||
/**
|
||||
* This class is only used for internal purposes, except for the {@link InitPackageResourcesParam}
|
||||
* subclass.
|
||||
*/
|
||||
public abstract class XC_InitPackageResources extends XCallback implements IXposedHookInitPackageResources {
|
||||
/**
|
||||
* Creates a new callback with default priority.
|
||||
* @hide
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public XC_InitPackageResources() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new callback with a specific priority.
|
||||
*
|
||||
* @param priority See {@link XCallback#priority}.
|
||||
* @hide
|
||||
*/
|
||||
public XC_InitPackageResources(int priority) {
|
||||
super(priority);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps information about the resources being initialized.
|
||||
*/
|
||||
public static final class InitPackageResourcesParam extends XCallback.Param {
|
||||
/** @hide */
|
||||
public InitPackageResourcesParam(CopyOnWriteSortedSet<XC_InitPackageResources> callbacks) {
|
||||
super(callbacks);
|
||||
}
|
||||
|
||||
/** The name of the package for which resources are being loaded. */
|
||||
public String packageName;
|
||||
|
||||
/**
|
||||
* Reference to the resources that can be used for calls to
|
||||
* {@link XResources#setReplacement(String, String, String, Object)}.
|
||||
*/
|
||||
public XResources res;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@Override
|
||||
protected void call(Param param) throws Throwable {
|
||||
if (param instanceof InitPackageResourcesParam)
|
||||
handleInitPackageResources((InitPackageResourcesParam) param);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
package de.robv.android.xposed.callbacks;
|
||||
|
||||
import de.robv.android.xposed.IXposedHookZygoteInit;
|
||||
|
||||
/**
|
||||
* This class is only used for internal purposes, except for the {@link StartupParam}
|
||||
* subclass.
|
||||
*/
|
||||
public abstract class XC_InitZygote extends XCallback implements IXposedHookZygoteInit {
|
||||
|
||||
/**
|
||||
* Creates a new callback with default priority.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public XC_InitZygote() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new callback with a specific priority.
|
||||
*
|
||||
* @param priority See {@link XCallback#priority}.
|
||||
* @hide
|
||||
*/
|
||||
public XC_InitZygote(int priority) {
|
||||
super(priority);
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@Override
|
||||
protected void call(Param param) throws Throwable {
|
||||
if (param instanceof StartupParam)
|
||||
initZygote((StartupParam) param);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,99 +0,0 @@
|
|||
package de.robv.android.xposed.callbacks;
|
||||
|
||||
import android.content.res.XResources;
|
||||
import android.content.res.XResources.ResourceNames;
|
||||
import android.view.View;
|
||||
|
||||
import de.robv.android.xposed.XposedBridge.CopyOnWriteSortedSet;
|
||||
|
||||
/**
|
||||
* Callback for hooking layouts. Such callbacks can be passed to {@link XResources#hookLayout}
|
||||
* and its variants.
|
||||
*/
|
||||
public abstract class XC_LayoutInflated extends XCallback {
|
||||
/**
|
||||
* Creates a new callback with default priority.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public XC_LayoutInflated() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new callback with a specific priority.
|
||||
*
|
||||
* @param priority See {@link XCallback#priority}.
|
||||
*/
|
||||
public XC_LayoutInflated(int priority) {
|
||||
super(priority);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps information about the inflated layout.
|
||||
*/
|
||||
public static final class LayoutInflatedParam extends XCallback.Param {
|
||||
/** @hide */
|
||||
public LayoutInflatedParam(CopyOnWriteSortedSet<XC_LayoutInflated> callbacks) {
|
||||
super(callbacks);
|
||||
}
|
||||
|
||||
/** The view that has been created from the layout. */
|
||||
public View view;
|
||||
|
||||
/** Container with the ID and name of the underlying resource. */
|
||||
public ResourceNames resNames;
|
||||
|
||||
/** Directory from which the layout was actually loaded (e.g. "layout-sw600dp"). */
|
||||
public String variant;
|
||||
|
||||
/** Resources containing the layout. */
|
||||
public XResources res;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@Override
|
||||
protected void call(Param param) throws Throwable {
|
||||
if (param instanceof LayoutInflatedParam)
|
||||
handleLayoutInflated((LayoutInflatedParam) param);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called when the hooked layout has been inflated.
|
||||
*
|
||||
* @param liparam Information about the layout and the inflated view.
|
||||
* @throws Throwable Everything the callback throws is caught and logged.
|
||||
*/
|
||||
public abstract void handleLayoutInflated(LayoutInflatedParam liparam) throws Throwable;
|
||||
|
||||
/**
|
||||
* An object with which the callback can be removed.
|
||||
*/
|
||||
public class Unhook implements IXUnhook<XC_LayoutInflated> {
|
||||
private final String resDir;
|
||||
private final int id;
|
||||
|
||||
/** @hide */
|
||||
public Unhook(String resDir, int id) {
|
||||
this.resDir = resDir;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the resource ID of the hooked layout.
|
||||
*/
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XC_LayoutInflated getCallback() {
|
||||
return XC_LayoutInflated.this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unhook() {
|
||||
XResources.unhookLayout(resDir, id, XC_LayoutInflated.this);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
package de.robv.android.xposed.callbacks;
|
||||
|
||||
import android.content.pm.ApplicationInfo;
|
||||
|
||||
import de.robv.android.xposed.IXposedHookLoadPackage;
|
||||
import de.robv.android.xposed.XposedBridge.CopyOnWriteSortedSet;
|
||||
|
||||
/**
|
||||
* This class is only used for internal purposes, except for the {@link LoadPackageParam}
|
||||
* subclass.
|
||||
*/
|
||||
public abstract class XC_LoadPackage extends XCallback implements IXposedHookLoadPackage {
|
||||
/**
|
||||
* Creates a new callback with default priority.
|
||||
* @hide
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public XC_LoadPackage() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new callback with a specific priority.
|
||||
*
|
||||
* @param priority See {@link XCallback#priority}.
|
||||
* @hide
|
||||
*/
|
||||
public XC_LoadPackage(int priority) {
|
||||
super(priority);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps information about the app being loaded.
|
||||
*/
|
||||
public static final class LoadPackageParam extends XCallback.Param {
|
||||
/** @hide */
|
||||
public LoadPackageParam(CopyOnWriteSortedSet<XC_LoadPackage> callbacks) {
|
||||
super(callbacks);
|
||||
}
|
||||
|
||||
/** The name of the package being loaded. */
|
||||
public String packageName;
|
||||
|
||||
/** The process in which the package is executed. */
|
||||
public String processName;
|
||||
|
||||
/** The ClassLoader used for this package. */
|
||||
public ClassLoader classLoader;
|
||||
|
||||
/** More information about the application being loaded. */
|
||||
public ApplicationInfo appInfo;
|
||||
|
||||
/** Set to {@code true} if this is the first (and main) application for this process. */
|
||||
public boolean isFirstApplication;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@Override
|
||||
protected void call(Param param) throws Throwable {
|
||||
if (param instanceof LoadPackageParam)
|
||||
handleLoadPackage((LoadPackageParam) param);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,154 +0,0 @@
|
|||
package de.robv.android.xposed.callbacks;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.elderdrivers.riru.edxp.config.EdXpConfigGlobal;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import de.robv.android.xposed.IModuleContext;
|
||||
import de.robv.android.xposed.XposedBridge;
|
||||
import de.robv.android.xposed.XposedBridge.CopyOnWriteSortedSet;
|
||||
|
||||
/**
|
||||
* Base class for Xposed callbacks.
|
||||
*
|
||||
* This class only keeps a priority for ordering multiple callbacks.
|
||||
* The actual (abstract) callback methods are added by subclasses.
|
||||
*/
|
||||
public abstract class XCallback implements Comparable<XCallback>, IModuleContext {
|
||||
/**
|
||||
* Callback priority, higher number means earlier execution.
|
||||
*
|
||||
* <p>This is usually set to {@link #PRIORITY_DEFAULT}. However, in case a certain callback should
|
||||
* be executed earlier or later a value between {@link #PRIORITY_HIGHEST} and {@link #PRIORITY_LOWEST}
|
||||
* can be set instead. The values are just for orientation though, Xposed doesn't enforce any
|
||||
* boundaries on the priority values.
|
||||
*/
|
||||
public final int priority;
|
||||
|
||||
/** @deprecated This constructor can't be hidden for technical reasons. Nevertheless, don't use it! */
|
||||
@Deprecated
|
||||
public XCallback() {
|
||||
this.priority = PRIORITY_DEFAULT;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public XCallback(int priority) {
|
||||
this.priority = priority;
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for Xposed callback parameters.
|
||||
*/
|
||||
public static abstract class Param {
|
||||
/** @hide */
|
||||
public final Object[] callbacks;
|
||||
private Bundle extra;
|
||||
|
||||
/** @deprecated This constructor can't be hidden for technical reasons. Nevertheless, don't use it! */
|
||||
@Deprecated
|
||||
protected Param() {
|
||||
callbacks = null;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
protected Param(CopyOnWriteSortedSet<? extends XCallback> callbacks) {
|
||||
this.callbacks = callbacks.getSnapshot();
|
||||
}
|
||||
|
||||
/**
|
||||
* This can be used to store any data for the scope of the callback.
|
||||
*
|
||||
* <p>Use this instead of instance variables, as it has a clear reference to e.g. each
|
||||
* separate call to a method, even when the same method is called recursively.
|
||||
*
|
||||
* @see #setObjectExtra
|
||||
* @see #getObjectExtra
|
||||
*/
|
||||
public synchronized Bundle getExtra() {
|
||||
if (extra == null)
|
||||
extra = new Bundle();
|
||||
return extra;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object stored with {@link #setObjectExtra}.
|
||||
*/
|
||||
public Object getObjectExtra(String key) {
|
||||
Serializable o = getExtra().getSerializable(key);
|
||||
if (o instanceof SerializeWrapper)
|
||||
return ((SerializeWrapper) o).object;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores any object for the scope of the callback. For data types that support it, use
|
||||
* the {@link Bundle} returned by {@link #getExtra} instead.
|
||||
*/
|
||||
public void setObjectExtra(String key, Object o) {
|
||||
getExtra().putSerializable(key, new SerializeWrapper(o));
|
||||
}
|
||||
|
||||
private static class SerializeWrapper implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final Object object;
|
||||
public SerializeWrapper(Object o) {
|
||||
object = o;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public static void callAll(Param param) {
|
||||
|
||||
if (param instanceof XC_LoadPackage.LoadPackageParam) {
|
||||
// deopt methods in system apps or priv-apps, this would be not necessary
|
||||
// only if we found out how to recompile their apks
|
||||
XC_LoadPackage.LoadPackageParam lpp = (XC_LoadPackage.LoadPackageParam) param;
|
||||
EdXpConfigGlobal.getHookProvider().deoptMethods(lpp.packageName, lpp.classLoader);
|
||||
}
|
||||
|
||||
if (param.callbacks == null)
|
||||
throw new IllegalStateException("This object was not created for use with callAll");
|
||||
|
||||
for (int i = 0; i < param.callbacks.length; i++) {
|
||||
try {
|
||||
((XCallback) param.callbacks[i]).call(param);
|
||||
} catch (Throwable t) { XposedBridge.log(t); }
|
||||
}
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
protected void call(Param param) throws Throwable {}
|
||||
|
||||
@Override
|
||||
public String getApkPath() {
|
||||
return "";
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@Override
|
||||
public int compareTo(XCallback other) {
|
||||
if (this == other)
|
||||
return 0;
|
||||
|
||||
// order descending by priority
|
||||
if (other.priority != this.priority)
|
||||
return other.priority - this.priority;
|
||||
// then randomly
|
||||
else if (System.identityHashCode(this) < System.identityHashCode(other))
|
||||
return -1;
|
||||
else
|
||||
return 1;
|
||||
}
|
||||
|
||||
/** The default priority, see {@link #priority}. */
|
||||
public static final int PRIORITY_DEFAULT = 50;
|
||||
|
||||
/** Execute this callback late, see {@link #priority}. */
|
||||
public static final int PRIORITY_LOWEST = -10000;
|
||||
|
||||
/** Execute this callback early, see {@link #priority}. */
|
||||
public static final int PRIORITY_HIGHEST = 10000;
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
/**
|
||||
* Contains the base classes for callbacks.
|
||||
*
|
||||
* <p>For historical reasons, {@link de.robv.android.xposed.XC_MethodHook} and
|
||||
* {@link de.robv.android.xposed.XC_MethodReplacement} are directly in the
|
||||
* {@code de.robv.android.xposed} package.
|
||||
*/
|
||||
package de.robv.android.xposed.callbacks;
|
||||
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
/**
|
||||
* Contains the main classes of the Xposed framework.
|
||||
*/
|
||||
package de.robv.android.xposed;
|
||||
|
|
@ -1,179 +0,0 @@
|
|||
package de.robv.android.xposed.services;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import de.robv.android.xposed.SELinuxHelper;
|
||||
|
||||
/**
|
||||
* General definition of a file access service provided by the Xposed framework.
|
||||
*
|
||||
* <p>References to a concrete subclass should generally be retrieved from {@link SELinuxHelper}.
|
||||
*/
|
||||
public abstract class BaseService {
|
||||
/** Flag for {@link #checkFileAccess}: Read access. */
|
||||
public static final int R_OK = 4;
|
||||
/** Flag for {@link #checkFileAccess}: Write access. */
|
||||
public static final int W_OK = 2;
|
||||
/** Flag for {@link #checkFileAccess}: Executable access. */
|
||||
public static final int X_OK = 1;
|
||||
/** Flag for {@link #checkFileAccess}: File/directory exists. */
|
||||
public static final int F_OK = 0;
|
||||
|
||||
/**
|
||||
* Checks whether the services accesses files directly (instead of using IPC).
|
||||
*
|
||||
* @return {@code true} in case direct access is possible.
|
||||
*/
|
||||
public boolean hasDirectFileAccess() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a file is accessible. SELinux might enforce stricter checks.
|
||||
*
|
||||
* @param filename The absolute path of the file to check.
|
||||
* @param mode The mode for POSIX's {@code access()} function.
|
||||
* @return The result of the {@code access()} function.
|
||||
*/
|
||||
public abstract boolean checkFileAccess(String filename, int mode);
|
||||
|
||||
/**
|
||||
* Check whether a file exists.
|
||||
*
|
||||
* @param filename The absolute path of the file to check.
|
||||
* @return The result of the {@code access()} function.
|
||||
*/
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
public boolean checkFileExists(String filename) {
|
||||
return checkFileAccess(filename, F_OK);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the size and modification time of a file.
|
||||
*
|
||||
* @param filename The absolute path of the file to check.
|
||||
* @return A {@link FileResult} object holding the result.
|
||||
* @throws IOException In case an error occurred while retrieving the information.
|
||||
*/
|
||||
public abstract FileResult statFile(String filename) throws IOException;
|
||||
|
||||
/**
|
||||
* Determine the size time of a file.
|
||||
*
|
||||
* @param filename The absolute path of the file to check.
|
||||
* @return The file size.
|
||||
* @throws IOException In case an error occurred while retrieving the information.
|
||||
*/
|
||||
public long getFileSize(String filename) throws IOException {
|
||||
return statFile(filename).size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the size time of a file.
|
||||
*
|
||||
* @param filename The absolute path of the file to check.
|
||||
* @return The file modification time.
|
||||
* @throws IOException In case an error occurred while retrieving the information.
|
||||
*/
|
||||
public long getFileModificationTime(String filename) throws IOException {
|
||||
return statFile(filename).mtime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a file into memory.
|
||||
*
|
||||
* @param filename The absolute path of the file to read.
|
||||
* @return A {@code byte} array with the file content.
|
||||
* @throws IOException In case an error occurred while reading the file.
|
||||
*/
|
||||
public abstract byte[] readFile(String filename) throws IOException;
|
||||
|
||||
/**
|
||||
* Read a file into memory, but only if it has changed since the last time.
|
||||
*
|
||||
* @param filename The absolute path of the file to read.
|
||||
* @param previousSize File size of last read.
|
||||
* @param previousTime File modification time of last read.
|
||||
* @return A {@link FileResult} object holding the result.
|
||||
* <p>The {@link FileResult#content} field might be {@code null} if the file
|
||||
* is unmodified ({@code previousSize} and {@code previousTime} are still valid).
|
||||
* @throws IOException In case an error occurred while reading the file.
|
||||
*/
|
||||
public abstract FileResult readFile(String filename, long previousSize, long previousTime) throws IOException;
|
||||
|
||||
/**
|
||||
* Read a file into memory, optionally only if it has changed since the last time.
|
||||
*
|
||||
* @param filename The absolute path of the file to read.
|
||||
* @param offset Number of bytes to skip at the beginning of the file.
|
||||
* @param length Number of bytes to read (0 means read to end of file).
|
||||
* @param previousSize Optional: File size of last read.
|
||||
* @param previousTime Optional: File modification time of last read.
|
||||
* @return A {@link FileResult} object holding the result.
|
||||
* <p>The {@link FileResult#content} field might be {@code null} if the file
|
||||
* is unmodified ({@code previousSize} and {@code previousTime} are still valid).
|
||||
* @throws IOException In case an error occurred while reading the file.
|
||||
*/
|
||||
public abstract FileResult readFile(String filename, int offset, int length,
|
||||
long previousSize, long previousTime) throws IOException;
|
||||
|
||||
/**
|
||||
* Get a stream to the file content.
|
||||
* Depending on the service, it may or may not be read completely into memory.
|
||||
*
|
||||
* @param filename The absolute path of the file to read.
|
||||
* @return An {@link InputStream} to the file content.
|
||||
* @throws IOException In case an error occurred while reading the file.
|
||||
*/
|
||||
public InputStream getFileInputStream(String filename) throws IOException {
|
||||
return new ByteArrayInputStream(readFile(filename));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a stream to the file content, but only if it has changed since the last time.
|
||||
* Depending on the service, it may or may not be read completely into memory.
|
||||
*
|
||||
* @param filename The absolute path of the file to read.
|
||||
* @param previousSize Optional: File size of last read.
|
||||
* @param previousTime Optional: File modification time of last read.
|
||||
* @return A {@link FileResult} object holding the result.
|
||||
* <p>The {@link FileResult#stream} field might be {@code null} if the file
|
||||
* is unmodified ({@code previousSize} and {@code previousTime} are still valid).
|
||||
* @throws IOException In case an error occurred while reading the file.
|
||||
*/
|
||||
public FileResult getFileInputStream(String filename, long previousSize, long previousTime) throws IOException {
|
||||
FileResult result = readFile(filename, previousSize, previousTime);
|
||||
if (result.content == null)
|
||||
return result;
|
||||
return new FileResult(new ByteArrayInputStream(result.content), result.size, result.mtime);
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
/*package*/ BaseService() {}
|
||||
|
||||
/*package*/ static void ensureAbsolutePath(String filename) {
|
||||
if (!filename.startsWith("/")) {
|
||||
throw new IllegalArgumentException("Only absolute filenames are allowed: " + filename);
|
||||
}
|
||||
}
|
||||
|
||||
/*package*/ static void throwCommonIOException(int errno, String errorMsg, String filename, String defaultText) throws IOException {
|
||||
switch (errno) {
|
||||
case 1: // EPERM
|
||||
case 13: // EACCES
|
||||
throw new FileNotFoundException(errorMsg != null ? errorMsg : "Permission denied: " + filename);
|
||||
case 2: // ENOENT
|
||||
throw new FileNotFoundException(errorMsg != null ? errorMsg : "No such file or directory: " + filename);
|
||||
case 12: // ENOMEM
|
||||
throw new OutOfMemoryError(errorMsg);
|
||||
case 21: // EISDIR
|
||||
throw new FileNotFoundException(errorMsg != null ? errorMsg : "Is a directory: " + filename);
|
||||
default:
|
||||
throw new IOException(errorMsg != null ? errorMsg : "Error " + errno + defaultText + filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,166 +0,0 @@
|
|||
package de.robv.android.xposed.services;
|
||||
|
||||
import android.os.IBinder;
|
||||
import android.os.Parcel;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/** @hide */
|
||||
public final class BinderService extends BaseService {
|
||||
public static final int TARGET_APP = 0;
|
||||
public static final int TARGET_SYSTEM = 1;
|
||||
|
||||
/**
|
||||
* Retrieve the binder service running in the specified context.
|
||||
* @param target Either {@link #TARGET_APP} or {@link #TARGET_SYSTEM}.
|
||||
* @return A reference to the service.
|
||||
* @throws IllegalStateException In case the service doesn't exist (should never happen).
|
||||
*/
|
||||
public static BinderService getService(int target) {
|
||||
if (target < 0 || target > sServices.length) {
|
||||
throw new IllegalArgumentException("Invalid service target " + target);
|
||||
}
|
||||
synchronized (sServices) {
|
||||
if (sServices[target] == null) {
|
||||
sServices[target] = new BinderService(target);
|
||||
}
|
||||
return sServices[target];
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkFileAccess(String filename, int mode) {
|
||||
ensureAbsolutePath(filename);
|
||||
|
||||
Parcel data = Parcel.obtain();
|
||||
Parcel reply = Parcel.obtain();
|
||||
data.writeInterfaceToken(INTERFACE_TOKEN);
|
||||
data.writeString(filename);
|
||||
data.writeInt(mode);
|
||||
|
||||
try {
|
||||
mRemote.transact(ACCESS_FILE_TRANSACTION, data, reply, 0);
|
||||
} catch (RemoteException e) {
|
||||
data.recycle();
|
||||
reply.recycle();
|
||||
return false;
|
||||
}
|
||||
|
||||
reply.readException();
|
||||
int result = reply.readInt();
|
||||
reply.recycle();
|
||||
data.recycle();
|
||||
return result == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileResult statFile(String filename) throws IOException {
|
||||
ensureAbsolutePath(filename);
|
||||
|
||||
Parcel data = Parcel.obtain();
|
||||
Parcel reply = Parcel.obtain();
|
||||
data.writeInterfaceToken(INTERFACE_TOKEN);
|
||||
data.writeString(filename);
|
||||
|
||||
try {
|
||||
mRemote.transact(STAT_FILE_TRANSACTION, data, reply, 0);
|
||||
} catch (RemoteException e) {
|
||||
data.recycle();
|
||||
reply.recycle();
|
||||
throw new IOException(e);
|
||||
}
|
||||
|
||||
reply.readException();
|
||||
int errno = reply.readInt();
|
||||
if (errno != 0)
|
||||
throwCommonIOException(errno, null, filename, " while retrieving attributes for ");
|
||||
|
||||
long size = reply.readLong();
|
||||
long time = reply.readLong();
|
||||
reply.recycle();
|
||||
data.recycle();
|
||||
return new FileResult(size, time);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] readFile(String filename) throws IOException {
|
||||
return readFile(filename, 0, 0, 0, 0).content;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileResult readFile(String filename, long previousSize, long previousTime) throws IOException {
|
||||
return readFile(filename, 0, 0, previousSize, previousTime);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileResult readFile(String filename, int offset, int length,
|
||||
long previousSize, long previousTime) throws IOException {
|
||||
ensureAbsolutePath(filename);
|
||||
|
||||
Parcel data = Parcel.obtain();
|
||||
Parcel reply = Parcel.obtain();
|
||||
data.writeInterfaceToken(INTERFACE_TOKEN);
|
||||
data.writeString(filename);
|
||||
data.writeInt(offset);
|
||||
data.writeInt(length);
|
||||
data.writeLong(previousSize);
|
||||
data.writeLong(previousTime);
|
||||
|
||||
try {
|
||||
mRemote.transact(READ_FILE_TRANSACTION, data, reply, 0);
|
||||
} catch (RemoteException e) {
|
||||
data.recycle();
|
||||
reply.recycle();
|
||||
throw new IOException(e);
|
||||
}
|
||||
|
||||
reply.readException();
|
||||
int errno = reply.readInt();
|
||||
String errorMsg = reply.readString();
|
||||
long size = reply.readLong();
|
||||
long time = reply.readLong();
|
||||
byte[] content = reply.createByteArray();
|
||||
reply.recycle();
|
||||
data.recycle();
|
||||
|
||||
switch (errno) {
|
||||
case 0:
|
||||
return new FileResult(content, size, time);
|
||||
case 22: // EINVAL
|
||||
if (errorMsg != null) {
|
||||
IllegalArgumentException iae = new IllegalArgumentException(errorMsg);
|
||||
if (offset == 0 && length == 0)
|
||||
throw new IOException(iae);
|
||||
else
|
||||
throw iae;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Offset " + offset + " / Length " + length
|
||||
+ " is out of range for " + filename + " with size " + size);
|
||||
}
|
||||
default:
|
||||
throwCommonIOException(errno, errorMsg, filename, " while reading ");
|
||||
throw new IllegalStateException(); // not reached
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
private static final String INTERFACE_TOKEN = "de.robv.android.xposed.IXposedService";
|
||||
|
||||
private static final int ACCESS_FILE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 2;
|
||||
private static final int STAT_FILE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 3;
|
||||
private static final int READ_FILE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 4;
|
||||
|
||||
private static final String[] SERVICE_NAMES = { "user.xposed.app", "user.xposed.system" };
|
||||
private static final BinderService[] sServices = new BinderService[2];
|
||||
private final IBinder mRemote;
|
||||
|
||||
private BinderService(int target) {
|
||||
IBinder binder = ServiceManager.getService(SERVICE_NAMES[target]);
|
||||
if (binder == null)
|
||||
throw new IllegalStateException("Service " + SERVICE_NAMES[target] + " does not exist");
|
||||
this.mRemote = binder;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,113 +0,0 @@
|
|||
package de.robv.android.xposed.services;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/** @hide */
|
||||
public final class DirectAccessService extends BaseService {
|
||||
@Override
|
||||
public boolean hasDirectFileAccess() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@SuppressWarnings("RedundantIfStatement")
|
||||
@Override
|
||||
public boolean checkFileAccess(String filename, int mode) {
|
||||
File file = new File(filename);
|
||||
if (mode == F_OK && !file.exists()) return false;
|
||||
if ((mode & R_OK) != 0 && !file.canRead()) return false;
|
||||
if ((mode & W_OK) != 0 && !file.canWrite()) return false;
|
||||
if ((mode & X_OK) != 0 && !file.canExecute()) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkFileExists(String filename) {
|
||||
return new File(filename).exists();
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileResult statFile(String filename) throws IOException {
|
||||
File file = new File(filename);
|
||||
return new FileResult(file.length(), file.lastModified());
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] readFile(String filename) throws IOException {
|
||||
File file = new File(filename);
|
||||
byte content[] = new byte[(int)file.length()];
|
||||
FileInputStream fis = new FileInputStream(file);
|
||||
fis.read(content);
|
||||
fis.close();
|
||||
return content;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileResult readFile(String filename, long previousSize, long previousTime) throws IOException {
|
||||
File file = new File(filename);
|
||||
long size = file.length();
|
||||
long time = file.lastModified();
|
||||
if (previousSize == size && previousTime == time)
|
||||
return new FileResult(size, time);
|
||||
return new FileResult(readFile(filename), size, time);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileResult readFile(String filename, int offset, int length, long previousSize, long previousTime) throws IOException {
|
||||
File file = new File(filename);
|
||||
long size = file.length();
|
||||
long time = file.lastModified();
|
||||
if (previousSize == size && previousTime == time)
|
||||
return new FileResult(size, time);
|
||||
|
||||
// Shortcut for the simple case
|
||||
if (offset <= 0 && length <= 0)
|
||||
return new FileResult(readFile(filename), size, time);
|
||||
|
||||
// Check range
|
||||
if (offset > 0 && offset >= size) {
|
||||
throw new IllegalArgumentException("Offset " + offset + " is out of range for " + filename);
|
||||
} else if (offset < 0) {
|
||||
offset = 0;
|
||||
}
|
||||
|
||||
if (length > 0 && (offset + length) > size) {
|
||||
throw new IllegalArgumentException("Length " + length + " is out of range for " + filename);
|
||||
} else if (length <= 0) {
|
||||
length = (int) (size - offset);
|
||||
}
|
||||
|
||||
byte content[] = new byte[length];
|
||||
FileInputStream fis = new FileInputStream(file);
|
||||
fis.skip(offset);
|
||||
fis.read(content);
|
||||
fis.close();
|
||||
return new FileResult(content, size, time);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>This implementation returns a BufferedInputStream instead of loading the file into memory.
|
||||
*/
|
||||
@Override
|
||||
public InputStream getFileInputStream(String filename) throws IOException {
|
||||
return new BufferedInputStream(new FileInputStream(filename), 16*1024);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>This implementation returns a BufferedInputStream instead of loading the file into memory.
|
||||
*/
|
||||
@Override
|
||||
public FileResult getFileInputStream(String filename, long previousSize, long previousTime) throws IOException {
|
||||
File file = new File(filename);
|
||||
long size = file.length();
|
||||
long time = file.lastModified();
|
||||
if (previousSize == size && previousTime == time)
|
||||
return new FileResult(size, time);
|
||||
return new FileResult(new BufferedInputStream(new FileInputStream(filename), 16*1024), size, time);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
package de.robv.android.xposed.services;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Holder for the result of a {@link BaseService#readFile} or {@link BaseService#statFile} call.
|
||||
*/
|
||||
public final class FileResult {
|
||||
/** File content, might be {@code null} if the file wasn't read. */
|
||||
public final byte[] content;
|
||||
/** File input stream, might be {@code null} if the file wasn't read. */
|
||||
public final InputStream stream;
|
||||
/** File size. */
|
||||
public final long size;
|
||||
/** File last modification time. */
|
||||
public final long mtime;
|
||||
|
||||
/*package*/ FileResult(long size, long mtime) {
|
||||
this.content = null;
|
||||
this.stream = null;
|
||||
this.size = size;
|
||||
this.mtime = mtime;
|
||||
}
|
||||
|
||||
/*package*/ FileResult(byte[] content, long size, long mtime) {
|
||||
this.content = content;
|
||||
this.stream = null;
|
||||
this.size = size;
|
||||
this.mtime = mtime;
|
||||
}
|
||||
|
||||
/*package*/ FileResult(InputStream stream, long size, long mtime) {
|
||||
this.content = null;
|
||||
this.stream = stream;
|
||||
this.size = size;
|
||||
this.mtime = mtime;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder("{");
|
||||
if (content != null) {
|
||||
sb.append("content.length: ");
|
||||
sb.append(content.length);
|
||||
sb.append(", ");
|
||||
}
|
||||
if (stream != null) {
|
||||
sb.append("stream: ");
|
||||
sb.append(stream.toString());
|
||||
sb.append(", ");
|
||||
}
|
||||
sb.append("size: ");
|
||||
sb.append(size);
|
||||
sb.append(", mtime: ");
|
||||
sb.append(mtime);
|
||||
sb.append("}");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
package de.robv.android.xposed.services;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
/** @hide */
|
||||
@SuppressWarnings("JniMissingFunction")
|
||||
public final class ZygoteService extends BaseService {
|
||||
@Override
|
||||
public native boolean checkFileAccess(String filename, int mode);
|
||||
|
||||
@Override
|
||||
public native FileResult statFile(String filename) throws IOException;
|
||||
|
||||
@Override
|
||||
public native byte[] readFile(String filename) throws IOException;
|
||||
|
||||
@Override
|
||||
// Just for completeness, we don't expect this to be called often in Zygote.
|
||||
public FileResult readFile(String filename, long previousSize, long previousTime) throws IOException {
|
||||
FileResult stat = statFile(filename);
|
||||
if (previousSize == stat.size && previousTime == stat.mtime)
|
||||
return stat;
|
||||
return new FileResult(readFile(filename), stat.size, stat.mtime);
|
||||
}
|
||||
|
||||
@Override
|
||||
// Just for completeness, we don't expect this to be called often in Zygote.
|
||||
public FileResult readFile(String filename, int offset, int length, long previousSize, long previousTime) throws IOException {
|
||||
FileResult stat = statFile(filename);
|
||||
if (previousSize == stat.size && previousTime == stat.mtime)
|
||||
return stat;
|
||||
|
||||
// Shortcut for the simple case
|
||||
if (offset <= 0 && length <= 0)
|
||||
return new FileResult(readFile(filename), stat.size, stat.mtime);
|
||||
|
||||
// Check range
|
||||
if (offset > 0 && offset >= stat.size) {
|
||||
throw new IllegalArgumentException("offset " + offset + " >= size " + stat.size + " for " + filename);
|
||||
} else if (offset < 0) {
|
||||
offset = 0;
|
||||
}
|
||||
|
||||
if (length > 0 && (offset + length) > stat.size) {
|
||||
throw new IllegalArgumentException("offset " + offset + " + length " + length + " > size " + stat.size + " for " + filename);
|
||||
} else if (length <= 0) {
|
||||
length = (int) (stat.size - offset);
|
||||
}
|
||||
|
||||
byte[] content = readFile(filename);
|
||||
return new FileResult(Arrays.copyOfRange(content, offset, offset + length), stat.size, stat.mtime);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
/**
|
||||
* Contains file access services provided by the Xposed framework.
|
||||
*/
|
||||
package de.robv.android.xposed.services;
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2009-2013 Panxiaobo
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package pxb.android;
|
||||
|
||||
public interface ResConst {
|
||||
int RES_STRING_POOL_TYPE = 0x0001;
|
||||
int RES_TABLE_TYPE = 0x0002;
|
||||
int RES_TABLE_PACKAGE_TYPE = 0x0200;
|
||||
int RES_TABLE_TYPE_SPEC_TYPE = 0x0202;
|
||||
int RES_TABLE_TYPE_TYPE = 0x0201;
|
||||
|
||||
int RES_XML_TYPE = 0x0003;
|
||||
int RES_XML_RESOURCE_MAP_TYPE = 0x0180;
|
||||
int RES_XML_END_NAMESPACE_TYPE = 0x0101;
|
||||
int RES_XML_END_ELEMENT_TYPE = 0x0103;
|
||||
int RES_XML_START_NAMESPACE_TYPE = 0x0100;
|
||||
int RES_XML_START_ELEMENT_TYPE = 0x0102;
|
||||
int RES_XML_CDATA_TYPE = 0x0104;
|
||||
|
||||
}
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2009-2013 Panxiaobo
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package pxb.android;
|
||||
|
||||
public class StringItem {
|
||||
public String data;
|
||||
public int dataOffset;
|
||||
public int index;
|
||||
|
||||
public StringItem() {
|
||||
super();
|
||||
}
|
||||
|
||||
public StringItem(String data) {
|
||||
super();
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
StringItem other = (StringItem) obj;
|
||||
if (data == null) {
|
||||
if (other.data != null)
|
||||
return false;
|
||||
} else if (!data.equals(other.data))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((data == null) ? 0 : data.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return String.format("S%04d %s", index, data);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,163 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2009-2013 Panxiaobo
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package pxb.android;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class StringItems extends ArrayList<StringItem> {
|
||||
private static final int UTF8_FLAG = 0x00000100;
|
||||
|
||||
|
||||
public static String[] read(ByteBuffer in) throws IOException {
|
||||
int trunkOffset = in.position() - 8;
|
||||
int stringCount = in.getInt();
|
||||
int styleOffsetCount = in.getInt();
|
||||
int flags = in.getInt();
|
||||
int stringDataOffset = in.getInt();
|
||||
int stylesOffset = in.getInt();
|
||||
int offsets[] = new int[stringCount];
|
||||
String strings[] = new String[stringCount];
|
||||
for (int i = 0; i < stringCount; i++) {
|
||||
offsets[i] = in.getInt();
|
||||
}
|
||||
|
||||
int base = trunkOffset + stringDataOffset;
|
||||
for (int i = 0; i < offsets.length; i++) {
|
||||
in.position(base + offsets[i]);
|
||||
String s;
|
||||
|
||||
if (0 != (flags & UTF8_FLAG)) {
|
||||
u8length(in); // ignored
|
||||
int u8len = u8length(in);
|
||||
int start = in.position();
|
||||
int blength = u8len;
|
||||
while (in.get(start + blength) != 0) {
|
||||
blength++;
|
||||
}
|
||||
s = new String(in.array(), start, blength, "UTF-8");
|
||||
} else {
|
||||
int length = u16length(in);
|
||||
s = new String(in.array(), in.position(), length * 2, "UTF-16LE");
|
||||
}
|
||||
strings[i] = s;
|
||||
}
|
||||
return strings;
|
||||
}
|
||||
|
||||
static int u16length(ByteBuffer in) {
|
||||
int length = in.getShort() & 0xFFFF;
|
||||
if (length > 0x7FFF) {
|
||||
length = ((length & 0x7FFF) << 8) | (in.getShort() & 0xFFFF);
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
static int u8length(ByteBuffer in) {
|
||||
int len = in.get() & 0xFF;
|
||||
if ((len & 0x80) != 0) {
|
||||
len = ((len & 0x7F) << 8) | (in.get() & 0xFF);
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
byte[] stringData;
|
||||
|
||||
public int getSize() {
|
||||
return 5 * 4 + this.size() * 4 + stringData.length + 0;// TODO
|
||||
}
|
||||
|
||||
public void prepare() throws IOException {
|
||||
for (StringItem s : this) {
|
||||
if (s.data.length() > 0x7FFF) {
|
||||
useUTF8 = false;
|
||||
}
|
||||
}
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
int i = 0;
|
||||
int offset = 0;
|
||||
baos.reset();
|
||||
Map<String, Integer> map = new HashMap<String, Integer>();
|
||||
for (StringItem item : this) {
|
||||
item.index = i++;
|
||||
String stringData = item.data;
|
||||
Integer of = map.get(stringData);
|
||||
if (of != null) {
|
||||
item.dataOffset = of;
|
||||
} else {
|
||||
item.dataOffset = offset;
|
||||
map.put(stringData, offset);
|
||||
if (useUTF8) {
|
||||
int length = stringData.length();
|
||||
byte[] data = stringData.getBytes("UTF-8");
|
||||
int u8lenght = data.length;
|
||||
|
||||
if (length > 0x7F) {
|
||||
offset++;
|
||||
baos.write((length >> 8) | 0x80);
|
||||
}
|
||||
baos.write(length);
|
||||
|
||||
if (u8lenght > 0x7F) {
|
||||
offset++;
|
||||
baos.write((u8lenght >> 8) | 0x80);
|
||||
}
|
||||
baos.write(u8lenght);
|
||||
baos.write(data);
|
||||
baos.write(0);
|
||||
offset += 3 + u8lenght;
|
||||
} else {
|
||||
int length = stringData.length();
|
||||
byte[] data = stringData.getBytes("UTF-16LE");
|
||||
if (length > 0x7FFF) {
|
||||
int x = (length >> 16) | 0x8000;
|
||||
baos.write(x);
|
||||
baos.write(x >> 8);
|
||||
offset += 2;
|
||||
}
|
||||
baos.write(length);
|
||||
baos.write(length >> 8);
|
||||
baos.write(data);
|
||||
baos.write(0);
|
||||
baos.write(0);
|
||||
offset += 4 + data.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO
|
||||
stringData = baos.toByteArray();
|
||||
}
|
||||
|
||||
private boolean useUTF8 = true;
|
||||
|
||||
public void write(ByteBuffer out) throws IOException {
|
||||
out.putInt(this.size());
|
||||
out.putInt(0);// TODO style count
|
||||
out.putInt(useUTF8 ? UTF8_FLAG : 0);
|
||||
out.putInt(7 * 4 + this.size() * 4);
|
||||
out.putInt(0);
|
||||
for (StringItem item : this) {
|
||||
out.putInt(item.dataOffset);
|
||||
}
|
||||
out.put(stringData);
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2009-2013 Panxiaobo
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package pxb.android.arsc;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import pxb.android.axml.Util;
|
||||
|
||||
/**
|
||||
* dump an arsc file
|
||||
*
|
||||
* @author bob
|
||||
*
|
||||
*/
|
||||
public class ArscDumper {
|
||||
public static void dump(List<Pkg> pkgs) {
|
||||
for (int x = 0; x < pkgs.size(); x++) {
|
||||
Pkg pkg = pkgs.get(x);
|
||||
|
||||
System.out.println(String.format(" Package %d id=%d name=%s typeCount=%d", x, pkg.id, pkg.name,
|
||||
pkg.types.size()));
|
||||
for (Type type : pkg.types.values()) {
|
||||
System.out.println(String.format(" type %d %s", type.id - 1, type.name));
|
||||
|
||||
int resPrefix = pkg.id << 24 | type.id << 16;
|
||||
for (int i = 0; i < type.specs.length; i++) {
|
||||
ResSpec spec = type.getSpec(i);
|
||||
System.out.println(String.format(" spec 0x%08x 0x%08x %s", resPrefix | spec.id, spec.flags,
|
||||
spec.name));
|
||||
}
|
||||
for (int i = 0; i < type.configs.size(); i++) {
|
||||
Config config = type.configs.get(i);
|
||||
System.out.println(" config");
|
||||
|
||||
List<ResEntry> entries = new ArrayList<ResEntry>(config.resources.values());
|
||||
for (int j = 0; j < entries.size(); j++) {
|
||||
ResEntry entry = entries.get(j);
|
||||
System.out.println(String.format(" resource 0x%08x %-20s: %s",
|
||||
resPrefix | entry.spec.id, entry.spec.name, entry.value));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String... args) throws IOException {
|
||||
if (args.length == 0) {
|
||||
System.err.println("asrc-dump file.arsc");
|
||||
return;
|
||||
}
|
||||
byte[] data = Util.readFile(new File(args[0]));
|
||||
List<Pkg> pkgs = new ArscParser(data).parse();
|
||||
|
||||
dump(pkgs);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,317 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2009-2013 Panxiaobo
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package pxb.android.arsc;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import pxb.android.ResConst;
|
||||
import pxb.android.StringItems;
|
||||
|
||||
/**
|
||||
*
|
||||
* Read the resources.arsc inside an Android apk.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* <pre>
|
||||
* byte[] oldArscFile= ... ; //
|
||||
* List<Pkg> pkgs = new ArscParser(oldArscFile).parse(); // read the file
|
||||
* modify(pkgs); // do what you want here
|
||||
* byte[] newArscFile = new ArscWriter(pkgs).toByteArray(); // build a new file
|
||||
* </pre>
|
||||
*
|
||||
* The format of arsc is described here (gingerbread)
|
||||
* <ul>
|
||||
* <li>frameworks/base/libs/utils/ResourceTypes.cpp</li>
|
||||
* <li>frameworks/base/include/utils/ResourceTypes.h</li>
|
||||
* </ul>
|
||||
* and the cmd line <code>aapt d resources abc.apk</code> is also good for debug
|
||||
* (available in android sdk)
|
||||
*
|
||||
* <p>
|
||||
* Todos:
|
||||
* <ul>
|
||||
* TODO add support to read styled strings
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* Thanks to the the following projects
|
||||
* <ul>
|
||||
* <li>android4me https://code.google.com/p/android4me/</li>
|
||||
* <li>Apktool https://code.google.com/p/android-apktool</li>
|
||||
* <li>Android http://source.android.com/</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author bob
|
||||
*
|
||||
*/
|
||||
public class ArscParser implements ResConst {
|
||||
|
||||
/* pkg */class Chunk {
|
||||
|
||||
public final int headSize;
|
||||
public final int location;
|
||||
public final int size;
|
||||
public final int type;
|
||||
|
||||
public Chunk() {
|
||||
location = in.position();
|
||||
type = in.getShort() & 0xFFFF;
|
||||
headSize = in.getShort() & 0xFFFF;
|
||||
size = in.getInt();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If set, this resource has been declared public, so libraries are allowed
|
||||
* to reference it.
|
||||
*/
|
||||
static final int ENGRY_FLAG_PUBLIC = 0x0002;
|
||||
|
||||
/**
|
||||
* If set, this is a complex entry, holding a set of name/value mappings. It
|
||||
* is followed by an array of ResTable_map structures.
|
||||
*/
|
||||
final static short ENTRY_FLAG_COMPLEX = 0x0001;
|
||||
public static final int TYPE_STRING = 0x03;
|
||||
|
||||
private int fileSize = -1;
|
||||
private ByteBuffer in;
|
||||
private String[] keyNamesX;
|
||||
private Pkg pkg;
|
||||
private List<Pkg> pkgs = new ArrayList<Pkg>();
|
||||
private String[] strings;
|
||||
private String[] typeNamesX;
|
||||
|
||||
public ArscParser(byte[] b) {
|
||||
this.in = ByteBuffer.wrap(b).order(ByteOrder.LITTLE_ENDIAN);
|
||||
}
|
||||
|
||||
public List<Pkg> parse() throws IOException {
|
||||
if (fileSize < 0) {
|
||||
Chunk head = new Chunk();
|
||||
if (head.type != RES_TABLE_TYPE) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
fileSize = head.size;
|
||||
in.getInt();// packagecount
|
||||
}
|
||||
while (in.hasRemaining()) {
|
||||
Chunk chunk = new Chunk();
|
||||
switch (chunk.type) {
|
||||
case RES_STRING_POOL_TYPE:
|
||||
strings = StringItems.read(in);
|
||||
break;
|
||||
case RES_TABLE_PACKAGE_TYPE:
|
||||
readPackage(in);
|
||||
}
|
||||
in.position(chunk.location + chunk.size);
|
||||
}
|
||||
return pkgs;
|
||||
}
|
||||
|
||||
// private void readConfigFlags() {
|
||||
// int size = in.getInt();
|
||||
// if (size < 28) {
|
||||
// throw new RuntimeException();
|
||||
// }
|
||||
// short mcc = in.getShort();
|
||||
// short mnc = in.getShort();
|
||||
//
|
||||
// char[] language = new char[] { (char) in.get(), (char) in.get() };
|
||||
// char[] country = new char[] { (char) in.get(), (char) in.get() };
|
||||
//
|
||||
// byte orientation = in.get();
|
||||
// byte touchscreen = in.get();
|
||||
// short density = in.getShort();
|
||||
//
|
||||
// byte keyboard = in.get();
|
||||
// byte navigation = in.get();
|
||||
// byte inputFlags = in.get();
|
||||
// byte inputPad0 = in.get();
|
||||
//
|
||||
// short screenWidth = in.getShort();
|
||||
// short screenHeight = in.getShort();
|
||||
//
|
||||
// short sdkVersion = in.getShort();
|
||||
// short minorVersion = in.getShort();
|
||||
//
|
||||
// byte screenLayout = 0;
|
||||
// byte uiMode = 0;
|
||||
// short smallestScreenWidthDp = 0;
|
||||
// if (size >= 32) {
|
||||
// screenLayout = in.get();
|
||||
// uiMode = in.get();
|
||||
// smallestScreenWidthDp = in.getShort();
|
||||
// }
|
||||
//
|
||||
// short screenWidthDp = 0;
|
||||
// short screenHeightDp = 0;
|
||||
//
|
||||
// if (size >= 36) {
|
||||
// screenWidthDp = in.getShort();
|
||||
// screenHeightDp = in.getShort();
|
||||
// }
|
||||
//
|
||||
// short layoutDirection = 0;
|
||||
// if (size >= 38 && sdkVersion >= 17) {
|
||||
// layoutDirection = in.getShort();
|
||||
// }
|
||||
//
|
||||
// }
|
||||
|
||||
private void readEntry(Config config, ResSpec spec) {
|
||||
int size = in.getShort();
|
||||
|
||||
int flags = in.getShort(); // ENTRY_FLAG_PUBLIC
|
||||
int keyStr = in.getInt();
|
||||
spec.updateName(keyNamesX[keyStr]);
|
||||
|
||||
ResEntry resEntry = new ResEntry(flags, spec);
|
||||
|
||||
if (0 != (flags & ENTRY_FLAG_COMPLEX)) {
|
||||
|
||||
int parent = in.getInt();
|
||||
int count = in.getInt();
|
||||
BagValue bag = new BagValue(parent);
|
||||
for (int i = 0; i < count; i++) {
|
||||
Map.Entry<Integer, Value> entry = new AbstractMap.SimpleEntry(in.getInt(), readValue());
|
||||
bag.map.add(entry);
|
||||
}
|
||||
resEntry.value = bag;
|
||||
} else {
|
||||
resEntry.value = readValue();
|
||||
}
|
||||
config.resources.put(spec.id, resEntry);
|
||||
}
|
||||
|
||||
private void readPackage(ByteBuffer in) throws IOException {
|
||||
int pid = in.getInt() % 0xFF;
|
||||
|
||||
String name;
|
||||
{
|
||||
int nextPisition = in.position() + 128 * 2;
|
||||
StringBuilder sb = new StringBuilder(32);
|
||||
for (int i = 0; i < 128; i++) {
|
||||
int s = in.getShort();
|
||||
if (s == 0) {
|
||||
break;
|
||||
} else {
|
||||
sb.append((char) s);
|
||||
}
|
||||
}
|
||||
name = sb.toString();
|
||||
in.position(nextPisition);
|
||||
}
|
||||
|
||||
pkg = new Pkg(pid, name);
|
||||
pkgs.add(pkg);
|
||||
|
||||
int typeStringOff = in.getInt();
|
||||
int typeNameCount = in.getInt();
|
||||
int keyStringOff = in.getInt();
|
||||
int specNameCount = in.getInt();
|
||||
|
||||
{
|
||||
Chunk chunk = new Chunk();
|
||||
if (chunk.type != RES_STRING_POOL_TYPE) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
typeNamesX = StringItems.read(in);
|
||||
in.position(chunk.location + chunk.size);
|
||||
}
|
||||
{
|
||||
Chunk chunk = new Chunk();
|
||||
if (chunk.type != RES_STRING_POOL_TYPE) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
keyNamesX = StringItems.read(in);
|
||||
in.position(chunk.location + chunk.size);
|
||||
}
|
||||
|
||||
out: while (in.hasRemaining()) {
|
||||
Chunk chunk = new Chunk();
|
||||
switch (chunk.type) {
|
||||
case RES_TABLE_TYPE_SPEC_TYPE: {
|
||||
int tid = in.get() & 0xFF;
|
||||
in.get(); // res0
|
||||
in.getShort();// res1
|
||||
int entryCount = in.getInt();
|
||||
|
||||
Type t = pkg.getType(tid, typeNamesX[tid - 1], entryCount);
|
||||
for (int i = 0; i < entryCount; i++) {
|
||||
t.getSpec(i).flags = in.getInt();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case RES_TABLE_TYPE_TYPE: {
|
||||
int tid = in.get() & 0xFF;
|
||||
in.get(); // res0
|
||||
in.getShort();// res1
|
||||
int entryCount = in.getInt();
|
||||
Type t = pkg.getType(tid, typeNamesX[tid - 1], entryCount);
|
||||
int entriesStart = in.getInt();
|
||||
|
||||
int p = in.position();
|
||||
int size = in.getInt();
|
||||
// readConfigFlags();
|
||||
byte[] data = new byte[size];
|
||||
in.position(p);
|
||||
in.get(data);
|
||||
Config config = new Config(data, entryCount);
|
||||
|
||||
in.position(chunk.location + chunk.headSize);
|
||||
|
||||
int[] entrys = new int[entryCount];
|
||||
for (int i = 0; i < entryCount; i++) {
|
||||
entrys[i] = in.getInt();
|
||||
}
|
||||
for (int i = 0; i < entrys.length; i++) {
|
||||
if (entrys[i] != -1) {
|
||||
in.position(chunk.location + entriesStart + entrys[i]);
|
||||
ResSpec spec = t.getSpec(i);
|
||||
readEntry(config, spec);
|
||||
}
|
||||
}
|
||||
|
||||
t.addConfig(config);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break out;
|
||||
}
|
||||
in.position(chunk.location + chunk.size);
|
||||
}
|
||||
}
|
||||
|
||||
private Object readValue() {
|
||||
int size1 = in.getShort();// 8
|
||||
int zero = in.get();// 0
|
||||
int type = in.get() & 0xFF; // TypedValue.*
|
||||
int data = in.getInt();
|
||||
String raw = null;
|
||||
if (type == TYPE_STRING) {
|
||||
raw = strings[data];
|
||||
}
|
||||
return new Value(type, data, raw);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,400 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2009-2013 Panxiaobo
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package pxb.android.arsc;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import pxb.android.ResConst;
|
||||
import pxb.android.StringItem;
|
||||
import pxb.android.StringItems;
|
||||
import pxb.android.axml.Util;
|
||||
|
||||
/**
|
||||
* Write pkgs to an arsc file
|
||||
*
|
||||
* @see ArscParser
|
||||
* @author bob
|
||||
*
|
||||
*/
|
||||
public class ArscWriter implements ResConst {
|
||||
private static class PkgCtx {
|
||||
Map<String, StringItem> keyNames = new HashMap<String, StringItem>();
|
||||
StringItems keyNames0 = new StringItems();
|
||||
public int keyStringOff;
|
||||
int offset;
|
||||
Pkg pkg;
|
||||
int pkgSize;
|
||||
List<StringItem> typeNames = new ArrayList<StringItem>();
|
||||
|
||||
StringItems typeNames0 = new StringItems();
|
||||
int typeStringOff;
|
||||
|
||||
public void addKeyName(String name) {
|
||||
if (keyNames.containsKey(name)) {
|
||||
return;
|
||||
}
|
||||
StringItem stringItem = new StringItem(name);
|
||||
keyNames.put(name, stringItem);
|
||||
keyNames0.add(stringItem);
|
||||
}
|
||||
|
||||
public void addTypeName(int id, String name) {
|
||||
while (typeNames.size() <= id) {
|
||||
typeNames.add(null);
|
||||
}
|
||||
|
||||
StringItem item = typeNames.get(id);
|
||||
if (item == null) {
|
||||
typeNames.set(id, new StringItem(name));
|
||||
} else {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void D(String fmt, Object... args) {
|
||||
|
||||
}
|
||||
|
||||
private List<PkgCtx> ctxs = new ArrayList<PkgCtx>(5);
|
||||
private List<Pkg> pkgs;
|
||||
private Map<String, StringItem> strTable = new TreeMap<String, StringItem>();
|
||||
private StringItems strTable0 = new StringItems();
|
||||
|
||||
public ArscWriter(List<Pkg> pkgs) {
|
||||
this.pkgs = pkgs;
|
||||
}
|
||||
|
||||
public static void main(String... args) throws IOException {
|
||||
if (args.length < 2) {
|
||||
System.err.println("asrc-write-test in.arsc out.arsc");
|
||||
return;
|
||||
}
|
||||
byte[] data = Util.readFile(new File(args[0]));
|
||||
List<Pkg> pkgs = new ArscParser(data).parse();
|
||||
// ArscDumper.dump(pkgs);
|
||||
byte[] data2 = new ArscWriter(pkgs).toByteArray();
|
||||
// ArscDumper.dump(new ArscParser(data2).parse());
|
||||
Util.writeFile(data2, new File(args[1]));
|
||||
}
|
||||
|
||||
private void addString(String str) {
|
||||
if (strTable.containsKey(str)) {
|
||||
return;
|
||||
}
|
||||
StringItem stringItem = new StringItem(str);
|
||||
strTable.put(str, stringItem);
|
||||
strTable0.add(stringItem);
|
||||
}
|
||||
|
||||
private int count() {
|
||||
|
||||
int size = 0;
|
||||
|
||||
size += 8 + 4;// chunk, pkgcount
|
||||
{
|
||||
int stringSize = strTable0.getSize();
|
||||
if (stringSize % 4 != 0) {
|
||||
stringSize += 4 - stringSize % 4;
|
||||
}
|
||||
size += 8 + stringSize;// global strings
|
||||
}
|
||||
for (PkgCtx ctx : ctxs) {
|
||||
ctx.offset = size;
|
||||
int pkgSize = 0;
|
||||
pkgSize += 8 + 4 + 256;// chunk,pid+name
|
||||
pkgSize += 4 * 4;
|
||||
|
||||
ctx.typeStringOff = pkgSize;
|
||||
{
|
||||
int stringSize = ctx.typeNames0.getSize();
|
||||
if (stringSize % 4 != 0) {
|
||||
stringSize += 4 - stringSize % 4;
|
||||
}
|
||||
pkgSize += 8 + stringSize;// type names
|
||||
}
|
||||
|
||||
ctx.keyStringOff = pkgSize;
|
||||
|
||||
{
|
||||
int stringSize = ctx.keyNames0.getSize();
|
||||
if (stringSize % 4 != 0) {
|
||||
stringSize += 4 - stringSize % 4;
|
||||
}
|
||||
pkgSize += 8 + stringSize;// key names
|
||||
}
|
||||
|
||||
for (Type type : ctx.pkg.types.values()) {
|
||||
type.wPosition = size + pkgSize;
|
||||
pkgSize += 8 + 4 + 4 + 4 * type.specs.length; // trunk,id,entryCount,
|
||||
// configs
|
||||
|
||||
for (Config config : type.configs) {
|
||||
config.wPosition = pkgSize + size;
|
||||
int configBasePostion = pkgSize;
|
||||
pkgSize += 8 + 4 + 4 + 4; // trunk,id,entryCount,entriesStart
|
||||
int size0 = config.id.length;
|
||||
if (size0 % 4 != 0) {
|
||||
size0 += 4 - size0 % 4;
|
||||
}
|
||||
pkgSize += size0;// config
|
||||
|
||||
if (pkgSize - configBasePostion > 0x0038) {
|
||||
throw new RuntimeException("config id too big");
|
||||
} else {
|
||||
pkgSize = configBasePostion + 0x0038;
|
||||
}
|
||||
|
||||
pkgSize += 4 * config.entryCount;// offset
|
||||
config.wEntryStart = pkgSize - configBasePostion;
|
||||
int entryBase = pkgSize;
|
||||
for (ResEntry e : config.resources.values()) {
|
||||
e.wOffset = pkgSize - entryBase;
|
||||
pkgSize += 8;// size,flag,keyString
|
||||
if (e.value instanceof BagValue) {
|
||||
BagValue big = (BagValue) e.value;
|
||||
pkgSize += 8 + big.map.size() * 12;
|
||||
} else {
|
||||
pkgSize += 8;
|
||||
}
|
||||
}
|
||||
config.wChunkSize = pkgSize - configBasePostion;
|
||||
}
|
||||
}
|
||||
ctx.pkgSize = pkgSize;
|
||||
size += pkgSize;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
private List<PkgCtx> prepare() throws IOException {
|
||||
for (Pkg pkg : pkgs) {
|
||||
PkgCtx ctx = new PkgCtx();
|
||||
ctx.pkg = pkg;
|
||||
ctxs.add(ctx);
|
||||
|
||||
for (Type type : pkg.types.values()) {
|
||||
ctx.addTypeName(type.id - 1, type.name);
|
||||
for (ResSpec spec : type.specs) {
|
||||
ctx.addKeyName(spec.name);
|
||||
}
|
||||
for (Config config : type.configs) {
|
||||
for (ResEntry e : config.resources.values()) {
|
||||
Object object = e.value;
|
||||
if (object instanceof BagValue) {
|
||||
travelBagValue((BagValue) object);
|
||||
} else {
|
||||
travelValue((Value) object);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ctx.keyNames0.prepare();
|
||||
ctx.typeNames0.addAll(ctx.typeNames);
|
||||
ctx.typeNames0.prepare();
|
||||
}
|
||||
strTable0.prepare();
|
||||
return ctxs;
|
||||
}
|
||||
|
||||
public byte[] toByteArray() throws IOException {
|
||||
prepare();
|
||||
int size = count();
|
||||
ByteBuffer out = ByteBuffer.allocate(size).order(ByteOrder.LITTLE_ENDIAN);
|
||||
write(out, size);
|
||||
return out.array();
|
||||
}
|
||||
|
||||
private void travelBagValue(BagValue bag) {
|
||||
for (Map.Entry<Integer, Value> e : bag.map) {
|
||||
travelValue(e.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
private void travelValue(Value v) {
|
||||
if (v.raw != null) {
|
||||
addString(v.raw);
|
||||
}
|
||||
}
|
||||
|
||||
private void write(ByteBuffer out, int size) throws IOException {
|
||||
out.putInt(RES_TABLE_TYPE | (0x000c << 16));
|
||||
out.putInt(size);
|
||||
out.putInt(ctxs.size());
|
||||
|
||||
{
|
||||
int stringSize = strTable0.getSize();
|
||||
int padding = 0;
|
||||
if (stringSize % 4 != 0) {
|
||||
padding = 4 - stringSize % 4;
|
||||
}
|
||||
out.putInt(RES_STRING_POOL_TYPE | (0x001C << 16));
|
||||
out.putInt(stringSize + padding + 8);
|
||||
strTable0.write(out);
|
||||
out.put(new byte[padding]);
|
||||
}
|
||||
|
||||
for (PkgCtx pctx : ctxs) {
|
||||
if (out.position() != pctx.offset) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
final int basePosition = out.position();
|
||||
out.putInt(RES_TABLE_PACKAGE_TYPE | (0x011c << 16));
|
||||
out.putInt(pctx.pkgSize);
|
||||
out.putInt(pctx.pkg.id);
|
||||
int p = out.position();
|
||||
out.put(pctx.pkg.name.getBytes("UTF-16LE"));
|
||||
out.position(p + 256);
|
||||
|
||||
out.putInt(pctx.typeStringOff);
|
||||
out.putInt(pctx.typeNames0.size());
|
||||
|
||||
out.putInt(pctx.keyStringOff);
|
||||
out.putInt(pctx.keyNames0.size());
|
||||
|
||||
{
|
||||
if (out.position() - basePosition != pctx.typeStringOff) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
int stringSize = pctx.typeNames0.getSize();
|
||||
int padding = 0;
|
||||
if (stringSize % 4 != 0) {
|
||||
padding = 4 - stringSize % 4;
|
||||
}
|
||||
out.putInt(RES_STRING_POOL_TYPE | (0x001C << 16));
|
||||
out.putInt(stringSize + padding + 8);
|
||||
pctx.typeNames0.write(out);
|
||||
out.put(new byte[padding]);
|
||||
}
|
||||
|
||||
{
|
||||
if (out.position() - basePosition != pctx.keyStringOff) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
int stringSize = pctx.keyNames0.getSize();
|
||||
int padding = 0;
|
||||
if (stringSize % 4 != 0) {
|
||||
padding = 4 - stringSize % 4;
|
||||
}
|
||||
out.putInt(RES_STRING_POOL_TYPE | (0x001C << 16));
|
||||
out.putInt(stringSize + padding + 8);
|
||||
pctx.keyNames0.write(out);
|
||||
out.put(new byte[padding]);
|
||||
}
|
||||
|
||||
for (Type t : pctx.pkg.types.values()) {
|
||||
D("[%08x]write spec", out.position(), t.name);
|
||||
if (t.wPosition != out.position()) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
out.putInt(RES_TABLE_TYPE_SPEC_TYPE | (0x0010 << 16));
|
||||
out.putInt(4 * 4 + 4 * t.specs.length);// size
|
||||
|
||||
out.putInt(t.id);
|
||||
out.putInt(t.specs.length);
|
||||
for (ResSpec spec : t.specs) {
|
||||
out.putInt(spec.flags);
|
||||
}
|
||||
|
||||
for (Config config : t.configs) {
|
||||
D("[%08x]write config", out.position());
|
||||
int typeConfigPosition = out.position();
|
||||
if (config.wPosition != typeConfigPosition) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
out.putInt(RES_TABLE_TYPE_TYPE | (0x0038 << 16));
|
||||
out.putInt(config.wChunkSize);// size
|
||||
|
||||
out.putInt(t.id);
|
||||
out.putInt(t.specs.length);
|
||||
out.putInt(config.wEntryStart);
|
||||
|
||||
D("[%08x]write config ids", out.position());
|
||||
out.put(config.id);
|
||||
|
||||
int size0 = config.id.length;
|
||||
int padding = 0;
|
||||
if (size0 % 4 != 0) {
|
||||
padding = 4 - size0 % 4;
|
||||
}
|
||||
out.put(new byte[padding]);
|
||||
|
||||
out.position(typeConfigPosition + 0x0038);
|
||||
|
||||
D("[%08x]write config entry offsets", out.position());
|
||||
for (int i = 0; i < config.entryCount; i++) {
|
||||
ResEntry entry = config.resources.get(i);
|
||||
if (entry == null) {
|
||||
out.putInt(-1);
|
||||
} else {
|
||||
out.putInt(entry.wOffset);
|
||||
}
|
||||
}
|
||||
|
||||
if (out.position() - typeConfigPosition != config.wEntryStart) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
D("[%08x]write config entrys", out.position());
|
||||
for (ResEntry e : config.resources.values()) {
|
||||
D("[%08x]ResTable_entry", out.position());
|
||||
boolean isBag = e.value instanceof BagValue;
|
||||
out.putShort((short) (isBag ? 16 : 8));
|
||||
int flag = e.flag;
|
||||
if (isBag) { // add complex flag
|
||||
flag |= ArscParser.ENTRY_FLAG_COMPLEX;
|
||||
} else { // remove
|
||||
flag &= ~ArscParser.ENTRY_FLAG_COMPLEX;
|
||||
}
|
||||
out.putShort((short) flag);
|
||||
out.putInt(pctx.keyNames.get(e.spec.name).index);
|
||||
if (isBag) {
|
||||
BagValue bag = (BagValue) e.value;
|
||||
out.putInt(bag.parent);
|
||||
out.putInt(bag.map.size());
|
||||
for (Map.Entry<Integer, Value> entry : bag.map) {
|
||||
out.putInt(entry.getKey());
|
||||
writeValue(entry.getValue(), out);
|
||||
}
|
||||
} else {
|
||||
writeValue((Value) e.value, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void writeValue(Value value, ByteBuffer out) {
|
||||
out.putShort((short) 8);
|
||||
out.put((byte) 0);
|
||||
out.put((byte) value.type);
|
||||
if (value.type == ArscParser.TYPE_STRING) {
|
||||
out.putInt(strTable.get(value.raw).index);
|
||||
} else {
|
||||
out.putInt(value.data);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2009-2013 Panxiaobo
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package pxb.android.arsc;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
public class BagValue {
|
||||
public List<Map.Entry<Integer, Value>> map = new ArrayList<Entry<Integer, Value>>();
|
||||
public final int parent;
|
||||
|
||||
public BagValue(int parent) {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (!(obj instanceof BagValue))
|
||||
return false;
|
||||
BagValue other = (BagValue) obj;
|
||||
if (map == null) {
|
||||
if (other.map != null)
|
||||
return false;
|
||||
} else if (!map.equals(other.map))
|
||||
return false;
|
||||
if (parent != other.parent)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((map == null) ? 0 : map.hashCode());
|
||||
result = prime * result + parent;
|
||||
return result;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(String.format("{bag%08x", parent));
|
||||
for (Map.Entry<Integer, Value> e : map) {
|
||||
sb.append(",").append(String.format("0x%08x", e.getKey()));
|
||||
sb.append("=");
|
||||
sb.append(e.getValue());
|
||||
}
|
||||
|
||||
return sb.append("}").toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2009-2013 Panxiaobo
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package pxb.android.arsc;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
public class Config {
|
||||
public final int entryCount;
|
||||
public final byte[] id;
|
||||
public Map<Integer, ResEntry> resources = new TreeMap<Integer, ResEntry>();
|
||||
/* package */int wChunkSize;
|
||||
/* package */int wEntryStart;
|
||||
/* package */int wPosition;
|
||||
|
||||
public Config(byte[] id, int entryCount) {
|
||||
super();
|
||||
this.id = id;
|
||||
this.entryCount = entryCount;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2009-2013 Panxiaobo
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package pxb.android.arsc;
|
||||
|
||||
import java.util.TreeMap;
|
||||
|
||||
public class Pkg {
|
||||
public final int id;
|
||||
public String name;
|
||||
public TreeMap<Integer, Type> types = new TreeMap<Integer, Type>();
|
||||
|
||||
public Pkg(int id, String name) {
|
||||
super();
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Type getType(int tid, String name, int entrySize) {
|
||||
Type type = types.get(tid);
|
||||
if (type != null) {
|
||||
if (name != null) {
|
||||
if (type.name == null) {
|
||||
type.name = name;
|
||||
} else if (!name.endsWith(type.name)) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
if (type.specs.length != entrySize) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
type = new Type();
|
||||
type.id = tid;
|
||||
type.name = name;
|
||||
type.specs = new ResSpec[entrySize];
|
||||
types.put(tid, type);
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2009-2013 Panxiaobo
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package pxb.android.arsc;
|
||||
|
||||
public class ResEntry {
|
||||
public final int flag;
|
||||
|
||||
public final ResSpec spec;
|
||||
/**
|
||||
* {@link BagValue} or {@link Value}
|
||||
*/
|
||||
public Object value;
|
||||
|
||||
/* package */int wOffset;
|
||||
|
||||
public ResEntry(int flag, ResSpec spec) {
|
||||
super();
|
||||
this.flag = flag;
|
||||
this.spec = spec;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2009-2013 Panxiaobo
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package pxb.android.arsc;
|
||||
|
||||
public class ResSpec {
|
||||
public int flags;
|
||||
public final int id;
|
||||
public String name;
|
||||
|
||||
public ResSpec(int id) {
|
||||
super();
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public void updateName(String s) {
|
||||
String name = this.name;
|
||||
if (name == null) {
|
||||
this.name = s;
|
||||
} else if (!s.equals(name)) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2009-2013 Panxiaobo
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package pxb.android.arsc;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class Type {
|
||||
public List<Config> configs = new ArrayList<Config>();
|
||||
public int id;
|
||||
public String name;
|
||||
public ResSpec[] specs;
|
||||
/* package */int wPosition;
|
||||
|
||||
public void addConfig(Config config) {
|
||||
if (config.entryCount != specs.length) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
configs.add(config);
|
||||
}
|
||||
|
||||
public ResSpec getSpec(int resId) {
|
||||
ResSpec res = specs[resId];
|
||||
if (res == null) {
|
||||
res = new ResSpec(resId);
|
||||
specs[resId] = res;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue