Migrate to GitHub

This commit is contained in:
solohsu 2019-01-19 01:54:14 +08:00
parent f598a7d6a4
commit 628ac9fd33
617 changed files with 134710 additions and 1 deletions

14
.gitignore vendored Normal file
View File

@ -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

1
Bridge/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

132
Bridge/build.gradle Normal file
View File

@ -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.

30
Bridge/proguard-rules.pro vendored Normal file
View File

@ -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 { *; }

View File

@ -0,0 +1,2 @@
<manifest package="com.elderdrivers.riru.xposed"
xmlns:android="http://schemas.android.com/apk/res/android"/>

View File

@ -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.

View File

@ -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

View File

@ -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())

View File

@ -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

File diff suppressed because it is too large Load Diff

View 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);
}
}
}

View 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('&copy;') = 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('&copy;') = 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('&copy;') = 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('&copy;') = 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('&copy;') = 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('&copy;') = 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('&copy;') = 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('&copy;') = false
* </pre>
*
* @param ch the character to check
* @return true if between 48 and 57 or 65 and 90 or 97 and 122 inclusive
*/
public static boolean isAsciiAlphanumeric(char ch) {
return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9');
}
}

File diff suppressed because it is too large Load Diff

View 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;
}
}

View 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;
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View 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&lt;Font&gt; {
* 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();
}

View 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;
}
}

View 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();
}
}

View 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;
}
}

View File

@ -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(&quot;An object: &quot; + 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) &amp;&amp; !f.getName().equals(&quot;password&quot;);
* }
* }).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 &quot;toString()'ed&quot;, such an object is rendered as if
* <code>Object.toString()</code> had been called and not implemented by the object.
* </p>
*
* @param clazz
* The class of object parameter
*/
protected void appendFieldsIn(Class<?> clazz) {
if (clazz.isArray()) {
this.reflectionAppendArray(this.getObject());
return;
}
Field[] fields = clazz.getDeclaredFields();
AccessibleObject.setAccessible(fields, true);
for (Field field : fields) {
String fieldName = field.getName();
if (this.accept(field)) {
try {
// Warning: Field.get(Object) creates wrappers objects
// for primitive types.
Object fieldValue = this.getValue(field);
this.append(fieldName, fieldValue);
} catch (IllegalAccessException ex) {
//this can't happen. Would get a Security exception
// instead
//throw a runtime exception in case the impossible
// happens.
throw new InternalError("Unexpected IllegalAccessException: " + ex.getMessage());
}
}
}
}
/**
* @return Returns the excludeFieldNames.
*/
public String[] getExcludeFieldNames() {
return this.excludeFieldNames.clone();
}
/**
* <p>
* Gets the last super class to stop appending fields for.
* </p>
*
* @return The last super class to stop appending fields for.
*/
public Class<?> getUpToClass() {
return this.upToClass;
}
/**
* <p>
* Calls <code>java.lang.reflect.Field.get(Object)</code>.
* </p>
*
* @param field
* The Field to query.
* @return The Object from the given Field.
*
* @throws IllegalArgumentException
* see {@link java.lang.reflect.Field#get(Object)}
* @throws IllegalAccessException
* see {@link java.lang.reflect.Field#get(Object)}
*
* @see java.lang.reflect.Field#get(Object)
*/
protected Object getValue(Field field) throws IllegalArgumentException, IllegalAccessException {
return field.get(this.getObject());
}
/**
* <p>
* Gets whether or not to append static fields.
* </p>
*
* @return Whether or not to append static fields.
* @since 2.1
*/
public boolean isAppendStatics() {
return this.appendStatics;
}
/**
* <p>
* Gets whether or not to append transient fields.
* </p>
*
* @return Whether or not to append transient fields.
*/
public boolean isAppendTransients() {
return this.appendTransients;
}
/**
* <p>
* Append to the <code>toString</code> an <code>Object</code> array.
* </p>
*
* @param array
* the array to add to the <code>toString</code>
* @return this
*/
public ReflectionToStringBuilder reflectionAppendArray(Object array) {
this.getStyle().reflectionAppendArrayDetail(this.getStringBuffer(), null, array);
return this;
}
/**
* <p>
* Sets whether or not to append static fields.
* </p>
*
* @param appendStatics
* Whether or not to append static fields.
* @since 2.1
*/
public void setAppendStatics(boolean appendStatics) {
this.appendStatics = appendStatics;
}
/**
* <p>
* Sets whether or not to append transient fields.
* </p>
*
* @param appendTransients
* Whether or not to append transient fields.
*/
public void setAppendTransients(boolean appendTransients) {
this.appendTransients = appendTransients;
}
/**
* Sets the field names to exclude.
*
* @param excludeFieldNamesParam
* The excludeFieldNames to excluding from toString or <code>null</code>.
* @return <code>this</code>
*/
public ReflectionToStringBuilder setExcludeFieldNames(String... excludeFieldNamesParam) {
if (excludeFieldNamesParam == null) {
this.excludeFieldNames = null;
} else {
//clone and remove nulls
this.excludeFieldNames = toNoNullStringArray(excludeFieldNamesParam);
Arrays.sort(this.excludeFieldNames);
}
return this;
}
/**
* <p>
* Sets the last super class to stop appending fields for.
* </p>
*
* @param clazz
* The last super class to stop appending fields for.
*/
public void setUpToClass(Class<?> clazz) {
if (clazz != null) {
Object object = getObject();
if (object != null && clazz.isInstance(object) == false) {
throw new IllegalArgumentException("Specified class is not a superclass of the object");
}
}
this.upToClass = clazz;
}
/**
* <p>
* Gets the String built by this builder.
* </p>
*
* @return the built string
*/
@Override
public String toString() {
if (this.getObject() == null) {
return this.getStyle().getNullText();
}
Class<?> clazz = this.getObject().getClass();
this.appendFieldsIn(clazz);
while (clazz.getSuperclass() != null && clazz != this.getUpToClass()) {
clazz = clazz.getSuperclass();
this.appendFieldsIn(clazz);
}
return super.toString();
}
}

File diff suppressed because it is too large Load Diff

View 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>

View File

@ -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);
}
}

View 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>

View 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);
}

View 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);
}
}

View 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>

View 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>

View 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>

View 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;
}
}

View 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;
}
}

View 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>

View 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();
}
}

View 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());
}
}

View 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>

View File

@ -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();
}
}
}

View File

@ -0,0 +1,4 @@
/**
* Contains {@link android.app.AndroidAppHelper} with various methods for information about the current app.
*/
package android.app;

View File

@ -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);
}
}

View File

@ -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

View File

@ -0,0 +1,4 @@
/**
* Contains classes that are required for replacing resources.
*/
package android.content.res;

View File

@ -0,0 +1,4 @@
package com.elderdrivers.riru.common;
public interface KeepAll {
}

View File

@ -0,0 +1,4 @@
package com.elderdrivers.riru.common;
public interface KeepMembers {
}

View File

@ -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);
}

View File

@ -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));
}
}
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}

View File

@ -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));
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}

View File

@ -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());
}
}

View File

@ -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()
};
}

View File

@ -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()
};
}

View File

@ -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()
};
}

View File

@ -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) {
}
}

View File

@ -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) {
}
}
}

View File

@ -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) {
}
}

View File

@ -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;
}
}

View File

@ -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) {
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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() {}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}

View File

@ -0,0 +1,4 @@
package de.robv.android.xposed;
/** Marker interface for Xposed modules. Cannot be implemented directly. */
/* package */ interface IXposedMod {}

View File

@ -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();
// }
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
};
}
}

View File

@ -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");
}
}

View File

@ -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

View File

@ -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);
}
}

View File

@ -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();
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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;

View File

@ -0,0 +1,4 @@
/**
* Contains the main classes of the Xposed framework.
*/
package de.robv.android.xposed;

View File

@ -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);
}
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -0,0 +1,4 @@
/**
* Contains file access services provided by the Xposed framework.
*/
package de.robv.android.xposed.services;

7
Core/.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
/.externalNativeBuild
/build
/libs
/obj
/release
/template_override/system/framework/edxposed.dex
*.iml

16
Core/build-module.sh Normal file
View File

@ -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
}

57
Core/build.gradle Normal file
View File

@ -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