Migrate to GitHub
This commit is contained in:
parent
f598a7d6a4
commit
628ac9fd33
|
|
@ -0,0 +1,14 @@
|
||||||
|
*.iml
|
||||||
|
.gradle
|
||||||
|
/local.properties
|
||||||
|
.idea
|
||||||
|
/.idea/caches/build_file_checksums.ser
|
||||||
|
/.idea/libraries
|
||||||
|
/.idea/modules.xml
|
||||||
|
/.idea/workspace.xml
|
||||||
|
.DS_Store
|
||||||
|
/build
|
||||||
|
/captures
|
||||||
|
/release
|
||||||
|
.externalNativeBuild
|
||||||
|
elf-cleaner.sh
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
/build
|
||||||
|
|
@ -0,0 +1,132 @@
|
||||||
|
import groovy.xml.XmlUtil
|
||||||
|
|
||||||
|
apply plugin: 'com.android.application'
|
||||||
|
|
||||||
|
gradle.projectsEvaluated {
|
||||||
|
tasks.withType(JavaCompile) {
|
||||||
|
options.compilerArgs.add('-Xbootclasspath/p:libs/framework-stub.jar')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion 28
|
||||||
|
buildToolsVersion '28.0.3'
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
multiDexEnabled false
|
||||||
|
minSdkVersion 23
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
main {
|
||||||
|
java.srcDirs += ['src/main/apacheCommonsLang']
|
||||||
|
jniLibs.srcDirs = ['libs']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled true
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only build the release variant
|
||||||
|
// variantFilter { variant ->
|
||||||
|
// if (variant.buildType.name != BuilderConstants.DEBUG) {
|
||||||
|
// variant.ignore = true
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
task generateStubs(type: Javadoc, dependsOn: 'compileReleaseSources') {
|
||||||
|
source = file('src/main/java')
|
||||||
|
ext.stubsDir = "$buildDir/api/stub-sources"
|
||||||
|
outputs.dir ext.stubsDir
|
||||||
|
title = null
|
||||||
|
|
||||||
|
options {
|
||||||
|
doclet = 'com.google.doclava.Doclava'
|
||||||
|
docletpath = fileTree(dir: 'doclib', include: '**/*.jar').asType(List)
|
||||||
|
jFlags '-Dignore.symbol.file'
|
||||||
|
addBooleanOption 'nodocs', true
|
||||||
|
addFileOption 'stubs', file(ext.stubsDir)
|
||||||
|
addFileOption 'api', file('doclib/api/current.txt')
|
||||||
|
addBooleanOption 'hide 111', true
|
||||||
|
addBooleanOption 'hide 113', true
|
||||||
|
addBooleanOption 'hidePackage xposed.dummy', true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task compileStubs(type: JavaCompile, dependsOn: 'generateStubs') {
|
||||||
|
source = fileTree(generateStubs.ext.stubsDir)
|
||||||
|
destinationDir = file("$buildDir/api/stub-classes")
|
||||||
|
options.compilerArgs += '-XDsuppressNotes'
|
||||||
|
}
|
||||||
|
|
||||||
|
task jarStubs(type: Jar) {
|
||||||
|
from compileStubs
|
||||||
|
destinationDir = file("$buildDir/api")
|
||||||
|
baseName = 'api'
|
||||||
|
}
|
||||||
|
|
||||||
|
task jarStubsSource(type: Jar) {
|
||||||
|
from generateStubs.source
|
||||||
|
destinationDir = jarStubs.destinationDir
|
||||||
|
baseName = jarStubs.baseName
|
||||||
|
classifier = 'sources'
|
||||||
|
}
|
||||||
|
|
||||||
|
task generateAPI(dependsOn: ['generateStubs', 'jarStubs', 'jarStubsSource'])
|
||||||
|
|
||||||
|
// Make sure that hiddenapistubs are placed before the Android SDK in app.iml
|
||||||
|
// as there doesn't seem to be any way to configure this in Android Studio.
|
||||||
|
task fixIml {
|
||||||
|
ext.imlFile = projectDir.absolutePath + '/' + project.name + '.iml'
|
||||||
|
inputs.file imlFile
|
||||||
|
outputs.file imlFile
|
||||||
|
|
||||||
|
println imlFile
|
||||||
|
doLast {
|
||||||
|
def imlFile = file(project.name + ".iml")
|
||||||
|
println 'Change ' + project.name + '.iml order'
|
||||||
|
try {
|
||||||
|
def parsedXml = (new 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 Node(parsedXml.component[1], 'orderEntry', ['type': 'jdk', 'jdkName': sdkString, 'jdkType': 'Android SDK'])
|
||||||
|
XmlUtil.serialize(parsedXml, new FileOutputStream(imlFile))
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
// nop, iml not found
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.preBuild.dependsOn fixIml
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compileOnly files("libs/framework-stub.jar")
|
||||||
|
compileOnly project(':dexmaker')
|
||||||
|
}
|
||||||
|
|
||||||
|
afterEvaluate {
|
||||||
|
|
||||||
|
task javac
|
||||||
|
|
||||||
|
tasks.withType(JavaCompile) {
|
||||||
|
options.compilerArgs.add('-Xbootclasspath/p:libs/framework-stub.jar')
|
||||||
|
}
|
||||||
|
|
||||||
|
android.applicationVariants.all { variant ->
|
||||||
|
def nameCapped = variant.name.capitalize()
|
||||||
|
def nameLowered = variant.name.toLowerCase()
|
||||||
|
|
||||||
|
def makeAndCopyTask = task("makeAndCopy${nameCapped}", type: Copy, dependsOn: "assemble${nameCapped}") {
|
||||||
|
from "build/intermediates/transforms/dexMerger/${nameLowered}/0/classes.dex"
|
||||||
|
into '../Core/template_override/system/framework'
|
||||||
|
rename("classes.dex", "edxposed.dex")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Binary file not shown.
|
|
@ -0,0 +1,30 @@
|
||||||
|
# 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
|
||||||
|
|
||||||
|
-keep class de.robv.android.xposed.** {*;}
|
||||||
|
-keep class android.** { *; }
|
||||||
|
|
||||||
|
-keep interface com.elderdrivers.riru.common.KeepAll
|
||||||
|
-keep interface com.elderdrivers.riru.common.KeepMembers
|
||||||
|
|
||||||
|
-keep class * implements com.elderdrivers.riru.common.KeepAll { *; }
|
||||||
|
-keepclassmembers class * implements com.elderdrivers.riru.common.KeepMembers { *; }
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
<manifest package="com.elderdrivers.riru.xposed"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"/>
|
||||||
|
|
@ -0,0 +1,202 @@
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
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
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
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())
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
$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
|
||||||
5797
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/ArrayUtils.java
vendored
Normal file
5797
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/ArrayUtils.java
vendored
Normal file
File diff suppressed because it is too large
Load Diff
197
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/CharSequenceUtils.java
vendored
Normal file
197
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/CharSequenceUtils.java
vendored
Normal file
|
|
@ -0,0 +1,197 @@
|
||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
539
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/CharUtils.java
vendored
Normal file
539
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/CharUtils.java
vendored
Normal file
|
|
@ -0,0 +1,539 @@
|
||||||
|
/*
|
||||||
|
* 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');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
1135
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/ClassUtils.java
vendored
Normal file
1135
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/ClassUtils.java
vendored
Normal file
File diff suppressed because it is too large
Load Diff
168
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/JavaVersion.java
vendored
Normal file
168
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/JavaVersion.java
vendored
Normal file
|
|
@ -0,0 +1,168 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
609
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/ObjectUtils.java
vendored
Normal file
609
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/ObjectUtils.java
vendored
Normal file
|
|
@ -0,0 +1,609 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
6582
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/StringUtils.java
vendored
Normal file
6582
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/StringUtils.java
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1443
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/SystemUtils.java
vendored
Normal file
1443
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/SystemUtils.java
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1070
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/Validate.java
vendored
Normal file
1070
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/Validate.java
vendored
Normal file
File diff suppressed because it is too large
Load Diff
89
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/builder/Builder.java
vendored
Normal file
89
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/builder/Builder.java
vendored
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
/*
|
||||||
|
* 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();
|
||||||
|
}
|
||||||
1020
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/builder/CompareToBuilder.java
vendored
Normal file
1020
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/builder/CompareToBuilder.java
vendored
Normal file
File diff suppressed because it is too large
Load Diff
945
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/builder/EqualsBuilder.java
vendored
Normal file
945
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/builder/EqualsBuilder.java
vendored
Normal file
|
|
@ -0,0 +1,945 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
961
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/builder/HashCodeBuilder.java
vendored
Normal file
961
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/builder/HashCodeBuilder.java
vendored
Normal file
|
|
@ -0,0 +1,961 @@
|
||||||
|
/*
|
||||||
|
* 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
74
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/builder/IDKey.java
vendored
Normal file
74
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/builder/IDKey.java
vendored
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,691 @@
|
||||||
|
/*
|
||||||
|
* 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
1079
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/builder/ToStringBuilder.java
vendored
Normal file
1079
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/builder/ToStringBuilder.java
vendored
Normal file
File diff suppressed because it is too large
Load Diff
2271
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/builder/ToStringStyle.java
vendored
Normal file
2271
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/builder/ToStringStyle.java
vendored
Normal file
File diff suppressed because it is too large
Load Diff
28
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/builder/package.html
vendored
Normal file
28
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/builder/package.html
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
<!--
|
||||||
|
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>
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
27
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/exception/package.html
vendored
Normal file
27
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/exception/package.html
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
<!--
|
||||||
|
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>
|
||||||
54
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/mutable/Mutable.java
vendored
Normal file
54
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/mutable/Mutable.java
vendored
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
|
||||||
|
}
|
||||||
273
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/mutable/MutableInt.java
vendored
Normal file
273
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/mutable/MutableInt.java
vendored
Normal file
|
|
@ -0,0 +1,273 @@
|
||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
29
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/mutable/package.html
vendored
Normal file
29
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/mutable/package.html
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
<!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>
|
||||||
23
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/overview.html
vendored
Normal file
23
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/overview.html
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
<!--
|
||||||
|
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>
|
||||||
25
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/package.html
vendored
Normal file
25
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/package.html
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
<!--
|
||||||
|
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>
|
||||||
185
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/reflect/MemberUtils.java
vendored
Normal file
185
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/reflect/MemberUtils.java
vendored
Normal file
|
|
@ -0,0 +1,185 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
537
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/reflect/MethodUtils.java
vendored
Normal file
537
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/reflect/MethodUtils.java
vendored
Normal file
|
|
@ -0,0 +1,537 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
29
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/reflect/package.html
vendored
Normal file
29
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/reflect/package.html
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
<!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>
|
||||||
103
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/tuple/ImmutablePair.java
vendored
Normal file
103
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/tuple/ImmutablePair.java
vendored
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
/*
|
||||||
|
* 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
177
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/tuple/Pair.java
vendored
Normal file
177
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/tuple/Pair.java
vendored
Normal file
|
|
@ -0,0 +1,177 @@
|
||||||
|
/*
|
||||||
|
* 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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
22
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/tuple/package.html
vendored
Normal file
22
Bridge/src/main/apacheCommonsLang/external/org/apache/commons/lang3/tuple/package.html
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
<!--
|
||||||
|
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>
|
||||||
|
|
@ -0,0 +1,223 @@
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
/**
|
||||||
|
* Contains {@link android.app.AndroidAppHelper} with various methods for information about the current app.
|
||||||
|
*/
|
||||||
|
package android.app;
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
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
|
|
@ -0,0 +1,4 @@
|
||||||
|
/**
|
||||||
|
* Contains classes that are required for replacing resources.
|
||||||
|
*/
|
||||||
|
package android.content.res;
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
package com.elderdrivers.riru.common;
|
||||||
|
|
||||||
|
public interface KeepAll {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
package com.elderdrivers.riru.common;
|
||||||
|
|
||||||
|
public interface KeepMembers {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
package com.elderdrivers.riru.xposed;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
|
import com.elderdrivers.riru.common.KeepAll;
|
||||||
|
import com.elderdrivers.riru.xposed.core.HookMethodResolver;
|
||||||
|
import com.elderdrivers.riru.xposed.entry.Router;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
@SuppressLint("DefaultLocale")
|
||||||
|
public class Main implements KeepAll {
|
||||||
|
|
||||||
|
// private static String sForkAndSpecializePramsStr = "";
|
||||||
|
// private static String sForkSystemServerPramsStr = "";
|
||||||
|
public static String sAppDataDir = "";
|
||||||
|
|
||||||
|
static {
|
||||||
|
init(Build.VERSION.SDK_INT);
|
||||||
|
HookMethodResolver.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// entry points
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
public static void forkAndSpecializePre(int uid, int gid, int[] gids, int debugFlags,
|
||||||
|
int[][] rlimits, int mountExternal, String seInfo,
|
||||||
|
String niceName, int[] fdsToClose, int[] fdsToIgnore,
|
||||||
|
boolean startChildZygote, String instructionSet, String appDataDir) {
|
||||||
|
// sForkAndSpecializePramsStr = String.format(
|
||||||
|
// "Zygote#forkAndSpecialize(%d, %d, %s, %d, %s, %d, %s, %s, %s, %s, %s, %s, %s)",
|
||||||
|
// uid, gid, Arrays.toString(gids), debugFlags, Arrays.toString(rlimits),
|
||||||
|
// mountExternal, seInfo, niceName, Arrays.toString(fdsToClose),
|
||||||
|
// Arrays.toString(fdsToIgnore), startChildZygote, instructionSet, appDataDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void forkAndSpecializePost(int pid, String appDataDir) {
|
||||||
|
// Utils.logD(sForkAndSpecializePramsStr + " = " + pid);
|
||||||
|
if (pid == 0) {
|
||||||
|
// in app process
|
||||||
|
sAppDataDir = appDataDir;
|
||||||
|
Router.onProcessForked(false);
|
||||||
|
} else {
|
||||||
|
// in zygote process, res is child zygote pid
|
||||||
|
// don't print log here, see https://github.com/RikkaApps/Riru/blob/77adfd6a4a6a81bfd20569c910bc4854f2f84f5e/riru-core/jni/main/jni_native_method.cpp#L55-L66
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void forkSystemServerPre(int uid, int gid, int[] gids, int debugFlags, int[][] rlimits,
|
||||||
|
long permittedCapabilities, long effectiveCapabilities) {
|
||||||
|
// sForkSystemServerPramsStr = String.format("Zygote#forkSystemServer(%d, %d, %s, %d, %s, %d, %d)",
|
||||||
|
// uid, gid, Arrays.toString(gids), debugFlags, Arrays.toString(rlimits),
|
||||||
|
// permittedCapabilities, effectiveCapabilities);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void forkSystemServerPost(int pid) {
|
||||||
|
// Utils.logD(sForkSystemServerPramsStr + " = " + pid);
|
||||||
|
if (pid == 0) {
|
||||||
|
// in system_server process
|
||||||
|
sAppDataDir = "/data/data/android/";
|
||||||
|
Router.onProcessForked(true);
|
||||||
|
} else {
|
||||||
|
// in zygote process, res is child zygote pid
|
||||||
|
// don't print log here, see https://github.com/RikkaApps/Riru/blob/77adfd6a4a6a81bfd20569c910bc4854f2f84f5e/riru-core/jni/main/jni_native_method.cpp#L55-L66
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// native methods
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
public static native boolean backupAndHookNative(Object target, Method hook, Method backup);
|
||||||
|
|
||||||
|
public static native void ensureMethodCached(Method hook, Method backup);
|
||||||
|
|
||||||
|
// JNI.ToReflectedMethod() could return either Method or Constructor
|
||||||
|
public static native Object findMethodNative(Class targetClass, String methodName, String methodSig);
|
||||||
|
|
||||||
|
private static native void init(int SDK_version);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,167 @@
|
||||||
|
package com.elderdrivers.riru.xposed.core;
|
||||||
|
|
||||||
|
import com.elderdrivers.riru.xposed.util.Utils;
|
||||||
|
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import static com.elderdrivers.riru.xposed.Main.backupAndHookNative;
|
||||||
|
import static com.elderdrivers.riru.xposed.Main.findMethodNative;
|
||||||
|
|
||||||
|
public class HookMain {
|
||||||
|
|
||||||
|
public static void doHookDefault(ClassLoader patchClassLoader, ClassLoader originClassLoader, String hookInfoClassName) {
|
||||||
|
try {
|
||||||
|
Class<?> hookInfoClass = Class.forName(hookInfoClassName, true, patchClassLoader);
|
||||||
|
String[] hookItemNames = (String[]) hookInfoClass.getField("hookItemNames").get(null);
|
||||||
|
for (String hookItemName : hookItemNames) {
|
||||||
|
doHookItemDefault(patchClassLoader, hookItemName, originClassLoader);
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
Utils.logE("error when hooking all in: " + hookInfoClassName, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void doHookItemDefault(ClassLoader patchClassLoader, String hookItemName, ClassLoader originClassLoader) {
|
||||||
|
try {
|
||||||
|
Utils.logD("Start hooking with item " + hookItemName);
|
||||||
|
Class<?> hookItem = Class.forName(hookItemName, true, patchClassLoader);
|
||||||
|
|
||||||
|
String className = (String) hookItem.getField("className").get(null);
|
||||||
|
String methodName = (String) hookItem.getField("methodName").get(null);
|
||||||
|
String methodSig = (String) hookItem.getField("methodSig").get(null);
|
||||||
|
|
||||||
|
if (className == null || className.equals("")) {
|
||||||
|
Utils.logW("No target class. Skipping...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Class<?> clazz = null;
|
||||||
|
try {
|
||||||
|
clazz = Class.forName(className, true, originClassLoader);
|
||||||
|
} catch (ClassNotFoundException cnfe) {
|
||||||
|
Utils.logE(className + " not found in " + originClassLoader);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (Modifier.isAbstract(clazz.getModifiers())) {
|
||||||
|
Utils.logW("Hook may fail for abstract class: " + className);
|
||||||
|
}
|
||||||
|
|
||||||
|
Method hook = null;
|
||||||
|
Method backup = null;
|
||||||
|
for (Method method : hookItem.getDeclaredMethods()) {
|
||||||
|
if (method.getName().equals("hook") && Modifier.isStatic(method.getModifiers())) {
|
||||||
|
hook = method;
|
||||||
|
} else if (method.getName().equals("backup") && Modifier.isStatic(method.getModifiers())) {
|
||||||
|
backup = method;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hook == null) {
|
||||||
|
Utils.logE("Cannot find hook for " + methodName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
findAndBackupAndHook(clazz, methodName, methodSig, hook, backup);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
Utils.logE("error when hooking " + hookItemName, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void findAndHook(Class targetClass, String methodName, String methodSig, Method hook) {
|
||||||
|
hook(findMethod(targetClass, methodName, methodSig), hook);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void findAndBackupAndHook(Class targetClass, String methodName, String methodSig,
|
||||||
|
Method hook, Method backup) {
|
||||||
|
backupAndHook(findMethod(targetClass, methodName, methodSig), hook, backup);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void hook(Object target, Method hook) {
|
||||||
|
backupAndHook(target, hook, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void backupAndHook(Object target, Method hook, Method backup) {
|
||||||
|
if (target == null) {
|
||||||
|
throw new IllegalArgumentException("null target method");
|
||||||
|
}
|
||||||
|
if (hook == null) {
|
||||||
|
throw new IllegalArgumentException("null hook method");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Modifier.isStatic(hook.getModifiers())) {
|
||||||
|
throw new IllegalArgumentException("Hook must be a static method: " + hook);
|
||||||
|
}
|
||||||
|
checkCompatibleMethods(target, hook, "Original", "Hook");
|
||||||
|
if (backup != null) {
|
||||||
|
if (!Modifier.isStatic(backup.getModifiers())) {
|
||||||
|
throw new IllegalArgumentException("Backup must be a static method: " + backup);
|
||||||
|
}
|
||||||
|
// backup is just a placeholder and the constraint could be less strict
|
||||||
|
checkCompatibleMethods(target, backup, "Original", "Backup");
|
||||||
|
}
|
||||||
|
if (backup != null) {
|
||||||
|
HookMethodResolver.resolveMethod(hook, backup);
|
||||||
|
}
|
||||||
|
if (!backupAndHookNative(target, hook, backup)) {
|
||||||
|
throw new RuntimeException("Failed to hook " + target + " with " + hook);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Object findMethod(Class cls, String methodName, String methodSig) {
|
||||||
|
if (cls == null) {
|
||||||
|
throw new IllegalArgumentException("null class");
|
||||||
|
}
|
||||||
|
if (methodName == null) {
|
||||||
|
throw new IllegalArgumentException("null method name");
|
||||||
|
}
|
||||||
|
if (methodSig == null) {
|
||||||
|
throw new IllegalArgumentException("null method signature");
|
||||||
|
}
|
||||||
|
return findMethodNative(cls, methodName, methodSig);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void checkCompatibleMethods(Object original, Method replacement, String originalName, String replacementName) {
|
||||||
|
ArrayList<Class<?>> originalParams;
|
||||||
|
if (original instanceof Method) {
|
||||||
|
originalParams = new ArrayList<>(Arrays.asList(((Method) original).getParameterTypes()));
|
||||||
|
} else if (original instanceof Constructor) {
|
||||||
|
originalParams = new ArrayList<>(Arrays.asList(((Constructor<?>) original).getParameterTypes()));
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Type of target method is wrong");
|
||||||
|
}
|
||||||
|
|
||||||
|
ArrayList<Class<?>> replacementParams = new ArrayList<>(Arrays.asList(replacement.getParameterTypes()));
|
||||||
|
|
||||||
|
if (original instanceof Method
|
||||||
|
&& !Modifier.isStatic(((Method) original).getModifiers())) {
|
||||||
|
originalParams.add(0, ((Method) original).getDeclaringClass());
|
||||||
|
} else if (original instanceof Constructor) {
|
||||||
|
originalParams.add(0, ((Constructor<?>) original).getDeclaringClass());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (!Modifier.isStatic(replacement.getModifiers())) {
|
||||||
|
replacementParams.add(0, replacement.getDeclaringClass());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (original instanceof Method
|
||||||
|
&& !replacement.getReturnType().isAssignableFrom(((Method) original).getReturnType())) {
|
||||||
|
throw new IllegalArgumentException("Incompatible return types. " + originalName + ": " + ((Method) original).getReturnType() + ", " + replacementName + ": " + replacement.getReturnType());
|
||||||
|
} else if (original instanceof Constructor) {
|
||||||
|
if (replacement.getReturnType().equals(Void.class)) {
|
||||||
|
throw new IllegalArgumentException("Incompatible return types. " + "<init>" + ": " + "V" + ", " + replacementName + ": " + replacement.getReturnType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (originalParams.size() != replacementParams.size()) {
|
||||||
|
throw new IllegalArgumentException("Number of arguments don't match. " + originalName + ": " + originalParams.size() + ", " + replacementName + ": " + replacementParams.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < originalParams.size(); i++) {
|
||||||
|
if (!replacementParams.get(i).isAssignableFrom(originalParams.get(i))) {
|
||||||
|
throw new IllegalArgumentException("Incompatible argument #" + i + ": " + originalName + ": " + originalParams.get(i) + ", " + replacementName + ": " + replacementParams.get(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,156 @@
|
||||||
|
package com.elderdrivers.riru.xposed.core;
|
||||||
|
|
||||||
|
import android.os.Build;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.elderdrivers.riru.xposed.Main;
|
||||||
|
import com.elderdrivers.riru.xposed.util.Utils;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* create by Swift Gan on 14/01/2019
|
||||||
|
* To ensure method in resolved cache
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class HookMethodResolver {
|
||||||
|
|
||||||
|
public static Class artMethodClass;
|
||||||
|
|
||||||
|
public static Field resolvedMethodsField;
|
||||||
|
public static Field dexCacheField;
|
||||||
|
public static Field dexMethodIndexField;
|
||||||
|
public static Field artMethodField;
|
||||||
|
|
||||||
|
public static boolean canResolvedInJava = false;
|
||||||
|
public static boolean isArtMethod = false;
|
||||||
|
|
||||||
|
public static long resolvedMethodsAddress = 0;
|
||||||
|
public static int dexMethodIndex = 0;
|
||||||
|
|
||||||
|
public static Method testMethod;
|
||||||
|
public static Object testArtMethod;
|
||||||
|
|
||||||
|
public static void init() {
|
||||||
|
checkSupport();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void checkSupport() {
|
||||||
|
try {
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
isArtMethod = false;
|
||||||
|
canResolvedInJava = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
testMethod = HookMethodResolver.class.getDeclaredMethod("init");
|
||||||
|
artMethodField = getField(Method.class, "artMethod");
|
||||||
|
|
||||||
|
testArtMethod = artMethodField.get(testMethod);
|
||||||
|
|
||||||
|
if (hasJavaArtMethod() && testArtMethod.getClass() == artMethodClass) {
|
||||||
|
checkSupportForArtMethod();
|
||||||
|
isArtMethod = true;
|
||||||
|
} else if (testArtMethod instanceof Long) {
|
||||||
|
checkSupportForArtMethodId();
|
||||||
|
isArtMethod = false;
|
||||||
|
} else {
|
||||||
|
canResolvedInJava = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Throwable throwable) {
|
||||||
|
Utils.logE("error when checkSupport", throwable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// may 5.0
|
||||||
|
private static void checkSupportForArtMethod() throws Exception {
|
||||||
|
dexMethodIndexField = getField(artMethodClass, "dexMethodIndex");
|
||||||
|
dexCacheField = getField(Class.class, "dexCache");
|
||||||
|
Object dexCache = dexCacheField.get(testMethod.getDeclaringClass());
|
||||||
|
resolvedMethodsField = getField(dexCache.getClass(), "resolvedMethods");
|
||||||
|
if (resolvedMethodsField.get(dexCache) instanceof Object[]) {
|
||||||
|
canResolvedInJava = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// may 6.0
|
||||||
|
private static void checkSupportForArtMethodId() throws Exception {
|
||||||
|
dexMethodIndexField = getField(Method.class, "dexMethodIndex");
|
||||||
|
dexMethodIndex = (int) dexMethodIndexField.get(testMethod);
|
||||||
|
dexCacheField = getField(Class.class, "dexCache");
|
||||||
|
Object dexCache = dexCacheField.get(testMethod.getDeclaringClass());
|
||||||
|
resolvedMethodsField = getField(dexCache.getClass(), "resolvedMethods");
|
||||||
|
Object resolvedMethods = resolvedMethodsField.get(dexCache);
|
||||||
|
if (resolvedMethods instanceof Long) {
|
||||||
|
canResolvedInJava = false;
|
||||||
|
resolvedMethodsAddress = (long) resolvedMethods;
|
||||||
|
} else if (resolvedMethods instanceof long[]) {
|
||||||
|
canResolvedInJava = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void resolveMethod(Method hook, Method backup) {
|
||||||
|
if (canResolvedInJava && artMethodField != null) {
|
||||||
|
// in java
|
||||||
|
try {
|
||||||
|
resolveInJava(hook, backup);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// in native
|
||||||
|
resolveInNative(hook, backup);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// in native
|
||||||
|
resolveInNative(hook, backup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void resolveInJava(Method hook, Method backup) throws Exception {
|
||||||
|
Object dexCache = dexCacheField.get(hook.getDeclaringClass());
|
||||||
|
if (isArtMethod) {
|
||||||
|
Object artMethod = artMethodField.get(backup);
|
||||||
|
int dexMethodIndex = (int) dexMethodIndexField.get(artMethod);
|
||||||
|
Object resolvedMethods = resolvedMethodsField.get(dexCache);
|
||||||
|
((Object[])resolvedMethods)[dexMethodIndex] = artMethod;
|
||||||
|
} else {
|
||||||
|
int dexMethodIndex = (int) dexMethodIndexField.get(backup);
|
||||||
|
Object resolvedMethods = resolvedMethodsField.get(dexCache);
|
||||||
|
long artMethod = (long) artMethodField.get(backup);
|
||||||
|
((long[])resolvedMethods)[dexMethodIndex] = artMethod;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void resolveInNative(Method hook, Method backup) {
|
||||||
|
Main.ensureMethodCached(hook, backup);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Field getField(Class topClass, String fieldName) throws NoSuchFieldException {
|
||||||
|
while (topClass != null && topClass != Object.class) {
|
||||||
|
try {
|
||||||
|
Field field = topClass.getDeclaredField(fieldName);
|
||||||
|
field.setAccessible(true);
|
||||||
|
return field;
|
||||||
|
} catch (Exception e) {
|
||||||
|
}
|
||||||
|
topClass = topClass.getSuperclass();
|
||||||
|
}
|
||||||
|
throw new NoSuchFieldException(fieldName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean hasJavaArtMethod() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (artMethodClass != null)
|
||||||
|
return true;
|
||||||
|
try {
|
||||||
|
artMethodClass = Class.forName("java.lang.reflect.ArtMethod");
|
||||||
|
return true;
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
package com.elderdrivers.riru.xposed.dexmaker;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.elderdrivers.riru.xposed.BuildConfig;
|
||||||
|
|
||||||
|
public class DexLog {
|
||||||
|
|
||||||
|
public static final String TAG = "EdXposed-dexmaker";
|
||||||
|
|
||||||
|
public static int v(String s) {
|
||||||
|
return Log.v(TAG, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int i(String s) {
|
||||||
|
return Log.i(TAG, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int d(String s) {
|
||||||
|
if (BuildConfig.DEBUG) {
|
||||||
|
return Log.d(TAG, s);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int w(String s) {
|
||||||
|
return Log.w(TAG, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int e(String s) {
|
||||||
|
return Log.e(TAG, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int e(String s, Throwable t) {
|
||||||
|
return Log.e(TAG, s, t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,186 @@
|
||||||
|
package com.elderdrivers.riru.xposed.dexmaker;
|
||||||
|
|
||||||
|
import external.com.android.dx.Code;
|
||||||
|
import external.com.android.dx.Local;
|
||||||
|
import external.com.android.dx.TypeId;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class DexMakerUtils {
|
||||||
|
|
||||||
|
public static void autoBoxIfNecessary(Code code, Local<Object> target, Local source) {
|
||||||
|
String boxMethod = "valueOf";
|
||||||
|
TypeId<?> boxTypeId;
|
||||||
|
TypeId typeId = source.getType();
|
||||||
|
if (typeId.equals(TypeId.BOOLEAN)) {
|
||||||
|
boxTypeId = TypeId.get(Boolean.class);
|
||||||
|
code.invokeStatic(boxTypeId.getMethod(boxTypeId, boxMethod, TypeId.BOOLEAN), target, source);
|
||||||
|
} else if (typeId.equals(TypeId.BYTE)) {
|
||||||
|
boxTypeId = TypeId.get(Byte.class);
|
||||||
|
code.invokeStatic(boxTypeId.getMethod(boxTypeId, boxMethod, TypeId.BYTE), target, source);
|
||||||
|
} else if (typeId.equals(TypeId.CHAR)) {
|
||||||
|
boxTypeId = TypeId.get(Character.class);
|
||||||
|
code.invokeStatic(boxTypeId.getMethod(boxTypeId, boxMethod, TypeId.CHAR), target, source);
|
||||||
|
} else if (typeId.equals(TypeId.DOUBLE)) {
|
||||||
|
boxTypeId = TypeId.get(Double.class);
|
||||||
|
code.invokeStatic(boxTypeId.getMethod(boxTypeId, boxMethod, TypeId.DOUBLE), target, source);
|
||||||
|
} else if (typeId.equals(TypeId.FLOAT)) {
|
||||||
|
boxTypeId = TypeId.get(Float.class);
|
||||||
|
code.invokeStatic(boxTypeId.getMethod(boxTypeId, boxMethod, TypeId.FLOAT), target, source);
|
||||||
|
} else if (typeId.equals(TypeId.INT)) {
|
||||||
|
boxTypeId = TypeId.get(Integer.class);
|
||||||
|
code.invokeStatic(boxTypeId.getMethod(boxTypeId, boxMethod, TypeId.INT), target, source);
|
||||||
|
} else if (typeId.equals(TypeId.LONG)) {
|
||||||
|
boxTypeId = TypeId.get(Long.class);
|
||||||
|
code.invokeStatic(boxTypeId.getMethod(boxTypeId, boxMethod, TypeId.LONG), target, source);
|
||||||
|
} else if (typeId.equals(TypeId.SHORT)) {
|
||||||
|
boxTypeId = TypeId.get(Short.class);
|
||||||
|
code.invokeStatic(boxTypeId.getMethod(boxTypeId, boxMethod, TypeId.SHORT), target, source);
|
||||||
|
} else if (typeId.equals(TypeId.VOID)) {
|
||||||
|
code.loadConstant(target, null);
|
||||||
|
} else {
|
||||||
|
code.move(target, source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void autoUnboxIfNecessary(Code code, Local target, Local source) {
|
||||||
|
String unboxMethod;
|
||||||
|
TypeId typeId = target.getType();
|
||||||
|
TypeId<?> boxTypeId;
|
||||||
|
if (typeId.equals(TypeId.BOOLEAN)) {
|
||||||
|
unboxMethod = "booleanValue";
|
||||||
|
boxTypeId = TypeId.get(Boolean.class);
|
||||||
|
code.invokeVirtual(boxTypeId.getMethod(TypeId.BOOLEAN, unboxMethod), target, source);
|
||||||
|
} else if (typeId.equals(TypeId.BYTE)) {
|
||||||
|
unboxMethod = "byteValue";
|
||||||
|
boxTypeId = TypeId.get(Byte.class);
|
||||||
|
code.invokeVirtual(boxTypeId.getMethod(TypeId.BYTE, unboxMethod), target, source);
|
||||||
|
} else if (typeId.equals(TypeId.CHAR)) {
|
||||||
|
unboxMethod = "charValue";
|
||||||
|
boxTypeId = TypeId.get(Character.class);
|
||||||
|
code.invokeVirtual(boxTypeId.getMethod(TypeId.CHAR, unboxMethod), target, source);
|
||||||
|
} else if (typeId.equals(TypeId.DOUBLE)) {
|
||||||
|
unboxMethod = "doubleValue";
|
||||||
|
boxTypeId = TypeId.get(Double.class);
|
||||||
|
code.invokeVirtual(boxTypeId.getMethod(TypeId.DOUBLE, unboxMethod), target, source);
|
||||||
|
} else if (typeId.equals(TypeId.FLOAT)) {
|
||||||
|
unboxMethod = "floatValue";
|
||||||
|
boxTypeId = TypeId.get(Float.class);
|
||||||
|
code.invokeVirtual(boxTypeId.getMethod(TypeId.FLOAT, unboxMethod), target, source);
|
||||||
|
} else if (typeId.equals(TypeId.INT)) {
|
||||||
|
unboxMethod = "intValue";
|
||||||
|
boxTypeId = TypeId.get(Integer.class);
|
||||||
|
code.invokeVirtual(boxTypeId.getMethod(TypeId.INT, unboxMethod), target, source);
|
||||||
|
} else if (typeId.equals(TypeId.LONG)) {
|
||||||
|
unboxMethod = "longValue";
|
||||||
|
boxTypeId = TypeId.get(Long.class);
|
||||||
|
code.invokeVirtual(boxTypeId.getMethod(TypeId.LONG, unboxMethod), target, source);
|
||||||
|
} else if (typeId.equals(TypeId.SHORT)) {
|
||||||
|
unboxMethod = "shortValue";
|
||||||
|
boxTypeId = TypeId.get(Short.class);
|
||||||
|
code.invokeVirtual(boxTypeId.getMethod(TypeId.SHORT, unboxMethod), target, source);
|
||||||
|
} else if (typeId.equals(TypeId.VOID)) {
|
||||||
|
code.loadConstant(target, null);
|
||||||
|
} else {
|
||||||
|
code.move(target, source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Map<TypeId, Local> createResultLocals(Code code) {
|
||||||
|
HashMap<TypeId, Local> resultMap = new HashMap<>();
|
||||||
|
Local<Boolean> booleanLocal = code.newLocal(TypeId.BOOLEAN);
|
||||||
|
Local<Byte> byteLocal = code.newLocal(TypeId.BYTE);
|
||||||
|
Local<Character> charLocal = code.newLocal(TypeId.CHAR);
|
||||||
|
Local<Double> doubleLocal = code.newLocal(TypeId.DOUBLE);
|
||||||
|
Local<Float> floatLocal = code.newLocal(TypeId.FLOAT);
|
||||||
|
Local<Integer> intLocal = code.newLocal(TypeId.INT);
|
||||||
|
Local<Long> longLocal = code.newLocal(TypeId.LONG);
|
||||||
|
Local<Short> shortLocal = code.newLocal(TypeId.SHORT);
|
||||||
|
Local<Void> voidLocal = code.newLocal(TypeId.VOID);
|
||||||
|
Local<Object> objectLocal = code.newLocal(TypeId.OBJECT);
|
||||||
|
|
||||||
|
Local<Object> booleanObjLocal = code.newLocal(TypeId.get("Ljava/lang/Boolean;"));
|
||||||
|
Local<Object> byteObjLocal = code.newLocal(TypeId.get("Ljava/lang/Byte;"));
|
||||||
|
Local<Object> charObjLocal = code.newLocal(TypeId.get("Ljava/lang/Character;"));
|
||||||
|
Local<Object> doubleObjLocal = code.newLocal(TypeId.get("Ljava/lang/Double;"));
|
||||||
|
Local<Object> floatObjLocal = code.newLocal(TypeId.get("Ljava/lang/Float;"));
|
||||||
|
Local<Object> intObjLocal = code.newLocal(TypeId.get("Ljava/lang/Integer;"));
|
||||||
|
Local<Object> longObjLocal = code.newLocal(TypeId.get("Ljava/lang/Long;"));
|
||||||
|
Local<Object> shortObjLocal = code.newLocal(TypeId.get("Ljava/lang/Short;"));
|
||||||
|
Local<Object> voidObjLocal = code.newLocal(TypeId.get("Ljava/lang/Void;"));
|
||||||
|
|
||||||
|
// backup need initialized locals
|
||||||
|
code.loadConstant(booleanLocal, Boolean.valueOf(false));
|
||||||
|
code.loadConstant(byteLocal, Byte.valueOf("0"));
|
||||||
|
code.loadConstant(charLocal, Character.valueOf('\0'));
|
||||||
|
code.loadConstant(floatLocal, Float.valueOf(0));
|
||||||
|
code.loadConstant(intLocal, 0);
|
||||||
|
code.loadConstant(longLocal, Long.valueOf(0));
|
||||||
|
code.loadConstant(shortLocal, Short.valueOf("0"));
|
||||||
|
code.loadConstant(voidLocal, null);
|
||||||
|
code.loadConstant(objectLocal, null);
|
||||||
|
// all to null
|
||||||
|
code.loadConstant(booleanObjLocal, null);
|
||||||
|
code.loadConstant(byteObjLocal, null);
|
||||||
|
code.loadConstant(charObjLocal, null);
|
||||||
|
code.loadConstant(floatObjLocal, null);
|
||||||
|
code.loadConstant(intObjLocal, null);
|
||||||
|
code.loadConstant(longObjLocal, null);
|
||||||
|
code.loadConstant(shortObjLocal, null);
|
||||||
|
code.loadConstant(voidObjLocal, null);
|
||||||
|
// package all
|
||||||
|
resultMap.put(TypeId.BOOLEAN, booleanLocal);
|
||||||
|
resultMap.put(TypeId.BYTE, byteLocal);
|
||||||
|
resultMap.put(TypeId.CHAR, charLocal);
|
||||||
|
resultMap.put(TypeId.DOUBLE, doubleLocal);
|
||||||
|
resultMap.put(TypeId.FLOAT, floatLocal);
|
||||||
|
resultMap.put(TypeId.INT, intLocal);
|
||||||
|
resultMap.put(TypeId.LONG, longLocal);
|
||||||
|
resultMap.put(TypeId.SHORT, shortLocal);
|
||||||
|
resultMap.put(TypeId.VOID, voidLocal);
|
||||||
|
resultMap.put(TypeId.OBJECT, objectLocal);
|
||||||
|
|
||||||
|
resultMap.put(TypeId.get("Ljava/lang/Boolean;"), booleanObjLocal);
|
||||||
|
resultMap.put(TypeId.get("Ljava/lang/Byte;"), byteObjLocal);
|
||||||
|
resultMap.put(TypeId.get("Ljava/lang/Character;"), charObjLocal);
|
||||||
|
resultMap.put(TypeId.get("Ljava/lang/Double;"), doubleObjLocal);
|
||||||
|
resultMap.put(TypeId.get("Ljava/lang/Float;"), floatObjLocal);
|
||||||
|
resultMap.put(TypeId.get("Ljava/lang/Integer;"), intObjLocal);
|
||||||
|
resultMap.put(TypeId.get("Ljava/lang/Long;"), longObjLocal);
|
||||||
|
resultMap.put(TypeId.get("Ljava/lang/Short;"), shortObjLocal);
|
||||||
|
resultMap.put(TypeId.get("Ljava/lang/Void;"), voidObjLocal);
|
||||||
|
|
||||||
|
return resultMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TypeId getObjTypeIdIfPrimitive(TypeId typeId) {
|
||||||
|
if (typeId.equals(TypeId.BOOLEAN)) {
|
||||||
|
return TypeId.get("Ljava/lang/Boolean;");
|
||||||
|
} else if (typeId.equals(TypeId.BYTE)) {
|
||||||
|
return TypeId.get("Ljava/lang/Byte;");
|
||||||
|
} else if (typeId.equals(TypeId.CHAR)) {
|
||||||
|
return TypeId.get("Ljava/lang/Character;");
|
||||||
|
} else if (typeId.equals(TypeId.DOUBLE)) {
|
||||||
|
return TypeId.get("Ljava/lang/Double;");
|
||||||
|
} else if (typeId.equals(TypeId.FLOAT)) {
|
||||||
|
return TypeId.get("Ljava/lang/Float;");
|
||||||
|
} else if (typeId.equals(TypeId.INT)) {
|
||||||
|
return TypeId.get("Ljava/lang/Integer;");
|
||||||
|
} else if (typeId.equals(TypeId.LONG)) {
|
||||||
|
return TypeId.get("Ljava/lang/Long;");
|
||||||
|
} else if (typeId.equals(TypeId.SHORT)) {
|
||||||
|
return TypeId.get("Ljava/lang/Short;");
|
||||||
|
} else if (typeId.equals(TypeId.VOID)) {
|
||||||
|
return TypeId.get("Ljava/lang/Void;");
|
||||||
|
} else {
|
||||||
|
return typeId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void returnRightValue(Code code, Class<?> returnType, Map<Class, Local> resultLocals) {
|
||||||
|
String unboxMethod;
|
||||||
|
TypeId<?> boxTypeId;
|
||||||
|
code.returnValue(resultLocals.get(returnType));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,107 @@
|
||||||
|
package com.elderdrivers.riru.xposed.dexmaker;
|
||||||
|
|
||||||
|
import android.app.AndroidAppHelper;
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
|
import com.elderdrivers.riru.xposed.Main;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
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.util.HashMap;
|
||||||
|
|
||||||
|
import de.robv.android.xposed.XposedBridge;
|
||||||
|
|
||||||
|
public final class DynamicBridge {
|
||||||
|
|
||||||
|
private static HashMap<Member, HookerDexMaker> hookedInfo = new HashMap<>();
|
||||||
|
|
||||||
|
public static synchronized void hookMethod(Member hookMethod, XposedBridge.AdditionalHookInfo additionalHookInfo) {
|
||||||
|
|
||||||
|
if (hookMethod.toString().contains("com.tencent.wcdb.database")) {
|
||||||
|
DexLog.w("wcdb not permitted.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!checkMember(hookMethod)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hookedInfo.containsKey(hookMethod)) {
|
||||||
|
DexLog.w("already hook method:" + hookMethod.toString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DexLog.d("start to generate class for: " + hookMethod);
|
||||||
|
try {
|
||||||
|
// for Android Oreo and later use InMemoryClassLoader
|
||||||
|
String dexDirPath = "";
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||||
|
// under Android Oreo, using DexClassLoader
|
||||||
|
String dataDir = Main.sAppDataDir;
|
||||||
|
String processName = AndroidAppHelper.currentProcessName();
|
||||||
|
File dexDir = new File(dataDir, "cache/edhookers/" + processName + "/");
|
||||||
|
dexDir.mkdirs();
|
||||||
|
dexDirPath = dexDir.getAbsolutePath();
|
||||||
|
}
|
||||||
|
HookerDexMaker dexMaker = new HookerDexMaker();
|
||||||
|
dexMaker.start(hookMethod, additionalHookInfo,
|
||||||
|
hookMethod.getDeclaringClass().getClassLoader(), dexDirPath);
|
||||||
|
hookedInfo.put(hookMethod, dexMaker);
|
||||||
|
} catch (Exception e) {
|
||||||
|
DexLog.e("error occur when generating dex", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean checkMember(Member member) {
|
||||||
|
|
||||||
|
if (member instanceof Method) {
|
||||||
|
return true;
|
||||||
|
} else if (member instanceof Constructor<?>) {
|
||||||
|
return true;
|
||||||
|
} else if (member.getDeclaringClass().isInterface()) {
|
||||||
|
DexLog.e("Cannot hook interfaces: " + member.toString());
|
||||||
|
return false;
|
||||||
|
} else if (Modifier.isAbstract(member.getModifiers())) {
|
||||||
|
DexLog.e("Cannot hook abstract methods: " + member.toString());
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
DexLog.e("Only methods and constructors can be hooked: " + member.toString());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Object invokeOriginalMethod(Member method, Object thisObject, Object[] args)
|
||||||
|
throws InvocationTargetException, IllegalAccessException {
|
||||||
|
HookerDexMaker dexMaker = hookedInfo.get(method);
|
||||||
|
if (dexMaker == null) {
|
||||||
|
throw new IllegalStateException("method not hooked, cannot call original method.");
|
||||||
|
}
|
||||||
|
Method callBackup = dexMaker.getCallBackupMethod();
|
||||||
|
if (callBackup == null) {
|
||||||
|
throw new IllegalStateException("original method is null, something must be wrong!");
|
||||||
|
}
|
||||||
|
if (!Modifier.isStatic(callBackup.getModifiers())) {
|
||||||
|
throw new IllegalStateException("original method is not static, something must be wrong!");
|
||||||
|
}
|
||||||
|
callBackup.setAccessible(true);
|
||||||
|
if (args == null) {
|
||||||
|
args = new Object[0];
|
||||||
|
}
|
||||||
|
final int argsSize = args.length;
|
||||||
|
if (Modifier.isStatic(method.getModifiers())) {
|
||||||
|
return callBackup.invoke(null, args);
|
||||||
|
} else {
|
||||||
|
Object[] newArgs = new Object[argsSize + 1];
|
||||||
|
newArgs[0] = thisObject;
|
||||||
|
for (int i = 1; i < newArgs.length; i++) {
|
||||||
|
newArgs[i] = args[i - 1];
|
||||||
|
}
|
||||||
|
return callBackup.invoke(null, newArgs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,540 @@
|
||||||
|
package com.elderdrivers.riru.xposed.dexmaker;
|
||||||
|
|
||||||
|
import android.os.Build;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import external.com.android.dx.BinaryOp;
|
||||||
|
import external.com.android.dx.Code;
|
||||||
|
import external.com.android.dx.Comparison;
|
||||||
|
import external.com.android.dx.DexMaker;
|
||||||
|
import external.com.android.dx.FieldId;
|
||||||
|
import external.com.android.dx.Label;
|
||||||
|
import external.com.android.dx.Local;
|
||||||
|
import external.com.android.dx.MethodId;
|
||||||
|
import external.com.android.dx.TypeId;
|
||||||
|
import com.elderdrivers.riru.xposed.core.HookMain;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.Member;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
|
import dalvik.system.InMemoryDexClassLoader;
|
||||||
|
import de.robv.android.xposed.XC_MethodHook;
|
||||||
|
import de.robv.android.xposed.XposedBridge;
|
||||||
|
|
||||||
|
import static com.elderdrivers.riru.xposed.dexmaker.DexMakerUtils.autoBoxIfNecessary;
|
||||||
|
import static com.elderdrivers.riru.xposed.dexmaker.DexMakerUtils.autoUnboxIfNecessary;
|
||||||
|
import static com.elderdrivers.riru.xposed.dexmaker.DexMakerUtils.createResultLocals;
|
||||||
|
import static com.elderdrivers.riru.xposed.dexmaker.DexMakerUtils.getObjTypeIdIfPrimitive;
|
||||||
|
|
||||||
|
public class HookerDexMaker {
|
||||||
|
|
||||||
|
public static final String METHOD_NAME_BACKUP = "backup";
|
||||||
|
public static final String METHOD_NAME_HOOK = "hook";
|
||||||
|
public static final String METHOD_NAME_CALL_BACKUP = "callBackup";
|
||||||
|
public static final String METHOD_NAME_SETUP = "setup";
|
||||||
|
private static final String CLASS_DESC_PREFIX = "L";
|
||||||
|
private static final String CLASS_NAME_PREFIX = "EdHooker";
|
||||||
|
private static final String FIELD_NAME_HOOK_INFO = "additionalHookInfo";
|
||||||
|
private static final String FIELD_NAME_METHOD = "method";
|
||||||
|
private static final String PARAMS_FIELD_NAME_METHOD = "method";
|
||||||
|
private static final String PARAMS_FIELD_NAME_THIS_OBJECT = "thisObject";
|
||||||
|
private static final String PARAMS_FIELD_NAME_ARGS = "args";
|
||||||
|
private static final String CALLBACK_METHOD_NAME_BEFORE = "callBeforeHookedMethod";
|
||||||
|
private static final String CALLBACK_METHOD_NAME_AFTER = "callAfterHookedMethod";
|
||||||
|
private static final String PARAMS_METHOD_NAME_IS_EARLY_RETURN = "isEarlyReturn";
|
||||||
|
|
||||||
|
public static final TypeId<Object[]> objArrayTypeId = TypeId.get(Object[].class);
|
||||||
|
private static final TypeId<Throwable> throwableTypeId = TypeId.get(Throwable.class);
|
||||||
|
private static final TypeId<Member> memberTypeId = TypeId.get(Member.class);
|
||||||
|
private static final TypeId<XC_MethodHook> callbackTypeId = TypeId.get(XC_MethodHook.class);
|
||||||
|
private static final TypeId<XposedBridge.AdditionalHookInfo> hookInfoTypeId
|
||||||
|
= TypeId.get(XposedBridge.AdditionalHookInfo.class);
|
||||||
|
private static final TypeId<XposedBridge.CopyOnWriteSortedSet> callbacksTypeId
|
||||||
|
= TypeId.get(XposedBridge.CopyOnWriteSortedSet.class);
|
||||||
|
private static final TypeId<XC_MethodHook.MethodHookParam> paramTypeId
|
||||||
|
= TypeId.get(XC_MethodHook.MethodHookParam.class);
|
||||||
|
private static final MethodId<XC_MethodHook.MethodHookParam, Void> setResultMethodId =
|
||||||
|
paramTypeId.getMethod(TypeId.VOID, "setResult", TypeId.OBJECT);
|
||||||
|
private static final MethodId<XC_MethodHook.MethodHookParam, Void> setThrowableMethodId =
|
||||||
|
paramTypeId.getMethod(TypeId.VOID, "setThrowable", throwableTypeId);
|
||||||
|
private static final MethodId<XC_MethodHook.MethodHookParam, Object> getResultMethodId =
|
||||||
|
paramTypeId.getMethod(TypeId.OBJECT, "getResult");
|
||||||
|
private static final MethodId<XC_MethodHook.MethodHookParam, Throwable> getThrowableMethodId =
|
||||||
|
paramTypeId.getMethod(throwableTypeId, "getThrowable");
|
||||||
|
private static final MethodId<XC_MethodHook.MethodHookParam, Boolean> hasThrowableMethodId =
|
||||||
|
paramTypeId.getMethod(TypeId.BOOLEAN, "hasThrowable");
|
||||||
|
private static final MethodId<XC_MethodHook, Void> callAfterCallbackMethodId =
|
||||||
|
callbackTypeId.getMethod(TypeId.VOID, CALLBACK_METHOD_NAME_AFTER, paramTypeId);
|
||||||
|
private static final MethodId<XC_MethodHook, Void> callBeforeCallbackMethodId =
|
||||||
|
callbackTypeId.getMethod(TypeId.VOID, CALLBACK_METHOD_NAME_BEFORE, paramTypeId);
|
||||||
|
private static final FieldId<XC_MethodHook.MethodHookParam, Boolean> returnEarlyFieldId =
|
||||||
|
paramTypeId.getField(TypeId.BOOLEAN, "returnEarly");
|
||||||
|
private static final TypeId<XposedBridge> xposedBridgeTypeId = TypeId.get(XposedBridge.class);
|
||||||
|
private static final MethodId<XposedBridge, Void> logMethodId =
|
||||||
|
xposedBridgeTypeId.getMethod(TypeId.VOID, "log", throwableTypeId);
|
||||||
|
|
||||||
|
private static AtomicLong sClassNameSuffix = new AtomicLong(0);
|
||||||
|
|
||||||
|
private FieldId<?, XposedBridge.AdditionalHookInfo> mHookInfoFieldId;
|
||||||
|
private FieldId<?, Member> mMethodFieldId;
|
||||||
|
private MethodId<?, ?> mBackupMethodId;
|
||||||
|
private MethodId<?, ?> mCallBackupMethodId;
|
||||||
|
private MethodId<?, ?> mHookMethodId;
|
||||||
|
|
||||||
|
private TypeId<?> mHookerTypeId;
|
||||||
|
private TypeId<?>[] mParameterTypeIds;
|
||||||
|
private Class<?>[] mActualParameterTypes;
|
||||||
|
private Class<?> mReturnType;
|
||||||
|
private TypeId<?> mReturnTypeId;
|
||||||
|
private boolean mIsStatic;
|
||||||
|
// TODO use this to generate methods
|
||||||
|
private boolean mHasThrowable;
|
||||||
|
|
||||||
|
private DexMaker mDexMaker;
|
||||||
|
private Member mMember;
|
||||||
|
private XposedBridge.AdditionalHookInfo mHookInfo;
|
||||||
|
private ClassLoader mAppClassLoader;
|
||||||
|
private Class<?> mHookClass;
|
||||||
|
private Method mHookMethod;
|
||||||
|
private Method mBackupMethod;
|
||||||
|
private Method mCallBackupMethod;
|
||||||
|
private String mDexDirPath;
|
||||||
|
|
||||||
|
private static TypeId<?>[] getParameterTypeIds(Class<?>[] parameterTypes, boolean isStatic) {
|
||||||
|
int parameterSize = parameterTypes.length;
|
||||||
|
int targetParameterSize = isStatic ? parameterSize : parameterSize + 1;
|
||||||
|
TypeId<?>[] parameterTypeIds = new TypeId<?>[targetParameterSize];
|
||||||
|
int offset = 0;
|
||||||
|
if (!isStatic) {
|
||||||
|
parameterTypeIds[0] = TypeId.OBJECT;
|
||||||
|
offset = 1;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < parameterTypes.length; i++) {
|
||||||
|
parameterTypeIds[i + offset] = TypeId.get(parameterTypes[i]);
|
||||||
|
}
|
||||||
|
return parameterTypeIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Class<?>[] getParameterTypes(Class<?>[] parameterTypes, boolean isStatic) {
|
||||||
|
if (isStatic) {
|
||||||
|
return parameterTypes;
|
||||||
|
}
|
||||||
|
int parameterSize = parameterTypes.length;
|
||||||
|
int targetParameterSize = parameterSize + 1;
|
||||||
|
Class<?>[] newParameterTypes = new Class<?>[targetParameterSize];
|
||||||
|
int offset = 1;
|
||||||
|
newParameterTypes[0] = Object.class;
|
||||||
|
System.arraycopy(parameterTypes, 0, newParameterTypes, offset, parameterTypes.length);
|
||||||
|
return newParameterTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start(Member member, XposedBridge.AdditionalHookInfo hookInfo,
|
||||||
|
ClassLoader appClassLoader, String dexDirPath) throws Exception {
|
||||||
|
if (member instanceof Method) {
|
||||||
|
Method method = (Method) member;
|
||||||
|
mIsStatic = Modifier.isStatic(method.getModifiers());
|
||||||
|
mReturnType = method.getReturnType();
|
||||||
|
if (mReturnType.equals(Void.class) || mReturnType.equals(void.class)
|
||||||
|
|| mReturnType.isPrimitive()) {
|
||||||
|
mReturnTypeId = TypeId.get(mReturnType);
|
||||||
|
} else {
|
||||||
|
// all others fallback to plain Object for convenience
|
||||||
|
mReturnType = Object.class;
|
||||||
|
mReturnTypeId = TypeId.OBJECT;
|
||||||
|
}
|
||||||
|
mParameterTypeIds = getParameterTypeIds(method.getParameterTypes(), mIsStatic);
|
||||||
|
mActualParameterTypes = getParameterTypes(method.getParameterTypes(), mIsStatic);
|
||||||
|
mHasThrowable = method.getExceptionTypes().length > 0;
|
||||||
|
} else if (member instanceof Constructor) {
|
||||||
|
Constructor constructor = (Constructor) member;
|
||||||
|
mIsStatic = false;
|
||||||
|
mReturnType = void.class;
|
||||||
|
mReturnTypeId = TypeId.VOID;
|
||||||
|
mParameterTypeIds = getParameterTypeIds(constructor.getParameterTypes(), mIsStatic);
|
||||||
|
mActualParameterTypes = getParameterTypes(constructor.getParameterTypes(), mIsStatic);
|
||||||
|
mHasThrowable = constructor.getExceptionTypes().length > 0;
|
||||||
|
} else if (member.getDeclaringClass().isInterface()) {
|
||||||
|
throw new IllegalArgumentException("Cannot hook interfaces: " + member.toString());
|
||||||
|
} else if (Modifier.isAbstract(member.getModifiers())) {
|
||||||
|
throw new IllegalArgumentException("Cannot hook abstract methods: " + member.toString());
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Only methods and constructors can be hooked: " + member.toString());
|
||||||
|
}
|
||||||
|
mMember = member;
|
||||||
|
mHookInfo = hookInfo;
|
||||||
|
mDexDirPath = dexDirPath;
|
||||||
|
if (appClassLoader == null
|
||||||
|
|| appClassLoader.getClass().getName().equals("java.lang.BootClassLoader")) {
|
||||||
|
mAppClassLoader = this.getClass().getClassLoader();
|
||||||
|
} else {
|
||||||
|
mAppClassLoader = appClassLoader;
|
||||||
|
}
|
||||||
|
doMake();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doMake() throws Exception {
|
||||||
|
mDexMaker = new DexMaker();
|
||||||
|
// Generate a Hooker class.
|
||||||
|
String className = CLASS_NAME_PREFIX + sClassNameSuffix.getAndIncrement();
|
||||||
|
String classDesc = CLASS_DESC_PREFIX + className + ";";
|
||||||
|
mHookerTypeId = TypeId.get(classDesc);
|
||||||
|
mDexMaker.declare(mHookerTypeId, className + ".generated", Modifier.PUBLIC, TypeId.OBJECT);
|
||||||
|
generateFields();
|
||||||
|
generateSetupMethod();
|
||||||
|
generateBackupMethod();
|
||||||
|
generateHookMethod();
|
||||||
|
generateCallBackupMethod();
|
||||||
|
|
||||||
|
ClassLoader loader;
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
// in memory dex classloader
|
||||||
|
byte[] dexBytes = mDexMaker.generate();
|
||||||
|
loader = new InMemoryDexClassLoader(ByteBuffer.wrap(dexBytes), mAppClassLoader);
|
||||||
|
} else {
|
||||||
|
if (TextUtils.isEmpty(mDexDirPath)) {
|
||||||
|
throw new IllegalArgumentException("dexDirPath should not be empty!!!");
|
||||||
|
}
|
||||||
|
// Create the dex file and load it.
|
||||||
|
loader = mDexMaker.generateAndLoad(mAppClassLoader, new File(mDexDirPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
mHookClass = loader.loadClass(className);
|
||||||
|
// Execute our newly-generated code in-process.
|
||||||
|
mHookClass.getMethod(METHOD_NAME_SETUP, Member.class, XposedBridge.AdditionalHookInfo.class)
|
||||||
|
.invoke(null, mMember, mHookInfo);
|
||||||
|
mHookMethod = mHookClass.getMethod(METHOD_NAME_HOOK, mActualParameterTypes);
|
||||||
|
mBackupMethod = mHookClass.getMethod(METHOD_NAME_BACKUP, mActualParameterTypes);
|
||||||
|
mCallBackupMethod = mHookClass.getMethod(METHOD_NAME_CALL_BACKUP, mActualParameterTypes);
|
||||||
|
HookMain.backupAndHook(mMember, mHookMethod, mBackupMethod);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Method getHookMethod() {
|
||||||
|
return mHookMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Method getBackupMethod() {
|
||||||
|
return mBackupMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Method getCallBackupMethod() {
|
||||||
|
return mCallBackupMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Class getHookClass() {
|
||||||
|
return mHookClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void generateFields() {
|
||||||
|
mHookInfoFieldId = mHookerTypeId.getField(hookInfoTypeId, FIELD_NAME_HOOK_INFO);
|
||||||
|
mMethodFieldId = mHookerTypeId.getField(memberTypeId, FIELD_NAME_METHOD);
|
||||||
|
mDexMaker.declare(mHookInfoFieldId, Modifier.STATIC, null);
|
||||||
|
mDexMaker.declare(mMethodFieldId, Modifier.STATIC, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void generateSetupMethod() {
|
||||||
|
MethodId<?, Void> setupMethodId = mHookerTypeId.getMethod(
|
||||||
|
TypeId.VOID, METHOD_NAME_SETUP, memberTypeId, hookInfoTypeId);
|
||||||
|
Code code = mDexMaker.declare(setupMethodId, Modifier.PUBLIC | Modifier.STATIC);
|
||||||
|
// init logic
|
||||||
|
// get parameters
|
||||||
|
Local<Member> method = code.getParameter(0, memberTypeId);
|
||||||
|
Local<XposedBridge.AdditionalHookInfo> hookInfo = code.getParameter(1, hookInfoTypeId);
|
||||||
|
// save params to static
|
||||||
|
code.sput(mMethodFieldId, method);
|
||||||
|
code.sput(mHookInfoFieldId, hookInfo);
|
||||||
|
code.returnVoid();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void generateBackupMethod() {
|
||||||
|
mBackupMethodId = mHookerTypeId.getMethod(mReturnTypeId, METHOD_NAME_BACKUP, mParameterTypeIds);
|
||||||
|
Code code = mDexMaker.declare(mBackupMethodId, Modifier.PUBLIC | Modifier.STATIC);
|
||||||
|
Map<TypeId, Local> resultLocals = createResultLocals(code);
|
||||||
|
// do nothing
|
||||||
|
if (mReturnTypeId.equals(TypeId.VOID)) {
|
||||||
|
code.returnVoid();
|
||||||
|
} else {
|
||||||
|
// we have limited the returnType to primitives or Object, so this should be safe
|
||||||
|
code.returnValue(resultLocals.get(mReturnTypeId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void generateCallBackupMethod() {
|
||||||
|
mCallBackupMethodId = mHookerTypeId.getMethod(mReturnTypeId, METHOD_NAME_CALL_BACKUP, mParameterTypeIds);
|
||||||
|
Code code = mDexMaker.declare(mCallBackupMethodId, Modifier.PUBLIC | Modifier.STATIC);
|
||||||
|
// just call backup and return its result
|
||||||
|
Local[] allArgsLocals = createParameterLocals(code);
|
||||||
|
Map<TypeId, Local> resultLocals = createResultLocals(code);
|
||||||
|
if (mReturnTypeId.equals(TypeId.VOID)) {
|
||||||
|
code.invokeStatic(mBackupMethodId, null, allArgsLocals);
|
||||||
|
code.returnVoid();
|
||||||
|
} else {
|
||||||
|
Local result = resultLocals.get(mReturnTypeId);
|
||||||
|
code.invokeStatic(mBackupMethodId, result, allArgsLocals);
|
||||||
|
code.returnValue(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void generateHookMethod() {
|
||||||
|
mHookMethodId = mHookerTypeId.getMethod(mReturnTypeId, METHOD_NAME_HOOK, mParameterTypeIds);
|
||||||
|
Code code = mDexMaker.declare(mHookMethodId, Modifier.PUBLIC | Modifier.STATIC);
|
||||||
|
|
||||||
|
// code starts
|
||||||
|
|
||||||
|
// prepare common labels
|
||||||
|
Label noHookReturn = new Label();
|
||||||
|
Label incrementAndCheckBefore = new Label();
|
||||||
|
Label tryBeforeCatch = new Label();
|
||||||
|
Label noExceptionBefore = new Label();
|
||||||
|
Label checkAndCallBackup = new Label();
|
||||||
|
Label beginCallBefore = new Label();
|
||||||
|
Label beginCallAfter = new Label();
|
||||||
|
Label tryOrigCatch = new Label();
|
||||||
|
Label noExceptionOrig = new Label();
|
||||||
|
Label tryAfterCatch = new Label();
|
||||||
|
Label decrementAndCheckAfter = new Label();
|
||||||
|
Label noBackupThrowable = new Label();
|
||||||
|
Label throwThrowable = new Label();
|
||||||
|
// prepare locals
|
||||||
|
Local<Boolean> disableHooks = code.newLocal(TypeId.BOOLEAN);
|
||||||
|
Local<XposedBridge.AdditionalHookInfo> hookInfo = code.newLocal(hookInfoTypeId);
|
||||||
|
Local<XposedBridge.CopyOnWriteSortedSet> callbacks = code.newLocal(callbacksTypeId);
|
||||||
|
Local<Object[]> snapshot = code.newLocal(objArrayTypeId);
|
||||||
|
Local<Integer> snapshotLen = code.newLocal(TypeId.INT);
|
||||||
|
Local<Object> callbackObj = code.newLocal(TypeId.OBJECT);
|
||||||
|
Local<XC_MethodHook> callback = code.newLocal(callbackTypeId);
|
||||||
|
|
||||||
|
Local<Object> resultObj = code.newLocal(TypeId.OBJECT);
|
||||||
|
Local<Integer> one = code.newLocal(TypeId.INT);
|
||||||
|
Local<Object> nullObj = code.newLocal(TypeId.OBJECT);
|
||||||
|
Local<Throwable> throwable = code.newLocal(throwableTypeId);
|
||||||
|
|
||||||
|
Local<XC_MethodHook.MethodHookParam> param = code.newLocal(paramTypeId);
|
||||||
|
Local<Member> method = code.newLocal(memberTypeId);
|
||||||
|
Local<Object> thisObject = code.newLocal(TypeId.OBJECT);
|
||||||
|
Local<Object[]> args = code.newLocal(objArrayTypeId);
|
||||||
|
Local<Boolean> returnEarly = code.newLocal(TypeId.BOOLEAN);
|
||||||
|
|
||||||
|
Local<Integer> actualParamSize = code.newLocal(TypeId.INT);
|
||||||
|
Local<Integer> argIndex = code.newLocal(TypeId.INT);
|
||||||
|
|
||||||
|
Local<Integer> beforeIdx = code.newLocal(TypeId.INT);
|
||||||
|
Local<Object> lastResult = code.newLocal(TypeId.OBJECT);
|
||||||
|
Local<Throwable> lastThrowable = code.newLocal(throwableTypeId);
|
||||||
|
Local<Boolean> hasThrowable = code.newLocal(TypeId.BOOLEAN);
|
||||||
|
|
||||||
|
Local[] allArgsLocals = createParameterLocals(code);
|
||||||
|
|
||||||
|
Map<TypeId, Local> resultLocals = createResultLocals(code);
|
||||||
|
|
||||||
|
code.loadConstant(args, null);
|
||||||
|
code.loadConstant(argIndex, 0);
|
||||||
|
code.loadConstant(one, 1);
|
||||||
|
code.loadConstant(snapshotLen, 0);
|
||||||
|
code.loadConstant(nullObj, null);
|
||||||
|
|
||||||
|
// check XposedBridge.disableHooks flag
|
||||||
|
|
||||||
|
FieldId<XposedBridge, Boolean> disableHooksField =
|
||||||
|
xposedBridgeTypeId.getField(TypeId.BOOLEAN, "disableHooks");
|
||||||
|
code.sget(disableHooksField, disableHooks);
|
||||||
|
// disableHooks == true => no hooking
|
||||||
|
code.compareZ(Comparison.NE, noHookReturn, disableHooks);
|
||||||
|
|
||||||
|
// check callbacks length
|
||||||
|
code.sget(mHookInfoFieldId, hookInfo);
|
||||||
|
code.iget(hookInfoTypeId.getField(callbacksTypeId, "callbacks"), callbacks, hookInfo);
|
||||||
|
code.invokeVirtual(callbacksTypeId.getMethod(objArrayTypeId, "getSnapshot"), snapshot, callbacks);
|
||||||
|
code.arrayLength(snapshotLen, snapshot);
|
||||||
|
// snapshotLen == 0 => no hooking
|
||||||
|
code.compareZ(Comparison.EQ, noHookReturn, snapshotLen);
|
||||||
|
|
||||||
|
// start hooking
|
||||||
|
|
||||||
|
// prepare hooking locals
|
||||||
|
int paramsSize = mParameterTypeIds.length;
|
||||||
|
int offset = 0;
|
||||||
|
// thisObject
|
||||||
|
if (mIsStatic) {
|
||||||
|
// thisObject = null
|
||||||
|
code.loadConstant(thisObject, null);
|
||||||
|
} else {
|
||||||
|
// thisObject = args[0]
|
||||||
|
offset = 1;
|
||||||
|
code.move(thisObject, allArgsLocals[0]);
|
||||||
|
}
|
||||||
|
// actual args (exclude thisObject if this is not a static method)
|
||||||
|
code.loadConstant(actualParamSize, paramsSize - offset);
|
||||||
|
code.newArray(args, actualParamSize);
|
||||||
|
for (int i = offset; i < paramsSize; i++) {
|
||||||
|
Local parameter = allArgsLocals[i];
|
||||||
|
// save parameter to resultObj as Object
|
||||||
|
autoBoxIfNecessary(code, resultObj, parameter);
|
||||||
|
code.loadConstant(argIndex, i - offset);
|
||||||
|
// save Object to args
|
||||||
|
code.aput(args, argIndex, resultObj);
|
||||||
|
}
|
||||||
|
// create param
|
||||||
|
code.newInstance(param, paramTypeId.getConstructor());
|
||||||
|
// set method, thisObject, args
|
||||||
|
code.sget(mMethodFieldId, method);
|
||||||
|
code.iput(paramTypeId.getField(memberTypeId, "method"), param, method);
|
||||||
|
code.iput(paramTypeId.getField(TypeId.OBJECT, "thisObject"), param, thisObject);
|
||||||
|
code.iput(paramTypeId.getField(objArrayTypeId, "args"), param, args);
|
||||||
|
|
||||||
|
// call beforeCallbacks
|
||||||
|
code.loadConstant(beforeIdx, 0);
|
||||||
|
|
||||||
|
code.mark(beginCallBefore);
|
||||||
|
// start of try
|
||||||
|
code.addCatchClause(throwableTypeId, tryBeforeCatch);
|
||||||
|
|
||||||
|
code.aget(callbackObj, snapshot, beforeIdx);
|
||||||
|
code.cast(callback, callbackObj);
|
||||||
|
code.invokeVirtual(callBeforeCallbackMethodId, null, callback, param);
|
||||||
|
code.jump(noExceptionBefore);
|
||||||
|
|
||||||
|
// end of try
|
||||||
|
code.removeCatchClause(throwableTypeId);
|
||||||
|
|
||||||
|
// start of catch
|
||||||
|
code.mark(tryBeforeCatch);
|
||||||
|
code.moveException(throwable);
|
||||||
|
code.invokeStatic(logMethodId, null, throwable);
|
||||||
|
code.invokeVirtual(setResultMethodId, null, param, nullObj);
|
||||||
|
code.loadConstant(returnEarly, false);
|
||||||
|
code.iput(returnEarlyFieldId, param, returnEarly);
|
||||||
|
code.jump(incrementAndCheckBefore);
|
||||||
|
|
||||||
|
// no exception when calling beforeCallbacks
|
||||||
|
code.mark(noExceptionBefore);
|
||||||
|
code.iget(returnEarlyFieldId, returnEarly, param);
|
||||||
|
// if returnEarly == false, continue
|
||||||
|
code.compareZ(Comparison.EQ, incrementAndCheckBefore, returnEarly);
|
||||||
|
// returnEarly == true, break
|
||||||
|
code.op(BinaryOp.ADD, beforeIdx, beforeIdx, one);
|
||||||
|
code.jump(checkAndCallBackup);
|
||||||
|
|
||||||
|
// increment and check to continue
|
||||||
|
code.mark(incrementAndCheckBefore);
|
||||||
|
code.op(BinaryOp.ADD, beforeIdx, beforeIdx, one);
|
||||||
|
code.compare(Comparison.LT, beginCallBefore, beforeIdx, snapshotLen);
|
||||||
|
|
||||||
|
// check and call backup
|
||||||
|
code.mark(checkAndCallBackup);
|
||||||
|
code.iget(returnEarlyFieldId, returnEarly, param);
|
||||||
|
// if returnEarly == true, go to call afterCallbacks directly
|
||||||
|
code.compareZ(Comparison.NE, noExceptionOrig, returnEarly);
|
||||||
|
// try to call backup
|
||||||
|
// try start
|
||||||
|
code.addCatchClause(throwableTypeId, tryOrigCatch);
|
||||||
|
// get pre-created Local with a matching typeId
|
||||||
|
if (mReturnTypeId.equals(TypeId.VOID)) {
|
||||||
|
code.invokeStatic(mBackupMethodId, null, allArgsLocals);
|
||||||
|
// TODO maybe keep preset result to do some magic?
|
||||||
|
code.invokeVirtual(setResultMethodId, null, param, nullObj);
|
||||||
|
} else {
|
||||||
|
Local returnedResult = resultLocals.get(mReturnTypeId);
|
||||||
|
code.invokeStatic(mBackupMethodId, returnedResult, allArgsLocals);
|
||||||
|
// save returnedResult to resultObj as a Object
|
||||||
|
autoBoxIfNecessary(code, resultObj, returnedResult);
|
||||||
|
// save resultObj to param
|
||||||
|
code.invokeVirtual(setResultMethodId, null, param, resultObj);
|
||||||
|
}
|
||||||
|
// go to call afterCallbacks
|
||||||
|
code.jump(noExceptionOrig);
|
||||||
|
// try end
|
||||||
|
code.removeCatchClause(throwableTypeId);
|
||||||
|
// catch
|
||||||
|
code.mark(tryOrigCatch);
|
||||||
|
code.moveException(throwable);
|
||||||
|
// exception occurred when calling backup, save throwable to param
|
||||||
|
code.invokeVirtual(setThrowableMethodId, null, param, throwable);
|
||||||
|
|
||||||
|
code.mark(noExceptionOrig);
|
||||||
|
code.op(BinaryOp.SUBTRACT, beforeIdx, beforeIdx, one);
|
||||||
|
|
||||||
|
// call afterCallbacks
|
||||||
|
code.mark(beginCallAfter);
|
||||||
|
// save results of backup calling
|
||||||
|
code.invokeVirtual(getResultMethodId, lastResult, param);
|
||||||
|
code.invokeVirtual(getThrowableMethodId, lastThrowable, param);
|
||||||
|
// try start
|
||||||
|
code.addCatchClause(throwableTypeId, tryAfterCatch);
|
||||||
|
code.aget(callbackObj, snapshot, beforeIdx);
|
||||||
|
code.cast(callback, callbackObj);
|
||||||
|
code.invokeVirtual(callAfterCallbackMethodId, null, callback, param);
|
||||||
|
// all good, just continue
|
||||||
|
code.jump(decrementAndCheckAfter);
|
||||||
|
// try end
|
||||||
|
code.removeCatchClause(throwableTypeId);
|
||||||
|
// catch
|
||||||
|
code.mark(tryAfterCatch);
|
||||||
|
code.moveException(throwable);
|
||||||
|
code.invokeStatic(logMethodId, null, throwable);
|
||||||
|
// if lastThrowable == null, go to recover lastResult
|
||||||
|
code.compareZ(Comparison.EQ, noBackupThrowable, lastThrowable);
|
||||||
|
// lastThrowable != null, recover lastThrowable
|
||||||
|
code.invokeVirtual(setThrowableMethodId, null, param, lastThrowable);
|
||||||
|
// continue
|
||||||
|
code.jump(decrementAndCheckAfter);
|
||||||
|
code.mark(noBackupThrowable);
|
||||||
|
// recover lastResult and continue
|
||||||
|
code.invokeVirtual(setResultMethodId, null, param, lastResult);
|
||||||
|
// decrement and check continue
|
||||||
|
code.mark(decrementAndCheckAfter);
|
||||||
|
code.op(BinaryOp.SUBTRACT, beforeIdx, beforeIdx, one);
|
||||||
|
code.compareZ(Comparison.GE, beginCallAfter, beforeIdx);
|
||||||
|
|
||||||
|
// callbacks end
|
||||||
|
// return
|
||||||
|
code.invokeVirtual(hasThrowableMethodId, hasThrowable, param);
|
||||||
|
// if hasThrowable, throw the throwable and return
|
||||||
|
code.compareZ(Comparison.NE, throwThrowable, hasThrowable);
|
||||||
|
// return getResult
|
||||||
|
if (mReturnTypeId.equals(TypeId.VOID)) {
|
||||||
|
code.returnVoid();
|
||||||
|
} else {
|
||||||
|
// getResult always return an Object, so save to resultObj
|
||||||
|
code.invokeVirtual(getResultMethodId, resultObj, param);
|
||||||
|
// have to unbox it if returnType is primitive
|
||||||
|
// casting Object
|
||||||
|
TypeId objTypeId = getObjTypeIdIfPrimitive(mReturnTypeId);
|
||||||
|
Local matchObjLocal = resultLocals.get(objTypeId);
|
||||||
|
code.cast(matchObjLocal, resultObj);
|
||||||
|
// have to use matching typed Object(Integer, Double ...) to do unboxing
|
||||||
|
Local toReturn = resultLocals.get(mReturnTypeId);
|
||||||
|
autoUnboxIfNecessary(code, toReturn, matchObjLocal);
|
||||||
|
// return
|
||||||
|
code.returnValue(toReturn);
|
||||||
|
}
|
||||||
|
// throw throwable
|
||||||
|
code.mark(throwThrowable);
|
||||||
|
code.invokeVirtual(getThrowableMethodId, throwable, param);
|
||||||
|
code.throwValue(throwable);
|
||||||
|
|
||||||
|
// call backup and return
|
||||||
|
code.mark(noHookReturn);
|
||||||
|
if (mReturnTypeId.equals(TypeId.VOID)) {
|
||||||
|
code.invokeStatic(mBackupMethodId, null, allArgsLocals);
|
||||||
|
code.returnVoid();
|
||||||
|
} else {
|
||||||
|
Local result = resultLocals.get(mReturnTypeId);
|
||||||
|
code.invokeStatic(mBackupMethodId, result, allArgsLocals);
|
||||||
|
code.returnValue(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Local[] createParameterLocals(Code code) {
|
||||||
|
Local[] paramLocals = new Local[mParameterTypeIds.length];
|
||||||
|
for (int i = 0; i < mParameterTypeIds.length; i++) {
|
||||||
|
paramLocals[i] = code.getParameter(i, mParameterTypeIds[i]);
|
||||||
|
}
|
||||||
|
return paramLocals;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
package com.elderdrivers.riru.xposed.entry;
|
||||||
|
|
||||||
|
import com.elderdrivers.riru.xposed.core.HookMain;
|
||||||
|
import com.elderdrivers.riru.xposed.entry.bootstrap.AppBootstrapHookInfo;
|
||||||
|
import com.elderdrivers.riru.xposed.entry.bootstrap.SysBootstrapHookInfo;
|
||||||
|
import com.elderdrivers.riru.xposed.entry.bootstrap.SysInnerHookInfo;
|
||||||
|
import com.elderdrivers.riru.xposed.entry.hooker.SystemMainHooker;
|
||||||
|
import com.elderdrivers.riru.xposed.util.Utils;
|
||||||
|
|
||||||
|
import de.robv.android.xposed.XposedBridge;
|
||||||
|
import de.robv.android.xposed.XposedInit;
|
||||||
|
|
||||||
|
public class Router {
|
||||||
|
|
||||||
|
public static void onProcessForked(boolean isSystem) {
|
||||||
|
// Initialize the Xposed framework and modules
|
||||||
|
try {
|
||||||
|
XposedInit.initForZygote(isSystem);
|
||||||
|
// FIXME some coredomain app can't reading modules.list
|
||||||
|
XposedInit.loadModules();
|
||||||
|
} catch (Throwable t) {
|
||||||
|
Utils.logE("Errors during Xposed initialization", t);
|
||||||
|
XposedBridge.disableHooks = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void startBootstrapHook(boolean isSystem) {
|
||||||
|
Utils.logD("startBootstrapHook starts: isSystem = " + isSystem);
|
||||||
|
ClassLoader classLoader = XposedBridge.BOOTCLASSLOADER;
|
||||||
|
if (isSystem) {
|
||||||
|
HookMain.doHookDefault(
|
||||||
|
Router.class.getClassLoader(),
|
||||||
|
classLoader,
|
||||||
|
SysBootstrapHookInfo.class.getName());
|
||||||
|
} else {
|
||||||
|
HookMain.doHookDefault(
|
||||||
|
Router.class.getClassLoader(),
|
||||||
|
classLoader,
|
||||||
|
AppBootstrapHookInfo.class.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void startSystemServerHook() {
|
||||||
|
HookMain.doHookDefault(
|
||||||
|
Router.class.getClassLoader(),
|
||||||
|
SystemMainHooker.systemServerCL,
|
||||||
|
SysInnerHookInfo.class.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
package com.elderdrivers.riru.xposed.entry.bootstrap;
|
||||||
|
|
||||||
|
import com.elderdrivers.riru.common.KeepMembers;
|
||||||
|
import com.elderdrivers.riru.xposed.entry.hooker.HandleBindAppHooker;
|
||||||
|
import com.elderdrivers.riru.xposed.entry.hooker.InstrumentationHooker;
|
||||||
|
import com.elderdrivers.riru.xposed.entry.hooker.LoadedApkConstructorHooker;
|
||||||
|
import com.elderdrivers.riru.xposed.entry.hooker.MakeAppHooker;
|
||||||
|
|
||||||
|
public class AppBootstrapHookInfo implements KeepMembers {
|
||||||
|
public static String[] hookItemNames = {
|
||||||
|
InstrumentationHooker.CallAppOnCreate.class.getName(),
|
||||||
|
HandleBindAppHooker.class.getName(),
|
||||||
|
// MakeAppHooker.class.getName(),
|
||||||
|
LoadedApkConstructorHooker.class.getName()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
package com.elderdrivers.riru.xposed.entry.bootstrap;
|
||||||
|
|
||||||
|
import com.elderdrivers.riru.common.KeepMembers;
|
||||||
|
import com.elderdrivers.riru.xposed.entry.hooker.HandleBindAppHooker;
|
||||||
|
import com.elderdrivers.riru.xposed.entry.hooker.InstrumentationHooker;
|
||||||
|
import com.elderdrivers.riru.xposed.entry.hooker.LoadedApkConstructorHooker;
|
||||||
|
import com.elderdrivers.riru.xposed.entry.hooker.MakeAppHooker;
|
||||||
|
import com.elderdrivers.riru.xposed.entry.hooker.SystemMainHooker;
|
||||||
|
|
||||||
|
public class SysBootstrapHookInfo implements KeepMembers {
|
||||||
|
public static String[] hookItemNames = {
|
||||||
|
// InstrumentationHooker.CallAppOnCreate.class.getName(),
|
||||||
|
HandleBindAppHooker.class.getName(),
|
||||||
|
MakeAppHooker.class.getName(),
|
||||||
|
SystemMainHooker.class.getName(),
|
||||||
|
LoadedApkConstructorHooker.class.getName()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
package com.elderdrivers.riru.xposed.entry.bootstrap;
|
||||||
|
|
||||||
|
import com.elderdrivers.riru.common.KeepMembers;
|
||||||
|
import com.elderdrivers.riru.xposed.entry.hooker.StartBootstrapServicesHooker;
|
||||||
|
|
||||||
|
public class SysInnerHookInfo implements KeepMembers {
|
||||||
|
public static String[] hookItemNames = {
|
||||||
|
StartBootstrapServicesHooker.class.getName()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
package com.elderdrivers.riru.xposed.entry.hooker;
|
||||||
|
|
||||||
|
import android.app.ActivityThread;
|
||||||
|
import android.app.LoadedApk;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.pm.ApplicationInfo;
|
||||||
|
import android.content.res.CompatibilityInfo;
|
||||||
|
|
||||||
|
import com.elderdrivers.riru.common.KeepMembers;
|
||||||
|
|
||||||
|
import de.robv.android.xposed.XposedBridge;
|
||||||
|
import de.robv.android.xposed.callbacks.XC_LoadPackage;
|
||||||
|
|
||||||
|
import static com.elderdrivers.riru.xposed.util.ClassLoaderUtils.replaceParentClassLoader;
|
||||||
|
import static de.robv.android.xposed.XposedHelpers.getObjectField;
|
||||||
|
import static de.robv.android.xposed.XposedHelpers.setObjectField;
|
||||||
|
import static de.robv.android.xposed.XposedInit.INSTALLER_PACKAGE_NAME;
|
||||||
|
import static de.robv.android.xposed.XposedInit.loadedPackagesInProcess;
|
||||||
|
import static de.robv.android.xposed.XposedInit.logD;
|
||||||
|
import static de.robv.android.xposed.XposedInit.logE;
|
||||||
|
|
||||||
|
// normal process initialization (for new Activity, Service, BroadcastReceiver etc.)
|
||||||
|
public class HandleBindAppHooker implements KeepMembers {
|
||||||
|
|
||||||
|
public static String className = "android.app.ActivityThread";
|
||||||
|
public static String methodName = "handleBindApplication";
|
||||||
|
public static String methodSig = "(Landroid/app/ActivityThread$AppBindData;)V";
|
||||||
|
|
||||||
|
public static void hook(Object thiz, Object bindData) {
|
||||||
|
backup(thiz, bindData);
|
||||||
|
if (XposedBridge.disableHooks) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
logD("ActivityThread#handleBindApplication() starts");
|
||||||
|
ActivityThread activityThread = (ActivityThread) thiz;
|
||||||
|
ApplicationInfo appInfo = (ApplicationInfo) getObjectField(bindData, "appInfo");
|
||||||
|
String reportedPackageName = appInfo.packageName.equals("android") ? "system" : appInfo.packageName;
|
||||||
|
ComponentName instrumentationName = (ComponentName) getObjectField(bindData, "instrumentationName");
|
||||||
|
if (instrumentationName != null) {
|
||||||
|
logD("Instrumentation detected, disabling framework for");
|
||||||
|
XposedBridge.disableHooks = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
CompatibilityInfo compatInfo = (CompatibilityInfo) getObjectField(bindData, "compatInfo");
|
||||||
|
if (appInfo.sourceDir == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setObjectField(activityThread, "mBoundApplication", bindData);
|
||||||
|
loadedPackagesInProcess.add(reportedPackageName);
|
||||||
|
LoadedApk loadedApk = activityThread.getPackageInfoNoCheck(appInfo, compatInfo);
|
||||||
|
|
||||||
|
replaceParentClassLoader(loadedApk.getClassLoader());
|
||||||
|
|
||||||
|
XC_LoadPackage.LoadPackageParam lpparam = new XC_LoadPackage.LoadPackageParam(XposedBridge.sLoadedPackageCallbacks);
|
||||||
|
lpparam.packageName = reportedPackageName;
|
||||||
|
lpparam.processName = (String) getObjectField(bindData, "processName");
|
||||||
|
lpparam.classLoader = loadedApk.getClassLoader();
|
||||||
|
lpparam.appInfo = appInfo;
|
||||||
|
lpparam.isFirstApplication = true;
|
||||||
|
XC_LoadPackage.callAll(lpparam);
|
||||||
|
|
||||||
|
if (reportedPackageName.equals(INSTALLER_PACKAGE_NAME)) {
|
||||||
|
XposedInstallerHooker.hookXposedInstaller(lpparam.classLoader);
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
logE("error when hooking bindApp", t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void backup(Object thiz, Object bindData) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
package com.elderdrivers.riru.xposed.entry.hooker;
|
||||||
|
|
||||||
|
import android.app.AndroidAppHelper;
|
||||||
|
import android.app.Application;
|
||||||
|
import android.app.LoadedApk;
|
||||||
|
|
||||||
|
import com.elderdrivers.riru.common.KeepMembers;
|
||||||
|
|
||||||
|
import de.robv.android.xposed.XposedBridge;
|
||||||
|
import de.robv.android.xposed.callbacks.XC_LoadPackage;
|
||||||
|
|
||||||
|
import static com.elderdrivers.riru.xposed.util.ClassLoaderUtils.replaceParentClassLoader;
|
||||||
|
import static de.robv.android.xposed.XposedHelpers.getObjectField;
|
||||||
|
import static de.robv.android.xposed.XposedInit.INSTALLER_PACKAGE_NAME;
|
||||||
|
import static de.robv.android.xposed.XposedInit.loadedPackagesInProcess;
|
||||||
|
import static de.robv.android.xposed.XposedInit.logD;
|
||||||
|
import static de.robv.android.xposed.XposedInit.logE;
|
||||||
|
|
||||||
|
|
||||||
|
public class InstrumentationHooker {
|
||||||
|
|
||||||
|
public static class CallAppOnCreate implements KeepMembers {
|
||||||
|
|
||||||
|
public static String className = "android.app.Instrumentation";
|
||||||
|
public static String methodName = "callApplicationOnCreate";
|
||||||
|
public static String methodSig = "(Landroid/app/Application;)V";
|
||||||
|
|
||||||
|
public static void hook(Object thiz, Application application) {
|
||||||
|
try {
|
||||||
|
logD("Instrumentation#callApplicationOnCreate starts");
|
||||||
|
LoadedApk loadedApk = (LoadedApk) getObjectField(application, "mLoadedApk");
|
||||||
|
String reportedPackageName = application.getPackageName();
|
||||||
|
loadedPackagesInProcess.add(reportedPackageName);
|
||||||
|
|
||||||
|
replaceParentClassLoader(loadedApk.getClassLoader());
|
||||||
|
|
||||||
|
XC_LoadPackage.LoadPackageParam lpparam = new XC_LoadPackage.LoadPackageParam(XposedBridge.sLoadedPackageCallbacks);
|
||||||
|
lpparam.packageName = reportedPackageName;
|
||||||
|
lpparam.processName = AndroidAppHelper.currentProcessName();
|
||||||
|
lpparam.classLoader = loadedApk.getClassLoader();
|
||||||
|
lpparam.appInfo = application.getApplicationInfo();
|
||||||
|
lpparam.isFirstApplication = true;
|
||||||
|
XC_LoadPackage.callAll(lpparam);
|
||||||
|
|
||||||
|
if (reportedPackageName.equals(INSTALLER_PACKAGE_NAME)) {
|
||||||
|
XposedInstallerHooker.hookXposedInstaller(lpparam.classLoader);
|
||||||
|
}
|
||||||
|
} catch (Throwable throwable) {
|
||||||
|
logE("error when hooking Instru#callAppOnCreate", throwable);
|
||||||
|
}
|
||||||
|
backup(thiz, application);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void backup(Object thiz, Application application) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
package com.elderdrivers.riru.xposed.entry.hooker;
|
||||||
|
|
||||||
|
import android.app.ActivityThread;
|
||||||
|
import android.app.AndroidAppHelper;
|
||||||
|
import android.app.LoadedApk;
|
||||||
|
import android.content.pm.ApplicationInfo;
|
||||||
|
import android.content.res.CompatibilityInfo;
|
||||||
|
|
||||||
|
import com.elderdrivers.riru.common.KeepMembers;
|
||||||
|
|
||||||
|
import de.robv.android.xposed.XposedBridge;
|
||||||
|
import de.robv.android.xposed.callbacks.XC_LoadPackage;
|
||||||
|
|
||||||
|
import static com.elderdrivers.riru.xposed.util.ClassLoaderUtils.replaceParentClassLoader;
|
||||||
|
import static de.robv.android.xposed.XposedHelpers.getBooleanField;
|
||||||
|
import static de.robv.android.xposed.XposedHelpers.getObjectField;
|
||||||
|
import static de.robv.android.xposed.XposedInit.loadedPackagesInProcess;
|
||||||
|
import static de.robv.android.xposed.XposedInit.logD;
|
||||||
|
import static de.robv.android.xposed.XposedInit.logE;
|
||||||
|
|
||||||
|
// when a package is loaded for an existing process, trigger the callbacks as well
|
||||||
|
// ed: remove resources related hooking
|
||||||
|
public class LoadedApkConstructorHooker implements KeepMembers {
|
||||||
|
public static String className = "android.app.LoadedApk";
|
||||||
|
public static String methodName = "<init>";
|
||||||
|
public static String methodSig = "(Landroid/app/ActivityThread;" +
|
||||||
|
"Landroid/content/pm/ApplicationInfo;" +
|
||||||
|
"Landroid/content/res/CompatibilityInfo;" +
|
||||||
|
"Ljava/lang/ClassLoader;ZZZ)V";
|
||||||
|
|
||||||
|
public static void hook(Object thiz, ActivityThread activityThread,
|
||||||
|
ApplicationInfo aInfo, CompatibilityInfo compatInfo,
|
||||||
|
ClassLoader baseLoader, boolean securityViolation,
|
||||||
|
boolean includeCode, boolean registerPackage) {
|
||||||
|
|
||||||
|
if (XposedBridge.disableHooks) {
|
||||||
|
backup(thiz, activityThread, aInfo, compatInfo, baseLoader, securityViolation, includeCode, registerPackage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logD("LoadedApk#<init> starts");
|
||||||
|
backup(thiz, activityThread, aInfo, compatInfo, baseLoader, securityViolation,
|
||||||
|
includeCode, registerPackage);
|
||||||
|
|
||||||
|
try {
|
||||||
|
LoadedApk loadedApk = (LoadedApk) thiz;
|
||||||
|
String packageName = loadedApk.getPackageName();
|
||||||
|
Object mAppDir = getObjectField(thiz, "mAppDir");
|
||||||
|
logD("LoadedApk#<init> ends: " + mAppDir);
|
||||||
|
if (packageName.equals("android")) {
|
||||||
|
logD("LoadedApk#<init> is android, skip: " + mAppDir);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!loadedPackagesInProcess.add(packageName)) {
|
||||||
|
logD("LoadedApk#<init> has been loaded before, skip: " + mAppDir);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!getBooleanField(loadedApk, "mIncludeCode")) {
|
||||||
|
logD("LoadedApk#<init> mIncludeCode == false: " + mAppDir);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
replaceParentClassLoader(loadedApk.getClassLoader());
|
||||||
|
|
||||||
|
XC_LoadPackage.LoadPackageParam lpparam = new XC_LoadPackage.LoadPackageParam(XposedBridge.sLoadedPackageCallbacks);
|
||||||
|
lpparam.packageName = packageName;
|
||||||
|
lpparam.processName = AndroidAppHelper.currentProcessName();
|
||||||
|
lpparam.classLoader = loadedApk.getClassLoader();
|
||||||
|
lpparam.appInfo = loadedApk.getApplicationInfo();
|
||||||
|
lpparam.isFirstApplication = false;
|
||||||
|
XC_LoadPackage.callAll(lpparam);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
logE("error when hooking LoadedApk.<init>", t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void backup(Object thiz, ActivityThread activityThread,
|
||||||
|
ApplicationInfo aInfo, CompatibilityInfo compatInfo,
|
||||||
|
ClassLoader baseLoader, boolean securityViolation,
|
||||||
|
boolean includeCode, boolean registerPackage) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
package com.elderdrivers.riru.xposed.entry.hooker;
|
||||||
|
|
||||||
|
import android.app.AndroidAppHelper;
|
||||||
|
import android.app.Application;
|
||||||
|
import android.app.Instrumentation;
|
||||||
|
import android.app.LoadedApk;
|
||||||
|
|
||||||
|
import com.elderdrivers.riru.common.KeepMembers;
|
||||||
|
|
||||||
|
import de.robv.android.xposed.XposedBridge;
|
||||||
|
import de.robv.android.xposed.callbacks.XC_LoadPackage;
|
||||||
|
|
||||||
|
import static com.elderdrivers.riru.xposed.entry.hooker.XposedInstallerHooker.hookXposedInstaller;
|
||||||
|
import static com.elderdrivers.riru.xposed.util.ClassLoaderUtils.replaceParentClassLoader;
|
||||||
|
import static de.robv.android.xposed.XposedHelpers.getBooleanField;
|
||||||
|
import static de.robv.android.xposed.XposedHelpers.getObjectField;
|
||||||
|
import static de.robv.android.xposed.XposedInit.INSTALLER_PACKAGE_NAME;
|
||||||
|
import static de.robv.android.xposed.XposedInit.logD;
|
||||||
|
import static de.robv.android.xposed.XposedInit.logE;
|
||||||
|
|
||||||
|
|
||||||
|
public class MakeAppHooker implements KeepMembers {
|
||||||
|
|
||||||
|
public static String className = "android.app.LoadedApk";
|
||||||
|
public static String methodName = "makeApplication";
|
||||||
|
public static String methodSig = "(ZLandroid/app/Instrumentation;)Landroid/app/Application;";
|
||||||
|
|
||||||
|
public static Application hook(Object thiz, boolean forceDefaultAppClass,
|
||||||
|
Instrumentation instrumentation) {
|
||||||
|
if (XposedBridge.disableHooks) {
|
||||||
|
return backup(thiz, forceDefaultAppClass, instrumentation);
|
||||||
|
}
|
||||||
|
logD("LoadedApk#makeApplication() starts");
|
||||||
|
boolean shouldHook = getObjectField(thiz, "mApplication") == null;
|
||||||
|
logD("LoadedApk#makeApplication() shouldHook == " + shouldHook);
|
||||||
|
Application application = backup(thiz, forceDefaultAppClass, instrumentation);
|
||||||
|
if (shouldHook) {
|
||||||
|
try {
|
||||||
|
LoadedApk loadedApk = (LoadedApk) thiz;
|
||||||
|
String packageName = loadedApk.getPackageName();
|
||||||
|
|
||||||
|
if (!getBooleanField(loadedApk, "mIncludeCode")) {
|
||||||
|
logD("LoadedApk#makeApplication() mIncludeCode == false");
|
||||||
|
return application;
|
||||||
|
}
|
||||||
|
|
||||||
|
logD("LoadedApk#makeApplication() mIncludeCode == true");
|
||||||
|
|
||||||
|
replaceParentClassLoader(loadedApk.getClassLoader());
|
||||||
|
|
||||||
|
XC_LoadPackage.LoadPackageParam lpparam = new XC_LoadPackage.LoadPackageParam(
|
||||||
|
XposedBridge.sLoadedPackageCallbacks);
|
||||||
|
lpparam.packageName = packageName;
|
||||||
|
lpparam.processName = AndroidAppHelper.currentProcessName();
|
||||||
|
lpparam.classLoader = loadedApk.getClassLoader();
|
||||||
|
lpparam.appInfo = loadedApk.getApplicationInfo();
|
||||||
|
lpparam.isFirstApplication = true;
|
||||||
|
XC_LoadPackage.callAll(lpparam);
|
||||||
|
if (packageName.equals(INSTALLER_PACKAGE_NAME)) {
|
||||||
|
hookXposedInstaller(lpparam.classLoader);
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
logE("error when hooking LoadedApk#makeApplication", t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return application;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Application backup(Object thiz, boolean forceDefaultAppClass,
|
||||||
|
Instrumentation instrumentation) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
package com.elderdrivers.riru.xposed.entry.hooker;
|
||||||
|
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
|
import com.elderdrivers.riru.common.KeepMembers;
|
||||||
|
|
||||||
|
import de.robv.android.xposed.XC_MethodReplacement;
|
||||||
|
import de.robv.android.xposed.XposedBridge;
|
||||||
|
import de.robv.android.xposed.XposedHelpers;
|
||||||
|
import de.robv.android.xposed.callbacks.XC_LoadPackage;
|
||||||
|
|
||||||
|
import static com.elderdrivers.riru.xposed.util.ClassLoaderUtils.replaceParentClassLoader;
|
||||||
|
import static com.elderdrivers.riru.xposed.util.Utils.logD;
|
||||||
|
import static de.robv.android.xposed.XposedHelpers.findAndHookMethod;
|
||||||
|
import static de.robv.android.xposed.XposedInit.loadedPackagesInProcess;
|
||||||
|
import static de.robv.android.xposed.XposedInit.logE;
|
||||||
|
|
||||||
|
public class StartBootstrapServicesHooker implements KeepMembers {
|
||||||
|
public static String className = "com.android.server.SystemServer";
|
||||||
|
public static String methodName = "startBootstrapServices";
|
||||||
|
public static String methodSig = "()V";
|
||||||
|
|
||||||
|
public static void hook(Object systemServer) {
|
||||||
|
|
||||||
|
if (XposedBridge.disableHooks) {
|
||||||
|
backup(systemServer);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logD("SystemServer#startBootstrapServices() starts");
|
||||||
|
|
||||||
|
try {
|
||||||
|
loadedPackagesInProcess.add("android");
|
||||||
|
|
||||||
|
replaceParentClassLoader(SystemMainHooker.systemServerCL);
|
||||||
|
|
||||||
|
XC_LoadPackage.LoadPackageParam lpparam = new XC_LoadPackage.LoadPackageParam(XposedBridge.sLoadedPackageCallbacks);
|
||||||
|
lpparam.packageName = "android";
|
||||||
|
lpparam.processName = "android"; // it's actually system_server, but other functions return this as well
|
||||||
|
lpparam.classLoader = SystemMainHooker.systemServerCL;
|
||||||
|
lpparam.appInfo = null;
|
||||||
|
lpparam.isFirstApplication = true;
|
||||||
|
XC_LoadPackage.callAll(lpparam);
|
||||||
|
|
||||||
|
// Huawei
|
||||||
|
try {
|
||||||
|
findAndHookMethod("com.android.server.pm.HwPackageManagerService", SystemMainHooker.systemServerCL, "isOdexMode", XC_MethodReplacement.returnConstant(false));
|
||||||
|
} catch (XposedHelpers.ClassNotFoundError | NoSuchMethodError ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
String className = "com.android.server.pm." + (Build.VERSION.SDK_INT >= 23 ? "PackageDexOptimizer" : "PackageManagerService");
|
||||||
|
findAndHookMethod(className, SystemMainHooker.systemServerCL, "dexEntryExists", String.class, XC_MethodReplacement.returnConstant(true));
|
||||||
|
} catch (XposedHelpers.ClassNotFoundError | NoSuchMethodError ignored) {
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
logE("error when hooking startBootstrapServices", t);
|
||||||
|
} finally {
|
||||||
|
backup(systemServer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void backup(Object systemServer) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
package com.elderdrivers.riru.xposed.entry.hooker;
|
||||||
|
|
||||||
|
import android.app.ActivityThread;
|
||||||
|
|
||||||
|
import com.elderdrivers.riru.common.KeepMembers;
|
||||||
|
import com.elderdrivers.riru.xposed.entry.Router;
|
||||||
|
|
||||||
|
import de.robv.android.xposed.XposedBridge;
|
||||||
|
|
||||||
|
import static de.robv.android.xposed.XposedInit.logD;
|
||||||
|
import static de.robv.android.xposed.XposedInit.logE;
|
||||||
|
|
||||||
|
|
||||||
|
// system_server initialization
|
||||||
|
// ed: only support sdk >= 21 for now
|
||||||
|
public class SystemMainHooker implements KeepMembers {
|
||||||
|
|
||||||
|
public static String className = "android.app.ActivityThread";
|
||||||
|
public static String methodName = "systemMain";
|
||||||
|
public static String methodSig = "()Landroid/app/ActivityThread;";
|
||||||
|
|
||||||
|
public static ClassLoader systemServerCL;
|
||||||
|
|
||||||
|
public static ActivityThread hook() {
|
||||||
|
if (XposedBridge.disableHooks) {
|
||||||
|
return backup();
|
||||||
|
}
|
||||||
|
logD("ActivityThread#systemMain() starts");
|
||||||
|
ActivityThread activityThread = backup();
|
||||||
|
try {
|
||||||
|
// get system_server classLoader
|
||||||
|
systemServerCL = Thread.currentThread().getContextClassLoader();
|
||||||
|
Router.startSystemServerHook();
|
||||||
|
} catch (Throwable t) {
|
||||||
|
logE("error when hooking systemMain", t);
|
||||||
|
}
|
||||||
|
return activityThread;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ActivityThread backup() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
package com.elderdrivers.riru.xposed.entry.hooker;
|
||||||
|
|
||||||
|
import com.elderdrivers.riru.xposed.util.Utils;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import de.robv.android.xposed.XC_MethodHook;
|
||||||
|
import de.robv.android.xposed.XC_MethodReplacement;
|
||||||
|
import de.robv.android.xposed.XposedBridge;
|
||||||
|
|
||||||
|
import static de.robv.android.xposed.XposedHelpers.callStaticMethod;
|
||||||
|
import static de.robv.android.xposed.XposedHelpers.findAndHookMethod;
|
||||||
|
import static de.robv.android.xposed.XposedHelpers.findClass;
|
||||||
|
import static de.robv.android.xposed.XposedHelpers.getObjectField;
|
||||||
|
import static de.robv.android.xposed.XposedHelpers.setObjectField;
|
||||||
|
import static de.robv.android.xposed.XposedInit.INSTALLER_PACKAGE_NAME;
|
||||||
|
|
||||||
|
public class XposedInstallerHooker {
|
||||||
|
|
||||||
|
public static void hookXposedInstaller(ClassLoader classLoader) {
|
||||||
|
try {
|
||||||
|
final String xposedAppClass = INSTALLER_PACKAGE_NAME + ".XposedApp";
|
||||||
|
final Class InstallZipUtil = findClass(INSTALLER_PACKAGE_NAME
|
||||||
|
+ ".util.InstallZipUtil", classLoader);
|
||||||
|
findAndHookMethod(xposedAppClass, classLoader, "getActiveXposedVersion",
|
||||||
|
XC_MethodReplacement.returnConstant(XposedBridge.getXposedVersion()));
|
||||||
|
findAndHookMethod(xposedAppClass, classLoader,
|
||||||
|
"reloadXposedProp", new XC_MethodHook() {
|
||||||
|
@Override
|
||||||
|
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
|
||||||
|
Utils.logD("before reloadXposedProp...");
|
||||||
|
final String propFieldName = "mXposedProp";
|
||||||
|
final Object thisObject = param.thisObject;
|
||||||
|
if (getObjectField(thisObject, propFieldName) != null) {
|
||||||
|
param.setResult(null);
|
||||||
|
Utils.logD("reloadXposedProp already done, skip...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
File file = new File("/system/framework/edconfig.dex");
|
||||||
|
FileInputStream is = null;
|
||||||
|
try {
|
||||||
|
is = new FileInputStream(file);
|
||||||
|
Object props = callStaticMethod(InstallZipUtil,
|
||||||
|
"parseXposedProp", is);
|
||||||
|
synchronized (thisObject) {
|
||||||
|
setObjectField(thisObject, propFieldName, props);
|
||||||
|
}
|
||||||
|
Utils.logD("reloadXposedProp done...");
|
||||||
|
param.setResult(null);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Utils.logE("Could not read " + file.getPath(), e);
|
||||||
|
} finally {
|
||||||
|
if (is != null) {
|
||||||
|
try {
|
||||||
|
is.close();
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (Throwable t) {
|
||||||
|
Utils.logE("Could not hook Xposed Installer", t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,106 @@
|
||||||
|
package com.elderdrivers.riru.xposed.util;
|
||||||
|
|
||||||
|
import android.os.Build;
|
||||||
|
import android.util.ArrayMap;
|
||||||
|
|
||||||
|
import com.elderdrivers.riru.xposed.BuildConfig;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import dalvik.system.PathClassLoader;
|
||||||
|
|
||||||
|
public class ClassLoaderUtils {
|
||||||
|
|
||||||
|
public static final String DEXPATH = "/system/framework/edxposed.dex:/system/framework/eddalvikdx.dex:/system/framework/eddexmaker.dex";
|
||||||
|
|
||||||
|
public static void replaceParentClassLoader(ClassLoader appClassLoader) {
|
||||||
|
if (appClassLoader == null) {
|
||||||
|
Utils.logE("appClassLoader is null, you might be kidding me?");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
ClassLoader curCL = ClassLoaderUtils.class.getClassLoader();
|
||||||
|
ClassLoader parent = appClassLoader;
|
||||||
|
ClassLoader lastChild = appClassLoader;
|
||||||
|
while (parent != null) {
|
||||||
|
ClassLoader tmp = parent.getParent();
|
||||||
|
if (tmp == curCL) {
|
||||||
|
Utils.logD("replacing has been done before, skip.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (tmp == null) {
|
||||||
|
Utils.logD("before replacing =========================================>");
|
||||||
|
dumpClassLoaders(appClassLoader);
|
||||||
|
Field parentField = ClassLoader.class.getDeclaredField("parent");
|
||||||
|
parentField.setAccessible(true);
|
||||||
|
parentField.set(curCL, parent);
|
||||||
|
parentField.set(lastChild, curCL);
|
||||||
|
Utils.logD("after replacing ==========================================>");
|
||||||
|
dumpClassLoaders(appClassLoader);
|
||||||
|
}
|
||||||
|
lastChild = parent;
|
||||||
|
parent = tmp;
|
||||||
|
}
|
||||||
|
} catch (Throwable throwable) {
|
||||||
|
Utils.logE("error when replacing class loader.", throwable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void dumpClassLoaders(ClassLoader classLoader) {
|
||||||
|
if (BuildConfig.DEBUG) {
|
||||||
|
while (classLoader != null) {
|
||||||
|
Utils.logD(classLoader + " =>");
|
||||||
|
classLoader = classLoader.getParent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<ClassLoader> getAppClassLoader() {
|
||||||
|
List<ClassLoader> cacheLoaders = new ArrayList<>(0);
|
||||||
|
try {
|
||||||
|
Utils.logD("start getting app classloader");
|
||||||
|
Class appLoadersClass = Class.forName("android.app.ApplicationLoaders");
|
||||||
|
Field loadersField = appLoadersClass.getDeclaredField("gApplicationLoaders");
|
||||||
|
loadersField.setAccessible(true);
|
||||||
|
Object loaders = loadersField.get(null);
|
||||||
|
Field mLoaderMapField = loaders.getClass().getDeclaredField("mLoaders");
|
||||||
|
mLoaderMapField.setAccessible(true);
|
||||||
|
ArrayMap<String, ClassLoader> mLoaderMap = (ArrayMap<String, ClassLoader>) mLoaderMapField.get(loaders);
|
||||||
|
Utils.logD("mLoaders size = " + mLoaderMap.size());
|
||||||
|
cacheLoaders = new ArrayList<>(mLoaderMap.values());
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Utils.logE("error get app class loader.", ex);
|
||||||
|
}
|
||||||
|
return cacheLoaders;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static HashSet<ClassLoader> classLoaders = new HashSet<>();
|
||||||
|
|
||||||
|
public static boolean addPathToClassLoader(ClassLoader classLoader) {
|
||||||
|
if (!(classLoader instanceof PathClassLoader)) {
|
||||||
|
Utils.logW(classLoader + " is not a BaseDexClassLoader!!!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (classLoaders.contains(classLoader)) {
|
||||||
|
Utils.logD(classLoader + " has been hooked before");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
PathClassLoader baseDexClassLoader = (PathClassLoader) classLoader;
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
baseDexClassLoader.addDexPath(DEXPATH);
|
||||||
|
} else {
|
||||||
|
DexUtils.injectDexAtFirst(DEXPATH, baseDexClassLoader);
|
||||||
|
}
|
||||||
|
classLoaders.add(classLoader);
|
||||||
|
return true;
|
||||||
|
} catch (Throwable throwable) {
|
||||||
|
Utils.logE("error when addPath to ClassLoader: " + classLoader, throwable);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
package com.elderdrivers.riru.xposed.util;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
|
import java.lang.reflect.Array;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
|
||||||
|
import dalvik.system.BaseDexClassLoader;
|
||||||
|
import dalvik.system.DexClassLoader;
|
||||||
|
import dalvik.system.PathClassLoader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For 6.0 only.
|
||||||
|
*/
|
||||||
|
@TargetApi(Build.VERSION_CODES.M)
|
||||||
|
public class DexUtils {
|
||||||
|
|
||||||
|
public static void injectDexAtFirst(String dexPath, BaseDexClassLoader classLoader) throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
|
||||||
|
DexClassLoader dexClassLoader = new DexClassLoader(dexPath, null, dexPath, classLoader);
|
||||||
|
Object baseDexElements = getDexElements(getPathList(classLoader));
|
||||||
|
Object newDexElements = getDexElements(getPathList(dexClassLoader));
|
||||||
|
Object allDexElements = combineArray(newDexElements, baseDexElements);
|
||||||
|
Object pathList = getPathList(classLoader);
|
||||||
|
setField(pathList, pathList.getClass(), "dexElements", allDexElements);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Object getDexElements(Object paramObject)
|
||||||
|
throws IllegalArgumentException, NoSuchFieldException, IllegalAccessException {
|
||||||
|
return getField(paramObject, paramObject.getClass(), "dexElements");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Object getPathList(Object baseDexClassLoader)
|
||||||
|
throws IllegalArgumentException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
|
||||||
|
return getField(baseDexClassLoader, Class.forName("dalvik.system.BaseDexClassLoader"), "pathList");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Object combineArray(Object firstArray, Object secondArray) {
|
||||||
|
Class<?> localClass = firstArray.getClass().getComponentType();
|
||||||
|
int firstArrayLength = Array.getLength(firstArray);
|
||||||
|
int allLength = firstArrayLength + Array.getLength(secondArray);
|
||||||
|
Object result = Array.newInstance(localClass, allLength);
|
||||||
|
for (int k = 0; k < allLength; ++k) {
|
||||||
|
if (k < firstArrayLength) {
|
||||||
|
Array.set(result, k, Array.get(firstArray, k));
|
||||||
|
} else {
|
||||||
|
Array.set(result, k, Array.get(secondArray, k - firstArrayLength));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Object getField(Object obj, Class<?> cl, String field)
|
||||||
|
throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
|
||||||
|
Field localField = cl.getDeclaredField(field);
|
||||||
|
localField.setAccessible(true);
|
||||||
|
return localField.get(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setField(Object obj, Class<?> cl, String field, Object value)
|
||||||
|
throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
|
||||||
|
Field localField = cl.getDeclaredField(field);
|
||||||
|
localField.setAccessible(true);
|
||||||
|
localField.set(obj, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
package com.elderdrivers.riru.xposed.util;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.elderdrivers.riru.xposed.BuildConfig;
|
||||||
|
|
||||||
|
|
||||||
|
public class Utils {
|
||||||
|
|
||||||
|
public static final String LOG_TAG = "EdXposed-Fwk";
|
||||||
|
|
||||||
|
public static void logD(Object msg) {
|
||||||
|
if (BuildConfig.DEBUG)
|
||||||
|
Log.d(LOG_TAG, msg.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void logD(String msg, Throwable throwable) {
|
||||||
|
if (BuildConfig.DEBUG)
|
||||||
|
Log.d(LOG_TAG, msg, throwable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void logW(String msg) {
|
||||||
|
Log.w(LOG_TAG, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void logW(String msg, Throwable throwable) {
|
||||||
|
Log.w(LOG_TAG, msg, throwable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void logI(String msg) {
|
||||||
|
Log.i(LOG_TAG, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void logI(String msg, Throwable throwable) {
|
||||||
|
Log.i(LOG_TAG, msg, throwable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void logE(String msg) {
|
||||||
|
Log.e(LOG_TAG, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void logE(String msg, Throwable throwable) {
|
||||||
|
Log.e(LOG_TAG, msg, throwable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,235 @@
|
||||||
|
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() {}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
package de.robv.android.xposed;
|
||||||
|
|
||||||
|
public class GeneClass_Template {
|
||||||
|
public static java.lang.reflect.Member method;
|
||||||
|
public static de.robv.android.xposed.XposedBridge.AdditionalHookInfo tAdditionalInfoObj;
|
||||||
|
|
||||||
|
public static boolean backup(java.lang.Object obj, int i) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean hook(java.lang.Object obj, int i) throws Throwable {
|
||||||
|
java.lang.Throwable th;
|
||||||
|
if (!de.robv.android.xposed.XposedBridge.disableHooks) {
|
||||||
|
java.lang.Object[] snapshot = tAdditionalInfoObj.callbacks.getSnapshot();
|
||||||
|
int length = snapshot.length;
|
||||||
|
if (length != 0) {
|
||||||
|
de.robv.android.xposed.XC_MethodHook.MethodHookParam methodHookParam = new de.robv.android.xposed.XC_MethodHook.MethodHookParam();
|
||||||
|
methodHookParam.method = method;
|
||||||
|
java.lang.Object[] objArr = new java.lang.Object[1];
|
||||||
|
methodHookParam.args = objArr;
|
||||||
|
methodHookParam.thisObject = obj;
|
||||||
|
objArr[0] = java.lang.Integer.valueOf(i);
|
||||||
|
int i2 = 0;
|
||||||
|
do {
|
||||||
|
try {
|
||||||
|
((de.robv.android.xposed.XC_MethodHook) snapshot[i2]).callBeforeHookedMethod(methodHookParam);
|
||||||
|
if (methodHookParam.returnEarly) {
|
||||||
|
i2++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (java.lang.Throwable th2) {
|
||||||
|
de.robv.android.xposed.XposedBridge.log(th2);
|
||||||
|
methodHookParam.setResult(null);
|
||||||
|
methodHookParam.returnEarly = false;
|
||||||
|
}
|
||||||
|
i2++;
|
||||||
|
} while (i2 < length);
|
||||||
|
if (!methodHookParam.returnEarly) {
|
||||||
|
try {
|
||||||
|
methodHookParam.setResult(java.lang.Boolean.valueOf(backup(obj, i)));
|
||||||
|
} catch (java.lang.Throwable th3) {
|
||||||
|
methodHookParam.setThrowable(th3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i2--;
|
||||||
|
do {
|
||||||
|
java.lang.Object result = methodHookParam.getResult();
|
||||||
|
Throwable th2 = methodHookParam.getThrowable();
|
||||||
|
try {
|
||||||
|
((de.robv.android.xposed.XC_MethodHook) snapshot[i2]).callAfterHookedMethod(methodHookParam);
|
||||||
|
} catch (java.lang.Throwable th4) {
|
||||||
|
de.robv.android.xposed.XposedBridge.log(th4);
|
||||||
|
if (th2 == null) {
|
||||||
|
methodHookParam.setResult(result);
|
||||||
|
} else {
|
||||||
|
methodHookParam.setThrowable(th2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i2--;
|
||||||
|
} while (i2 >= 0);
|
||||||
|
if (!methodHookParam.hasThrowable()) {
|
||||||
|
return ((java.lang.Boolean) methodHookParam.getResult()).booleanValue();
|
||||||
|
}
|
||||||
|
throw methodHookParam.getThrowable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return backup(obj, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setup(java.lang.reflect.Member member, de.robv.android.xposed.XposedBridge.AdditionalHookInfo additionalHookInfo) {
|
||||||
|
method = member;
|
||||||
|
tAdditionalInfoObj = additionalHookInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
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;
|
||||||
|
public Wrapper(IXposedHookInitPackageResources instance) {
|
||||||
|
this.instance = instance;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void handleInitPackageResources(InitPackageResourcesParam resparam) throws Throwable {
|
||||||
|
instance.handleInitPackageResources(resparam);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
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;
|
||||||
|
public Wrapper(IXposedHookLoadPackage instance) {
|
||||||
|
this.instance = instance;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void handleLoadPackage(LoadPackageParam lpparam) throws Throwable {
|
||||||
|
instance.handleLoadPackage(lpparam);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
package de.robv.android.xposed;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 {
|
||||||
|
/*package*/ StartupParam() {}
|
||||||
|
|
||||||
|
/** 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
package de.robv.android.xposed;
|
||||||
|
|
||||||
|
/** Marker interface for Xposed modules. Cannot be implemented directly. */
|
||||||
|
/* package */ interface IXposedMod {}
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
package de.robv.android.xposed;
|
||||||
|
|
||||||
|
import android.os.SELinux;
|
||||||
|
|
||||||
|
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() {
|
||||||
|
return sIsSELinuxEnabled && SELinux.isSELinuxEnforced();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
private static boolean sIsSELinuxEnabled = false;
|
||||||
|
private static BaseService sServiceAppDataFile = new DirectAccessService(); // ed: initialized directly
|
||||||
|
|
||||||
|
/*package*/ 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();
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,168 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,87 @@
|
||||||
|
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) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,295 @@
|
||||||
|
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 org.xmlpull.v1.XmlPullParserException;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
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 final File mFile;
|
||||||
|
private final String mFilename;
|
||||||
|
private Map<String, Object> mMap;
|
||||||
|
private boolean mLoaded = false;
|
||||||
|
private long mLastModified;
|
||||||
|
private long mFileSize;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read settings from the specified file.
|
||||||
|
* @param prefFile The file to read the preferences from.
|
||||||
|
*/
|
||||||
|
public XSharedPreferences(File prefFile) {
|
||||||
|
mFile = prefFile;
|
||||||
|
mFilename = mFile.getAbsolutePath();
|
||||||
|
startLoadFromDisk();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
mFile = new File(Environment.getDataDirectory(), "data/" + packageName + "/shared_prefs/" + prefFileName + ".xml");
|
||||||
|
mFilename = mFile.getAbsolutePath();
|
||||||
|
startLoadFromDisk();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
return mFile.setReadable(true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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", 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", 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())
|
||||||
|
startLoadFromDisk();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 Not supported by this implementation. */
|
||||||
|
@Deprecated
|
||||||
|
@Override
|
||||||
|
public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
|
||||||
|
throw new UnsupportedOperationException("listeners are not supported in this implementation");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @deprecated Not supported by this implementation. */
|
||||||
|
@Deprecated
|
||||||
|
@Override
|
||||||
|
public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
|
||||||
|
throw new UnsupportedOperationException("listeners are not supported in this implementation");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,529 @@
|
||||||
|
package de.robv.android.xposed;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
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.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import com.elderdrivers.riru.xposed.dexmaker.DynamicBridge;
|
||||||
|
import de.robv.android.xposed.XC_MethodHook.MethodHookParam;
|
||||||
|
import de.robv.android.xposed.callbacks.XC_InitPackageResources;
|
||||||
|
import de.robv.android.xposed.callbacks.XC_LoadPackage;
|
||||||
|
|
||||||
|
import static de.robv.android.xposed.XposedHelpers.getIntField;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
private 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<>();
|
||||||
|
|
||||||
|
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);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
private static void initXResources() throws IOException {
|
||||||
|
// ed: no support for now
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("SetWorldReadable")
|
||||||
|
private static File ensureSuperDexFile(String clz, Class<?> realSuperClz, Class<?> topClz) throws IOException {
|
||||||
|
XposedBridge.removeFinalFlagNative(realSuperClz);
|
||||||
|
File dexFile = DexCreator.ensure(clz, realSuperClz, topClz);
|
||||||
|
dexFile.setReadable(true, false);
|
||||||
|
return dexFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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() {
|
||||||
|
// ed: fixed value for now
|
||||||
|
return 90;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes a message to the Xposed error 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) {
|
||||||
|
Log.i(TAG, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs a stack trace to the Xposed error 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);
|
||||||
|
hookMethodNative(hookMethod, declaringClass, slot, 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) {
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
private static Object handleHookedMethod(Member method, int 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
// TODO not supported yet
|
||||||
|
// synchronized (sInitPackageResourcesCallbacks) {
|
||||||
|
// sInitPackageResourcesCallbacks.add(callback);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Intercept every call to the specified method and call a handler function instead.
|
||||||
|
* @param method The method to intercept
|
||||||
|
*/
|
||||||
|
private synchronized static void hookMethodNative(final Member method, Class<?> declaringClass,
|
||||||
|
int slot, final Object additionalInfoObj) {
|
||||||
|
DynamicBridge.hookMethod(method, (AdditionalHookInfo) additionalInfoObj);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Object invokeOriginalMethodNative(Member method, int methodId,
|
||||||
|
Class<?>[] parameterTypes,
|
||||||
|
Class<?> returnType,
|
||||||
|
Object thisObject, Object[] args)
|
||||||
|
throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
|
||||||
|
return DynamicBridge.invokeOriginalMethod(method, 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 NullPointerException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
|
return invokeOriginalMethodNative(method, 0, 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 native void removeFinalFlagNative(Class<?> 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 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
|
|
@ -0,0 +1,234 @@
|
||||||
|
package de.robv.android.xposed;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.app.AndroidAppHelper;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.android.internal.os.ZygoteInit;
|
||||||
|
import com.elderdrivers.riru.xposed.entry.Router;
|
||||||
|
import com.elderdrivers.riru.xposed.util.Utils;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipFile;
|
||||||
|
|
||||||
|
import dalvik.system.DexFile;
|
||||||
|
import dalvik.system.PathClassLoader;
|
||||||
|
import de.robv.android.xposed.services.BaseService;
|
||||||
|
|
||||||
|
import static de.robv.android.xposed.XposedHelpers.closeSilently;
|
||||||
|
import static de.robv.android.xposed.XposedHelpers.findClass;
|
||||||
|
import static de.robv.android.xposed.XposedHelpers.findFieldIfExists;
|
||||||
|
import static de.robv.android.xposed.XposedHelpers.setStaticBooleanField;
|
||||||
|
import static de.robv.android.xposed.XposedHelpers.setStaticLongField;
|
||||||
|
|
||||||
|
public final class XposedInit {
|
||||||
|
private static final String TAG = XposedBridge.TAG;
|
||||||
|
private static boolean startsSystemServer = false;
|
||||||
|
private static final String startClassName = ""; // ed: no support for tool process anymore
|
||||||
|
|
||||||
|
public static final String INSTALLER_PACKAGE_NAME = "de.robv.android.xposed.installer";
|
||||||
|
@SuppressLint("SdCardPath")
|
||||||
|
private static final String BASE_DIR = Build.VERSION.SDK_INT >= 24
|
||||||
|
? "/data/user_de/0/" + INSTALLER_PACKAGE_NAME + "/"
|
||||||
|
: "/data/data/" + INSTALLER_PACKAGE_NAME + "/";
|
||||||
|
private static final String INSTANT_RUN_CLASS = "com.android.tools.fd.runtime.BootstrapApplication";
|
||||||
|
// TODO not supported yet
|
||||||
|
private static boolean disableResources = true;
|
||||||
|
private static final String[] XRESOURCES_CONFLICTING_PACKAGES = {"com.sygic.aura"};
|
||||||
|
|
||||||
|
private XposedInit() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook some methods which we want to create an easier interface for developers.
|
||||||
|
*/
|
||||||
|
/*package*/
|
||||||
|
public static void initForZygote(boolean isSystem) throws Throwable {
|
||||||
|
startsSystemServer = isSystem;
|
||||||
|
Router.startBootstrapHook(isSystem);
|
||||||
|
// 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) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*package*/
|
||||||
|
static void hookResources() throws Throwable {
|
||||||
|
// ed: not for now
|
||||||
|
}
|
||||||
|
|
||||||
|
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>BASE_DIR/conf/modules.list</code>
|
||||||
|
*/
|
||||||
|
private static volatile AtomicBoolean modulesLoaded = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
public static void loadModules() throws IOException {
|
||||||
|
if (!modulesLoaded.compareAndSet(false, true)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final String filename = BASE_DIR + "conf/modules.list";
|
||||||
|
BaseService service = SELinuxHelper.getAppDataFileService();
|
||||||
|
if (!service.checkFileExists(filename)) {
|
||||||
|
Log.e(TAG, "Cannot load any modules because " + filename + " was not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClassLoader topClassLoader = XposedBridge.BOOTCLASSLOADER;
|
||||||
|
ClassLoader parent;
|
||||||
|
while ((parent = topClassLoader.getParent()) != null) {
|
||||||
|
topClassLoader = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
InputStream stream = service.getFileInputStream(filename);
|
||||||
|
BufferedReader apks = new BufferedReader(new InputStreamReader(stream));
|
||||||
|
String apk;
|
||||||
|
while ((apk = apks.readLine()) != null) {
|
||||||
|
loadModule(apk, topClassLoader);
|
||||||
|
}
|
||||||
|
apks.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a module from an APK by calling the init(String) method for all classes defined
|
||||||
|
* in <code>assets/xposed_init</code>.
|
||||||
|
*/
|
||||||
|
private static void loadModule(String apk, ClassLoader topClassLoader) {
|
||||||
|
Log.i(TAG, "Loading modules from " + apk);
|
||||||
|
|
||||||
|
if (!new File(apk).exists()) {
|
||||||
|
Log.e(TAG, " File does not exist");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DexFile dexFile;
|
||||||
|
try {
|
||||||
|
dexFile = new DexFile(apk);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, " Cannot load module", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dexFile.loadClass(INSTANT_RUN_CLASS, topClassLoader) != null) {
|
||||||
|
Log.e(TAG, " Cannot load module, please disable \"Instant Run\" in Android Studio.");
|
||||||
|
closeSilently(dexFile);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dexFile.loadClass(XposedBridge.class.getName(), topClassLoader) != 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");
|
||||||
|
closeSilently(dexFile);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
closeSilently(dexFile);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
is = zipFile.getInputStream(zipEntry);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, " Cannot read assets/xposed_init in the APK", e);
|
||||||
|
closeSilently(zipFile);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClassLoader mcl = new PathClassLoader(apk, XposedInit.class.getClassLoader());
|
||||||
|
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;
|
||||||
|
((IXposedHookZygoteInit) moduleInstance).initZygote(param);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (moduleInstance instanceof IXposedHookLoadPackage)
|
||||||
|
XposedBridge.hookLoadPackage(new IXposedHookLoadPackage.Wrapper((IXposedHookLoadPackage) moduleInstance));
|
||||||
|
|
||||||
|
if (moduleInstance instanceof IXposedHookInitPackageResources)
|
||||||
|
XposedBridge.hookInitPackageResources(new IXposedHookInitPackageResources.Wrapper((IXposedHookInitPackageResources) moduleInstance));
|
||||||
|
} 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, " Failed to load module from " + apk, e);
|
||||||
|
} finally {
|
||||||
|
closeSilently(is);
|
||||||
|
closeSilently(zipFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final static HashSet<String> loadedPackagesInProcess = new HashSet<>(1);
|
||||||
|
|
||||||
|
public static void logD(String prefix) {
|
||||||
|
Utils.logD(String.format("%s: pkg=%s, prc=%s", prefix, AndroidAppHelper.currentPackageName(),
|
||||||
|
AndroidAppHelper.currentProcessName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void logE(String prefix, Throwable throwable) {
|
||||||
|
Utils.logE(String.format("%s: pkg=%s, prc=%s", prefix, AndroidAppHelper.currentPackageName(),
|
||||||
|
AndroidAppHelper.currentProcessName()), throwable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,99 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,138 @@
|
||||||
|
package de.robv.android.xposed.callbacks;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
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> {
|
||||||
|
/**
|
||||||
|
* 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.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 {}
|
||||||
|
|
||||||
|
/** @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;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
/**
|
||||||
|
* Contains the main classes of the Xposed framework.
|
||||||
|
*/
|
||||||
|
package de.robv.android.xposed;
|
||||||
|
|
@ -0,0 +1,179 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,166 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,113 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
/**
|
||||||
|
* Contains file access services provided by the Xposed framework.
|
||||||
|
*/
|
||||||
|
package de.robv.android.xposed.services;
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
/.externalNativeBuild
|
||||||
|
/build
|
||||||
|
/libs
|
||||||
|
/obj
|
||||||
|
/release
|
||||||
|
/template_override/system/framework/edxposed.dex
|
||||||
|
*.iml
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
function copy_files {
|
||||||
|
# /data/misc/riru/modules/template exists -> libriru_template.so will be loaded
|
||||||
|
# Change "template" to your module name
|
||||||
|
# You can also use this folder as your config folder
|
||||||
|
NAME="edxposed"
|
||||||
|
mkdir -p $TMP_DIR_MAGISK/data/misc/riru/modules/$NAME
|
||||||
|
cp $MODULE_NAME/template_override/riru_module.prop $TMP_DIR_MAGISK/data/misc/riru/modules/$NAME/module.prop
|
||||||
|
|
||||||
|
cp $MODULE_NAME/template_override/config.sh $TMP_DIR_MAGISK
|
||||||
|
cp $MODULE_NAME/template_override/module.prop $TMP_DIR_MAGISK
|
||||||
|
|
||||||
|
cp -r $MODULE_NAME/template_override/system $TMP_DIR_MAGISK
|
||||||
|
cp -r $MODULE_NAME/template_override/common $TMP_DIR_MAGISK
|
||||||
|
cp -r $MODULE_NAME/template_override/META-INF $TMP_DIR_MAGISK
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
apply plugin: 'com.android.library'
|
||||||
|
version "v0.2.6_beta"
|
||||||
|
extensions["module_name"] = "EdXposed"
|
||||||
|
android {
|
||||||
|
compileSdkVersion 28
|
||||||
|
defaultConfig {
|
||||||
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
|
|
||||||
|
externalNativeBuild {
|
||||||
|
ndkBuild {
|
||||||
|
abiFilters 'arm64-v8a', 'armeabi-v7a'
|
||||||
|
arguments "NDK_PROJECT_PATH=jni/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
externalNativeBuild {
|
||||||
|
ndkBuild {
|
||||||
|
path 'jni/Android.mk'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
afterEvaluate {
|
||||||
|
|
||||||
|
android.libraryVariants.all { variant ->
|
||||||
|
def nameCapped = variant.name.capitalize()
|
||||||
|
def nameLowered = variant.name.toLowerCase()
|
||||||
|
|
||||||
|
def zipTask = task("zip${nameCapped}", type: Exec, dependsOn: ":Bridge:makeAndCopy${nameCapped}") {
|
||||||
|
workingDir '..'
|
||||||
|
commandLine 'sh', 'build.sh',\
|
||||||
|
project.name,\
|
||||||
|
"${project.version}-${nameLowered}",\
|
||||||
|
"${project.extensions['module_name']}"
|
||||||
|
}
|
||||||
|
|
||||||
|
// def renameTask = task("build${nameCapped}", type: Copy) {
|
||||||
|
// from "release/magisk-${project.name}-arm-arm64-${project.version}.zip"
|
||||||
|
// into "release"
|
||||||
|
// rename("${project.name}", "${project.extensions['module_name']}")
|
||||||
|
// rename("${project.version}", "${project.version}-${nameLowered}")
|
||||||
|
// }
|
||||||
|
|
||||||
|
def pushTask = task("push${nameCapped}", type: Exec) {
|
||||||
|
workingDir 'release'
|
||||||
|
commandLine 'cmd', '/c',
|
||||||
|
"adb push magisk-${project.extensions['module_name']}-arm-arm64" +
|
||||||
|
"-${project.version}-${nameLowered}.zip /sdcard/"
|
||||||
|
}
|
||||||
|
|
||||||
|
// renameTask.dependsOn(zipTask)
|
||||||
|
pushTask.dependsOn(zipTask)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue